漫谈兼容内核之九ELF映像的装入二Word格式文档下载.docx

上传人:b****8 文档编号:22426598 上传时间:2023-02-04 格式:DOCX 页数:23 大小:32.71KB
下载 相关 举报
漫谈兼容内核之九ELF映像的装入二Word格式文档下载.docx_第1页
第1页 / 共23页
漫谈兼容内核之九ELF映像的装入二Word格式文档下载.docx_第2页
第2页 / 共23页
漫谈兼容内核之九ELF映像的装入二Word格式文档下载.docx_第3页
第3页 / 共23页
漫谈兼容内核之九ELF映像的装入二Word格式文档下载.docx_第4页
第4页 / 共23页
漫谈兼容内核之九ELF映像的装入二Word格式文档下载.docx_第5页
第5页 / 共23页
点击查看更多>>
下载资源
资源描述

漫谈兼容内核之九ELF映像的装入二Word格式文档下载.docx

《漫谈兼容内核之九ELF映像的装入二Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《漫谈兼容内核之九ELF映像的装入二Word格式文档下载.docx(23页珍藏版)》请在冰豆网上搜索。

漫谈兼容内核之九ELF映像的装入二Word格式文档下载.docx

各个程序头表项当然也是数据结构,这是对映像文件中各个“节(Segment)”的(结构性)描述。

从映像装入的角度看,一个映像是由若干个Segment构成的。

有些Segment需要被装入、即被映射到用户空间,有些则不需要被装入。

在前一篇漫谈中读者已经看到,只有类型为PT_LOAD的Segment才需要被装入。

所以,映像装入的过程只“管”到Segment为止。

而从映像的动态连接、重定位(即浮动)、和启动运行的角度看,则映像是由若干个“段(Section)”构成的。

我们通常所说映像中的“代码段”、“数据段”等等都是Section。

所以,动态连接和启动运行的过程所涉及的则是Section。

一般而言,一个Segment可以包含多个Section。

其实,Segment和Section都是从操作/处理的角度对映像的划分;

对于不同的操作/处理,划分的方式也就可以不同。

所以,读者在后面将会看到,一个Segment里面也可以包含几个别的Segment,这就是因为它们是按不同的操作/处理划分的、不同意义上的Segment。

Section也是一样。

在Linux系统中,(应用软件主体)目标映像本身的装入是由内核负责的,这个过程读者已经看到;

而动态连接的过程则由运行于用户空间的“解释器”负责。

这里要注意:

第一,“解释器”是与具体的映像相连系的,其本身也有个映像,也需要被装入。

与目标映像相连系的“解释器”也是由内核装入的,这一点读者也已看到。

第二,动态连接的过程包括了共享库映像的装入,那却是由“解释器”在用户空间实现的。

本来,看了内核中与装入目标映像有关的代码以后,应该接着看“解释器”的代码了。

但是后者比前者复杂得多,也繁琐得多,原因是牵涉到许多ELF和ABI的原理和细节,所以有必要先对ELF动态连接的原理作一介绍。

明白了有关的原理和大致的方法以后,具体的代码实现倒在其次了。

前面讲过,Linux提供了两个很有用的工具,即readelf和objdump。

下面就用这两个工具对映像/usr/local/bin/wine进行一番考察,以期在此过程中逐步对ELF和ABI有所了解和理解,这也是进一步阅读、理解“解释器”的代码所需要的。

我们用命令行“readelf–a/usr/local/bin/wine”和“objdump–d/usr/local/bin/wine”产生两个文件(把结果重定向到文件中),然后察看这两个文件的部分内容。

首先是目标映像的ELF头:

ELFHeader:

Magic:

7f454c46010101000000000000000000

Class:

ELF32

Data:

2'

scomplement,littleendian

Version:

1(current)

OS/ABI:

UNIX-SystemV

ABIVersion:

0

Type:

EXEC(Executablefile)

Machine:

Intel80386

0x1

Entrypointaddress:

0x8048750

