VA(虚拟地址)和RVA(相对虚拟地址)

69
给链接器作为输入的文件称为目标文件。链接器生成一个映像文件,然后由加载器用作输入。 来自Microsoft Portable Executable and Common Object File Format Specification的简介 RVA(相对虚拟地址)。在映像文件中,加载到内存中后的项的地址,减去映像文件的基址。RVA几乎总是与其在磁盘上的文件位置(文件指针)不同。
在对象文件中,RVA的意义较小,因为未分配内存位置。在这种情况下,RVA将是部分内部地址(稍后在此表中描述),在链接期间应用重定位。为简单起见,编译器应将每个部分中的第一个RVA设置为零。 VA(虚拟地址)。与RVA相同,除了不减去映像文件的基址。该地址称为“VA”,因为Windows为每个进程创建一个独立的VA空间,独立于物理内存。对于几乎所有目的,VA应被视为仅仅是一个地址。VA不像RVA那么可预测,因为加载器可能不会在其首选位置加载映像。
即使我读了这篇文章,我仍然不明白。我有很多问题。请用实际的方式解释一下。请遵守所述的Object FileImage File的术语。
关于地址,我知道的只有:
- 在Object File和Image File中,我们都不知道确切的内存位置,因此, - 当汇编器生成Object File时,会相对于各个部分(.data和.text)计算地址。 - 当链接器将多个Object File作为输入生成一个Image file时,它首先合并每个Object File的所有部分,并在合并时重新计算相对于每个部分的地址偏移量。而且,没有全局偏移量。
如果我的理解有误,请纠正我。
编辑:
阅读Francis给出的答案后,我清楚了物理地址、VA和RVA是什么以及它们之间的关系。
所有变量和方法的RVA必须在重定位期间由链接器计算。 因此,(方法/变量的RVA值) == (它相对于文件开头的偏移量)?应该是正确的。但令人惊讶的是,它不是这样的。 为什么?
我使用PEView检查了c:\WINDOWS\system32\kernel32.dll上的内容,发现:
  1. RVA和FileOffset在节的开始之前是相同的(`.text`是这个dll中的第一个节)。
  2. 从`.text`开头到`.data`,`.rsrc`直到最后一个节(`.reloc`)的最后一个字节,RVA和FileOffset是不同的。同时,第一个节第一个字节的RVA始终显示为`0x1000`。
  3. 有趣的是,每个节的字节在FileOffset上都是连续的。我的意思是,另一个节从节的最后一个字节的下一个字节开始。但是,如果我在RVA中看到相同的事情,那么在某个节的最后一个字节和下一个节的第一个字节的RVA之间存在巨大的间隙。
  1. 所有在第一个 (.text 这里) 部分之前的数据字节实际上并没有加载到进程的 VA 空间中,这些数据字节仅用于定位和描述这些部分。它们可以被称为 "元部分数据"。

    由于它们没有加载到进程的 VA 空间中,因此 RVA 术语的使用也是无意义的。这就是为什么对于这些字节,RVA == FileOffset 的原因。

  2. 由于:

    • RVA 术语仅适用于那些实际加载到 VA 空间中的字节。
    • .text.data.rsrc.reloc 的字节属于这种字节。
    • PEView 软件从 0x1000 开始而不是从 RVA 0x00000 开始。
  3. 我无法理解第三个观察结果。我无法解释。

2个回答

90
大多数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 可能会发生变化。
关于您的猜测,它们非常接近正确答案。
  1. 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.

  2. 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.

  3. 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...

(编辑) 让我再次提供一个简单的概述。

  1. 在DLL/EXE(PE格式)文件中,“RVA”通常是相对于“内存中的加载基地址”的(但不总是-您必须阅读规范)
  2. PE格式包含一个“节”映射结构,将物理文件内容映射到内存。因此,RVA实际上并不是相对于文件偏移量的。
  3. 要计算某个字节的RVA,您必须找到它在节中的偏移量并添加节基址。

谢谢您提供如此清晰的解释。只有一个建议,能否在 RVA 示例中将“Process A”的名称更改为“Process X”。因为 DLL A 和 Process A 可能会引起混淆。 :) - claws
1
我已经将DLL A更改为DLL C。还优化了一些文本部分以防止混淆。 - Francis
我并没有完全理解SectionAlignmentFileAlignmentbordersPhysicalSize的概念。我尝试阅读PE规范,但其中也没有太多具体说明。如果您能的话,请详细解答一下,或者推荐一些好的链接。非常感谢您花费时间和精力来帮助我。 :) - claws
4
RVA(相对虚拟地址)重定位是如何完成的?链接器/装载器是否会物理地遍历文件并重新编写所有“jmp”、“jne”等指令(我相当确定这是不可能的,因为x86使用多字节指令,不可能始终可靠地反汇编)?还是.dll中的所有地址都存储在数组或类似结构中? - BlueRaja - Danny Pflughoeft
我认为.reloc节用于此事。 - user1156544
显示剩余5条评论

15

相对虚拟地址(RVA)是文件加载地址的偏移量。一个例子可能是最简单的方式来理解这个概念。假设你有一个文件(如,DLL),它被加载到地址1000h。在该文件中,你有一个变量在RVA 200h。在这种情况下,该变量的虚拟地址(在DLL映射到内存后)是1200h(即DLL的1000h基址加上变量到RVA(偏移)的200h)。


2
你有一个位于 RVA 200h 的变量。这是相对于什么的?是相对于 DLL 的开头(DLL 的第一个字节)还是相对于它所在的节(在这种情况下是 .data)? - claws
3
大多数 RVA(相对虚拟地址)是相对于文件开头给出的,但有时候(特别是查看目标文件而不是可执行文件时),你会看到一个基于节的 RVA。 - Jerry Coffin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接