LIB文件格式
LIB文件{
char[8] flag;//“!<arch>\n”
Section First;
Section Second;
Section Long;
Section Obj[x];//包含有多少个obj文件,就有多少个obj section
}
FirstSec
第一节,通常就是Lib中的每一个小节。它的名称是“/”。其数据部分的结构如下:typedef struct {
unsigned long SymbolNum; // 库中符号的数量
unsigned long SymbolOffset[n]; // 符号所在目标节的偏移
char StrTable[m]; // 符号名称字符串表
}FirstSec;
第一个成员SymbolNum是符号的数量。注意!它是以Big-Endian方式储存的(x86平台上的数据是以Little-Endian方式储存的。这里应该注意转换。后面给出的convert函数可以在Little-Endian格式与Big-Endian格式之间进行相互转换)。
第二个成员SymbolOffset是一个数组,它的长度n就是符号的数量,也就是SymbolNum。这个数组储存了每一个符号所在的目标节的偏移。我们可以方便地通过它来查找符号所在的目标文件。注意!它也是以Big-Endian格式储存的。
第三个成员StrTable是一个字符串表,它的长度m就是SectionHeader.Size的值减去(SymbolNum+1)*4。其结构很简单,就是一堆以‘\0’结尾的字符串(和COFF文件中的字符串表结构相同)。在有的系统中,它还可能是以“/\n”这两个字符结尾的字符串的集合。
很简单的一个结构,不过有两个成员的长度是不定的。怎么才能方便地从Lib中读出这些数据,留给大家自己想吧!下面我只给出一个进行Little-Endian与Big-Endian互转的函数。
SectionHeader
SectionHeader{
char Name[16]; // 名称
char Time[12]; // 时间
char UserID[6]; // 用户ID
char GroupID[6]; // 组ID
char Mode[8]; // 模式
char Size[10]; // 长度
char EndOfHeader[2];// 结束符,其内容为“`\n”
}
要从文件提取obj文件,我们一般只需要关注第二个区段。这个里面保存了如下的信息:
typedef struct {
unsigned long ObjNum; // Obj Sec的数量
unsigned long ObjOffset[x]; // 每一个Obj Sec的偏移
unsigned long SymbolNum; // 库中符号的数量
unsigned short SymbolIdx[n]; // 符号在ObjOffset表中的索引
char StrTable[m]; // 符号名称字符串表
}SecondSec;
但是我们却不能直接去读取他,因为
ObjOffset[x]; // 每一个Obj Sec的偏移
长度是不固定的,取决于
ObjNum; // Obj Sec的数量
而且,first section的长度也是不固定的。这怎么办呢?
解决办法如下:
first section和second section都以
char szSecName[16] = {'/', 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20};
这样的形式开头。
我就定义了一个char,一个个字节去读取,遇到'/'就判断后面是不是15个0x20,如果读了2次这样的结构,那么说明到了second section,马上读后面的内容。这就是ObjNum,也就是obj的数量。
然后去申请一块内存,用来存储ObjOffset[x]; // 每一个Obj Sec的偏移
ULONG* m_offset = new ULONG[ObjNum],然后从文件里面将obj偏移读取到这个数组。
请注意,这些偏移值,并不是由小到大排列的,所以得自己排列一次。
用第二个偏移减去第一个偏移,得到的就是第一个obj section的大小(不是obj文件的大小)。因为每个obj section前面还有一个SectionHeader结构体,所以obj文件真正的偏移是:
读出来的偏移 offset + sizeof(SectionHeader),从这里开始读,才是obj文件的首地址。
这一节与第一节很相似!它通常也就是Lib文件的第二个节。它的名字也是“/”(注意:文件中第一个叫“/”的节是第一节,第二个就是第二节)。不过它的结构与第一节有些不同,如下:
typedef struct {
unsigned long ObjNum; // Obj Sec的数量
unsigned long ObjOffset[x]; // 每一个Obj Sec的偏移
unsigned long SymbolNum; // 库中符号的数量
unsigned short SymbolIdx[n]; // 符号在ObjOffset表中的索引
char StrTable[m]; // 符号名称字符串表
}SecondSec;
第一个成员ObjNum是库中Obj Sec的数量。
第二个成员ObjOffset是一个偏移表,它记录了库中所有Obj Sec的偏移。这个表的记录数x就是ObjNum。
第三个成员SymbolNum与First Sec中的SymbolNum意义相同。
第四个成员SymbolIdx变成了一个索引,它记录了相应名称字符串在ObjOffset这个表中的位置,我们要通过两次索引才能找到我们所要符号的Obj Sec位置。它的项目数n为SymbolNum。但请注意,这个索引是unsigned short型,不再是unsigned long型。
第五个成员StrTable结构与First Sec中的一样。不过,它的长度m为SectionHeader.Size的值减去((ObjNum+1)*4+(SymbolNum+2)*2)。
值得注意的是,这里的所有数据都是Little-Endian格式的。千万不要弄错了!
Longname Sec
这个小节就是一个字符串表,它的名称为“//”,其结构同FirstSec.StrTable。这里就不多说了。Obj Sec
这一节中的数据就是COFF文件的原始数据,把它读出来存成文件,就是一个COFF文件。它的格式请参考《COFF格式》一文。要指出的是它的命名方式有些特殊。如果Obj文件的名称少于16个字符,它就会被保存在SectionHeader的Name成员中,以‘/’字符结尾。如果无法保存在Name成员中,则Name成员的第一个字符就为‘/’,之后再跟上这个名称在Longname Sec中的偏移。
例如:
!<arch>\n
……
LongName Sec:
This_Is_Long_Name0001\0
This_Is_Long_Name0002\0
……
Obj Sec1:
Name[16]:“shortname”
……
Obj Sec2:
Name[16]:“0” // 这里使用了第一个长文件名This_Is_Long_Name0001
……
Obj Sec3:
Name[16]:“22” // 这里使用了第二个长文件名This_Is_Long_Name0002