You want to know every file an application opens. Or every byte it writes. Or every memory allocation it makes — with sizes, flags, and call stacks. You could patch the binary. Or you could use the operating system's own hooking mechanism: **`SetFunction()`**.
`SetFunction()` is AmigaOS's official API for **replacing a library's JMP table entry at runtime.** It atomically swaps the target address of a specific LVO, returning the original pointer so you can construct a trampoline. Every call through that LVO — from every task, in every process — now routes through your code. This is the foundation of Amiga reverse engineering tooling: file system monitors, API tracers, memory debuggers, and anti-piracy checks all begin with `SetFunction()`.
**Critical:** Always restore the original function before the program exits. Failure leaves a dangling pointer in the library JMP table, causing crashes for any subsequent users of the library.
| **Task A6 replacement** | Single task | Medium | Per-application sandboxing |
| **Binary patch (file)** | Single binary | High (modifies disk) | Permanent behavior change, crack intros |
---
## Named Antipatterns
### 1. "The Leaky Hook"
**What it looks like** — installing a hook but never removing it:
```c
void setup(void) {
Forbid();
orig = SetFunction(DOSBase, -48, my_write);
Permit();
// No atexit() cleanup — hook lives forever
}
```
**Why it fails:** When the hooking program exits, `my_write` is unloaded from memory. But the JMP table still points to it. The next task that calls `Write()` jumps into freed memory → Guru Meditation.
**Correct:** Always register cleanup:
```c
void cleanup(void) {
Forbid();
SetFunction(DOSBase, -48, orig); // restore original
Permit();
}
// In main():
atexit(cleanup);
```
### 2. "The Forbid-Free Patch"
**What it looks like** — calling `SetFunction()` without `Forbid()`:
```c
// BROKEN — task switch during SetFunction may corrupt list
orig = SetFunction(DOSBase, -48, my_write);
```
**Why it fails:** `SetFunction()` modifies the library's `lib_OpenCnt` and may trigger expunge logic. If a task switch occurs during this modification, another task may see an inconsistent state. The result: corrupted open counts, premature expunge, or lost patches.
**Correct:** Always wrap in `Forbid()`/`Permit()`.
### 3. "The Register Stomper"
**What it looks like** — a hook that corrupts registers before calling the original:
```asm
_my_write:
MOVEM.L D0-D2/A0-A1, -(SP) ; save only D0-D2/A0-A1
JSR _log_args
MOVEM.L (SP)+, D0-D2/A0-A1
MOVEA.L _orig_write, A0
JMP (A0) ; D3-D7/A2-A6 may contain garbage!
```
**Why it fails:** The original `Write()` expects `D1`=file, `D2`=buffer, `D3`=length. If your logging code modified D3 and you didn't save/restore it, the original function sees a corrupted length — potentially writing gigabytes or zero bytes. Even worse: the caller may rely on other registers (D4-D7, A2-A5) being preserved per the AmigaOS ABI, and your hook trashed them.
**Correct:** Save and restore ALL registers the original function might read or the caller expects preserved. The safest approach is `MOVEM.L D0-D7/A0-A6`.
---
## Use-Case Cookbook
### File Access Tracer — Log Every Open
```c
static APTR orig_Open;
LONG __asm my_Open(register __d1 STRPTR name,
register __d2 LONG mode) {
LONG result = ((LONG(*)(STRPTR,LONG))orig_Open)(name, mode);
| JMP table modification | IAT hooking | PLT/GOT hooking | Amiga's JMP table is simpler — one 6-byte write vs multi-level indirection |
| Trampoline pattern | Detour trampoline | `dlsym(RTLD_NEXT, "write")` | Same: call original after instrumentation |
| `Forbid()`/`Permit()` | `SuspendThread` / `ResumeThread` (crude) | Signal blocking (crude) | Amiga's task-level atomicity is unique — no per-thread suspend needed |
| System-wide by default | Per-process by default | Per-process by default | Amiga's flat address space means one hook covers everything |
---
## FAQ
### Does SetFunction work on all library types?
Yes — `SetFunction()` works on any library with a standard JMP table (exec, dos, graphics, intuition, third-party). It does NOT work on ROM-based resident modules that use a different dispatch mechanism (some Kickstart modules).
### Can multiple hooks coexist on the same function?
Yes — in a chain. Each hook saves the "original" pointer (which may itself be a previous hook's trampoline). Removal must happen in reverse order: last hooked = first removed. Removing hooks out of order breaks the chain.
### Is SetFunction safe across CPU architectures?
On 68000–68060, yes. However, 68040+ systems with data cache enabled may cache the old JMP table entry. Always call `CacheClearU()` after `SetFunction()` on 040/060 to flush the data cache and ensure the new target address is visible to the instruction fetch unit.