35 KiB
C++ Reverse Engineering — Vtables, Inheritance, and OOP Reconstruction
Overview
C++ on the Amiga — primarily via StormC, GCC 2.95.x, and SAS/C with limited C++ support — compiles object-oriented constructs into predictable patterns in the binary. Virtual method dispatch goes through vtables (arrays of function pointers at fixed offsets from the object pointer), constructors chain through inheritance hierarchies, and name mangling encodes the full class-qualified signature into linker symbols. Reversing C++ binaries means reconstructing the class hierarchy from these artifacts — recovering which methods are virtual, how many levels of inheritance exist, and where each class appears in the vtable dispatch graph.
Unlike modern platforms with rich RTTI and exception metadata, Amiga C++ binaries are typically stripped and lean. RTTI is often disabled (-fno-rtti), and exception support is minimal. The vtable is your primary reconstruction tool — it encodes the entire polymorphic structure of the program.
graph TB
subgraph "C++ Source"
CLASS["class Window : public Gadget {<br/> virtual void Draw();<br/> virtual void HandleEvent();<br/>};"]
end
subgraph "Binary Layout"
OBJ["Window object<br/>───────────<br/>+00: _vptr → vtable"]
VTABLE["Window vtable<br/>───────────<br/>+00: ~Window()<br/>+04: Draw()<br/>+08: HandleEvent()<br/>+0C: RTTI ptr → typeinfo"]
RTTI["RTTI<br/>───────────<br/>type_info struct<br/>mangled name<br/>base class list"]
end
OBJ -->|"+$00"| VTABLE
VTABLE -->|"+$0C (g++), -$04 (StormC)"| RTTI
Architecture: C++ to m68k Mapping
Vtable Layout — Complete (GCC 2.95.x on m68k)
On the Amiga, GCC 2.95.x follows the Itanium C++ ABI concepts adapted for 32-bit m68k. The vtable pointer (_vptr) resides at object offset +$00 and points to the first virtual function entry, not the start of the vtable itself.
Full GCC Vtable Layout (m68k, 32-bit, all entries 4 bytes):
┌─────────────────────┐ ← vtable_start (symbol address, e.g. _ZTV6Window)
│ offset_to_top = 0 │ vtable[-2] — always 0 for most-derived class
├─────────────────────┤
│ RTTI pointer │ vtable[-1] — _ZTI6Window (type_info for Window)
├─────────────────────┤ ← _vptr points here (vtable_start + 8)
│ Window::~Window() │ vtable[0] — virtual destructor (D1 complete)
├─────────────────────┤
│ Window::~Window() │ vtable[1] — virtual destructor (D0 deleting)
├─────────────────────┤
│ Window::Draw() │ vtable[2] — first user virtual method
├─────────────────────┤
│ Window::HandleEvt() │ vtable[3] — second user virtual method
├─────────────────────┤
│ ... │ vtable[n] — more virtual methods
└─────────────────────┘
| Vtable Offset (from vptr) | Vtable Offset (from start) | Contents | Notes |
|---|---|---|---|
-8 (vptr − 2) |
+$00 |
offset_to_top |
Always 0 for most-derived; non-zero in multiple inheritance for non-primary bases |
-4 (vptr − 1) |
+$04 |
RTTI pointer (type_info*) |
Points to type_info struct with mangled class name; NULL if -fno-rtti |
+0 (vptr + 0) |
+$08 |
Destructor variant 1 | In-charge non-deleting destructor (D1); cleans up object, does NOT call FreeMem |
+4 (vptr + 1) |
+$0C |
Destructor variant 2 | In-charge deleting destructor (D0); cleans up AND calls operator delete |
+8 (vptr + 2) |
+$10 |
First user virtual method | Declaration order in the class body |
+12 (vptr + 3) |
+$14 |
Second user virtual method | ...continues for all declared virtuals |
Virtual Method Dispatch
; In C++: obj->Draw()
; Becomes:
MOVE.L obj_ptr(FP), A0 ; load object pointer
MOVE.L (A0), A1 ; dereference vtable pointer (at offset +00)
MOVE.L $04(A1), A0 ; load Draw() from vtable[1]
JSR (A0) ; call via function pointer
The signature pattern: MOVE.L (A0), An followed by MOVE.L $offset(An), target then JSR (target) — this is the C++ vtable dispatch fingerprint.
Constructor Pattern — Full Lifecycle
; C++: new Window()
; Generates:
; 1. Allocate memory (operator new → AllocMem)
; 2. Call base class constructor (Gadget::Gadget)
; 3. Store vtable pointer at object+$00
; 4. Initialize Window-specific members
; 5. Return object pointer in D0
MOVE.L #sizeof_Window, D0
MOVE.L #MEMF_CLEAR, D1
JSR -$C6(A6) ; AllocMem
MOVE.L D0, A2 ; save object ptr
; Call base constructor (Gadget::Gadget):
MOVE.L A2, -(SP)
JSR _Gadget_ctor ; calls SUPER::ctor
ADDQ.L #4, SP
; Install vtable:
LEA _Window_vtable, A0
MOVE.L A0, (A2) ; _vptr = &vtable
; Initialize Window members:
MOVE.W #$00FF, $14(A2) ; this->width = 255
; Return this:
MOVE.L A2, D0
RTS
Destructor Pattern — Multiple Variants
GCC generates up to three distinct destructor functions per class. Understanding which is which is critical for vtable reconstruction:
| Variant | GCC Suffix | Purpose | Vtable[0] or [1]? | Contains FreeMem call? |
|---|---|---|---|---|
| D2 (not-in-charge) | ~ClassName (base variant) |
Destroys this subobject only; called by derived class destructors | Neither — called directly by derived dtors | No |
| D1 (in-charge, non-deleting) | ~ClassName (complete) |
Destroys full object; does NOT free memory | vtable[0] | No |
| D0 (in-charge, deleting) | ~ClassName (deleting) |
Destroys full object AND calls operator delete | vtable[1] | Yes (JSR operator delete) |
; D2 — Not-in-charge destructor (base subobject cleanup):
__6Window_D2: ; no vtable entry points here directly
LINK A6, #0
; Destroy Window-specific members
; CALL base class D2 destructor
JSR __6Gadget_D2
UNLK A6
RTS
; D1 — In-charge non-deleting (cleans up, no FreeMem):
__6Window_D1: ; vtable[0] = this function
LINK A6, #0
; Store vtable pointer (restore to most-derived)
LEA _ZTV6Window, A0
MOVE.L A0, (A2) ; _vptr = &Window_vtable
; Destroy Window-specific members
JSR __6Gadget_D2 ; call base D2
UNLK A6
RTS ; NO FreeMem call!
; D0 — In-charge deleting (cleans up AND frees memory):
__6Window_D0: ; vtable[1] = this function
LINK A6, #0
BSR __6Window_D1 ; call D1 to do the cleanup
; Now free the memory:
MOVE.L A2, -(SP)
JSR operator_delete ; calls FreeVec/FreeMem
ADDQ.L #4, SP
UNLK A6
RTS
Inheritance Hierarchy in the Binary
Single Inheritance
Gadget object:
+00: _vptr → Gadget_vtable
+04: gadget_member_1
+08: gadget_member_2
Window object (extends Gadget):
+00: _vptr → Window_vtable ← overwrites Gadget's vptr
+04: gadget_member_1 ← inherited
+08: gadget_member_2 ← inherited
+0C: window_member_1 ← new in Window
+10: window_member_2 ← new in Window
Multiple Inheritance
Window object (extends Gadget AND Drawable):
+00: _vptr → Window_vtable (primary: Gadget subobject)
+04: gadget_member_1
+08: gadget_member_2
+0C: _vptr → Window_Drawable_vtable (secondary: Drawable subobject)
+10: drawable_member_1
+14: window_member_1
this-adjustment thunk for Drawable::method():
ADDQ.L #$0C, A0 ; adjust this to Drawable subobject
JMP _Window_Drawable_method ; tail-call real implementation
Virtual Inheritance (Diamond Problem)
Name Mangling — GCC 2.95.x Reference
The GCC 2.95.x mangling scheme (based on the Itanium C++ ABI draft) encodes the full qualified name and parameter types into linker symbols. This is your primary source for recovering class names and method signatures:
| Source Declaration | GCC 2.95.x Mangled Symbol | Decode |
|---|---|---|
Window::Draw(void) |
Draw__6Window or Draw__6WindowFv |
Draw method of class Window (6 chars) |
Window::SetPos(int, int) |
SetPos__6WindowFii |
SetPos method, takes two int parameters |
Window::SetPos(long, long) |
SetPos__6WindowFll |
Same method name, different mangling for long |
operator new(unsigned long) |
__nw__FUl |
new operator, takes unsigned long (size) |
operator delete(void *) |
__dl__FPv |
delete operator, takes void* |
Window::~Window(void) |
__6Window or _$_6Window |
Destructor; _$ prefix often on Amiga GCC builds |
| Static class member function | GetCount__6WindowFv |
Same mangling as instance method — context determines static |
operator+(Window const &) |
__pl__6WindowFRC6Window |
__pl = operator+, FRC6Window = const reference param |
Window::Window(int, int) |
__6WindowFii |
Constructor — same pattern as destructor but no special prefix |
Demangling helper (Python):
# Quick-and-dirty GCC 2.95.x demangler for Amiga symbols
import re
def demangle_gcc295(sym):
# Example: SetPos__6WindowFii → Window::SetPos(int, int)
m = re.match(r'(.+)__(d+)(.+?)(F.*)?$', sym)
if m:
method = m.group(1)
class_len = int(m.group(2))
class_name = m.group(3)[:class_len]
params = m.group(4) or ''
type_map = {'i': 'int', 'l': 'long', 'v': 'void', 'c': 'char',
's': 'short', 'f': 'float', 'Pv': 'void*', 'Ul': 'unsigned long'}
return f"{class_name}::{method}(...)"
return sym
Name Mangling — StormC Differences
StormC uses a different mangling scheme from GCC:
| C++ Construct | GCC 2.95.x | StormC |
|---|---|---|
Method Draw() on class Window |
Draw__6Window |
Draw_Window or Window_Draw |
Operator new |
__nw__FUl |
__nw_Ul or inline to AllocMem |
| Destructor | __6Window |
_dtor_Window or ~Window |
| RTTI | _ZTI6Window |
_Type_Window or absent |
| Vtable | _ZTV6Window |
_VTable_Window or Window_VTable |
StormC binaries typically use fewer mangled symbols because StormC often inlines trivial methods and may not emit RTTI or vtable symbols with predictable names. Look for the constructor pattern (vtable store at offset +00) as an alternative anchor.
RTTI Structure Format (GCC 2.95.x)
The type_info struct provides class identity and — for derived classes — the inheritance chain:
/* GCC 2.95.x type_info layout on m68k: */
struct type_info {
/* +00: vtable pointer for type_info itself (points to __class_type_info vtable) */
void * _vptr_type_info;
/* +04: mangled class name (null-terminated string) */
const char * _name; // e.g., "6Window" (6-char prefix + "Window")
};
/* Single inheritance class type info: */
struct __si_class_type_info : public type_info {
/* +08: pointer to base class type_info */
const type_info * _base_type;
};
/* Multiple inheritance class type info: */
struct __vmi_class_type_info : public type_info {
/* +08: flags (bit 0 = diamond inheritance) */
unsigned int _flags;
/* +0C: number of base classes */
unsigned int _base_count;
/* +10: array of __base_class_type_info entries */
struct __base_class_type_info {
const type_info * _base_type; // pointer to base class type_info
long _offset_flags; // offset to base subobject within most-derived
/* bit 0-7: offset shift, bit 8: is_virtual flag */
} _base_info[];
};
Warning
Most Amiga C++ binaries use
-fno-rttito save space. RTTI is present in fewer than 25% of Amiga C++ productions. When present, it's a goldmine. When absent, rely on vtable structure and mangled names exclusively.
Decision Guide
graph TD
BIN["Binary loaded"]
VPTR{"_vptr store pattern<br/>MOVE.L vtable, (An)"}
VTABLE_DISPATCH{"Vtable dispatch<br/>MOVE.L (A0),An / JSR (An)"}
MANGLED{"Mangled symbol<br/>names in HUNK_SYMBOL"}
NEW_DELETE{"new/delete wrappers<br/>AllocMem+ctor sequence"}
THUNKS{"this-adjustment<br/>thunks present?"}
BIN --> VPTR
VPTR -->|"Yes"| CXX_CONFIRMED["C++ confirmed"]
VPTR -->|"No"| VTABLE_DISPATCH
VTABLE_DISPATCH -->|"Yes"| CXX_CONFIRMED
VTABLE_DISPATCH -->|"No"| MANGLED
MANGLED -->|"Yes"| CXX_CONFIRMED
MANGLED -->|"No"| NEW_DELETE
NEW_DELETE -->|"Yes"| CXX_LIKELY["Likely C++ — no virtuals"]
NEW_DELETE -->|"No"| NOT_CXX["Probably C or asm<br/>→ see other guides"]
CXX_CONFIRMED --> THUNKS
THUNKS -->|"Yes"| MULTI_INHERIT["Multiple/virtual<br/>inheritance present"]
THUNKS -->|"No"| SINGLE_INHERIT["Single inheritance<br/>or no inheritance"]
When to Use C++ RE vs Alternatives
| Scenario | Approach |
|---|---|
| Vtable dispatch patterns present | C++ RE (this article) |
| No vtables, but name mangling suggests classes | C++ without virtual methods (use ANSI C RE as base + class struct reconstruction) |
| MUI BOOPSI class (C-implemented OOP) | C RE + BOOPSI dispatcher analysis |
| Pure C with function pointer tables | See ansi_c_reversing.md — not C++ vtables |
Methodology
Phase 1: Detect C++ Usage
Signs of C++:
- Vtable store:
LEA _vtable, A0/MOVE.L A0, (A1)at object construction - Vtable dispatch:
MOVE.L (A0), A1/MOVE.L $0N(A1), A0/JSR (A0) - Name mangling: Symbol names containing
__with class name and parameter type encoding new/deletecalls: Wrappers aroundAllocMem/FreeMemwith constructor/destructor callsthispointer: First argument passed in A0 or on stack, used as base for member access- this-adjustment thunks:
ADDQ.L #offset, A0/JMP real_method - RTTI structures:
type_infowith.namepointer
Phase 2: Reconstruct Vtables
- Search for
LEA xxx, A0/MOVE.L A0, (An)pairs — each LEA target is a vtable candidate - At each vtable address, enumerate the function pointers (4-byte entries)
- Cross-reference each function pointer back to its implementation
- Map vtable index → method name (from mangled symbols or manual deduction)
- Identify the destructor (vtable[0]): look for
FreeMemcalls, base destructor chains, virtual destructor helpers
Phase 3: Recover Class Hierarchy
- Single inheritance: Objects share the same
_vptroffset (+00); derived class vtable extends base class vtable entries - Multiple inheritance: Multiple
_vptrfields at different offsets in the object;thisadjustment thunks - Virtual base classes: Shared base via pointer indirection (virtual base offset table)
- Common base detection: Look for identical vtable prefix sequences across multiple classes
Phase 4: Match Constructors to Classes
Constructors are entry points that:
- Call a base constructor (recursive)
- Store a vtable pointer
- Initialize member variables after the vtable store
- Return
thisin D0
Phase 5: Reconstruct Class Member Layout
Global Constructor & Destructor Arrays (GCC)
GCC 2.95.x emits two arrays that the startup code must process before calling main():
__CTOR_LIST__ format:
┌──────────────────────┐
│ count (N) │ __CTOR_LIST__[0] = number of constructor functions
├──────────────────────┤
│ constructor_func_1 │ __CTOR_LIST__[1] = function pointer
├──────────────────────┤
│ constructor_func_2 │ __CTOR_LIST__[2]
├──────────────────────┤
│ ... │
├──────────────────────┤
│ 0x00000000 │ Terminator (NULL)
└──────────────────────┘
__DTOR_LIST__ has the SAME format.
Startup processing (found in the startup code, before main() is called):
; Iterate __CTOR_LIST__ and call each constructor:
LEA __CTOR_LIST__, A0
MOVE.L (A0)+, D0 ; load count
BEQ no_ctors
ctor_loop:
MOVE.L (A0)+, A1 ; load constructor function pointer
JSR (A1) ; call it
SUBQ.L #1, D0
BNE ctor_loop
no_ctors:
In the RE workflow: If you find __CTOR_LIST__ and __DTOR_LIST__ in the symbol table:
- Each function pointer in
__CTOR_LIST__is a global object constructor — these initialize global C++ objects - Trace each constructor to find which class it initializes
- The matching destructor is in
__DTOR_LIST__at the same index - Destructors are called in reverse order at program exit by the startup code
Note
__CTOR_LIST__and__DTOR_LIST__are emitted even with-nostdlib(as described in the RastPort article on C++ without standard library). They're part of the GCC ABI, not the standard library.
Common operator patterns in m68k disassembly:
| Operator | Assembly Signature | Notes |
|---|---|---|
operator= |
CMP.L src, this / BEQ skip (self-assignment guard) then member-by-member copy |
Self-assignment check is the definitive marker |
operator new |
MOVE.L size, D0 / JSR AllocVec — thin wrapper, returns this in D0 |
Size argument is sizeof(Class) — confirms class identity |
operator delete |
JSR FreeVec — calls destructor first if virtual |
May be a single instruction if inlined |
operator== |
CMP.L per member, SEQ D0 / EXT.L D0 / RTS — returns 0 or 1 in D0 |
Boolean return in D0 is distinctive |
operator+ |
Creates new object via operator new, initializes with sum of two operands, returns new object |
Creates and returns new object in D0 |
operator[] |
MOVE.L index, D0 / ASL.L #element_shift, D0 / ADD.L base, D0 then access at (D0) |
Index calculation → base+offset load |
operator++ (prefix) |
Increments member, returns *this in D0 |
Returns reference to modified object |
operator++ (postfix) |
Saves old value, increments member, returns old value | Postfix has dummy int parameter in mangling |
Phase 7: Dynamic Verification
Tool-Specific Workflows
IDA Pro
Ghidra
Best Practices
- Start from the vtable — it's the most information-dense artifact in a C++ binary
- Identify the destructor first — it anchors the vtable; everything else chains from it
- Match constructors to vtables — each constructor stores exactly one vtable; that's your class identity
- Use mangled names when available — they encode class name, method name, and parameter types
- Trace
thisthrough the function — document which offsets are read/written; those are member fields - Detect multiple inheritance by counting
_vptrstores per constructor — more than one store per object = multiple inheritance - Don't assume RTTI is present — it's often stripped; rely on vtable structure instead
- Build a class diagram as you work — manually or via tooling; the relationships become visible from vtable sharing
- Verify destructor chains dynamically — breakpoint on
FreeMemto see which destructors run in which order - Document the vtable layout in a table — offset → method name → implementation address; this is your reconstruction artifact
Antipatterns
1. The Missing Base Class
Wrong: Assuming a vtable with N entries represents a single class with N virtual methods.
Why: In single inheritance, the derived vtable contains entries from ALL base classes plus its own additions. A 12-entry vtable might be a 3-level hierarchy with 4 virtual methods per class.
2. The Flat Vtable Assumption
Wrong: Treating all vtable entries as equal without identifying the destructor.
Why: The destructor (first vtable entry) is the anchor. Once you identify the destructor chain, you can trace back through the constructor chain to reconstruct the class hierarchy.
3. The Single Inheritance Blindness
Wrong: Assuming _vptr is always at offset +00.
Why: In multiple inheritance, each base class subobject has its own _vptr. An object may have 2–3 vtable pointers at different offsets (+00, +$10, +$20).
4. The RTTI Assumption
Wrong: Relying on RTTI being present to name classes and map hierarchies.
Why: Most Amiga C++ projects use -fno-rtti to save space. RTTI is the exception, not the rule. You must reconstruct class names from mangled symbols or manual analysis.
5. The Thunk-as-Function Mistake
Wrong: Treating this-adjustment thunks as separate virtual methods.
Why: A thunk is a 2-instruction trampoline (ADDQ.L #offset, A0 / JMP real_method). Adding it to the vtable count inflates the method inventory and confuses the call graph.
6. The Virtual Destructor Blind Spot
Wrong: Assuming every destructor is a standalone function.
Why: GCC generates up to 3 destructor variants: D0 (in-charge, deletes object), D1 (in-charge, no delete), D2 (not-in-charge, base subobject destructor). All three may appear as separate functions near each other. Missing one means missing an entire constructor chain path.
7. The Constructor-as-Init Confusion
Wrong: Assuming any function that initializes memory is a constructor.
Why: C-style init functions, factory functions, and reset methods all initialize objects but don't set vtable pointers. Only C++ constructors store the vtable. The vtable store is the definitive constructor marker.
8. The Virtual Inheritance Denial
Wrong: Assuming inheritance is always simple enough to reconstruct from vtable layout alone.
Why: Virtual inheritance (diamond problem) introduces vbase pointers and offset tables that make the object layout non-linear. Without recognizing the vbase pattern, you'll place fields at wrong offsets and misidentify base class relationships.
Pitfalls
1. Thunk Confusion
; "this adjustment" thunk for multiple inheritance:
; When calling base2::method() through a derived pointer,
; the compiler generates:
ADDQ.L #$10, A0 ; adjust this to base2 subobject
JMP _base2_method ; tail-call the real method
These small thunks are not real methods — they're pointer adjustments. Mistaking them for separate functions inflates the vtable count.
2. Inline Destructor Deception
GCC sometimes inlines trivial destructors, making the vtable entry point directly to FreeMem with no destructor body. This looks like a bug but is correct — the class has no resources to free.
3. RTTI Disabled
Not all C++ compilers for Amiga emitted RTTI. Don't assume type_info will be present — GCC -fno-rtti disables it, and many Amiga projects used this flag to save space.
4. Multiple Destructor Variants
5. vtable Sharing Between Classes
6. Static Initialization Order
7. Exception Handling Artifacts
8. Template Instantiation Code Bloat
9. Compiler-Generated Default Methods
Use-Case Cookbook
Pattern 1: Extracting All Vtables from a Binary
Pattern 2: Reconstructing a Single Inheritance Chain
Pattern 3: Recovering Class Method Names from Mangled Symbols
Pattern 4: Mapping new/delete to Class Types
Pattern 5: Reconstructing Multiple Inheritance from Thunks
Pattern 6: Identifying a MUI Custom Class (BOOPSI/C++ Hybrid)
Pattern 7: Reconstructing a StormC++ Class Hierarchy
Pattern 8: Tracing Object Lifetimes Through Constructors/Destructors
Pattern 9: Decompiling Virtual Method Bodies
Pattern 10: Identifying the Root Base Class
Real-World Examples
Applications
Games
Libraries
Historical Context — C++ on Amiga
C++ adoption on Amiga was limited by several factors:
| Factor | Impact |
|---|---|
| Late compiler arrival | StormC (1996) was the first practical Amiga-native C++ IDE. GCC 2.95.x cross-compiler arrived later. Before this, C++ on Amiga was essentially non-existent. |
| RAM constraints | Virtual dispatch tables, RTTI data, and template instantiations consumed RAM that a stock A500 (512KB–1MB) couldn't spare |
| Performance overhead | Virtual method dispatch on a 7 MHz 68000 (2 indirections + JSR) was measurably slower than a direct function call — problematic for real-time code |
| SAS/C limited support | SAS/C had rudimentary C++ support but no exceptions, no RTTI, and no STL. It was effectively "C with classes" |
| StormC dominance (late 1990s) | StormC brought a full IDE and usable C++ to Amiga. Most late-era Amiga C++ software was compiled with StormC |
| GCC cross-compilation | Developers targeting Amiga from Linux/Windows used GCC 2.95.x m68k cross-compilers, bringing modern C++ (templates, STL) to Amiga |
-fno-rtti -fno-exceptions were standard |
Nearly all Amiga C++ binaries disable RTTI and exceptions to save space and avoid runtime overhead |
Notable C++ Amiga applications:
- YAM (Yet Another Mailer): C++ with MUI, complex class hierarchy — one of the most-studied Amiga C++ applications
- StormC IDE: Self-hosting — the StormC compiler was written in C++ and compiled with itself
- Foundation / Foundation: Gold: Large C++ game with custom memory management
- fxPaint: C++ graphics application with plugin architecture
- MUI custom classes: Many MUI widgets (NList, NListview, TextEditor) were implemented in C++
Modern Analogies
| Amiga C++ Concept | Modern Analogy | Where It Holds / Breaks |
|---|---|---|
| Vtable dispatch | C++ vtable (unchanged!) | Holds: exactly the same mechanism; breaks: modern ABIs add more metadata |
| this-adjustment thunk | Multiple inheritance pointer fixup | Holds: same concept; breaks: modern compilers use more compact thunk encoding |
| Name mangling | Itanium C++ ABI mangling | Holds: GCC uses same mangling scheme; breaks: StormC uses different scheme |
| RTTI type_info | std::type_info / typeid |
Holds: same structure; breaks: Amiga often stripped RTTI to save space |
| Virtual destructor | Virtual destructor (unchanged) | Holds: identical concept; breaks: Amiga compilers generate multiple destructor variants |
| operator new/delete | operator new/delete |
Holds: same wrappers around allocator; breaks: Amiga wraps AllocMem/FreeMem directly |
FAQ
Q1: How do I identify the destructor in a vtable?
Q2: Why are there empty "padding" entries in the vtable?
Q3: How do I tell virtual inheritance from multiple inheritance?
Q4: What does this adjustment look like and how do I automate detection?
Q5: How do I distinguish between SAS/C C++ and GCC C++ in disassembly?
Q6: How do I recover the original class name without RTTI?
Q7: Why do I see three destructors for one class?
Q8: How do I identify a C++ exception handler in the binary?
Q9: How do I reconstruct template instantiations?
Q10: Can I tell if a class inherits from a BOOPSI base (MUI)?
Q11: How do I handle mixed C and C++ in the same binary?
Q12: What's the difference between StormC++ and GCC vtable layout?
FPGA / Emulation Impact
References
- ansi_c_reversing.md — C binary reverse engineering
- struct_recovery.md — Struct layout reconstruction from offsets
- compiler_fingerprints.md — Compiler identification
- m68k_codegen_patterns.md — Code generation patterns
- asm68k_binaries.md — Hand-written assembly RE
- api_call_identification.md — Library call recognition
- Itanium C++ ABI (reference for GCC-compatible vtable layout)
- StormC++ Manual — Amiga-native C++ compiler documentation
- NDK 3.9:
include/exec/types.h— Fundamental type layout