windows LIB文件格式
静态库
以Math.c为例,将其分别编译静态库文件libMath.lib
Math.c
1 | __declspec(dllexport) double Add(double a,double b){ |
制作静态库
1 | lib Math.c /NAME:libMath.lib |
lib以一个文件魔数!<arch>\n
开头,
接下来顺次是First Section,Second Section,Long Section,Obj Section.
如果本lib文件中包括了多个obj文件则ObjSection可以有多个.
Long Section用来存放太长的obj名称,如果没有超过16个字节的obj名则该节不存在
文件头魔数
不管是静态库还是导入库, 只要是lib文件,开头八个字节都得是文件魔数,
arch,archieve,归档
节区
每个节区都是以一个结构体开始,可以认为是”节区头”
1 | struct SectionHeader{ |
全都是字符串格式用字节存储,其好处是不管大小端机器,都可以兼容
很奇怪的是FirstSection采用大端模式,到了SecondSection就又成了小端存储了
节区头后面紧跟着是该节的节区正文
即每个节区都是节区头+节区正文格式
相邻两个节区之间可能存在胸垫填充,比如FirstSection的节区正文结束和SecondSection的节头开始之间就存在一个字节的填充
怎么判断有没有填充呢?怎么寻找下一个节的开始位置呢?
每个节头大小是固定的60字节,最后两个字节一定是endMarker[2]=”\60 \0A”
First Section
大端模式
包含库中所有符号名以及这些符号所在目标文件在本lib库文件中的偏移量
此处”所有”是指参与组成本lib文件的所有目标文件中的所有符号,不只是一个目标文件中的所有符号
其节名就用了一个字符”/“
Size指明了本节正文占用了多少个字节
节正文结构:
1 | struct FirstSection{ |
对于libMath.lib的第FirstSection,其节区正文的16进制表示为:
1 | SymbolNum = 00 00 00 03 |
在32位机器上long和int一样长都是32位,因此unsigned long占用了前4个字节
由于大端存储因此03在最高位(最右侧)
SymbolNum=3
,这与Math.c中正好有三个函数符号相吻合
SymbolOffset[3]={C2,C2,C2}
这表明三个符号同属于一个目标模块,这个目标模块在本lib文件中的偏移量是C2
C2位置确实是第一个也是唯一一个ObjSection的偏移量,这个ObjSection就是Math.obj
StrTable[m]=_Add._Mul._Sub.
即所有符号名,每个符号名都以00结尾
Second Section
小端模式
内容和FirstSection相同,但是是一个有序表,查这个比查First Section来的快
名字也和FirstSection相同,都是”/“
正文结构:
1 | struct SecondSection{ |
1 | ObjNum= 01 00 00 00 |
比较FirstSection和SecondSection
FirstSection | SecondSection | 意义是否相同 | |
---|---|---|---|
存储方式 | 大端 | 小端 | |
记录符号个数 | SymbolNum | SymbolNum | 相同 |
记录符号位置 | SymbolOffset[i]第i个符号所在obj的节偏移量 | SymbolIdx[i]第i个符号所在的obj节是第几个obj节 | 不同 |
记录符号名 | StrTable[m] | StrTable[m] | 相同 |
记录Obj节数 | 无 | ObjNum | |
记录每个Obj节的偏移量 | 无 | ObjOffset[ObjNum] |
Long Section
长名称节,存放太长的obj名
每个节开始时的SectionHeader会记录该节的信息,但是SectionHeader.Name只有16字节,如果一个obj文件名比如”LinkedDoubleList.obj”长度就超过了16字节,用Name[16]显然放不下,就得放到Long section节里,Name[16]存放的是”/<LongSection中的偏移量>
“表明该节名需要去Long Section找,并且给出了相对于该节的位置
Math.obj显然不够16个字节,本lib文件中没有该节
Obj Section
目标文件节,存放不同的目标文件的原始数据.
本节的节区正文相当于把COFF文件直接乎过来了
节头:
从名称上可以看出Math.obj/
Size=794本节正文长794个字节
节区正文是直接抄的obj文件
节区正文和Math.obj完全相同
导入库
还是以Math.c制作动态库时形成的导入库Math.lib为例
1 | __declspec(dllexport) double Add(double a,double b){ |
1 | cl Math.c /c |
编译链接完成后同一目录下面形成Math.lib和Math.dll
此Math.lib就是导入库
从节头名看,前两个节分别是FirstSection和SecondSection,后面就都是目标文件了
文件头魔数
所有lib文件不管是静态库还是导入库都一样!<arch>
节区
First Section
节头除了本节正文大小之外就没有什么有效信息了主要是看节区:
1 | struct FirstSection{ |
1 | SymbolNum = 00 00 00 09 |
可以看出本导入库中有9个符号,前三个符号
1 | __IMPORT_DESCRIPTOR_Math |
是预定义的,就算我们啥也不写,照样有这三个符号
然后每个我们自己写的函数都有两个名字,比如
1 | _Add |
这是x64符号名修饰造成的,实际上两个符号指向同一函数
Second Section
1 | struct SecondSection{ |
1 | ObjNum = 06 00 00 00 |
共有6个ObjSection段
每个ObjSection的节头中的名字都叫”Math.dll”
Obj Section
前三个Obj节都是关于三个预定义符号的,所有的导入库中他仨基本相同
后面三个Obj节是关于我们自定义的函数的,每个自定义函数自成一节,这也就解释了为啥默认状态下动态库的链接是以函数为单位,而静态库的链接是以模块为单位的了.导入库中每个函数自成一个模块
下面炎鸠后三节的结构,参考The Structure of import Library File (.lib) - CodeProject
以Add函数所在节为例
节头表明正文部分有34字节
描述正文的结构体:
1 | struct SYMBOL_DESCRIPTOR // size = 0x14 |
1 | 00 00 |