PE文件各区段说明Word格式文档下载.docx

上传人:b****6 文档编号:19521728 上传时间:2023-01-07 格式:DOCX 页数:11 大小:24.59KB
下载 相关 举报
PE文件各区段说明Word格式文档下载.docx_第1页
第1页 / 共11页
PE文件各区段说明Word格式文档下载.docx_第2页
第2页 / 共11页
PE文件各区段说明Word格式文档下载.docx_第3页
第3页 / 共11页
PE文件各区段说明Word格式文档下载.docx_第4页
第4页 / 共11页
PE文件各区段说明Word格式文档下载.docx_第5页
第5页 / 共11页
点击查看更多>>
下载资源
资源描述

PE文件各区段说明Word格式文档下载.docx

《PE文件各区段说明Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《PE文件各区段说明Word格式文档下载.docx(11页珍藏版)》请在冰豆网上搜索。

PE文件各区段说明Word格式文档下载.docx

你不能够

以DLL函数的真正地址初始化一个变量。

例如:

FARPROCpfnGetMessage=GetMessage;

是把GetMessage函数地址放到pfnGetMessage变量中。

在Win16这没问题,在

Win32,变量中放的其实将是稍早我说过的JMPDWORDPTR[XXXXXXXX]指令的地址。

果你根据这个函数指针来呼叫函数,事情会如你所预期。

但如果你要以此指针读取

GetMessage的前数个字节,幸运之神不会站在你那边。

稍后我将在「PE文件的输出

(exports)」一节中再继续讨论这个主题。

在我写完本章的第一个版本之后,VisualC++2.0推出了。

它介绍另一种新的呼叫方式。

如果你看过VisualC++2.0的系统表头文件(例如WINBASE.H),你将看到和过去不同

的东西。

在VisualC++2.0中,API函数原型都有一个__declspec(dllimport)作为原型

的一部份。

当你呼叫一个这样的函数,编译器不会在模块的另一个地方产生JMPDWORDPTR

[XXXXXXXX]指令,而是产生一个CALLDWORDPTR[XXXXXXXX]函数呼叫。

XXXXXXXX位

址位于.idata内,作用与原先在JMPDWORDPTR[XXXXXXXX]指令中的地址相同。

就我

所知,BorlandC++4.5编译器并没有这样的性质。

BorlandCODE以及.icodesections

BorlandC++4.5编译器和联结器不能够使用COFFOBJ档,它们固守IntelOMF32位

元格式。

Borland编译器当然可以吐出一个名为.text的section,但它却选择"

CODE"

个名称。

为了决定PE档中的一个section名称,BorlandC++联结器(TLINK32.EXE)

从OBJ档中取出section名称并把它拦断为8个字符(如果必要)。

所以,BorlandC++有

一个CODEsection而不是.textsection。

名称不同不算什么,更重要的不同存在于Borland工具联结出来的PE档中。

稍早我说

过,所有对OBJ的呼叫都经由一个JMPDWORDPTR[XXXXXXXX]指令。

在微软的系统中,

这指令来自一个importlibrary的.textsection。

也就是说联结器不需要知道如何产生这

个指令。

importlibrary可视为「需要联结到PE档中」的更多的码和资料。

Borland系统的处理方式就不一样,它比较类似16位NE文件所采行的方法。

Borland

联结器所使用的importlibrary真正只是函数名称和DLL名称的列表而已。

TLINK32有

责任决定哪一些待修正记录(fixups)是针对外部DLLs,然后为它们产生JMPDWORDPTR

[XXXXXXXX]指令。

BorlandC++4.0的TLINK32把它所产生的这个指令存放在.icode

section中,但是到了BorlandC++4.02,TLINK32又改变了,把所有这些JMP指令放

到CODEsection中。

.datasection

这是你的初始化资料的存放区。

所谓初始化数据,包括全域变量和静态变量(globaland

staticvariable),在编译器时期就给定初值。

它也包括字符串常数,像是C/C++程序中的

"

HelloWorld"

联结器把OBJ和LIB文件中所有的.data组合起来放到EXE文件

的.data。

