程序员自我修养1:GCC构建过程
2020-10-09 本文已影响0人
梦工厂
示例代码:hello.c
#include <stdio.h>
int main()
{
printf("Hello, World!");
return 0;
}
1. 预处理 gcc –E hello.c –o hello.i
预处理过程主要处理源代码中以#开始的预编译指令,将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些代码输出到一个 ".i" 文件中等待进一步处理。
- 将所有的 "#define" 删除,并且展开所有的宏定义
- 处理所有条件预编译指令,比如"#if"、"#ifdef"、"#elif"、"#else"、"#endif"
- 处理"#include"预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件
- 删除所有的注释"//"和"/* */"
- 添加行号和文件名标识,比如 #2 "hello.c" 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号
- 保留所有的 #pragma 编译器指令,因为编译器需要使用它们
......
extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__));
# 914 "/usr/include/stdio.h" 3 4
extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 944 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
# 3 "hello.c"
int main()
{
printf("Hello, World!");
return 0;
}
2. 编译 gcc –S hello.c –o hello.s
汇编过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析、优化,生成相应的汇编代码。 构建的核心
程序员自我修养2:编译过程
cat 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
movl $0, %eax
call printf@PLT
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 6.3.0-18+deb9u1) 6.3.0 20170516"
.section .note.GNU-stack,"",@progbits
3. 汇编 gcc –c hello.c –o hello.o
汇编过程是将汇编代码转变为机器指令,每个汇编语句几乎对应一条机器指令。
过程比较简单,没有复杂语法和语义,也没指令优化,根据汇编指令和机器指令的对照表一一翻译即可。
4. 链接 gcc hello.o –o hello
链接过程将多个模块的目标文件和静态库一起拼接成最终的可执行文件,并自动的去修正函数和变量的地址;
静态链接的步骤
- 空间和地址分配
扫描所有的输入目标文件,获得各个段的长度、属性和位置,相似段合并。将各个目标文件的符号表收集统一到全局符号表。
此时各个段的虚拟地址已经确定,各个符号的虚拟地址也确定了(段内偏移+段的偏移)。 - 符号的解析与重定位
每个段中需要修正的外部符号都会存储在重定位表中,对某个符号重定位时,从全局符号表中查找符号的地址。
链接之前,printf函数地址为空;
objdump -r hello.o //重定位信息
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000007 R_X86_64_PC32 .rodata-0x0000000000000004
0000000000000011 R_X86_64_PLT32 printf-0x0000000000000004
objdump -d hello.o //反汇编
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # b <main+0xb>
b: b8 00 00 00 00 mov $0x0,%eax
10: e8 00 00 00 00 callq 15 <main+0x15>
15: b8 00 00 00 00 mov $0x0,%eax
1a: 5d pop %rbp
1b: c3 retq
链接之后,确定了函数的调用地址;
00000000000006b0 <main>:
6b0: 55 push %rbp
6b1: 48 89 e5 mov %rsp,%rbp
6b4: 48 8d 3d 99 00 00 00 lea 0x99(%rip),%rdi # 754 <_IO_stdin_used+0x4>
6bb: b8 00 00 00 00 mov $0x0,%eax
6c0: e8 9b fe ff ff callq 560 <printf@plt>
6c5: b8 00 00 00 00 mov $0x0,%eax
6ca: 5d pop %rbp
6cb: c3 retq
6cc: 0f 1f 40 00 nopl 0x0(%rax)