linux存档文件格式分析

来源:互联网 发布:北京seo 排名 编辑:程序博客网 时间:2024/06/05 03:12

出于好奇,想了解一下linux的存档文件(*.a)的格式。在网上大概找了一下,没找到比较好的(没细找),所以想自己摸索
一下,把它的文件格式弄清楚。结构发现这个文件的格式竟然那么简单。在这里和大家分享一下,有错误的地方希望大家指正。
1.存档文件(archive)简介
 存档文件(*.a)文件相当于windows的静态链接库,他把很多的目标文件(*.o)文件打包为单一的库文件。在链接程序
的时候,需要用到库里面的函数时,只需在链接时加上-lX    (X表示库的名字),链接器会自动得根据要调用函数,找到相应的
目标文件,链接到可执行程序中,不需要的目标不会被链接到可执行程序中。

2.archive文件的格式[参考/usr/include/ar.h中的说明]
 archive文件的格式主要保护两个部分,主要是:
 一个头部标志ARMAG,
 多个数据区。
 ['头部标志'和'数据区'是我自己为了方便说明起的名字,跟官方的说法应该是有出入的]。
2.1 头部标志
 头部标志的作用仅用于说明这是一个archive文件
 在ar.h中定义了两个宏:
 #define ARMAG   "!<arch>\n" /* String that begins an archive file.  */
 #define SARMAG  8       /* Size of that string.  */
 ARMAG指明了头部标志的内容,SARMAG说明了头部标志的长度。
2.2 数据区
 一个archive文件有多个数据区,每个数据区包含一个头部(ar_hdr)和一段数据段,他们是紧跟在一起的。
 在ar.h中ar_hdr的声明是这样的
 struct ar_hdr
 {
     char ar_name[16];       /* Member file name, sometimes / terminated. */
      char ar_date[12];       /* File date, decimal seconds since Epoch.  */
       char ar_uid[6], ar_gid[6];  /* User and group IDs, in ASCII decimal.  */
       char ar_mode[8];        /* File mode, in ASCII octal.  */
       char ar_size[10];       /* File size, in ASCII decimal.  */
       char ar_fmag[2];        /* Always contains ARFMAG.  */
 };
 在archive中每个目标文件(*.o)都会被分配一个数据区,数据区的数据段是该目标文件(*.o)原封不动的拷贝,ar_hdr
则存放了对该目标文件的说明。
 ar_name文件说明了目标文件的名字(以/作为终止符),
 ar_date说明了该文件的日期,
 ar_uid、ar_gid指明了该目标文件的用户ID和组ID,
 ar_mode指明了文件的访问属性(权限),
 ar_size指明了目标文件的大小,同时是该数据区数据段的大小
 ar_fmag总是包含ARFMAG,该宏被定义为#define ARFMAG  "`\n" , 用户指明ar_hdr的尾部。
 
 除了为每个目标文件分配一个数据区之外,archive文件还添加一个特殊的数据区,它位于第一个数据区,它的数据段
包含了该archive文件的符号数(函数/全局变量)、符号名称、符号所在目标文件的偏移量。
3.archive文件的分析
 首先,建立两个测试文件,并把它们加到archive文件中:
[root:/root/test/ar]cat test_ar.c
int test(void)    <---------------------定义一个函数和一个变量
{
        return 0;
};
int error;
[root:/root/test/ar]gcc -o test_ar.o -c test_ar.c
[root:/root/test/ar]cat test_ar_1.c
int test(void)    <------------------------定义一个函数
{
        return 1;
}
[root:/root/test/ar]gcc -o test_ar_1.o -c test_ar_1.c
[root:/root/test/ar]ar -rv libtest_ar.a test_ar.o test_ar_1.o
r - test_ar.o
r - test_ar_1.o
[root:/root/test/ar]ar -vt libtest_ar.a
rw-rw-r-- 0/0    706 Jul 17 15:45 2008 test_ar.o
rw-rw-r-- 0/0    686 Jul 17 15:45 2008 test_ar_1.o
 这样,我们把test_ar.o test_ar_1.o放到了libtest_ar.a中。我们用hexdump对libtest_ar.a进程剖析
 (不熟悉hexdump用法的参见man hexdump)
[root:/root/test/ar]hexdump -C -s 0x0 -n 8 libtest_ar.a    
00000000  21 3c 61 72 63 68 3e 0a                           |!<arch>.|  <----头部标志
00000008
 上面是头部标志部分,位于文件最前面。
