编译过程
笔者使用 使用编译器为GCC
gcc 一般格式为 GCC [选项] 要编译的文件 [选项] 目标文件
1. 预处理
预处理相当于将预处理指令组装成新的程序,会替换宏,条件编译指令等内容。同原文件无异,知识内容有所不同。
* 处理(#开头的指令)伪指令
- 将所有的 #define 删除,并展开所有的宏定义
- 处理所有条件编译指令,如 #if , #endif
- 处理预编译指令 #include ,将包含的文件插入该位置
* 删除所有注释
* 添加行号和文件名标识
* 保留所有的 #pragma 编译器指令
gcc -E file.c -o file.i
cpp -- 预处理器
.i是经过预处理的C原始程序
2. 编译
将预处理完的文件进行一系列的词法分析、语法分析、语义分析及优化后。产生响应的汇编代码文件
gcc -S file.i -o file.s
cc -- 编译器
3. 汇编
将编译完的汇编代码文件翻译成机器指令。并生成可重定位目标程序的 .o 文件。该文件是二进制文件,字节编码是机器指令。
汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎对应一条机器指令。所以汇编器的汇编过程相对于编译器比较简单。
它没有复杂的语法,也没有语义,不需要做指令优化。知识根据汇编只看和机器指令的对照表一一翻译即可。
gcc -c file.s -o file.o
as -- 汇编器
4. 链接
通过链接器将一个个目标文件(或许还有库文件)链接在一起生成一个完整的可执行程序。
由汇编器生成的目标文件并不能立即执行,其中还有许多没有解决的问题 例如某个源文件中的函数可能引用了另一个源文件中定义的某个符号,程序中可能调用了某个库文件中的函数,等等。所以需要连接器处理
链接程序的主要工作就是讲有关的目标文件彼此链接,也就是讲一个文件引用的符号同该符号在另外一个文件中的定义链接起来。 使得所有的目标文件成为一个能够被操作系统装入执行的统一整体
gcc file.o -o file
ld -- 连接器
5. GCC 编译选项
1. 总体选项
-E:仅作预处理,不进行编译,汇编和链接-S: 编译到汇编语言,不进行汇编和链接-c:编译到目标代码-o: 输出为可执行程序-static:禁止使用动态库-share:尽量使用动态库-I dir: 在头文件的搜索路径中添加dir-L dir:在库文件的搜索路径中添加dir-llibrary:链接名为library的库文件
2. 警告和出错
-
-Wall:打开所有类型语法警告 -
-Wcomment:当/*出现在/*...*/注释中,或者\出现在//...注释结尾处会给出警告 -
-fsyntax-only:检查语法错误 -
-w:禁止所有警告信息
6.GDB调试
命令:gcc -g file.c -o file
注意:一定加上参数-g
1.设置断点
break <linenum>break <function>break * address
break也可以使用 b
2. 查看断点
info break[n]
3. 运行代码
输如r即可,默认是从首行开始运行,若想指定行开始运行可以载r后面加上行号。
4. 查看变量值
p+变量
段错误
1. 调试设置
- 检查
ulimit
$ulimit -c 若是0:表示禁止生成core文件,此时需要ulimit -c unlimited临时生效
或者在.bashrc 添加 ulimit - c unlimitd,然后source .bashrc使其生效
2. 段错误调试
gdb ./file ./core:可以查看具体段错误信息
7. C程序的储存空间布局
- 正文段,这是有CPU执行的机器指令部分,通常正文段是可以共享的,另外正文段常常是只读的
- 初始化数据段 ,通常此段成为数据段,包含了程序中需明确赋初值的变量,如函数之外的声明
int max = 99 - 未初始化的数据段 ,通常成为bss段,来源于“由符号开始的块”(block started by symbol),程序开始执行之前,内核将此段中的数据初始化为0
- 栈 自动变量以及每次函数调用时所需保存的信息存于此段
- 堆 通常在堆中进行动态储存分配
8. 共享库
静态库步骤
- 把源码编译成目标文件(*.o)
gcc - c xxx.c
- 用
ar命令把目标文件生成lib文件
ar crs libxxx.a xxx.o
- 生成的lib文件与主程序一起编译
gcc -o 目标文件 -L库路径 -lxxx
gcc -o 目标文件 -static -L库路径 -lxxx
动态库步骤
- 编译目标文件
gcc -c -fPIC xxx.c 生成位置无关码
- 生成动态链接库
gcc -shared -o libxxx.so xxx.o
- 编译
gcc -o 目标文件 -L库路径 -lxxx
Q:执行时候找不到库文件?
- 动态库添加到系统的库文件目录
export LD_LIBRARY=$LD_LIBRARY_PATH:你的动态路径- 添加
/etc/ld.so.conf.d/*conf文件,执行ldconfig刷新
其他工具
部分工具的使用, 需要编译时加-g选项
1. readelf
elf文件头描述elf文件的总体信息。包括:系统相关,类型相关,加载相关,链接相关。
- 系统相关表示:elf文件标识的魔术数,以及硬件和平台等相关信息,增加了elf文件的移植性,使交叉编译成为可能。
- 类型相关就是前面说的那个类型。
- 加载相关:包括程序头表相关信息。
- 链接相关:节头表相关信息。
$ readelf -h a.out
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x400430
Start of program headers: 64 (bytes into file)
Start of section headers: 6688 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 31
Section header string table index: 28
2. size
读取各个段大小
$size a.out
text data bss dec hex filename
1286 552 8 1846 736 file_io
3. objcopy
将目标文件的一部分或者全部拷贝到另一目标文件,或者实现目标文件类型转换
$objcopy -O binary a.out a.bin
4. objdump
查看目标文件或者可执行的目标文件的构成
-D:反汇编所有的section-S:尽可能反汇编出源代码,尤其是当编译的时候指定了-g这种调试参数时,效果比较明显,隐含了-d参数
$objdump -DS a.out > a.dis
5.nm
用于显示二进制目标文件的符号表,编译时需要加上-g选项
$nm a.out > a.map
# T/t 代码段
# D/d 数据段
# B/b bss 段
# R/r 只读数据段
# 大写表示非静态
# 小写表示静态 static
6. addr2line
将地址转换成文件名和行号,给定了可执行程序中的地址或者重定向目标文件中的段的偏移地址,可以计算出相关的文件名字和行号
$addr2line -e test1 400506
/home/hanfoo/code/test/addr2line/test1.c:5
7. strip
去掉文件调试信息,可以给可执行文件瘦身