greatdong
驱动牛犊
驱动牛犊
  • 注册日期2004-11-06
  • 最后登录2008-12-18
  • 粉丝0
  • 关注0
  • 积分7分
  • 威望40点
  • 贡献值0点
  • 好评度10点
  • 原创分1分
  • 专家分0分
阅读:1496回复:1

An In-Depth Look into the Win32 Portable Executable File Format-2

楼主#
更多 发布于:2005-03-12 08:21

这篇文章里我来讲一讲更常见的 sections. 我们来看一下会在可执行文件和 OBJs 里遇到的 sections. 除非特别指出,
Figure 1 中的 section 名都来自于 Microsoft tools.


The Exports Section

所谓 EXE exports code 或 data, 就是使其函数或变量可由其它 EXEs 使用. 为简单起见, 我用 \"symbols\" 来代指
exported 函数或变量. 要 export, 至少应该有一种定义好的方法来获得 exported symbol 的地址. 每一个 exported symbol 都相应有一个 ordinal number, 这样可通过此 ordinal number 来查找该 exported symbol. 除此之外, 几乎每个 symbol 都有一个 ASCII name. 尽管 exported symbol name 可以与源程序中的函数或变量名不同, 但习惯上还是取一样的名字. 当可执行文件 imports 一个 symbol 时, 一般会使用 symbol name 而不用ordinal.

然而,当用name来importing时, 系统就用name来查找所需symbol的export ordinal, 再用ordinal的值来获得地址. 若一上来就使用ordinal, 速度会稍微快一点点. 用name来Exporting和importing只是为了方便程序员.

可以用 .DEF 文件的 exports section 中的 ORDINAL keyword 来告诉 linker 去创建一个 import library, 在该library 中强迫使用 ordinal, 而非 name, 来 import API.

先来看一下 IMAGE_EXPORT_DIRECTORY structure, 如 Figure 2 所示. exports directory 指向三个数组和一个 ASCII strings 的表. 唯一必需的数组是 Export Address Table (EAT), 该数组是一个函数指针的数组, 数组成员为 exported function 的地址. export ordinal 就是该数组的索引(见Figure 3).

+-----------------------+
|  Characteristics      |
+-----------------------+
| ...other fields...    |
+-----------------------+
| Name                  |-> \"Foo.dll\"
+-----------------------+
| Base = 1              |
+-----------------------+
| NumberOfFunctons = 4  |
+-----------------------+
| NumberOfNames = 3     |  Export Address Table (RVAs)
+-----------------------+  +----+----+----+----+
| AddressOfFunctions    |->| 42 |1084|  0 | 520|
+-----------------------+  +----+----+----+----+
| AddressOfNames        |->|    |    |    |Export Name Table
|                       |  +-|--+-|--+-|--+
|                       |    v    +--v +-------------v  
+-----------------------| \"Export1\" \"AnotherExport\" \"StillAnother\"
| AddressOfNameOrdinals |  +-------+---------------+--------------+
|                       |->|   1   |       2       |       4      |
+-----------------------+  +-------+---------------+--------------+

Figure 3 The IMAGE_EXPORT_DIRECTORY Structure

我们用一个例子说明如何 export. Figure 4 为 KERNEL32.DLL 中的一些 exports. 假设对 KERNEL32.DLL 中的
AddAtomA API 调用 GetProcAddress. 系统先定位 KERNEL32 的IMAGE_EXPORT_DIRECTORY, 从中得到 Export Names Table (ENT)的起始地址. 获知数组中共有 0x3A0 个 entries 后, 进行折半查找, 直至找到字符串 \"AddAtomA\". 假设 loader 发现 AddAtomA 为数组的第二个 entry. loader 就从 export ordinal table 中相应读入第二个值. 这个值就是 AddAtomA 的export ordinal. 将 export ordinal 作为索引查找 EAT (还要算上Base 的值), 就得到 AddAtomA 在相对虚拟地址(RVA) 0x82C2 上. 将 0x82C2 加到 KERNEL32 的加载地址上就得出了AddAtomA 的实际地址.


Export Forwarding

exports 有一个很聪明的特性就是能将 export \"forward\" 到另一个 DLL. 例如在 Windows NT, Windows 2000 和 Windows XP 下, KERNEL32 的 HeapAlloc 就 forwarded 到由 NTDLL export 的 RtlAllocHeap. Forwarding 是在连结时用 .DEF 文件的 EXPORTS section 里的一种特殊的语法完成的. 以 HeapAlloc 为例, KERNEL32 的 DEF 文件包含:

   EXPORTS
   ???
   HeapAlloc = NTDLL.RtlAllocHeap

怎么才能知道函数被 forwarded 而不是被正常地 exported? 这有点复杂. 一般, exported symbol 的 RVA 由 EAT 保存. 然而, 若函数的 RVA 在 exports section 里面(由 DataDirectory 中的 VirtualAddress 和 Size 域给出), 则该 symbol 是 forwarded 的. 若 symbol 是 forwarded 的, 则其 RVA 显然不会是当前 module 的 code 或 data 的地址. 此时 RVA 指向所要 forwarded 的 DLL 和 symbol name 的一个字符串. 在前面的例子中, 就是 NTDLL.RtlAllocHeap.


The Imports Section

与 exporting 函数或变量相反的就是 importing. 为与前面的 section 一致, 仍使用 \"symbol\" 来指代 imported 的
函数或变量.

imports data 的最重要的部分就是 IMAGE_IMPORT_DESCRIPTOR structure. imports 的 DataDirectory entry 指向此结构的数组. 每个 imported executable 都有一个 IMAGE_IMPORT_DESCRIPTOR. IMAGE_IMPORT_DESCRIPTOR 数组的结尾为一个所有域都为0的 entry. Figure 5 为 IMAGE_IMPORT_DESCRIPTOR 的内容.

