Before diving into identification techniques, it helps to understand the mechanics from first principles.
### What is a Shared Library?
On AmigaOS, a **shared library** is a block of code loaded into RAM once and shared by every program that needs it. Programs don't link the OS code into their own executable — they call it indirectly at runtime. This keeps executables small and allows the OS to be upgraded without relinking every application.
When you open a library, exec returns a pointer to the **library base** — a `struct Library` that lives in RAM. Immediately *before* this pointer (at negative offsets) sits the **JMP table**: a sequence of `JMP <address>` instructions, one per library function.
```
Memory layout:
lib_base - 30: JMP Open_impl ← first user function
lib_base - 24: JMP Reserved
lib_base - 18: JMP Expunge
lib_base - 12: JMP Close
lib_base - 6: JMP Open (standard)
lib_base + 0: struct Library ← pointer returned by OpenLibrary()
lib_base + N: private library data
```
Every program that wants to call `dos.library Open()` stores the library base somewhere and calls `JSR -30(A6)`, where A6 holds the library base.
---
## What is an LVO?
**LVO** stands for **Library Vector Offset**. It is the negative byte offset from the library base to a specific function's JMP table slot.
The formula is:
```
LVO = −6 × (slot_index + 1)
slot 0 (Open standard): −6
slot 1 (Close standard): −12
slot 2 (Expunge): −18
slot 3 (Reserved): −24
slot 4 (first user fn): −30 ← dos.library Open()
slot 5: −36 ← dos.library Close()
...
```
So `JSR -30(A6)` means "call the function at LVO −30 in the library whose base is in A6." Every unique LVO in every library maps to exactly one function.
### Why Negative Offsets?
The JMP table grows **downward** in memory from the library base. Using negative offsets means programs only need to store a single pointer (the library base) and derive all function entry points from it with a constant displacement. This is the same trick used by C++ vtables.
---
## What is an .fd File?
**`.fd` files** (Function Descriptor files) are part of the Amiga NDK (Native Developer Kit). They are simple text files that declare every public function in a library: its name, argument registers, and LVO (called the **bias** in `.fd` terminology).
### Example: `dos_lib.fd` (excerpt)
```
##base _DOSBase
##bias 30
##public
Open(name,accessMode)(d1,d2)
##bias 36
Close(file)(d1)
##bias 42
Read(file,buffer,length)(d1,d2,d3)
##bias 48
Write(file,buffer,length)(d1,d2,d3)
##bias 54
Input()(-)
##bias 60
Output()(-)
##bias 138
Delay(timeout)(d1)
```
Reading this:
-`##base _DOSBase` — the global variable that holds the library base
-`##bias 30` — the **positive** bias; the actual call offset is `−30`
-`Open(name,accessMode)(d1,d2)` — function name, argument names, and the registers each argument goes in
So `##bias 30` means LVO `−30`. When you see `JSR (-30,A6)` in disassembly and A6 holds `DOSBase`, that is `dos.library Open()`.
### Where are .fd files?
In the NDK39 distribution at:
```
NDK39/
fd/
dos_lib.fd
exec_lib.fd
graphics_lib.fd
intuition_lib.fd
...
```
They are plain text — open any with a text editor.
---
## The Canonical Call Pattern
Every AmigaOS library call in disassembly looks like this:
```asm
MOVEA.L (_DOSBase).L, A6 ; (1) load the library base into A6
JSR (-30,A6) ; (2) call function at LVO -30 = Open()
; D0 now contains the return value
```
Sometimes the base is loaded once and reused:
```asm
MOVEA.L (_DOSBase).L, A6
JSR (-30,A6) ; Open
...
; A6 still holds DOSBase — no reload needed
JSR (-48,A6) ; Write
```
And for `exec.library`, programs often use the fixed address `$4` directly:
```asm
MOVEA.L 4.W, A6 ; exec.library base is always at $4
MOVEQ #40, D0 ; minimum version
LEA _str_dos(PC), A1 ; "dos.library"
JSR (-552,A6) ; exec.library OpenLibrary(A1,D0)
MOVE.L D0, _DOSBase ; save result for later
```
---
## Step-by-Step: Tracing OS Calls in IDA Pro
### Step 1 — Find OpenLibrary calls at startup
Search for `JSR (-552,A6)` — that is always `exec.library OpenLibrary`. The instruction immediately before it loads A1 with a library name string.
```asm
LEA (_str_dos).L, A1 ; → xref this to see "dos.library"
MOVEQ #40, D0
MOVEA.L 4.W, A6
JSR (-552,A6) ; OpenLibrary("dos.library", 40)
MOVE.L D0, (_DOSBase).L ; ← label this global "_DOSBase"
```
Press `N` in IDA on the `_DOSBase` write to name the variable.
### Step 2 — Find all reads of that library base
Press `X` on `_DOSBase` to show all cross-references. Each xref is either a write (the open) or a read (before a JSR).
### Step 3 — Resolve each JSR to a function name
For each `JSR (-N,A6)` where A6 holds `_DOSBase`:
1. Look up `N` in `dos_lib.fd` under `##bias N`
2. Read the function name
3. Press `N` in IDA on the JSR instruction's displacement to annotate it
After annotation:
```asm
MOVEA.L (_DOSBase).L, A6
JSR (Open,A6) ; was: JSR (-30,A6)
```
### Step 4 — Note argument registers
From `dos_lib.fd`:
```
Open(name,accessMode)(d1,d2)
```
So immediately before the JSR:
-`D1` is loaded with the filename pointer
-`D2` is loaded with the access mode (`MODE_OLDFILE` = 1005, `MODE_NEWFILE` = 1006)