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
44
04_linking_and_libraries/README.md
Normal file
44
04_linking_and_libraries/README.md
Normal 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
|
||||
188
04_linking_and_libraries/compiler_stubs.md
Normal file
188
04_linking_and_libraries/compiler_stubs.md
Normal 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 **A0–A3**, **D0–D7** as specified by the `.fd` file
|
||||
- Return value in **D0** (or D0+D1 for 64-bit returns)
|
||||
- **Callee** preserves: D2–D7, A2–A6
|
||||
- **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
|
||||
172
04_linking_and_libraries/fd_files.md
Normal file
172
04_linking_and_libraries/fd_files.md
Normal 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`
|
||||
198
04_linking_and_libraries/inline_stubs.md
Normal file
198
04_linking_and_libraries/inline_stubs.md
Normal 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`) |
|
||||
| D1–D7, A0–A3 | Arguments (per `.fd` file) |
|
||||
| A4, A5 | Scratch in OS (do not rely on preservation) |
|
||||
| D2–D7, A2–A3 | **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/
|
||||
142
04_linking_and_libraries/library_structure.md
Normal file
142
04_linking_and_libraries/library_structure.md
Normal 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
|
||||
172
04_linking_and_libraries/link_libraries.md
Normal file
172
04_linking_and_libraries/link_libraries.md
Normal 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
|
||||
194
04_linking_and_libraries/lvo_table.md
Normal file
194
04_linking_and_libraries/lvo_table.md
Normal 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
|
||||
124
04_linking_and_libraries/register_conventions.md
Normal file
124
04_linking_and_libraries/register_conventions.md
Normal 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`) |
|
||||
| D1–D7, A0–A3 | Arguments — exact registers per `.fd` |
|
||||
| D2–D7, A2–A3 | **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`)
|
||||
- D2–D7/A2–A3 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
|
||||
158
04_linking_and_libraries/setfunction.md
Normal file
158
04_linking_and_libraries/setfunction.md
Normal 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
|
||||
116
04_linking_and_libraries/shared_libraries_runtime.md
Normal file
116
04_linking_and_libraries/shared_libraries_runtime.md
Normal 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
|
||||
209
04_linking_and_libraries/startup_code.md
Normal file
209
04_linking_and_libraries/startup_code.md
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue