diff --git a/doc/1.5.1_c_basic.md b/doc/1.5.1_c_basic.md index 67261de..0fd55fb 100644 --- a/doc/1.5.1_c_basic.md +++ b/doc/1.5.1_c_basic.md @@ -31,6 +31,17 @@ hello world ```text $gcc -E hello.c -o hello.i ``` +``` +# 1 "hello.c" +# 1 "" +# 1 "" +...... +extern int printf (const char *__restrict __format, ...); +...... +main() { + printf("hello, world\n"); +} +``` 预编译过程主要处理源代码中以 “#” 开始的预编译指令: - 将所有的 “#define” 删除,并且展开所有的宏定义。 @@ -44,6 +55,35 @@ $gcc -E hello.c -o hello.i ```text $gcc -S hello.c -o hello.s ``` +``` + .file "hello.c" + .section .rodata +.LC0: + .string "hello, world" + .text + .globl main + .type main, @function +main: +.LFB0: + .cfi_startproc + pushq %rbp + .cfi_def_cfa_offset 16 + .cfi_offset 6, -16 + movq %rsp, %rbp + .cfi_def_cfa_register 6 + leaq .LC0(%rip), %rdi + call puts@PLT + movl $0, %eax + popq %rbp + .cfi_def_cfa 7, 8 + ret + .cfi_endproc +.LFE0: + .size main, .-main + .ident "GCC: (GNU) 7.2.0" + .section .note.GNU-stack,"",@progbits +``` + 编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。 #### 汇编 @@ -52,10 +92,61 @@ $gcc -c hello.s -o hello.o 或者 $gcc -c hello.c -o hello.o ``` +``` +$ objdump -sd hello.o + +hello.o: file format elf64-x86-64 + +Contents of section .text: + 0000 554889e5 488d3d00 000000e8 00000000 UH..H.=......... + 0010 b8000000 005dc3 .....]. +Contents of section .rodata: + 0000 68656c6c 6f2c2077 6f726c64 00 hello, world. +Contents of section .comment: + 0000 00474343 3a202847 4e552920 372e322e .GCC: (GNU) 7.2. + 0010 3000 0. +Contents of section .eh_frame: + 0000 14000000 00000000 017a5200 01781001 .........zR..x.. + 0010 1b0c0708 90010000 1c000000 1c000000 ................ + 0020 00000000 17000000 00410e10 8602430d .........A....C. + 0030 06520c07 08000000 .R...... + +Disassembly of section .text: + +0000000000000000
: + 0: 55 push %rbp + 1: 48 89 e5 mov %rsp,%rbp + 4: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # b + b: e8 00 00 00 00 callq 10 + 10: b8 00 00 00 00 mov $0x0,%eax + 15: 5d pop %rbp + 16: c3 retq +``` + 汇编器将汇编代码转变成机器可以执行的指令。 #### 链接 -目标文件需要链接一大堆文件才能得到最终的可执行文件。链接过程主要包括地址和空间分配(Address and Storage Allocation)、符号决议(Symbol Resolution)和重定向(Relocation)等。 +``` +$ gcc hello.o -o hello +``` +``` +$ objdump -d -j .text hello +...... +000000000000064a
: + 64a: 55 push %rbp + 64b: 48 89 e5 mov %rsp,%rbp + 64e: 48 8d 3d 9f 00 00 00 lea 0x9f(%rip),%rdi # 6f4 <_IO_stdin_used+0x4> + 655: e8 d6 fe ff ff callq 530 + 65a: b8 00 00 00 00 mov $0x0,%eax + 65f: 5d pop %rbp + 660: c3 retq + 661: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) + 668: 00 00 00 + 66b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) +...... +``` + +目标文件需要链接一大堆文件才能得到最终的可执行文件(上面只展示了链接后的 main 函数,可以和 hello.o 中的 main 函数作对比)。链接过程主要包括地址和空间分配(Address and Storage Allocation)、符号决议(Symbol Resolution)和重定向(Relocation)等。 #### gcc 技巧 通常在编译后只会生成一个可执行文件,而中间过程生成的 `.i`、`.s`、`.o` 文件都不会被保存。我们可以使用参数 `-save-temps` 永久保存这些临时的中间文件。 @@ -70,6 +161,21 @@ a.out hello.c hello.i hello.o hello.s $ file a.out a.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=533aa4ca46d513b1276d14657ec41298cafd98b1, not stripped ``` + +使用参数 `--verbose` 可以输出 gcc 详细的工作流程。 +``` +$ gcc hello.c -static --verbose +``` +东西很多,我们主要关注下面几条信息: +``` +/usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/cc1 -quiet -v hello.c -quiet -dumpbase hello.c -mtune=generic -march=x86-64 -auxbase hello -version -o /tmp/ccj1jUMo.s + +as -v --64 -o /tmp/ccAmXrfa.o /tmp/ccj1jUMo.s + +/usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/collect2 -plugin /usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/lto-wrapper -plugin-opt=-fresolution=/tmp/cc1l5oJV.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_eh -plugin-opt=-pass-through=-lc --build-id --hash-style=gnu -m elf_x86_64 -static /usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/../../../../lib/crt1.o /usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/../../../../lib/crti.o /usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/crtbeginT.o -L/usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0 -L/usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/../../../../lib -L/lib/../lib -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/../../.. /tmp/ccAmXrfa.o --start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/crtend.o /usr/lib/gcc/x86_64-pc-linux-gnu/7.2.0/../../../../lib/crtn.o +``` +三条指令分别是 `cc1`、`as` 和 `collect2`,cc1 是 gcc 的编译器,将 `.c` 文件编译为 `.s` 文件,as 是汇编器命令,将 `.s` 文件汇编成 `.o` 文件,collect2 是链接器命令,它是对命令 ld 的封装。静态链接时,gcc 将 C 语言运行时库的 5 个重要目标文件 `crt1.o`、`crti.o`、`crtbeginT.o`、`crtend.o`、`crtn.o` 和 `-lgcc`、`-lgcc_eh`、`-lc` 表示的 3 个静态库链接到可执行文件中。 + 更多的内容我们会在 1.5.3 中专门对 ELF 文件进行讲解。