七、节目录(Section directories)
---------------------------------
节由两个主要部分组成:首先,是一个节描述(IMAGE_SECTION_HEADER[意为“节头”]类型的),然后是原始的节数据。因此,我们会在数据目录后发现一“NumberOfSections”个节头组成的数组,它们按照各节的RVA排序。
节头包括:
1)一个IMAGE_SIZEOF_SHORT_NAME (8位)(意为“短名的大小”)个字节的数组,形成节的名称(ASCIIX形式)。如果所有的8位都被用光,该字符串就没有0结束符!典型的名称象“.data”或“.text”或“.bss”形式。开头的“.”不是必须的,名称也可能为“CODE”或“IAT”或类似的形式。
请注意:并不是所有的名称都和节中的内容相关。一个名叫“.code”的节可能包含也可能不包含可执行代码;它还可能只包含输入地址表;它也可能包含代码“和”地址表“和”未初始化数据。要找到节中的信息,你必须通过可选头的数据目录来查询它。既不要过分相信它的名称,也不要以为节的原始数据会从节的开头就开始。
2)IMAGE_SECTION_HEADER(“节头”)的下一个成员是一个32位的、“PhysicalAddress”(物理地址)和“VirtualSize”(虚拟大小)组成的共用体。在目标文件中,它是内容重定位到的地址;在可执行文件中,它是内容的大小。事实上,此域似乎没被使用;因为有的链接器输入大小,有的链接器输入地址,我还发现有一个链接器输入0,而所有的可执行文件都运行如风。
3)下一个成员是“VirtualAddress”(虚拟地址),是一个32位的值,用来保存载入RAM(内存)后,节中数据的RVA。
4)然后,我们到了32位的“SizeOfRawData”(意味“原始数据大小”),它表示节中数据被大约到下一个“FileAlignment”的整数倍时节的大小。
5)下一个是“PointerToRawData”(意味“原始数据指针”,32位),它特别有用,因为它是从文件的开头到节中数据的偏移量。如果它为0,那么节的数据就不包含在文件中,并且要在载入时才定。
6-9)然后,我们得到“PointerToRelocations”(意味“重定位指针”,32位)和“PointerToLinenumbers”(意味“行数指针”,也是32位),以及“NumberOfRelocations”(意味“重定位数”,16位)和“NumberOfLinenumbers”(意味“行数数”,也是16位)。所以这些都是只用于目标文件的信息。可执行文件拥有一个特殊的基址重定位目录,并且行数信息(如果真的存在的话)通常包含在有一个特殊目的的调试段中或者别的什么地方。
10)节头的最后一个成员是32位的“Characteristics”(意味“特性”),它是一串描述节的内存如何被处理的标志:
如果位5 IMAGE_SCN_CNT_CODE(代码内容节)被置1,表示节中包含可执行代码。
如果位6 IMAGE_SCN_CNT_INITIALIZED_DATA(初始化数据内容节)被置1,表示节中包含执行开始前即取得已定义值的数据。换言之:文件中节的数据就是有意义的。
如果位7 IMAGE_SCN_CNT_UNINITIALIZED_DATA(未初始化数据内容节)被置1, 表示节中包含未初始化数据,并需于执行开始前被初始化为全0。这通常是BSS节。
如果位9 IMAGE_SCN_LNK_INFO(链接器信息节)被置1, 表示节中不包含映象数据,只有一些注释、描述或者其他的文档。这些信息是目标文件的一部分,并有可能是提供给链接器的信息,比如需要哪些库文件。
如果位11 IMAGE_SCN_LNK_REMOVE(链接可删除节)被置1,表示数据是目标文件的、被预定于可执行文件被链接后丢弃掉的节的一部分。常和位9连用。
如果位12 IMAGE_SCN_LNK_COMDAT(链接通用块节)被置1, 表示节中包含“common block data”(通用块数据),也即某种形式的打包函数。
如果位15 IMAGE_SCN_MEM_FARDATA(内存远程数据节)被置1,表示我们拥有远程数据----意味着什么。此位的含义不明。
如果位17 IMAGE_SCN_MEM_PURGEABLE(内存可清除节)被置1, 表示节中的数据可清除----但我认为它和“可丢弃”不是一回事,可丢弃拥有自己的标志位,参见后面。同样,它也明显的不是用来指示16位信息的,因为它也有一个IMAGE_SCN_MEM_16BIT定义。此位的含义不明。
如果位18 IMAGE_SCN_MEM_LOCKED(内存被锁节)被置1, 表示节不应该被从内存中移除?抑或表明没有重定位信息?此位的含义不明。
如果位19 IMAGE_SCN_MEM_PRELOAD(内存预载入节)被置1,表示节在执行开始前应该被页载入?此位的含义不明。
位20至23 指定我没有找到信息的对齐。诸如#defines IMAGE_SCN_ALIGN_16BYTES之类。我曾经见过的唯一值为0,是16位的缺省对齐。 我怀疑它们是库之类文件的目标对齐。
如果位24 IMAGE_SCN_LNK_NRELOC_OVFL(链接扩展重定位节)被置1,表示节中包含一些我不知道的扩展重定位。
如果位25 IMAGE_SCN_MEM_DISCARDABLE(内存可丢弃节)被置1,表示节中的数据在进程启动后就不需要了。它是,举例来说,含有重定位信息的情况。我曾经见过它也用于只执行一次的驱动和服务程序的启动例程,还用于输入目录。
如果位26 IMAGE_SCN_MEM_NOT_CACHED(内存不缓存节)被置1,表示节中的数据不应该被缓存。不要问我为什么不。这是不是意味着关掉2级缓存?
如果位27 IMAGE_SCN_MEM_NOT_PAGED(内存不页清除节)被置1,表示节中的数据不应该页清除。它对驱动程序有意义。
如果位28 IMAGE_SCN_MEM_SHARED(内存共享节)被置1,表示节中的数据在映象文件的所有正在运行的实例中共享。如果它是,例如DLL文件的未初始化数据,那么DLL的所有正在运行的实例程序在任何时候都将拥有相同的变量内容。
注意:只有第一个实例的节被初始化。
含有代码的节总是被共享写时拷贝(copy-on-write)(亦即:如果重定位必不可少,那么共享就不工作)。(译注:“写时拷贝”的译法也许根本就是错误的,但我一时找不到更准确的翻译,也不清楚其具体含义,只能以此充数了。希望知情着指点。)
如果位29 IMAGE_SCN_MEM_EXECUTE(内存可执行节)被置1,表示进程对节的内存有“执行”的存取权限。
如果位30 IMAGE_SCN_MEM_READ(内存可读节)被置1,表示进程对节的内存有“读”的存取权限。
如果位31 IMAGE_SCN_MEM_WRITE(内存可写节)被置1,表示进程对节的内存有“写”的存取权限。
在节头之后,我们就会发现节本身。在文件中,它们按照“FileAlignment”(文件对齐)的字节数对齐(也就是说,在可选头之后和每个节的数据之后将要填充一些字节)并按照它们的RVA排序。在载入后(内存中), 它们按照“SectionAlignment”(节对齐)的字节数对齐。
试举一例,如果可选头在文件的偏移量981处结束,“FileAlignment”(文件对齐)的值为512,那么第一个节将于1024字节处开始。注意:你可通过“PointerToRawData”(原始数据指针)或者“VirtualAddress”(虚拟地址)的值来找到各节,因此实际上根本没必要在对齐上小题大做。
试画映象文件的全图如下:
+-------------------+
| DOS-根 |
+-------------------+
| 文件头 |
+-------------------+
| 可选头 |
|- - - - - - - - - -|
| |----------------+
| 数据目录 | |
| | |
| (指向节中 |-------------+ |
| 目录的RVA) | | |
| |---------+ | |
| | | | |
+-------------------+ | | |
| |-----+ | | |
| 节头 | | | | |
| (指向节 |--+ | | | |
| 边界的RVA) | | | | | |
+-------------------+<-+ | | | |
| | | <-+ | |
| 节数据 1 | | | |
| | | <-----+ |
+-------------------+<----+ |
| | |
| 节数据 2 | |
| | <--------------+
+-------------------+
每个节都有一个节头,并且每个数据目录都会指向其中的一个节(几个数据目录有可能指向同一个节,而且也可能有的节没有数据目录指向它们)。