...someplace, where there isn't any trouble? Do you suppose there is such a place, Toto?

Dem DLL-Lader auf die Sprünge helfen...Teil 2

So, jetzt wo wir wissen, wie man fehlendes oder falsches Rebasing erkennen kann, die Antort auf die Frage "Wozu das Ganze, geht doch auch so!?!".

Um das zu erklären muß man wissen, was mit dem Inhalt einer DLL passiert, wenn sie vom Programmlader in den Adreßraum einer DLL geladen wird. Was für jedermann einleuchtend passieren muss, ist, daß die Instruktionen und die Daten, die in einer DLL drinstecken, irgendwie in den Adreßraum des sie verwendenden Prozesses kommen müssen. Klar ist sicherlich auch, daß ein Funktionsaufruf, wie er in einer Sprache wie C geschieht, in aller Regel zu einer Assembler-Instruktion führt, die den Instruction Pointer (IP) auf eine andere Adresse setzt, beispielsweise eine JMP oder CALL Instruktion. Für diese Assemblerinstruktionen braucht man aber innerhalb des virtuellen Adreßraums eineindeutige Adressen, wo dann die Ausführung hinspringen kann um den Assemblercode an dieser Stelle zur Ausführung zu bringen. Haben wir aber nun ein derartiges Konzept mit nachladbarem Code, wie unter Windows es mit DLLs nun einfach mal und gottseidank existiert, so kann eine einzelne DLL niemals wissen ob sie wirklich an diejenige Stelle im virtuellen Adressraum des Prozesses geladen wird, wo sie all ihre Sprungadressen oder die Adressen von globalen Daten nun mal hat. Denn der Prozess könnte ja eine völlig wildfremde unbekannte DLL an genau diese Stelle schon Jahre vorher geladen haben. Daher müssen DLLs eigentlich immer damit rechnen, daß sie ohne Vorwarnung an eine vogelwilde Adresse im Adreßraum des sie verwendenden Prozesses geladen werden.

Wie aber nun werden die Zieladressen für Sprungziele oder die Adressen von globalen Variablen der DLL zur Laufzeit ermittelt? Ganz einfach, all die Stellen, wo diese Adaption an die tatsächliche Ladeadresse erforderlich ist, werden in einer Tabelle erfasst, der sogenannten Relocation Table, die Teil des PE File Formats ist und die vom Programmlader während des Ladens der DLL ausgelesen wird. Selbiger geht dann nämlich während des Ladevorgangs alle Tabelleneinträge durch und ändert alle relativen virtuellen Adressen (RVA) in der zu ladenden Datei auf die tatsächlichen Adressen im Adreßraum des fraglichen Prozesses ab.

Soweit so gut, dann geht doch alles auch gut, ohne daß man die DLL rebaset, oder? Ja, schon, aber es gibt drei Riesenprobleme damit:

  • Dieser Vorgang des Fixups der Einträge in der Relocation Table ist extrem teuer. Die Performance leidet darunter erheblich. Bei einer sehr prominenten Applikation von επτ€σ konnte ich durch korrektes Rebasing die Startzeit von 10s auf 5s drücken. Das heißt für den täglichen Benutzer der Software schon einiges.
  • Wird ein- und dieselbe DLL in zwei unterschiedlichen Prozessen an unterschiedliche Ladeadressen geladen, kann der Code und konstante Daten (also alle read-only Pages in virual memory parlance) beider DLL-Instanzen nicht geshared werden, der Memory Manager muß für jede DLL-Instanz Memory committen. Das verbraucht nicht nur unnötig Speicher sondern ist auch schlecht für die Performance, aus verschiedensten Gründen.
  • Ein Dr.Watson Log in Verbindung mit einem Map-File und Symbolen ist komplett unbrauchbar, wenn man daraus nicht die Codezeile ermitteln kann, wo ein Crash beim Kunden passiert ist.

Beim nächsten Mal schreibe ich dann darüber, welche Möglichkeiten des Rebaing es gibt und wie man mit Hilfe von Tools erkennen kann, ob ein Binary sauber gerebaset wurde.

Trackback address for this post

This is a captcha-picture. It is used to prevent mass-access by robots.
Please enter the characters from the image above. (case insensitive)

No feedback yet

Comments are closed for this post.