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:
Ilia Sharin 2026-04-23 12:16:52 -04:00
parent f07a368bf1
commit 21751c0025
172 changed files with 19701 additions and 0 deletions

View file

@ -0,0 +1,44 @@
[← Home](../README.md)
# Linking & Library Integration
## Overview
This section documents how AmigaOS shared libraries work at the binary level — how compilers produce library call stubs, how the linker wires them up, and how to reconstruct this mechanism during reverse engineering.
## Contents
| File | Topic |
|---|---|
| [library_structure.md](library_structure.md) | Library node, LVO table, OpenLibrary mechanics |
| [fd_files.md](fd_files.md) | Function Definition files — the library ABI source |
| [lvo_table.md](lvo_table.md) | JMP table layout and reconstruction |
| [compiler_stubs.md](compiler_stubs.md) | How SAS/C, GCC, VBCC call libraries |
| [setfunction.md](setfunction.md) | Runtime function patching with SetFunction |
| [startup_code.md](startup_code.md) | c.o / gcrt0.S — startup and exit sequences |
## The Library ABI Model
Every AmigaOS shared library exposes its functions through a **negative-offset JMP table** relative to the library base pointer:
```
Library base: LIB+0 → Library node (struct Library)
LIB-6 → JMP _funcN (last function)
LIB-12 → JMP _funcN-1
...
LIB-6 → JMP _func1 (first user function)
```
A C call like `OpenLibrary("graphics.library", 0)` compiles to:
```asm
MOVE.L 4.W, A6 ; A6 = SysBase (exec)
JSR -552(A6) ; LVO for OpenLibrary = -552
```
The negative offset (`-552`) is the **Library Vector Offset (LVO)** — a fixed ABI value defined in the library's `.fd` file and `proto/` include.
## References
- NDK39: `fd/` directory, `proto/` includes, `inline/` inline stubs
- ADCD 2.1: Library writing guide, ROM Kernel Manual
- http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0000.html

View file

@ -0,0 +1,188 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# Compiler Library Call Stubs
## Overview
This document explains how each major AmigaOS compiler generates library calls and what the resulting machine code looks like — essential for recognising compiler signatures during reverse engineering.
---
## The AmigaOS Calling Convention
All library calls use the same register-based convention:
- **A6** = library base pointer
- Arguments in **A0A3**, **D0D7** as specified by the `.fd` file
- Return value in **D0** (or D0+D1 for 64-bit returns)
- **Callee** preserves: D2D7, A2A6
- **Caller** may destroy: D0, D1, A0, A1
The code generated by all compilers follows this — the difference is how they **set up A6** before the `JSR`.
---
## SAS/C 6.x
SAS/C uses **global library base variables** (`_DOSBase`, `_SysBase`, etc.) and generates direct `JSR LVO(A6)` calls. The standard pattern:
```asm
; Calling Write(file, buffer, length):
; D1=file, D2=buffer, D3=length, A6=_DOSBase
MOVE.L _DOSBase, A6 ; load library base from global
MOVE.L file, D1 ; arg 1
MOVE.L buffer, D2 ; arg 2
MOVE.L length, D3 ; arg 3
JSR -48(A6) ; Write LVO = -48
```
### SAS/C Inline Pragmas
SAS/C uses `#pragma libcall` to define inline stubs:
```c
#pragma libcall DOSBase Write 30 32103
/* 30 = bias/6 = 8th function (LVO -48)
32103 = register encoding: D3,D2,D1 → args 3,2,1 */
```
The pragma-generated stub wraps the JSR into a C-callable inline function.
### SAS/C Library Base Globals
SAS/C's `clib/dos_protos.h` + startup code declares:
```c
extern struct DosLibrary *DOSBase;
extern struct ExecBase *SysBase;
```
These are initialised in `c.o` (the startup stub):
```asm
_c_start:
MOVE.L 4.W, A6 ; SysBase from exception vector
MOVE.L A6, _SysBase ; store in global
LEA _dosname, A1 ; "dos.library"
MOVEQ #0, D0 ; version 0
JSR -552(A6) ; OpenLibrary
MOVE.L D0, _DOSBase
```
---
## GCC (m68k-amigaos / bebbo GCC)
GCC uses a different stub mechanism — **inline functions** from `inline/` headers or the `libnix` library:
```c
/* inline/exec.h (generated from exec_lib.fd) */
static __inline APTR
AllocMem(ULONG byteSize, ULONG requirements)
{
register APTR _res __asm("d0");
register struct ExecBase *const _SysBase __asm("a6") = SysBase;
register ULONG _byteSize __asm("d0") = byteSize;
register ULONG _requirements __asm("d1") = requirements;
__asm volatile ("jsr a6@(-198:W)"
: "=r"(_res)
: "r"(_SysBase), "r"(_byteSize), "r"(_requirements)
: "a0", "a1", "fp0", "fp1", "cc", "memory");
return _res;
}
```
The `jsr a6@(-198:W)` emits a 4-byte instruction: `JSR -198(A6)` using 16-bit word displacement.
### GCC Code Pattern
```asm
; GCC calling AllocMem(1024, MEMF_CHIP):
MOVEA.L _SysBase, A6 ; GCC uses same global SysBase
MOVEQ #$01, D1 ; MEMF_CHIP = 2... actually:
MOVE.L #$00000400, D0 ; byteSize = 1024
MOVE.L #$00000002, D1 ; MEMF_CHIP
JSR -$C6(A6) ; -198 decimal = AllocMem
MOVE.L D0, _mybuf ; save return value
```
### PC-Relative Addressing (GCC Default)
GCC with `-fpic` or `-fbaserel` generates **PC-relative** accesses:
```asm
LEA _SysBase(PC), A0 ; load SysBase address PC-relative
MOVEA.L (A0), A6 ; dereference to get SysBase value
JSR -$228(A6) ; OpenLibrary LVO = -552
```
This reduces relocation entries and is the default for GCC on AmigaOS.
---
## VBCC (vbcc m68k-amigaos)
VBCC uses `inline.h` style stubs similar to GCC. The calling pattern is identical at the assembly level:
```asm
; VBCC compiled library call — indistinguishable from GCC at asm level
MOVEA.L (_SysBase), A6
MOVE.L D2, -(SP) ; VBCC may push/pop D2 as scratch
MOVE.L arg, D0
JSR -$C6(A6)
MOVE.L (SP)+, D2
```
VBCC is slightly more aggressive about not clobbering D2-D7, matching the calling convention exactly.
---
## Recognising Compiler Signatures During RE
### SAS/C Signature
```asm
; Entry prologue:
LINK A5, #-N ; frame setup (A5 = frame pointer)
MOVEM.L D2-D7/A2-A4, -(SP) ; save callee-saved regs
; ...
MOVEM.L (SP)+, D2-D7/A2-A4 ; restore
UNLK A5 ; teardown
RTS
```
SAS/C uses `A5` as frame pointer and `LINK/UNLK` for stack frames.
### GCC Signature
```asm
; Entry prologue (GCC without frame pointer):
MOVEM.L D2/A2, -(SP) ; only saves what it uses
; ...
MOVEM.L (SP)+, D2/A2
RTS
```
GCC typically does **not** use LINK/UNLK unless required. It uses `A5` as an additional scratch register and often generates `JSR` through `A0` for indirect calls.
### VBCC Signature
VBCC generates very clean, minimal code — similar to GCC but with more consistent callee-save patterns and no GCC-specific idioms (e.g., no `__builtin_` calls).
---
## Library Call Pattern Summary
| Pattern | Compiler | Key Signature |
|---|---|---|
| `LINK A5, #-N` + `MOVEM D2-D7/A2-A4` | SAS/C | Frame pointer A5, full reglist |
| `JSR LVO(A6)` with global `_LibBase` | SAS/C / GCC | Both use global |
| `jsr a6@(-LVO:W)` (GAS syntax) | GCC inline | 16-bit short displacement |
| PC-relative `LEA _SysBase(PC)` | GCC -fpic | No absolute refs |
| No LINK, minimal MOVEM | GCC / VBCC | No frame pointer |
---
## References
- NDK39: `fd/`, `proto/`, `inline/`, `clib/` directories
- SAS/C 6.x Programmer's Guide — pragma libcall chapter
- GCC m68k-amigaos port documentation (bebbo/m68k-amigaos-toolchain)
- VBCC documentation — register calling convention
- *Amiga ROM Kernel Reference Manual: Libraries* — calling conventions appendix