每个 MAGE_IMPORT_DESCRIPTOR 一般都指向两个一模一样的数组. 这些数组有几个不同的名字, 但最常用的有两
个, Import Address Table(IAT) 和 Import Name Table (INT). Figure 6 为从 USER32.DLL 中 import APIs
的 executable.

                                         HintNameArray                ImportAddressTable
                               +------------>+----+                        +----+<------+      
 IMAGE_IMPORT_DESCRIPTOR       |             |    |->+-----44-----------+<-|    |       |
+--------------------------+   |             +----+  | GetMessage       |  +----+       |
| Original First Thunk     |---+             |    |->+-----72-----------+<-|    |       |
+--------------------------+                 +----+  | LoadIcon         |  +----+       |
| TimeDateStamp            |                 |    |->+-----19-----------+<-|    |       |
+--------------------------+                 +----+  | TranslateMessage |  +----+       |
| Forwarder Chain          |                 |    |->+-----95-----------+<-|    |       |
+--------------------------+  +------------+ +----+  | IsWindows        |  +----+       |
| Imported Dll Name        |->|\"User32.dll\"|         +------------------+  This table   |
+--------------------------+  +------------+                               ever written |
| First Thunk              |------------------------------------------------------------+
+--------------------------+                                               by PE loader
| Additional               |                                    
| IMAGE_IMPORT_DESCRIPTORs |
| for other Dlls           |
| as necessory             |
+vvvvvvvvvvvvvvvvvvvvvvvvvv+

Figure 6 Two Parallel Arrays of Pointers

两个数组的成员都为 IMAGE_THUNK_DATA 类型, 该类型为一 pointer-sized 的 union. 每个 IMAGE_THUNK_DATA 成员都对应着一个从 executable 中 imported 的. 这两个数组都以一个值为 0 的 IMAGE_THUNK_DATA 成员结尾. IMAGE_THUNK_DATA union 为一个 DWORD, 以下是对其的解释:

DWORD Function;       // Memory address of the imported function
DWORD Ordinal;        // Ordinal value of imported API
DWORD AddressOfData;  // RVA to an IMAGE_IMPORT_BY_NAME with
                      // the imported API name
DWORD ForwarderString;// RVA to a forwarder string

IAT 中的 IMAGE_THUNK_DATA structures 身兼二职. 在 executable file 中, 它们要么保存 imported API 的 ordinal, 要么保存 IMAGE_IMPORT_BY_NAME structure 的 RVA. IMAGE_IMPORT_BY_NAME structure 就是一个 WORD 和一个命名 imported API 的字符串. WORD 的值向 loader 暗示 imported API 的值. 当 loader 加载 executable 时, 它用 imported function 的实际地址 overwrite 掉 IAT 的每一 entry. 理解这一点是继续学习的关键. 我本人极力推荐阅读 Russell Osterlund 讨论此问题的文章, 该文章描述了 Windows loader 的各个步骤.

在 executable 载入之前, 有没有本法能确定 IMAGE_THUNK_DATA structure 保存的是 import ordinal 还是 IMAGE_IMPORT_BY_NAME structure 的 RVA? 答案的关键是 IMAGE_THUNK_DATA 的最高位的值. 若该位置位, 则后31位(对于 64-bit 的executable 则为63位) 视为 ordinal 值. 若未置位, 则 IMAGE_THUNK_DATA 值为 IMAGE_IMPORT_BY_NAME 的 RVA.

另一个数组, INT, 与 IAT 形式上完全相同. 也是一个 IMAGE_THUNK_DATA structures 的数组. 关键的不同是被载入内存时 INT 不会被 loader overwrite. 为什么要对从 DLL import 的每一组 APIs 使用两个数组呢? 答案是一种叫做 binding 的概念. 当 binding process 重写文件中的IAT时(之后介绍此process), 仍需保留某种方法以取得原来的信息. INT, 该信息的一份副本, 此时就派上用场了.

对于加载 executable 来说, 并不需要 INT. 然而, 要是没有 INT, executable 就不能被 bound. Microsoft 的 linker
似乎总会生成 INT, 但相当长的一段时间里, Borland 的 linker (TLINK) 却不这样. Borland-created 的文件就不能被 bound.

对于早期的 Microsoft linkers, imports section 并非只用于 linker. 组成 executable 的 imports 的所有数据都来自于 import libraries. 使用 Dumpbin 或 PEDUMP 来查看 import library 会发现名字类似于 .idata$3 和 .idata$4 的 sections. linker 仅根据规则来合并 sections, 所有的 structures 和 arrays 都会神奇地归位. 几年前, Microsoft 引入了一种新的 import library 格式. 利用该格式, linker 在创建 import data 时能更主动并因此创建出小得多的 import libraries.


Binding

当 executable 被 bound (如通过Bind程序) 时, IAT 中的 IMAGE_THUNK_DATA structures 被 imported function 的
实际地址 overwritten. 磁盘上的 executable file 将其它 DLLs 中的 APIs 的 in-memory 的实际地址记录在 IAT 中.
当加载一个被bound了的executable时, Windows loader会略过查找每一 imported API并将其写入IAT的步骤. 正确的地址已经在那儿了! 这只有在正确对齐时才行. 我在2000年5月的专栏有一些 benchmarks, 表明 binding executables 能在多大程度上提高加载速度.

也许有人会怀疑 executable binding 的安全性. 毕竟, 所 bind 的 executable 和 DLLs 的 imports 变了怎么办? 若这种情况出现, IAT 中的所有地址都是无效的. loader 会检查这种情况并采取相应行动. 若IAT中的地址已经过时, loader 仍然可以从 INT 得到所有必需的信息来 resolve imported APIs 的地址.
 
