10101010等于4文件魔数什么意思

这是在www.rpm.org上看到的一篇文章

    以下描述的关于RPM文件的格式细节只在当时写此文的时候是准确的,以下三点请一定要注意:

    2.如果想要对一个RPM文件进行操作强烈建议你使用rpmlib提供的库函数来操作,为什么看第一条!

    3.本附录描述的是最近的RPM文件格式的细节,当时叫做version 3你可以使用shell命令:file,来查看一个RPM文件的文件格式的版本

    每一个RPM文件包在逻辑上可以被分成以下独立的四个分区。他们分别是:

    RPM文件是使用网络字节序来存储在磁盘上的如果需要,RPM会自动把它们转成主机字节序当读取RPM文件包的时候。让我们来看一看每一个分区的组成先从lead分区开始。

RPM包文件的第一分区就是lead所謂的第一,可以认为是文件的最靠前的一分区文件内容这分区以前是被用来存储一些信息,这分区信息被RPM内部使用但是到了现在,lead分區的唯一目的是使得RPM文件更容易被外界识别举例来说的话,file(1)命令就是读取了lead分区从而识别一个文件是不是rpm文件。Lead分区所包含的所有信息全部被header分区重复或者取代了。

让我们来看看一个真是的RPM包文件检查lead分区的所有数据的组成。在接下来的展示中最左边的列表示从攵件开头的字节偏移数,十六进制表示的八个由四个字符组成的组代表十六进制的值——每个组分别代表两个字节。最右边的列表示这些值对应的ASCII字符但是当一个值对应的ASCII字符是不可打印的,我们用“.”来代替下面就是rpm-2.2.1-1.i386.rpm这个文件的前32个字节的详细内容:

    前4个字节(edab eedb)僦是magic值(插一句,magic值一般翻译成魔数是一个固定的数值,用来标识一种特定的东西)在RPM中,这个魔数就是用来鉴别一个文件是不是RPM文件file(1)命令和RPM都是用这魔数来判断一个RPM文件是否合法有效。(也就是说看一个RPM文件是否有效,就读取前四个字节看看是不是edab eedb。推而广之那么其他类型的文件格式,有可能就用另外的一个魔数来标识自己例如FLV文件,前3个字节就是FLV的ASCII表里面对应的数值)

    接下来的2个字节(0300)代表RPM文件格式的版本在本例中,文件的主版本号是3次版本号是0,所以这个RPM文件的版本号就是3.0

    再往下2个字节决定了RPM文件的哪种类型嘚。目前为止有两种类型:

    二进制文件就是说,RPM是由已经编译好的二进制文件打包而来的跟目标OS的架构有关系,后面2个字节会指示这個目标OS的架构究竟是什么

    源文件就是说,RPM是由未编译的源代码文件打包而来的这样就跟OS的架构无关了,在任意架构的OS上都可以用一般来说。

    在本例中这个文件是二进制打包的RPM文件。

    然后继续往下2个字节(0001)用来存储这个RPM包对应的目标架构的代号。在本例中是1代表i386架构。当然如果RPM是由源代码文件打包而来的,那么这2个字节就可以忽略了你也知道,源代码对于平台是无关紧要的一般在任意平囼都可以进行编译,然后生成可执行的二进制文件或者库

    接下来的66个字节(本例中从d开始)包含的是这个包的名字(不是文件名,切记)名字必须以'\0'来结尾,意味着真正的名字长度其实是65字节名字的模版如下:name-version-release-sytle。在本例中我们可以从右边那几列中读取到这个包的名芓:rpm-2.2.1-1。

    让我们跳过这些填充的'\0'字节直接看到偏移这一行,会看到2个字节(0001):

    这2个字节代表构建该RPM包的操作系统的代号在本例中是1,玳表Linux正如前面说的“架构-代号”的转换,这里的“操作系统-代号”的对应关系可以在/usr/lib/rpmrc中找到

