从源文件到可执行文件:源文件的预处理、编译、汇编、链接


从源文件到可执行文件:源文件的预处理、编译、汇编、链接

当我们写完了C语言代码后,通过gcc将其编译成可执行文件运行,这中间具体经过的步骤包括预处理、编译、汇编、链接四个步骤。

从源文件到可执行文件:源文件的预处理、编译、汇编、链接

最简单的hello.c源文件内容如下:

# include <stdio.h>  // 这是一行注释 int main(void) {     printf("hello world!n");     printf("%sn", __DATE__);     return 0; } 

预处理

处理源文件中以“#”开头的元素,比如#include #define,将其转换后直接插入源文件中,处理后的文件通常以.i作为文件扩展名。这一步具体包括:

  • 展开头文件:#include
  • 宏替换: #define
  • 条件编译:#if #elif else ifdef ifndef
  • 删除注释
  • 添加行号
  • 预定义符号常量:__DATE__ __TIME__ __TIMESTAMP__ __LINE__ __FILE__ __STDC__

gcc可以通过如下指令得到预处理后的文件:gcc -E hello.c -o hello.ihello.i文件很长,这里截取一小部分:

# 4 "hello.c" int main(void) {     printf("hello world!n");     printf("%sn", "May  3 2022");     return 0; }  

可以看到注释已经被删除了,符号常量__DATE__也已经被展开。

编译

编译阶段包括词法分析、语法分析、语义分析、中间代码生成、目标代码生成与优化,编译完成后会生成汇编代码,通常文件扩展名为.s

gcc可以通过如下指令得到编译后的汇编代码:gcc -S hello.c -o hello.s

默认生成的汇编代码是AT&T格式的,可采用如下指令得到intel格式的汇编代码:gcc -S hello.c -o hello.s -masm=intel,intel格式的hello.s内容如下:

    .file   "hello.c"     .intel_syntax noprefix     .text     .section    .rodata .LC0:     .string    "hello world!" .LC1:     .string    "May  3 2022"     .text     .globl  main     .type   main, @function main: .LFB0:     .cfi_startproc     endbr64     push    rbp     .cfi_def_cfa_offset 16     .cfi_offset 6, -16     mov rbp, rsp     .cfi_def_cfa_register 6     lea rdi, .LC0[rip]     call    puts@PLT     lea rdi, .LC1[rip]     call    puts@PLT     mov eax, 0     pop rbp     .cfi_def_cfa 7, 8     ret     .cfi_endproc .LFE0:     .size   main, .-main     .ident  "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0"     .section    .note.GNU-stack,"",@progbits     .section    .note.gnu.property,"a"     .align 8     .long    1f - 0f     .long    4f - 1f     .long    5 0:     .string  "GNU" 1:     .align 8     .long    0xc0000002     .long    3f - 2f 2:     .long    0x3 3:     .align 8 4:  

汇编

汇编是根据汇编指令与机器指令的对应关系将汇编文件翻译成目标文件,如果从源文件开始,gcc命令是gcc -c hello.c -o hello.o,如果从汇编文件开始,gcc命令是gcc -c hello.s -o hello.o。通过file命令查看目标文件hello.ofile hello.o,终端显示为:hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped,说明这是一个ELF文件,关于ELF文件的内容将在下一篇博客中介绍。

hello.o文件内容无法直接在编辑器中显示,但可以通过objdump显示:objdump -sd hello.o -M intel

hello.o:     文件格式 elf64-x86-64  Contents of section .text:  0000 f30f1efa 554889e5 488d3d00 000000e8  ....UH..H.=.....  0010 00000000 488d3d00 000000e8 00000000  ....H.=.........  0020 b8000000 005dc3                      .....].          Contents of section .rodata:  0000 68656c6c 6f20776f 726c6421 004d6179  hello world!.May  0010 20203320 32303232 00                   3 2022.        Contents of section .comment:  0000 00474343 3a202855 62756e74 7520392e  .GCC: (Ubuntu 9.  0010 342e302d 31756275 6e747531 7e32302e  4.0-1ubuntu1~20.  0020 30342e31 2920392e 342e3000           04.1) 9.4.0.     Contents of section .note.gnu.property:  0000 04000000 10000000 05000000 474e5500  ............GNU.  0010 020000c0 04000000 03000000 00000000  ................ Contents of section .eh_frame:  0000 14000000 00000000 017a5200 01781001  .........zR..x..  0010 1b0c0708 90010000 1c000000 1c000000  ................  0020 00000000 27000000 00450e10 8602430d  ....'....E....C.  0030 065e0c07 08000000                    .^......          Disassembly of section .text:  0000000000000000 <main>:    0:   f3 0f 1e fa             endbr64     4:   55                      push   rbp    5:   48 89 e5                mov    rbp,rsp    8:   48 8d 3d 00 00 00 00    lea    rdi,[rip+0x0]        # f <main+0xf>    f:   e8 00 00 00 00          call   14 <main+0x14>   14:   48 8d 3d 00 00 00 00    lea    rdi,[rip+0x0]        # 1b <main+0x1b>   1b:   e8 00 00 00 00          call   20 <main+0x20>   20:   b8 00 00 00 00          mov    eax,0x0   25:   5d                      pop    rbp   26:   c3                      ret     

此时由于还未链接,目标文件中符号的虚拟地址无法确定。此时,如果运行hello.o会报错:可执行文件格式错误。

链接

链接包括静态链接和动态链接两种,gcc默认使用动态链接,添加编译选项-static可以进行静态链接,这一阶段将目标文件与其依赖库进行链接,主要包括地址和空间分配(Address and Storage Allocation)、符号绑定(Symbol Binding)、重定位(Relocation)等。gcc命令:gcc hello.c -o hello。经过objdump后,部分内容如下:

0000000000001149 <main>:     1149:   f3 0f 1e fa             endbr64      114d:   55                      push   rbp     114e:   48 89 e5                mov    rbp,rsp     1151:   48 8d 3d ac 0e 00 00    lea    rdi,[rip+0xeac]        # 2004 <_IO_stdin_used+0x4>     1158:   e8 f3 fe ff ff          call   1050 <puts@plt>     115d:   48 8d 3d ad 0e 00 00    lea    rdi,[rip+0xead]        # 2011 <_IO_stdin_used+0x11>     1164:   e8 e7 fe ff ff          call   1050 <puts@plt>     1169:   b8 00 00 00 00          mov    eax,0x0     116e:   5d                      pop    rbp     116f:   c3                      ret   

跟未经过链接的目标文件相比,虚拟地址已经确定了,运行hello便可以得到结果:

hello world! May  3 2022 

参考资料

CTF竞赛权威指南(Pwn篇)(杨超 编著,吴石 eee战队 审校,电子工业出版社)

发表评论

相关文章