amiga-bootcamp/04_linking_and_libraries/library_structure.md
2026-04-26 14:46:18 -04:00

432 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[← Home](../README.md) · [Linking & Libraries](README.md)
# AmigaOS Library Structure — Architecture, Construction, Memory Layout
## Overview
Every AmigaOS library, device, and resource is a **struct Library** preceded by a negative-offset JMP table. This document covers the complete memory layout, how `MakeLibrary()` constructs a library from scratch, the function vector table encoding, checksum verification, and how to create your own shared libraries.
---
## Memory Layout
```
Low addresses High addresses
──────────────────────────────────────────────────────────────────────────
┌──────────────────────────────────┬──────────────────────────────────┐
│ JMP Table (negative) │ Library Data (positive) │
│ │ │
│ base - N: JMP func_N │ base + $00: struct Library │
│ ... │ base + $22: lib_OpenCnt │
│ base - 36: JMP func_6 │ base + $24: [private data] │
│ base - 30: JMP func_5 (1st user)│ base + $XX: [more private] │
│ base - 24: JMP Reserved │ │
│ base - 18: JMP Expunge │ │
│ base - 12: JMP Close │ │
│ base - 6: JMP Open │ │
└──────────────────────────────────┴──────────────────────────────────┘
Library Base Pointer
(returned by OpenLibrary)
```
### Memory Allocation
The library occupies a **single contiguous allocation**:
```c
/* Total allocation = JMP table + Library header + private data */
ULONG total = lib_NegSize + lib_PosSize;
/* AllocMem returns the start of the block */
APTR block = AllocMem(total, MEMF_PUBLIC | MEMF_CLEAR);
/* Library base pointer = block + lib_NegSize */
struct Library *lib = (struct Library *)((UBYTE *)block + lib_NegSize);
```
---
## The Library Node
```c
/* exec/libraries.h */
struct Library {
struct Node lib_Node; /* +$00: linked list node (14 bytes) */
UBYTE lib_Flags; /* +$0E: LIBF_ flags */
UBYTE lib_pad; /* +$0F: alignment padding */
UWORD lib_NegSize; /* +$10: bytes of JMP table */
UWORD lib_PosSize; /* +$12: bytes of Library struct + private */
UWORD lib_Version; /* +$14: major version */
UWORD lib_Revision; /* +$16: minor revision */
APTR lib_IdString; /* +$18: "mylib.library 1.0 (1.1.95)\r\n" */
ULONG lib_Sum; /* +$1C: checksum of JMP table */
UWORD lib_OpenCnt; /* +$20: current open count */
};
/* sizeof(struct Library) = 34 bytes ($22) */
```
### The Node Header
```c
/* exec/nodes.h */
struct Node {
struct Node *ln_Succ; /* +$00: next node in list */
struct Node *ln_Pred; /* +$04: previous node */
UBYTE ln_Type; /* +$08: NT_LIBRARY, NT_DEVICE, NT_RESOURCE */
BYTE ln_Pri; /* +$09: priority (for Enqueue) */
char *ln_Name; /* +$0A: "dos.library" */
};
```
| ln_Type Value | Hex | Type |
|---|---|---|
| `NT_LIBRARY` | $09 | Standard shared library |
| `NT_DEVICE` | $03 | I/O device (trackdisk.device, etc.) |
| `NT_RESOURCE` | $08 | Hardware resource (cia.resource, etc.) |
---
## JMP Table Encoding
Each function vector is a 6-byte absolute JMP instruction:
```
Bytes: 4E F9 AA AA AA AA
└──┘ └──────────┘
JMP 32-bit absolute target address
.L
```
### Why 6 Bytes?
The 68000 `JMP (xxx).L` instruction is exactly 6 bytes:
- 2 bytes: opcode `$4EF9`
- 4 bytes: 32-bit absolute address
This is the most compact way to do an indirect jump to an arbitrary address. The fixed 6-byte slot size allows the LVO to be calculated as a simple multiplication: `LVO = -(function_index × 6)`.
### Calling Through the JMP Table
```asm
; User code:
MOVEA.L _DOSBase, A6 ; load library base into A6
JSR -48(A6) ; jump to JMP table slot at base-48
; CPU executes:
; 1. Push return address onto stack
; 2. Compute effective address: A6 + (-48) = base - 48
; 3. Read 6 bytes at that address: 4EF9 AAAAAAAA
; 4. Jump to AAAAAAAA (the actual function body)
; 5. Function executes and RTS returns to caller
```
The double-jump (JSR→JMP→function→RTS) costs approximately 12 extra cycles on a 68000 — negligible compared to function execution time.
### Standard Functions (Mandatory for All Libraries)
| LVO | Index | Function | Purpose |
|---|---|---|---|
| 6 | 1 | `Open()` | Called by `OpenLibrary()` — increment refcount, return base |
| 12 | 2 | `Close()` | Called by `CloseLibrary()` — decrement refcount |
| 18 | 3 | `Expunge()` | Free resources when refcount reaches 0 |
| 24 | 4 | `Reserved()` | Must return 0 — reserved for future use |
| 30 | 5 | (first public function) | Library-specific — defined by `.fd` file |
---
## MakeLibrary() — Building a Library
```c
struct Library *MakeLibrary(
APTR funcArray, /* A0: function pointer array */
APTR structInit, /* A1: struct initializer table (or NULL) */
APTR initFunc, /* A2: init function (or NULL) */
ULONG dataSize, /* D0: sizeof(MyLibBase) */
BPTR segList /* D1: segment list (for UnLoadSeg on expunge) */
);
```
### Function Array Formats
`funcArray` can be in two formats:
**Format 1: Absolute Pointers** (most common for C libraries)
```c
/* NULL-terminated array of function pointers */
APTR myFuncArray[] = {
(APTR)MyOpen, /* LVO -6 */
(APTR)MyClose, /* LVO -12 */
(APTR)MyExpunge, /* LVO -18 */
(APTR)NULL, /* LVO -24: Reserved */
(APTR)MyFunc1, /* LVO -30 */
(APTR)MyFunc2, /* LVO -36 */
(APTR)-1 /* terminator */
};
```
**Format 2: Relative Offsets** (used by ROM modules)
```c
/* First entry = -1 signals relative mode */
/* Following entries are WORD offsets from funcArray base */
WORD myFuncArray[] = {
-1, /* flag: relative mode */
MyOpen - funcBase, /* WORD offset to Open */
MyClose - funcBase, /* WORD offset to Close */
MyExpunge - funcBase, /* WORD offset to Expunge */
0, /* Reserved = NULL (offset 0 maps to a NOP) */
MyFunc1 - funcBase,
MyFunc2 - funcBase,
-1 /* terminator */
};
```
### MakeLibrary Internals
```c
/* What MakeLibrary does internally: */
/* 1. Count functions by scanning funcArray to the terminator */
ULONG numFuncs = CountFunctions(funcArray);
ULONG negSize = numFuncs * 6; /* 6 bytes per JMP slot */
/* 2. Allocate combined block */
ULONG posSize = MAX(dataSize, sizeof(struct Library));
APTR block = AllocMem(negSize + posSize, MEMF_PUBLIC | MEMF_CLEAR);
struct Library *lib = (struct Library *)((UBYTE *)block + negSize);
/* 3. Fill JMP table using MakeFunctions() */
MakeFunctions(lib, funcArray, NULL);
/* For each function pointer, writes:
*(UWORD *)(slot) = 0x4EF9; // JMP.L opcode
*(ULONG *)(slot + 2) = funcAddr; // target address */
/* 4. Initialize struct Library fields */
lib->lib_NegSize = negSize;
lib->lib_PosSize = posSize;
/* 5. Apply structInit table (if provided) */
if (structInit)
InitStruct(structInit, lib, 0);
/* 6. Call init function (if provided) */
if (initFunc)
lib = ((struct Library *(*)(struct Library *, BPTR, struct ExecBase *))
initFunc)(lib, segList, SysBase);
/* 7. Compute and store checksum */
SumLibrary(lib);
return lib;
```
---
## Creating a Shared Library — Complete Example
### Library Source (my.library)
```c
#include <exec/types.h>
#include <exec/libraries.h>
#include <exec/resident.h>
#include <proto/exec.h>
/* Private library base — extends struct Library */
struct MyLibBase {
struct Library lib; /* MUST be first */
ULONG myPrivate; /* library-specific data */
BPTR segList; /* for Expunge to UnLoadSeg */
};
/* Forward declarations */
struct Library * __saveds MyOpen(void);
BPTR __saveds MyClose(void);
BPTR __saveds MyExpunge(void);
ULONG MyReserved(void);
LONG __saveds MyAdd(LONG a __asm("d0"), LONG b __asm("d1"));
/* Function table */
static APTR FuncTable[] = {
(APTR)MyOpen,
(APTR)MyClose,
(APTR)MyExpunge,
(APTR)MyReserved,
(APTR)MyAdd, /* LVO -30: first user function */
(APTR)-1
};
/* Struct init table */
static struct MyInitData {
/* ... InitStruct data to set lib_Node.ln_Type, ln_Name, etc. */
} InitData;
/* Init function — called once when library first loads */
struct Library * __saveds LibInit(
struct MyLibBase *base __asm("d0"),
BPTR segList __asm("a0"),
struct ExecBase *sysBase __asm("a6"))
{
base->segList = segList;
base->myPrivate = 0;
return (struct Library *)base;
}
/* RomTag — how exec finds us */
static struct Resident RomTag = {
RTC_MATCHWORD, /* $4AFC */
&RomTag, /* pointer to self */
&RomTag + 1, /* end skip */
RTF_AUTOINIT, /* auto-init flag */
1, /* version */
NT_LIBRARY, /* type */
0, /* priority */
"my.library", /* name */
"my.library 1.0 (1.1.95)\r\n",
&AutoInitTable /* init — points to auto-init array */
};
/* Auto-init table (for RTF_AUTOINIT) */
static ULONG AutoInitTable[] = {
sizeof(struct MyLibBase), /* data size */
(ULONG)FuncTable, /* function array */
(ULONG)&InitData, /* struct init data */
(ULONG)LibInit /* init function */
};
/* LVO -6: Open */
struct Library * __saveds MyOpen(void)
{
struct MyLibBase *base = (struct MyLibBase *)
REG_A6; /* library base passed in A6 */
base->lib.lib_OpenCnt++;
base->lib.lib_Flags &= ~LIBF_DELEXP;
return (struct Library *)base;
}
/* LVO -12: Close */
BPTR __saveds MyClose(void)
{
struct MyLibBase *base = (struct MyLibBase *)REG_A6;
base->lib.lib_OpenCnt--;
if (base->lib.lib_OpenCnt == 0 &&
(base->lib.lib_Flags & LIBF_DELEXP))
{
return MyExpunge();
}
return 0;
}
/* LVO -18: Expunge */
BPTR __saveds MyExpunge(void)
{
struct MyLibBase *base = (struct MyLibBase *)REG_A6;
if (base->lib.lib_OpenCnt > 0)
{
base->lib.lib_Flags |= LIBF_DELEXP;
return 0; /* Can't expunge yet — still in use */
}
BPTR seg = base->segList;
Remove(&base->lib.lib_Node); /* Remove from LibList */
FreeMem((UBYTE *)base - base->lib.lib_NegSize,
base->lib.lib_NegSize + base->lib.lib_PosSize);
return seg; /* Return segment list for UnLoadSeg */
}
/* LVO -24: Reserved */
ULONG MyReserved(void) { return 0; }
/* LVO -30: Your first function */
LONG __saveds MyAdd(LONG a __asm("d0"), LONG b __asm("d1"))
{
return a + b;
}
```
### Corresponding .fd File
```
##base _MyBase
##bias 30
##public
Add(a,b)(D0,D1)
##end
```
---
## Library Flags
```c
/* lib_Flags bits: */
#define LIBF_SUMMING (1<<0) /* Currently computing checksum */
#define LIBF_CHANGED (1<<1) /* JMP table was patched (SetFunction) */
#define LIBF_SUMUSED (1<<2) /* Checksum is valid and in use */
#define LIBF_DELEXP (1<<3) /* Delayed expunge — expunge when refcount=0 */
```
| Flag | Set By | Checked By |
|---|---|---|
| `LIBF_SUMMING` | `SumLibrary()` | Internal — prevents recursive sum |
| `LIBF_CHANGED` | `SetFunction()` | `ShowConfig`, virus scanners — detect patches |
| `LIBF_SUMUSED` | `SumLibrary()` | `exec` — decides if checksum valid |
| `LIBF_DELEXP` | Memory-low handler | `Close()` — triggers deferred expunge |
---
## Checksum Verification
```c
/* SumLibrary() computes checksum over the JMP table: */
void SumLibrary(struct Library *lib)
{
lib->lib_Flags |= LIBF_SUMMING;
ULONG sum = 0;
UWORD *p = (UWORD *)((UBYTE *)lib - lib->lib_NegSize);
ULONG count = lib->lib_NegSize / 2; /* in words */
while (count--)
sum += *p++;
lib->lib_Sum = sum;
lib->lib_Flags &= ~LIBF_SUMMING;
lib->lib_Flags |= LIBF_SUMUSED;
}
/* To verify: recompute and compare to lib_Sum */
/* If mismatch: Alert(AN_LibChkSum) — Guru Meditation */
```
> **Anti-tamper**: exec periodically validates library checksums. If `SetFunction()` patches a library, it sets `LIBF_CHANGED` so exec skips the checksum check. Direct JMP table writes (bypassing `SetFunction`) will trigger a checksum Guru on the next validation pass.
---
## Library Lifecycle
```mermaid
stateDiagram-v2
[*] --> OnDisk : LIBS:my.library file
OnDisk --> Loading : OpenLibrary("my.library", 1)
Loading --> Initialized : LoadSeg + MakeLibrary + LibInit
Initialized --> Added : AddLibrary → SysBase→LibList
Added --> Open : OpenLibrary → Open() LVO → lib_OpenCnt++
Open --> Open : More OpenLibrary calls
Open --> Closing : CloseLibrary → Close() LVO → lib_OpenCnt--
Closing --> Added : lib_OpenCnt > 0
Closing --> Expunging : lib_OpenCnt == 0
Expunging --> [*] : Expunge() → FreeMem + UnLoadSeg
Expunging --> Added : LIBF_DELEXP (deferred — still has openers)
```
---
## References
- NDK39: `exec/libraries.h`, `exec/nodes.h`, `exec/resident.h`
- ADCD 2.1 Autodocs: `OpenLibrary`, `MakeLibrary`, `MakeFunctions`, `AddLibrary`, `SetFunction`, `SumLibrary`
- *Amiga ROM Kernel Reference Manual: Libraries* — library architecture chapter
- See also: [SetFunction](setfunction.md) — runtime patching
- See also: [LVO Table](lvo_table.md) — complete offset tables
- See also: [Shared Libraries Runtime](shared_libraries_runtime.md) — OpenLibrary internals