3中新加入的一种类型。好了0005之后的内容僦是属于signature分区的了,所以lead一共是96个字节长但是在讲解signature分区之前,我们需要先讨论几个附加的细节的了,所以lead分区一共是96个字节长但昰在讲解signature分区之前,我们需要先讨论几个附加的细节

渴求:一种新的RPM数据结构

通过回顾之前上面提到的那个lead分区定义的C语言结构体,然後再对照着真实RPM文件的每个字节来看想要理解出lead分区存储的数据显得太繁琐了。但是从编程的角度来看这又显得更容易来操作lead分区中嘚数据——只需要简单的使用该结构体中的成员即可。但是终究这是一个困恼的问题所以因为这个原因,lead分区现在已经不被RPM内部所使用叻定义的C语言结构体,然后再对照着真实RPM文件的每个字节来看想要理解出lead分区存储的数据显得太繁琐了。但是从编程的角度来看这叒显得更容易来操作lead分区中的数据——只需要简单的使用该结构体中的成员即可。但是终究这是一个困恼的问题所以因为这个原因,lead分區现在已经不被RPM内部所使用了

Lead分区,一个被抛弃的数据结构

    究竟问题在哪为何lead分区不再被RPM文件内部所使用呢?答案很简单:可扩展性差在文件中定义一个C语言结构体这种技术不是很具有扩展性,让我们举个例子说明一下!

    请翻到之前介绍lead那个章节回顾一下那个C语言嘚结构体定义。打个比方说有个名字很长很长的软件诞生了,名字超长至少超过66个字节了。意味着结构体里面的66个字节的成员name已经不足以hold住这个软件的名字了

    这时我们该怎么办?我们当然可以修改之前定义的结构体定义然后把name成员的长度加到100字节。感觉好像你解决叻这个问题但与此同时你也创造了一个新版本的RPM文件,此时我们又面临两个新的问题:

1.任何新版本RPM程序创建的RPM包文件不能被老版本的RPM程序来解析安装了;

2.任何老版本的RPM程序不能解析安装新版本RPM程序创建的RPM包文件;

    情况不妙啊!!理论上来说无论如何,我们不应该来使用這种“写死”的方式来设计文件格式好的文件格式应该在不损失兼容性的前提下,还允许我们完成以下三件事:

1.在文件格式中新增额外嘚数据分区;

2.改变已存在的数据分区的大小;

3.给数据分区重新排序;

    听上去感觉很难的样子但是已经有一种解决方案了。

    这个方案就是紦从文件中检索信息的方式给标准化、规范化通过创造设计一种优秀的文件数据结构,包含易于检索的有关信息并且在物理上在文件數据结构中隔离那些信息。

    当想要查找某个数据分区时通过那些易于检索的信息(这些信息有点儿类似于一本书的目录),从而得知数據分区的位置那么这样做带来的好处就是每个数据分区可以随意的放置于RPM文件中的任意位置,并且某个数据分区自己的格式是可以改变嘚

    头结构就是RPM程序中关于之前那个标准化、规范化从文件中简便检索信息的解决方案。头结构的唯一目标就是包含零或者数据一个RPM文件可以包含不止一个这种结构。事实上现在的RPM文件中包含两个这种结构,signature分区和header分区(请不要混淆头结构是头分区,可以认为头结构昰类而签名分区和头部分区是这个类对应的两个实例对象)

    每一个头结构由三个section组成。第一个section叫做头结构的头部(header structure header不得不说,这样起洺字真心让人晕)头部是用来确定这个头结构的起始位置、大小、包含的数据项的数量。

    第二个叫做index(索引部分)索引部分包含一个戓者多个入口(entry),每一个索引部分的入口包含了相关的信息一个具体的数据项,以及一个指向数据项的指针

    最后一个叫做store(存储部汾)。真正的数据项就是存储在存储部分的存储部分的数据被尽可能紧凑的打包在一起。数据存储的顺序是无关紧要的——这种格式和lead汾区中用到的C语言结构体大相径庭吧!

    让我们深入的来看一下头结构的真实格式吧从头结构的头部分开始:

    头结构的头部总是以一个3字節长的魔数开始的——8e ad e8。紧接着是一个1字节的版本号再接着4个字节是保留字节,预留给未来扩展使用的在保留字节之后有一个4字节的數字来指示这个头结构中一共有多少个索引入口存在,然后又有一个4字节的数字来指示这个头结构一共有多大

    头结构的索引部分是由0个戓者多个索引入口组成,每一个入口都是16字节长入口的前4个字节包含一个tag(标签)——一个数值,用来明确该入口指向的数据的类型是哪种标签的值依赖于头结构在RPM包文件中的位置。文章的后面会有一张表说明了标签的每个取值代表什么意思。

    标签后面跟着一个4字节嘚type(类型)也是一个数值,用来描述指向的数据的格式type不同于tag,type的值在不同的头结构中都是一样的意义下面是一张表,描述了type的值囷对应的数据的格式:

    上表中有些类型需要解释说明一下STRING类型就是一种普通的以null结尾的字符串,而STRING_ARRAY是一组字符串最后一点,BIN类型的数據是一些二进制数据BIN通常用来识别比INT长,但又是不可打印的数据的

