==================== Hello World (Teil 2) [TOC:2] ==================== Bis jetzt haben wir aus dem C Source Code (scheinbar) direkt ein ausführbares Programm erzeugt. Wir wollen die einzelnen Schritte nun von Hand ausführen: - Aus dem Source Code den Assembler Code erzeugen. - Aus dem Assembler Code den Object Code erzeugen. - Den Object Code mit den System Libraries linken und so das ausführbare Programm erzeugen. Und in manchen Fällen wollen wir auch aus Source Code direkt den Object Code erzeugen. Was passiert genau beim Übersetzen? =================================== Mit der Option `-v` kann man die einzelnen Phasen der Übersetzung ausgeben lassen. *--[SHELL(path=session03,hostname=heim)]--------------------------------* | | | gcc -v helloworld.c | | | *-----------------------------------------------------------------------* Das ganze von Hand: Assembler Code, Object Code und Executable erzeugen ======================================================================= *--[SHELL(path=session03,hostname=heim)]--------------------------------* | | | gcc -S -fno-asynchronous-unwind-tables -fomit-frame-pointer +++| | -Wall helloworld.c | | cat helloworld.s | | | *-----------------------------------------------------------------------* Object Code Erzeugen ==================== Aus dem Assembler Code kann nun der Object Code erzeugt werden. Dazu kann man entweder: - Den Assembler `as` direkt aufrufen oder - durch den C-Compiler aufrufen lassen. Assembler direkt aufrufen ------------------------- *--[SHELL(path=session03,hostname=heim)]--------------------------------* | | | as -o helloworld.o helloworld.s | | ls -l helloworld.* | | | *-----------------------------------------------------------------------* Mit der Option `-o` gibt man an, dass das Ergebnis in der Datei `helloworld.o` gespeichert werden soll. Assembler indirekt über den C-Compiler aufrufen ----------------------------------------------- Wenn der C-Compiler eine Datei mit Endung `.s` bekommt nimmt er an, dass es sich um Assembler Code handelt. Als erstes wird dann also der Assembler aufgerufen und dann der Linker. Will man aber vor dem Linken das Prozedere abbrechen, dann muss man noch die Option `-c` hinzufügen: *--[SHELL(path=session03,hostname=heim)]--------------------------------* | | | gcc -c helloworld.s | | ls -l helloworld.* | | | *-----------------------------------------------------------------------* Dass wirklich nur der `as` aufgerufen wird kann man wieder mit der Option `-v` Überprüfen: *--[SHELL(path=session03,hostname=heim)]--------------------------------* | | | gcc -v -c helloworld.s | | | *-----------------------------------------------------------------------* Inspektion eines Object Files ============================= Wenn man sich später (zwangsläufig) mit Linker Fehlern beschäftigt, dann muss man wissen: - Welche Funktionen und globale Variablen werden in einem Object File *definiert* und - Welche Funktionen und globale Variablen werden von einem Object File *gebraucht* aber nicht definiert. Mit dem Kommando `nm` (name mangeling) kann man sozusagen das Inhaltverzeichnis von Object Files, Libraries und ausführbaren Dateien ausgeben lassen: *--[SHELL(path=session03,hostname=heim)]--------------------------------* | | | nm helloworld.o | | | *-----------------------------------------------------------------------* Die Buchstaben in der zweiten Spalte geben unter anderem an, was definiert und was benötigt wird: T Wird definiert (Wahrscheinlich steht das für `Target`) und kann vom Linker benutzt werden, d.h. kann angesprungen werden. t Wird definiert (Wahrscheinlich steht das für `Target`) aber wird nicht exportiert (werden wir später benötigen). Das heisst der Code darf nicht vom Linker zum Auflösen von benötigten Referenzen benutzt werden. U Wird benötigt. Der Linker muss hier eine Referenz auflösen. s Interne Adresse z.B. für Daten (siehe obiger Assembler Code). Weitere Symbole werden wir später kennen lernen. Source Code in Object Code Übersetzen ===================================== Wenn man nicht am Assembler Code interessiert ist, dann kann man mit `-c` vor dem Linken abbrechen. Ist die Eingabedatei ein C Source Code, dann erhält man (scheinbar) ohne Umwege den Object Code aus dem Source Code: *--[SHELL(path=session03,hostname=heim)]--------------------------------* | | | gcc -c helloworld.c | | | *-----------------------------------------------------------------------* Linken ====== Man kann wieder entweder den Linker `ln` direkt aufrufen oder den C Compiler dazu benutzen. In beiden Fällen hat man nach dem Linken mit den System Libraries eine ausführbare Datei. Linken mit Hilfe des C Compilers -------------------------------- Haben die Eingabedateien die Endung `.o`, dann geht der Compiler davon aus, dass es sich um Object Code handelt. Er beginnt dann direkt mit dem Linken: *--[SHELL(path=session03,hostname=heim)]--------------------------------* | | | gcc helloworld.o | | ./a.out | | | *-----------------------------------------------------------------------* Dass wirklich nur der Linker aufgerufen wird Überprüft man wieder mit `-v`: *--[SHELL(path=session03,hostname=heim)]--------------------------------* | | | gcc -v helloworld.o | | ./a.out | | | *-----------------------------------------------------------------------* Linken mit `ld` --------------- Will man direkt mit `ld` Linken muss man wissen wo die System Libraries liegen und welche nötig sind. Dies ist abhängig von der Plattform. Man kann aber den C Compiler benutzen, um herauszufinden welche Suchpfade notwendig sind und welche Libraries mindestens gelinkt werden müssen. Das haben wir bereits oben bei der Ausgabe von `gcc -v helloworld.o` gesehen: *--[SHELL(path=session03,hostname=heim)]-------------------------------------------------------------------------------* | | | /usr/lib/gcc/x86_64-linux-gnu/4.7/collect2 --sysroot=/ --build-id --no-add-needed --eh-frame-hdr -m elf_x86_64 +++| | --hash-style=both -dynamic-linker +++| | /lib64/ld-linux-x86-64.so.2 /usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu/crt1.o +++| | /usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/4.7/crtbegin.o +++| | -L/usr/lib/gcc/x86_64-linux-gnu/4.7 -L/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu +++| | -L/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib +++| | -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/4.7/../../.. helloworld.o -lgcc +++| | --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed +++| | /usr/lib/gcc/x86_64-linux-gnu/4.7/crtend.o /usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu/crtn.o | | ./a.out | | | *----------------------------------------------------------------------------------------------------------------------* Inspektion der ausführbaren Datei ================================= Betrachtet man das Inhaltverzeichnis von `a.out` *--[SHELL(path=session03,hostname=heim)]--------------------------------* | | | nm a.out | | | *-----------------------------------------------------------------------* so sieht man, dass die Referenz von `_puts` immer noch nicht aufgelöst ist. Das liegt daran, dass die System Libraries nicht statisch sondern dynamisch gelinkt werden. Der Linker prüft in dem Fall nur, ob Referenzen zu Funktionen durch dynamische Libraries aufgelöst werden. Erst wenn das Programm gestartet wird, werden diese aufgelöst. Der Vorteil ist, dass sie Dateigröße von `a.out` damit kleiner ist. Der Nachteil ist ein gewissen Overhead bis ein Programm wirklich gestartet ist. *--[SHELL(path=session03,hostname=heim)]--------------------------------* | | | gcc -static helloworld.c | | | *-----------------------------------------------------------------------* :navigate: up -> doc:index back -> doc:session03/page02