首页 文章

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

提问于
浏览
44

作为链接器输入的文件称为对象文件 . 链接器生成一个Image文件,该文件又被加载器用作输入 .

来自“ 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 的术语 .

我所知道的就是地址

  • 在目标文件和图像文件中,我们都不知道确切的内存位置,所以,

  • 生成对象文件时的汇编程序计算相对于 .data.text (用于函数名称)的部分的地址 .

  • 链接器将多个目标文件作为输入生成一个Image文件 . 在生成时,它首先合并每个目标文件的所有部分,并在合并它时重新计算相对于每个部分的地址偏移量 . 并且,没有像全球抵消那样的东西 .

如果我知道有什么问题,请纠正我 .

EDIT:

在阅读弗朗西斯给出的答案之后,我清楚了解物理地址,VA和RVA是什么以及它们之间的关系 .

所有变量和方法的RVAs必须在重定位期间由链接器计算 . 那么, (the value of RVA of a method/variable) == (its offset from the beginning of the file) ?一定是真的 . 但令人惊讶的是,它没有 . 为什么这样?

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

  • RVA和FileOffset在Sections开头之前是相同的 . ( .text 是此dll中的第一部分) .

  • .text 开始到 .data.rsrc 直到最后一节的最后一个字节( .reloc )RVA和FileOffset不同 . 并且第一部分的第一个字节的RVA是"always",显示为 0x1000

  • 有趣的是每个部分的字节在FileOffset中是连续的 . 我的意思是另一个部分从一个部分的最后一个字节的下一个字节开始 . 但是如果我在RVA中看到同样的事情,那么这是一个部分的最后一个字节和下一部分的第一个字节之间的巨大差距 .

我猜:

  • 全部,第一个( .text 此处)部分之前的数据字节实际上是"not"加载到进程的VA空间中,这些数据字节仅用于定位和描述这些部分 . 它们可以被称为"meta section data" .

因为它们没有加载到VA空间的过程中 . 术语RVA的用法也毫无意义,这就是为什么 RVA == FileOffset 这些字节的原因 .

  • 因为,

  • RVA术语仅对将实际加载到VA空间的那些字节有效 .

  • .text.data.rsrc.reloc 的字节是这样的字节 .

  • 而不是从RVA 0x00000 开始,PEView软件从 0x1000 开始 .

  • 我无法理解为什么第3次观察 . 我无法解释 .

2 回答

  • 57

    大多数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相关 .

    总结一下,

    • 物理内存地址是CPU看到的

    • 虚拟地址(VA)与每个进程的物理地址相关(由OS管理)

    • RVA相对于VA(文件库或节基),每个文件(由链接器和加载器管理)

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

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

    您的工具,PEView正在尝试显示每个字节的RVA以加载基址 . 由于这些部分从不同的基部开始,因此当穿过部分时RVA可能变得不同 .

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

    • 通常我们不会在部分之前讨论“RVA”,但是PE Headers 仍将被加载到部分 Headers 的末尾 . 节 Headers 和节主体(如果有)之间的间隙不会被加载 . 您可以通过调试器进行检查 . 但是,如果各部分之间存在一些差距,则可能无法加载 .

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

    • 您错过的是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 Headers 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加载有关,所以可能有点难以理解......

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

    • DLL / EXE(PE格式)文件中的"RVA"通常是相对于"load base address in memory"(但并非总是如此 - 您必须阅读规范)

    • PE格式包含"section"映射结构,用于将物理文件内容映射到内存中 . 因此RVA并不是真正相对于文件偏移量 .

    • 要计算某个字节的RVA,您必须在该部分中找到其偏移量并添加部分基数 .

  • 10

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

相关问题