最好在安装时进行程序的 binding. Windows installer 的 BindImage 动作会完成此工作. 另外一种方法是用 IMAGEHLP.DLL 提供的 BindImageEx API. 无论用什么方法, 只要能 binding 就好. 若 loader 确定 binding 信息是当前的, executables 就加载得更快. 令 binding 有效的关键步骤之一就是 loader 确定 IAT 中的 binding 信息是当前的. 当 executable 被 bound 时, 被引用的 DLLs 就被放入 executable 中. loader 检查该信息以快速决定 binding 的有效性. 在 binding 的最初实现中并未加入此信息. 因此, 可以用旧的或新的方法来 bind executable. 我这里讲的是新方法.
 
确定 bound imports 有效性的关键的数据结构就是一个 IMAGE_BOUND_IMPORT_DESCRIPTOR. 一个 bound executable 包含一个该结构的列表. 每一个 IMAGE_BOUND_IMPORT_DESCRIPTOR structure 代表了被 bound 的 imported DLL 的 time/date stamp. 该列表的 RVA 由 DataDirectory 的 IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 给出. IMAGE_BOUND_IMPORT_DESCRIPTOR 的成员为:

TimeDateStamp, 一个DWORD, imported DLL的time/date stamp.

OffsetModuleName, 一个WORD, 包含imported DLL名字字符串的offset. 此域是以第一个 IMAGE_BOUND_IMPORT_DESCRIPTOR为                    基址的offset (非RVA).

NumberOfModuleForwarderRefs, 一个WORD, 包含紧跟此结构的IMAGE_BOUND_FORWARDER_REF structures 的数目.除最后一个                                WORD(NumberOfModuleForwarderRefs)保留外, 这些 structures 与                                                     IMAGE_BOUND_IMPORT_DESCRIPTOR 相同.

简单讲, 每个 imported DLL 的 IMAGE_BOUND_IMPORT_DESCRIPTORs 就是一个数组. 但是, 当 binding 一个 forwarded 到另一个 DLL 的API时, 还必须要检查 forwarded DLL 的有效性. 因此, IMAGE_BOUND_FORWARDER_REF structures 与 IMAGE_BOUND_IMPORT_DESCRIPTORs 就交织了起来.

假设要连结 HeapAlloc, HeapAlloc 又 forwarded 到 NTDLL 的 RtlAllocateHeap. 这时对 executable 运行BIND, 在 EXE 中就有一个 KERNEL32.DLL 的 IMAGE_BOUND_IMPORT_DESCRIPTOR, 后面是 NTDLL.DLL 的 IMAGE_BOUND_FORWARDER_REF, 再后面可能是其它 imported 和 bound DLLs 的 IMAGE_ BOUND_IMPORT_DESCRIPTORs.


Delayload Data

先前我提到了 delayloading DLL 的办法介于隐式 import 与用 LoadLibrary 和 GetProcAddress 的显式 importing APIs 之间. 现在来看一下 data structures, 看看 delayloading 如何工作.

要记住 delayloading 并非 operating system 的特性. 该技术完全是由 linker 和 runtime library 所加的额外的 code 和 data 完成的. 因此, 在 WINNT.H 中没有太多对 delayloading 的引用. 然而, 可以看到 delayload data 与一般 imports data 之间有着 definite 的 parallels.

delayload data 由 DataDirectory 中的 IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT entry 所指向. 此为 ImgDelayDescr structures 数组的 RVA, ImgDelayDescr 在 Visual C++ 的 DelayImp.H 中定义. Figure 7 为其内容. 每一个 delayload imported DLL 都有一个 ImgDelayDescr.

ImgDelayDescr 保存着 DLL 的 IAT 和 INT 的地址. 这些表与它们 imports 同胞的表在格式上是一样的, 只是它们是由 runtime library code 而不是 operating system 进行读写的. 当第一次从 delayloaded DLL 调用 API 时, runtime 就调用 LoadLibrary (若需要), 然后调用 GetProcAddress. 所得的地址存入 delayload IAT, 这样以后的调用就直接进入 API.

在最初的 Visual C++ 6.0 的实现中, 所有的 ImgDelayDescr 域所保存的地址都使用 virtual addresses, 而不是 RVAs. 也就是说, 所保存的是 delayload data 的实际地址. 这些域都是 DWORDs, 即 x86 上的指针的大小.

很快现在要对 IA-64 进行支持了. 突然间, 4 字节已不足以保存地址了. Ooops! 这时, Microsoft将包含地址的域改为了 RVAs. 如 Figure 7 所示, 我使用的是修改后的结构定义和名称.

还有一个问题就是需要确定 ImgDelayDescr 用的是 RVAs 还是 virtual addresses. 该结构有一个域保存着标志值, 当 grAttrs 域的 \"1\" bit 打开时, 结构的成员应视为 RVAs. 这是 Visual Studio.NET 和 64-bit compiler 引入的唯一的 option. 若 grAttrs 的那一位为关闭的, 则 ImgDelayDescr 域为 virtual addresses.


The Resources Section

PE 的所有 sections 里, 资源是最难掌握的. 这里, 我只讲一下用来得到诸如 icons, bitmaps 和 dialogs 这类 raw resource data 的数据结构. 我就不讨论 resource data 的实际格式了, 因为那些东西超出了本文的范围.

resources 都在一个叫 .rsrc 的 section 里. DataDirectory 的 IMAGE_DIRECTORY_ENTRY_RESOURCE entry 保存着 resources 的 RVA 和 size. 由于多种原因, resources 采用了与文件系统类似的组织方法――有目录和叶子节点.