View file

@ -0,0 +1,172 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# Function Definition (.fd) Files
## Overview
**Function Definition files** (`.fd`) are the authoritative source of truth for AmigaOS library ABIs. They define:
- The **name** of every library function
- The **LVO** (Library Vector Offset) — the negative offset used to call it
- The **register parameters** — which M68k registers carry each argument
- The **bias** (starting offset, always negative)
Every AmigaOS library call ultimately traces back to an `.fd` file.
---
## File Location
In the NDK39:
```
NDK39/fd/
dos_lib.fd
exec_lib.fd
graphics_lib.fd
intuition_lib.fd
icon_lib.fd
...
```
Also part of the AmigaOS developer CD (adjust to your NDK path):
```
NDK39/fd/
```
---
## .fd File Format
```
##base _DOSBase ; library base variable name
##bias 30 ; first LVO = -30 (4 mandatory: -6,-12,-18,-24)
##public
Open(name,accessMode)(D1,D2) ; LVO -30
Close(file)(D1) ; LVO -36
Read(file,buffer,length)(D1,D2,D3); LVO -42
Write(file,buffer,length)(D1,D2,D3); LVO -48
...
##private
_InternalOpen(...) ; private function, not in public ABI
```
### Grammar Rules
| Directive | Meaning |
|---|---|
| `##base _LibBase` | Name of the library base global variable |
| `##bias N` | Starting LVO magnitude; first public function is at `-N` |
| `##public` | Following functions are part of the public ABI |
| `##private` | Following functions are internal/private |
| `FuncName(args)(regs)` | Function with argument names and register assignments |
The LVO of function `n` (0-indexed from first public) is:
```
LVO = -(bias + n × 6)
```
---
## Register Notation
Register assignments are listed in order matching the argument list, using Amiga register names:
```
Open(name, accessMode)(D1, D2)
```
Means:
- `name` → D1
- `accessMode` → D2
- Caller must put library base in A6 (by convention)
Special register names:
- `A6` — always the library base (implicit, not listed)
- `A0``A3`, `D0``D7` — data and address registers
- `D0` — return value (by convention)
---
## LVO Calculation Example
For `dos_lib.fd` with `##bias 30`:
| LVO | Function |
|---|---|
| -6 | `Open` (mandatory) |
| -12 | `Close` (mandatory) |
| -18 | `Expunge` (mandatory) |
| -24 | `Reserved` (mandatory) |
| -30 | first public function |
| -36 | second public function |
| ... | |
| -144 | `SetComment` |
| -150 | `SetProtection` |
The actual LVOs match `dos/dos.h` defines:
```c
#define DOS_Open (-30)
#define DOS_Close (-36)
#define DOS_Read (-42)
```
---
## Proto Includes (Generated from .fd)
The `proto/` directory contains compiler-specific call wrappers generated from `.fd` files:
```c
/* proto/dos.h — generated automatically from dos_lib.fd */
#ifndef PROTO_DOS_H
#include <clib/dos_protos.h> /* function prototypes */
#include <inline/dos.h> /* inline register call stubs */
#endif
```
`inline/dos.h` contains:
```c
#define Open(name,accessMode) \
({ ULONG _r; \
__asm volatile("movea.l %1,a6; jsr -30(a6)" : "=r"(_r) : "r"(_DOSBase) : "a6"); \
_r; })
```
---
## .fd Files and Reverse Engineering
When reversing an Amiga binary, `.fd` files give you the **complete function name and parameter register map** for every library call. If you see:
```asm
MOVEA.L $00BEEF04, A6 ; graphics.library base
JSR -$114(A6) ; LVO -276
```
Look up `-276` in `graphics_lib.fd`:
```
-276 ÷ 6 = 46th function from start of table
```
Or directly look at `graphics/graphics.h` or `proto/graphics.h` for the constant:
```c
#define GFX_BltBitMap (-276)
/* → JSR -276(A6) = BltBitMap() */
```
---
## Full LVO Table Reconstruction
See [lvo_table.md](lvo_table.md) for the complete process of reconstructing a library's JMP table from scratch during reverse engineering.
---
## References
- NDK39: `fd/` directory — all `.fd` files
- NDK39: `proto/` directory — generated call wrappers
- NDK39: `clib/` directory — raw C prototypes
- ADCD 2.1: library chapters reference LVOs
- Community: `https://wiki.amigaos.net/wiki/Fd_Files`

View file

