ar 工具及 ar 库格式研究

📅 2026/7/13 5:55:06 👁️ 阅读次数 📝 编程学习
ar 工具及 ar 库格式研究

author: hjjdebug
date: 2026年 07月 12日 星期日 11:44:14 CST
descrip: ar 工具及 ar 库格式研究.


文章目录

  • 1. 用ar 工具及 nm 工具来了解 ar 库文件
  • 2. 用自编简单工具来了解ar 库文件
    • 2.1 实验结果

ar 是文档工具, 与tar, cpio 有类似的性质,但主要用在将.o文件打包成.a文件,
同时还会生成符号索引.
我一直对这个所谓的符号索引不知所云, 所以决定花点时间认真研究一下:

1. 用ar 工具及 nm 工具来了解 ar 库文件

  1. 准备2个文件test.c, test1.c
$ cat test.cvoidfunc(){}$ cat test1.cvoidfunc1(){}
  1. 生成库文件, 一个带索引(默认带索引),一个不带索引,如下
$ 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//不带索引
  1. 查看区别.
    $ ar tv <库文件> 命令查看, 看不到区别
    $ nm <库文件> 命令查看, 也看不到区别
    $ nm -s <库文件> 查看, 看到了区别, 如下:
$ nm-s libtest.a 归档索引: func in test.o func1 in test1.o test.o:0000000000000000T func test1.o:0000000000000000T func1 $ nm-s libtest1.a test.o:0000000000000000T func test1.o:0000000000000000T func1

查帮助 nm -s 就是 打印armap 的意思, 就是打印文档的索引符号.
但我感觉还是对索引符号的概念不太清楚.
要想深刻的理解索引符号,需要来一段代码,并认真研读.
索引符号是函数名称与文件偏移构成的表.
这样当你知道函数名称时,通过这个表就知道在库中的偏移,为gcc 编译连接提供了信息.

2. 用自编简单工具来了解ar 库文件

下面为是对ar 库的解析文件, 很短,读懂此文件即了解了.a文件的构成.

$ cat ar_parse.c#include<stdio.h>#include<stdlib.h>#include<string.h>#include<ctype.h>#defineARMAG"!<arch>\n"#defineARFMAG"`\n"structar_hdr{charar_name[16];charar_date[12];charar_uid[6];charar_gid[6];charar_mode[8];charar_size[10];charar_fmag[2];};staticvoidtrim_space(char*buf,intlen){for(inti=len-1;i>=0;i--){if(buf[i]==' ')buf[i]='\0';elsebreak;}}// 大端4字节转本机longstaticunsignedlongbe32_to_host(constunsignedchar*p){return((unsignedlong)p[0]<<24)|((unsignedlong)p[1]<<16)|((unsignedlong)p[2]<<8)|(unsignedlong)p[3];}// 解析GNU "/"符号索引staticvoidparse_gnu_armap(FILE*fp,longmap_data_start,longmap_sz){unsignedcharbuf[4];fseek(fp,map_data_start,SEEK_SET);// 读取符号总数N(大端)if(fread(buf,1,4,fp)!=4)return;unsignedintsym_cnt=be32_to_host(buf);printf("==== GNU AR符号索引(/)解析开始 ====\n");printf("符号总数: %u\n",sym_cnt);// 读取N个成员偏移unsignedlong*offsets=malloc(sym_cnt*sizeof(unsignedlong));for(unsignedi=0;i<sym_cnt;i++){fread(buf,1,4,fp);offsets[i]=be32_to_host(buf);//读取文件偏移}// 读取符号名串表charsymname[1024];unsignedstr_off=0;for(unsignedi=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");}intmain(intargc,char**argv){if(argc!=2){printf("usage: %s libtest.a\n",argv[0]);return1;}FILE*fp=fopen(argv[1],"rb");if(!fp){perror("fopen");return1;}//magic 固定长度为8 "!<arch>."charmagic[8];size_t rd=fread(magic,1,8,fp);if(rd!=8||memcmp(magic,ARMAG,8)!=0){printf("非合法AR归档文件\n");fclose(fp);return1;}printf("AR全局魔数校验通过: !<arch>\\n\n");longcur_file_offset=8;structar_hdrhdr;while(1){rd=fread(&hdr,1,sizeof(structar_hdr),fp);if(rd!=sizeof(structar_hdr))break;if(memcmp(hdr.ar_fmag,ARFMAG,2)!=0){printf("成员头archive file magic校验失败,终止解析\n");break;}charname[17]={0};memcpy(name,hdr.ar_name,16);trim_space(name,16);charsize_str[11]={0};memcpy(size_str,hdr.ar_size,10);trim_space(size_str,10);longdata_sz=atol(size_str);longhdr_sz=sizeof(structar_hdr);longfile_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字节对齐补齐longskip=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);return0;}

2.1 实验结果

$./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文件打包的格式也有了认识, 不仅知其然,而其知其所以然.