[GH-ISSUE #3624] [Feature] Native C++ semantic reconstruction: RTTI, vtables, class hierarchies, and virtual dispatch #1350

Open
opened 2026-10-16 21:04:33 +00:00 by bsakaguchi · 4 comments
Owner

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

Type: Feature Domain: Game & Engine RE Status: Proposed Ecosystem: REA

Summary

Modern native applications, game engines, operating system utilities, browser engines, and malware are predominantly authored in C++. However, when compiled into native executables and shared libraries (PE/COFF on Windows, ELF on Linux/Android, Mach-O on Apple), the high-level object-oriented architecture is flattened into raw assembly instructions, unannotated memory offsets, and indirect virtual call sites.

Currently, REA's native analysis pipeline (via Ghidra, Hopper, and Rizin) treats native code at the level of isolated functions, basic blocks, and strings. Outside of specialized Swift vtable decoding on Apple targets (src/native/AppleSwiftVtables.ts), REA does not recover C++ object-oriented semantics. Functions remain anonymous subroutines, virtual calls (call qword ptr [rax + 0x20]) remain unresolved indirect branches, and class inheritance structures are invisible.

This feature proposal introduces Native C++ Semantic Reconstruction to REA: extracting Run-Time Type Information (RTTI) and virtual function tables (vtables) across both major compiler ABIs (MSVC and Itanium C++ ABI), reconstructing class inheritance hierarchies and memory layouts, resolving virtual dispatch call sites, and hydrating Ghidra/Rizin with qualified class structures.


Technical Capabilities & Architecture

                                    +-----------------------------------------+
                                    | Input: Native Binary (PE, ELF, Mach-O)  |
                                    +--------------------+--------------------+
                                                         |
                         +-------------------------------+-------------------------------+
                         |                                                               |
                         v                                                               v
           +---------------------------+                                   +---------------------------+
           | MSVC RTTI & Vftable       |                                   | Itanium C++ ABI RTTI      |
           | Engine (PE/COFF x86/x64)  |                                   | Engine (ELF, Mach-O)      |
           +-------------+-------------+                                   +-------------+-------------+
                         |                                                               |
                         +-------------------------------+-------------------------------+
                         |                                                               |
                         v                                                               v
           +---------------------------+                                   +---------------------------+
           | Class Hierarchy & Memory  |                                   | Virtual Dispatch & Thunk  |
           | Layout Reconstruction     |                                   | Resolution (Indirect Call)|
           +-------------+-------------+                                   +-------------+-------------+
                         |                                                               |
                         +-------------------------------+-------------------------------+
                                                         |
                                                         v
                                     +---------------------------------------+
                                     | Provider Hydration (Ghidra / Rizin)   |
                                     | & Structured Evidence (Classes/VTable)|
                                     +---------------------------------------+

1. MSVC C++ RTTI & Vftable Reconstruction (Windows PE/COFF)

  • Complete Object Locator (RTTICompleteObjectLocator):
    • Detect vtable references pointing to RTTICompleteObjectLocator structures.
    • Support both 32-bit absolute addresses and 64-bit Image-Base Relative (IBO/RVA) layouts (signature == 1).
    • Extract offset-to-top (offset), constructor displacement (cdOffset), and type descriptor pointers.
  • TypeDescriptor Parsing:
    • Extract mangled class names (e.g. .?AVPlayerController@@, .?AUInterface@@) and invoke demangling to recover clean C++ namespaces and class names.
  • Class Hierarchy Descriptor (RTTIClassHierarchyDescriptor):
    • Parse base class arrays (RTTIBaseClassArray) and individual RTTIBaseClassDescriptor records.
    • Reconstruct single inheritance, multiple inheritance, and virtual inheritance trees.
  • Virtual Base Tables (vbtable): Identify and map MSVC vbtable structures used for resolving virtual base class offsets.

2. Itanium C++ ABI RTTI & Vtable Reconstruction (Linux/Android ELF & macOS Mach-O)

  • Vtable Structure Parsing:
    • Locate vtables (via _ZTV symbols or heuristic pointer tables preceded by an offset-to-top and a typeinfo pointer).
    • Parse prefix slots: offset-to-top (offset of the subobject within the complete object) and RTTI pointer.
  • Typeinfo (type_info) Hierarchy Traversal:
    • Parse abi::__class_type_info (root classes with no bases).
    • Parse abi::__si_class_type_info (single public, non-virtual inheritance, extracting parent typeinfo pointer).
    • Parse abi::__vmi_class_type_info (multiple and virtual inheritance, parsing base class flags, offsets, and non-virtual/virtual flags).
  • VTT & Construction Vtables: Parse Virtual Table Tables (_ZTT) and construction vtables (_ZTC) used during base class construction in complex inheritance hierarchies.

3. Virtual Dispatch & Indirect Call Resolution

  • Constructor & Vftable Pointer Binding:
    • Track this pointer initialization in constructors and destructors (this->__vftable = &ClassName::vftable).
    • Associate allocated object instances with their concrete runtime class.
  • Indirect Call Target Inference:
    • Analyze indirect virtual calls (call [rax + slot_offset]), resolving call targets against the vtable slot index of candidate classes.
    • Report direct call target candidates rather than leaving calls as unresolvable indirect branches.
  • Adjustor Thunks Identification:
    • Detect compiler-generated adjustor thunks (e.g. sub rcx, 0x10; jmp RealMethod) used in multiple inheritance, associating them with the primary virtual implementation.

4. Class Memory Layout & Special Member Function Discovery

  • Member Function Categorization:
    • Classify member functions: default constructors, copy/move constructors, virtual destructors, and scalar/vector deleting destructors (operator delete wrappers).
  • Object Layout Synthesis:
    • Infer struct/class memory layouts: placement of primary vptr, virtual base table pointers (vbptr), base class subobjects, and member field offsets inferred from constructor initialization lists.
  • Class Dependency Graph: Build a directed acyclic graph (DAG) representing the complete object-oriented class architecture of the binary.

5. Provider Hydration (Ghidra & Rizin)

  • Type Management: Inject recovered class structures, vtable structs (ClassName_vtbl), and interface types directly into Ghidra and Rizin program databases.
  • Symbol Renaming: Automatically rename anonymous virtual functions (e.g. FUN_140010200 -> PlayerController::OnDamageTaken).
  • MCP Surface: Expose reconstructed class hierarchies and method resolutions via inspect_native_classes and enrich inspect_native_functions dossiers.

Acceptance Criteria

  1. Introduce inspect_native_classes in REA CLI and MCP contracts, accepting PE, ELF, and Mach-O binaries.
  2. Reconstruct complete class inheritance trees and demangled class names from MSVC x86 and x64 test fixtures with RTTI enabled.
  3. Reconstruct single and multiple inheritance graphs from Itanium C++ ABI (GCC / Clang) ELF and Mach-O test fixtures.
  4. Accurately resolve virtual function vtable slot indices and label virtual methods in headless Ghidra analysis.
  5. Detect adjustor thunks and associate them with their primary virtual function targets.
  6. Safely handle binaries with stripped RTTI or malformed metadata, reporting partial observations without unhandled crashes.

  • #743 [Feature] Recover .NET NativeAOT metadata in Ghidra workflows
  • #492 Inspect recovered native data types and layouts
  • #3607 [Feature] Comprehensive Unity extraction and reverse engineering
  • #3620 [Feature] Shader and GPU Bytecode Reverse Engineering
Originally created by @matbrgz on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3624 [![Type: Feature](https://img.shields.io/badge/type-feature-blue.svg?style=flat-square)](https://github.com/morluto/rea) [![Domain: Game & Engine RE](https://img.shields.io/badge/domain-game_&_engine_RE-green.svg?style=flat-square)](https://github.com/morluto/rea) [![Status: Proposed](https://img.shields.io/badge/status-proposed-blue.svg?style=flat-square)](https://github.com/morluto/rea) [![Ecosystem: REA](https://img.shields.io/badge/ecosystem-morluto%2Frea-8A2BE2.svg?style=flat-square)](https://github.com/morluto/rea) ## Summary Modern native applications, game engines, operating system utilities, browser engines, and malware are predominantly authored in C++. However, when compiled into native executables and shared libraries (PE/COFF on Windows, ELF on Linux/Android, Mach-O on Apple), the high-level object-oriented architecture is flattened into raw assembly instructions, unannotated memory offsets, and indirect virtual call sites. Currently, REA's native analysis pipeline (via Ghidra, Hopper, and Rizin) treats native code at the level of isolated functions, basic blocks, and strings. Outside of specialized Swift vtable decoding on Apple targets (`src/native/AppleSwiftVtables.ts`), REA does not recover C++ object-oriented semantics. Functions remain anonymous subroutines, virtual calls (`call qword ptr [rax + 0x20]`) remain unresolved indirect branches, and class inheritance structures are invisible. This feature proposal introduces **Native C++ Semantic Reconstruction** to REA: extracting Run-Time Type Information (RTTI) and virtual function tables (vtables) across both major compiler ABIs (**MSVC** and **Itanium C++ ABI**), reconstructing class inheritance hierarchies and memory layouts, resolving virtual dispatch call sites, and hydrating Ghidra/Rizin with qualified class structures. --- ## Technical Capabilities & Architecture ``` +-----------------------------------------+ | Input: Native Binary (PE, ELF, Mach-O) | +--------------------+--------------------+ | +-------------------------------+-------------------------------+ | | v v +---------------------------+ +---------------------------+ | MSVC RTTI & Vftable | | Itanium C++ ABI RTTI | | Engine (PE/COFF x86/x64) | | Engine (ELF, Mach-O) | +-------------+-------------+ +-------------+-------------+ | | +-------------------------------+-------------------------------+ | | v v +---------------------------+ +---------------------------+ | Class Hierarchy & Memory | | Virtual Dispatch & Thunk | | Layout Reconstruction | | Resolution (Indirect Call)| +-------------+-------------+ +-------------+-------------+ | | +-------------------------------+-------------------------------+ | v +---------------------------------------+ | Provider Hydration (Ghidra / Rizin) | | & Structured Evidence (Classes/VTable)| +---------------------------------------+ ``` ### 1. MSVC C++ RTTI & Vftable Reconstruction (Windows PE/COFF) - **Complete Object Locator (`RTTICompleteObjectLocator`):** - Detect vtable references pointing to `RTTICompleteObjectLocator` structures. - Support both 32-bit absolute addresses and 64-bit Image-Base Relative (IBO/RVA) layouts (`signature == 1`). - Extract offset-to-top (`offset`), constructor displacement (`cdOffset`), and type descriptor pointers. - **TypeDescriptor Parsing:** - Extract mangled class names (e.g. `.?AVPlayerController@@`, `.?AUInterface@@`) and invoke demangling to recover clean C++ namespaces and class names. - **Class Hierarchy Descriptor (`RTTIClassHierarchyDescriptor`):** - Parse base class arrays (`RTTIBaseClassArray`) and individual `RTTIBaseClassDescriptor` records. - Reconstruct single inheritance, multiple inheritance, and virtual inheritance trees. - **Virtual Base Tables (`vbtable`):** Identify and map MSVC `vbtable` structures used for resolving virtual base class offsets. ### 2. Itanium C++ ABI RTTI & Vtable Reconstruction (Linux/Android ELF & macOS Mach-O) - **Vtable Structure Parsing:** - Locate vtables (via `_ZTV` symbols or heuristic pointer tables preceded by an offset-to-top and a typeinfo pointer). - Parse prefix slots: `offset-to-top` (offset of the subobject within the complete object) and `RTTI pointer`. - **Typeinfo (`type_info`) Hierarchy Traversal:** - Parse `abi::__class_type_info` (root classes with no bases). - Parse `abi::__si_class_type_info` (single public, non-virtual inheritance, extracting parent typeinfo pointer). - Parse `abi::__vmi_class_type_info` (multiple and virtual inheritance, parsing base class flags, offsets, and non-virtual/virtual flags). - **VTT & Construction Vtables:** Parse Virtual Table Tables (`_ZTT`) and construction vtables (`_ZTC`) used during base class construction in complex inheritance hierarchies. ### 3. Virtual Dispatch & Indirect Call Resolution - **Constructor & Vftable Pointer Binding:** - Track `this` pointer initialization in constructors and destructors (`this->__vftable = &ClassName::vftable`). - Associate allocated object instances with their concrete runtime class. - **Indirect Call Target Inference:** - Analyze indirect virtual calls (`call [rax + slot_offset]`), resolving call targets against the vtable slot index of candidate classes. - Report direct call target candidates rather than leaving calls as unresolvable indirect branches. - **Adjustor Thunks Identification:** - Detect compiler-generated adjustor thunks (e.g. `sub rcx, 0x10; jmp RealMethod`) used in multiple inheritance, associating them with the primary virtual implementation. ### 4. Class Memory Layout & Special Member Function Discovery - **Member Function Categorization:** - Classify member functions: default constructors, copy/move constructors, virtual destructors, and scalar/vector deleting destructors (`operator delete` wrappers). - **Object Layout Synthesis:** - Infer struct/class memory layouts: placement of primary `vptr`, virtual base table pointers (`vbptr`), base class subobjects, and member field offsets inferred from constructor initialization lists. - **Class Dependency Graph:** Build a directed acyclic graph (DAG) representing the complete object-oriented class architecture of the binary. ### 5. Provider Hydration (Ghidra & Rizin) - **Type Management:** Inject recovered class structures, vtable structs (`ClassName_vtbl`), and interface types directly into Ghidra and Rizin program databases. - **Symbol Renaming:** Automatically rename anonymous virtual functions (e.g. `FUN_140010200` -> `PlayerController::OnDamageTaken`). - **MCP Surface:** Expose reconstructed class hierarchies and method resolutions via `inspect_native_classes` and enrich `inspect_native_functions` dossiers. --- ## Acceptance Criteria 1. Introduce `inspect_native_classes` in REA CLI and MCP contracts, accepting PE, ELF, and Mach-O binaries. 2. Reconstruct complete class inheritance trees and demangled class names from MSVC x86 and x64 test fixtures with RTTI enabled. 3. Reconstruct single and multiple inheritance graphs from Itanium C++ ABI (GCC / Clang) ELF and Mach-O test fixtures. 4. Accurately resolve virtual function vtable slot indices and label virtual methods in headless Ghidra analysis. 5. Detect adjustor thunks and associate them with their primary virtual function targets. 6. Safely handle binaries with stripped RTTI or malformed metadata, reporting partial observations without unhandled crashes. --- ## Related - #743 [Feature] Recover .NET NativeAOT metadata in Ghidra workflows - #492 Inspect recovered native data types and layouts - #3607 [Feature] Comprehensive Unity extraction and reverse engineering - #3620 [Feature] Shader and GPU Bytecode Reverse Engineering
Author
Owner

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

Real-provider control for the analyst question “what might this C++ virtual call reach?” I compiled, but did not run, an arm64 Mach-O with a Base virtual method, two derived implementations (Doubler and Offsetter), a shared audit_dispatch(Base *, int) and one caller constructing each derived object. The selected source was built with Clang -O1 -g -fno-inline -fno-optimize-sibling-calls; this is a small symbolized single-inheritance fixture, not a general C++ corpus.

On REA e6d0618979f0df3f1d5db218b0fc7b5e6a9cf3f9 using real Ghidra 12.1.4 and Kuna 1.986 through MCP:

  • Both procedure inventories retained the demangled implementation and constructor names on this symbolized input, and both showed the caller constructing a Doubler before calling the shared dispatch function. This is a useful existing observation, not proof that stripped C++ functions are named or that a recovered name is a class hierarchy.
  • The shared dispatch contained an indirect blr at 0x10000055c. Ghidra procedure_references retained it as an unresolved site, resolve_native_call_targets returned unresolved, and trace_native_values retained a call-target unknown. Both providers' trace_call_path results from the two callers to their corresponding implementation were unknown with partial coverage. Kuna's typed call-reference and call-target operations were unavailable. These are appropriate limits for the generic Base * dispatch, not proof that no implementation can run.
  • A bounded manual Ghidra-backed evidence chain was possible: the constructor pseudocode and outgoing data reference pointed to a class-specific vtable slot; read_bytes of the selected slot returned the corresponding method address, and an incoming xref from each method included that slot. The two slots and methods remained separate. This can motivate a provenance-bound candidate for a particular constructor/call context, but these separate facts are not an automatic object-flow proof or runtime dispatch observation.
  • The slot's original Mach-O file bytes and Ghidra initialized-memory bytes differed on both examples. The retained inspect_native_load_image relocation page reported an APPLIED relocation at each slot with both byte sequences; independent file reads agreed with the original bytes. A future candidate workflow should preserve source versus loaded bytes, pointer fixups, address space, selection and uncertainty rather than decode an unqualified raw pointer.

This narrows the blanket statement that every C++ function is anonymous: the current inventory can retain names on this symbolized sample, and useful low-level primitives already exist. It does not establish RTTI parsing, inheritance completeness, cross-function object flow, stripped-binary support, or safe provider hydration. No additional tool or mutation authority follows from this one case. Both sessions closed, and the target SHA-256 was unchanged.

<!-- gh-comment-id:6541173935 --> @morluto commented on GitHub (Oct 31, 2026): Real-provider control for the analyst question “what might this C++ virtual call reach?” I compiled, but did not run, an arm64 Mach-O with a `Base` virtual method, two derived implementations (`Doubler` and `Offsetter`), a shared `audit_dispatch(Base *, int)` and one caller constructing each derived object. The selected source was built with Clang `-O1 -g -fno-inline -fno-optimize-sibling-calls`; this is a small symbolized single-inheritance fixture, not a general C++ corpus. On REA `e6d0618979f0df3f1d5db218b0fc7b5e6a9cf3f9` using real Ghidra 12.1.4 and Kuna 1.986 through MCP: - Both procedure inventories retained the demangled implementation and constructor names on this symbolized input, and both showed the caller constructing a `Doubler` before calling the shared dispatch function. This is a useful existing observation, not proof that stripped C++ functions are named or that a recovered name is a class hierarchy. - The shared dispatch contained an indirect `blr` at `0x10000055c`. Ghidra `procedure_references` retained it as an unresolved site, `resolve_native_call_targets` returned `unresolved`, and `trace_native_values` retained a call-target unknown. Both providers' `trace_call_path` results from the two callers to their corresponding implementation were `unknown` with partial coverage. Kuna's typed call-reference and call-target operations were unavailable. These are appropriate limits for the generic `Base *` dispatch, not proof that no implementation can run. - A bounded manual Ghidra-backed evidence chain was possible: the constructor pseudocode and outgoing data reference pointed to a class-specific vtable slot; `read_bytes` of the selected slot returned the corresponding method address, and an incoming xref from each method included that slot. The two slots and methods remained separate. This can motivate a provenance-bound **candidate** for a particular constructor/call context, but these separate facts are not an automatic object-flow proof or runtime dispatch observation. - The slot's original Mach-O file bytes and Ghidra initialized-memory bytes differed on both examples. The retained `inspect_native_load_image` relocation page reported an `APPLIED` relocation at each slot with both byte sequences; independent file reads agreed with the original bytes. A future candidate workflow should preserve source versus loaded bytes, pointer fixups, address space, selection and uncertainty rather than decode an unqualified raw pointer. This narrows the blanket statement that every C++ function is anonymous: the current inventory can retain names on this symbolized sample, and useful low-level primitives already exist. It does not establish RTTI parsing, inheritance completeness, cross-function object flow, stripped-binary support, or safe provider hydration. No additional tool or mutation authority follows from this one case. Both sessions closed, and the target SHA-256 was unchanged.
Author
Owner

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

Stripped control for the preceding fixture: I copied the binary and ran strip -u -r on the copy before analysis. llvm-nm no longer listed the authored C++ methods, constructors, or vtable symbols; the fixture still contained class-name strings and RTTI-related material, so this is not an RTTI-free or metadata-free control.

Both real REA/Ghidra and REA/Kuna procedure inventories used generated names for the known code addresses, while search_strings found 7Doubler and 9Offsetter. The generic virtual call stayed unknown in both providers' call paths. On Ghidra, starting at the selected 7Doubler string, xrefs found a typeinfo-member address; using the known Itanium layout to select its containing record then exposed an incoming reference from the vtable's typeinfo field. A separately selected vtable function slot pointed through loaded memory to the known implementation, and another xref connected the slot to the constructor. This was a scripted, analyst-guided chain using the unstripped control and ABI knowledge; no agent autonomously derived the structure, and the observations do not prove runtime dispatch.

For the same string and selected slots, REA/Kuna xrefs returned no rows and its read_bytes operation was unavailable. Direct Kuna 1.986 xrefs --to also returned zero references to that string in this fixture, while direct read of the function slot returned 3c05000000001000, matching an independent source-file read. Ghidra's initialized-memory read_bytes at that slot returned 3c05000001000000; the earlier relocation control recorded the loader adjustment. This reinforces #8170: forwarding a successful Kuna read as equivalent to the initialized-memory contract would lose an important distinction.

The stripped copy's own Ghidra load-image relocation page also reported APPLIED at both slots and retained those distinct original and memory byte sequences. The original binary was preserved; the stripped copy's digest stayed unchanged throughout provider analysis and neither was executed. This is one arm64 Mach-O with remaining RTTI material, not general stripped-code coverage or a complete reference inventory.

<!-- gh-comment-id:6541189421 --> @morluto commented on GitHub (Oct 31, 2026): Stripped control for the preceding fixture: I copied the binary and ran `strip -u -r` on the copy before analysis. `llvm-nm` no longer listed the authored C++ methods, constructors, or vtable symbols; the fixture still contained class-name strings and RTTI-related material, so this is **not** an RTTI-free or metadata-free control. Both real REA/Ghidra and REA/Kuna procedure inventories used generated names for the known code addresses, while `search_strings` found `7Doubler` and `9Offsetter`. The generic virtual call stayed unknown in both providers' call paths. On Ghidra, starting at the selected `7Doubler` string, `xrefs` found a typeinfo-member address; using the known Itanium layout to select its containing record then exposed an incoming reference from the vtable's typeinfo field. A separately selected vtable function slot pointed through loaded memory to the known implementation, and another xref connected the slot to the constructor. This was a scripted, analyst-guided chain using the unstripped control and ABI knowledge; no agent autonomously derived the structure, and the observations do not prove runtime dispatch. For the same string and selected slots, REA/Kuna `xrefs` returned no rows and its `read_bytes` operation was unavailable. Direct Kuna 1.986 `xrefs --to` also returned zero references to that string in this fixture, while direct `read` of the function slot returned `3c05000000001000`, matching an independent source-file read. Ghidra's initialized-memory `read_bytes` at that slot returned `3c05000001000000`; the earlier relocation control recorded the loader adjustment. This reinforces #8170: forwarding a successful Kuna read as equivalent to the initialized-memory contract would lose an important distinction. The stripped copy's own Ghidra load-image relocation page also reported `APPLIED` at both slots and retained those distinct original and memory byte sequences. The original binary was preserved; the stripped copy's digest stayed unchanged throughout provider analysis and neither was executed. This is one arm64 Mach-O with remaining RTTI material, not general stripped-code coverage or a complete reference inventory.
Author
Owner

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

Additional -fno-rtti control: I rebuilt the same source with Clang -fno-rtti, copied and stripped it with strip -u -r, and analyzed the copy without executing it. llvm-nm on the unstripped build showed vtable symbols but no typeinfo symbols; the stripped copy's symbol table no longer listed the authored methods or vtables. This tests one compiler/ABI/fixture, not every possible RTTI-disabled binary.

Both REA/Ghidra and REA/Kuna returned generated procedure names and an unknown call path to the selected implementation, with partial coverage at the indirect call. With the implementation, constructor, and vtable-slot addresses taken from the unstripped control, Ghidra still showed the constructor's pointer to the slot, read the loaded slot value as the method address, and returned the constructor in the slot's xrefs. Kuna's constructor pseudocode retained the same slot address, but its byte read was unavailable and slot xrefs returned no rows. These are useful selected facts, not an automatically discovered class or runtime target.

The string control also matters: REA/Kuna search_strings("Doubler") returned no rows, while REA/Ghidra returned several fragments. Ghidra's load-image mapping placed the selected fragment in its __LINKEDIT block, and address_to_file_offset mapped it to retained file bytes; xrefs for that fragment was empty. It is therefore a residual string observation, not evidence that RTTI for that class was recovered. The selected target and original control were not executed or modified during provider analysis. This also reinforces #8245's distinction between string matches, mapping provenance and meaning.

<!-- gh-comment-id:6541206854 --> @morluto commented on GitHub (Oct 31, 2026): Additional `-fno-rtti` control: I rebuilt the same source with Clang `-fno-rtti`, copied and stripped it with `strip -u -r`, and analyzed the copy without executing it. `llvm-nm` on the unstripped build showed vtable symbols but no `typeinfo` symbols; the stripped copy's symbol table no longer listed the authored methods or vtables. This tests one compiler/ABI/fixture, not every possible RTTI-disabled binary. Both REA/Ghidra and REA/Kuna returned generated procedure names and an `unknown` call path to the selected implementation, with partial coverage at the indirect call. With the implementation, constructor, and vtable-slot addresses taken from the unstripped control, Ghidra still showed the constructor's pointer to the slot, read the loaded slot value as the method address, and returned the constructor in the slot's xrefs. Kuna's constructor pseudocode retained the same slot address, but its byte read was unavailable and slot xrefs returned no rows. These are useful selected facts, not an automatically discovered class or runtime target. The string control also matters: REA/Kuna `search_strings("Doubler")` returned no rows, while REA/Ghidra returned several fragments. Ghidra's load-image mapping placed the selected fragment in its `__LINKEDIT` block, and `address_to_file_offset` mapped it to retained file bytes; `xrefs` for that fragment was empty. It is therefore a residual string observation, not evidence that RTTI for that class was recovered. The selected target and original control were not executed or modified during provider analysis. This also reinforces #8245's distinction between string matches, mapping provenance and meaning.
Author
Owner

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

Multiple-inheritance control for candidate identity: I built an unexecuted arm64 Mach-O with Both : Left, Right, with a virtual Right::right override. The compiler emitted a secondary vtable entry for a non-virtual adjustment thunk at 0x100000558; independent LLVM disassembly showed it subtracts 8 from the incoming object pointer and calls the implementation at 0x10000054c. This fixture used -fno-optimize-sibling-calls, so its thunk is a call-and-return wrapper rather than a tail jump.

Real REA/Ghidra 12.1.4 showed the constructor installing a second vtable pointer, read_bytes at the selected secondary slot 0x1000040c8 returned the thunk address, and an xref linked that slot to the constructor. The thunk's pseudocode retained the this - 8 adjustment; trace_native_values retained a derived pointer-add operation flowing into the direct call, and trace_call_path from thunk to implementation reached the implementation. The generic virtual call remained unresolved, with the constructor-to-implementation path unknown. These separately selected facts do not establish runtime dispatch.

Ghidra procedure_info reported classification.thunk: false with ghidra-function-manager provenance for this compiler-generated adjustment wrapper. That is Ghidra's own function classification, not proof that the entry has no C++ adjustment role; the pseudocode comment and instruction evidence show that distinction here. Ghidra's inventory displayed both entry addresses as Both::right; a name-only request was refused as ambiguous and supplied both exact addresses for recovery. REA/Kuna gave the wrapper a distinct {virtual override thunk} name and its path to the implementation was reached, but typed byte/call-reference tools remained unavailable. Direct Kuna xrefs from the wrapper reported its direct call.

For a future candidate workflow, preserving the actual vtable entry, the adjustment and the separately evidenced implementation avoids silently replacing a callable entry with a different address or losing its object-pointer semantics. This is one symbolized compiler/architecture case; it does not establish all inheritance, thunk, ABI or stripped-target behavior. The target digest stayed unchanged and no target was executed.

<!-- gh-comment-id:6541226237 --> @morluto commented on GitHub (Oct 31, 2026): Multiple-inheritance control for candidate identity: I built an unexecuted arm64 Mach-O with `Both : Left, Right`, with a virtual `Right::right` override. The compiler emitted a secondary vtable entry for a **non-virtual adjustment thunk** at `0x100000558`; independent LLVM disassembly showed it subtracts 8 from the incoming object pointer and calls the implementation at `0x10000054c`. This fixture used `-fno-optimize-sibling-calls`, so its thunk is a call-and-return wrapper rather than a tail jump. Real REA/Ghidra 12.1.4 showed the constructor installing a second vtable pointer, `read_bytes` at the selected secondary slot `0x1000040c8` returned the **thunk** address, and an xref linked that slot to the constructor. The thunk's pseudocode retained the `this - 8` adjustment; `trace_native_values` retained a derived pointer-add operation flowing into the direct call, and `trace_call_path` from thunk to implementation reached the implementation. The generic virtual call remained unresolved, with the constructor-to-implementation path `unknown`. These separately selected facts do not establish runtime dispatch. Ghidra `procedure_info` reported `classification.thunk: false` with `ghidra-function-manager` provenance for this compiler-generated adjustment wrapper. That is Ghidra's own function classification, not proof that the entry has no C++ adjustment role; the pseudocode comment and instruction evidence show that distinction here. Ghidra's inventory displayed both entry addresses as `Both::right`; a name-only request was refused as ambiguous and supplied both exact addresses for recovery. REA/Kuna gave the wrapper a distinct `{virtual override thunk}` name and its path to the implementation was reached, but typed byte/call-reference tools remained unavailable. Direct Kuna xrefs from the wrapper reported its direct call. For a future candidate workflow, preserving the actual vtable entry, the adjustment and the separately evidenced implementation avoids silently replacing a callable entry with a different address or losing its object-pointer semantics. This is one symbolized compiler/architecture case; it does not establish all inheritance, thunk, ABI or stripped-target behavior. The target digest stayed unchanged and no target was executed.
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#1350
No description provided.