@ -0,0 +1,198 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# Compiler Inline Stubs
## Overview
AmigaOS library functions are not called via the C standard ABI. Every call goes through a **negative-offset JMP table** relative to a library base pointer held in an address register (conventionally A6). Compiler vendors each solve this differently:
- **SAS/C**`#pragma` headers and `inline/` headers
- **GCC (m68k-amigaos)** — inline-assembly `__attribute__((regparm))` stubs
- **VBCC**`__reg()` storage class and module pragmas
All approaches ultimately compile to the same machine code:
```asm
MOVEA.L _DOSBase, A6
JSR -LVO(A6) ; e.g. JSR -138(A6) for dos.library Output()
```
---
## SAS/C: Pragma-Based Stubs
### Pragma Syntax
SAS/C uses `#pragma` directives to declare register assignments:
```c
#pragma amicall(DOSBase, 0x008A, Open(D1, D2))
```
- `DOSBase` — global holding the library base (placed in A6 automatically)
- `0x008A` — LVO offset (hex)
- `Open(D1, D2)` — function name and register allocation for arguments
### Include Structure (NDK39)
```
NDK39/
include/
pragmas/
dos_pragmas.h ← #pragma amicall directives for dos.library
exec_pragmas.h ← exec.library pragmas
graphics_pragmas.h
...
inline/
dos.h ← Alternative: inline-macro stubs (SAS/C 6.x)
```
Usage:
```c
#include <clib/dos_protos.h> /* ANSI prototypes */
#include <pragmas/dos_pragmas.h> /* register pragmas */
extern struct DosLibrary *DOSBase;
BPTR fh = Open("foo", MODE_OLDFILE); /* expands to JSR -30(A6) */
```
### Pragma-Generated Code
```asm
; Open("foo", MODE_OLDFILE)
MOVE.L #MODE_OLDFILE, D2
MOVE.L #_str_foo, D1
MOVEA.L _DOSBase, A6
JSR -30(A6)
; Return value in D0
```
No stack frame involved — pure register passing.
---
## GCC (m68k-amigaos): Inline Assembly Stubs
### Modern Approach (GCC 6.x+ / bebbo cross-compiler)
The NDK provides `<proto/dos.h>` which pulls in compiler-specific stubs. Under bebbo GCC:
```c
/* auto-generated stub in clib2 / libnix */
static inline BPTR __attribute__((always_inline))
Open(CONST_STRPTR name, LONG accessMode)
{
register BPTR __ret __asm("d0");
register struct DosLibrary *const __DOSBase __asm("a6") = DOSBase;
register CONST_STRPTR __name __asm("d1") = name;
register LONG __accessMode __asm("d2") = accessMode;
__asm volatile ("jsr %%a6@(-30:W)"
: "=r"(__ret)
: "r"(__DOSBase), "r"(__name), "r"(__accessMode)
: "d1", "a0", "a1", "fp0", "fp1", "cc", "memory");
return __ret;
}
```
Key points:
- `__asm("a6")` forces the library base into A6
- `__asm("d1")`, `__asm("d2")` — per-function register assignments from `.fd` file
- `"jsr %%a6@(-30:W)"` — 16-bit signed displacement form (most efficient)
- Clobbers declared explicitly to prevent register allocation conflicts
### Older GCC (< 6.x): `__OLDSTYLE__` macros
```c
#define Open(name, mode) \
({ BPTR _r; \
__asm volatile ("movea.l %2,a6; jsr -30(a6)" : "=d"(_r) : \
"d"(name), "d"(mode), "m"(DOSBase) : "a6"); \
_r; })
```
Less elegant — explicitly moves DOSBase to A6 inside the macro.
---
## VBCC: `__reg()` Storage Class
VBCC uses a compiler extension to place variables in specific registers:
```c
/* vbcc style */
BPTR __reg("d0") Open(__reg("d1") CONST_STRPTR name,
__reg("d2") LONG mode);
#pragma amicall(DOSBase, 0x1E, Open(d1,d2))
```
VBCC automatically inserts `MOVEA.L DOSBase,A6` and `JSR -30(A6)`.
Module pragma file (`dos.fd`-derived):
```
##base DOSBase
##bias 30
Open(name,accessMode)(d1,d2)
##bias 36
Close(file)(d1)
...
```
---
## Generated Machine Code (All Compilers)
For `Write(fh, buf, len)` at LVO 48 (`$30`):
```asm
MOVEA.L _DOSBase, A6 ; load library base into A6
MOVE.L fh_var, D1 ; BPTR filehandle → D1
MOVE.L buf_ptr, D2 ; buffer address → D2
MOVE.L len_val, D3 ; byte count → D3
JSR -48(A6) ; call Write()
; D0 = bytes actually written, or -1 on error
```
---
## Register Allocation Convention
All AmigaOS library functions follow this invariant:
| Register | Role |
|---|---|
| A6 | Library base (always) |
| D0 | Return value (32-bit) |
| D0+D1 | 64-bit return (rare: `DivideU`) |
| D1D7, A0A3 | Arguments (per `.fd` file) |
| A4, A5 | Scratch in OS (do not rely on preservation) |
| D2D7, A2A3 | **Preserved** by OS calls (callee-saved) |
A compiler stub must:
1. Load arguments into exact registers from the `.fd` specification
2. Load the library base into A6
3. Execute `JSR -LVO(A6)`
4. Collect the return value from D0
---
## Stub Generation Tools
| Tool | Input | Output |
|---|---|---|
| `fd2pragma` (SAS) | `.fd` file | `#pragma amicall` header |
| `fd2inline` | `.fd` file | GCC inline-asm header |
| `fd2sfd` | `.fd` file | SFD (Amiga DevKit) format |
| `cvinclude.pl` | `.fd` + `.sfd` | VBCC pragma headers |
NDK39 ships pre-generated `pragmas/` and `inline/` directories — you only need to run these tools when writing a new library.
---
## References
- NDK39: `include/pragmas/`, `include/inline/`, `fd/`
- SAS/C 6.x Programmer's Reference Manual — chapter on pragmas
- GCC for Amiga (bebbo): `m68k-amigaos-gcc` repo, `libnix` stubs
- VBCC manual: http://www.compilers.de/vbcc.html — register specification chapter
- vlink documentation: http://sun.hasenbraten.de/vlink/

View file

