程序编译过程(2)

2018-12-29 19:00

如一个名为.rdata的节表明它是一个只读数据节。使用节名只是为了方便人们处理文件,它对操作系统来说无关紧要。一个名为FOOBAR的节可能实际上是一个代码节,就像.text节一样。Microsoft通常在它们的节名前加一个圆点,但这并不是必须的。多少年来,Borland链接器使用的节名都是CODE和DATA。

节并不是完全由链接器生成的,在OBJ文件中就有它们的身影。这通常是由编译器放在那里的。链接器的工作就是把OBJ文件和库文件中所有需要的节组合成PE文件中最终相应的节。例如你的工程中的每个OBJ文件可能都至少有一个包含代码的.text节。链接器把各种OBJ文件中的所有.text节组合成单个的.text节放入PE文件。同样,各种OBJ文件中的所有.data节也被组合成PE文件中单个的.data节。 节有两种对齐值,一种是在文件中的对齐值,另一种是在内存中的对齐值。PE文件头中指定了这两种值,它们可以不同。每个节的起始地址都是对齐值的倍数。例如在PE文件中,典型的对齐值是0x200。因此每个节的起始起始地址都是0x200的倍数。

一旦映射进内存,节总是从页的边界开始。也就是说,当PE文件被映射进内存时,每个节的开头都对应于一个内存页的开始。在x86 CPU上,页按4KB对齐;在IA-64上,页按8KB对齐。以下是PEDUMP输出的Windows XP中的

KERNEL32.DLL的.text节和.data节的情况: Section Table

01 .text VirtSize: 00074658 VirtAddr: 00001000 raw data offs: 00000400 raw data size: 00074800 ……

02 .data VirtSize: 000028CA VirtAddr: 00076000 raw data offs: 00074C00 raw data size: 00002400

上面的输出表明,.text节的文件偏移是0x400,在内存中它比KERNEL32的加载地址高0x1000字节。同样,.data节的文件偏移是0x74C00,它在内存中比KERNEL32的加载地址高0x76000字节。

可以创建一个PE文件,使它的节在文件中的偏移与在内存中的偏移相同。但这会使可执行文件相当大,不过可以提高它在Windows 9x或Windows Me中的加载速度。使用默认的/OPT:WIN98链接器选项(由Visual Studio 6.0引进)就可以创建这样的PE文件。在Visual Studio? .NET中,链接器可能使用也可能不使用这个选项,这取决于文件是不是足够小。 链接器一个比较有趣的功能就是合并节。如果两个节属性相似、相互兼容时,它们通常会在链接时被合并成一个节。这是通过链接器的/MERGE选项来完成的。例如以下的链接器

选项会把.rdata节和.text节组合成单个的.text节: /MERGE:.rdata=.text

把节合并起来的好处是可以节省在磁盘上和在内存中的空间。每个节至少要在内存中占用一个页面。如果你能将一个可执行文件中节的数目从四个减少到三个,你很可能就会少占用一页内存。当然,这取决于那两个被合并的节中未使用的空间加起来够不够一页。

当你合并节时就会发生一些有趣的情况,因为这并没有硬性的和快速的规则可以遵守。例如,把.rdata节合并到.text节当然可以,但是你不能把.rsrc节、.reloc节或者.pdata节合并到其它节中。在Visual Studio .NET之前,你可以把.idata节合并到其它节中。但是在Visual Studio .NET中,这是不允许的。但在创建程序的发行版本时,链接器自己却经常把部分的.idata节合并到其它节中,例如.rdata节。

由于导入表要被Windows加载器改写,你可能想知道它们怎么能被放在只读节中。这是因为在加载时系统临时将包含导入表的那些页面的属性设置为可读/可写。一旦导入表被初始化,这些页面就会被设置成原来的属性。

相对虚拟地址

在可执行文件中,许多地方都需要被指定一个在内存中的地址。例如在使用全局变量时需要它的地址。PE文件可以被加

载到进程地址空间中的任何地方。虽然它有一个首选地址,但你却不能依赖可执行文件一定会被加载到那个地址。因此就需要按一定方式指定地址,使它们并不依赖于可执行文件的加载地址。

为了避免在PE文件中硬编码内存地址,因此就使用了RVA。RVA只是一个相对于PE文件在内存中的加载位置的偏移。例如假定一个EXE文件被加载在0x400000处,而它的代码节在0x401000处。那么这个代码节的RVA就是:

(目标地址)0x401000 - (加载地址)0x400000 = (RVA)0x1000

要把一个RVA转换成实际地址,只需要简单地逆着上述过程进行:将RVA与实际加载地址相加就能得到实际的内存地址。顺便说一下,按照PE格式中的说法,实际的内存地址被称为虚拟地址(Virtual Address,VA)。另外一种考虑VA的方式就是把它当成RVA加上首选加载地址。不要忘了我前面说过加载地址与HMODULE是一回事。

想在内存中探索一些DLL内部的数据结构吗?这里就是方法——用DLL的名称作为参数调用GetModuleHandle函数,它返回的HMODULE就是这个DLL的加载地址,你可以利用你学的关于PE文件结构的知识在这个模块中找到你想找到的一切。 数据目录

在可执行文件中有许多数据结构需要被快速地定位。导入表、导出表、资源以及基址重定位信息等就是一些明显的例子。所有这些广为人知的结构都是以同样的方式被定位的,这些位置被称为数据目录。

struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16

数据目录是一个有16个(WINNT.H中定义为

IMAGE_NUMBEROF_DIRECTORY_ENTRIES)元素的结构数组。每个数组元素所指代的内容已经被预先定义好了。WINNT.H文件中的这些IMAGE_DIRECTORY_ENTRY_xxx定义就是数据目录的索引(从0到15)。下表描述了每个IMAGE_DIRECTORY_ENTRY_xxx值所指代的内容。由它们指向的许多数据结构将在本文的第二部分中详细描述。值

描述IMAGE_DIRECTORY_ENTRY_EXPORT

指向导出表(IMAGE_EXPORT_DIRECTORY结构)。


程序编译过程(2).doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:第2章极限与连续

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: