author: hjjdebug
date: 2026年 07月 12日 星期日 11:44:14 CST
descrip: ar 工具及 ar 库格式研究.
文章目录
- [1. 用ar 工具及 nm 工具来了解 ar 库文件](#1. 用ar 工具及 nm 工具来了解 ar 库文件)
- [2. 用自编简单工具来了解ar 库文件](#2. 用自编简单工具来了解ar 库文件)
-
- [2.1 实验结果](#2.1 实验结果)
ar 是文档工具, 与tar, cpio 有类似的性质,但主要用在将.o文件打包成.a文件,
同时还会生成符号索引.
我一直对这个所谓的符号索引不知所云, 所以决定花点时间认真研究一下:
1. 用ar 工具及 nm 工具来了解 ar 库文件
- 准备2个文件test.c, test1.c
cpp
$ cat test.c
void func()
{
}
$ cat test1.c
void func1()
{
}
- 生成库文件, 一个带索引(默认带索引),一个不带索引,如下
cpp
$ gcc -c -o test.o test.c
$ gcc -c -o test1.o test1.c
$ ar rc libtest.a test.o test1.o
$ ar rcS libtest1.a test.o test1.o //不带索引
- 查看区别.
ar tv \<库文件\> 命令查看, 看不到区别 nm <库文件> 命令查看, 也看不到区别
$ nm -s <库文件> 查看, 看到了区别, 如下:
cpp
$ nm -s libtest.a
归档索引:
func in test.o
func1 in test1.o
test.o:
0000000000000000 T func
test1.o:
0000000000000000 T func1
$ nm -s libtest1.a
test.o:
0000000000000000 T func
test1.o:
0000000000000000 T func1
查帮助 nm -s 就是 打印armap 的意思, 就是打印文档的索引符号.
但我感觉还是对索引符号的概念不太清楚.
要想深刻的理解索引符号,需要来一段代码,并认真研读.
索引符号是函数名称与文件偏移构成的表.
这样当你知道函数名称时,通过这个表就知道在库中的偏移,为gcc 编译连接提供了信息.
2. 用自编简单工具来了解ar 库文件
下面为是对ar 库的解析文件, 很短,读懂此文件即了解了.a文件的构成.
cpp
$ cat ar_parse.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define ARMAG "!<arch>\n"
#define ARFMAG "`\n"
struct ar_hdr {
char ar_name[16];
char ar_date[12];
char ar_uid[6];
char ar_gid[6];
char ar_mode[8];
char ar_size[10];
char ar_fmag[2];
};
static void trim_space(char *buf, int len)
{
for (int i = len - 1; i >= 0; i--)
{
if (buf[i] == ' ')
buf[i] = '\0';
else
break;
}
}
// 大端4字节转本机long
static unsigned long be32_to_host(const unsigned char *p)
{
return ((unsigned long)p[0] << 24) |
((unsigned long)p[1] << 16) |
((unsigned long)p[2] << 8) |
(unsigned long)p[3];
}
// 解析GNU "/"符号索引
static void parse_gnu_armap(FILE *fp, long map_data_start, long map_sz)
{
unsigned char buf[4];
fseek(fp, map_data_start, SEEK_SET);
// 读取符号总数N(大端)
if (fread(buf, 1, 4, fp) != 4)
return;
unsigned int sym_cnt = be32_to_host(buf);
printf("==== GNU AR符号索引(/)解析开始 ====\n");
printf("符号总数: %u\n", sym_cnt);
// 读取N个成员偏移
unsigned long *offsets = malloc(sym_cnt * sizeof(unsigned long));
for (unsigned i = 0; i < sym_cnt; i++)
{
fread(buf, 1, 4, fp);
offsets[i] = be32_to_host(buf); //读取文件偏移
}
// 读取符号名串表
char symname[1024];
unsigned str_off = 0;
for (unsigned i = 0; i < sym_cnt; i++) //读取文件名称, 符号名即是文件名称
{
fseek(fp, map_data_start + 4 + sym_cnt*4 + str_off, SEEK_SET);
fgets(symname, sizeof(symname), fp);
size_t len = strlen(symname);
if (len && symname[len-1] == '\0') len--;
str_off += len + 1;
printf("符号: %-12s -> 归档成员偏移: 0x%lx\n", symname, offsets[i]);
}
free(offsets);
printf("==== 符号索引解析结束 ====\n\n");
}
int main(int argc, char **argv)
{
if (argc != 2)
{
printf("usage: %s libtest.a\n", argv[0]);
return 1;
}
FILE *fp = fopen(argv[1], "rb");
if (!fp)
{
perror("fopen");
return 1;
}
//magic 固定长度为8 "!<arch>."
char magic[8];
size_t rd = fread(magic, 1, 8, fp);
if (rd != 8 || memcmp(magic, ARMAG, 8) != 0)
{
printf("非合法AR归档文件\n");
fclose(fp);
return 1;
}
printf("AR全局魔数校验通过: !<arch>\\n\n");
long cur_file_offset = 8;
struct ar_hdr hdr;
while (1)
{
rd = fread(&hdr, 1, sizeof(struct ar_hdr), fp);
if (rd != sizeof(struct ar_hdr))
break;
if (memcmp(hdr.ar_fmag, ARFMAG, 2) != 0)
{
printf("成员头archive file magic校验失败,终止解析\n");
break;
}
char name[17] = {0};
memcpy(name, hdr.ar_name, 16);
trim_space(name, 16);
char size_str[11] = {0};
memcpy(size_str, hdr.ar_size, 10);
trim_space(size_str, 10);
long data_sz = atol(size_str);
long hdr_sz = sizeof(struct ar_hdr);
long file_data_start = cur_file_offset + hdr_sz;
printf("当前归档全局偏移: %ld\n", cur_file_offset);
printf("成员名称:[%s]\n", name);
printf("数据长度:%ld字节\n", data_sz);
// 命中GNU符号索引成员 /
if (strcmp(name, "/") == 0)
{
parse_gnu_armap(fp, file_data_start, data_sz);
fseek(fp, file_data_start, SEEK_SET); //恢复文件指针
}
// 2字节对齐补齐
long skip = data_sz;
if (skip & 1) skip++;
cur_file_offset += hdr_sz + skip; //跳到下一个文件头
fseek(fp, skip, SEEK_CUR);
}
printf("归档完整解析完毕,当前归档全局偏移:%ld\n", cur_file_offset);
fclose(fp);
return 0;
}
2.1 实验结果
cpp
$ ./ar_parse libtest.a
AR全局魔数校验通过: !<arch>\n
当前归档全局偏移: 8
成员名称:[/]
数据长度:24字节
==== GNU AR符号索引(/)解析开始 ====
符号总数: 2
符号: func -> 归档成员偏移: 0x5c
符号: func1 -> 归档成员偏移: 0x560
==== 符号索引解析结束 ====
当前归档全局偏移: 92
成员名称:[test.o/]
数据长度:1224字节
当前归档全局偏移: 1376
成员名称:[test1.o/]
数据长度:1224字节
归档完整解析完毕,当前归档全局偏移:2660
配合nm -s libtest.a, 再配合010editor 查看二进制文件, 可对它的符号索引区有清晰的认识
对ar文件打包的格式也有了认识, 不仅知其然,而其知其所以然.