Patchen van een library functie: niet zo innovatief hoor, Windows... Amiga was je voor

Friday 23 September 2011, 00:54:00 | software dev

Ik kwam dit artikel tegen vandaag op proggit: Why do Windows functions all begin with a pointless MOV EDI, EDI instruction?

Het blijkt dat "[it] is just enough space to patch in a jump instruction so that the function can be updated on the fly."

Ik moest direct denken aan hoe dat vroeger op de Commodore Amiga ging eind jaren '80 begin jaren '90 (goeie ouwe tijd). Behoorlijk veel in 680x0 assembler en C geprogrammeerd op dat fabuleuze apparaat. Met heel veel dingen liep de Amiga ver voor op de concurrentie van toen, en blijkbaar ook al als het ging om het patchen van library functies. Want die MOV EDI,EDI van Windows DLLs voelt maar erg primitief aan ten opzichte van wat Amiga's operating systeem destijds al had: shared libraries met OS-support voor het patchen van de library functies.

[[image: amigalibrary.png]] Het patchen van library functies werd op de Amiga erg veel toegepast om bijvoorbeeld bugs te fixen in de libraries die in ROM werden geleverd en dus niet via een normale software update gerepareerd kunnen worden (zoals exec.library, graphics.library, intuition.library). Tevens waren er veel 3rd-party patch tools om bijvoorbeeld de performance van bepaalde functies te verbeteren (bijvoorbeeld CopyMemQuick). En zo waren er nog veel meer mogelijkheden: je kon elke library functie omleiden naar een nieuw stukje code, zonder daarvoor te hoeven hacken, want het was een officieel door het OS ondersteund mechanisme.

Hoe werkte het namelijk:

Een library op de Amiga werd in het geheugen geladen zoals getoond in het plaatje hiernaast. Je had de library base op offset 0. Op negatieve offsets t.o.v. de library base waren de officiƫle library functies die je kon aanroepen. In de header files voor je compiler/assembler werden ze als constanten gedefinieerd, b.v.

OpenLibrary  equ -552 
CloseLibrary equ -414 
AllocMem     equ -356

en een library call ging ongeveer als volgt:

move.l  ExecBase,a6    ; library base pointer van exec.library
move.l  #500,d0        ; functie parameter
jsr     AllocMem(a6)   ; roep functie aan

(De waardes kloppen niet en misschien de exacte syntax ook niet maar vergeef me, het is meer dan 15 jaar geleden dat ik 680x0 assembly op de Amiga programmeerde ;-) )

Omdat die laatste jsr AllocMem(a6) aanroep dus effectief naar een negatieve offset t.o.v. de library base springt, komt hij in de "LVO" jumptable terecht (Library Vector Offset). Hierin staat de feitelijke jump instructie naar de daadwerkelijke functie in de library (op positieve offset). Deze jump instructie in de LVO jump table kun je eenvoudig vervangen door een sprong naar een ander adres, waardoor de library functie gepatched wordt. De officiƫle manier om dat te doen was via de exec.library/SetFunction() call.

Omdat de LVO jump table negatief is t.o.v. de library base, en de library implementatie zelf positief, kunnen libraries gewoon uitgebreid worden met nieuwe functies. De LVO jump offsets van de library calls wijzigen nooit! De daadwerkelijke functie waar ze heen springen kan overal staan zonder dat de aanroepende code dat dus hoeft te merken.

Omdat de library base en jump table in RAM staan was het dus ook mogelijk om de libraries uit het ROM van de Amiga te patchen zonder dat er een nieuwe ROM in de computer gezet hoeft te worden ;-)

Hier nog wat linkjes met wat extra achtergrondinformatie:

Soms mis ik het wel een beetje de verbluffende eenvoud en kracht van de Amiga ^_^