| Zeile 69: | Zeile 69: | ||
* <b>Kompilieren</b>: Zum einen ist da der Aufruf von <i>cc1</i>. cc1 ist der eigentliche Compiler, er übersetzt C-Code in Assemblercode. Wenn man <i>gcc -S hello.c</i> aufruft, ruft gcc nur den Compiler auf und lässt die Assemblerdatei als <i>hello.s</i> liegen. | * <b>Kompilieren</b>: Zum einen ist da der Aufruf von <i>cc1</i>. cc1 ist der eigentliche Compiler, er übersetzt C-Code in Assemblercode. Wenn man <i>gcc -S hello.c</i> aufruft, ruft gcc nur den Compiler auf und lässt die Assemblerdatei als <i>hello.s</i> liegen. | ||
* <b>Assemblieren</b>: Der Assembler wandelt den Assemblercode in eine Objektdatei um. Er heißt üblicherweise <i>as</i>, wenn betont werden soll, dass es sich um den GNU-Assembler handelt (z.B. auf anderen Systemen, wo andere Assembler üblich sind), wird er auch <i>gas</i> genannt. Wenn man <i>gcc -c hello.c</i> aufruft, wird bis an dieser Stelle gebaut und die Objektdatei <i>hello.o</i> bleibt übrig. Eine Objektdatei enthält zwar schon Maschinencode, aber er ist noch nicht in seiner endgültigen Form. Externe "Symbole" (d.h. Funkions- oder Variablennamen) sind noch als Verweise (<i>Referenzen</i>) vorhanden - printf selbst existiert in der Objektdatei nicht. | * <b>Assemblieren</b>: Der Assembler wandelt den Assemblercode in eine Objektdatei um. Er heißt üblicherweise <i>as</i>, wenn betont werden soll, dass es sich um den GNU-Assembler handelt (z.B. auf anderen Systemen, wo andere Assembler üblich sind), wird er auch <i>gas</i> genannt. Wenn man <i>gcc -c hello.c</i> aufruft, wird bis an dieser Stelle gebaut und die Objektdatei <i>hello.o</i> bleibt übrig. Eine Objektdatei enthält zwar schon Maschinencode, aber er ist noch nicht in seiner endgültigen Form. Externe "Symbole" (d.h. Funkions- oder Variablennamen) sind noch als Verweise (<i>Referenzen</i>) vorhanden - printf selbst existiert in der Objektdatei nicht. | ||
| − | * <b>Linken</b>: Der Linker <i>ld</i> (in meinem Fall indirekt über collect2 aufgerufen) nimmt mehrere Objektdateien (oder Bibliotheken, die im Prinzip auch nur eine Sammlung von Objektdateien sind) und produziert daraus die endgültige ausführbare Datei. An dieser Stelle muss der Linker dann auch | + | * <b>Linken</b>: Der Linker <i>ld</i> (in meinem Fall indirekt über collect2 aufgerufen) nimmt mehrere Objektdateien (oder Bibliotheken, die im Prinzip auch nur eine Sammlung von Objektdateien sind) und produziert daraus die endgültige ausführbare Datei. An dieser Stelle muss der Linker dann auch printf <i>auflösen</i> können, d.h. die Adresse einsetzen, an der printf am Ende tatsächlich liegt. Mit <i>-lc</i> wird die Standardbibliothek <i>libc</i> eingebunden, die unter anderem diese Funktion enthält. |
Genaugenommen habe ich beim Linker nicht ganz die Wahrheit gesagt - was ich beschrieben habe ist <i>statisches Linken</i> (das ist auch, was wir vorerst brauchen, wenn wir unser Betriebssystem schreiben). In Wirklichkeit werden Linuxprogramme normal dynamisch gelinkt, d.h. auch die ausführbare Datei enthält noch nicht die endgültigen Adressen, sondern nur Referenzen. Das Programm wird dann erst direkt bei der Ausführung vom Betriebssystem mit den Bibliotheken (Shared Libraries; *.so unter Linux oder *.dll unter Windows) gelinkt. Statisches Linken kann man mit <i>gcc -static</i> erzwingen, aber das macht die erzeugte Datei nur unübersichtlich, also lassen wir das erst einmal bleiben. | Genaugenommen habe ich beim Linker nicht ganz die Wahrheit gesagt - was ich beschrieben habe ist <i>statisches Linken</i> (das ist auch, was wir vorerst brauchen, wenn wir unser Betriebssystem schreiben). In Wirklichkeit werden Linuxprogramme normal dynamisch gelinkt, d.h. auch die ausführbare Datei enthält noch nicht die endgültigen Adressen, sondern nur Referenzen. Das Programm wird dann erst direkt bei der Ausführung vom Betriebssystem mit den Bibliotheken (Shared Libraries; *.so unter Linux oder *.dll unter Windows) gelinkt. Statisches Linken kann man mit <i>gcc -static</i> erzwingen, aber das macht die erzeugte Datei nur unübersichtlich, also lassen wir das erst einmal bleiben. | ||
| Zeile 133: | Zeile 133: | ||
Man sieht, dass jetzt immer zuerst die C-Zeile angezeigt wird und dann der Assemblercode, mit dem sie umgesetzt worden ist. In der Betriebssystementwicklung passiert es einem oft, dass man bei einem Absturz nur die Adresse weiß, an der etwas schiefgegangen ist. Durch diese objdump-Ausgabe findet man dann sehr schnell den zugehörigen C-Befehl, und möglicherweise ist der zugehörige Assemblercode klein genug, dass man ihn nachvollziehen und dann sogar genau sagen kann, welcher Teil der C-Zeile schiefgeht. | Man sieht, dass jetzt immer zuerst die C-Zeile angezeigt wird und dann der Assemblercode, mit dem sie umgesetzt worden ist. In der Betriebssystementwicklung passiert es einem oft, dass man bei einem Absturz nur die Adresse weiß, an der etwas schiefgegangen ist. Durch diese objdump-Ausgabe findet man dann sehr schnell den zugehörigen C-Befehl, und möglicherweise ist der zugehörige Assemblercode klein genug, dass man ihn nachvollziehen und dann sogar genau sagen kann, welcher Teil der C-Zeile schiefgeht. | ||
| − | Die zweite Neuerung ist, dass die Referenzen aufgelöst werden. Wir haben hier zum einen eine Referenz auf irgendwelche Daten in .rodata (das ist unser String "Hello World") und zum zweiten auf eine externe Funktion, nämlich puts. Huch, wollten wir nicht printf? Ja, aber der Compiler hat hier ein wenig optimiert, weil wir die Komplexität von printf hier nicht brauchen (man kann das übrigens | + | Die zweite Neuerung ist, dass die Referenzen aufgelöst werden. Wir haben hier zum einen eine Referenz auf irgendwelche Daten in .rodata (das ist unser String "Hello World") und zum zweiten auf eine externe Funktion, nämlich puts. Huch, wollten wir nicht printf? Ja, aber der Compiler hat hier ein wenig optimiert, weil wir die Komplexität von printf hier nicht brauchen (man kann das übrigens über die Kommandozeilenoption -fno-builtin abschalten). Dumm nur, wenn das eigene Betriebssystem zwar schon printf hat, aber noch kein puts... Gut, dass wir jetzt eine Methode haben, dem Compiler auf die Finger zu schauen und solche Dinge zu erkennen. |
Nachdem ich jetzt erklärt habe, wie toll -g ist, möchte ich auch den Nachteil nicht verschweigen: | Nachdem ich jetzt erklärt habe, wie toll -g ist, möchte ich auch den Nachteil nicht verschweigen: | ||