编译和链接【四】链接详解
前言
在我大一的时候, 我使用VC6.0对C语言程序进行编译链接和运行 , 然后我接触了VS, Qt creator等众多IDE, 这些IDE界面友好, 使用方便, 例如我最喜欢的VS,一键编译运行。对于大一的我,不需要了解编译的整个过程就可以运行,这无疑是非常棒的,并且增加了我对编程的兴趣,同时也简化了我后续的软件开发, 我只需要关心业务和功能代码即可。
但是今天, 我不想“逃课了”,欢迎来到我的频道,本系列 将会介绍编译中的一系列细节。
在正式开始之前,我要推荐两本书,一本是《程序员的自我修养》,另一本是《鲸书》,这两本书对编译的整个过程做了非常详细,非常完备的介绍,但是恰恰如此,我想很多时候,很多知识在工作上是用不到的,也许这句话在很多年多的我会反驳,但是站在工作一年的现在,我将会给你介绍,我所了解的编译和链接。
系列文章入口
关注我~持续更新
符号表和重定位表
在链接过程中,符号表和重定位表是非常重要的两个表。在汇编阶段,汇编器会分析汇编语言中各个section的信息,收集各种符号,生成符号表,将符号在section内的偏移地址也填充到符号表里。
使用 readelf -s main.o 查看目标文件的符号表信息
在符号表里,可以看到许多符号信息,比如符号的地址,类型和占用空间的大小。
符号表本质上一个结构体数组,在Arm平台下,定义在Linux内核的/arch/arm/include/asm/elf.h里
typedef struct elf32_sym
{
Elf32_word st_name;
Elf32_Addr st_value;
Elf32_word st_size;
unsigned char st_info;
unsigned cahr st_other;
Elf32_Half st_shndx;
}
符号的类型主要有:
- OBJECT:对象类型,一般用来标识变量
- FUNC:函数
- FILE:当前目标文件的名称
- SECTION:代表一个section,用来重定位
- COMMON:公用块数据对象,是一个全局弱符号,在当前文件中未分配空间
- TLS:表示该符号对应的变量存储在线程局部存储
在 C/C++中,编译器是是源文件为翻译单元进行编译的,如果在我们的程序中,我们引用了其他文件的函数或者全局变量,那么编译器会不会报错呢?
其实是不会的,只要你在调用之前进行声明,那么编译器就会认为你的这个函数或者全局变量在其他文件中定义,编译阶段是不会报错的,链接器会尝试在其他文件或者库里查找这个符号的具体定义,但是如果此时还没找到,那么就会报连链接错误。
main.cpp:undefined reference to ‘Addr’
编译器在给每个目标文件生成符号表的过程中,如果没用找到符号的定义,那么也会把这些符号搜集在一起并保存到一个单独的符号表中,这个符号表就是重定位符号表
使用 readelf -s main.o 查看目标文件的符号表信息
在这个表的Type列,类型为NOTYPE属于未定义状态,需要后续填充,同时在main.o中会使用一个重定位表**.rel.text**来记录这些需要重定位的符号。使用readelf查看重定位表和section header table信息
readelf -S main.o # 查看section header table信息
readelf -r main.o # 查看重定位信息
可以看到:
Relocation section '.rela.text' at offset 0x4c0 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000004 000200000002 R_X86_64_PC32 0000000000000000 printf - 4
00000000000c 000300000002 R_X86_64_PC32 0000000000000000 puts - 4
我们看到了需要重定位的符号:prin