3.3

address-to-file-offset

address-to-file-offset maps one virtual address to the byte in the file that Ghidra loaded there, and the segment load command lets you check the answer by hand.

inspect-macho says _grade is exported at 0x100000518. You want to open ledger-arm64-O1 in a hex viewer and read those bytes, so you need the file offset of that address. One rea command answers that question, and the Mach-O load commands let you compute the same number without rea.

When you finish this section, you can turn an address into a file offset with rea address-to-file-offset. You can do the same arithmetic from the LC_SEGMENT_64 fields, and say when the two can disagree.

The command

the command and its exittextFull log
$ rea address-to-file-offset fixtures/ledger-arm64-O1 0x100000518 --provider ghidra --format json
[exit 0] (…) -> 08-address-to-file-offset.json
Two lines from the command index. The time in parentheses changes on every run and is cut.

The call took 7 to 9 s on the capture Mac. Every one-shot CLI call that needs Ghidra imports the target into a fresh headless project. Section 4.1 shows that cost in detail, and the same question costs under 100 ms inside an open MCP session.

the answer and the rule behind itjsonFull log
"operation": "address_to_file_offset",
"parameters": {
  "address": "0x100000518"
},
"raw_result": {
  "address": "0x100000518",
  "file_offset": 1304
},
"normalized_result": {
  "address": "0x100000518",
  "file_offset": 1304
},
"confidence": "observed",
"authority": "shipped-artifact",
…
"limitations": [
  …
  "Offsets come from Ghidra source-byte mappings, not virtual-address arithmetic. Uninitialized memory, missing mappings and ambiguous mappings fail explicitly.",
  …
],
Cut from the record. The subject, provider, and analysis_profile blocks and five of the six limitations are cut.

The answer is 1304. The record is observed: Ghidra reported which file byte it loaded at that address, and rea passed the number through.

The arithmetic from the load command

File offset: the position of a byte in the file, counted from zero. Virtual address: the position of the same byte in memory after the loader maps the segment that contains it. A segment maps filesize bytes starting at fileoff to vmsize bytes starting at vmaddr, so inside one segment the two positions differ by a constant.

From the previous section, __TEXT has vmaddr 0x100000000 and fileoff 0. The rule for any byte in that segment is offset = address - vmaddr + fileoff. For _grade, that is 0x100000518 - 0x100000000 + 0, which is 0x518, which is 1304.

The section gives a second check. __text has address 0x100000500 and file_offset 1280. The function starts 0x18 bytes, 24 bytes, after the section, so its offset is 1280 + 24, again 1304. Open the fixture at byte 1304 and you find e8 cc 8c 52. That is the little-endian encoding of mov w8, #0x6667, the first instruction of _grade in section 4.3.

MethodInputsResult
Segment rulevmaddr 0x100000000, fileoff 00x100000518 - 0x100000000 + 0 = 1304
Section ruleaddress 0x100000500, file_offset 12801280 + (0x518 - 0x500) = 1304
rea with Ghidrathe addressfile_offset: 1304
Fig. 3.4from 0x100000518 to file offset 1304structure
From 0x100000518 to file offset 1304 Three rows. The __TEXT segment has vmaddr 0x100000000 and fileoff 0, so address minus vmaddr plus fileoff is 0x518, which is 1304. The __text section has address 0x100000500 and file_offset 1280, so 1280 plus 24 is 1304. The rea tool address_to_file_offset asks Ghidra for its one source mapping and answers file_offset 1304, where the bytes e8 cc 8c 52 encode mov w8, #0x6667. WHAT THE FILE SAYS THE OFFSET __TEXT segment vmaddr 0x100000000 · fileoff 0 address - vmaddr + fileoff 0x100000518 - 0x100000000 + 0 = 0x518 __text section address 0x100000500 · file_offset 1280 file_offset + (address - addr) 1280 + (0x518 - 0x500) = 1280 + 24 address_to_file_offset Ghidra source mapping, exactly one file_offset 1304 bytes e8 cc 8c 52 · mov w8, #0x6667
The segment rule, the section rule, and Ghidra's source mapping give the same byte, 1304, and that byte starts the first instruction of _grade. Read each row left to right. Green boxes are load command facts, grey boxes are your arithmetic, the violet box is Ghidra's answer.

The ELF rule has the same shape. For a loadable segment it is offset = address - p_vaddr + p_offset, and for a section it is sh_offset. Lesson 32 does that arithmetic on ledger-x86_64-O1.o. A relocatable object has no program header table, so no segment places it in memory. When section 3.4 shows .text of that object at 0x100000, the number is a placement Ghidra chose, not a fact in the file.

What Ghidra does instead

The bridge does not compute. addressToFileOffset asks Ghidra for the memory block at the address and requires an initialized block. Otherwise it fails with invalid_request and the message "Address has no initialized file-backed memory". It then walks the source mappings of the block. It keeps only a mapping whose FileBytes start at file offset 0 and whose original bytes have the SHA-256 of the admitted target. It requires exactly one such mapping, and it refuses an offset above 2^53 - 1.

The reply carries only address and file_offset. The output schema of rea also allows provider_file_offset, image_base_file_offset, and source_path, and the Ghidra provider at 6.7.0 leaves them out.

Three addresses show the three outcomes. The capture ran only the first row. The other two follow from the bridge code above.

AddressWhere it isOutcome
0x100000518__text, initialized, one FileBytes mappingfile_offset 1304
0x0__PAGEZERO, filesize 0, no file bytesinvalid_request, no initialized file-backed memory
EXTERNAL:0x1Ghidra's external space for imported functionsinvalid_request, no file bytes at all

This is why the limitation says the offsets are not virtual-address arithmetic. Your arithmetic and Ghidra's mapping agree for every byte of __TEXT, __DATA_CONST, and the 1104 used bytes of __LINKEDIT. They disagree where your formula still produces a number and Ghidra refuses. Those places are the 4 GiB of __PAGEZERO, the padding after byte 33872, and the EXTERNAL block of section 4.2.

rea (rea-agents) with the Ghidra provider rea-agents 6.7.0, Ghidra 12.1.4 · 27dc42d · 2026-10-18 · Edition 2026.10