Startofprogramheaders:

52(bytesintofile)

Startofsectionheaders:

114904(bytesintofile)

Flags:

0x0

Sizeofthisheader:

52(bytes)

Sizeofprogramheaders:

32(bytes)

Numberofprogramheaders:

6

Sizeofsectionheaders:

40(bytes)

Numberofsectionheaders:

36

Sectionheaderstringtableindex:

33

这就是映像文件开头处的ELF头,其最初4个字节为‘0x7f’、和‘E’、‘L’、‘F’。

从其余字段中我们可以看出:

●OS是Unix、其实是Linux、而ABI是系统5的ABI。

ABI的版本号为0。

●CPU为x86。

●映像的类型为EXEC,即带有主函数main()的应用软件映像(若是共享库则类型为DYN、即动态连接库)。

●映像的程序入口地址为0x8048750。

如前所述,EXEC映像的装入地址是固定的、不能浮动。

●程序头数组起点在文件中的位移为52(字节),而ELF头的大小正好也是52,所以紧接ELF头的后面就是程序头数组。

数组的大小为6,即映像中有6个Segment。

●Section头的数组则一直在后面位移位114904的地方,映像中有36个Section。

于是,我们接下去看程序头数组:

ProgramHeaders:

TypeOffsetVirtAddrPhysAddrFileSizMemSizFlgAlign

PHDR0x0000340x080480340x080480340x000c00x000c0RE0x4

INTERP0x0000f40x080480f40x080480f40x000130x00013R0x1

[Requestingprograminterpreter:

/lib/ld-linux.so.2]

LOAD0x0000000x080480000x080480000x011cc0x011ccRE0x1000

LOAD0x0011cc0x0804a1cc0x0804a1cc0x001580x00160RW0x1000

DYNAMIC0x0011d80x0804a1d80x0804a1d80x000d80x000d8RW0x4

NOTE0x0001080x080481080x080481080x000200x00020R0x4

一个程序头就是关于一个Segment的说明,所以这就是6个Segment。

第一个Segment的类型是PHDR,在文件中的位移为0x34、即52,这就是程序头数组本身。

其大小为0xc0、即192。

前面说每个程序头的大小为32字节,而6X32=192。

第二个Segment的类型是INTERP,即“解释器”的文件/路径名,是个字符串,这里说是“/lib/ld-linux.so.2”。

下面是两个类型为LOAD的Segment。

如前所述,只有这种类型的Segment才需要装入。

但是,看一下前者的说明,其起点在文件中的位移是0,大小是0x011cc,显然是把ELF头和前两个Segment也包含在里面了。

再看后者,其起点的位移是0x011cc,所以是和前者连在一起的;

其大小为0x158,这样两个Segment合在一起是从0到0x1324。

计算一下就可知道,实际上是把所有的Segment都包括进去了。

所以,对于这个特定的映像,说是只装入类型为LOAD的Segment,实际上装入的却是整个映像。

那么,映像中的什么内容可以不必装入呢?

例如bss段,那是无初始内容的数据段,就不用装入;

还有(与动态连接无关的)符号表,那也不需要装入。

注意两个LOAD类Segment的边界(Alignment)都是0x1000,即4KB,那正好是存储页面的大小。

还有个问题,既然两个LOAD类的Segment是连续的,那为什么不合并成一个呢?

看一下它们的特性标志位就可以知道,第一个Segment的映像是可读可执行、但是不可写;

第二个则是可读可写、但是不可执行,这当然不能合并。

再往下看,下一个Segment的类型是DYNAMIC,那就是跟动态连接有关的信息。

如上所述,这个Segment其实是包含在前一个Segment中的,所以也会被装入。

最后一个Segment的类型是NOTE,那只是注释、说明一类的信息了。

当然,跟动态连接有关的信息是我们最为关心的,所以我们看一下这个Segment的具体内容:

Dynamicsegmentatoffset0x11d8contains22entries:

TagTypeName/Value

0x00000001(NEEDED)Sharedlibrary:

[libwine.so.1]

[libpthread.so.0]

[libc.so.6]

0x0000000c(INIT)0x80485e8

0x0000000d(FINI)0x8049028

0x00000004(HASH)0x8048128

0x00000005(STRTAB)0x8048368

0x00000006(SYMTAB)0x80481d8

0x0000000a(STRSZ)301(bytes)

0x0000000b(SYMENT)16(bytes)

0x00000015(DEBUG)0x0

0x00000003(PLTGOT)0x804a2c4

0x00000002(PLTRELSZ)160(bytes)

0x00000014(PLTREL)REL

0x00000017(JMPREL)0x8048548

0x00000011(REL)0x8048538

0x00000012(RELSZ)16(bytes)

0x00000013(RELENT)8(bytes)

0x6ffffffe(VERNEED)0x80484c8

0x6fffffff(VERNEEDNUM)3

0x6ffffff0(VERSYM)0x8048496

0x00000000(NULL)0x0

这个Segment中有22项数据,开头几项类型为NEEDED的数据是我们此刻最为关心的,因为这些数据告诉了我们目标映像要求装入那一些共享库,例如libwine.so.1。

读者已经看过内核怎样装入用户空间映像,解释器只不过是在用户空间做同样的事,所以共享库的装入对于读者并不复杂,问题是怎样实现动态连接,这是我后面要着重讲的。

前面说过,Segment是从映像装入角度考虑的划分,Section才是从连接/启动角度考虑的划分,现在我们就来看Section。

先看Section与Segment的对应关系:

SectiontoSegmentmapping:

SegmentSections...

00

01.interp

02.interp.note.ABI-tag.hash.dynsym.dynstr.gnu.version.gnu.version_r

.rel.dyn.rel.plt.init.plt.text.fini.rodata.eh_frame

03.data.dynamic.ctors.dtors.jcr.got.bss

04.dynamic

05.note.ABI-tag

Section的名称都以’.’开头,例如.interp;

名称中间也可以有’.’,例如rel.dyn。

这说明,Segment0不含有任何Section,因为这就是程序头数组。

Segment1只含有一个Section,那就是.interp,即解释器的文件/路径名。

而Segment2所包含的Section就多了。

而且,这个Segment还包含了前面两个Segmrnt,所以.interp又同时出现在这个Segment中。

余类推。

前面ELF头中说一共有36个Section,下面就是一份清单:

SectionHeaders:

[Nr]NameTypeAddrOffSizeESFlgLkInfAl

[0]NULL0000000000000000000000000

[1].interpPROGBITS080480f40000f400001300A001

[2].note.ABI-tagNOTE0804810800010800002000A004

[3].hashHASH080481280001280000b004A404

[4].dynsymDYNSYM080481d80001d800019010A514

[5].dynstrSTRTAB0804836800036800012d00A001

[6].gnu.versionVERSYM0804849600049600003202A402

[7].gnu.version_rVERNEED080484c80004c800007000A534

[8].rel.dynREL0804853800053800001008A404

[9].rel.pltREL080485480005480000a008A4b4

[10].initPROGBITS080485e80005e800001700AX004

[11].pltPROGBITS0804860000060000015004AX004

[12].textPROGBITS080487500007500008d800AX004

[13].finiPROGBITS0804902800102800001b00AX004

[14].rodataPROGBITS0804906000106000016600A0032

[15].eh_framePROGBITS080491c80011c800000400A004

[16].dataPROGBITS0804a1cc0011cc00000c00WA004

[17].dynamicDYNAMIC0804a1d80011d80000d808WA504

[18].ctorsPROGBITS0804a2b00012b000000800WA004

[19].dtorsPROGBITS0804a2b80012b800000800WA004

[20].jcrPROGBITS0804a2c00012c000000400WA004

[21].gotPROGBITS0804a2c40012c400006004WA004

[22].bssNOBITS0804a32400132400000800WA004

[23].stabPROGBITS000000000013240048780c2404