@ -0,0 +1,142 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# AmigaOS Library Structure
## The Library Node
Every AmigaOS library (and device, resource) begins with a `struct Library` at its base address:
```c
/* exec/libraries.h */
struct Library {
struct Node lib_Node; /* +$00: linked list node */
UBYTE lib_Flags; /* +$0E: LIBF_ flags */
UBYTE lib_pad; /* +$0F: (reserved) */
UWORD lib_NegSize; /* +$10: bytes of function table (negative area) */
UWORD lib_PosSize; /* +$12: bytes of struct Library + private data */
UWORD lib_Version; /* +$14: major version */
UWORD lib_Revision; /* +$16: minor revision */
APTR lib_IdString; /* +$18: pointer to ID string */
ULONG lib_Sum; /* +$1C: checksum of the function table */
UWORD lib_OpenCnt; /* +$20: number of current openers */
};
```
`lib_NegSize` is the total byte size of the JMP table (the negative-address area preceding the struct). It equals `num_functions × 6` bytes per JMP instruction.
---
## JMP Table (Negative Offset Area)
The function table is constructed **below** the library base address. Each entry is a 6-byte JMP instruction:
```
Library base - 6: 4E F9 xx xx xx xx JMP AbsAddress (Open)
Library base - 12: 4E F9 xx xx xx xx JMP AbsAddress (Close)
Library base - 18: 4E F9 xx xx xx xx JMP AbsAddress (Expunge)
Library base - 24: 4E F9 xx xx xx xx JMP AbsAddress (Reserved)
Library base - 30: 4E F9 xx xx xx xx JMP AbsAddress (first user function)
Library base - 36: ...
```
### Standard Functions (Fixed LVOs for All Libraries)
| LVO (offset) | Function |
|---|---|
| -6 | `Open` |
| -12 | `Close` |
| -18 | `Expunge` |
| -24 | `Reserved` (must return 0) |
These four are mandatory for every library. User functions start at LVO -30.
### JMP Encoding
`4E F9 AAAA AAAA` = `JMP.L absolute_address`
To call function at LVO -30:
```asm
MOVEA.L LibBase, A6
JSR -30(A6) ; call through JMP table
```
The `JSR -30(A6)` does **not** jump directly to the function — it jumps to the JMP slot, which then jumps to the real function. This indirection is essential for `SetFunction()` patching.
---
## MakeLibrary() — Constructing the Table
`exec.library MakeLibrary()` builds a library:
```c
struct Library *MakeLibrary(
APTR funcArray, /* array of function pointers (APTR) or LONG offsets */
APTR structInit, /* structure initialiser table (or NULL) */
ULONG (*initFunc)(), /* init function (or NULL) */
ULONG dataSize, /* size of library data area */
BPTR segList /* segment list of the library code */
);
```
`funcArray` is a NULL-terminated list of function addresses. `MakeLibrary` allocates the combined negative+positive area and fills in the JMP table.
---
## Library Initialisation
At `MakeNode()` / `MakeLibrary()` time:
1. `AllocMem(lib_NegSize + lib_PosSize, MEMF_PUBLIC | MEMF_CLEAR)`
2. Fill JMP table at negative offsets
3. Initialise `struct Library` fields at positive offsets
4. Set `lib_Sum` to the checksum of the JMP table
At `AddLibrary()`:
1. Library is added to `SysBase->LibList`
2. Future `OpenLibrary()` calls find it by name via `FindName()`
---
## OpenLibrary() Path
```c
struct Library *base = OpenLibrary("mylib.library", MIN_VERSION);
```
Internally:
1. `exec` searches `SysBase->LibList` for a node with `ln_Name == "mylib.library"`
2. If found and version sufficient: calls `LVO_Open` (offset -6) on the library
3. If not found: attempts to load `LIBS:mylib.library` from disk via `LoadSeg()` + `InitResident()`
4. Returns library base pointer, or NULL on failure
---
## ROM Libraries (Kickstart)
Some libraries are **resident** — embedded directly in the Kickstart ROM:
- `exec.library` — always in ROM; base at `$4` in exec exception vector
- `graphics.library`, `intuition.library`, `dos.library` — loaded from ROM on boot
ROM-resident libraries are listed in the **Resident module** list. During boot, `exec` calls `InitResident()` for each module marked as auto-init.
---
## Library Flags
```c
/* lib_Flags bits: */
#define LIBF_SUMMING (1<<0) /* currently computing checksum */
#define LIBF_CHANGED (1<<1) /* function table was patched (SetFunction) */
#define LIBF_SUMUSED (1<<2) /* checksum is valid */
#define LIBF_DELEXP (1<<3) /* delayed expunge requested */
```
`LIBF_CHANGED` is set by `SetFunction()` to signal that the checksum is no longer valid — tools like `ShowConfig` use this to detect patched libraries.
---
## References
- NDK39: `exec/libraries.h`, `exec/nodes.h`
- ADCD 2.1 Autodocs: exec — OpenLibrary, MakeLibrary, AddLibrary, SetFunction
- *Amiga ROM Kernel Reference Manual: Libraries* — library architecture chapter
- http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0002.html

View file

@ -0,0 +1,172 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# AmigaLib Static Linking
## Overview
AmigaOS programs are linked against a set of **static libraries** that provide startup code, C runtime stubs, and glue for OS entry points. The linker resolves all external symbols at link time and produces a self-contained HUNK-format executable.
---
## Key Static Libraries (NDK39 / SAS/C)
| Library | Purpose |
|---|---|
| `amiga.lib` | OS stub functions — `OpenLibrary`, `AllocMem`, etc. for non-inline linking |
| `sc.lib` / `c.lib` | SAS/C runtime: `printf`, `malloc`, `strlen`, standard I/O |
| `auto.lib` | Auto-open libraries (`DOSBase`, `SysBase` acquisition) |
| `debug.lib` | `kprintf`, `dprintf` serial debugging stubs |
| `m.lib` | Math library (software floating point) |
| `scm68020.lib` | SAS/C math for 68020 (co-processor stubs) |
For GCC:
| Library | Purpose |
|---|---|
| `libnix` | C runtime for `m68k-amigaos-gcc` — replaces `sc.lib` |
| `libamiga` | OS glue for GCC (NDK-based) |
| `libm` | Soft-float math |
| `libgcc` | GCC internal helpers (division, etc.) |
---
## Startup Object: `c.o` / `_start`
Every AmigaOS C program begins execution at the **first word of segment 0** — not at `main()`. The startup object `c.o` (SAS/C) or `crt0.o` (GCC/libnix) is always linked first and provides:
```asm
;; SAS/C c.o skeleton (simplified)
_start:
MOVE.L 4.W, A6 ; SysBase from absolute location $4
MOVE.L A0, _CommandStr ; raw CLI argument string (from dos.library)
MOVE.L A1, _WBenchMsg ; WBStartup message (if WB launch, else NULL)
JSR __main ; C runtime init → eventually calls main()
; D0 = exit code from main()
MOVE.L D0, _rc ; save return code
JSR __exit ; C runtime cleanup
RTS ; return to dos.library
```
### What `__main` Does
1. Opens `DOSBase` via `OpenLibrary("dos.library", 0)` using SysBase in A6
2. Sets up `stdin`/`stdout`/`stderr` file handles (wraps `dos.library` I/O)
3. Allocates C heap (if any static `malloc`/`new` usage)
4. Initializes `errno`, `_timezone`, `__ProgramName`
5. Calls any registered `__constructor` functions (C++ static init)
6. Calls `main(argc, argv)` — argument string is parsed from `_CommandStr`
7. Calls `exit(return_code)` → runs `atexit()` handlers → `CloseLibrary(DOSBase)`
---
## WBStartup Glue
When launched from Workbench (double-click), `A0 = NULL`, `A1 = WBStartup msg ptr`. The startup code must:
```c
/* standard WB detection in __main / startup */
if (_WBenchMsg) {
/* We are a WB launch. argc=0, argv=NULL passed to main() */
/* Must not return until cleanup */
}
```
On exit from a WB-launched program:
```c
Forbid(); /* prevent task switching during cleanup */
ReplyMsg((struct Message *)_WBenchMsg); /* unblock Workbench */
/* now safe to RTS / remove task */
```
This pattern is critical: **failing to `ReplyMsg` a WB launch will hang Workbench**.
---
## Stack Cookie
SAS/C startup checks for a stack size cookie in the executable:
```c
/* In your source — sets minimum stack to 8 KB */
LONG __stack = 8192;
```
The linker includes this symbol at a known offset; the OS shell reads it and allocates at least that much stack before launching. IDA Pro will often highlight `_stack` as a data symbol.
---
## CTRL-C Checking
SAS/C runtime polls for CTRL-C via `CheckSignal(SIGBREAKF_CTRL_C)` inside `printf`, `fgets`, and other stdio functions. Programs that do long computation loops should call:
```c
if (SetSignal(0, 0) & SIGBREAKF_CTRL_C)
cleanup_and_exit();
```
Or use `SAS/C`'s `__chkabort()` hook.
---
## Typical Link Command (SAS/C)
```
slink FROM lib/c.o myobj.o LIB lib/sc.lib lib/amiga.lib TO myprogram
```
Order matters:
1. `lib/c.o`**must be first** (entry point at start of segment 0)
2. Object files (`myobj.o`, ...)
3. `sc.lib` — C runtime (resolves `printf`, etc.)
4. `amiga.lib` — OS stubs (resolves any non-inlined OS calls)
### Typical GCC Link (bebbo)
```bash
m68k-amigaos-gcc -o myprogram myobj.o -lamiga -lnix -lgcc \
-Wl,-Map,myprogram.map
```
Linker script places `crt0.o` automatically via `-lnix` startup group.
---
## Library Archive Format
Static libraries (`.lib`) are HUNK_LIB archives — sequences of embedded HUNK_UNIT object files with a HUNK_INDEX for fast symbol lookup:
```
HUNK_LIB
[HUNK_UNIT: AllocMem.o]
HUNK_CODE
HUNK_EXT (export: _AllocMem)
HUNK_END
[HUNK_UNIT: FreeMem.o]
...
HUNK_INDEX
[symbol table: _AllocMem → unit 0, _FreeMem → unit 1, ...]
```
The linker only pulls in units whose exported symbols are referenced — unused code from libraries is **not** linked into the executable (link-time dead-stripping).
---
## Segment Layout in Final Executable
With a typical 3-object, 2-lib link:
```
Segment 0 (code): c.o startup + all CODE sections merged
Segment 1 (data): all DATA sections merged
Segment 2 (BSS): all BSS sections (zero-filled)
```
Most linkers merge same-type sections by default. `slink` supports explicit placement control via `CHIP` / `FAST` keywords.
---
## References
- NDK39: `lib/``amiga.lib`, `auto.lib`, `debug.lib`, `c.o`
- SAS/C 6.x Programmer's Reference Manual — linking chapter
- libnix source: https://github.com/bebbo/libnix
- *Amiga ROM Kernel Reference Manual: Libraries* — process startup appendix

