amiga-bootcamp/04_linking_and_libraries/startup_code.md

210 lines
5.5 KiB
Markdown
Raw Permalink Normal View History

[← Home](../README.md) · [Linking & Libraries](README.md)
# Startup Code — c.o / gcrt0.S
## Overview
2026-04-26 14:46:18 -04:00
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 initialization 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