dustland

dustball in dustland

程序员的自我修养 chapter 9 LIB

windows LIB文件格式

静态库

以Math.c为例,将其分别编译静态库文件libMath.lib

Math.c

1
2
3
4
5
6
7
8
9
__declspec(dllexport) double Add(double a,double b){
return a+b;
}
__declspec(dllexport) double Sub(double a,double b){
return a-b;
}
__declspec(dllexport) double Mul(double a,double b){
return a*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名则该节不存在

libMath.lib的结构

文件头魔数

不管是静态库还是导入库, 只要是lib文件,开头八个字节都得是文件魔数,

arch,archieve,归档

image-20220818144331204

节区

每个节区都是以一个结构体开始,可以认为是"节区头"

1
2
3
4
5
6
7
8
9
struct SectionHeader{
char Name[16]; // 节名称,即obj文件名
char Time[12]; // 时间
char UserID[6]; // 用户ID
char GroupID[6]; // 组ID
char Mode[8]; // 模式
char Size[10]; // 节区正文长度
char EndOfHeader[2];// 节头结束符
} ;

全都是字符串格式用字节存储,其好处是不管大小端机器,都可以兼容

很奇怪的是FirstSection采用大端模式,到了SecondSection就又成了小端存储了

节区头后面紧跟着是该节的节区正文

即每个节区都是节区头+节区正文格式

相邻两个节区之间可能存在胸垫填充,比如FirstSection的节区正文结束和SecondSection的节头开始之间就存在一个字节的填充

image-20220818164735293

怎么判断有没有填充呢?怎么寻找下一个节的开始位置呢?

每个节头大小是固定的60字节,最后两个字节一定是endMarker[2]="\60 \0A"

First Section

大端模式

包含库中所有符号名以及这些符号所在目标文件在本lib库文件中的偏移量

此处"所有"是指参与组成本lib文件的所有目标文件中的所有符号,不只是一个目标文件中的所有符号

其节名就用了一个字符"/"

Size指明了本节正文占用了多少个字节

节正文结构:

1
2
3
4
5
struct FirstSection{
unsigned long SymbolNum;//大端存储的符号数量
unsigned long SymbolOffset[SymbolNum];//符号所在目标节的偏移
char StrTable[m];//符号名称字符串表,m取决于所有符号的长度
}

对于libMath.lib的第FirstSection,其节区正文的16进制表示为:

1
2
3
4
5
6
7
8
9
10
11
SymbolNum = 00 00 00 03 

SymbolOffset[SymbolNum] =
00 00 00 C2
00 00 00 C2
00 00 00 C2

StrTable[m] =
5F 41 64 64 00
5F 4D 75 6C 00
5F 53 75 62 00

在32位机器上long和int一样长都是32位,因此unsigned long占用了前4个字节

由于大端存储因此03在最高位(最右侧)

SymbolNum=3,这与Math.c中正好有三个函数符号相吻合

SymbolOffset[3]={C2,C2,C2}这表明三个符号同属于一个目标模块,这个目标模块在本lib文件中的偏移量是C2

image-20220818152323244

C2位置确实是第一个也是唯一一个ObjSection的偏移量,这个ObjSection就是Math.obj

StrTable[m]=_Add._Mul._Sub.即所有符号名,每个符号名都以00结尾

Second Section

小端模式

内容和FirstSection相同,但是是一个有序表,查这个比查First Section来的快

名字也和FirstSection相同,都是"/"

正文结构:

1
2
3
4
5
6
7
struct SecondSection{
unsigned long ObjNum;//本库文件中的Obj节数量
unsigned long ObjOffset[ObjNum];//每个Obj节分别的偏移量,
unsigned long SymbolNum;//所有符号数量,作用和FirstSection.SymbolNum相同
unsigned short SymbolIdx[SymbolNum];//第i个符号所在的Obj节下标
char StrTable[m];//符号名表,同FirstSection.StrTable,第i个符号的符号名
}
1
2
3
4
5
6
7
8
9
10
11
ObjNum= 01 00 00 00 
ObjOffset[ObjNum] = C2 00 00 00
SymbolNum = 03 00 00 00
SymbolIdx[SymbolNum] =
01 00
01 00
01 00
StrTable[SymbolNum] =
5F 41 64 64 00 _Add\0
5F 4D 75 6C 00 _Mul\0
5F 53 75 62 00 _Sum\0

比较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找,并且给出了相对于该节的位置

image-20220818154931447

Math.obj显然不够16个字节,本lib文件中没有该节

Obj Section

目标文件节,存放不同的目标文件的原始数据.

本节的节区正文相当于把COFF文件直接乎过来了

节头:

image-20220818154534122

从名称上可以看出Math.obj/

Size=794本节正文长794个字节

节区正文是直接抄的obj文件

节区正文
Math.obj

节区正文和Math.obj完全相同

导入库

还是以Math.c制作动态库时形成的导入库Math.lib为例

1
2
3
4
5
6
7
8
9
__declspec(dllexport) double Add(double a,double b){
return a+b;
}
__declspec(dllexport) double Sub(double a,double b){
return a-b;
}
__declspec(dllexport) double Mul(double a,double b){
return a*b;
}
1
2
cl Math.c /c
link /dll Math.obj

编译链接完成后同一目录下面形成Math.lib和Math.dll

此Math.lib就是导入库

image-20220818165520115

从节头名看,前两个节分别是FirstSection和SecondSection,后面就都是目标文件了

文件头魔数

所有lib文件不管是静态库还是导入库都一样!<arch>

节区

First Section

节头除了本节正文大小之外就没有什么有效信息了主要是看节区:

1
2
3
4
5
struct FirstSection{
unsigned long SymbolNum;//大端存储的符号数量
unsigned long SymbolOffset[SymbolNum];//符号所在目标节的偏移
char StrTable[m];//符号名称字符串表,m取决于所有符号的长度
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SymbolNum = 00 00 00 09 
SymbolOffset[SymbolNum] =
00 00 01 CA
00 00 03 E8
00 00 05 1C
00 00 06 68
00 00 06 68
00 00 07 24
00 00 07 24
00 00 06 C6
00 00 06 C6
StrTable[m] =
__IMPORT_DESCRIPTOR_Math: 5F 5F 49 4D 50 4F 52 54 5F 44 45 53 43 52 49 50 54 4F 52 5F 4D 61 74 68 00
__NULL_IMPORT_DESCRIPTOR: 5F 5F 4E 55 4C 4C 5F 49 4D 50 4F 52 54 5F 44 45 53 43 52 49 50 54 4F 52 00
Math_NULL_THUNK_DATA: 7F 4D 61 74 68 5F 4E 55 4C 4C 5F 54 48 55 4E 4B 5F 44 41 54 41 00
_Add: 5F 41 64 64 00
__imp__Add: 5F 5F 69 6D 70 5F 5F 41 64 64 00
_Sub: 5F 53 75 62 00
__imp__Sub: 5F 5F 69 6D 70 5F 5F 53 75 62 00
_Mul: 5F 4D 75 6C 00
__imp__Mul: 5F 5F 69 6D 70 5F 5F 4D 75 6C 00

可以看出本导入库中有9个符号,前三个符号

1
2
3
__IMPORT_DESCRIPTOR_Math
__NULL_IMPORT_DESCRIPTOR
Math_NULL_THUNK_DATA

是预定义的,就算我们啥也不写,照样有这三个符号

然后每个我们自己写的函数都有两个名字,比如

1
2
_Add
__imp__Add

这是x64符号名修饰造成的,实际上两个符号指向同一函数

Second Section

1
2
3
4
5
6
7
struct SecondSection{
unsigned long ObjNum;//本库文件中的Obj节数量
unsigned long ObjOffset[ObjNum];//每个Obj节分别的偏移量,
unsigned long SymbolNum;//所有符号数量,作用和FirstSection.SymbolNum相同
unsigned short SymbolIdx[SymbolNum];//第i个符号所在的Obj节下标
char StrTable[m];//符号名表,同FirstSection.StrTable,第i个符号的符号名
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
ObjNum = 06 00 00 00 
ObjOffset[ObjNum]=
CA 01 00 00
E8 03 00 00
1C 05 00 00
68 06 00 00
24 07 00 00
C6 06 00 00
SymbolNum=09 00 00 00
SymbolIdx[SymbolNum]=
04 00
06 00
05 00
01 00
02 00
04 00
06 00
05 00
03 00
StrTable[m]=
5F 41 64 64 00
5F 4D 75 6C 00
5F 53 75 62 00
5F 5F 49 4D 50 4F 52 54 5F 44 45 53 43 52 49 50 54 4F 52 5F 4D 61 74 68 00
5F 5F 4E 55 4C 4C 5F 49 4D 50 4F 52 54 5F 44 45 53 43 52 49 50 54 4F 52 00
5F 5F 69 6D 70 5F 5F 41 64 64 00
5F 5F 69 6D 70 5F 5F 4D 75 6C 00
5F 5F 69 6D 70 5F 5F 53 75 62 00
7F 4D 61 74 68 5F 4E 55 4C 4C 5F 54 48 55 4E 4B 5F 44 41 54 41 00

共有6个ObjSection段

每个ObjSection的节头中的名字都叫"Math.dll"

Obj Section

前三个Obj节都是关于三个预定义符号的,所有的导入库中他仨基本相同

后面三个Obj节是关于我们自定义的函数的,每个自定义函数自成一节,这也就解释了为啥默认状态下动态库的链接是以函数为单位,而静态库的链接是以模块为单位的了.导入库中每个函数自成一个模块

下面炎鸠后三节的结构,参考The Structure of import Library File (.lib) - CodeProject

以Add函数所在节为例

image-20220819104759118

节头表明正文部分有34字节

描述正文的结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct SYMBOL_DESCRIPTOR          // size = 0x14
{
WORD a;
WORD b;
WORD c;
WORD Architecture;//程序可以运行的体系结构,比如x86,x86_64
DWORD Id;//随机生成的ID
DWORD Length;//符号名和库名 字符串总长度
union
{
WORD Hint;//最有可能的序号
WORD Ordinal;
WORD Value;
}
WORD Type;
};
1
2
3
4
5
6
7
8
9
10
11
00 00 
FF FF
00 00
4C 01 //Architecture,x86
F2 29 95 FA //随机数
0E 00 00 00 //_Add\0Math.dll\0 两个字符串的总长度(包括00)
00 00 //Hint=0,表示AddressOfNames中的下标
08 00 //Type=8,表示x86 __cdecl调用约定

5F 41 64 64 00 //_Add
4D 61 74 68 2E 64 6C 6C 00 //Math.dll

工具

MSVC

cl /c 只编译,不链接,生成目标模块

lib /EXTRACT:<目标文件> /OUT:<目标文件> 从lib文件中提取obj文件

lib Math.c /NAME:libMath.lib 制作静态库