然后接下来又是一个4字节的offset(偏移),它存储了数据相对于store部分的開始的位置的偏移我们稍后会讨论store部分的。

最后还有一个4字节的count(计数)它存储了这个入口所指向的数据项的个数,对于不同的类型嘚数据项count的意义会有一些不同。STRING类型count总是1;对于STRING_ARRAY来说,count的值等于其中包含的字符串的个数

    store部分就是头结构中存储数据的地方。根据index.typeΦ的值的不同下面有三点细节我们应该记住:

    2.INT类型的数据,每个INT*的数据都是按照它的类型的自然边界存储的例如,一个INT64是存储在一个8芓节的范围之内INT16存储在2个字节的范围之内。

    3.所有的数据都是用网络字节序存储的(切记)

    Signature分d区在RPM文件中紧接着lead分区。它包含了一些信息这些信息可以被用来验证该文件的完整性,另外(选择性的啦)还可以用来验证绝大对数RPM包文件的真实性、可靠性Signature分区是用头结构來实现的。

    你可能注意到了我上面用了“绝大多数”这个词其实signature分区的内容完全就是依赖于header分区和archive分区的。当创建签名相关的信息时并鈈包括lead分区和signature分区并且后续也不会基于签名信息对这两个分区进行任何检查。

似乎看起来这是一个RPM程序的疏忽不!对于lead分区来说,由於这个分区是用来给别人方便的识别RPM文件的所以任何对此分区的改变,如果说最坏的情况会使得这个文件成为不被RPM程序识别的文件(假设你把lead分区的前4个魔数字节改掉,可想而知)同样的,对于signature分区来说任何对其的改变将会使得RPM程序认为此文件的不完整、不可靠,洇为签名信息被改了与其原来的值不同了。

    我们切换到了偏移位置因为lead分区是96字节(定长),那么就是第97字节了

    前3个字节一看便知,头结构的魔数(8e ad e8)味着头结构从这开始。然后接下来的1个字节是头结构的版本号(01)正如我之前讨论过的,接下来的4个是保留字节那么()也没什么可解释的了。接下来的4个字节是一个数值(网络字节序)()就表示接下来的index这个section中包含有3个index entry入口。再下来的4个字節就告诉我们这个头结构(在这里是signature头结构)一共有(0000 00ac)个字节也就是十六进制的ac,就是十进制的172字节

    从上面可以看出,tag的只是十六進制的()也就是十进制的1000,不妨看一下rpm的源文件中的“lib/signature.h”(我在rpm-4.11的源代码中找到下列信息是在“lib/rpmtag.h”中)我们会发现类似于下面这些東西:

    所以从这个映射就可以看出,我们正在研究的index entry指向的数据是SIZE类型的(SIZE具体是什么意思,我也不清楚接着往下看)

    接下来的4个字節就是type,()意味着这个index entry指向的store区域存储的是INT32类型的数据。先暂时跳过接下来的4个字节offset(之后有详细说明的)直接看到最后4个字节(),就是说这个index entry对应的store存储了“1”个type类型(INT32)的数据

    现在让我们回到之前跳过的那4个字节,offset()偏移是0,这个好理解但是从哪数起呢?答案就是从本头结构的store的开始数起!本头结构那就是signature分区啦,很简单那本头结构的store section在哪开始呢?想一下在header section中提到,本头结构一囲有3个index entry然后每一个index entry的长度是固定的16字节,那header section也是16字节固定的那好,从本头结构的开始也就是那个魔数“8e ad e8”的偏移()开始,跳跃(16 + 16 * 3)个字节那么我们就到了这个位置了:

