66

作为链接器输入的文件称为Object File。链接器生成一个Image 文件,该文件又用作加载程序的输入。

Microsoft Portable Executable and Common Object File Format Specification ”的简介

RVA(相对虚拟地址)。在图像文件中,项目加载到内存后的地址,减去图像文件的基地址。一个项目的 RVA 几乎总是不同于它在磁盘上文件中的位置(文件指针)。

在目标文件中,RVA 的意义不大,因为没有分配内存位置。在这种情况下,RVA 将是一个段内的地址(稍后在此表中描述),稍后在链接期间对其应用重定位。为简单起见,编译器只需将每个部分中的第一个 RVA 设置为零。

VA(虚拟地址)。与 RVA 相同,只是不减去图像文件的基地址。该地址被称为“VA”,因为 Windows 为每个进程创建了一个不同的 VA 空间,独立于物理内存。对于几乎所有目的,一个 VA 应该被认为只是一个地址。VA 不像 RVA 那样可预测,因为加载器可能不会在其首选位置加载图像。

即使读了这篇文章,我仍然不明白。我有很多问题。任何人都可以用实际的方式解释它。Object File请按照说明遵守&的术语Image File

我所知道的地址是

  • 在目标文件和图像文件中,我们都不知道确切的内存位置,所以,
  • 汇编器在生成目标文件时计算相对于部分.data&的地址.text(对于函数名称)。
  • 将多个目标文件作为输入的链接器会生成一个图像文件。在生成时,它首先合并每个目标文件的所有部分,并在合并时重新计算相对于每个部分的地址偏移量。而且,没有什么比全局偏移量更好的了。

如果我所知道的有什么不对的地方,请纠正我。

编辑:

在阅读了弗朗西斯的回答后,我很清楚什么是物理地址、VA 和 RVA 以及它们之间的关系。

所有变量和方法的 RVA 必须在重定位期间由链接器计算。那么,(方法/变量的 RVA 值)==(它与文件开头的偏移量)?一定是真的。但令人惊讶的是,它不是。为什么这样?

我通过使用PEView检查 了这一点c:\WINDOWS\system32\kernel32.dll,发现:

  1. RVA 和 FileOffset 直到 Sections 的开头都是相同的。(.text是这个 dll 中的第一部分)。
  2. .textthrough的开始.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就是这样的字节。
    • 0x00000PEView 软件不是从 RVA开始,而是从0x1000.
  3. 我不明白为什么第三次观察。我无法解释。

4

2 回答 2

84

大多数 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(文件库或节库),每个文件(由链接器和加载器管理)

(编辑)关于爪的新问题:

方法/变量的 RVA 值并不总是它与文件开头的偏移量。它们通常与某些 VA 相关,这可能是默认加载基地址或节基 VA - 这就是为什么我说您必须检查PE 格式规范以获取详细信息。

您的工具 PEView 正在尝试显示每个字节的 RVA 以加载基地址。由于截面从不同的基数开始,因此在交叉截面时 RVA 可能会变得不同。

关于您的猜测,它们非常接近正确答案:

  1. 通常我们不会在节之前讨论“RVA”,但 PE 头仍然会被加载,直到节头结束。不会加载节标题和节正文(如果有)之间的间隙。您可以通过调试器进行检查。此外,当部分之间有一些间隙时,它们可能不会被加载。

  2. 正如我所说,RVA 只是“相对于某个 VA”,无论它是什么 VA(尽管在谈论 PE 时,VA 通常指的是加载基址)。当您阅读 PE 格式规范时,您可能会发现一些“RVA”,它与某些特殊地址(如资源起始地址)相关。从 0x1000 开始的 PEView 列表 RVA 是因为该部分从 0x1000 开始。为什么是 0x1000?因为链接器为PE头留下了0x1000字节,所以RVA从0x1000开始。

  3. 您错过的是 PE 加载阶段的“部分”概念。PE 可能包含几个“段”,每个段映射到一个新的起始 VA 地址。例如,这是从 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
    

    有一个不可见的“0 标题 RVA=0000, SIZE=1000”,它强制 .text 从 RVA 1000 开始。这些部分在加载到内存(即 VA)时应该是连续的,因此它们的 RVA 是连续的。但是,由于内存是按页面分配的,因此它将是页面大小的倍数(4096=0x1000 字节)。这就是为什么 #2 部分从 1000 + C5000 = C6000 开始(C5000 来自 C44C1)。

    为了提供内存映射,这些部分仍必须按一定大小对齐(文件对齐大小 - 由链接器决定。在我上面的示例中,它是 0x200=512 字节),它控制着 PhysSize 字段。偏移量的意思是“到物理 PE 文件开始的偏移量”。

    所以 headers 占用文件的 0x800 字节(映射到内存时为 0x1000),这是第 #1 节的偏移量。然后通过对齐其数据(c44c1 字节),我们得到 physsize C4600。C4600+800 = C4E00,正好是第二段的偏移量。

    好的,这与整个 PE 加载内容有关,所以可能有点难以理解......

(编辑)让我再做一个新的简单总结。

  1. DLL/EXE(PE 格式)文件中的“RVA”通常与“在内存中加载基地址”相关(但并非总是如此——您必须阅读规范)
  2. PE 格式包含一个“节”映射结构,用于将物理文件内容映射到内存中。所以 RVA 并不是真正相对于文件偏移量。
  3. 要计算某个字节的 RVA,您必须在节中找到它的偏移量并添加节基。
于 2010-02-01T01:44:29.980 回答
12

相对虚拟地址是从加载文件的地址的偏移量。获得这个想法的最简单方法可能是举个例子。假设您有一个在地址 1000h 加载的文件(例如,一个 DLL)。在该文件中,您在 RVA 200h 处有一个变量。在这种情况下,该变量的 VA(在 DLL 映射到内存之后)是 1200h(即 DLL 的 1000h 基地址加上变量的 200h RVA(偏移量)。

于 2010-01-31T07:01:24.780 回答