【问题标题】:Does the .so file still contain infomation about label.so 文件是否仍包含有关标签的信息
【发布时间】:2021-08-28 12:45:10
【问题描述】:

编译器在哪个编译阶段将label替换为实际的addr

我理解像jmp abc 这样的指令,其中abc 只是一个注释,最终会被替换为实际地址,是吗?

最终的.so 文件是否仍然包含有关label 的信息,或者label 在内存中加载时被替换为实际的addr

【问题讨论】:

    标签: linux compilation arm reverse-engineering


    【解决方案1】:

    TL;DR - 你的问题很难回答,因为它混合了几个概念。对于典型的汇编标签,我们使用 PC 相关标签,标签在汇编时解析。对于其他“外部”标签,情况很多,解决方法视情况而定。


    在几乎所有 CPU 上,尤其是在 ARM 上,有四种概念性的方法。

    1. PC 相对地址。当前指令 +/- 偏移量。
    2. 绝对地址。这是您在概念上考虑的那个。
    3. 注册计算地址。在运行时计算。 ldr pc, [rn, #xx]
    4. 基于表的寻址。全局偏移表等。很像寄存器计算地址。 ldr pc, [Rbase, Rindex, lsl #2]

    前两个适合单个指令并且非常有效。第一个是最理想的,因为代码可以在 ANY 地址执行,只要它保持其原始布局(即,您不会通过拆分代码来加载它)。

    在上表中,还有“构建时间”和“运行时间”的概念。区别在于 linkerloader 之间的区别。您已标记此“linux”并引用“so”或共享库。另外,您指的是汇编程序“标签”。它们是非常相似的概念,但可以不同,因为它们是上述四类寻址之一。

    在汇编程序中最常见的是,标签是相对于 PC 的。除了保持代码块的连续性外,无需使用 PC relative 实现额外的结构。如果汇编器是“模块”(编译单元,用于编译)或由汇编器处理并生成“对象”,它将使用 PC 相对寻址。

    对象格式可以用外部地址进行注释,并且在汇编程序如何输出这些地址方面有很多选择。它们通常由“伪操作”控制。那是目标文件中的注释(具有定义格式的单独部分);这种形式的指令是半完整的。它可能准备使用偏移表,使用基于寄存器的计算(如r9+constant)等。

    对于典型的链接情况(在构建时完成),我们将使用 PC 相对或绝对。如果我们将二进制文件修复为仅在一个地址上运行,则汇编器可以设置绝对寻址并通过链接解决这些问题。在这种情况下,二进制文件必须加载到固定地址。汇编程序“模块”或目标文件可以完全粘合在一起以解决所有问题。然后没有“加载”时间修复。其他决定因素是代码/数据是否分开,以及系统是否使用 MMU。通常需要保持代码不变,以便许多进程可以使用相同的 RAM/ROM 页,但它们将具有单独的数据。除了内存效率之外,这还可以提供某种形式的安全性(尽管它不是非常强大),它可以防止意外的代码覆盖,并以 SIGSEGV 的形式提供调试帮助。

    可以编写一个与 PC 相关的初始化例程,该例程将进行修复以在您自己的二进制文件中创建一个表。所以“加载器”只是为了确定你在哪里运行,然后进行计算。对于静态共享库,您通常知道要运行的库,但不知道它们在哪里。对于动态共享库,您甚至可能在编译时都不知道要运行的库是什么。

    Linux 发行版可以使用其中任何一种。如果您有某种标准的 Linux 桌面发行版(Ubuntu/Debian、Redhat 等)。您将拥有基于 ARM ELF LSB 和动态共享库的内容。您需要使用汇编器伪操作来支持这种类型的寻址或使用编译器来为您完成。共享库中的大多数“标签”都是 PC 相关的,不会出现。出于调试原因 (man strip) 可能会显示一些标签,而在运行时解析地址是绝对需要的。

    我还问了一个我前段时间发现相关的问题,Using GCC pre-processor as an assembler...所以关键概念是汇编程序通常是“两次通过”,需要进行这些本地地址修复。然后这个问题询问我们在哪里添加共享库的第二级概念 A/B。如果您想了解更多信息,在线书籍Linkers and Loaders 是一个很好的资源。

    另见:

    【讨论】:

      【解决方案2】:

      最终的可执行文件必须包含所有地址,否则它将无法工作。

      要记住的是有静态链接和动态链接(例如使用共享库)。在静态链接二进制文件的情况下,所有地址都已解析。在加载过程中解析动态链接地址的情况下,而二进制文件具有由动态链接器替换为实际地址的重定位信息。但是到一天结束时,内存中加载的二进制文件具有所有地址。

      编译器在编译的哪个阶段将label替换成 实际地址

      当编译器知道目标地址时,它可以用实际地址替换。例如,这是对同一编译单元中的函数的调用。

      当目标地址在编译单元之外并且编译器无法访问时,编译器会在目标文件中留下重定位信息。链接器同时将其替换为内存中的实际地址。

      【讨论】:

      • 这是一个很好的答案,但“可执行文件必须具有所有地址”是不正确的。事实上,没有地址的技术允许许多恶意软件和共享库发挥作用。如果您没有“加载程序”,那么这是正确的。加载器可以作为您自己代码的一部分存在,或者您可以使用“ldd”或其他一些系统加载器来确定运行时的最终地址。对于大多数嵌入式程序员(裸机),我们不会使用加载器。加载器只是以各种方式执行运行时修复的简单代码。
      猜你喜欢
      • 2013-01-13
      • 1970-01-01
      • 2012-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多