区域变量(localvariable)位于线程堆栈之中,不占用.data或.bss空间。

DATASECTION

BorlandC++以DATA作为其预设的资料区域。

相当于微软编译器所制作的.data。

.bsssection

这是任何未初始化的静态变量和全域变量的存放区。

联结器把OBJ和LIB文件中所有

的.bss组合起来放到EXE文件的.bss。

在sectiontable中,.bss的RawDataOffset栏

位总是为0,表示这个section不占用文件的任何一点空间。

TLINK32并不吐出一

个.bss,它的作法是扩充DATAsection的虚拟大小,以接纳未初始化的资料。

.CRTsection

这是微软的C/C++runtimelibrary(CRT)所使用的另一个初始化的datasection。

这里所

放的资料用于「在main或WinMain之前执行的staticC++类别建构式」中。

.rsrcsection

此处内含模块资源。

早期的NT,16位RC.EXE所输出的.RES档并不被微软的联

结器所了解,那个时候的CVTRES程序就是用来把一个.RES档转换为一个COFF

OBJ,把资源放到OBJ档的一个.rsrc之中。

联结器于是就可以产生一个resourceOBJ。

也就是说,联结器不需要知道任何有关于资源的事情。

后来的微软联结器已经能够直接

处理.RES档。

我将在「PE文件的资源」一节中涵盖资源section的格式。

.idatasection

这个section内含有关于「模块从其它DLLs中输入(import)函数和资料」的相关资

讯。

它相当于NT档的modulereferencetable。

关键性的差异是,每一个输入函数都被

列在这个section之中。

如果要在NE文件中找出对等的信息,你必须深掘每一个节区的

原始内容的重定位资料。

我将在「PE文件的输入(imports)」一节中涵盖importtable的

格式。

.edatasection

这是PE档输出函数(exportfunction)的相关信息。

它的NE对等物是entrytable、resident

namestable和nonresidentnamestable的组合。

和Win16不同的是,很少有机会从一个

EXE中输出一个函数出去,所以通常你只在DLL中才会看到.edata。

BorlandC++所

产生的EXE是个例外,它总是有一个输出函数(__GetExceptDLLinfo)给runtimelibrary

的内部使用。

exporttable的格式将于本章的「PE文件的输出(exports)」一节讨论。

如果使用微软

工具,.edata的资料来自.EXP档,但是联结器没有能力产生这个文件,必须依赖函数

库管理器LIB32.EXE扫描OBJ文件然后才产生EXP档,然后才能交给联结器。

是的,

那是真的,EXP档其实就是拥有不同扩展名的OBJ档罢了。

使用PEDUMP/S观察EXP

档,你可以看到其中的输出函数(exportfunctions)。

.relocsection

这个section内含一表格的baserelocations。

所谓baserelocation是一个指令或初始化

变量的调整值。

如果加载器没有办法把EXE或DLL文件加载到预设的地址的话,就

必须做这样的调整;

否则加载器可以忽略「重定位」这件事情。

如果你希望加载器总是能够把image加载到预定的基地址,你可以使用/FIXED选

项,告诉联结器剥除本项信息。

虽然这可以节省EXE的文件空间,却可能使得EXE档

没办法在其它Win32平台上执行。

例如,你为NT开发了一个EXE,基地址为

0x10000。

如果你告诉联结器把这信息剥除,这个EXE就没有办法在Windows95上跑,

因为0x10000不适用(Windows95的最低加载地址是0x400000,也就是4MB)。

注意一点,编译器所产生的JMP和CALL指令,其所使用的offset值是与该指令成相对

地址关系,而不是真正的32位平滑节区的offset值。

如果image被加载到一个并非

联结器指定的基地址去,JMP和CALL指令不需修改,因为它们用的是相对寻址。

也就是说,其实没有如你想象中那么多的重定位动作要做。

只有使用32-bitoffset的指令

才需要重定位动作。

假设你有下面的全域变量宣告:

inti;

int*ptr=&

i;

如果联结器设定基地址是0x10000,变量i的地址是0x12004。

在被用来存放ptr的

内存中,联结器将写入0x12004,因为那是变量i的地址。

