最近一直在思考微软C++编译器和连接器的工作原理,上网搜寻了很多,没有一篇文章是系统介绍的,无赖之下只得把个人的观点写了下来,纯属个人观点。当中有很多值得商榷或者根本就是错误的地方,但一时又不知道如何修改也不想修改了,因为这个问题我想了几天,似乎越想越糊涂,所以干脆做个了结,非常欢迎大家批评指修改补充,大家一起讨论,共同进步。
首先定义一个结构体:
typedef struct
{
DWORD dName;
DWORD dAddr;
BOOL bPublic;//public型才可被其他模块引用
}_SDTE,_ERTE;//符号描述表项,外部应用表项
compiler:
1.翻译语句,并收集所有符号,分别加入到SDT和ERT。编译器遇到变量定义语句时,会分配空间给该变量,并添加一条_SDTE,翻译程序忽略这条语句;编译器遇到符号声明语句时,根据其前缀决定是加入ERT还是SDT,然后忽略本条语句。编译器发现函数定义时,首先扫描SDT,如果未包含此符号,则加入一条_SDTE;如果已经包含该符号, 则判断该_SDTE的dAddr是否为空,若为空,则修复表项,否则报错“重复定义”。
2.编译器检查ERT和SDT是否有相同项,如果有则报错“重复定义”。
3.逐条扫描SDT中未解析的符号(即没有确定地址的symbol,通常是函数),在文件中寻找其定义,修复表项。
4.再次扫描SDT,如果依然有未解析的符号,编译器报错“未定义的符号”,生成obj文件失败,编译结束;否则进入下一步。
5.用符号地址替换替换符号名(ERT中符号名除外),完成符号解析。
6.添加其他控制信息,生成obj文件,编译结束。
linker:
1.合并各文件的SDT和ERT,组装成全局SDT和ERT。
2.连接器将所有obj、lib文件连接到同一地址空间,重定向SDT中所有符号地址,以及符号引用处地址。
3.逐条扫描ERT中条目,全局范围内搜索其定义(应该可以直接从SDT中提取),修复ERT。
4.对于_imp_?开头的符号,连接器认为他们保存的是DLL中导出函数或变量的地址,对于这类符号,连接器会从lib中提取相应信息 生成输入目录项,同时将这些_imp_?开头的符号组织在一起,构成IAT。4.解析文件中所有符号(基本上就是函数调用的部分)。
5.将各模块的公共部分合并成段,比如代码部分合并成代码段,数据部分合并成数据段等等,添加必要的控制信息,组装成PE文件。
注:lib分为两种,一种是普通cpp文件的obj集合,另一种是DLL的lib。DLL的lib中通常会有形如_imp_?symbol的变量,该变量实际上保存了DLL中的对应符号的地址信息(函数或者变量),比如_imp_?symbol,就保存了DLL中symbol的地址(可能是变量或函数),另外lib中还有一些存根函数以及DLL名等信息。判断一个符号是否在某个模块(obj)定义,只要查看该模块的SDT中是否有该符号即可,即查看该符号是否有地址,有地址即已定义。