参考项目:https://code.google.com/p/portable-executable-library/
1. RVA与VA,File Offset
参考:http://www.pediy.com/kssd/pediy10/61737.html
VA是内存中的真实的虚拟地址值;
RVA是VA相对于ImageBase的相对偏移;
而File Offset就是在PE映像文件中相对于文件开始处的偏移。
PE文件的布局与最终加载到内存中的布局是不完全相同的,主要是因为有一些对齐的要求。
在加载到内存时,粒度是section,也就是说一个section里面的内容会被加载到内存中连续的一片区域。
因此,在一个section内部,所有内容的RVA/VA/File Offset是遵循相同的函数关系的。
这个函数关系的信息,是保存在PE文件的section header结构中的。
PE内部的地址都是以RVA形式保存的,从RVA转换到VA很简单,只需要获取到PE加载到内存中的ImageBase。
虽然PE头部也会指定其“钟意”的基地址,但是很有可能最终没有被加载到这里。
从RVA到File Offset的转换就是与具体的section相关的了。
PE Bliss对于RVA到File Offset的转换代码如下:
//RVA to RAW file offset convertion (4gb max)
uint32_t pe_base::rva_to_file_offset(uint32_t rva) const
{
//Maybe, RVA is inside PE headers
if(rva < get_size_of_headers())
return rva;
const section& s = section_from_rva(rva);
return s.get_pointer_to_raw_data() + rva - s.get_virtual_address();
}
因为一个section里的内容的file offset与rva之间的偏移关系是相同的,即
A.rva - A.offset = B.rva - B.offset
即
A.offset = B.rva + A.rva - B.offset
将B选取为这个section的头部,而A是该section中的任意一点,那么A.offset就可以通过上面的公式求出。
2. 导入表
导入表揭示了PE文件对于其他PE文件的依赖关系。
当一个PE文件需要使用其他PE文件定义的函数或者变量时,需要知道这些内容在内存中的具体加载地址。
这个信息是在链接过程中写入到PE文件中,要保证这个信息的正确,必须对依赖的那些PE文件的布局有了解,这也是为什么一个PE文件在链接时,需要指明它所依赖的其他PE文件的lib文件,因为lib文件就提供了这些相对位置的信息。
这些相对位置,其实就是函数或者变量在其他PE文件中的RVA。
3. 为什么PE文件中使用的地址都用RVA形式表示
因为PE真正关心的是当它自己被拉起来运行时,它需要的内容要到内存中的哪个位置去找,而不是要到文件中的哪个位置去找。因此它关心其实是VA,而一旦相关映像被加载到内存中了,那么它们的ImageBase也就确定了,所以知道了RVA,就知道了VA。