如果加载器为了某种理由

把文件加载到0x70000处,i的地址将是0x72004,然而,预先初始化过的ptr值变成

错误值,因为i现在的位置已经提升了0x60000。

这就是需要重定位信息参一脚的场合了。

.reloc用来表示「联结器所假设的加载地址」

和「真正的加载地址」之间的差异。

我将在「PE档的BaseRelocations」一节有比较详

细的讨论。

.tlssection

当你使用编译器的"

__declspec(thread)"

性质,你定义的资料并没有进入.data或.bss之

中,倒是有一份拷贝进入.tls之中。

.tls的名称是因为threadlocalstorage而来,和TlsAlloc

函数家族有密切关系。

为了简单描述所谓的threadlocalstorage,请把它想象成「让每一个线程拥有各自的全

域变量」的一种方法。

也就是说,每一个线程可以拥有它自己的一组静态资料,使用

这些资料的程序代码,不需在意现在是哪一个线程正在执行。

假设某程序有数个线程,

处理相同的工作。

也因此执行相同的码。

如果你宣告一个tls,像这样:

__declspec(thread)inti=0;

//thisisaglobalvariabledeclaration

每一个线程将因此拥有变量i的一个副本。

你可以明白地在执行时期索求并使用tls,相关函数是TlsAlloc、TlsSetValue、TlsGetValue

等(第3章对于TlsXXX函数的描述比较详细)。

通常,以__declspec(thread)在程序中

宣告你的资料,比使用TlsAlloc简单得多。

这里有一个坏消息。

在NT和Windows95中,tls机制不能够有效运作--如果运作对

象是以LoadLibrary动态加载的DLL。

至于在一个EXE或是一个隐式加载(implicitly

loaded,译注)的DLL之中,每一件事情都没问题。

如果你不能够以隐晦方式加载DLL,

但又需要让每一个线程有自己的资料,那你只好使用TlsAlloc和TlsGetValue。

注意,

每一线程真正的内存区块并不是放在.tlssection中,也就是说,当切换线程的时

候,内存管理器并不改变「实际映像至模块之.tlssection」的内存。

.tls内只不过是

一些资料,用来初始化真正的线程专属区块。

初始化动作是靠操作系统与runtimelibrary

的合作,过程之中需要另外一些储存在.rdata之中的资料:

TLSdirectory。

译注:

如果程序与DLL的importlibrary联结,我们说这是implicitlylink,并导至DLL被implicitlyloaded。

如果程序没有与DLL的importlibrary联结,而是在需要时(执行时期)呼叫LoadLibrary和GetProcAddress以取得函数地址,再呼叫之,我们称此为explicitlylinked,并导至DLL被explicitlyloaded。

.rdatasection

.rdata至少有四个用途。

第一,在被微软联结器产生的EXEs之中,.rdata内含debug

directory(OBJ档中并没有debugdirectory)。

而在TLINK32所产生的EXEs之中,debug

directory是一个名为.debug的section。

debugdirectory是一个由

IMAGE_DEBUG_DIRECTORY结构所组成的数组。

这些结构持有文件之中各种除错资

讯的型态、大小、位置。

除错信息可能有三种型态:

CodeView、COFF、FPO。

图8-5

显示PEDUMP对一典型的debugdirectory的输出结果。

Type

Size

Address

FilePtr

Charactr

TimeData

Version

COFF

000065C5

00000000

00009200

2CF83F3D

0.00

(unknown)

00000114

0000F7C8

FPO

000004B0

0000F8DC

CODEVIEW

0000B0B4

0000FD8C

图8-5一个典型的debugdirectory。

debugdirectory并不一定会在.rdata的起始处被发现。

要找到它,你必须使用datadirectory

的第7笔资料(IMAGE_DIRECTORY_ENTRY_DEBUG)。

还记得吗,datadirectory位

于PE表头的尾端。

为了确定微软联结器所做出来的debugdirectory的项目个数,请把

debugdirectory的大小(可从debugdirectory的"

size"

字段获得)除以

IMAGE_DEBUG_DIRECTORY的结构大小。

至于TLINK32则是把debugdirectories的

