mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-13 00:26:28 +00:00
322 lines
12 KiB
Markdown
322 lines
12 KiB
Markdown
|
|
[← Home](../../../README.md) · [Reverse Engineering](../../README.md) · [Static Analysis](../README.md) · [Compilers](README.md)
|
|||
|
|
|
|||
|
|
# StormC / StormC++ — Reverse Engineering Field Manual
|
|||
|
|
|
|||
|
|
## Overview
|
|||
|
|
|
|||
|
|
**StormC** (by Haage & Partner, 1996–2000) was a native Amiga IDE with integrated C and C++ compiler. It occupies a unique position in Amiga RE: it's the **only native Amiga compiler with full C++ support** (exceptions, RTTI, STL), yet its C++ ABI is **incompatible with GCC's C++ ABI** — StormC uses its own name mangling, vtable layout, and exception handling mechanism. For the RE practitioner, StormC binaries look like SAS/C at the C level (A5 frame pointer, absolute strings) but diverge significantly when C++ constructs appear.
|
|||
|
|
|
|||
|
|
Key constraints:
|
|||
|
|
- **A5 frame pointer** — StormC follows the SAS/C convention (`LINK A5, #-N`), making C-level code appear SAS/C-compatible.
|
|||
|
|
- **C++ ABI is unique** — StormC's name mangling, vtable layout, RTTI, and exception handling differ from both GCC and the Itanium C++ ABI. StormC++ libraries cannot link with GCC C++ code.
|
|||
|
|
- **Native IDE integration** — StormC embeds project metadata (source paths, build configs) in the binary via custom HUNK_DEBUG entries.
|
|||
|
|
- **PowerPC support (v3+)** — StormC 3.0+ could target PPC (WarpOS/PowerUP). PPC code sections use a different hunk type and appear as foreign code in 68k disassembly.
|
|||
|
|
- **Hunk names**: `CODE`, `DATA` (Amiga standard, SAS/C-compatible)
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TB
|
|||
|
|
subgraph "Source (.c / .cpp)"
|
|||
|
|
SRC["C/C++ source"]
|
|||
|
|
end
|
|||
|
|
subgraph "StormC IDE"
|
|||
|
|
IDE["Project Manager"]
|
|||
|
|
EDITOR["GUI Editor"]
|
|||
|
|
COMPILER["StormC Compiler"]
|
|||
|
|
LINKER["StormLink"]
|
|||
|
|
end
|
|||
|
|
subgraph "Binary Output"
|
|||
|
|
HUNK["Amiga HUNK executable"]
|
|||
|
|
CODE["CODE hunk — 68k code"]
|
|||
|
|
PPC["PPC_CODE (optional, v3+)"]
|
|||
|
|
SYMBOL["HUNK_SYMBOL — StormC mangled names"]
|
|||
|
|
DEBUG["HUNK_DEBUG — project metadata + line info"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
SRC --> IDE
|
|||
|
|
IDE --> COMPILER --> LINKER
|
|||
|
|
LINKER --> HUNK
|
|||
|
|
HUNK --> CODE & PPC
|
|||
|
|
HUNK --> SYMBOL & DEBUG
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Binary Identification
|
|||
|
|
|
|||
|
|
### C-Level Code (SAS/C-Compatible)
|
|||
|
|
|
|||
|
|
At the C level, StormC output is deliberately SAS/C-compatible:
|
|||
|
|
|
|||
|
|
```asm
|
|||
|
|
; StormC C function (looks identical to SAS/C):
|
|||
|
|
_my_c_function:
|
|||
|
|
LINK A5, #-$10 ; A5 frame pointer
|
|||
|
|
MOVEM.L D2-D7/A2-A4, -(SP) ; 9-reg save — same as SAS/C
|
|||
|
|
; ... function body ...
|
|||
|
|
MOVEM.L (SP)+, D2-D7/A2-A4
|
|||
|
|
UNLK A5
|
|||
|
|
RTS
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**How to distinguish from SAS/C**: Without symbols, C-level StormC code is nearly indistinguishable from SAS/C. Look for:
|
|||
|
|
1. **Project metadata in HUNK_DEBUG** — StormC embeds source file paths and project names
|
|||
|
|
2. **StormC-specific startup code** — different library open sequence
|
|||
|
|
3. **C++ markers** — if you see C++ constructs with non-GCC mangling, it's StormC
|
|||
|
|
|
|||
|
|
### C++ Level — Where StormC Diverges
|
|||
|
|
|
|||
|
|
StormC++ uses its own ABI:
|
|||
|
|
|
|||
|
|
```asm
|
|||
|
|
; StormC++ virtual method dispatch (different from GCC!):
|
|||
|
|
MOVEA.L obj_ptr(FP), A0 ; A0 = object pointer
|
|||
|
|
MOVE.L (A0), D0 ; D0 = vtable pointer (at offset +$00)
|
|||
|
|
MOVEA.L D0, A1
|
|||
|
|
JSR $XX(A1) ; call virtual method at vtable[XX]
|
|||
|
|
; No offset_to_top, no RTTI pointer before vtable!
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Name Mangling — StormC vs GCC
|
|||
|
|
|
|||
|
|
| Construct | StormC++ Mangled | GCC 2.95.x Mangled |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `Window::Draw()` | `Draw__6Window` | `Draw__6Window` — *can be identical for simple cases* |
|
|||
|
|
| `Window::SetPos(int,int)` | `SetPos__6WindowFii` | `SetPos__6Windowii` (no `F`) |
|
|||
|
|
| `operator new(unsigned long)` | `__nw__FUl` | `__nw__FUl` (may match) |
|
|||
|
|
| Constructor | `__ct__6Window` | `__6Window` (GCC uses different prefix) |
|
|||
|
|
| Destructor | `__dt__6Window` | `__6Window` (GCC encodes in vtable entry type) |
|
|||
|
|
|
|||
|
|
**Key disambiguator**: StormC prepends `__ct__` and `__dt__` to constructor/destructor names. GCC encodes the constructor/destructor type in the vtable offset, not the name.
|
|||
|
|
|
|||
|
|
### Vtable Layout Differences
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
GCC 2.95.x vtable layout: StormC++ vtable layout:
|
|||
|
|
┌──────────────────────┐ ┌──────────────────────┐
|
|||
|
|
│ offset_to_top = 0 │ vtable[-2] │ (no offset_to_top) │
|
|||
|
|
├──────────────────────┤ ├──────────────────────┤
|
|||
|
|
│ RTTI pointer │ vtable[-1] │ (RTTI pointer or 0) │
|
|||
|
|
├──────────────────────┤ ← vptr ├──────────────────────┤ ← vptr
|
|||
|
|
│ virtual destructor │ vtable[0] │ first virtual method │ vtable[0]
|
|||
|
|
├──────────────────────┤ ├──────────────────────┤
|
|||
|
|
│ virtual method 1 │ vtable[1] │ second virtual meth │ vtable[1]
|
|||
|
|
├──────────────────────┤ ├──────────────────────┤
|
|||
|
|
│ ... │ │ ... │
|
|||
|
|
└──────────────────────┘ └──────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!WARNING]
|
|||
|
|
> StormC++ vtables start at the first virtual function. There is no `offset_to_top` field at `vtable[-2]`. If your struct layout assumes the GCC layout, all vtable offsets will be wrong by 2 entries.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Library Call Patterns
|
|||
|
|
|
|||
|
|
StormC uses SAS/C-compatible library calls:
|
|||
|
|
|
|||
|
|
```asm
|
|||
|
|
MOVEA.L _DOSBase, A6 ; load from global
|
|||
|
|
MOVE.L filename, D1
|
|||
|
|
MOVE.L #MODE_OLDFILE, D2
|
|||
|
|
JSR -$1E(A6) ; Open()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
The difference is in **how** `_DOSBase` is initialized — StormC's startup code may use different symbol naming or library open order.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## C++ Exception Handling
|
|||
|
|
|
|||
|
|
StormC 3.0+ supports C++ exceptions with a custom unwinding mechanism:
|
|||
|
|
|
|||
|
|
```asm
|
|||
|
|
; Exception handling setup (simplified):
|
|||
|
|
; StormC registers an exception handler frame on the stack:
|
|||
|
|
PEA .exception_handler ; handler address
|
|||
|
|
MOVE.L ___current_exception_frame, -(SP)
|
|||
|
|
MOVE.L SP, ___current_exception_frame
|
|||
|
|
|
|||
|
|
; ... try block code ...
|
|||
|
|
|
|||
|
|
; Cleanup on normal exit:
|
|||
|
|
MOVE.L (SP)+, ___current_exception_frame
|
|||
|
|
ADDQ.L #4, SP ; discard handler
|
|||
|
|
|
|||
|
|
.exception_handler:
|
|||
|
|
; Exception recovery code
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
This is structurally different from GCC's exception handling (which uses DWARF2 unwinding tables or setjmp/longjmp). In the binary, look for a global `___current_exception_frame` variable being pushed/popped in functions with try/catch blocks.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Startup Code
|
|||
|
|
|
|||
|
|
StormC's startup differs from SAS/C `c.o`:
|
|||
|
|
|
|||
|
|
```asm
|
|||
|
|
; StormC startup (typical pattern):
|
|||
|
|
_start:
|
|||
|
|
MOVEA.L 4.W, A6 ; SysBase
|
|||
|
|
MOVE.L A6, ___SysBase
|
|||
|
|
|
|||
|
|
; StormC may use different library open order:
|
|||
|
|
JSR ___OpenStormCLibs ; open DOS, Intuition, etc.
|
|||
|
|
|
|||
|
|
; C++ static constructors (if C++ code present):
|
|||
|
|
JSR ___init_cpp ; calls __ct__ functions
|
|||
|
|
|
|||
|
|
; Call main()
|
|||
|
|
BSR _main
|
|||
|
|
|
|||
|
|
; C++ static destructors:
|
|||
|
|
JSR ___exit_cpp ; calls __dt__ functions
|
|||
|
|
|
|||
|
|
; Cleanup
|
|||
|
|
JSR ___CloseStormCLibs
|
|||
|
|
MOVE.L D0, ___ReturnCode
|
|||
|
|
RTS
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Same C Function — StormC Output
|
|||
|
|
|
|||
|
|
```asm
|
|||
|
|
; CountWords() — StormC 4.0, C mode, -O2:
|
|||
|
|
; (Structurally identical to SAS/C — StormC's C codegen mirrors SAS/C)
|
|||
|
|
|
|||
|
|
_CountWords:
|
|||
|
|
LINK A5, #-$08
|
|||
|
|
MOVEM.L D2-D3, -(SP)
|
|||
|
|
|
|||
|
|
MOVEQ #0, D2 ; count
|
|||
|
|
MOVEQ #0, D3 ; in_word
|
|||
|
|
|
|||
|
|
MOVEA.L $08(A5), A0 ; str (arg1 at A5+8)
|
|||
|
|
|
|||
|
|
BRA.S .loop_test
|
|||
|
|
|
|||
|
|
.loop_body:
|
|||
|
|
MOVEQ #' ', D0
|
|||
|
|
CMP.B (A0), D0
|
|||
|
|
BEQ.S .not_word
|
|||
|
|
MOVEQ #'\t', D0
|
|||
|
|
CMP.B (A0), D0
|
|||
|
|
BEQ.S .not_word
|
|||
|
|
MOVEQ #'\n', D0
|
|||
|
|
CMP.B (A0), D0
|
|||
|
|
BEQ.S .not_word
|
|||
|
|
|
|||
|
|
TST.B D3
|
|||
|
|
BNE.S .next_char
|
|||
|
|
ADDQ.L #1, D2
|
|||
|
|
MOVEQ #1, D3
|
|||
|
|
BRA.S .next_char
|
|||
|
|
|
|||
|
|
.not_word:
|
|||
|
|
MOVEQ #0, D3
|
|||
|
|
|
|||
|
|
.next_char:
|
|||
|
|
ADDQ.L #1, A0
|
|||
|
|
|
|||
|
|
.loop_test:
|
|||
|
|
TST.B (A0)
|
|||
|
|
BNE.S .loop_body
|
|||
|
|
|
|||
|
|
MOVE.L D2, D0
|
|||
|
|
MOVEM.L (SP)+, D2-D3
|
|||
|
|
UNLK A5
|
|||
|
|
RTS
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Named Antipatterns
|
|||
|
|
|
|||
|
|
### "The GCC-C++ Assumption" — Using GCC Vtable Layout on StormC++
|
|||
|
|
|
|||
|
|
Applying GCC vtable offsets to StormC++ binaries will misidentify every virtual method by 2 slots and miss `offset_to_top`. Always determine the C++ compiler BEFORE applying vtable layout assumptions.
|
|||
|
|
|
|||
|
|
### "The StormC-C++ Silence" — Missing C++ in What Looks Like C
|
|||
|
|
|
|||
|
|
StormC C code looks identical to SAS/C. But if the binary was compiled with StormC++ (C++ mode), global constructors run before `main()`, exceptions unwind, and objects have vtables — all invisible at the C codegen level. Check `HUNK_SYMBOL` for `__ct__` and `__dt__` prefixes.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Pitfalls & Common Mistakes
|
|||
|
|
|
|||
|
|
### 1. Linking StormC++ Objects with GCC Code
|
|||
|
|
|
|||
|
|
StormC++ and GCC C++ share NO ABI compatibility. Name mangling, vtable layout, RTTI, and exception handling all differ. If you're patching a binary and need to add C++ code, you must use the same compiler that produced the original.
|
|||
|
|
|
|||
|
|
### 2. PowerPC Code Sections (StormC 3+)
|
|||
|
|
|
|||
|
|
```asm
|
|||
|
|
; In the HUNK structure, PPC code appears as a separate hunk type:
|
|||
|
|
; If your disassembler only handles HUNK_CODE ($03E9), PPC sections
|
|||
|
|
; will appear as unknown hunk types. StormC PPC sections use custom
|
|||
|
|
; hunk types for WarpOS/PowerUP code.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Use Cases
|
|||
|
|
|
|||
|
|
### Software Known to Be StormC-Compiled
|
|||
|
|
|
|||
|
|
| Application | Version | Notes |
|
|||
|
|
|---|---|---|
|
|||
|
|
| **AmigaWriter** | StormC 3/4 | Word processor with C++ document model |
|
|||
|
|
| **Various MUI applications** | StormC 3+ | MUI class wizard generated C++ classes |
|
|||
|
|
| **WarpOS/PowerUP software** | StormC 3+ | Mixed 68k/PPC binaries — check for PPC hunk sections |
|
|||
|
|
| **Late-era Amiga games** | StormC 3/4 | C++ game engines with 68k-optimized inner loops |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Historical Context
|
|||
|
|
|
|||
|
|
StormC arrived at a pivotal moment: the Amiga market had shrunk, SAS/C was abandoned after 6.58, and developers wanted a modern IDE. Haage & Partner (known for AmigaOS 3.5/3.9) positioned StormC as the future of native Amiga development. It offered features no other native compiler had: a GUI debugger, C++ with exceptions, PowerPC support, and integrated MUI class generation.
|
|||
|
|
|
|||
|
|
However, the PowerPC era fragmented quickly (WarpOS vs PowerUP), the Amiga market collapsed, and Haage & Partner ceased operations. StormC 4.0 was the last release. Today, GCC (cross-compilation) and VBCC dominate, but StormC binaries remain in the wild — particularly late-1998 to 2000 era C++ applications.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Modern Analogies
|
|||
|
|
|
|||
|
|
| StormC Concept | Modern Equivalent |
|
|||
|
|
|---|---|
|
|||
|
|
| Native IDE with built-in compiler | Xcode with Clang, Visual Studio with MSVC |
|
|||
|
|
| Proprietary C++ ABI | MSVC's C++ ABI (incompatible with Itanium/GCC ABI) |
|
|||
|
|
| Mixed 68k/PPC binaries | Universal Binaries (Intel + ARM) on macOS |
|
|||
|
|
| MUI class generation wizard | Qt Creator's class wizard, Visual Studio's MFC wizard |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## FPGA / Emulation Impact
|
|||
|
|
|
|||
|
|
- **PowerPC sections**: If the binary contains PPC hunk sections (StormC 3+), a 68k-only FPGA core cannot execute them — a PowerPC emulation layer (like WarpOS emulation in WinUAE) is required.
|
|||
|
|
- **C++ exception handling**: StormC's custom exception mechanism uses a linked list of exception frames on the stack — the 68000 core must support `MOVE.L SP, An` correctly (standard ISA support, no issues).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## FAQ
|
|||
|
|
|
|||
|
|
**Q: How do I tell StormC from SAS/C if both use LINK A5?**
|
|||
|
|
A: Check `HUNK_SYMBOL` — SAS/C uses `_name` with `=APS` stabs; StormC uses `__ct__`/`__dt__` prefixes for C++. Check `HUNK_DEBUG` for project metadata strings (StormC embeds source paths). Check startup code — StormC's `___OpenStormCLibs` vs SAS/C's `_OpenLibraries`.
|
|||
|
|
|
|||
|
|
**Q: Can I link StormC objects with SAS/C objects?**
|
|||
|
|
A: For C-only code, possibly yes if the calling conventions match. For C++ code, absolutely not — the ABIs are incompatible.
|
|||
|
|
|
|||
|
|
**Q: Does StormC support `__saveds`?**
|
|||
|
|
A: Yes — StormC supports SAS/C calling convention keywords for compatibility: `__saveds`, `__stdargs`, `__reg`, `__interrupt`.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## References
|
|||
|
|
|
|||
|
|
- [13_toolchain/stormc.md](../../../13_toolchain/stormc.md) — StormC usage and features
|
|||
|
|
- [compiler_fingerprints.md](../../compiler_fingerprints.md) — Quick identification
|
|||
|
|
- [cpp_vtables_reversing.md](../cpp_vtables_reversing.md) — C++ vtable layouts (GCC focus — StormC differences noted)
|
|||
|
|
- See also: [sasc.md](sasc.md), [gcc.md](gcc.md) — compare with other compilers
|