资源,比如对话框、菜单、图标等等,都存储在IMAGE_DIRECTORY_ENTRY_RESOURCE(“资源目录项”)指向的数据目录中。它们处于一个至少“IMAGE_SCN_CNT_INITIALIZED_DATA(已初始化数据内容节)”和“IMAGE_SCN_MEM_READ(内存可读节)”标志位都被置为1的节中。
资源的基础是“资源目录”(IMAGE_RESOURCE_DIRECTORY);它包含好几个“资源目录项”(IMAGE_RESOURCE_DIRECTORY_ENTRY),其中的每一项反过来又可能指向一个“资源目录”。按照这种方式,你就得到一个以“资源目录项”为树叶的“资源目录”树;它们的树叶指向实际的资源数据。
在实际使用中,情况会稍微简单些。一般你不会遇到不可能理清的特别复杂的树的。
通常,它的层次结构是这样的:一个目录作为根。它指向很多目录,每种资源类型都有一个。这些目录又指向子目录,每个子目录都有一个名字或者ID号并指向这个资源所提供的各种语言的目录;每种语言你都能找到一个资源项,资源项最终指向(具体的)数据。(注意:多语言资源不能在Win95上运行。即使程序有好几种语言,Win95也总是使用相同的资源----我没有查出是哪一种,但我猜测肯定是它最先碰到的那种。多语言资源在NT系统上可以运行。)
没有指针的树大致象这样:
( 根 )
|
+----------------+------------------+
| | |
菜单 对话框 图标
| | |
+-----+-----+ +-+----+ +-+----+----+
| | | | | | |
"main" "popup" 0x10 "maindlg" 0x100 0x110 0x120
| | | | | | |
+---+-+ | | | | | |
| | default english default def. def. def.
german english
一个“资源目录项”(IMAGE_RESOURCE_DIRECTORY)包含:
32位未使用标志,叫做“特征”(Characteristics);
32位“时间日期戳”(同样按常用的time_t表示法),告诉你资源被创建的时间(如果此项被设置的话);
16位“主版本”(MajorVersion)和16位“最小版本”(MinorVersion),以允许你据此维护资源的几个版本;
16位“已命名项目数”(NumberOfNamedEntries)和另一个16位的“ID项目数”(NumberOfIdEntries)。
紧随此结构后的是“已命名项目数”+“ID项目数”两结构体,它们都是“资源目录项”格式,都以名字开头。它们可能指向下一个“资源目录”或者指向实际的资源数据。
一个“资源目录项”由下面组成:
32位单元提供你它所描述的资源的ID或者是目录;
32位的到数据的偏移量或者是到下一个子目录的偏移量。
ID的含义取决于树中的层次;ID可能是一个数字(如果最高位为0)也可能是一个名字(如果最高位为1)。如果是一个名字,它的低31位就是从资源节原始数据的开始到这个名字(名字有16位长并由unicode的宽字符而不是0结尾符作为结束)的偏移量。
如果你位于根目录之中,且如果ID是一个数字的话,那么它指的就是下面的一种资源类型:
1: 光标
2: 位图
3: 图标
4: 菜单
5: 对话框
6: 字串表
7: 字体目录
8: 字体
9: 快捷键
10: 未格式化资源数据
11: 信息表
12: 组光标
14: 组图标
16: 版本信息
任何其它数字都是用户自定义的。任何有类型名的资源类型也是用户自定义的。
如果你处于(树的)下一层当中,此时ID一定是一个数字,且就是资源的一个特例的语言ID号;例如,你可以(同时)拥有澳大利亚英语、加拿大法语和瑞士德语等本地化形式的对话框,并且它们分享同一个资源ID。系统会根据线程的地点来选择要使用的对话框,反过来地点又反映了用户的“区域设置”。(如果资源找不到线程地点,系统将先使用一个中性的子语言资源作为地点,比如它将寻找标准法语而不是用户所拥有的加拿大法语;如果它还是找不到,就使用最小语言ID号的那个实例。必须注意,所有这些只工作于NT系统之上的。)
为便于辨认语言ID,使用宏PRIMARYLANGID()(意为“主语言ID”)和SUBLANGID()(意为“子语言ID”)将它分开为主语言ID和子语言ID,分别使用它的0-9位和10-15位。这些值定义在“winresrc.h”文件中。
语言资源只支持快捷键、对话框、菜单、资源数据或字符串等;其它资源类型必须为LANG_NEUTRAL/SUBLANG_NEUTRAL(中性语言/中性子语言)。
要确定资源目录的下一层是不是另一个目录,你可查看它的偏移量的最高位。如果它是1,剩下的31位就是从资源节原始数据的开始到下一层目录的偏移量,还是按“资源目录”后接“资源目录项”的格式。如果高位为0,它就是从资源节原始数据的开始到资源的原始数据描述,即一个资源数据项的偏移量。资源的原始数据描述包含32位的“OffsetToData”(到数据的偏移量)(指的是到原始数据的偏移量,从资源节原始数据的开头算起),32位的数据的“Size”(大小),32位的“CodePage”(代码页)和一个未使用的32位单元。
(不鼓励使用代码页,你应该使用“语言”的特性来支持多地域。)
原始数据格式依赖于资源类型;详细的介绍可在微软的SDK文档中找到。注意:除了用户自定义资源,资源中的任何字符串总是按UNICODE格式,明显的,用户自定义的资源按的是开发者选定的格式。
9.重定位(relocations)
-----------------------
我将要描述的最后一个数据目录是基址重定位目录。它是由可选头数据目录中的IMAGE_DIRECTORY_ENTRY_BASERELOC(基址重定位目录项)项来指向的。典型的,它包含在自己的节中,名字象“.reloc”这样,并且IMAGE_SCN_CNT_INITIALIZED_DATA(已初始化数据内容节)、 IMAGE_SCN_MEM_DISCARDABLE(内存可丢弃节)和IMAGE_SCN_MEM_READ(内存可读节)等标志位被置1。
如果映象文件不能被加载到可选头中提到的优先载入地址“ImageBase”(映象基址)时,重定位数据对加载器来说就是必须的。此时,链接器所提供的固定地址就不再有效,并且加载器将不得不对静态变量、字符串文字等使用的绝对地址进行修正。
所谓重定位目录就是一些连续的块,每一块都包含4K映象文件的重定位信息。块由一个“IMAGE_BASE_RELOCATION(基址重定位)”结构体开始,这个结构体包含一个32位的“VirtualAddress(虚拟地址)”项和一个32位的“SizeOfBlock(块大小)”项。跟在它们后面的就是块的实际重定位数据,每一条都是16位的。
“VirtualAddress(虚拟地址)”就是重定位所在块需要应用的基本的RVA;“SizeOfBlock(块大小)”就是整个块的字节大小;跟在后面的重定位的数目是:('SizeOfBlock'-sizeof(IMAGE_BASE_RELOCATION))/2个。当你碰到一个“VirtualAddress(虚拟地址)”值为0的“IMAGE_BASE_RELOCATION(基址重定位)”结构体时,重定位信息就结束了。
每一个16位的重定位信息由低12位的重定位位置和高4位的重定位类型组成。要得到重定位的RVA,你需要用这个12位的位置加上“IMAGE_BASE_RELOCATION(基址重定位)”中的“VirtualAddress(虚拟地址)”。类型是下面之一:
IMAGE_REL_BASED_ABSOLUTE (0)
这种不需操作;用于将块按32位边界对齐。位置应该为0。
IMAGE_REL_BASED_HIGH (1)
重定位的高16位必须被用于被偏移量所指向的那个16位的WORD单元,此WORD是一个32位的DWORD的高位WORD。
IMAGE_REL_BASED_LOW (2)
重定位的低16位必须被用于被偏移量所指向的那个16位的WORD单元,此WORD是一个32位的DWORD的低位WORD。
IMAGE_REL_BASED_HIGHLOW (3)
重定位的全部32位必须应用于上面所说的全部32位。这种(和不需操作的第“0”种)是我在二进制文件种实际发现的仅有的重定位类型。
IMAGE_REL_BASED_HIGHADJ (4)
这是一种复杂的。请自己参阅(参考文献[6]),并努力弄懂它的意思:“高调整。这种修正要求一个全32位值。高16位定位于偏移量处,低16位定位在下一个数组元素(此数组元素包括在大小的域中)的偏移量处。它们两个需要被连成一个有符号的变量。加上32位的增量。然后加上0x8000并将有符号变量的高16位存储在偏移量处的16位域中。”
IMAGE_REL_BASED_MIPS_JMPADDR (5)
不清楚
IMAGE_REL_BASED_SECTION (6)
不清楚
IMAGE_REL_BASED_REL32 (7)
不清楚
举一个例子,如果你发现重定位信息是
0x00004000 (32位, 开始的RVA)
0x00000010 (32位, 块的大小)
0x3012 (16位的重定位数据)
0x3080 (16位的重定位数据)
0x30f6 (16位的重定位数据)
0x0000 (16位的重定位数据)
0x00000000 (下一块的RVA)
0xff341234
你知道第一块描述的重定位开始于RVA 0x4000处,有16字节长。因为头用掉了8字节,并且一个重定位要用2字节,所以块中计有(16-8)/2=4个重定位。第一个重定位被应用于0x4012处的DWORD,第二个于0x4080处的DWORD,第三个于0x40f6处的DWORD。最后一个不需操作。
下一块的RVA是0,列表结束。
好,你怎么处理一个重定位呢?
你能知道映象文件“被”重定位到可选头“ImageBase(映象基址)”的优先载入地址;你也能知道你真正载入的地址。如果它们相同,你什么也不用做。如果它们不同,你需计算出实际基址-优先基址的差并加上重定位位置的值(有符号,可能为负值),此值你可通过上面讲述