真正数量记录在"

字段中,而不是字节总长度。

PEDUMP可以处理这两种情况。

.rdata的第二个有用部份是descriptionstring。

如果你在程序的.DEF档中指定

DESCRIPTION,被指定的字符串就会出现在.rdata之中。

在NE档中,descriptionstring总

是nonresidentnamestable的第一个项目。

descriptionstring主要是用来设定一个有用的

字符串,用以描述这个文件。

不幸的是我还没有发现什么好方法来找到它。

我曾经看过有

些PE档的descriptionstring放在debugdirectory之前,有些却在debugdirectory之后。

.rdata的第三个用途是为了OLE程序设计所需的GUIDs。

UUID.LIB内含一系列的128

位GUIDs,当作interfaceIDs。

这些GUIDs都放在EXE或DLL的.rdata中。

.rdata的最后一个用途是用来放置TLS(ThreadLocalStorage)的directory。

TLSdirectory

是一个特殊数据结构,被编译器的runtimelibrary使用,以便能够透明化地提供TLS给

程序中宣告的变量。

TLSdirectory的格式可以在MSDN(MicrosoftDeveloperNetwork)

光盘片中找到:

PortableExecutableandCommonObjectFileFormat"

我们对TLSdirectory

的主要兴趣是指向资料(用来初始化每一个tls区块)的起头和结尾的指针,TLSdirectory

的RVA(RelativeVirtualAddress)可以在PE表头的datadirectory的

IMAGE_DIRECTORY_ENTRY_TLS项目中获得。

至于真正用来初始化TLS区块的资

料可以在.tlssection中找到。

.debug$S和.debug$Tsections

.debug$S和.debug$T只出现于COFFOBJs之中,内含CodeView的符号和型态资

看来十分奇怪的section名称系衍生自前一版微软编译器的节区名称($$SYMBOLS

和$$TYPES)。

.debug$T的唯一目的是为了放置.PDB档(内有项目中所有OBJs的

CodeView型态信息)的路径名称。

联结器利用.PDB为EXE档产生出一部份的

CodeView信息。

.drectvesection

这个section只出现在OBJ档,内含联结器命令列参数的文字表达。

例如,在微软的

VisualC++编译器,下面字符串一定会出现在.drectve中:

-defaultlib:

LIBC-defaultlib:

OLDNAMES

当你在程序代码中使用__declspec(export),编译器会制造出命令列上的对应东西,放

在.drectve之中(例如export:

MyFunction)。

含有$的sections(只针对OBJs/LIBs)

在OBJ档中,名称含有$的sections(例如.idata$2)将被联结器特别对待。

联结器把

所有拥有相同名称(直至$字符)的sections组合成为单一一个section。

例如,如果

联结器遭遇.idata$2和.idata$6,它会把它们整合为一个.idata。

被整合的sections的次序是以$之后的字符为准。

联结器以字母顺序排列之,所

以.idata$2在.idata$6之前。

.idata$A则在.idata$B之前。

那么到底带有$的section做什么用?

最普遍的用法就是importlibrary利用它们来存

放最终的.idata(importsection)的各部份资料。

这可有趣了,联结器本身并不需要从头

产生.idata,最终的.idata是由OBJ和LIB各贡献一部份而来。

杂项的sections

有时候我会从PEDUMP的输出中看到其它一些sections。

例如Windows95的GDI32.DLL

内含一个名为_GPFIX的datasection,我们推测它大概与GPfault的处理有关。

这有双重意义。

第一,不要以为你只能使用编译器或组译器提供的标准sections。

若有需

要,别犹豫不决。

在微软的C/C++编译器中,你可以使用#pragmacode_seg和#pragma

data_seg。

Borland的使用者则可以使用#pragmacodeseg和#pragmadataseg。

若是组合

语言,你只要产生一个32位节区并给予不同于「标准sections」的名称即可。

TLINK32

会把同类别的codesegments组合在一起,所以你要不就得为每一个codesegment指定

一个类别名称,要不就关闭"

codesegmentpacking"

这个性质。

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

当前位置:首页 > 高等教育 > 其它

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

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