View file

@ -0,0 +1,194 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# LVO Table Layout & Reconstruction
## Overview
The **Library Vector Offset (LVO) table** is the core mechanism of the AmigaOS ABI. Understanding and reconstructing it is essential for reversing any Amiga binary that calls system libraries.
---
## Memory Layout of a Loaded Library
```
Low addresses
←──────────────────────────────────────────────────────────→
High addresses
[JMP table (negative area)] [struct Library + private data]
... -36 -30 -24 -18 -12 -6 base+0 base+2 ...
↑ ↑
Last user function Library base pointer
(e.g., -576 for graphics) (returned by OpenLibrary)
```
Each slot is **6 bytes**:
```
4E F9 AA AA AA AA JMP.L AAAAAAAA
```
---
## Calculating LVOs
Given `##bias N` in the `.fd` file, the LVO of the `k`th public function (1-indexed) is:
```
LVO_k = -(N + (k-1) × 6)
```
The four mandatory functions before public functions (Open, Close, Expunge, Reserved) are at:
- `LVO_Open = -6`
- `LVO_Close = -12`
- `LVO_Expunge = -18`
- `LVO_Reserved = -24`
For a library with `##bias 30`, the first public function is at `-30`.
---
## Key Library LVO Tables
### exec.library (##bias 30)
| LVO | Function | Registers |
|---|---|---|
| -30 | `Supervisor` | A5=function |
| -36 | `ExitIntr` | (internal) |
| -42 | `Schedule` | (internal) |
| -48 | `Reschedule` | (internal) |
| -54 | `Switch` | (internal) |
| -60 | `Dispatch` | (internal) |
| -66 | `Exception` | (internal) |
| -72 | `InitCode` | D0=startClass, D1=version |
| -78 | `InitStruct` | A1=mem, A2=table, D0=size |
| -84 | `MakeLibrary` | A0=vectors, A1=struct, A2=init, D0=dataSize, D1=segList |
| -90 | `MakeFunctions` | A0=target, A1=funcArray, A2=funcDispBase |
| -96 | `FindResident` | A1=name |
| -102 | `InitResident` | A1=resident, D1=segList |
| -108 | `Alert` | D7=alertNum |
| -114 | `Debug` | D0=flags |
| -120 | `Disable` | |
| -126 | `Enable` | |
| -132 | `Forbid` | |
| -138 | `Permit` | |
| -144 | `SetSR` | D0=newSR, D1=mask |
| -150 | `SuperState` | |
| -156 | `UserState` | D0=savedSR |
| -162 | `SetIntVector` | D0=intNum, A1=interrupt |
| -168 | `AddIntServer` | D0=intNum, A1=interrupt |
| -174 | `RemIntServer` | D0=intNum, A1=interrupt |
| -180 | `Cause` | A1=interrupt |
| -186 | `Allocate` | A0=memHeader, D0=size |
| -192 | `Deallocate` | A0=memHeader, A1=memBlock, D0=size |
| -198 | `AllocMem` | D0=byteSize, D1=requirements |
| -204 | `AllocAbs` | D0=byteSize, A1=location |
| -210 | `FreeMem` | A1=memBlock, D0=byteSize |
| -216 | `AvailMem` | D1=requirements |
| -222 | `AllocEntry` | A0=entry |
| -228 | `FreeEntry` | A0=entry |
| -234 | `Insert` | A0=list, A1=node, A2=listNode |
| -240 | `AddHead` | A0=list, A1=node |
| -246 | `AddTail` | A0=list, A1=node |
| -252 | `Remove` | A1=node |
| -258 | `RemHead` | A0=list |
| -264 | `RemTail` | A0=list |
| -270 | `Enqueue` | A0=list, A1=node |
| -276 | `FindName` | A0=list, A1=name |
| -282 | `AddTask` | A1=task, A2=initialPC, A3=finalPC |
| -288 | `RemTask` | A1=task |
| -294 | `FindTask` | A1=name |
| -300 | `SetTaskPri` | A1=task, D0=priority |
| -306 | `SetSignal` | D0=newSignals, D1=signalSet |
| -312 | `SetExcept` | D0=newSignals, D1=signalSet |
| -318 | `Wait` | D0=signalSet |
| -324 | `Signal` | A1=task, D0=signals |
| -330 | `AllocSignal` | D0=signalNum |
| -336 | `FreeSignal` | D0=signalNum |
| -342 | `AllocTrap` | D0=trapNum |
| -348 | `FreeTrap` | D0=trapNum |
| -354 | `AddPort` | A1=port |
| -360 | `RemPort` | A1=port |
| -366 | `PutMsg` | A0=port, A1=message |
| -372 | `GetMsg` | A0=port |
| -378 | `ReplyMsg` | A1=message |
| -384 | `WaitPort` | A0=port |
| -390 | `FindPort` | A1=name |
| -396 | `AddLibrary` | A1=library |
| -402 | `RemLibrary` | A1=library |
| -408 | `OldOpenLibrary` | A1=libName |
| -414 | `CloseLibrary` | A1=library |
| -420 | `SetFunction` | A1=library, A0=funcOffset, D0=newFunc |
| -426 | `SumLibrary` | A1=library |
| -432 | `AddDevice` | A1=device |
| -438 | `RemDevice` | A1=device |
| -444 | `OpenDevice` | A0=devName, D0=unit, A1=ioReq, D1=flags |
| -450 | `CloseDevice` | A1=ioReq |
| -456 | `DoIO` | A1=ioReq |
| -462 | `SendIO` | A1=ioReq |
| -468 | `CheckIO` | A1=ioReq |
| -474 | `WaitIO` | A1=ioReq |
| -480 | `AbortIO` | A1=ioReq |
| -486 | `AddResource` | A1=resource |
| -492 | `RemResource` | A1=resource |
| -498 | `OpenResource` | A1=resName |
| -552 | `OpenLibrary` | A1=libName, D0=version |
| -558 | `InitSemaphore` | A0=semaphore |
| -564 | `ObtainSemaphore` | A0=semaphore |
| -570 | `ReleaseSemaphore` | A0=semaphore |
| -576 | `AttemptSemaphore` | A0=semaphore |
| -582 | `ObtainSemaphoreList` | A0=sigSemList |
| -588 | `ReleaseSemaphoreList` | A0=sigSemList |
| -594 | `FindSemaphore` | A1=sigSem |
| -600 | `AddSemaphore` | A1=sigSem |
| -606 | `RemSemaphore` | A1=sigSem |
| -612 | `SumKickData` | |
| -618 | `AddMemList` | D0=size, D1=attr, D2=pri, A0=base, A1=name |
| -624 | `CopyMem` | A0=source, A1=dest, D0=size |
| -630 | `CopyMemQuick` | A0=source, A1=dest, D0=size |
| -636 | `CacheClearU` | |
| -642 | `CacheClearE` | A0=addr, D0=len, D1=caches |
| -648 | `CacheControl` | D0=cacheBits, D1=cacheMask |
| -654 | `CreateIORequest` | A0=port, D0=size |
| -660 | `DeleteIORequest` | A0=ioreq |
| -666 | `CreateMsgPort` | |
| -672 | `DeleteMsgPort` | A0=port |
| -678 | `ObtainSemaphoreShared` | A0=sigSem |
| -684 | `AllocVec` | D0=size, D1=attr |
| -690 | `FreeVec` | A1=mem |
| -726 | `NewAddTask` | A1=task, A2=initialPC, A3=finalPC, A4=?? |
---
## Reconstructing the JMP Table During Reverse Engineering
When analysing a patched library (e.g., `bsdsocket.library`):
1. **Find the library base** — usually pointed to by a global variable or passed in A6
2. **Read `lib_NegSize`** at `base+0x10` — this gives the total JMP table byte count
3. **Scan the JMP table** — from `base - lib_NegSize` to `base - 6` in 6-byte steps
4. **Decode each JMP**`4E F9 AA AA AA AA` → target at `AAAAAAAA`
5. **Match to .fd file** — entry at offset `(-6 × n)` corresponds to function `n`
IDA Pro script to dump JMP table:
```python
import idc, idaapi
def dump_jmp_table(lib_base, num_funcs, fd_names):
for i, name in enumerate(fd_names):
slot = lib_base - 6 * (i + 1)
opcode = idc.get_wide_word(slot)
if opcode == 0x4EF9:
target = idc.get_wide_dword(slot + 2)
idc.set_name(slot, f"lvo_{name}", idc.SN_NOWARN)
idc.set_name(target, name, idc.SN_NOWARN)
print(f"LVO -{6*(i+1):4d} {name:40s} → {target:#010x}")
```
---
## References
- NDK39: `fd/exec_lib.fd`, `fd/dos_lib.fd`, `fd/graphics_lib.fd`
- NDK39: `include/exec/execbase.h` — SysBase layout
- ADCD 2.1 Autodocs: all library function descriptions

