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

时间:2010-01-31 06:53:59

标签: assembly linker loader portable-executable

  

作为链接器输入的文件称为对象文件。   链接器生成图像文件,然后由加载器用作输入。

来自“ Microsoft可移植可执行文件和通用对象文件格式规范”的简介

  

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

     

在目标文件中,RVA较少   因为记忆位置有意义   未分配。在这种情况下,一个RVA   将是一个部分内的地址   (在本表后面描述),到   稍后应用重定位   在链接期间。为简单起见,a   编译器应该只设置第一个RVA   在每个部分为零。

     

VA(虚拟地址)。与RVA相同,除了基地址   不减去图像文件。该   地址被称为“VA”,因为   Windows创建了一个独特的VA空间   对于每个过程,独立于   物理内存。对于几乎所有   目的,应考虑VA   只是一个地址。 VA不是   可以预测为RVA因为   加载程序可能无法加载图像   首选地点。

即使在读完这篇文章之后,我仍然没有得到它。我有很多问题。任何人都可以用实际的方式解释它。请坚持使用Object File&的术语。 Image File如上所述。

我所知道的地址,就是那个

  • 在目标文件和图像文件中,我们都不知道确切的内存位置,
  • 生成对象文件时的汇编程序计算相对于.data和&部分的地址。 .text(用于函数名称)。
  • 将多个目标文件作为输入的链接器生成一个Image文件。在生成时,它首先合并每个目标文件的所有部分,并在合并它时重新计算相对于每个部分的地址偏移量。并且,没有像全球抵消那样的东西。

如果我所知道的事情有问题,请纠正我。

修改

在给弗朗西斯的答案后,我很清楚什么是物理地址,VA& RVA和它们之间有什么关系。

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

我在c:\WINDOWS\system32\kernel32.dll上使用PEView进行了检查,发现:

  1. RVA& FileOffset在Sections的开头是相同的。(.text是这个dll的第一部分)。
  2. .text.data的开头,.rsrc到最后一节的最后一个字节(.reloc)RVA& FileOffset是不同的。 &安培;同样,第一部分第一个字节的RVA“始终”显示为0x1000
  3. 有趣的是,每个部分的字节在FileOffset中是连续的。我的意思是另一个部分从一个部分的最后一个字节的下一个字节开始。但是如果我在RVA中看到同样的事情,那么这是一个区段的最后一个字节和下一节的第一个字节之间的巨大差距。
  4. 我的猜测:

    1. 全部,数据的字节数 在第一个之前(.text在这里) 部分是“不”实际加载 进入VA空间的过程,这些 数据字节只是用来 定位&描述这些部分。 他们可以被称为“meta section” 数据“。

      因为他们没有加载到VA 过程空间。用法 这个术语RVA也没有意义 RVA == FileOffset这些字节的原因。

    2. 因为,

      • RVA术语仅对将要实际加载的字节有效 进入VA空间。
      • .text.data.rsrc.reloc的字节数是这样的字节。
      • 而不是从RVA 0x00000开始,PEView软件正在启动 来自0x1000
    3. 我无法理解为什么第3次观察。我无法解释。

2 个答案:

答案 0 :(得分:62)

大多数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. Virtual Addreess(VA)与每个进程的物理地址(由OS管理)相关
  3. RVA与VA(文件库或部分库)相关,每个文件(由链接器和加载器管理)
  4. (编辑)关于爪子的新问题:

    方法/变量的RVA值并不总是偏离文件的开头。它们通常是相对于某些VA,可能是默认的加载基地址或部分基础VA - 这就是为什么我说你必须检查PE format spec的详细信息。

    您的工具,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文件开始”。

      因此,标头占用0x800字节的文件(当映射到内存时为0x1000),这是第1节的偏移量。然后通过对齐其数据(c44c1字节),我们得到physsize C4600。 C4600 + 800 = C4E00,这正是第二部分的偏移量。

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

    4. (编辑)让我再次制作一个新的简单摘要。

      1. DLL / EXE(PE格式)文件中的“RVA”通常相对于“内存中的加载基址”(但并非总是如此 - 您必须阅读规范)
      2. PE格式包含“section”映射结构,用于将物理文件内容映射到内存中。所以RVA并不是真正相对于文件偏移量。
      3. 要计算某个字节的RVA,您必须在该部分中找到其偏移并添加部分基础。

答案 1 :(得分:11)

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