平时我们使用gcc 编译C程序,我们通常只用 gcc hello.c -o hello 之类的,顶多为了gdb调试再使用 -g 参数!
其实我们可以使用手动编译来了解整个gcc编译的过程!
我们使用最简单的Hello World 程序做示例:
#include "stdio.h"
int main()
{
printf("Hello World\n");
return 0;
}
1 、预处理(Pre-processing)
在该阶段,编译器将C源代码中的包含的头文件如stdio.h编译进来,用户可以使用gcc的选项”-E”进行查看。
用法: gcc -E hello.c -o hello.i
作用:将hello.c预处理输出hello.i文件。
#ls
hello.c
#gcc -E hello.c -o hello.i
#ls
hello.c hello.i
使用vim 查看hello.i 可以看到 hello.i中包括很多很多头文件:
# 1 "hello.c"
# 1 "<命令行>"
# 1 "hello.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 28 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
# 324 "/usr/include/features.h" 3 4
# 1 "/usr/include/i386-linux-gnu/bits/predefs.h" 1 3 4
# 325 "/usr/include/features.h" 2 3 4
# 357 "/usr/include/features.h" 3 4
……
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 940 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
int main()
{
printf("Hello World\n");
return 0;
}
我们也可以使用 gcc -M hello.c 生成文件关联的信息。包含目标文件所依赖的所有源代码。
#ls
hello.c hello.i
#gcc -M hello.c
hello.o: hello.c /usr/include/stdio.h /usr/include/features.h \
/usr/include/i386-linux-gnu/bits/predefs.h \
/usr/include/i386-linux-gnu/sys/cdefs.h \
/usr/include/i386-linux-gnu/bits/wordsize.h \
/usr/include/i386-linux-gnu/gnu/stubs.h \
/usr/include/i386-linux-gnu/gnu/stubs-32.h \
/usr/lib/gcc/i686-linux-gnu/4.7/include/stddef.h \
/usr/include/i386-linux-gnu/bits/types.h \
/usr/include/i386-linux-gnu/bits/typesizes.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/lib/gcc/i686-linux-gnu/4.7/include/stdarg.h \
/usr/include/i386-linux-gnu/bits/stdio_lim.h \
/usr/include/i386-linux-gnu/bits/sys_errlist.h
#
2 、编译阶段(Compiling)
第二步进行的是编译阶段,在这个阶段中,Gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,Gcc把代码翻译成汇编语言。用户可以使用”-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。
用法: gcc -S --verbose-asm hello.i -o hello.s
作用:将预处理输出文件hello.i汇编成hello.s文件。
#gcc -S --verbose-asm hello.i -o hello.s
#ls
hello.c hello.i hello.s
使用vim查看hello.s 文件:
.file "hello.c"
# GNU C (Ubuntu/Linaro 4.7.2-2ubuntu1) version 4.7.2 (i686-linux-gnu)
# compiled by GNU C version 4.7.2, GMP version 5.0.2, MPFR version 3.1.0-p3, MPC version 0.9
# GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
# 传递的选项: -fpreprocessed hello.i -mtune=generic -march=i686
# -auxbase-strip hello.s -fverbose-asm -fstack-protector
# 启用的选项: -fasynchronous-unwind-tables -fauto-inc-dec
# -fbranch-count-reg -fcommon -fdebug-types-section
# -fdelete-null-pointer-checks -fdwarf2-cfi-asm -fearly-inlining
# -feliminate-unused-debug-types -ffunction-cse -fgcse-lm -fgnu-runtime
# -fident -finline-atomics -fira-share-save-slots -fira-share-spill-slots
# -fivopts -fkeep-static-consts -fleading-underscore -fmath-errno
# -fmerge-debug-strings -fmove-loop-invariants -fpcc-struct-return
# -fpeephole -fprefetch-loop-arrays -fsched-critical-path-heuristic
# -fsched-dep-count-heuristic -fsched-group-heuristic -fsched-interblock
# -fsched-last-insn-heuristic -fsched-rank-heuristic -fsched-spec
# -fsched-spec-insn-heuristic -fsched-stalled-insns-dep -fshow-column
# -fsigned-zeros -fsplit-ivs-in-unroller -fstack-protector
# -fstrict-volatile-bitfields -ftrapping-math -ftree-cselim -ftree-forwprop
# -ftree-loop-if-convert -ftree-loop-im -ftree-loop-ivcanon
# -ftree-loop-optimize -ftree-parallelize-loops= -ftree-phiprop -ftree-pta
# -ftree-reassoc -ftree-scev-cprop -ftree-slp-vectorize
# -ftree-vect-loop-version -funit-at-a-time -funwind-tables
# -fvect-cost-model -fverbose-asm -fzero-initialized-in-bss -m32 -m80387
# -m96bit-long-double -maccumulate-outgoing-args -malign-stringops
# -mfancy-math-387 -mfp-ret-in-387 -mglibc -mieee-fp -mno-red-zone
# -mno-sse4 -mpush-args -msahf -mtls-direct-seg-refs
.section .rodata
.LC0:
.string "Hello World"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl %ebp #
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp #,
.cfi_def_cfa_register 5
andl $-16, %esp #,
subl $16, %esp #,
movl $.LC0, (%esp) #,
call puts #
movl $0, %eax #, D.1815
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
3 、汇编阶段(Assembling)
汇编阶段是把编译阶段生成的”.s”文件转成二进制目标代码.
用法:
gcc -c hello.s -o hello.o
作用:将汇编输出文件test.s编译输出test.o文件。
#ls
hello.c hello.i hello.s
#gcc -c hello.s -o hello.o
#ls
hello.c hello.i hello.o hello.s
使用 file hello.o 查看hello.o文件信息:
#file hello.o
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
#
#objdump -d hello.o
hello.o: 文件格式 elf32-i386
Disassembly of section .text:
00000000 <main>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 e4 f0 and $0xfffffff0,%esp
6: 83 ec 10 sub $0x10,%esp
9: c7 04 24 00 00 00 00 movl $0x0,(%esp)
10: e8 fc ff ff ff call 11 <main+0x11>
15: b8 00 00 00 00 mov $0x0,%eax
1a: c9 leave
1b: c3 ret
#
这个和我们使用gdb调试时使用disassemble查看是一样的!
(gdb) disassemble
Dump of assembler code for function main:
0x0804840c <+0>: push %ebp
0x0804840d <+1>: mov %esp,%ebp
0x0804840f <+3>: and $0xfffffff0,%esp
0x08048412 <+6>: sub $0x10,%esp
=> 0x08048415 <+9>: movl $0x80484c8,(%esp)
0x0804841c <+16>: call 0x80482f0 <puts@plt>
0x08048421 <+21>: mov $0x0,%eax
0x08048426 <+26>: leave
0x08048427 <+27>: ret
End of assembler dump.
(gdb)
4 、链接阶段(Link)
在成功编译之后,就进入了链接阶段。无选项链接。
用法:gcc -v hello.o -o hello
作用:将编译输出文件hello.o链接成最终可执行文件hello。
#gcc -v hello.o -o hello
使用内建 specs。
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/i686-linux-gnu/4.7/lto-wrapper
目标:i686-linux-gnu
配置为:../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.7.2-2ubuntu1' --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 --enable-targets=all --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnu
线程模型:posix
gcc 版本 4.7.2 (Ubuntu/Linaro 4.7.2-2ubuntu1)
COMPILER_PATH=/usr/lib/gcc/i686-linux-gnu/4.7/:/usr/lib/gcc/i686-linux-gnu/4.7/:/usr/lib/gcc/i686-linux-gnu/:/usr/lib/gcc/i686-linux-gnu/4.7/:/usr/lib/gcc/i686-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/i686-linux-gnu/4.7/:/usr/lib/gcc/i686-linux-gnu/4.7/../../../i386-linux-gnu/:/usr/lib/gcc/i686-linux-gnu/4.7/../../../../lib/:/lib/i386-linux-gnu/:/lib/../lib/:/usr/lib/i386-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/i686-linux-gnu/4.7/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-o' 'hello' '-mtune=generic' '-march=i686'
/usr/lib/gcc/i686-linux-gnu/4.7/collect2 --sysroot=/ --build-id --no-add-needed --as-needed --eh-frame-hdr -m elf_i386 --hash-style=gnu -dynamic-linker /lib/ld-linux.so.2 -z relro -o hello /usr/lib/gcc/i686-linux-gnu/4.7/../../../i386-linux-gnu/crt1.o /usr/lib/gcc/i686-linux-gnu/4.7/../../../i386-linux-gnu/crti.o /usr/lib/gcc/i686-linux-gnu/4.7/crtbegin.o -L/usr/lib/gcc/i686-linux-gnu/4.7 -L/usr/lib/gcc/i686-linux-gnu/4.7/../../../i386-linux-gnu -L/usr/lib/gcc/i686-linux-gnu/4.7/../../../../lib -L/lib/i386-linux-gnu -L/lib/../lib -L/usr/lib/i386-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/i686-linux-gnu/4.7/../../.. hello.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i686-linux-gnu/4.7/crtend.o /usr/lib/gcc/i686-linux-gnu/4.7/../../../i386-linux-gnu/crtn.o
#
我们可以使用 nm hello 查看使用的符号信息
#nm hello
0804a020 B __bss_start
0804a020 b completed.6382
0804a018 D __data_start
0804a018 W data_start
08048350 t deregister_tm_clones
080483c0 t __do_global_dtors_aux
08049f0c t __do_global_dtors_aux_fini_array_entry
0804a01c D __dso_handle
08049f14 d _DYNAMIC
0804a020 D _edata
0804a024 B _end
080484a8 T _fini
080484c0 R _fp_hw
080483e0 t frame_dummy
08049f08 t __frame_dummy_init_array_entry
080485c8 r __FRAME_END__
0804a000 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
080484a2 T __i686.get_pc_thunk.bx
080482b0 T _init
08049f0c t __init_array_end
08049f08 t __init_array_start
080484c4 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
08049f10 d __JCR_END__
08049f10 d __JCR_LIST__
w _Jv_RegisterClasses
080484a0 T __libc_csu_fini
08048430 T __libc_csu_init
U __libc_start_main@@GLIBC_2.0
0804840c T main
U puts@@GLIBC_2.0
08048380 t register_tm_clones
08048320 T _start
0804a020 D __TMC_END__
#