Content |
Hello World (Teil 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> gcc -v helloworld.c Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.7/lto-wrapper Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Debian 4.7.2-5' --with-bugurl=file:///usr/share/doc/gcc-4.7/README.Bugs --enable-languages=c,c++,go,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.7 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.7 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --with-arch-32=i586 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix gcc version 4.7.2 (Debian 4.7.2-5) COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64' /usr/lib/gcc/x86_64-linux-gnu/4.7/cc1 -quiet -v -imultiarch x86_64-linux-gnu helloworld.c -quiet -dumpbase helloworld.c -mtune=generic -march=x86-64 -auxbase helloworld -version -o /tmp/ccJtWakD.s GNU C (Debian 4.7.2-5) version 4.7.2 (x86_64-linux-gnu) compiled by GNU C version 4.7.2, GMP version 5.0.5, MPFR version 3.1.0-p10, MPC version 0.9 warning: GMP header version 5.0.5 differs from library version 6.1.0. warning: MPFR header version 3.1.0-p10 differs from library version 3.1.3. GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu" ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../x86_64-linux-gnu/include" #include "..." search starts here: #include <...> search starts here: /usr/lib/gcc/x86_64-linux-gnu/4.7/include /usr/local/include /usr/lib/gcc/x86_64-linux-gnu/4.7/include-fixed /usr/include/x86_64-linux-gnu /usr/include End of search list. GNU C (Debian 4.7.2-5) version 4.7.2 (x86_64-linux-gnu) compiled by GNU C version 4.7.2, GMP version 5.0.5, MPFR version 3.1.0-p10, MPC version 0.9 warning: GMP header version 5.0.5 differs from library version 6.1.0. warning: MPFR header version 3.1.0-p10 differs from library version 3.1.3. GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 Compiler executable checksum: 7fa7c2a970be5e19ce72b2057c14800d COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64' as -v --64 -o /tmp/cckTdJIg.o /tmp/ccJtWakD.s GNU assembler version 2.22 (x86_64-linux-gnu) using BFD version (GNU Binutils for Debian) 2.22 COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.7/:/usr/lib/gcc/x86_64-linux-gnu/4.7/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.7/:/usr/lib/gcc/x86_64-linux-gnu/ LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.7/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../:/lib/:/usr/lib/ COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64' /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/../../.. /tmp/cckTdJIg.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 $shell>
Das ganze von Hand: Assembler Code, Object Code und Executable erzeugen
$shell> gcc -S -fno-asynchronous-unwind-tables -fomit-frame-pointer -Wall helloworld.c $shell> cat helloworld.s .file "helloworld.c" .section .rodata .LC0: .string "Hello world!" .text .globl main .type main, @function main: subq $8, %rsp movl $.LC0, %edi call puts movl $0, %eax addq $8, %rsp ret .size main, .-main .ident "GCC: (Debian 4.7.2-5) 4.7.2" .section .note.GNU-stack,"",@progbits $shell>
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> as -o helloworld.o helloworld.s $shell> ls -l helloworld.* -rw-rw-r-- 1 lehn num 91 May 10 16:04 helloworld.c -rw-r--r-- 1 lehn num 1232 May 10 18:45 helloworld.o -rw-rw-r-- 1 lehn num 295 May 10 18:45 helloworld.s $shell>
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> gcc -c helloworld.s $shell> ls -l helloworld.* -rw-rw-r-- 1 lehn num 91 May 10 16:04 helloworld.c -rw-r--r-- 1 lehn num 1232 May 10 18:45 helloworld.o -rw-rw-r-- 1 lehn num 295 May 10 18:45 helloworld.s $shell>
Dass wirklich nur der as aufgerufen wird kann man wieder mit der Option -v Überprüfen:
$shell> gcc -v -c helloworld.s Using built-in specs. COLLECT_GCC=gcc Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Debian 4.7.2-5' --with-bugurl=file:///usr/share/doc/gcc-4.7/README.Bugs --enable-languages=c,c++,go,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.7 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.7 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --with-arch-32=i586 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix gcc version 4.7.2 (Debian 4.7.2-5) COLLECT_GCC_OPTIONS='-v' '-c' '-mtune=generic' '-march=x86-64' as -v --64 -o helloworld.o helloworld.s GNU assembler version 2.22 (x86_64-linux-gnu) using BFD version (GNU Binutils for Debian) 2.22 COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.7/:/usr/lib/gcc/x86_64-linux-gnu/4.7/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.7/:/usr/lib/gcc/x86_64-linux-gnu/ LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.7/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../:/lib/:/usr/lib/ COLLECT_GCC_OPTIONS='-v' '-c' '-mtune=generic' '-march=x86-64' $shell>
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> nm helloworld.o 0000000000000000 T main U puts $shell>
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> gcc -c helloworld.c $shell>
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> gcc helloworld.o $shell> ./a.out Hello world! $shell>
Dass wirklich nur der Linker aufgerufen wird Überprüft man wieder mit -v:
$shell> gcc -v helloworld.o Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.7/lto-wrapper Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Debian 4.7.2-5' --with-bugurl=file:///usr/share/doc/gcc-4.7/README.Bugs --enable-languages=c,c++,go,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.7 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.7 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --with-arch-32=i586 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix gcc version 4.7.2 (Debian 4.7.2-5) COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.7/:/usr/lib/gcc/x86_64-linux-gnu/4.7/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.7/:/usr/lib/gcc/x86_64-linux-gnu/ LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.7/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../:/lib/:/usr/lib/ COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64' /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 $shell> ./a.out Hello world! $shell>
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> /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 $shell> ./a.out Hello world! $shell>
Inspektion der ausführbaren Datei
Betrachtet man das Inhaltverzeichnis von a.out
$shell> nm a.out 00000000006006d8 d _DYNAMIC 00000000006008c0 d _GLOBAL_OFFSET_TABLE_ 00000000004005d8 R _IO_stdin_used w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable w _Jv_RegisterClasses 00000000004006b8 r __FRAME_END__ 00000000006006d0 d __JCR_END__ 00000000006006d0 d __JCR_LIST__ 00000000006008f8 D __TMC_END__ 00000000006008f8 A __bss_start 00000000006008e8 D __data_start 00000000004004c0 t __do_global_dtors_aux 00000000006006c8 t __do_global_dtors_aux_fini_array_entry 00000000006008f0 D __dso_handle 00000000006006c0 t __frame_dummy_init_array_entry w __gmon_start__ 00000000006006c8 t __init_array_end 00000000006006c0 t __init_array_start 0000000000400530 T __libc_csu_fini 0000000000400540 T __libc_csu_init U __libc_start_main@@GLIBC_2.2.5 00000000006008f8 A _edata 0000000000600900 A _end 00000000004005cc T _fini 00000000004003b8 T _init 0000000000400400 T _start 000000000040042c t call_gmon_start 00000000006008f8 b completed.6092 00000000006008e8 W data_start 0000000000400450 t deregister_tm_clones 00000000004004e0 t frame_dummy 000000000040050c T main U puts@@GLIBC_2.2.5 0000000000400480 t register_tm_clones $shell>
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> gcc -static helloworld.c $shell>