[24].stabstrSTRTAB00000000005b9c014cd400001

[25].commentPROGBITS0000000001a87000016500001

[26].debug_arangesPROGBITS0000000001a9d800007800008

[27].debug_pubnamesPROGBITS0000000001aa5000002500001

[28].debug_infoPROGBITS0000000001aa75000a9800001

[29].debug_abbrevPROGBITS0000000001b50d00013800001

[30].debug_linePROGBITS0000000001b64500028400001

[31].debug_framePROGBITS0000000001b8cc00001400004

[32].debug_strPROGBITS0000000001b8e00006be01MS001

[33].shstrtabSTRTAB0000000001bf9e00013a00001

[34].symtabSYMTAB0000000001c67800089010355c4

[35].strtabSTRTAB0000000001cf080005db00001

KeytoFlags:

W(write),A(alloc),X(execute),M(merge),S(strings)

I(info),L(linkorder),G(group),x(unknown)

O(extraOSprocessingrequired)o(OSspecific),p(processorspecific)

这是按Section的名称列出的,其中跟动态连接有关的Section也出现在前面名为Dynamic的Segment中,只是在那里是按类型列出的。

例如,前面类型为HASH的表项说与此有关的信息在0x8048128处,而这里则说有个名为.hash的Section,其起始地址为0x8048128。

还有,前面类型为PLTGOT的表项说与此有关的信息在0x804a2c4处,这里则说有个名为.got的Section,其起始地址为0x804a2c4,不过Section表中提供的信息更加详细一些,有些信息则互相补充。

在Section表中,只要类型为PROGBITS,就说明这个Section的内容都来自映像文件,反之类型为NOBITS就说明这个Section的内容并非来自映像文件。

有些Section名是读者本来就知道的,例如.text、.data、.bss;

有些则从它们的名称就可猜测出来,例如.symtab是符号表、.rodata是只读数据、还有.comment和.debug_info等等。

还有一些可能就不知道了,这里择其要者先作些简略的介绍:

(1).hash。

为便于根据函数/变量名找到有关的符号表项,需要对函数/变量名进行hash计算,并根据计算值建立hash队列。

●.dynsym。

需要加以动态连接的符号表,类似于内核模块中的INPORT符号表。

这是动态连接符号表的数据结构部分,须与.dynstr联用。

●.dynstr。

动态连接符号表的字符串部分,与.dynsym联用。

●.rel.dyn。

用于动态连接的重定位信息。

●.rel.plt。

一个结构数组,其中的每个元素都代表着GOP表中的一个表项GOTn(见下)。

●.init。

在进入main()之前执行的代码在这个Section中。

●.plt。

“过程连接表(ProcedureLinkingTable)”,见后。

●.fini。

从main()返回之后执行的代码在这个Section中,最后会调用exit()。

●.ctors。

表示“Constructor”,是一个函数指针数组,这些函数需要在程序初始化阶段(进入main()之前,在.init中)加以调用。

●.dtors。

表示“Distructor”,也是一个函数指针数组,这些函数需要在程序扫尾阶段(从main()返回之后,在.fini中)加以调用。

●.got。

“全局位移表(GlobalOffsetTable)”,见后。

●.strtab。

与符号表有关的字符串都集中在这个Section中。

其中我们最关心的是“过程连接表(ProcedureLinkingTable)”PLT和“全局位移表(GlobalOffsetTable)”GOT。

程序之间的动态连接就是通过这两个表实现的。

下面我们通过一个实例来说明程序之间的动态连接。

目标映像/usr/local/bin/wine的main()函数中调用了一个库函数getenv(),这个函数在C语言共享库libc.so.6中。

下面是main()经编译/连接以后的汇编代码:

08048ce0<

main>

:

8048ce0:

55push%ebp

8048ce1:

89e5mov%esp,%ebp

......

8048cef:

6820910408push$0x8049120

8048cf4:

e847f9ffffcall8048640<

_init+0x5

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 建筑土木

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1