目录
[文件名(char ar_name[16])](#文件名(char ar_name[16]))
[时间戳(char ar_date[12])](#时间戳(char ar_date[12]))
[UID(char ar_uid[6])](#UID(char ar_uid[6]))
[GID(char ar_gid[6])](#GID(char ar_gid[6]))
[拥有者、所属组、其他人的八进制权限(char ar_mode[8])](#拥有者、所属组、其他人的八进制权限(char ar_mode[8]))
[文件大小(char ar_size[10])](#文件大小(char ar_size[10]))
1.知识回顾
之前在OS39.【Linux】动态库和静态库 自制静态库文章提到过使用ar -rc命令生成静态库,即将所有o文件的集合(不含main函数的源文件生成的o文件)打包(ar命令)为静态库
其实ar命令不仅仅能打包o文件,准确来说,ar命令的作用是"create, modify, and extract from archives",即创建、修改、解压归档文件
博主比较好奇,就研究了一下ar归档文件的格式
2.archives是什么
百度翻译对archive的解释:

man手册对archives的描述:
The GNU ar program creates, modifies, and extracts from archives. An ++archive++ is a single file holding a collection of other files in a structure that makes it possible to retrieve the original individual files (called members of the archive).
archives定义:
1.归档是一个单一文件,以一种结构包含一组其他文件
2.这种结构可以检索出(retrieve)原始各个文件(称为++归档成员文件++)
man手册对文件内容的描述:
The original files' contents, mode (permissions), timestamp, owner, and group are preserved in the archive, and can be restored on extraction.
可以看出"归档是一个单一文件,以一种结构包含一组其他文件 "中的结构还含有其他内容,例如模式(权限),时间戳,所有者,所属组,还有文件的原始内容,说明: 归档文件中含有的其他文件没有经过压缩
3.ar命令打包非链接文件
从以上分析可以看出ar命令不仅仅可以打包链接文件,还可以打包非链接文件
ar命令特别说明
ar命令不是从Linux才有的,其实早在Unix就已经出现了ar命令\,可以参考Unix早期版本的源代码:
Unix版本树:
https://www.tuhs.org/cgi-bin/utree.pl
Unix V10源代码:
准备工作
现将3个文件打包为一个归档文件
文件1: test.txt
bash
teststring
文件2: test.bin
四字节的十六进制数据
bash
0xAA 0xBB 0xCC 0xDD
bash
printf '\xAA\xBB\xCC\xDD' > test.bin
文件3: test.c
cpp
int main()
{
return 0;
}

修改test.bin的拥有者为root:
bash
sudo chown root test.bin

test.bin添加可执行权限:
bash
sudo chmod a+x test.bin

修改test.txt的所属组为root:
bash
sudo chgrp root test.txt

打包为test.a:
bash
ar -rc test.a test.txt test.c test.bin

使用file命令分析test.a:
test.a是一个ar命令生成的归档文件

十六进制分析test.a
十六进制编辑器打开test.a,这里使用Windows下的HxD:

归档文件头(魔数)
文件开头的字节就是归档文件的魔数,用来表明这是一个归档文件
是!<arch>还是!<arch>加上后面的0x0A呢? 换句话说,是下图的哪种情况?

由于ar命令是Unix引入的,那么这要查Unix源码了,在Unix V10的/cmd/asd/ar.h中:
cpp
#define ARMAG "!<arch>\n"
#define SARMAG 8
#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];
};
这个ARMAG是ARchive MAGic number(归档文件魔数)的缩写,其为"!<arch>\n",需要带上\n,而且\n的ASCII码为0x0A
那么这个图是对的:

文件内容
原来没有归档时的文件内容:



对应到下图红框部分:

注意:所有数据按偶字节边界 对齐,即每个成员(归档文件头 + 归档文件内容)的起始偏移必须是2的倍数,因此某些情况下,文件内容的末尾会添加0x0A用于对齐,就像上图的那样
元数据
上方代码的struct ar_hdr结构体ar_hdr是archive_header的缩写,即归档文件头,存储这每个文件的元数据(meta data),有心的读者可能会去算这个结构体的大小:
cpp
#include <iostream>
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];
};
int main()
{
std::cout<<sizeof(ar_hdr);
return 0;
}
运行结果:

占60字节,对应到test.a,从文件名开始,到文件内容前,统计字节数:
0x43-0x08+1==0x3C==十进制的60

0x8B-0x50+1==0x3C==十进制的60

0xE5-0xAA+1==0x3C==十进制的60

发现从文件名开始,到文件内容前这部分占的字节数都是60,恰为struct ar_hdr结构体的大小
结论: 显然这部分内容就是每个文件的struct ar_hdr结构体实例化后的内容
有了这个结构体就能解析struct ar_hdr结构体的各个字段了
文件名(char ar_name[16])
下图黄框部分:

Unix源代码的/cmd/ar.c是这样打印归档文件中的各个文件名的:
cpp
char *
trim(s)
char *s;
{
register char *p1, *p2;
for(p1 = s; *p1; p1++)
;
while(p1 > s) {
if(*--p1 != '/')
break;
*p1 = 0;
}
p2 = s;
for(p1 = s; *p1; p1++)
if(*p1 == '/')
p2 = p1+1;
return(p2);
}
结论: 文件名的结尾是/
时间戳(char ar_date[12])
下图紫框部分:

UID(char ar_uid[6])
下图橙框部分:

GID(char ar_gid[6])
下图灰框部分:

拥有者、所属组、其他人的八进制权限(char ar_mode[8])
下图蓝框部分:

文件大小(char ar_size[10])
文件大小是以ASCII码的形式呈现的,下图棕框部分:

ar_fmag
这个ar_fmag是每个归档文件头末尾必须要有的,在https://unix.org/whitepapers/ar-format.html网站中有说明:

例如在Unix V10源码的/cmd/ar.c的getdir()函数中(截取部分代码):
cpp
if (strncmp(arbuf.ar_fmag, ARFMAG, sizeof(arbuf.ar_fmag))) {
fprintf(stderr, "ar: malformed archive (at %ld)\n", lseek(af, 0L, 1));
done(1);
}
会发现strncmp将arbuf.ar_fmag和宏ARFMAG比较,而ARFMAG会被替换为0x60 0x0A,也就是/cmd/asd/ar.h中定义的:
cpp
#define ARFMAG "`\n"
所有元数据一览
下图的每一格中的数据代表一个字节,用十六进制表示
|--------|--------|--------|--------|--------|--------|--------|--------|--------|--------|--------|--------|--------|--------|--------|--------|
| 21 | 3C | 61 | 72 | 63 | 68 | 3E | 0A | 74 | 65 | 73 | 74 | 2E | 74 | 78 | 74 |
| 2F | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 30 | 20 | 20 | 20 | 20 | 20 | 20 | 20 |
| 20 | 20 | 20 | 20 | 30 | 20 | 20 | 20 | 20 | 20 | 30 | 20 | 20 | 20 | 20 | 20 |
| 36 | 34 | 34 | 20 | 20 | 20 | 20 | 20 | 31 | 31 | 20 | 20 | 20 | 20 | 20 | 20 |
| 20 | 20 | 60 | 0A | 74 | 65 | 73 | 74 | 73 | 74 | 72 | 69 | 6E | 67 | 0A | 0A |
| 74 | 65 | 73 | 74 | 2E | 63 | 2F | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 |
| 30 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 30 | 20 | 20 | 20 |
| 20 | 20 | 30 | 20 | 20 | 20 | 20 | 20 | 36 | 34 | 34 | 20 | 20 | 20 | 20 | 20 |
| 32 | 39 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 60 | 0A | 69 | 6E | 74 | 20 |
| 6D | 61 | 69 | 6E | 28 | 29 | 0A | 7B | 0A | 20 | 20 | 20 | 20 | 72 | 65 | 74 |
| 75 | 72 | 6E | 20 | 30 | 3B | 0A | 7D | 0A | 0A | 74 | 65 | 73 | 74 | 2E | 62 |
| 69 | 6E | 2F | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 30 | 20 | 20 | 20 | 20 | 20 |
| 20 | 20 | 20 | 20 | 20 | 20 | 30 | 20 | 20 | 20 | 20 | 20 | 30 | 20 | 20 | 20 |
| 20 | 20 | 36 | 34 | 34 | 20 | 20 | 20 | 20 | 20 | 34 | 20 | 20 | 20 | 20 | 20 |
| 20 | 20 | 20 | 20 | 60 | 0A | AA | BB | CC | DD | | | | | | |