# Build and Inspect

> Build profiles pinned to one semantics, the ELF a build produces, the ProgramImage the loader makes of it, its report, and the program identity a verifier registers.

## Build

From the guest's directory, with no flag but the target:

```sh
cd guests/my-app
cargo build --release --target riscv32imac-unknown-none-elf     # .../release/my-app
cargo build --target riscv32imac-unknown-none-elf               # .../debug/my-app
```

The ELF lands in the guest workspace's one target directory, `guests/target/riscv32imac-unknown-none-elf/`. `guests/.cargo/config.toml` adds two linker arguments that you never type:

- **`-T crates/guest-sdk/link.ld`**, the memory map, which also defines the symbols the startup code and the allocator use.
- **`--no-relax`**. Linker relaxation rewrites instruction sequences and shifts every later address, and the program identity binds those addresses.

There is no `runner`: nothing outside Apogee maps a guest's regions, so `cargo run` has nothing to run it with. You run a guest through the emulator ([Run and profile](https://apogee.gweb3networks.com/docs/launch/run)).

## Profiles

`guests/Cargo.toml` pins both profiles to one semantics. They differ only in optimization and in dependencies' debug assertions:

| | dev | release |
| --- | --- | --- |
| `opt-level` | 0 | 3 |
| `overflow-checks` | on | **on** |
| `debug-assertions` | on | on in the guest crate, off in its dependencies |
| `panic`, `codegen-units`, `debug`, `incremental` | `abort`, 1, off, off | the same |

Cargo's default release profile turns overflow checks off, and in a guest that is not a performance setting. It changes the statement: `u32::MAX + 1` would commit `00000000` and exit 0 where the dev build panics with exit 101. So the workspace keeps the checks on in both profiles. A dependency's debug assertion checks that crate's own invariant, and a correct dependency computes the same without it, so release turns those off and keeps the cycles: 6.8% of the stateless Ethereum guest's run.

**Prove the release build.** Every executed instruction is a proved row, `opt-level = 3` removes a quarter to over half of a guest's image, and each family's code must fit its decoded table: the Ethereum guest's debug image needs `2^22`-row tables, its release image `2^20`. The identity you publish is the release image's.

## Reproducibility

Two clean builds on one machine produce identical ELFs. Builds on two machines generally do not: the ELF embeds absolute paths in panic-location strings, the toolchain's `core` sources, `crates/guest-sdk` and the cargo registry, while the guest's own files appear relative to `guests/`. A build elsewhere is another image with another identity.

So what you register and hand on is **a build's ELF, not a recipe**. Keep the ELF you proved, and let anyone who wants to check the identity recompute it from that ELF, the parameters and the ceremony file.

## Export the image

```sh
cargo run -p artifact-dump -- guests/target/riscv32imac-unknown-none-elf/release/my-app --out artifacts
```

This writes `artifacts/my-app.img`, the loaded `ProgramImage` in its wire form (`postcard`, no header), and `artifacts/my-app.img.txt`, a report rendered from the image read back through the validating reader. It prints the entry point, the segment and instruction counts, and the artifact's size and SHA-256. If the read-back differs, or the loader refuses the ELF, it writes nothing.

The `.img` is the program's static description, for keeping and diffing; nothing downstream needs it, since the setup and the tools take the ELF. Its SHA-256 pins bytes. It is **not** the program identity.

## Read the report

| Section | Shows |
| --- | --- |
| `entry and memory` | the entry, `_start` at `0x00010000`; the RAM window; `slot_base` and the slot span |
| `segments` | address, end, `mem_len`, file bytes, zero fill and instruction count of each segment: `.text`, `.rodata` if any, and one writable segment to `0x80000000` for `.data`, `.bss`, heap and stack |
| `instruction stream` | four- and two-byte instructions, mid-instruction slots and `not code` slots, summing to the slot count |
| `symbols` | names by address from the ELF's symbol table, which the artifact does not carry |
| `listing` | per instruction: address, length, the bytes in memory, the expanded 32-bit word, the symbol |

A compressed instruction keeps its address and its two bytes; `len` alone says whether the next pc is `pc + 2` or `pc + 4`. For mnemonics, use the `tables` view below or the pinned disassembler:

```sh
"$(rustc --print sysroot)"/lib/rustlib/*/bin/llvm-objdump \
    --disassemble --no-print-imm-hex -M no-aliases <elf>
```

### The `not code` halfwords

A report may show a line such as `---- not code: 0x00010f9a .. 0x00010f9c, 1 halfword ----`. That is ordinary compiler output. LLVM proved a `match`'s default arm unreachable, rustc lowered the unreachable block to `unimp`, and with the C extension that is `c.unimp`, the all-zero halfword, RVC's defined-illegal encoding. The loader records it as a non-instruction and moves on. No pc reaches it; one that did would stop the run with `NotAnInstruction`.

## What the VM will prove

```sh
cargo run --release -p artifact-dump -- tables <elf> --ptau assets/ptau/ppot_0080_24.ptau
```

This prints, at the default parameters, the `VmConfig` the image derives: each family's height, its live rows and decoded columns. Then it prints each instruction's pc, `next_pc`, family, mnemonic and fields. With `--ptau` and the ceremony file it also prints the **program identity**, the value a verifier registers. The [Quickstart](https://apogee.gweb3networks.com/docs/launch/quickstart#inspect) shows a real one.

The identity is one field element. It binds every instruction with its pc, length, operands and kind, every file-backed byte of the image (`.text`, `.rodata`, `.data`), the entry point, the family set, every height, the code-size ceiling and the code version. It does not bind the symbol table, `.bss`, or anything an execution chooses. One ELF at two settings of the heights has two identities.

Derivation refuses, naming the pc or the size:

| Refusal | Cause |
| --- | --- |
| `Not all opcodes supported: pc=…` | a word outside RV32IMA anywhere in executable code, such as a CSR access in assembly |
| `TableTooShort` | code past a family's reach, `pc ≤ 2h − 4`: 1.9375 MiB of code at `2^20` and 7.9375 MiB at `2^22` |
| `ProgramTooLarge` | the image is past `bytecode_size_words`, 4 MiB by default |
| `ImageOutsideWindow` | a file-backed byte lies past RAM window 0 at the chosen window height |
| `UnknownDelegation` | the image declares a delegation number no family answers |

## Check a build

Build into a fresh target directory, export again, and compare:

```sh
cd guests/my-app
CARGO_TARGET_DIR=/tmp/fresh cargo build --release --target riscv32imac-unknown-none-elf
cd ../..
cargo run -p artifact-dump -- /tmp/fresh/riscv32imac-unknown-none-elf/release/my-app --out /tmp/again
cmp artifacts/my-app.img /tmp/again/my-app.img
diff artifacts/my-app.img.txt /tmp/again/my-app.img.txt     # differs only in the `source ELF` line
```