[root:/root/test/ar]hexdump -C -s 0x8 -n 60 libtest_ar.a
00000008  2f 20 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |/               | 
00000018  31 32 31 36 32 38 30 37  39 36 20 20 30 20 20 20  |1216280796  0   | 
00000028  20 20 30 20 20 20 20 20  30 20 20 20 20 20 20 20  |  0     0       | 
00000038  33 32 20 20 20 20 20 20  20 20 60 0a              |32        `.| 
00000044
 上面是第一个数据区的头部ar_hdr,名字为空, '/'是终止符,1216280796是文件的时间,是被字符串化
的time_t类型的数据,三个0中前两个分别为uid和gid,后面一个标志目标文件属性,在这忽略,32是该数据
区后面数据段的长度, `.是终止符。
 
[root:/root/test/ar]hexdump -C -s 0x44 -n 32 libtest_ar.a
00000044  00 00 00 03 00 00 00 64  00 00 00 64 00 00 03 62  |.......d...d...b|
00000054  74 65 73 74 00 65 72 72  6f 72 00 74 65 73 74 00  |test.error.test.|
 上面是特殊数据区的数据段,第一行中00 00 00 03表示的是该archive文件向外
导出的符号数,这里为3(注意,这里的数据都是按大尾的编码方式的)。00 00 00 64 、
 00 00 00 64、 00 00 03 62分别是三个符号所在目标文件的ar_hdr在该archive文件中的
偏移量,分别是0x64、0x64、0x362,紧接着是符号名称的字符串表。
[root:/root/test/ar]hexdump -C -s 0x64 -n 60 libtest_ar.a
00000064  74 65 73 74 5f 61 72 2e  6f 2f 20 20 20 20 20 20  |test_ar.o/      |
00000074  31 32 31 36 32 38 30 37  31 38 20 20 30 20 20 20  |1216280718  0   |
00000084  20 20 30 20 20 20 20 20  31 30 30 36 36 34 20 20  |  0     100664  |
00000094  37 30 36 20 20 20 20 20  20 20 60 0a              |706       `.|
000000a0
[root:/root/test/ar]hexdump -C -s 0x362 -n 60 libtest_ar.a
00000362  74 65 73 74 5f 61 72 5f  31 2e 6f 2f 20 20 20 20  |test_ar_1.o/    |
00000372  31 32 31 36 32 38 30 37  35 30 20 20 30 20 20 20  |1216280750  0   |
00000382  20 20 30 20 20 20 20 20  31 30 30 36 36 34 20 20  |  0     100664  |
00000392  36 38 36 20 20 20 20 20  20 20 60 0a              |686       `.|
0000039e
 上面验证了0x64、0x362是目标文件的ar_hdr在该archive文件中的偏移量,可以看出,第一个test和error定义在
test_ar.o中,第二个test定义在test_ar_1.o中,这和我们之前写的文件是相符的。
 上面第一命令,同样test_ar.o/是目标文件的名字,1216280718是时间,两个0分别是uid和gid,100644表示文件
权限(rw_rw_r__), 706是该目标文件的大小,也是下面数据段的大小。
[root:/root/test/ar]ls -l *.o
-rw-rw-r-- 1 root root 686 07-17 15:45 test_ar_1.o
-rw-rw-r-- 1 root root 706 07-17 15:45 test_ar.o
 和archive文件中的数据相符的。
 接下来我们对比一下存档中的内容和目标文件的内容。