View file

@ -0,0 +1,124 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# M68k Register Calling Conventions on Amiga
## Overview
AmigaOS uses a **pure register-based calling convention** for all OS API calls. There is no stack-based C ABI for library functions. Every argument is passed in a specific CPU register defined by the `.fd` file for that library.
---
## The AmigaOS Register Convention
All OS library calls follow this scheme:
| Register | Role |
|---|---|
| **A6** | Library base pointer (always) |
| **D0** | Return value (32-bit integer or BOOL) |
| **D0+D1** | 64-bit return (rare; e.g., `DivideU`) |
| D1D7, A0A3 | Arguments — exact registers per `.fd` |
| D2D7, A2A3 | **Callee-preserved** (OS will not trash these) |
| D0, D1, A0, A1 | **Scratch** (may be destroyed by any OS call) |
| A4 | Global data pointer (VBCC; not used by OS) |
| A5 | Frame pointer (some compilers; not used by OS) |
| A6 | Library base — **always trashed to point to lib** |
| A7 | Stack pointer |
### Key rules:
- **A6 is always destroyed** — it holds the target library base after every OS call
- **D0, D1, A0, A1** are volatile — save them if needed across OS calls
- **FP0, FP1** are scratch if the FPU is present
---
## Example: `dos.library Write()`
From `fd/dos_lib.fd`:
```
Write(file,buffer,length)(d1,d2,d3)
```
```c
/* C call: */
LONG n = Write(fh, buf, 512);
/* Compiles to: */
MOVEA.L _DOSBase, A6
MOVE.L fh, D1
MOVE.L buf, D2
MOVE.L #512, D3
JSR -48(A6)
; D0 = bytes written (1 = error)
```
---
## Preserved vs. Scratch Register Summary
```
Scratch (caller must save if needed):
D0 D1 A0 A1 A6 FP0 FP1
Preserved (callee saves/restores):
D2 D3 D4 D5 D6 D7
A2 A3 A4 A5
FP2 FP3 FP4 FP5 FP6 FP7
```
This matches the Motorola 68000 family software convention, but AmigaOS does **not** use A5 as a frame pointer (unlike the standard System V m68k ABI).
---
## Inter-Library Calls
When one library function calls another library internally, it must:
```asm
; save A6 (current lib base), load new lib base
MOVEM.L A6, -(SP)
MOVEA.L _GfxBase, A6
JSR -102(A6) ; graphics.library BltClear()
MOVEM.L (SP)+, A6
```
Failure to save/restore A6 is a common bug in hand-written assembly library code.
---
## C Compiler Differences
### SAS/C 6.x
- Generates standard `MOVEA.L libbase,A6; JSR -lvo(A6)` via `#pragma amicall`
- Uses A5 as a frame pointer in non-leaf functions
- Stack frame: `LINK A5,#-N` on entry, `UNLK A5` on exit
### GCC (bebbo m68k-amigaos)
- Generates inline-asm stubs with explicit register constraints
- No frame pointer by default (`-fomit-frame-pointer`)
- D2D7/A2A3 saved on stack per function (ABI-compatible)
### VBCC
- Uses `__reg()` storage class for explicit register placement
- No frame pointer — tighter code than SAS/C for register-intensive functions
---
## Detecting the Calling Convention in IDA Pro
Pattern to identify an OS API call in disassembly:
```asm
MOVEA.L (_DOSBase).L, A6 ; load library base
JSR (-138,A6) ; call at LVO 138
```
Cross-reference the LVO against the `.fd` file to identify the function. IDA's Amiga loader applies LVO names automatically when library definitions are present.
---
## References
- NDK39: `fd/*.fd` — register assignments per function
- *Amiga ROM Kernel Reference Manual: Libraries* — register conventions appendix
- SAS/C 6.x Programmer's Guide — calling convention chapter
- GCC m68k-amigaos (bebbo) — `libnix` inline headers

View file