DataDirectory 的 resource 指针指向一个 IMAGE_RESOURCE_DIRECTORY 类型的 structure. IMAGE_RESOURCE_DIRECTORY structure 保存着未用的 Characteristic, TimeDateStamp 和 version number 域. IMAGE_RESOURCE_DIRECTORY 中有意思的域只有 NumberOfNamedEntries 和 NumberOfIdEntries. 跟在每个 IMAGE_RESOURCE_DIRECTORY structure 后面的是一个 IMAGE_RESOURCE_DIRECTORY_ENTRY structures 的数组. 将 IMAGE_RESOURCE_DIRECTORY 中的 NumberOfNamedEntries 和 NumberOfIdEntries 加到一起就得到了 IMAGE_RESOURCE_DIRECTORY_ENTRYs的总数. (If all these data structure names are painful for you to read, let me tell you, it\'s also awkward writing about them!)

directory entry 要么指向另一个 resource directory 要么指向一个单个资源的数据. 当 directory entry 指向另一 resource directory 时, IMAGE_RESOURCE_DIRECTORY_ENTRY structure 的第二个 DWORD 的最高位置位且后31位为 resource directory 的 offset. 这个 offset 是相对于 resource section 起始点的 offset, 而不是 RVA.

当 directory entry 指向实际资源实例时, IMAGE_RESOURCE_DIRECTORY_ENTRY 第二个 DWORD 的最高位为0. 后面的31位为 resource instance(比如一个 dialog) 的 offset. 这个 offset 也是相对于 resource section 的, 而非 RVA. Directory entries 可以有个名字, 还可以用 ID 值相区分. 这是与 .RC 文件中的 resources 是一致的, 在 .RC 文件中可以为 resource instance 指定名字或 ID. 在 directory entry 中,当第一个 DWORD 的最高位置位时, 后31位为 resource 名字字符串的 offset 若最高位为0, 则低16位为 ordinal identifier.

Enough theory! 我们来看一下实际的 resource section 并解密其意义. Figure 8 为对ADVAPI32.DLL中的 resources 的简略的 PEDUMP output. 以 \"ResDir\" 开头的每一行都对应着一个 IMAGE_RESOURCE_DIRECTORY structure. \"ResDir\" 之后是 resource directory 的名字, 在括号里. 本例中, resource directories 的名字分别为 0, MOFDATA, MOFRESOURCENAME, STRING, C36, RCDATA 和 66. 名字之后的是 directory entries (both named and by ID)的 combined number. 本例中, 最上层的 directory 有三个中级的 directory entries, 所有其它的 directories 都只有一个 entry.

日常使用中, 最上层 directory 类似于 file system 的根目录. 每一个 \"root\" 下面的 directory entry 总是有一个 directory. 每个这样的 second-level directories 都对应着一种资源类型(strings tables, dialogs, menus 等等). 在每个 second-level \"resource type\" directories 下, 还可以找到 third-level subdirectories. 对每个 resource instance 都有一个 third-level subdirectory. 例如, 若有5个 dialogs, 就会有一个 second-level DIALOG directory, 其下又有5个 directory entries. 这5个 directory entries 的每一个本身又是一个 directory. directory entry 的名字对应着 resource instance 的名字或 ID. 每个 directory entries 下是一个单个项保存着 resource data 的偏移. 简单吗, 不简单
吗?

要是你读代码时学得更快的话, 一定要研究一下 PEDUMP(2002 年2月本文的 code download)的 resource dumping code. 除显示所有 resource directories 及它们的 entries 之外, 它还能 dumps out 几种更常见的 resource instances, 如dialogs.
 
Base Relocations

在一个 executable 中的很多地方, 你都会找到内存地址. 当链接一个 executable 时, 该 executable 会得到一个首选的加载地址. 只有在 executable 加载在 IMAGE_FILE_HEADER structure 的 ImageBase 域指定的首选加载地址上时, 这些内存地址才是正确的. 若 loader 需要将 DLL 加载在另外的地址上, 则 executable 中的所有地址都会无效. 若 executable 不能被加载在首选加载地址上, base relocations 会告诉 loader executable 中所有需要修改的地方. 幸运的是 loader 不需要知道地址使用的细节.它只需要知道有一组地址需要用某种一致的方法进行修改.

为了说清楚, 我们来看一下 x86-based 的例子. 假设有以下指令,该指令将局部变量(位于地址 0x0040D434)的值载入 ECX 寄存器:

00401020: 8B 0D 34 D4 40 00  mov ecx,dword ptr [0x0040D434]

这条指令位于地址 0x00401020 处, 长6字节. 前两个字节(0x8B 0x0D) 构成了指令的操作码.剩下的4个字节为一个 DWORD 地址(0x0040D434). 本例中, 此指令的 executable 的首选加载地址为 0x00400000. 因此全局变量的 RVA 为 0xD434.若 executable 确实加载在 0x00400000, 指令则按原样进行. 但假设 executable 被加载到了地址 0x00500000 上. 若是这样, 指令的最后的4个字节就需要改为 0x0050D434.

loader 是如何修改的呢? loader 比较首选的和实际的加载地址并计算差值. 本例中的差值就是 0x00100000. 这个差值会加到 DWORD-sized 的地址上来得到变量的新地址. 在前面的例子中, 地址 0x00401022 会有一个 base relocation, 这个地址就是指令中 DWORD 的位置. 简言之, base relocations 就是 executable 中的一组地址, 在 executable 中要向内存中已有的内容上加上一个差值. 只有在需要的时候 executable 的页才被载入内存, 而base relocations 的格式反映出这一点. base relocations 位于一个叫 .reloc 的 section, 但查找它们的正确的方法是用 DataDirectory 中的 IMAGE_DIRECTORY_ENTRY_BASERELOC entry.

Base relocations 是一组非常简单的 IMAGE_BASE_RELOCATION structures. VirtualAddress 域为 relocations 所属的内存区的 RVA. SizeOfBlock 域指示此 base 的 relocation 信息是由多少字节构成的, 其中包括 IMAGE_BASE_RELOCATION structure的大小.

紧随 IMAGE_BASE_RELOCATION structure 之后的是一组数目不定的 WORD 值. WORDs 的数量可从 SizeOfBlock 域得知每一个 WORD 由两部分构成.最高4位指示 relocation 的类型, 类型由 WINNT.H 中的 IMAGE_REL_BASED_xxx 值给出. 低12为偏移, 相对于 VirtualAddress 域, 对该域应使用 relocation.

在前面的 base relocations 的例子里, 我把事情简化了一点. 实际上 base relocations 以及应用的办法有好几种. 对于 x86 的 executables, 所有的 base relocations 都是 IMAGE_REL_BASED_HIGHLOW 类型的. 在一组 relocations 的结尾常会见到一种 IMAGE_REL_BASED_ABSOLUTE 类型的 relocation. 这些 relocations 什么也不做, 只是起个充数的作用, 这样下一个 IMAGE_BASE_RELOCATION 就能对齐到 4-byte 的 boundary上.

对于IA-64上的 executables, relocations 好像总是 IMAGE_REL_BASED_DIR64 类型的. 与 x86 的 relocations 一样,总有一个用于充数的 IMAGE_REL_BASED_ABSOLUTE. 有趣的是, 尽管 IA-64 EXEs 的页都是8KB, base relocations 却仍然用的是4KB的 chunks.

在 Visual C++ 6.0 里进行 release build 时, linker 省略掉了 EXEs 的 relocations. 这是因为 EXEs 是最先载入地址空间的, 因此就保证了其能被加载到首选加载地址. DLLs 就不这么幸运了, 因此总是要进行 base relocations, 除非有十足的把握敢用 /FIXED switch 把它省略掉. 在 Visual Studio.NET 里, linker 对 debug 和 release 的 EXE 文件都会省略 base relocations.

The Debug Directory

当生成的 executable 带有 debug 信息时, 可以自行选择是否包含信息格式的细节及其位置. operating system 并不需要这些信息来运行 executable, 但这些信息对开发工具来说是非常有用的. EXE 可以有多种形式的 debug 信息; 一个叫 debug directory 的数据结构指示都有哪些信息可用.

DebugDirectory 可在 DataDirectory 的 IMAGE_DIRECTORY_ENTRY_DEBUG 里找到. 它是由一个 IMAGE_DEBUG_DIRECTORY structures (见 Figure 9) 的数组构成的. 每类 debug 信息一个. debug directory的成员数目可由 DataDirectory 的 Size 域计算.

到目前为止, 最普遍的 debug 信息的格式就是 PDB 文件. PDB 文件由 CodeView-style 型的 debug 信息演化而来. PDB 信息由 IMAGE_DEBUG_TYPE_CODEVIEW 类型的 debug directory entry 指示. 若察看由此 entry 指向的数据, 就会发现一个小的 CodeView-style 的 header. 这种 debug 数据的大部分就是外部的 PDB 文件的path. 在 Visual Studio 6.0 里, 这个 debug header 开头是一个 NB10 的 signature. 在 Visual Studio.NET 里, 此 header 开头是一个RSDS.

在 Visual Studio 6.0 里, COFF 型的 debug 信息可由 /DEBUGTYPE:COFF linker switch 生成. 这个功能在 Visual Studio.NET 里被拿掉了. 优化的 x86 代码使用 Frame Pointer Omission (FPO) 的 debug 信息, 这时函数可能没有通常的 stack frame. FPO data 使得 debugger 能定位 local variables 和parameters.

两类 OMAP debug 信息只存在于 Microsoft 的 programs 里. Microsoft 的 internal tool 能够识别 executable files 里的 code 并因此最小化分页. (是的, 比 Working Set Tuner 还能干) OMAP 信息使得 tools 在 debug 信息的原始地址与被移动后的新地址间转换.
 
巧了, DBG 文件也包含一个如前所述的 debug directory. DBG 文件盛行于 Windows NT 4.0 时代, 主要包含一些 COFF 的 debug 信息. 然而, 因 Windows XP 的 PDB 文件, 它们已渐渐退出舞台.


The .NET Header

为 Microsoft .NET 环境生成的 executables 首先也主要是 PE 文件. 然而, 大多情况下的 .NET 文件中的 normal code and data 是最少的. .NET executable 的主要目的是使 .NET-specific 的信息, 如 metadata 和 intermediate language
(IL), 进入内存. 另外, .NET executable 要与 MSCOREE.DLL 链接.这个 DLL 是 .NET process 的起点. 当加载 .NET executable 时, 其入口点通常是一个小的代码 stub. 这个 stub 跳转至 MSCOREE.DLL 中的一个 exported function(_CorExeMain 或 _CorDllMain). MSCOREE从那儿进行接管, 并开始使用 executable 文件中的 metadata 和IL. 这个过程和Visual Basic (.NET版以前的)程序使用 MSVBVM60.DLL 类似. .NET 信息的起始点是 IMAGE_COR20_HEADER structure, 其在 .NET Framework SDK 的 CorHDR.H 和最近版本的 WINNT.H 中定义. IMAGE_COR20_HEADER 由 DataDirectory 的 IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR entry 指向. Figure 10 为 IMAGE_COR20_HEADER 的各域. metadata 的格式, method IL 和其它由 IMAGE_COR20_HEADER 指向的东西将在后续的文章中描述.


TLS Initialization

当使用用 __declspec(thread) 声明的 thread local variables 时, compiler 讲它们放进一个名为 .tls 的 section.
当系统发现新线程启动时, 就从 process heap 中分配内存来存放线程的 thread local variables. 这片内存用 .tls section 中的值初始化. 系统还要放一个指向在TLS数组中分配的内存的指针, 由FS:[2Ch](在x86上)指向. executable中是否有 thread local storage (TLS) 数据由 DataDirectory 中的 IMAGE_DIRECTORY_ENTRY_TLS entry 是否为 nonzero 来指示. 若为 nonzero, 则该 entry 指向一个 IMAGE_TLS_DIRECTORY structure, 如 Figure 11 所示.

一定要注意 IMAGE_TLS_DIRECTORY structure 中的地址是 virtual addresses, 而不是 RVAs. 因此若 executable 未加载到首选加载地址, 这些地址就会被 base relocations 修改. IMAGE_TLS_DIRECTORY 本身是在 .rdata section 中而不是在 .tls section 中.


Program Exception Data

一些计算机体系 (包括 IA-64) 不使用 x86 使用的 frame-based 的 exception handling , 而是使用 table-based 的. 在 table-based 的 exception handling 中有一个 table 包含每一个可能受 exception unwinding 影响的函数的信息. 每个函数的数据包括起始地址, 结束地址, 以及关于 exception 的位置和关于处理方法的信息. 当发生 exception 时, 系统查找此表来定位合适的 entry 并加以处理. exception table 是一个 IMAGE_RUNTIME_FUNCTION_ENTRY structures 的数组. 这个数组由 DataDirectory 中的 IMAGE_DIRECTORY_ENTRY_EXCEPTION entry 指向. IMAGE_RUNTIME_FUNCTION_ENTRY structure 的格式因体系不同而不同. 对于 IA-64, 形式如下:

DWORD BeginAddress;
DWORD EndAddress;
DWORD UnwindInfoAddress;

WINNT.H 中并未给出 UnwindInfoAddress data 的格式. 然而, 此格式可在 Intel 的 \"IA-64 Software Conventions
and Runtime Architecture Guide\" 的 Chapter 11 中找到.

--------------------------------------------------------------------------------


Figure 1 Section Names

Name Description
                              
.text           The default code section.

.data           The default read/write data section. Global variables typically go here.

.rdata          The default read-only data section. String literals and C++/COM vtables are
examples of items put into .rdata.

.idata          The imports table. It has become common practice (either explicitly, or via
linker default behavior) to merge the .idata section into another section,
typically .rdata. By default, the linker only merges the .idata section into
another section when creating a release mode executable.

.edata          The exports table. When creating an executable that exports APIs or data, the
linker creates an .EXP file. The .EXP file contains an .edata section that\'s
added into the final executable. Like the .idata section, the .edata section is
often found merged into the .text or .rdata sections.

.rsrc           The resources. This section is read-only. However, it should not be named
anything other than .rsrc, and should not be merged into other sections.

.bss            Uninitialized data. Rarely found in executables created with recent linkers.
Instead, the VirtualSize of the executable\'s .data section is expanded to make
enough room for uninitialized data.

.crt            Data added for supporting the C++ runtime (CRT). A good example is the function
pointers that are used to call the constructors and destructors of static C++
objects. See the January 2001 Under The Hood column for details on this.

.tls            Data for supporting thread local storage variables declared with
__declspec(thread). This includes the initial value of the data, as well as
additional variables needed by the runtime.

.reloc          The base relocations in an executable. Base relocations are generally only
needed for DLLs and not EXEs. In release mode, the linker doesn\'t emit base
relocations for EXE files. Relocations can be removed when linking with the
/FIXED switch.

.sdata          \"Short\" read/write data that can be addressed relative to the global pointer.
Used for the IA-64 and other architectures that use a global pointer register.
Regular-sized global variables on the IA-64 will go in this section.

.srdata         \"Short\" read-only data that can be addressed relative to the global pointer.
Used on the IA-64 and other architectures that use a global pointer register.

.pdata          The exception table. Contains an array of IMAGE_RUNTIME_FUNCTION_ENTRY
structures, which are CPU-specific. Pointed to by the
IMAGE_DIRECTORY_ENTRY_EXCEPTION slot in the DataDirectory. Used for
architectures with table-based exception handling, such as the IA-64. The only
architecture that doesn\'t use table-based exception handling is the x86.

.debug$S        Codeview format symbols in the OBJ file. This is a stream of variable-length
CodeView format symbol records.

.debug$T        Codeview format type records in the OBJ file. This is a stream of
variable-length CodeView format type records.

.debug$P        Found in the OBJ file when using precompiled headers.

.drectve        Contains linker directives and is only found in OBJs. Directives are ASCII
strings that could be passed on the linker command line. For instance:

  -defaultlib:LIBC

                Directives are separated by a space character.

.didat          Delayload import data. Found in executables built in nonrelease mode. In
release mode, the delayload data is merged into another section.

--------------------------------------------------------------------------------

Figure 2 IMAGE_EXPORT_DIRECTORY Structure Members

Size Member               Description

DWORD Characteristics         Flags for the exports. Currently, none are defined.

DWORD TimeDateStamp           The time/date that the exports were created. This field has the
same definition as the IMAGE_NT_HEADERS.FileHeader.
TimeDateStamp (number of seconds since 1/1/1970 GMT).

WORD MajorVersion            The major version number of the exports. Not used, and set to 0.

WORD MinorVersion            The minor version number of the exports. Not used, and set to 0.

DWORD Name                    A relative virtual address (RVA) to an ASCII string with the DLL
name associated with these exports (for example, KERNEL32.DLL).

DWORD Base                    This field contains the starting ordinal value to be used for
this executable\'s exports. Normally, this value is 1, but it\'s
not required to be so. When looking up an export by ordinal,
the value of this field is subtracted from the ordinal, with
the result used as a zero-based index into the Export Address
Table (EAT).

DWORD NumberOfFunctions       The number of entries in the EAT. Note that some entries may be
0, indicating that no code/data is exported with that ordinal
value.

DWORD NumberOfNames           The number of entries in the Export Names Table (ENT). This
value will always be less than or equal to the
NumberOf-Functions field. It will be less when there are
symbols exported by ordinal only. It can also be less if there
are numeric gaps in the assigned ordinals. This field is also
the size of the export ordinal table (below).
 
DWORD AddressOfFunctions      The RVA of the EAT. The EAT is an array of RVAs. Each nonzero
RVA in the array corresponds to an exported symbol.

DWORD AddressOfNames          The RVA of the ENT. The ENT is an array of RVAs to ASCII
strings. Each ASCII string corresponds to a symbol exported by
name. This table is sorted so that the ASCII strings are in
order. This allows the loader to do a binary search when
looking for an exported symbol. The sorting of the names is
binary (like the C++ RTL strcmp function provides), rather than
a locale-specific alphabetic ordering.

DWORD AddressOfNameOrdinals   The RVA of the export ordinal table. This table is an array of
WORDs. This table maps an array index from the ENT into the
corresponding export address table entry.  

--------------------------------------------------------------------------------

Figure 4 KERNEL32 Exports

exports table:
  Name:            KERNEL32.dll
  Characteristics: 00000000
  TimeDateStamp:   3B7DDFD8 -> Fri Aug 17 23:24:08 2001
  Version:         0.00
  Ordinal base:    00000001
  # of functions:  000003A0
  # of Names:      000003A0

  Entry Pt  Ordn  Name
  00012ADA     1  ActivateActCtx
  000082C2     2  AddAtomA
  
...remainder of exports omitted

--------------------------------------------------------------------------------

Figure 5 IMAGE_IMPORT_DESCRIPTOR Structure

Size Member Description

DWORD Characteristics Flags for the exports. Currently, none are defined.

DWORD TimeDateStamp The time/date that the exports were created. This field has the
same definition as the IMAGE_NT_HEADERS.FileHeader.
TimeDateStamp (number of seconds since 1/1/1970 GMT).

WORD MajorVersion The major version number of the exports. Not used, and set to 0.

WORD MinorVersion The minor version number of the exports. Not used, and set to 0.

DWORD Name A relative virtual address (RVA) to an ASCII string with the DLL
name associated with these exports (for example, KERNEL32.DLL).

DWORD Base This field contains the starting ordinal value to be used for
this executable\'s exports. Normally, this value is 1, but it\'s
not required to be so. When looking up an export by ordinal,
the value of this field is subtracted from the ordinal, with
the result used as a zero-based index into the Export Address
Table (EAT).

DWORD NumberOfFunctions The number of entries in the EAT. Note that some entries may be
0, indicating that no code/data is exported with that ordinal
value.

DWORD NumberOfNames The number of entries in the Export Names Table (ENT). This
value will always be less than or equal to the
NumberOf-Functions field. It will be less when there are
symbols exported by ordinal only. It can also be less if there
are numeric gaps in the assigned ordinals. This field is also
the size of the export ordinal table (below).

DWORD AddressOfFunctions The RVA of the EAT. The EAT is an array of RVAs. Each nonzero
RVA in the array corresponds to an exported symbol.

DWORD AddressOfNames The RVA of the ENT. The ENT is an array of RVAs to ASCII
strings. Each ASCII string corresponds to a symbol exported by
name. This table is sorted so that the ASCII strings are in
order. This allows the loader to do a binary search when
looking for an exported symbol. The sorting of the names is
binary (like the C++ RTL strcmp function provides), rather than
a locale-specific alphabetic ordering.

DWORD AddressOfNameOrdinals The RVA of the export ordinal table. This table is an array of
WORDs. This table maps an array index from the ENT into the
corresponding export address table entry.

--------------------------------------------------------------------------------

Figure 7 ImgDelayDescr Structure

Size Member Description
DWORD grAttrs The attributes for this structure. Currently, the only flag defined is
dlattrRva (1), indicating that the address fields in the structure
should be treated as RVAs, rather than virtual addresses.

RVA rvaDLLName An RVA to a string with the name of the imported DLL. This string is
passed to LoadLibrary.

RVA rvaHmod An RVA to an HMODULE-sized memory location. When the Delayloaded DLL is
brought into memory, its HMODULE is stored at this location.

RVA rvaIAT An RVA to the Import Address Table for this DLL. This is the same
format as a regular IAT.

RVA rvaINT An RVA to the Import Name Table for this DLL. This is the same format
as a regular INT.

RVA rvaBoundIAT An RVA of the optional bound IAT. An RVA to a bound copy of an Import
Address Table for this DLL. This is the same format as a regular IAT.
Currently, this copy of the IAT is not actually bound, but this feature
may be added in future versions of the BIND program.

RVA rvaUnloadIAT An RVA of the optional copy of the original IAT. An RVA to an unbound
copy of an Import Address Table for this DLL. This is the same format
as a regular IAT. Currently always set to 0.

DWORD dwTimeStamp The date/time stamp of the delayload imported DLL. Normally set to 0.

--------------------------------------------------------------------------------

Figure 8 Resources from ADVAPI32.DLL

Resources (RVA: 6B000)
ResDir (0) Entries:03 (Named:01, ID:02) TimeDate:00000000
    ―――――――――――――――――――――――――――――――
    ResDir (MOFDATA) Entries:01 (Named:01, ID:00) TimeDate:00000000
        ResDir (MOFRESOURCENAME) Entries:01 (Named:00, ID:01) TimeDate:00000000
            ID: 00000409  DataEntryOffs: 00000128
            DataRVA: 6B6F0  DataSize: 190F5  CodePage: 0
    ―――――――――――――――――――――――――――――――
    ResDir (STRING) Entries:01 (Named:00, ID:01) TimeDate:00000000
        ResDir (C36) Entries:01 (Named:00, ID:01) TimeDate:00000000
            ID: 00000409  DataEntryOffs: 00000138
            DataRVA: 6B1B0  DataSize: 0053C  CodePage: 0
    ―――――――――――――――――――――――――――――――
    ResDir (RCDATA) Entries:01 (Named:00, ID:01) TimeDate:00000000
        ResDir (66) Entries:01 (Named:00, ID:01) TimeDate:00000000
            ID: 00000409  DataEntryOffs: 00000148
            DataRVA: 85908  DataSize: 0005C  CodePage: 0

--------------------------------------------------------------------------------

Figure 9 Fields of IMAGE_DEBUG_DIRECTORY

Size Member Description
DWORD Characteristics Unused and set to 0.
DWORD TimeDateStamp The time/date stamp of this debug information (number of
seconds since 1/1/1970, GMT).

WORD MajorVersion The major version of this debug information. Unused.

WORD MinorVersion The minor version of this debug information. Unused.

DWORD Type The type of the debug information. The following types are the
most commonly encountered:
IMAGE_DEBUG_TYPE_COFF
IMAGE_DEBUG_TYPE_CODEVIEW // Including PDB files
IMAGE_DEBUG_TYPE_FPO // Frame pointer omission
IMAGE_DEBUG_TYPE_MISC // IMAGE_DEBUG_MISC
IMAGE_DEBUG_TYPE_OMAP_TO_SRC
IMAGE_DEBUG_TYPE_OMAP_FROM_SRC
IMAGE_DEBUG_TYPE_BORLAND // Borland format

DWORD SizeOfData The size of the debug data in this file. Doesn\'t count the size
of external debug files such as .PDBs.

DWORD AddressOfRawData The RVA of the debug data, when mapped into memory. Set to 0 if
the debug data isn\'t mapped in.

DWORD PointerToRawData The file offset of the debug data (not an RVA).

--------------------------------------------------------------------------------

Figure 10 IMAGE_COR20_HEADER Structure

Type Member Description
DWORD cb Size of the header in bytes.

WORD MajorRuntimeVersion The minimum version of the runtime required to
run this program. For the first release of .NET,
this value is 2.

WORD MinorRuntimeVersion The minor portion of the version. Currently 0.

IMAGE_DATA_DIRECTORY MetaData The RVA to the metadata tables.

DWORD Flags Flag values containing attributes for this image.
These values are currently defined as:

COMIMAGE_FLAGS_ILONLY // Image contains only IL
// code that is not
// required to run on a
// specific CPU.

COMIMAGE_FLAGS_32BITREQUIRED   // Only runs in
// 32-bit
// processes.
  
COMIMAGE_FLAGS_IL_LIBRARY
  STRONGNAMESIGNED // Image is signed with
// hash data.

COMIMAGE_FLAGS_TRACKDEBUGDATA // Causes the
// JIT/runtime to
// keep debug
// information
// around for
// methods

DWORD EntryPointToken Token for the MethodDef of the entry point for
the image. The .NET runtime calls this method to
begin managed execution in the file.

IMAGE_DATA_DIRECTORY Resources The RVA and size of the .NET resources.

IMAGE_DATA_DIRECTORY StrongNameSignature The RVA of the strong name hash data.

IMAGE_DATA_DIRECTORY CodeManagerTable The RVA of the code manager table. A code manager
contains the code required to obtain the state of
a running program (such as tracing the stack and
track GC references).

IMAGE_DATA_DIRECTORY VTableFixups The RVA of an array of function pointers that
need fixups. This is for support of unmanaged C++
vtables.
IMAGE_DATA_DIRECTORY ExportAddressTableJumps The RVA to an array of RVAs where export JMP thunks are written. These thunks allow managed
methods to be exported so that unmanaged code can
call them.

IMAGE_DATA_DIRECTORY ManagedNativeHeader For internal use of the .NET runtime in memory.
Set to 0 in the executable.

--------------------------------------------------------------------------------

Figure 11 IMAGE_TLS_DIRECTORY Structure

Size Member Description
DWORD StartAddressOfRawData The beginning address of a range of memory used to initialize a
new thread\'s TLS data in memory.

DWORD EndAddressOfRawData The ending address of the range of memory used to initialize a
new thread\'s TLS data in memory.

DWORD AddressOfIndex When the executable is brought into memory and a .tls section is
present, the loader allocates a TLS handle via TlsAlloc. It
stores the handle at the address given by this field. The runtime
library uses this index to locate the thread local data.

DWORD AddressOfCallBacks Address of an array of PIMAGE_TLS_CALLBACK function pointers.
When a thread is created or destroyed, each function in the list
is called. The end of the list is indicated by a pointer-sized
variable set to 0. In normal Visual C++ executables, this list is
empty.

DWORD SizeOfZeroFill The size in bytes of the initialization data, beyond the
initialized data delimited by the StartAddressOfRawData and
EndAddressOfRawData fields. All per-thread data after this range
is initialized to 0.

DWORD Characteristics Reserved. Currently set to 0.

--------------------------------------------------------------------------------







                                                                                                                                                                                                                                       
babybattle
驱动牛犊
驱动牛犊
  • 注册日期2004-07-12
  • 最后登录2005-10-17
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望0点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
沙发#
发布于:2005-03-12 08:26
虽然看不懂,但沙发还是要坐一下的。 :D
总于找到家了。。。。
游客

返回顶部