amiga-bootcamp/04_linking_and_libraries/lvo_table.md
Ilia Sharin 21751c0025 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.
2026-04-23 12:17:35 -04:00

194 lines
6.8 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)
# 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