list_procedures and function bodies
list_procedures returns every function Ghidra defined, internal and external, with thunks kept distinct, and the dossier body tells you how many bytes each one owns.
You wrote four functions and a main. Ghidra lists 13 procedures, four of them at addresses like EXTERNAL:0x1, and the one you called main is named entry. Each of those facts has a reason in the file, and the list is the place to learn them.
When you finish this section, you can account for all 13 procedures and tell a thunk from its external target. You can also read a function body as inclusive address ranges.
The inventory
"normalized_result": [
{
"address": "EXTERNAL:0x1",
"value": "/usr/lib/libSystem.B.dylib::___stack_chk_fail",
"procedure": {
"external": true,
"thunk": false,
"thunk_target": null
}
},
…
{
"address": "0x100000518",
"value": "_grade",
"procedure": {
"external": false,
"thunk": false,
"thunk_target": null
}
},
…
{
"address": "0x1000005d0",
"value": "entry",
"procedure": {
"external": false,
"thunk": false,
"thunk_target": null
}
},
{
"address": "0x100000740",
"value": "___stack_chk_fail",
"procedure": {
"external": false,
"thunk": true,
"thunk_target": "EXTERNAL:0x1"
}
},
…
],The bridge builds the list once per program from two Ghidra iterators, the internal functions and the external functions. It drops duplicates by entry address. For each function it records external, thunk, and the resolved thunk_target when there is one. The value is the qualified symbol name, which is why the external entries carry their library path. The record's limitation states the rule: "External functions and local thunks are distinct; procedure metadata identifies both and preserves a thunk target when Ghidra resolves one.".
| Address | Name | Kind | Where it comes from |
|---|---|---|---|
EXTERNAL:0x1 to EXTERNAL:0x4 | ___stack_chk_fail, _atoi, _fwrite, _printf | external | four of the six libSystem imports in section 3.2 |
0x100000500 | _percent_change | internal, 24 bytes | ledger.c |
0x100000518 | _grade | internal, 60 bytes | ledger.c |
0x100000554 | _total | internal, 52 bytes | ledger.c |
0x100000588 | _grade_label | internal, 72 bytes | ledger.c |
0x1000005d0 | entry | internal, 368 bytes | main in ledger-main.c, named by LC_MAIN |
0x100000740 to 0x100000764 | the same four names | thunk, 12 bytes each | the __stubs section, 48 bytes |
Thunk: a small function whose only job is to transfer control to another function. The four thunks sit in __stubs, the 48-byte section at 0x100000740 from section 3.2. Each one is a 12-byte stub that reaches the real libSystem function. Ghidra represents that function as an entry in its EXTERNAL block, the last segment in section 3.4. The two imports that are not functions, ___stack_chk_guard and ___stderrp, do not appear here, because they are data.
entry is _main. inspect-macho exports _main at 0x1000005d0, and LC_MAIN points there with entryoff 1488. Ghidra names the entry point entry and keeps _main as a second symbol at the same address. The docs say that a secondary symbol at a function entry, such as an imported _main label, is accepted as a selector, so rea function fixtures/ledger-arm64-O1 _main works.
Function bodies
The inventory has no sizes. The size of a function is in the procedure.body of its analyze_function dossier, and section 4.6 reads the rest of that record.
"procedure": {
"address": "0x100000518",
"name": "_grade",
"classification": {
"external": false,
"thunk": false,
"thunk_target": null,
"provenance": "ghidra-function-manager"
},
"body": {
"available": true,
"provenance": "ghidra-function-body-address-set",
"ranges": [
{
"start": "0x100000518",
"end": "0x100000553"
}
],
"total_bytes": 60,
"span_bytes": 60,
"non_contiguous": false,
"contains_entry": true
},
"signature": "undefined _grade()",
"locals": []
},Function body: the set of addresses Ghidra assigns to one function, reported as ranges with inclusive ends. _grade owns 0x100000518 to 0x100000553, 60 bytes, and _total starts at 0x100000554. The profile names this contract function_body_evidence: "complete-inclusive-ranges-v1". Three other fields in the record use exclusive ends: the segments of binary_overview, the basic_blocks of a dossier, and the memory blocks of list_segments. Read the field name before you add one.
The five internal bodies add up: 24 + 60 + 52 + 72 + 368 is 576, the size of __text. No byte of the code section belongs to two functions, and no byte belongs to none. span_bytes equals total_bytes and non_contiguous is false for all five. When they differ, the function has a gap, and the bytes in the gap are not owned by that function. The research notes a 16-bit case where a body of 503 bytes spans 517.
Selecting a procedure
Every function operation takes a procedure string. The rule for reading it:
A string with a 0x prefix or an address-space prefix is an address. Any other string is tried as a symbol name first. A name that matches more than one function fails with ambiguous and the list of entry addresses. A name that matches nothing fails with not_found, which section 4.6 shows as the MCP error invalid_request.
rea (rea-agents) with the Ghidra provider rea-agents 6.7.0, Ghidra 12.1.4 · 27dc42d · 2026-10-18 · Edition 2026.10