mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-13 00:26:28 +00:00
docs(amiga): complete AmigaOS 3.1/3.2 developer reference — 172 files across 17 sections
Comprehensive technical documentation covering: - Hardware: OCS/ECS/AGA custom chip registers, Copper & Blitter deep dives - Boot sequence: cold boot through startup-sequence - Binary format: HUNK executable spec, relocation, debug info - Linking & ABI: .fd files, LVO tables, register calling conventions - Exec kernel: tasks, interrupts, memory, signals, semaphores - AmigaDOS: file I/O, FFS/OFS layout, CLI/Shell scripting - Graphics: planar bitmaps, Copper programming, HAM/EHB modes - Intuition: screens, windows, IDCMP, BOOPSI - Devices: trackdisk, SCSI, serial, timer, audio, keyboard - Libraries: utility, expansion, IFFParse, locale, ARexx - Networking: bsdsocket API, SANA-II, TCP/IP stack comparison - Toolchain: GCC, vasm/vlink, SAS/C, NDK, debugging - Reverse engineering: IDA/Ghidra setup, compiler fingerprints, case studies - CPU & MMU: 68040/060 emulation libs, PMMU, cache management - Driver development: SANA-II, Picasso96/RTG, AHI audio All files include breadcrumb navigation. No local paths or proprietary content.
This commit is contained in:
parent
f07a368bf1
commit
21751c0025
172 changed files with 19701 additions and 0 deletions
296
05_reversing/static/api_call_identification.md
Normal file
296
05_reversing/static/api_call_identification.md
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Identifying OS API Calls in Disassembly
|
||||
|
||||
## Background: How AmigaOS Library Calls Work
|
||||
|
||||
Before diving into identification techniques, it helps to understand the mechanics from first principles.
|
||||
|
||||
### What is a Shared Library?
|
||||
|
||||
On AmigaOS, a **shared library** is a block of code loaded into RAM once and shared by every program that needs it. Programs don't link the OS code into their own executable — they call it indirectly at runtime. This keeps executables small and allows the OS to be upgraded without relinking every application.
|
||||
|
||||
Examples: `dos.library`, `graphics.library`, `intuition.library`.
|
||||
|
||||
### What is a Library Base?
|
||||
|
||||
When you open a library, exec returns a pointer to the **library base** — a `struct Library` that lives in RAM. Immediately *before* this pointer (at negative offsets) sits the **JMP table**: a sequence of `JMP <address>` instructions, one per library function.
|
||||
|
||||
```
|
||||
Memory layout:
|
||||
|
||||
lib_base - 30: JMP Open_impl ← first user function
|
||||
lib_base - 24: JMP Reserved
|
||||
lib_base - 18: JMP Expunge
|
||||
lib_base - 12: JMP Close
|
||||
lib_base - 6: JMP Open (standard)
|
||||
lib_base + 0: struct Library ← pointer returned by OpenLibrary()
|
||||
lib_base + N: private library data
|
||||
```
|
||||
|
||||
Every program that wants to call `dos.library Open()` stores the library base somewhere and calls `JSR -30(A6)`, where A6 holds the library base.
|
||||
|
||||
---
|
||||
|
||||
## What is an LVO?
|
||||
|
||||
**LVO** stands for **Library Vector Offset**. It is the negative byte offset from the library base to a specific function's JMP table slot.
|
||||
|
||||
The formula is:
|
||||
```
|
||||
LVO = −6 × (slot_index + 1)
|
||||
|
||||
slot 0 (Open standard): −6
|
||||
slot 1 (Close standard): −12
|
||||
slot 2 (Expunge): −18
|
||||
slot 3 (Reserved): −24
|
||||
slot 4 (first user fn): −30 ← dos.library Open()
|
||||
slot 5: −36 ← dos.library Close()
|
||||
...
|
||||
```
|
||||
|
||||
So `JSR -30(A6)` means "call the function at LVO −30 in the library whose base is in A6." Every unique LVO in every library maps to exactly one function.
|
||||
|
||||
### Why Negative Offsets?
|
||||
|
||||
The JMP table grows **downward** in memory from the library base. Using negative offsets means programs only need to store a single pointer (the library base) and derive all function entry points from it with a constant displacement. This is the same trick used by C++ vtables.
|
||||
|
||||
---
|
||||
|
||||
## What is an .fd File?
|
||||
|
||||
**`.fd` files** (Function Descriptor files) are part of the Amiga NDK (Native Developer Kit). They are simple text files that declare every public function in a library: its name, argument registers, and LVO (called the **bias** in `.fd` terminology).
|
||||
|
||||
### Example: `dos_lib.fd` (excerpt)
|
||||
|
||||
```
|
||||
##base _DOSBase
|
||||
##bias 30
|
||||
##public
|
||||
Open(name,accessMode)(d1,d2)
|
||||
##bias 36
|
||||
Close(file)(d1)
|
||||
##bias 42
|
||||
Read(file,buffer,length)(d1,d2,d3)
|
||||
##bias 48
|
||||
Write(file,buffer,length)(d1,d2,d3)
|
||||
##bias 54
|
||||
Input()(-)
|
||||
##bias 60
|
||||
Output()(-)
|
||||
##bias 138
|
||||
Delay(timeout)(d1)
|
||||
```
|
||||
|
||||
Reading this:
|
||||
- `##base _DOSBase` — the global variable that holds the library base
|
||||
- `##bias 30` — the **positive** bias; the actual call offset is `−30`
|
||||
- `Open(name,accessMode)(d1,d2)` — function name, argument names, and the registers each argument goes in
|
||||
|
||||
So `##bias 30` means LVO `−30`. When you see `JSR (-30,A6)` in disassembly and A6 holds `DOSBase`, that is `dos.library Open()`.
|
||||
|
||||
### Where are .fd files?
|
||||
|
||||
In the NDK39 distribution at:
|
||||
```
|
||||
NDK39/
|
||||
fd/
|
||||
dos_lib.fd
|
||||
exec_lib.fd
|
||||
graphics_lib.fd
|
||||
intuition_lib.fd
|
||||
...
|
||||
```
|
||||
|
||||
They are plain text — open any with a text editor.
|
||||
|
||||
---
|
||||
|
||||
## The Canonical Call Pattern
|
||||
|
||||
Every AmigaOS library call in disassembly looks like this:
|
||||
|
||||
```asm
|
||||
MOVEA.L (_DOSBase).L, A6 ; (1) load the library base into A6
|
||||
JSR (-30,A6) ; (2) call function at LVO -30 = Open()
|
||||
; D0 now contains the return value
|
||||
```
|
||||
|
||||
Sometimes the base is loaded once and reused:
|
||||
```asm
|
||||
MOVEA.L (_DOSBase).L, A6
|
||||
JSR (-30,A6) ; Open
|
||||
...
|
||||
; A6 still holds DOSBase — no reload needed
|
||||
JSR (-48,A6) ; Write
|
||||
```
|
||||
|
||||
And for `exec.library`, programs often use the fixed address `$4` directly:
|
||||
```asm
|
||||
MOVEA.L 4.W, A6 ; exec.library base is always at $4
|
||||
MOVEQ #40, D0 ; minimum version
|
||||
LEA _str_dos(PC), A1 ; "dos.library"
|
||||
JSR (-552,A6) ; exec.library OpenLibrary(A1,D0)
|
||||
MOVE.L D0, _DOSBase ; save result for later
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step-by-Step: Tracing OS Calls in IDA Pro
|
||||
|
||||
### Step 1 — Find OpenLibrary calls at startup
|
||||
|
||||
Search for `JSR (-552,A6)` — that is always `exec.library OpenLibrary`. The instruction immediately before it loads A1 with a library name string.
|
||||
|
||||
```asm
|
||||
LEA (_str_dos).L, A1 ; → xref this to see "dos.library"
|
||||
MOVEQ #40, D0
|
||||
MOVEA.L 4.W, A6
|
||||
JSR (-552,A6) ; OpenLibrary("dos.library", 40)
|
||||
MOVE.L D0, (_DOSBase).L ; ← label this global "_DOSBase"
|
||||
```
|
||||
|
||||
Press `N` in IDA on the `_DOSBase` write to name the variable.
|
||||
|
||||
### Step 2 — Find all reads of that library base
|
||||
|
||||
Press `X` on `_DOSBase` to show all cross-references. Each xref is either a write (the open) or a read (before a JSR).
|
||||
|
||||
### Step 3 — Resolve each JSR to a function name
|
||||
|
||||
For each `JSR (-N,A6)` where A6 holds `_DOSBase`:
|
||||
1. Look up `N` in `dos_lib.fd` under `##bias N`
|
||||
2. Read the function name
|
||||
3. Press `N` in IDA on the JSR instruction's displacement to annotate it
|
||||
|
||||
After annotation:
|
||||
```asm
|
||||
MOVEA.L (_DOSBase).L, A6
|
||||
JSR (Open,A6) ; was: JSR (-30,A6)
|
||||
```
|
||||
|
||||
### Step 4 — Note argument registers
|
||||
|
||||
From `dos_lib.fd`:
|
||||
```
|
||||
Open(name,accessMode)(d1,d2)
|
||||
```
|
||||
So immediately before the JSR:
|
||||
- `D1` is loaded with the filename pointer
|
||||
- `D2` is loaded with the access mode (`MODE_OLDFILE` = 1005, `MODE_NEWFILE` = 1006)
|
||||
|
||||
---
|
||||
|
||||
## Quick LVO Reference: dos.library
|
||||
|
||||
| LVO | Bias | Function | Args | Return |
|
||||
|---|---|---|---|---|
|
||||
| −30 | 30 | `Open` | D1=name, D2=mode | D0=BPTR handle (0=fail) |
|
||||
| −36 | 36 | `Close` | D1=handle | — |
|
||||
| −42 | 42 | `Read` | D1=handle, D2=buf, D3=len | D0=actual (−1=fail) |
|
||||
| −48 | 48 | `Write` | D1=handle, D2=buf, D3=len | D0=actual |
|
||||
| −54 | 54 | `Input` | — | D0=stdin handle |
|
||||
| −60 | 60 | `Output` | — | D0=stdout handle |
|
||||
| −66 | 66 | `IoErr` | — | D0=last error code |
|
||||
| −78 | 78 | `CreateDir` | D1=name | D0=lock |
|
||||
| −84 | 84 | `CurrentDir` | D1=lock | D0=old lock |
|
||||
| −90 | 90 | `Lock` | D1=name, D2=mode | D0=lock |
|
||||
| −96 | 96 | `UnLock` | D1=lock | — |
|
||||
| −102 | 102 | `DupLock` | D1=lock | D0=new lock |
|
||||
| −108 | 108 | `Examine` | D1=lock, D2=fib | D0=bool |
|
||||
| −120 | 120 | `ExNext` | D1=lock, D2=fib | D0=bool |
|
||||
| −126 | 126 | `Info` | D1=lock, D2=infoblock | D0=bool |
|
||||
| −132 | 132 | `Execute` | D1=string, D2=input, D3=output | D0=bool |
|
||||
| −138 | 138 | `Delay` | D1=ticks | — |
|
||||
| −144 | 144 | `DateStamp` | D1=datestamp | D0=datestamp |
|
||||
| −150 | 150 | `Exit` | D1=returnCode | — |
|
||||
| −156 | 156 | `LoadSeg` | D1=name | D0=seglist |
|
||||
| −162 | 162 | `UnLoadSeg` | D1=seglist | — |
|
||||
|
||||
## Quick LVO Reference: exec.library (selected)
|
||||
|
||||
| LVO | Bias | Function | Args | Return |
|
||||
|---|---|---|---|---|
|
||||
| −6 | 6 | `Supervisor` | A5=func | — |
|
||||
| −120 | 120 | `Forbid` | — | — |
|
||||
| −126 | 126 | `Permit` | — | — |
|
||||
| −132 | 132 | `Disable` | — | — |
|
||||
| −138 | 138 | `Enable` | — | — |
|
||||
| −168 | 168 | `FindTask` | A1=name | D0=task |
|
||||
| −174 | 174 | `SetTaskPri` | A1=task, D0=pri | D0=old |
|
||||
| −192 | 192 | `Signal` | A1=task, D0=signals | — |
|
||||
| −198 | 198 | `AllocMem` | D0=size, D1=attrs | D0=ptr |
|
||||
| −210 | 210 | `FreeMem` | A1=ptr, D0=size | — |
|
||||
| −234 | 234 | `Wait` | D0=signals | D0=set |
|
||||
| −270 | 270 | `AddPort` | A1=port | — |
|
||||
| −276 | 276 | `FindName` | A0=list, A1=name | D0=node |
|
||||
| −378 | 378 | `PutMsg` | A0=port, A1=msg | — |
|
||||
| −384 | 384 | `GetMsg` | A0=port | D0=msg |
|
||||
| −408 | 408 | `WaitPort` | A0=port | D0=msg |
|
||||
| −420 | 420 | `SetFunction` | A1=lib, A0=lvo, D0=func | D0=old |
|
||||
| −552 | 552 | `OpenLibrary` | A1=name, D0=ver | D0=base |
|
||||
| −558 | 558 | `CloseLibrary` | A1=lib | — |
|
||||
|
||||
Full tables: [`04_linking_and_libraries/lvo_table.md`](../../../04_linking_and_libraries/lvo_table.md)
|
||||
|
||||
---
|
||||
|
||||
## Automated IDA Script
|
||||
|
||||
```python
|
||||
# apply_dos_lvos.py — run from IDA's File → Script command
|
||||
import idaapi, idc, idautils
|
||||
|
||||
DOS_LVO = {
|
||||
-30: "Open", -36: "Close", -42: "Read", -48: "Write",
|
||||
-54: "Input", -60: "Output", -66: "IoErr", -132: "Execute",
|
||||
-138: "Delay", -156: "LoadSeg",-162: "UnLoadSeg",
|
||||
}
|
||||
|
||||
EXEC_LVO = {
|
||||
-120: "Forbid", -126: "Permit", -132: "Disable", -138: "Enable",
|
||||
-198: "AllocMem", -210: "FreeMem",-234: "Wait",
|
||||
-378: "PutMsg", -384: "GetMsg", -408: "WaitPort",
|
||||
-420: "SetFunction", -552: "OpenLibrary", -558: "CloseLibrary",
|
||||
}
|
||||
|
||||
def apply_lvos(lib_global_name, lvo_map):
|
||||
ea = idc.get_name_ea_simple(lib_global_name)
|
||||
if ea == idc.BADADDR:
|
||||
print(f"Global {lib_global_name} not found")
|
||||
return
|
||||
lib_ptr = idc.get_wide_dword(ea)
|
||||
for lvo, name in lvo_map.items():
|
||||
jmp_ea = lib_ptr + lvo
|
||||
# JMP ABS.L opcode: 4EF9, target at +2
|
||||
target = idc.get_wide_dword(jmp_ea + 2)
|
||||
if target != 0xFFFFFFFF:
|
||||
idc.set_name(target, f"{lib_global_name[1:]}_{name}",
|
||||
idaapi.SN_NOWARN)
|
||||
print(f" {lvo:+5d} → {name} @ {target:#010x}")
|
||||
|
||||
apply_lvos("_DOSBase", DOS_LVO)
|
||||
apply_lvos("_SysBase", EXEC_LVO)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Identifying Unknown Library Calls
|
||||
|
||||
If you encounter `JSR (-N,A6)` and don't know which library A6 holds:
|
||||
|
||||
1. Trace A6 backward in IDA (`View → Register tracking`) to its last write
|
||||
2. The write is `MOVEA.L (some_global).L, A6` — name that global
|
||||
3. Trace *that* global backward to its `MOVE.L D0, ...` after an `OpenLibrary` call
|
||||
4. The string argument to OpenLibrary names the library
|
||||
5. Look up LVO `−N` in the matching `.fd` file
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `fd/` directory — all library `.fd` files (plain text, open in any editor)
|
||||
- `04_linking_and_libraries/lvo_table.md` — formatted LVO tables
|
||||
- `static/library_jmp_table.md` — JMP table layout and IDA scripting
|
||||
- `04_linking_and_libraries/fd_files.md` — `.fd` file format specification
|
||||
- ADCD 2.1 Autodocs online: http://amigadev.elowar.com/read/ADCD_2.1/
|
||||
123
05_reversing/static/hunk_reconstruction.md
Normal file
123
05_reversing/static/hunk_reconstruction.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Static Analysis — HUNK Reconstruction
|
||||
|
||||
## Overview
|
||||
|
||||
Manually parsing a HUNK binary from a hex dump is a foundational Amiga RE skill. It reveals segment boundaries, symbol tables, and relocation data before any tool processing.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Identify Magic and Header
|
||||
|
||||
```bash
|
||||
xxd mybinary | head -8
|
||||
```
|
||||
|
||||
```
|
||||
00000000: 0000 03f3 ← HUNK_HEADER magic
|
||||
00000004: 0000 0000 ← resident library list (always 0)
|
||||
00000008: 0000 0003 ← num_hunks = 3
|
||||
0000000c: 0000 0000 ← first_hunk = 0
|
||||
00000010: 0000 0002 ← last_hunk = 2
|
||||
00000014: 0000 0200 ← hunk 0: 0x200 longs = 0x800 bytes (code)
|
||||
00000018: 0000 0020 ← hunk 1: 0x20 longs = 0x80 bytes (data)
|
||||
0000001c: 0000 0010 ← hunk 2: 0x10 longs = 0x40 bytes (BSS)
|
||||
```
|
||||
|
||||
Each size longword: **bits 31–30** = memory type flag, **bits 29–0** = size in longs.
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Walk the Hunk Stream
|
||||
|
||||
After the header, scan longword-by-longword:
|
||||
|
||||
```
|
||||
$000003E9 → HUNK_CODE: read next longword = size, then size*4 bytes
|
||||
$000003EA → HUNK_DATA: same
|
||||
$000003EB → HUNK_BSS: read size longword only (no data)
|
||||
$000003EC → HUNK_RELOC32: read pairs until terminator 0
|
||||
$000003F0 → HUNK_SYMBOL: read (name_len, name, value) until name_len=0
|
||||
$000003F1 → HUNK_DEBUG: read size longword, skip size*4 bytes
|
||||
$000003F2 → HUNK_END: advance to next hunk
|
||||
```
|
||||
|
||||
### Grep for hunk boundaries
|
||||
```bash
|
||||
xxd mybinary | grep -E "0003 (e9|ea|eb|ec|f0|f1|f2|f3)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Extract HUNK_SYMBOL Table
|
||||
|
||||
```bash
|
||||
# find HUNK_SYMBOL ($3F0)
|
||||
python3 - <<'EOF'
|
||||
import struct, sys
|
||||
|
||||
data = open("mybinary", "rb").read()
|
||||
i = 0
|
||||
while i < len(data) - 4:
|
||||
tag = struct.unpack_from(">I", data, i)[0]
|
||||
if tag == 0x3F0: # HUNK_SYMBOL
|
||||
print(f"HUNK_SYMBOL at offset {i:#x}")
|
||||
i += 4
|
||||
while True:
|
||||
nlen = struct.unpack_from(">I", data, i)[0]
|
||||
if nlen == 0: break
|
||||
name = data[i+4 : i+4+nlen*4].rstrip(b"\x00").decode("ascii","replace")
|
||||
val = struct.unpack_from(">I", data, i+4+nlen*4)[0]
|
||||
print(f" {name} = {val:#x}")
|
||||
i += 4 + nlen*4 + 4
|
||||
else:
|
||||
i += 4
|
||||
EOF
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Resolve HUNK_EXT Imports/Exports
|
||||
|
||||
In object files (HUNK_UNIT), `HUNK_EXT` carries import/export tables:
|
||||
|
||||
```python
|
||||
# Simplified HUNK_EXT parser
|
||||
elif tag == 0x3EF: # HUNK_EXT
|
||||
i += 4
|
||||
while True:
|
||||
word = struct.unpack_from(">I", data, i)[0]
|
||||
if word == 0: break
|
||||
ext_type = (word >> 24) & 0xFF
|
||||
nlen = word & 0x00FFFFFF
|
||||
name = data[i+4 : i+4+nlen*4].rstrip(b"\x00").decode("ascii","replace")
|
||||
i += 4 + nlen * 4
|
||||
if ext_type in (1, 2): # EXT_DEF / EXT_ABS
|
||||
val = struct.unpack_from(">I", data, i)[0]; i += 4
|
||||
print(f" EXPORT {name} = {val:#x}")
|
||||
elif ext_type == 0x81: # EXT_REF32
|
||||
nrefs = struct.unpack_from(">I", data, i)[0]; i += 4
|
||||
refs = struct.unpack_from(f">{nrefs}I", data, i); i += nrefs*4
|
||||
print(f" IMPORT {name} @ {[hex(r) for r in refs]}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Annotating Reloc Patches in IDA
|
||||
|
||||
After loading the HUNK file in IDA:
|
||||
1. `View → Open Subviews → Fixups` — lists all HUNK_RELOC32 patch sites
|
||||
2. Press `F5` on a relocated longword to see the computed address
|
||||
3. Use `Edit → Operand type → Offset (data segment)` to annotate as a pointer
|
||||
|
||||
IDA's Amiga loader applies relocations automatically, so all cross-hunk pointers show their final resolved addresses.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `dos/doshunks.h`
|
||||
- `hunk_format.md` — hunk type code reference
|
||||
- `hunk_relocation.md` — HUNK_RELOC32 mechanics
|
||||
- vlink documentation (HUNK appendix): http://sun.hasenbraten.de/vlink/
|
||||
142
05_reversing/static/library_jmp_table.md
Normal file
142
05_reversing/static/library_jmp_table.md
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Reconstructing Library JMP Tables
|
||||
|
||||
## Overview
|
||||
|
||||
Every AmigaOS library has a **JMP table** at negative offsets from its base pointer. Reconstructing this table maps LVOs to function names and is essential for identifying all OS calls made by a binary under analysis.
|
||||
|
||||
---
|
||||
|
||||
## JMP Table Layout
|
||||
|
||||
```
|
||||
lib_base - N*6: JFF xxxx xxxx ; JMP to function N (6 bytes)
|
||||
...
|
||||
lib_base - 24: JMP Reserved()
|
||||
lib_base - 18: JMP Expunge()
|
||||
lib_base - 12: JMP Close()
|
||||
lib_base - 6: JMP Open()
|
||||
lib_base + 0: struct Library ; lib_Node, lib_Version, ...
|
||||
```
|
||||
|
||||
Each entry is a 68k `JMP (abs.l)` — opcode `4EF9` followed by a 4-byte absolute address, totalling 6 bytes. Hence LVO = `−6 × slot_index`.
|
||||
|
||||
---
|
||||
|
||||
## Finding the Library Base
|
||||
|
||||
### From SysBase LibList
|
||||
|
||||
The `exec.library` maintains a doubly-linked list at `SysBase→LibList`:
|
||||
|
||||
```c
|
||||
struct ExecBase {
|
||||
...
|
||||
struct List LibList; /* offset +378 — list of open libraries */
|
||||
...
|
||||
};
|
||||
|
||||
/* Walk the list: */
|
||||
struct Node *n = SysBase->LibList.lh_Head;
|
||||
while (n->ln_Succ) {
|
||||
struct Library *lib = (struct Library *)n;
|
||||
printf("%s v%d\n", lib->lib_Node.ln_Name, lib->lib_Version);
|
||||
n = n->ln_Succ;
|
||||
}
|
||||
```
|
||||
|
||||
### In IDA Pro
|
||||
|
||||
After loading, `SysBase` is at `$4`. Use `Edit → Segments → Create Segment` pointed at `$4` with type `WORD` to follow the pointer to `ExecBase`. Then navigate to `LibList` at offset `+0x17A` and walk the linked list.
|
||||
|
||||
---
|
||||
|
||||
## Reading the JMP Table in IDA
|
||||
|
||||
1. Know the library base address (e.g., `DOSBase` from the `OpenLibrary` result)
|
||||
2. Navigate to `lib_base - 6` — first user function slot
|
||||
3. IDA shows `JMP sub_XXXXXX` — the target is the actual function implementation
|
||||
4. Rename each `sub_` with the function name from the LVO table
|
||||
|
||||
### Automated Script: `apply_lvo_names.py`
|
||||
|
||||
```python
|
||||
import idaapi, idc
|
||||
|
||||
LVO_DOS = {
|
||||
-30: "Open", # LVO -30 = Open(name, mode) d1/d2
|
||||
-36: "Close",
|
||||
-42: "Read",
|
||||
-48: "Write",
|
||||
-54: "Input",
|
||||
-60: "Output",
|
||||
-126: "WaitForChar",
|
||||
-138: "Delay",
|
||||
# ... extend from dos_lib.fd
|
||||
}
|
||||
|
||||
DOS_BASE = idc.get_name_ea_simple("_DOSBase")
|
||||
dos_ptr = idc.get_wide_dword(DOS_BASE)
|
||||
|
||||
for lvo, name in LVO_DOS.items():
|
||||
jmp_entry = dos_ptr + lvo
|
||||
# read the JMP target: opcode at jmp_entry is 4EF9, target at +2
|
||||
target = idc.get_wide_dword(jmp_entry + 2)
|
||||
idc.set_name(target, f"dos_{name}", idaapi.SN_NOWARN)
|
||||
print(f"LVO {lvo:+d}: {name} → {target:#010x}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mapping LVO → Function via `.fd` Files
|
||||
|
||||
NDK39 `.fd` files define the exact register assignments and bias (LVO offset):
|
||||
|
||||
```
|
||||
## NDK39/fd/dos_lib.fd (excerpt)
|
||||
##base _DOSBase
|
||||
##bias 30
|
||||
##public
|
||||
Open(name,accessMode)(d1,d2)
|
||||
##bias 36
|
||||
Close(file)(d1)
|
||||
##bias 42
|
||||
Read(file,buffer,length)(d1,d2,d3)
|
||||
##bias 48
|
||||
Write(file,buffer,length)(d1,d2,d3)
|
||||
```
|
||||
|
||||
The `##bias` value **is** the positive LVO — the actual call offset is `−bias`.
|
||||
|
||||
---
|
||||
|
||||
## JSR −LVO(A6) Pattern in Disassembly
|
||||
|
||||
```asm
|
||||
; Typical OS call site in disassembly:
|
||||
MOVEA.L (_DOSBase).L, A6
|
||||
JSR (-30,A6) ; Open(d1=name, d2=mode)
|
||||
; D0 = file handle (BPTR) or 0 on error
|
||||
```
|
||||
|
||||
In IDA, this appears as `jsr ($fffffffe2,a6)` with displacement `-30` (`$FFFFFFE2` in two's complement 16-bit). Applying LVO names makes this `jsr (Open,a6)`.
|
||||
|
||||
---
|
||||
|
||||
## Common Library Bases and LVO Tables
|
||||
|
||||
See [`../../../04_linking_and_libraries/lvo_table.md`](../../../04_linking_and_libraries/lvo_table.md) for complete LVO offset tables for:
|
||||
- `exec.library`
|
||||
- `dos.library`
|
||||
- `graphics.library`
|
||||
- `intuition.library`
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `fd/` directory — all library `.fd` files
|
||||
- `04_linking_and_libraries/lvo_table.md`
|
||||
- ADCD 2.1: `Libraries_Manual_guide/`
|
||||
- IDA Pro scripting: `idc.py` reference
|
||||
156
05_reversing/static/m68k_codegen_patterns.md
Normal file
156
05_reversing/static/m68k_codegen_patterns.md
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Compiler-Specific Code Generation Patterns
|
||||
|
||||
## Overview
|
||||
|
||||
Different Amiga compilers produce distinct code signatures. Recognising these helps quickly identify compiler origin, locate `main()`, and distinguish OS glue from application logic.
|
||||
|
||||
---
|
||||
|
||||
## SAS/C 6.x Patterns
|
||||
|
||||
### Function Prologue / Epilogue
|
||||
|
||||
```asm
|
||||
; Non-leaf function with local vars:
|
||||
LINK A5, #-N ; allocate N bytes of locals on stack
|
||||
MOVEM.L D2-D7/A2-A3, -(SP) ; save preserved registers
|
||||
...
|
||||
MOVEM.L (SP)+, D2-D7/A2-A3
|
||||
UNLK A5
|
||||
RTS
|
||||
|
||||
; Leaf function (no locals, no preserved regs):
|
||||
; — no LINK, pure computation, ends in RTS
|
||||
```
|
||||
|
||||
### D0 Save Pattern
|
||||
|
||||
SAS/C saves D0 at the start of functions that need it later:
|
||||
|
||||
```asm
|
||||
MOVE.L D0, -(SP) ; save return value from previous call
|
||||
JSR another_func
|
||||
MOVE.L (SP)+, D0 ; restore
|
||||
```
|
||||
|
||||
### Register Argument Passing
|
||||
|
||||
SAS/C passes OS call args via `#pragma amicall` register placement. Inside application functions, SAS/C uses a **stack-based C ABI** (unlike OS calls):
|
||||
|
||||
```asm
|
||||
; C function call in SAS/C: push args right-to-left
|
||||
MOVE.L arg3, -(SP)
|
||||
MOVE.L arg2, -(SP)
|
||||
MOVE.L arg1, -(SP)
|
||||
JSR _myfunction
|
||||
ADDQ.L #12, SP ; clean args (caller cleanup)
|
||||
```
|
||||
|
||||
### String Constants
|
||||
|
||||
SAS/C places string literals in the **data hunk**, referenced via absolute addresses requiring `HUNK_RELOC32`:
|
||||
|
||||
```asm
|
||||
MOVE.L #_str_hello, D1 ; absolute address → RELOC32 entry
|
||||
MOVEA.L _DOSBase, A6
|
||||
JSR (-48,A6) ; Write(stdout, "hello", ...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GCC (m68k-amigaos / bebbo) Patterns
|
||||
|
||||
### PC-Relative String Access
|
||||
|
||||
GCC uses PC-relative addressing by default, eliminating most HUNK_RELOC32 entries:
|
||||
|
||||
```asm
|
||||
LEA _str_hello(PC), A0 ; PC-relative — no reloc needed
|
||||
```
|
||||
|
||||
### No Frame Pointer (Default)
|
||||
|
||||
```asm
|
||||
; GCC -O2 leaf function:
|
||||
MOVEM.L D2/A2, -(SP) ; only save what's used
|
||||
...
|
||||
MOVEM.L (SP)+, D2/A2
|
||||
RTS
|
||||
; No LINK/UNLK — pure register allocation
|
||||
```
|
||||
|
||||
### GCC Function Prologues
|
||||
|
||||
```asm
|
||||
; Non-leaf with GCC -fno-omit-frame-pointer:
|
||||
LINK A6, #-N ; note: GCC uses A6 as frame pointer here
|
||||
; (conflicts with OS library base usage — rare)
|
||||
; More common with -O2:
|
||||
SUBQ.L #N, SP ; allocate locals without frame pointer
|
||||
```
|
||||
|
||||
### Integer Division / Modulo
|
||||
|
||||
GCC emits calls to `__divsi3`, `__modsi3` from `libgcc`:
|
||||
|
||||
```asm
|
||||
JSR ___divsi3 ; 32-bit signed divide (libgcc helper)
|
||||
; operands in D0:D1, result in D0
|
||||
```
|
||||
|
||||
SAS/C uses the 68k `DIVS.L` instruction directly (available on 020+) or `DIVS.W`.
|
||||
|
||||
---
|
||||
|
||||
## VBCC Patterns
|
||||
|
||||
VBCC generates very tight code with minimal function overhead:
|
||||
|
||||
```asm
|
||||
; VBCC typical function (no frame pointer, minimal saves):
|
||||
MOVEM.L D2-D4, -(SP)
|
||||
...
|
||||
MOVEM.L (SP)+, D2-D4
|
||||
RTS
|
||||
```
|
||||
|
||||
VBCC's OS call inline expansion looks identical to GCC's inline-asm stubs.
|
||||
|
||||
---
|
||||
|
||||
## Distinguishing Compiler Artefacts from Logic
|
||||
|
||||
| Pattern | Compiler | Meaning |
|
||||
|---|---|---|
|
||||
| `LINK A5, #-N` | SAS/C | Function with locals |
|
||||
| `LINK A6, #-N` | GCC (rare) | Frame pointer mode |
|
||||
| `JSR ___divsi3` | GCC | Software 32-bit division |
|
||||
| `DIVS.L D1, D0` | SAS/C (020+) | Hardware divide |
|
||||
| `MULS.L D1, D0` | SAS/C (020+) | Hardware multiply |
|
||||
| `LEA str(PC), A0` | GCC | PC-relative string ref |
|
||||
| `MOVE.L #_str, D1` | SAS/C | Absolute string ref (reloc'd) |
|
||||
| `JSR _main` | Startup | C main() entry point |
|
||||
| `MOVE.L 4.W, A6` | Startup | SysBase load |
|
||||
| `JSR -552(A6)` | Any | exec.library OpenLibrary |
|
||||
|
||||
---
|
||||
|
||||
## Locating `main()` via Startup Skip
|
||||
|
||||
After identifying the startup stub (`MOVE.L 4.W, A6` → `JSR _OpenLibraries`):
|
||||
|
||||
1. Find the first `JSR` or `BSR` after library opens
|
||||
2. That target is `__main` or directly `_main`
|
||||
3. If `__main`: follow its internal `JSR _main` call
|
||||
4. Label the target `main` in IDA
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- SAS/C 6.x manual — code generation chapter
|
||||
- GCC for m68k: https://github.com/bebbo/amiga-gcc
|
||||
- VBCC manual: http://www.compilers.de/vbcc.html
|
||||
- *Amiga ROM Kernel Reference Manual: Libraries* — register conventions
|
||||
123
05_reversing/static/string_xref_analysis.md
Normal file
123
05_reversing/static/string_xref_analysis.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# String Cross-Reference Analysis
|
||||
|
||||
## Overview
|
||||
|
||||
String references are the fastest entry point into a disassembled Amiga binary. Library name strings, error messages, and format strings immediately reveal program intent and identify OS API usage patterns.
|
||||
|
||||
---
|
||||
|
||||
## Finding Library Name Strings
|
||||
|
||||
Every `OpenLibrary` call is preceded by a string reference. Search for `".library"`:
|
||||
|
||||
```bash
|
||||
# Host: grep for library name strings in binary
|
||||
strings mybinary | grep -i library
|
||||
# → "dos.library", "graphics.library", "intuition.library", ...
|
||||
```
|
||||
|
||||
In IDA:
|
||||
1. `View → Open Subviews → Strings` (Shift+F12)
|
||||
2. Search for `.library`
|
||||
3. Press `X` on any result to see all cross-references
|
||||
4. Each xref leads to a `LEA str(PC), A1` or `MOVE.L #str, A1` before a `JSR -552(A6)` (OpenLibrary)
|
||||
|
||||
---
|
||||
|
||||
## Tracing OpenLibrary Calls to Their Targets
|
||||
|
||||
```asm
|
||||
; Pattern to find:
|
||||
LEA (_str_dos).L, A1 ; "dos.library"
|
||||
MOVEQ #36, D0 ; min version
|
||||
MOVEA.L 4.W, A6 ; exec.library
|
||||
JSR (-552,A6) ; OpenLibrary → D0 = DOSBase
|
||||
MOVE.L D0, (_DOSBase).L ; store for later use
|
||||
```
|
||||
|
||||
Xref `_str_dos` → find this block → identify the stored library base variable → label it `_DOSBase`.
|
||||
|
||||
---
|
||||
|
||||
## Using HUNK_SYMBOL Names as Seed Labels
|
||||
|
||||
If `HUNK_SYMBOL` is present (debug build), IDA auto-applies names. These seed labels help bootstrap analysis:
|
||||
|
||||
1. `View → Open Subviews → Names` → look for any `_` prefixed symbols
|
||||
2. Named functions often call unnamed helpers nearby — work outward
|
||||
3. String xrefs from named functions propagate names further
|
||||
|
||||
---
|
||||
|
||||
## Error Message Strings
|
||||
|
||||
Error/diagnostic strings reveal program flow:
|
||||
|
||||
```asm
|
||||
; Common pattern:
|
||||
LEA _err_nolib(PC), A0 ; "Can't open dos.library"
|
||||
MOVEA.L _DOSBase, A6
|
||||
JSR (-60,A6) ; Output() → D0 = stdout
|
||||
MOVE.L D0, D1
|
||||
LEA _err_nolib(PC), A2
|
||||
MOVE.L A2, D2
|
||||
MOVEQ #_err_nolib_end - _err_nolib, D3
|
||||
JSR (-48,A6) ; Write(stdout, msg, len)
|
||||
```
|
||||
|
||||
The error string tells you exactly what this code path handles.
|
||||
|
||||
---
|
||||
|
||||
## Format String Xref Analysis (printf)
|
||||
|
||||
SAS/C `printf` style calls via `dos.library VPrintf`:
|
||||
|
||||
```asm
|
||||
MOVEA.L _DOSBase, A6
|
||||
LEA _fmt_str(PC), A0 ; "Error: %ld\n"
|
||||
MOVE.L A0, D1
|
||||
MOVE.L A1, D2 ; varargs array
|
||||
JSR (-954,A6) ; VPrintf()
|
||||
```
|
||||
|
||||
Format strings like `"Error: %ld\n"` or `"Processing: %s"` reveal parameter types and function purpose.
|
||||
|
||||
---
|
||||
|
||||
## Workbench Title Strings
|
||||
|
||||
```asm
|
||||
; Typical NewScreen/OpenScreen call sequence:
|
||||
LEA _screen_title(PC), A0 ; "MyApp v1.0"
|
||||
MOVE.L A0, (NewScreen+ns_Title)
|
||||
```
|
||||
|
||||
Screen/window title strings appear in `intuition.library` `OpenScreen` / `OpenWindow` calls and give the product name.
|
||||
|
||||
---
|
||||
|
||||
## Automated String Map
|
||||
|
||||
Build a complete string inventory:
|
||||
|
||||
```python
|
||||
# IDA script: map all string xrefs
|
||||
for s in idautils.Strings():
|
||||
text = str(idc.get_strlit_contents(s.ea, s.length, s.strtype))
|
||||
refs = list(idautils.XrefsTo(s.ea))
|
||||
if refs:
|
||||
for ref in refs:
|
||||
func = idc.get_func_name(ref.frm)
|
||||
print(f"{s.ea:#x} [{text!r:40s}] ← {func or 'unknown'} @ {ref.frm:#x}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- IDA Pro: Strings subview (Shift+F12), Xrefs (X key)
|
||||
- `static/api_call_identification.md` — resolving library base from string xrefs
|
||||
- NDK39: `dos/dos.h` — `VPrintf`, `FPrintf`, error code strings
|
||||
137
05_reversing/static/struct_recovery.md
Normal file
137
05_reversing/static/struct_recovery.md
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Recovering Data Structures
|
||||
|
||||
## Overview
|
||||
|
||||
Amiga executables use OS structures extensively — `ExecBase`, `Node`, `Process`, `IORequest`, etc. This document describes how to recover and annotate these structures in disassembly by matching field access patterns against NDK39 header offsets.
|
||||
|
||||
---
|
||||
|
||||
## The MOVE.L offset(An),Dm Pattern
|
||||
|
||||
Structure field accesses appear as:
|
||||
|
||||
```asm
|
||||
MOVEA.L _DOSBase, A6
|
||||
MOVE.L ($17A,A6), A0 ; SysBase->LibList at offset +0x17A
|
||||
MOVE.L (A0), A1 ; lh_Head
|
||||
```
|
||||
|
||||
The key is the **base register** and **constant offset**. Match the offset against known structure definitions.
|
||||
|
||||
---
|
||||
|
||||
## Common Structures and Key Offsets
|
||||
|
||||
### `struct ExecBase` (at absolute address `$4`)
|
||||
|
||||
| Offset | Field | Type |
|
||||
|---|---|---|
|
||||
| +0 | `LibNode` | `struct Library` |
|
||||
| +0x128 | `TaskReady` | `struct List` |
|
||||
| +0x132 | `TaskWait` | `struct List` |
|
||||
| +0x17A | `LibList` | `struct List` |
|
||||
| +0x182 | `DeviceList` | `struct List` |
|
||||
| +0x21E | `ChipRevBits0` | `UWORD` |
|
||||
| +0x280 | `MemList` | `struct List` |
|
||||
|
||||
### `struct Node` (8 bytes)
|
||||
|
||||
| Offset | Field |
|
||||
|---|---|
|
||||
| +0 | `ln_Succ` (next node) |
|
||||
| +4 | `ln_Pred` (prev node) |
|
||||
| +8 | `ln_Type` (UBYTE) |
|
||||
| +9 | `ln_Pri` (BYTE priority) |
|
||||
| +10 | `ln_Name` (STRPTR) |
|
||||
|
||||
List traversal:
|
||||
```asm
|
||||
MOVEA.L lh_Head, A0 ; first node
|
||||
.loop:
|
||||
TST.L (A0) ; ln_Succ == NULL?
|
||||
BEQ.S .done
|
||||
; process node at A0
|
||||
MOVEA.L (A0), A0 ; A0 = ln_Succ
|
||||
BRA.S .loop
|
||||
```
|
||||
|
||||
### `struct Process` (extends `struct Task`)
|
||||
|
||||
| Offset | Field |
|
||||
|---|---|
|
||||
| +0 | `pr_Task` (struct Task) |
|
||||
| +92 | `pr_MsgPort` |
|
||||
| +128 | `pr_CLI` (BPTR, non-NULL if CLI) |
|
||||
| +172 | `pr_SegList` (BPTR to segment list) |
|
||||
|
||||
Detection in disassembly:
|
||||
```asm
|
||||
MOVE.L ($80,A4), D0 ; pr_CLI at offset +0x80
|
||||
BEQ.S .wb_launch ; NULL = Workbench
|
||||
```
|
||||
|
||||
### `struct IORequest` / `struct IOStdReq`
|
||||
|
||||
| Offset | Field |
|
||||
|---|---|
|
||||
| +0 | `io_Message` (struct Message) |
|
||||
| +20 | `io_Device` |
|
||||
| +24 | `io_Unit` |
|
||||
| +28 | `io_Command` (UWORD) |
|
||||
| +30 | `io_Flags` (UBYTE) |
|
||||
| +32 | `io_Error` (BYTE) |
|
||||
| +36 | `io_Length` (ULONG) |
|
||||
| +40 | `io_Actual` (ULONG) |
|
||||
| +44 | `io_Data` (APTR) |
|
||||
| +48 | `io_Offset` (ULONG) |
|
||||
|
||||
---
|
||||
|
||||
## Annotating Structures in IDA Pro
|
||||
|
||||
### Define a structure type:
|
||||
|
||||
1. `View → Open Subviews → Local Types` → `Insert` → paste C struct definition
|
||||
2. IDA parses the struct and creates a type entry
|
||||
3. Navigate to the base register in disassembly
|
||||
4. Press `T` (structure offset) on any `offset(An)` operand
|
||||
5. Select the struct type → all accesses auto-annotated
|
||||
|
||||
### Import NDK39 headers:
|
||||
|
||||
Use `File → Load file → Parse C header file` → select `exec/execbase.h`, `exec/tasks.h`, etc. from NDK39.
|
||||
|
||||
---
|
||||
|
||||
## Exec Node Traversal Loops
|
||||
|
||||
A recurring pattern: walking the `LibList` or `DeviceList`:
|
||||
|
||||
```asm
|
||||
; Annotated after struct recovery:
|
||||
MOVEA.L SysBase, A6
|
||||
LEA (LibList,A6), A0 ; &SysBase->LibList
|
||||
MOVEA.L (lh_Head,A0), A1 ; first lib node
|
||||
.scan:
|
||||
MOVEA.L (ln_Succ,A1), A2 ; peek next
|
||||
TST.L A2
|
||||
BEQ.S .not_found
|
||||
; compare ln_Name string
|
||||
MOVEA.L (ln_Name,A1), A0
|
||||
JSR ___strcmp
|
||||
TST.L D0
|
||||
BEQ.S .found
|
||||
MOVEA.L A2, A1
|
||||
BRA.S .scan
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `exec/execbase.h`, `exec/tasks.h`, `exec/nodes.h`, `exec/io.h`
|
||||
- `06_exec_os/exec_base.md` — full ExecBase field listing
|
||||
- `06_exec_os/lists_nodes.md` — MinList/List traversal
|
||||
- IDA Pro: Structure subview, Local Types, T hotkey for struct offset
|
||||
Loading…
Add table
Add a link
Reference in a new issue