大多数Windows进程(*.exe)都加载在内存地址0x00400000(用户模式),这就是我们所说的“虚拟地址”(VA)——因为它们仅对每个进程可见,并且将被操作系统转换为不同的物理地址(由内核/驱动程序层可见)以供使用。
例如,可能的物理内存地址(由CPU可见):
0x00300000 on physical memory has process A's main
0x00500000 on physical memory has process B's main
操作系统可能会有一个映射表:
process A's 0x00400000 (VA) = physical address 0x00300000
process B's 0x00400000 (VA) = physical address 0x00500000
当你在A进程中尝试读取0x004000000时,你将得到位于物理内存0x00300000的内容。
关于RVA,它的设计目的是为了方便重定位。在加载可重定位模块(例如DLL)时,系统会尝试通过进程内存空间来滑动它。因此,在文件布局中,它放置了一个“相对”地址以帮助计算。
例如,DLL C可能具有以下地址:
RVA 0x00001000 DLL C's main entry
当被加载到基地址为0x10000000的进程A中时,C的主入口变为
VA = 0x10000000 + 0x00001000 = 0x10001000
(if process A's VA 0x10000000 mapped to physical address was 0x30000000, then
C's main entry will be 0x30001000 for physical address).
当在基地址0x32000000被加载到进程B中时,C的主要入口变为
VA = 0x32000000 + 0x00001000 = 0x32001000
(if process B's VA 0x32000000 mapped to physical address was 0x50000000, then
C's main entry will be 0x50001000 for physical address).
通常情况下,图像文件中的 RVA 在加载到内存时相对于进程基地址,但某些 RVA 可能相对于图像或对象文件中“节”起始地址(必须查看 PE 格式规范以获取详细信息)。无论哪种情况,RVA 都是相对于“某个”基础 VA 的。
总之:
1. 物理内存地址是 CPU 所见的地址。
2. 虚拟地址(VA)相对于物理地址,每个进程有一个(由操作系统管理)。
3. RVA 相对于 VA(文件基地址或节基地址),每个文件有一个(由链接器和加载器管理)。
关于 claw 的新问题:
方法/变量的 RVA 值并不总是相对于文件开头的偏移量。它们通常是相对于某个 VA 的,该 VA 可能是默认加载基址或节基址 - 这就是为什么我说你必须查看
PE 格式规范 以获取详细信息。
您的工具 PEView 正试图显示每个字节相对于加载基地址的 RVA。由于各个节的起始位置不同,当跨越节时,RVA 可能会发生变化。
关于您的猜测,它们非常接近正确答案。
Usually we won't discuss the "RVA" before sections, but the PE header will still be loaded until the end of section headers. Gap between section header and section body (if any) won't be loaded. You can examine that by debuggers. Moreoever, when there's some gap between sections, they may be not loaded.
As I said, RVA is simply "relative to some VA", no matter what VA it is (although when talking about PE, the VA usually refers to the load base address). When you read thet PE format spec you may find some "RVA" which is relative to some special address like resource starting address. The PEView list RVA from 0x1000 is because that section starts at 0x1000. Why 0x1000? Because the linker left 0x1000 bytes for PE header, so the RVA starts at 0x1000.
What you've missed is the concept of "section" in PE loading stage. The PE may contain several "sections", each section maps to a new starting VA address. For example, this is dumped from win7 kernel32.dll:
# Name VirtSize RVA PhysSize Offset
1 .text 000C44C1 00001000 000C4600 00000800
2 .data 00000FEC 000C6000 00000E00 000C4E00
3 .rsrc 00000520 000C7000 00000600 000C5C00
4 .reloc 0000B098 000C8000 0000B200 000C6200
There is an invisible "0 header RVA=0000, SIZE=1000" which forced .text to start at RVA 1000. The sections should be continuous when being loaded into memory (i.e., VA) so their RVA is continuous. However since the memory is allocated by pages, it'll be multiple of page size (4096=0x1000 bytes). That's why #2 section starts at 1000 + C5000 = C6000 (C5000 comes from C44C1).
In order to provide memory mapping, these sections must still be aligned by some size (file alignment size - decide by linker. In my example above it's 0x200=512 bytes), which controls the PhysSize field. Offset means "offset to physical PE file beginning".
So the headers occupy 0x800 bytes of file (and 0x1000 when being mapped to memory), which is the offset of section #1. Then by aligning its data (c44c1 bytes), we get physsize C4600. C4600+800 = C4E00, which is exactly the offset of second section.
OK, this is related to whole PE loading stuff so it may be a little hard to understand...
(编辑) 让我再次提供一个简单的概述。
- 在DLL/EXE(PE格式)文件中,“RVA”通常是相对于“内存中的加载基地址”的(但不总是-您必须阅读规范)
- PE格式包含一个“节”映射结构,将物理文件内容映射到内存。因此,RVA实际上并不是相对于文件偏移量的。
- 要计算某个字节的RVA,您必须找到它在节中的偏移量并添加节基址。
SectionAlignment
、FileAlignment
、borders
和PhysicalSize
的概念。我尝试阅读PE规范,但其中也没有太多具体说明。如果您能的话,请详细解答一下,或者推荐一些好的链接。非常感谢您花费时间和精力来帮助我。 :) - claws