一个.c文件是如何成为一个可执行二进制文件的?
文件后缀 | 被执行过程 | 工具 | 细节 |
---|---|---|---|
.c | 预处理 | 预处理器(cpp,c pre-processor) | 宏替换、头文件展开、注释删除 |
.i | 编译 | ccl | C语言程序转化为汇编语言程序(.s) |
.s | 汇编 | 编译器 | 汇编语言程序转化可重定位目标文件(.o) |
.o | 链接 | ld(链接器) | 将多个.o文件链接为一个可执行文件 |
预处理
使用cpp(C 预处理器)完成了:
- 将.c文件转化为.i文件
- 在这里.i文件只是一个后缀而已,本质上仍然是文本文件
- 宏替换
- 头文件展开
- 找到#include的文件内容将其插入到.c文件中
- 注释删除
将hello.c文件得到的预处理结果重定向到hello.i
cpp hello.c > hello.i
可以使用man cpp得到预处理器的相关信息
测试代码:
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
char *msg = "Hello, world!\n";
syscall(SYS_write, 1, msg, strlen(msg));
return 0;
}
编译
- 使用编译器将.i文件转换为.s文件
- 将C语言文本转换为汇编语言文本(Assembly Code)
- 命令:
gcc hello.i -S -o hello.s
.file "hello.c"
.text
.section .rodata
.LC0:
.string "Hello, world!\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
leaq .LC0(%rip), %rax
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
movq %rax, %rdi
call strlen@PLT
movq %rax, %rdx
movq -8(%rbp), %rax
movq %rdx, %rcx
movq %rax, %rdx
movl $1, %esi
movl $1, %edi
movl $0, %eax
call syscall@PLT
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.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:
.file | 源文件 |
---|---|
.text | 代码段 |
.global | 全局变量 |
.data | 存放已经初始化的全局和静态C 变量 |
.section .rodata | 存放只读变量 |
.align | 对齐方式 |
.type | 表示是函数类型/对象类型 |
.size | 表示大小 |
.long .string | 表示是long类型/string类型 |
汇编
- 编译器将汇编语言程序转化为可重定位目标文件
- .s —> .o 指令为
gcc -c hello.s -o hello.o
- objdump 分析hello.o:
objdump -d hello.o
链接
将多个.o文件链接为一个可执行文件
关于链接器的更多内容:man ld
文章评论