ar 工具及 ar 库格式研究

ar 工具及 ar 库格式研究
author: hjjdebugdate: 2026年 07月 12日 星期日 11:44:14 CSTdescrip: ar 工具及 ar 库格式研究.文章目录1. 用ar 工具及 nm 工具来了解 ar 库文件2. 用自编简单工具来了解ar 库文件2.1 实验结果ar 是文档工具, 与tar, cpio 有类似的性质,但主要用在将.o文件打包成.a文件,同时还会生成符号索引.我一直对这个所谓的符号索引不知所云, 所以决定花点时间认真研究一下:1. 用ar 工具及 nm 工具来了解 ar 库文件准备2个文件test.c, test1.c$ cat test.cvoidfunc(){}$ cat test1.cvoidfunc1(){}生成库文件, 一个带索引(默认带索引),一个不带索引,如下$ 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 库文件 查看, 看到了区别, 如下:$ 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#includestdio.h#includestdlib.h#includestring.h#includectype.h#defineARMAG!arch\n#defineARFMAG\nstructar_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(intilen-1;i0;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_cntbe32_to_host(buf);printf( GNU AR符号索引(/)解析开始 \n);printf(符号总数: %u\n,sym_cnt);// 读取N个成员偏移unsignedlong*offsetsmalloc(sym_cnt*sizeof(unsignedlong));for(unsignedi0;isym_cnt;i){fread(buf,1,4,fp);offsets[i]be32_to_host(buf);//读取文件偏移}// 读取符号名串表charsymname[1024];unsignedstr_off0;for(unsignedi0;isym_cnt;i)//读取文件名称, 符号名即是文件名称{fseek(fp,map_data_start4sym_cnt*4str_off,SEEK_SET);fgets(symname,sizeof(symname),fp);size_t lenstrlen(symname);if(lensymname[len-1]\0)len--;str_offlen1;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*fpfopen(argv[1],rb);if(!fp){perror(fopen);return1;}//magic 固定长度为8 !arch.charmagic[8];size_t rdfread(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_offset8;structar_hdrhdr;while(1){rdfread(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_szatol(size_str);longhdr_szsizeof(structar_hdr);longfile_data_startcur_file_offsethdr_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字节对齐补齐longskipdata_sz;if(skip1)skip;cur_file_offsethdr_szskip;//跳到下一个文件头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文件打包的格式也有了认识, 不仅知其然,而其知其所以然.