[root:/root/test/ar]hexdump -C -s 0xa0 -n 706 libtest_ar.a      <-------0xa0是紧接着test_ar.o的ar_hdr之后的地址
000000a0  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
000000b0  01 00 03 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
000000c0  b4 00 00 00 00 00 00 00  34 00 00 00 00 00 28 00  |........4.....(.|
000000d0  09 00 06 00 55 89 e5 b8  00 00 00 00 5d c3 00 00  |....U.......]...|
000000e0  00 47 43 43 3a 20 28 47  4e 55 29 20 34 2e 31 2e  |.GCC: (GNU) 4.1.|
000000f0  32 20 32 30 30 37 30 39  32 35 20 28 52 65 64 20  |2 20070925 (Red |
00000100  48 61 74 20 34 2e 31 2e  32 2d 33 33 29 00 00 2e  |Hat 4.1.2-33)...|
00000110  73 79 6d 74 61 62 00 2e  73 74 72 74 61 62 00 2e  |symtab..strtab..|
00000120  73 68 73 74 72 74 61 62  00 2e 74 65 78 74 00 2e  |shstrtab..text..|
00000130  64 61 74 61 00 2e 62 73  73 00 2e 63 6f 6d 6d 65  |data..bss..comme|
00000140  6e 74 00 2e 6e 6f 74 65  2e 47 4e 55 2d 73 74 61  |nt..note.GNU-sta|
00000150  63 6b 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |ck..............|
00000160  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000170  00 00 00 00 00 00 00 00  00 00 00 00 1b 00 00 00  |................|
00000180  01 00 00 00 06 00 00 00  00 00 00 00 34 00 00 00  |............4...|
00000190  0a 00 00 00 00 00 00 00  00 00 00 00 04 00 00 00  |................|
000001a0  00 00 00 00 21 00 00 00  01 00 00 00 03 00 00 00  |....!...........|
000001b0  00 00 00 00 40 00 00 00  00 00 00 00 00 00 00 00  |....@...........|
000001c0  00 00 00 00 04 00 00 00  00 00 00 00 27 00 00 00  |............'...|
000001d0  08 00 00 00 03 00 00 00  00 00 00 00 40 00 00 00  |............@...|
000001e0  00 00 00 00 00 00 00 00  00 00 00 00 04 00 00 00  |................|
000001f0  00 00 00 00 2c 00 00 00  01 00 00 00 00 00 00 00  |....,...........|
00000200  00 00 00 00 40 00 00 00  2e 00 00 00 00 00 00 00  |....@...........|
00000210  00 00 00 00 01 00 00 00  00 00 00 00 35 00 00 00  |............5...|
00000220  01 00 00 00 00 00 00 00  00 00 00 00 6e 00 00 00  |............n...|
00000230  00 00 00 00 00 00 00 00  00 00 00 00 01 00 00 00  |................|
00000240  00 00 00 00 11 00 00 00  03 00 00 00 00 00 00 00  |................|
00000250  00 00 00 00 6e 00 00 00  45 00 00 00 00 00 00 00  |....n...E.......|
00000260  00 00 00 00 01 00 00 00  00 00 00 00 01 00 00 00  |................|
00000270  02 00 00 00 00 00 00 00  00 00 00 00 1c 02 00 00  |................|
00000280  90 00 00 00 08 00 00 00  07 00 00 00 04 00 00 00  |................|
00000290  10 00 00 00 09 00 00 00  03 00 00 00 00 00 00 00  |................|
000002a0  00 00 00 00 ac 02 00 00  16 00 00 00 00 00 00 00  |................|
000002b0  00 00 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
000002c0  00 00 00 00 00 00 00 00  00 00 00 00 01 00 00 00  |................|
000002d0  00 00 00 00 00 00 00 00  04 00 f1 ff 00 00 00 00  |................|
000002e0  00 00 00 00 00 00 00 00  03 00 01 00 00 00 00 00  |................|
000002f0  00 00 00 00 00 00 00 00  03 00 02 00 00 00 00 00  |................|
00000300  00 00 00 00 00 00 00 00  03 00 03 00 00 00 00 00  |................|
00000310  00 00 00 00 00 00 00 00  03 00 05 00 00 00 00 00  |................|
00000320  00 00 00 00 00 00 00 00  03 00 04 00 0b 00 00 00  |................|
00000330  00 00 00 00 0a 00 00 00  12 00 01 00 10 00 00 00  |................|
00000340  04 00 00 00 04 00 00 00  11 00 f2 ff 00 74 65 73  |.............tes|
00000350  74 5f 61 72 2e 63 00 74  65 73 74 00 65 72 72 6f  |t_ar.c.test.erro|
00000360  72 00                                             |r.|
00000362
[root:/root/test/ar]hexdump -C -s 0x0 -n 706 test_ar.o
00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  01 00 03 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  b4 00 00 00 00 00 00 00  34 00 00 00 00 00 28 00  |........4.....(.|
00000030  09 00 06 00 55 89 e5 b8  00 00 00 00 5d c3 00 00  |....U.......]...|
00000040  00 47 43 43 3a 20 28 47  4e 55 29 20 34 2e 31 2e  |.GCC: (GNU) 4.1.|
00000050  32 20 32 30 30 37 30 39  32 35 20 28 52 65 64 20  |2 20070925 (Red |
00000060  48 61 74 20 34 2e 31 2e  32 2d 33 33 29 00 00 2e  |Hat 4.1.2-33)...|
00000070  73 79 6d 74 61 62 00 2e  73 74 72 74 61 62 00 2e  |symtab..strtab..|
00000080  73 68 73 74 72 74 61 62  00 2e 74 65 78 74 00 2e  |shstrtab..text..|
00000090  64 61 74 61 00 2e 62 73  73 00 2e 63 6f 6d 6d 65  |data..bss..comme|
000000a0  6e 74 00 2e 6e 6f 74 65  2e 47 4e 55 2d 73 74 61  |nt..note.GNU-sta|
000000b0  63 6b 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |ck..............|
000000c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000d0  00 00 00 00 00 00 00 00  00 00 00 00 1b 00 00 00  |................|
000000e0  01 00 00 00 06 00 00 00  00 00 00 00 34 00 00 00  |............4...|
000000f0  0a 00 00 00 00 00 00 00  00 00 00 00 04 00 00 00  |................|
00000100  00 00 00 00 21 00 00 00  01 00 00 00 03 00 00 00  |....!...........|
00000110  00 00 00 00 40 00 00 00  00 00 00 00 00 00 00 00  |....@...........|
00000120  00 00 00 00 04 00 00 00  00 00 00 00 27 00 00 00  |............'...|
00000130  08 00 00 00 03 00 00 00  00 00 00 00 40 00 00 00  |............@...|
00000140  00 00 00 00 00 00 00 00  00 00 00 00 04 00 00 00  |................|
00000150  00 00 00 00 2c 00 00 00  01 00 00 00 00 00 00 00  |....,...........|
00000160  00 00 00 00 40 00 00 00  2e 00 00 00 00 00 00 00  |....@...........|
00000170  00 00 00 00 01 00 00 00  00 00 00 00 35 00 00 00  |............5...|
00000180  01 00 00 00 00 00 00 00  00 00 00 00 6e 00 00 00  |............n...|
00000190  00 00 00 00 00 00 00 00  00 00 00 00 01 00 00 00  |................|
000001a0  00 00 00 00 11 00 00 00  03 00 00 00 00 00 00 00  |................|
000001b0  00 00 00 00 6e 00 00 00  45 00 00 00 00 00 00 00  |....n...E.......|
000001c0  00 00 00 00 01 00 00 00  00 00 00 00 01 00 00 00  |................|
000001d0  02 00 00 00 00 00 00 00  00 00 00 00 1c 02 00 00  |................|
000001e0  90 00 00 00 08 00 00 00  07 00 00 00 04 00 00 00  |................|
000001f0  10 00 00 00 09 00 00 00  03 00 00 00 00 00 00 00  |................|
00000200  00 00 00 00 ac 02 00 00  16 00 00 00 00 00 00 00  |................|
00000210  00 00 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
00000220  00 00 00 00 00 00 00 00  00 00 00 00 01 00 00 00  |................|
00000230  00 00 00 00 00 00 00 00  04 00 f1 ff 00 00 00 00  |................|
00000240  00 00 00 00 00 00 00 00  03 00 01 00 00 00 00 00  |................|
00000250  00 00 00 00 00 00 00 00  03 00 02 00 00 00 00 00  |................|
00000260  00 00 00 00 00 00 00 00  03 00 03 00 00 00 00 00  |................|
00000270  00 00 00 00 00 00 00 00  03 00 05 00 00 00 00 00  |................|
00000280  00 00 00 00 00 00 00 00  03 00 04 00 0b 00 00 00  |................|
00000290  00 00 00 00 0a 00 00 00  12 00 01 00 10 00 00 00  |................|
000002a0  04 00 00 00 04 00 00 00  11 00 f2 ff 00 74 65 73  |.............tes|
000002b0  74 5f 61 72 2e 63 00 74  65 73 74 00 65 72 72 6f  |t_ar.c.test.erro|
000002c0  72 00                                             |r.|
000002c2
 对比上面两个输出,内容是完全一致的,正是我之前期盼的那样。

4.链接
 在上面的例子中,我故意在test_ar.c、test_ar_1.c中定义了同名的函数。但一个返回0,
一个返回1,主要是为了检查符号的解析。
 我又写了一个测试文件。
[root:/root/test/ar]cat main.c
#include <stdio.h>
int main()
{
        int i = test();
        printf("%d\n", i);
        return 0;
}
[root:/root/test/ar]gcc -o main main.c -L. -ltest_ar
[root:/root/test/ar]./main
0
输出结果为0,也就是它调用的时test_ar.o中的test()函数而不是调用test_ar_1.o中的函数。
我们看上面符号表中的数据
00000044  00 00 00 03 00 00 00 64  00 00 00 64 00 00 03 62  |.......d...d...b|
00000054  74 65 73 74 00 65 72 72  6f 72 00 74 65 73 74 00  |test.error.test.|
test_ar.o中test在前面,链接的过程我猜想是这样的[如有错误请纠正]:
1.函数遇到一个未解析的符号,它在-l指定的连接归档库中查找。
2.当查找到第一个符合的符号时,他即停止搜索。
3.根据符号获得目标文件在archive中的偏移量,把相关的目标文件(*.o)从库中复制一份出来,
链接到可执行程序中。