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:

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:

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:

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>