OS39.5.【Linux】分析ar命令生成的归档文件的格式

目录

1.知识回顾

2.archives是什么

3.ar命令打包非链接文件

ar命令特别说明

准备工作

十六进制分析test.a

归档文件头(魔数)

文件内容

元数据

[文件名(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]))

ar_fmag

所有元数据一览


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源代码:

https://www.tuhs.org/cgi-bin/utree.pl?file=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 | | | | | | |

相关推荐
XiaoHu02072 小时前
Linux关于进程(第一弹)
linux·运维·服务器
YoungHong19922 小时前
[教程] Linux 服务器无 Root (Sudo) 权限安装 CUDA Toolkit 终极指南
linux·运维·服务器
麒qiqi2 小时前
【Linux 进程间通信】信号通信与共享内存核心解析
java·linux·算法
Studying 开龙wu2 小时前
Linux 系统中apt-get 和 pip命令有什么区别
linux·运维·pip
OliverH-yishuihan2 小时前
下载、安装和设置 Linux 工作负载
linux·运维·服务器
MediaTea2 小时前
思考与练习(第四章 程序组成与输入输出)
java·linux·服务器·前端·javascript
松涛和鸣2 小时前
35、Linux IPC进阶:信号与System V共享内存
linux·运维·服务器·数据库·算法·list
惊鸿一博2 小时前
Linux文件同步/镜像—rsync
linux·运维
SunnyDays10112 小时前
Python 实现 PDF 文档压缩:完整指南
linux·开发语言·python