Microsoft便携式可执行文件和公用对象文件格式规范”的摘要



即使阅读了这篇文章,我还是听不懂。我有很多问题。任何人都可以用一种实用的方式来解释它。请遵循所声明的Object FileImage File的术语。

我只知道地址是

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

  • 如果我所知道的有什么问题,请纠正我。

    编辑:

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

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

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

    直到各节开始为止,
  • RVA和FileOffset都是相同的。(.text是此dll中的第一节)。
  • .text的开始到.data.rsrc到最后一节的最后一个字节(.reloc)的RVA和FileOffset是不同的。并且第一部分的第一个字节的RVA也总是显示为0x1000
  • 有趣的是,每个节的字节在FileOffset中是连续的。我的意思是另一个部分从一个部分的最后一个字节的下一个字节开始。但是,如果我在RVA中看到相同的内容,则这是一个节的最后一个字节与下一个节的第一个字节之间的RVA之间的巨大差距。

  • 我猜:
  • 全部,之前的数据字节
    在第一个之前(此处为.text)
    部分实际未加载
    进入VA空间的过程中,这些
    字节的数据只是用来
    找到并描述这些部分。
    他们可以称为“元部分
    数据”。

    由于它们未加载到VA中
    过程空间。的用法
    术语RVA也没有意义,这是
    这些字节为何使用RVA == FileOffset的原因。

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

    大多数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 header 仍将加载,直到节 header 结束。节标题和节主体(如果有)之间的间隙将不会加载。您可以通过调试器进行检查。此外,如果各节之间有间隙,则可能不会加载它们。
  • 正如我所说,RVA只是“相对于某些VA”,无论它是什么VA(尽管在谈论PE时,VA通常指的是负载基址)。阅读PE格式规范时,您可能会发现一些“RVA”,它相对于某些特殊地址(例如资源起始地址)是相对的。 PEView列表RVA从0x1000开始是因为该部分从0x1000开始。为什么是0x1000?由于链接器为PE header 保留了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 header RVA = 0000,SIZE = 1000”,它迫使.text从RVA 1000开始。这些段在加载到内存(即VA)时应该是连续的,因此它们的RVA是连续的。但是,由于内存是按页面分配的,因此它将是页面大小的倍数(4096 = 0x1000字节)。这就是为什么#2部分从1000 + C5000 = C6000(C5000来自C44C1)开始的原因。

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

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

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

  • (编辑)让我再次做一个新的简单总结。
  • DLL / EXE(PE格式)文件中的“RVA”通常相对于“在内存中加载基址”(但并非总是如此-您必须阅读规格)
  • PE格式包含一个“部分”映射结构,用于将物理文件内容映射到内存中。因此,RVA并不真正与文件偏移有关。
  • 要计算某个字节的RVA,您必须在节中找到其偏移量并添加节库。
  • 关于assembly - VA(虚拟地址)和RVA(相对虚拟地址),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/2170843/

    10-10 15:54