[← 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 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
