[GH-ISSUE #8200] [Design] Preserve call-site and indirect-call context in call-path workflows #6483

Open
opened 2026-11-01 21:11:31 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @morluto on GitHub (Oct 30, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/8200

Before submitting

  • I searched open and closed issues and pull requests for a related discussion. #8197 covers tail transfers in UI traces; #4247 covers multiple path goals.
  • I understand the contribution scope must be reviewed before integration; open questions are welcome here.
  • This discussion does not include credentials, private samples, or sensitive target data.

Decision needed

How should a call-path result communicate whether an adjacent edge came from a direct machine call, a resolved computed call, or an untyped provider relationship? Can an agent recover the supporting call site without paying an unbounded per-node query cost?

Context and evidence

On a self-built and unexecuted arm64 Mach-O (SHA-256 ec8ac17e086f0ee999b022f2df95eddf2486ecbdf903d8502601744134b9ab79), the source contains a direct call to _audit_add and a separate call through a mutable function pointer initially pointing at _audit_add. otool shows bl at 0x1000003f0 and blr at 0x1000003d8, respectively.

In the same real REA/Ghidra 12.1.4 MCP session:

  • trace_call_path from _audit_direct to _audit_add and from _audit_dispatch to _audit_add both returned goal_status: "reached" with the same edge shape: source_address, target_address, and discovery_depth. Neither edge supplies its call-site address or whether the call was computed.
  • resolve_native_call_targets at the two separately known sites returned direct and resolved-indirect respectively. trace_native_ui_action with the native indirect-function seed returned the latter as an indirect_call edge with the call site and provider ReferenceManager evidence.
  • The live trace_call_path description says it returns “direct-call edges”; that can be read as a machine-level direct call even though a provider-resolved computed call is also included. “Direct” might instead intend an adjacent function relationship, so this is a contract/UX question rather than a claim the traversal result violates its implementation.

An independent function-pointer-argument fixture, where Ghidra could not resolve a target, returned goal_status: "unknown", partial relationship coverage, and a residual unknown; trace_native_ui_action retained the unresolved call site. REA/Kuna likewise reported the call path unknown with partial coverage, while its call-target tool was unavailable. These are static observations: the mutable pointer's initial value or a reported candidate is not proof of runtime execution or an exhaustive target set.

Expected interaction with targets

Read existing local binaries and static provider results; do not execute or modify target code.

Boundary details (optional)

Preserve provider/source identity and distinguish adjacent graph edges from machine-direct calls, computed or ambiguous calls, and unknown classifications. A provider that exposes only untyped relationships must remain explicitly unknown rather than being labeled direct. Keep observed call sites and incomplete coverage bound to their Evidence; do not imply dynamic reachability.

Options and tradeoffs

Clarify the existing description and provide a bounded way to request supporting call-site/type evidence, or enrich a selected path with already-available typed facts. Compare with the existing trace_native_ui_action and procedure_references contracts; account for extra provider calls, output size, budgets and pagination instead of introducing an unconditional N+1 traversal.

Verification available so far

I have a prototype or observed results.

Success criteria and open questions

Test adjacent direct, resolved-indirect, unresolved, multiple-site and untyped-provider edges; preserve failure/coverage semantics and demonstrate a bounded recovery path. Validate both CLI/MCP output and distinguish static observations from runtime behavior.

References

Related: #8190 (Kuna outgoing reference semantics), #8197 (tail transfers), #4247 (multiple goals). REA d48e472fa plus unrelated Kuna baseline changes in #8187; macOS arm64.

Originally created by @morluto on GitHub (Oct 30, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8200 ### Before submitting - [x] I searched open and closed issues and pull requests for a related discussion. #8197 covers tail transfers in UI traces; #4247 covers multiple path goals. - [x] I understand the contribution scope must be reviewed before integration; open questions are welcome here. - [x] This discussion does not include credentials, private samples, or sensitive target data. ### Decision needed How should a call-path result communicate whether an adjacent edge came from a direct machine call, a resolved computed call, or an untyped provider relationship? Can an agent recover the supporting call site without paying an unbounded per-node query cost? ### Context and evidence On a self-built and unexecuted arm64 Mach-O (SHA-256 `ec8ac17e086f0ee999b022f2df95eddf2486ecbdf903d8502601744134b9ab79`), the source contains a direct call to `_audit_add` and a separate call through a mutable function pointer initially pointing at `_audit_add`. `otool` shows `bl` at `0x1000003f0` and `blr` at `0x1000003d8`, respectively. In the same real REA/Ghidra 12.1.4 MCP session: - `trace_call_path` from `_audit_direct` to `_audit_add` and from `_audit_dispatch` to `_audit_add` both returned `goal_status: "reached"` with the same edge shape: `source_address`, `target_address`, and `discovery_depth`. Neither edge supplies its call-site address or whether the call was computed. - `resolve_native_call_targets` at the two separately known sites returned `direct` and `resolved-indirect` respectively. `trace_native_ui_action` with the native indirect-function seed returned the latter as an `indirect_call` edge with the call site and provider ReferenceManager evidence. - The live `trace_call_path` description says it returns “direct-call edges”; that can be read as a machine-level direct call even though a provider-resolved computed call is also included. “Direct” might instead intend an adjacent function relationship, so this is a contract/UX question rather than a claim the traversal result violates its implementation. An independent function-pointer-argument fixture, where Ghidra could not resolve a target, returned `goal_status: "unknown"`, partial relationship coverage, and a residual unknown; `trace_native_ui_action` retained the unresolved call site. REA/Kuna likewise reported the call path unknown with partial coverage, while its call-target tool was unavailable. These are static observations: the mutable pointer's initial value or a reported candidate is not proof of runtime execution or an exhaustive target set. ### Expected interaction with targets Read existing local binaries and static provider results; do not execute or modify target code. ### Boundary details (optional) Preserve provider/source identity and distinguish adjacent graph edges from machine-direct calls, computed or ambiguous calls, and unknown classifications. A provider that exposes only untyped relationships must remain explicitly unknown rather than being labeled direct. Keep observed call sites and incomplete coverage bound to their Evidence; do not imply dynamic reachability. ### Options and tradeoffs Clarify the existing description and provide a bounded way to request supporting call-site/type evidence, or enrich a selected path with already-available typed facts. Compare with the existing `trace_native_ui_action` and `procedure_references` contracts; account for extra provider calls, output size, budgets and pagination instead of introducing an unconditional N+1 traversal. ### Verification available so far I have a prototype or observed results. ### Success criteria and open questions Test adjacent direct, resolved-indirect, unresolved, multiple-site and untyped-provider edges; preserve failure/coverage semantics and demonstrate a bounded recovery path. Validate both CLI/MCP output and distinguish static observations from runtime behavior. ### References Related: #8190 (Kuna outgoing reference semantics), #8197 (tail transfers), #4247 (multiple goals). REA `d48e472fa` plus unrelated Kuna baseline changes in #8187; macOS arm64.
Author
Owner

@morluto commented on GitHub (Oct 30, 2026):

A multiple-site control exercises an existing bounded recovery path. I built an unexecuted arm64 Mach-O whose one caller makes a direct bl and a separate computed blr through a mutable function pointer initially referencing the same callee. In real REA/Ghidra 12.1.4 MCP, trace_call_path and get_call_graph each represented one caller-to-callee adjacency. A single procedure_references({direction:"outgoing"}) on the caller returned two distinct source sites: 0x1000003d8 as UNCONDITIONAL_CALL and 0x1000003ec as COMPUTED_CALL, both to the same observed callee. Independently querying resolve_native_call_targets for those known sites returned direct and resolved-indirect respectively.

trace_native_ui_action with the caller as a native seed also returned two distinct edges with those sites and classifications. It correctly reported UI archives unsupported for this non-bundle fixture, static call coverage partial, and cross-function value flow unsupported. Thus there is a useful existing one-caller recovery route; it does not establish that this UI-oriented tool is the right generic call-path UX or that static computed-call resolution proves runtime targets.

REA/Kuna 1.986 returned the same adjacency with goal_status: reached, but its relationship coverage was partial and truncated: true because the indirect target was not fully resolved. Its UI trace reported one inferred provider-call edge; typed reference and call-target tools returned capability unavailability. Direct Kuna xrefs --from reported the direct call, while its output did not report the indirect instruction as a call to the callee on this fixture. The file digest was unchanged. This is one call-site pair, not a general count, benchmark, or autonomous-agent selection test.

<!-- gh-comment-id:6534588441 --> @morluto commented on GitHub (Oct 30, 2026): A multiple-site control exercises an existing bounded recovery path. I built an unexecuted arm64 Mach-O whose one caller makes a direct `bl` and a separate computed `blr` through a mutable function pointer initially referencing the same callee. In real REA/Ghidra 12.1.4 MCP, `trace_call_path` and `get_call_graph` each represented one caller-to-callee adjacency. A single `procedure_references({direction:"outgoing"})` on the caller returned two distinct source sites: `0x1000003d8` as `UNCONDITIONAL_CALL` and `0x1000003ec` as `COMPUTED_CALL`, both to the same observed callee. Independently querying `resolve_native_call_targets` for those known sites returned `direct` and `resolved-indirect` respectively. `trace_native_ui_action` with the caller as a native seed also returned two distinct edges with those sites and classifications. It correctly reported UI archives unsupported for this non-bundle fixture, static call coverage partial, and cross-function value flow unsupported. Thus there is a useful existing one-caller recovery route; it does not establish that this UI-oriented tool is the right generic call-path UX or that static computed-call resolution proves runtime targets. REA/Kuna 1.986 returned the same adjacency with `goal_status: reached`, but its relationship coverage was partial and `truncated: true` because the indirect target was not fully resolved. Its UI trace reported one inferred provider-call edge; typed reference and call-target tools returned capability unavailability. Direct Kuna `xrefs --from` reported the direct call, while its output did not report the indirect instruction as a call to the callee on this fixture. The file digest was unchanged. This is one call-site pair, not a general count, benchmark, or autonomous-agent selection test.
Author
Owner

@morluto commented on GitHub (Oct 30, 2026):

A second architecture and negative controls confirm the task-level distinction. In a self-built, unexecuted x86-64 ELF, three functions call through (1) a writable slot initially containing callee, (2) a zero-initialized slot, and (3) a register. Real REA/Ghidra 12.1.4 trace_call_path from the first function to callee returned reached and an untyped adjacent edge. At its known instruction, resolve_native_call_targets returned resolved-indirect based on a Ghidra computed-call reference. The other two paths returned unknown, partial relationship coverage and residual unknowns, consistent with unresolved computed call sites. The static candidate does not prove runtime behavior of the writable pointer.

REA/Kuna 1.986 returned unknown, partial coverage and a residual unknown for all three paths, including the populated-slot case. Its typed call-target query was unavailable. Direct Kuna xrefs --from returned call edges to the two slot addresses with no target function, and no xrefs for the register call. This supports retaining unresolved state and keeping a storage-address reference distinct from a recovered function target; it is not a general accuracy comparison. Artifact digest stayed unchanged.

<!-- gh-comment-id:6535306076 --> @morluto commented on GitHub (Oct 30, 2026): A second architecture and negative controls confirm the task-level distinction. In a self-built, unexecuted x86-64 ELF, three functions call through (1) a writable slot initially containing `callee`, (2) a zero-initialized slot, and (3) a register. Real REA/Ghidra 12.1.4 `trace_call_path` from the first function to `callee` returned `reached` and an untyped adjacent edge. At its known instruction, `resolve_native_call_targets` returned `resolved-indirect` based on a Ghidra computed-call reference. The other two paths returned `unknown`, partial relationship coverage and residual unknowns, consistent with unresolved computed call sites. The static candidate does not prove runtime behavior of the writable pointer. REA/Kuna 1.986 returned `unknown`, partial coverage and a residual unknown for all three paths, including the populated-slot case. Its typed call-target query was unavailable. Direct Kuna `xrefs --from` returned `call` edges to the two *slot addresses* with no target function, and no xrefs for the register call. This supports retaining unresolved state and keeping a storage-address reference distinct from a recovered function target; it is not a general accuracy comparison. Artifact digest stayed unchanged.
Author
Owner

@morluto commented on GitHub (Oct 31, 2026):

Looping in @gensokyo-dev @kaoru0822-kitauji @N0zoM1z0 for visibility on the Ghidra, Kuna, and REA tool-design investigation.

<!-- gh-comment-id:6538795157 --> @morluto commented on GitHub (Oct 31, 2026): Looping in @gensokyo-dev @kaoru0822-kitauji @N0zoM1z0 for visibility on the Ghidra, Kuna, and REA tool-design investigation.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#6483
No description provided.