@ -0,0 +1,158 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# SetFunction — Runtime Library Patching
## Overview
`exec.library SetFunction()` replaces a single function in a library's JMP table with a different function pointer. It is the standard AmigaOS mechanism for hooking, patching, and replacing library functions at runtime.
```c
/* exec/exec.h */
APTR SetFunction(
struct Library *library, /* A1: library to patch */
LONG funcOffset, /* A0: LVO (e.g., -30) — must be negative */
ULONG (*newFunc)() /* D0: new function address */
);
/* Returns: D0 = old function address (to call through in the replacement) */
```
---
## How SetFunction Works
SetFunction modifies the JMP table in the library's negative-offset area:
```
Before:
lib_base - 30: 4E F9 00 01 23 45 JMP $00012345 (original function)
After SetFunction(lib, -30, MyNewFunc):
lib_base - 30: 4E F9 00 FF 00 10 JMP $00FF0010 (replacement)
```
It also:
1. Sets `lib_Flags |= LIBF_CHANGED` — signals that the library was patched
2. Invalidates `lib_Sum` — checksum no longer matches
3. Returns the **old** function address so the replacement can chain to the original
---
## Canonical Patching Pattern
```c
/* Store the old function pointer for chaining */
static APTR OldOpen = NULL;
/* Replacement function — must match the library's calling convention */
BPTR __saveds MyOpen(struct DosLibrary *base __asm("a6"),
BSTR name __asm("d1"),
LONG accessMode __asm("d2"))
{
/* Pre-processing */
kprintf("Open called: %s mode %ld\n", BADDR(name), accessMode);
/* Chain to original */
return ((BPTR(*)(struct DosLibrary *, BSTR, LONG))OldOpen)(base, name, accessMode);
}
/* Installation */
void install_patch(void)
{
Forbid(); /* atomic with respect to task switching */
OldOpen = (APTR)SetFunction((struct Library *)DOSBase,
-30, /* Open LVO */
(ULONG(*)())MyOpen);
Permit();
}
```
> [!WARNING]
> `Forbid()` / `Permit()` must surround the `SetFunction` call to prevent race conditions where another task calls the function between the time you read the old pointer and the time the new pointer is installed.
---
## Calling the Original (Chaining)
The replacement function **must** call the original to maintain correct library behaviour. The old function address returned by `SetFunction` is a raw pointer to the original function body (not the JMP slot):
```c
/* Jump to original using register convention */
static APTR OldAllocMem;
APTR __saveds MyAllocMem(ULONG size __asm("d0"), ULONG flags __asm("d1"),
struct ExecBase *base __asm("a6"))
{
APTR result = ((APTR(*)(ULONG, ULONG, struct ExecBase *))OldAllocMem)(size, flags, base);
if (result == NULL && (flags & MEMF_CHIP)) {
kprintf("Chip RAM alloc failed: %lu bytes\n", size);
}
return result;
}
```
---
## Removing a Patch
To remove a patch cleanly, restore the original:
```c
void remove_patch(void)
{
Forbid();
SetFunction((struct Library *)DOSBase, -30, (ULONG(*)())OldOpen);
Permit();
}
```
This **must** be done before unloading the library that contains the replacement function — otherwise the JMP table points to freed memory.
---
## SetFunction in Reverse Engineering Context
`SetFunction` creates trampoline patterns recognisable in disassembly:
**JMP table after patching:**
```asm
; Library JMP slot (lib_base - 30):
JMP.L $00FF1234 ; replacement function (different segment)
```
**Replacement function preamble:**
```asm
00FF1234:
MOVEM.L D0-D7/A0-A6, -(SP) ; save all regs (wrapper)
; ... logging/modification ...
MOVEM.L (SP)+, D0-D7/A0-A6
JMP.L OldOpen ; chain to original
```
**Detection heuristics:**
- JMP slot points outside the library's code segment
- `lib_Flags & LIBF_CHANGED` is set
- The `lib_Sum` no longer matches a fresh calculation
- The pointed-to function immediately chains to another address
---
## SetFunction vs Manual Patching
Some protections directly write to the JMP table without using `SetFunction`:
```asm
; Direct JMP table write (bypasses SetFunction protocol):
MOVE.L #MyFunc, -30+2(A0) ; overwrite address part of JMP instruction
```
This does not set `LIBF_CHANGED` and is harder to detect. Look for direct writes to `LIB_BASE - N + 2` (the address word of a JMP.L instruction).
---
## References
- NDK39: `exec/execbase.h`, `exec/libraries.h`
- ADCD 2.1 Autodocs: exec — `SetFunction`
- http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node01A8.html
- *Amiga ROM Kernel Reference Manual: Libraries* — library patching chapter

View file

@ -0,0 +1,116 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# Shared Library Runtime Mechanics
## Overview
AmigaOS shared libraries are **resident in memory** — once opened, the same code is shared by all tasks. The OS tracks open counts, handles version negotiation, and defers unloading until all users have closed the library.
---
## Library Discovery: `OpenLibrary()`
```c
struct Library *OpenLibrary(CONST_STRPTR libName, ULONG version);
```
`exec.library` searches for the library in this order:
1. `exec.library` LibList (already-open libraries in RAM)
2. Resident module list (ROM-resident: exec, graphics, etc.)
3. DOS path search — `LIBS:` assign — scan for `libName`
4. If found on disk: `LoadSeg()` + `InitLib` → add to LibList
5. Increment `lib_OpenCnt`
6. Call library's `Open()` vector
7. Return library base pointer (NULL on failure or version mismatch)
### Version Checking
```c
DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 36);
if (!DOSBase) { /* OS older than 2.0 — handle gracefully */ }
```
The `version` argument is the **minimum** acceptable `lib_Version`. Version 0 accepts any.
| Library | Version | OS |
|---|---|---|
| `exec.library` | 33 | OS 1.2 |
| `exec.library` | 36 | OS 2.0 |
| `exec.library` | 39 | OS 3.0 |
| `exec.library` | 40 | OS 3.1 |
| `exec.library` | 44 | OS 3.2 |
---
## Library Base Structure
```c
/* exec/libraries.h */
struct Library {
struct Node lib_Node; /* ln_Type = NT_LIBRARY */
UBYTE lib_Flags; /* LIBF_SUMUSED, LIBF_DELEXP */
UBYTE lib_Pad;
UWORD lib_NegSize; /* bytes of JMP table preceding base */
UWORD lib_PosSize; /* sizeof(Library) + private fields */
UWORD lib_Version;
UWORD lib_Revision;
APTR lib_IdString; /* "dos.library 40.1 (16.7.93)" */
ULONG lib_Sum; /* JMP table checksum */
UWORD lib_OpenCnt; /* reference count */
};
```
The pointer returned by `OpenLibrary` points to this structure. The JMP table is **below** the base at negative offsets.
---
## Standard Library Vectors
| Offset | Function | Description |
|---|---|---|
| 6 | `Open` | Increment open count, return base |
| 12 | `Close` | Decrement count, optionally expunge |
| 18 | `Expunge` | Free library if open count == 0 |
| 24 | `Reserved` | Always NULL |
| 30 and below | Library-specific | Per `.fd` file |
---
## Open Count and Expunge Deferral
```c
/* exec.library CloseLibrary() pseudo-code */
lib->lib_OpenCnt--;
if (lib->lib_OpenCnt == 0) {
BPTR seg = CallVector(lib, CLOSE_VEC);
if (seg) UnLoadSeg(seg);
}
```
A library sets `LIBF_DELEXP` when it cannot unload (low memory). On the next close that drops the count to zero, expunge runs.
---
## Open/Close Lifecycle Diagram
```mermaid
stateDiagram-v2
[*] --> Unloaded
Unloaded --> Initialised : LoadSeg + InitLib
Initialised --> Open : lib_OpenCnt++ (OpenLibrary)
Open --> Open : additional opens
Open --> Initialised : CloseLibrary, count > 0
Initialised --> Expunging : CloseLibrary, count == 0
Expunging --> Unloaded : Expunge OK — FreeMem + UnLoadSeg
Expunging --> Initialised : LIBF_DELEXP — deferred
```
---
## References
- NDK39: `exec/libraries.h`, `exec/nodes.h`
- ADCD 2.1 Autodocs: `OpenLibrary`, `CloseLibrary`, `MakeLibrary`
- *Amiga ROM Kernel Reference Manual: Libraries* — library creation chapter
- http://amigadev.elowar.com/read/ADCD_2.1/Libraries_Manual_guide/node0124.html

