mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-12 16:16:28 +00:00
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.
194 lines
6.8 KiB
Markdown
194 lines
6.8 KiB
Markdown
[← 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
|