引言:之前看深入理解操作系统的时候了解了一些关于链接器的知识,但是书上讲得比较浅略,一直对有些地方有疑惑,这次趁着bitman的任务,再次对链接器进行学习。
链接器,主要完成两个工作,符号解析和重定位,其中我认为最关键的就是找到符号的引用和定义的地址,为什么这么说,符号解析无非是将引用和定义关联,如果发现未定义则链接失败,重定位就是把引用该符号的地方的代码重定位到定义的位置,所以这篇随笔主要围绕各种情况如何确定符号的引用和定义地址这个问题。
一个.obj文件
对于编译生成一个.obj的情况,符号的引用和定义全在一个.obj文件中可以找到。编译出来的.obj文件中引用位置数据为0x00000000,此时.obj文件结构大概是这样的
IMAGE_FILE_HEADER;
IMAGE_SECTION_HEADER[];
SECTION[];
IMAGE_RELOCATION;
IMAGE_SYMBOL_TABLE;
IMAGE_SYMBOL_STRING_TABLE;
在IMAGE_SECTION_HEADER中有两个属性
typedef struct _IMAGE_SECTION_HEADER {
...
DWORD PointerToRelocations;
WORD NumberOfRelocations;
...
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
PointerToRelocations就是这个段所拥有的符号引用在IMAGE_RELOCATION段中的开始偏移,NumberOfRelocations则是条目。
struct IMAGE_RELOCATION{
DWORD RVA;
DWORD symbolTableIndex;
WORD type;
}
对于每一个符号引用都会在relocation段中生成一个条目,RVA就是该符号在某个段(SECTION_HEADER中记录)的偏移。由此确立了引用的位置,Type可以区别是相对引用(偏移)还是绝对引用(地址),symbolTableIndex是该引用在symbolTable里面的下标,符号的定义信息都包含在symbolTable里面。
struct symbol{
union m{
char shortName[8];
struct {
int longName = 0;
u_int offsetIntoStringTable;
} longName;
struct {
u_int Length;
u_short numberOfRelocations;
u_short numberOfLinenumbers;
}d;
}
u_int value;
u_short sectionNumber;
...
};
前八个字节是名字,8字节以内的shortName会直接存储,8字节以上的longName会把前4字节置0,后四字节是该名字字符串在stringTable中的偏移。sectionNumber是指出该符号定义在那个section中,value是该符号的段内偏移。
多个.obj文件
这次考虑main函数引用另外一个cpp中的符号,同样的所有引用位置是0x0,relocation表中记录了引用信息,在symbolTable里,也有相应的所有符号信息,包括外部定义的符号,但是外部定义符号的sectionNum值等于0(猜测这是区分内部定义外部定义的条件),于是链接器记录该未定义符号,在下一个文件的符号表中寻找,找不到则报错。
与静态库.lib链接
与静态库的链接其实和多个.obj文件链接无2,无非是打了个包。.lib文件中包含了所有的.obj文件,遍历寻找即可。
与动态库进行链接
动态库与静态库不同的是,不能在载入前确定函数地址,第一次链接以后对函数的调用会指向一个IAT表,然后在加载器加在后调用动态连接器填写IAT表。