View file

@ -0,0 +1,209 @@
[← Home](../README.md) · [Linking & Libraries](README.md)
# Startup Code — c.o / gcrt0.S
## Overview
The AmigaOS **startup code** is the first code that runs when an executable is launched. It bridges the OS loader (which jumps to hunk 0 offset 0) and the C `main()` function. Understanding it is critical for reverse engineering — it reveals the initialisation sequence, library opens, argument parsing, and the Workbench vs CLI detection path.
---
## Entry Point Contract
When `CreateProc()` dispatches the new task, the CPU jumps to the first longword of hunk 0 with:
| Register | Value |
|---|---|
| D0 | Length of CLI argument string |
| A0 | Pointer to CLI argument string (or NULL for Workbench) |
| A1 | Pointer to WBStartup message (Workbench) or NULL (CLI) |
| A6 | (caller's A6 — not reliable) |
| SP | Top of the allocated stack |
---
## SAS/C Startup (c.o)
The SAS/C `c.o` module is the canonical AmigaOS C startup:
```asm
_start:
; 1. Get SysBase from absolute address 4
MOVE.L 4.W, A6
MOVE.L A6, _SysBase
; 2. Detect CLI vs Workbench launch
MOVE.L D0, _RawCommandLen ; save CLI arg length
MOVE.L A0, _RawCommandStr ; save CLI arg pointer
TST.L A1 ; A1=NULL means CLI, non-NULL = Workbench
BEQ.S .cli_launch
; Workbench path:
MOVE.L A1, _WBenchMsg ; save WBStartup message
JSR _OpenLibraries ; open DOS, graphics etc.
BSR _main ; call C main()
BRA.S .exit
.cli_launch:
JSR _OpenLibraries
BSR _main
; fall through to exit
.exit:
MOVE.L D0, _ReturnCode ; main() return value
JSR _CloseLibraries
; return D0 to AmigaDOS
RTS
```
### _OpenLibraries (SAS/C)
```asm
_OpenLibraries:
MOVEA.L _SysBase, A6
; Open dos.library
LEA _dosname(PC), A1 ; "dos.library"
MOVEQ #0, D0
JSR -552(A6) ; OpenLibrary
MOVE.L D0, _DOSBase
; (other libraries as needed by pragmas)
RTS
_dosname: DC.B "dos.library", 0
```
### Workbench Message Handling (SAS/C)
```asm
; After main() returns, if Workbench launch:
MOVEA.L _SysBase, A6
JSR -132(A6) ; Forbid()
MOVEA.L _WBenchMsg, A1
JSR -378(A6) ; ReplyMsg(_WBenchMsg)
JSR -138(A6) ; Permit() — never actually reached
RTS
```
> [!IMPORTANT]
> For Workbench launches, `Forbid()` must be called **before** `ReplyMsg()` on the WBStartup message. The Workbench process waits for this reply; if the app exits without replying, the Workbench can crash or hang.
---
## GCC Startup (gcrt0.S / libnix)
GCC's AmigaOS startup is provided by `libnix` (or newer `clib2`):
```asm
/* gcrt0.S — GCC startup */
.text
.globl _start
_start:
move.l 4.w, a6 /* SysBase */
jsr ___startup_SysBase /* store SysBase, init libnix */
/* Open dos.library */
lea _doslib(pc), a1
moveq #0, d0
jsr -552(a6) /* OpenLibrary */
move.l d0, _DOSBase
/* Parse args, set up __argv/__argc */
jsr ___parse_args
/* Call main() */
jsr _main
/* Exit cleanup */
move.l d0, -(sp) /* return value */
jsr ___exit
_doslib: .asciz "dos.library"
```
`libnix` provides a more complete C runtime than the minimal SAS/C `c.o`.
---
## Argument Parsing
### CLI Arguments
CLI arguments arrive as a **raw byte string** pointed to by A0, with D0 holding the length. The startup code must:
1. Copy the string (it lives in the caller's stack)
2. Tokenise it into `argc`/`argv` (standard) or pass raw via `RawArg()`
SAS/C standard `argc`/`argv`:
```c
/* _main.c in c.o */
int main(int argc, char *argv[]);
/* startup converts:
RawCommandStr = "myarg1 myarg2\n"
→ argc = 3, argv = ["progname", "myarg1", "myarg2"] */
```
### Workbench Arguments
For Workbench launches, the WBStartup message carries an array of `WBArg` structures:
```c
struct WBStartup {
struct Message sm_Message;
struct MsgPort *sm_Process; /* WB process port */
BPTR sm_Segment; /* loaded segment */
LONG sm_NumArgs; /* number of args */
char *sm_ToolWindow;
struct WBArg *sm_ArgList; /* array of WBArg */
};
struct WBArg {
BPTR wa_Lock; /* directory containing the icon */
STRPTR wa_Name; /* filename of the icon */
};
```
---
## Stack Setup
The stack size is specified at link time (SAS/C) or in the Tooltype/CLI:
```
; SAS/C: specify in linker command:
slink lib/c.o myobj.o TO myexe STACKSIZE 8192
; At runtime, AmigaDOS CreateProc() passes NP_StackSize
```
The startup code does **not** set up the stack — that is done by `CreateProc()` / the OS task dispatch. The startup can optionally check for stack overflow via `GetCC()` / `CheckStack()`.
---
## Recognising Startup Code in IDA Pro
The startup stub is always at the start of hunk 0. Look for:
```asm
; SAS/C signature:
MOVE.L $00000004, A6 ; or MOVEA.L 4.W, A6
; followed immediately by MOVE.L A6, _SysBase
```
```asm
; GCC signature (bebbo):
MOVE.L 4.W, A6
JSR some_init_stub ; libnix internal
```
After identifying startup, `main()` is the first function BSR/JSR'd after the library opens.
---
## References
- NDK39: `lib/` directory — `c.o`, `c_sm.o` (SAS/C startup variants)
- libnix source: https://github.com/bebbo/libnix
- clib2 source: https://github.com/adozenlines/clib2
- SAS/C 6.x Programmer's Guide — startup code chapter
- *Amiga ROM Kernel Reference Manual: Libraries* — process creation, Workbench startup