entry的tag值告诉我们,它所指向的数值的意义是SIGTAG_SIZE从字面上看起来是大小的意思,那究竟指的是哪个大尛呢是指的该RPM文件的大小吗?先转换成十进制得到281,679。然后我们看看该RPM文件的大小:

    下面展示第二个index entrytag值是十进制的1001,也就是表示指向嘚数据是一个MD5摘要字符串;type的值是7也就是BIN类型的;count是0x10,也就是说BIN类型的字符串是16个字节长

0004),它是offset意思就是第二个entry的数据是从store开始,再数4个字节然后才是对应的数据。这个MD5串到(5357)结束一共16个字节长,它是使用MD5算法对于header分区和archive分区产生的验证摘要信息。

byte有了咜们就填充了signature分区,是的signature分区的数据8字节对其所以下一分区(header分区)就是从开始了。这个150和之前说的150是一个意思嘛对,就是这样!所鉯signature分区中的SIGTAG_SIZE的tag就是指header分区加上archive分区的大小了

Header分区包含了所有RPM包文件可用的信息。它里面的entry将会包括包文件的名字版本,文件列表就想signature分区一样,header分区也是基于头结构实现的但是又和signature分区有点儿不同,signature分区一般只包含3个意义的tag例如大小、MD5签名串、PGP签名串,而header分区可能包含60个以上的不意义tagtag值对应的所有编号的意义将会在Header tag listing里面列出来(下面)。有一点要注意这个表里面的定义可能会很频繁的变动。

    朂简单的方式来找寻header分区的开始位置就是检索第二个头结构的魔数(8e ad e8)了

    下面看看第一个tag指向的STRING数据到底长什么样,我们应该跳到(0x160 + 21行)那就是0x370的偏移位置,请看:

    由于我们之前说过STRING类型的字符串是以null结尾的所以我们应该继续往后看,直到看见00为止很好,00在本行(6d00)中的(00)就是null,那么文件名就是“rpm”

    0x509是从(002f)中的2f开始的,也就是“/”然后一直读,读到第一个null就是(6d00)中的00,结果翻译成ASCII码僦是“/bin/rpm”这是第一个文件,接下来还有0x18 - 0x1个

    实际上还有很多tag等着我们来解析,但是就不一一举例了因为方法很相似。

    这就是上面提到過的tag的值和意义的映射表,在lib/rpmlib.h中(新版本可能变化了)

    Header分区的后面就是archive分区,该分区存储的真正的那个打包文件Archive分区是用GNU的zip软件压縮而成的,不妨来看一眼archive分区的真正样子:

在这个例子中,archive从偏移0xd43开始不妨对照/usr/lib/magic来看看,gzip生成的压缩文件的头部魔数是(1f8b)和偏移0xd43恰好吻合。接下来的1个字节(08)这个标志位代表gzip压缩的时候使用的“deflation”设定然后继续往后看,直到archive分区的第8个字节(02)这个表示gz压缩嘚时候使用了最大压缩比的设定。接下来的1个字节表示使用gzip的操作系统是哪种(03)表示Unix-like的操作系统。

    剩下的RPM文件内容就是被压缩的归档攵件了当archive分区被解压之后,就成了一个常见的cpio格式的归档文件


我要回帖

更多关于 10101010等于4 的文章

 

随机推荐