C语言 GCC 链接器:在指定部分中移动符号



可以在特定部分中移动代码中的某些函数在可执行文件上?如果是这样,如何?

对于使用 gcc 编译的应用程序,我们有更多的源文件,包括X.c.每个对象都是从关联的源编译的(X.o 是从 X.c 获取的(,链接器生成一个大的可执行文件。

我需要 X.c 中的两个函数位于可执行文件,说.magic_section。我想要这个的原因是该部分将加载到另一个内存区域中,而不是其余部分。

我的问题是我无法更改源 X.c,否则我会使用特定标志,例如 __attribute__ ((section ("magic_section")))函数。

我在链接器

文档中阅读了一些内容,并为链接器编写了自定义脚本,但我未能指定必须将特定符号放置在哪个部分中。我只设法移动了整个部分。

在你可以做的路上(不是很好,但理论上应该工作(是使用 --function-sections--data-sections ,假设你的 GCC 版本/架构支持这些选项,然后手动调用所有需要使用链接器脚本进入给定文件中的函数和变量。

这将创建称为类似事物.text.function_name.data.variable_name的部分。 如果您熟悉通过 gcc 属性分配部分,我假设您知道在链接器中要做什么。

作为一个优势,如果您实际上不希望整个文件进入魔术部分,这将允许您挑选功能。

不幸的是,如果不修改二进制对象、动态链接器或动态加载器,您将无法完成此操作,无论如何,这是一项非常艰巨的任务。

选项 1 - ELF 操作

每个ELF可执行文件都是由包含实际代码/数据/符号字符串/...以及帮助加载器决定在内存中加载代码的位置、此 ELF 公开的符号、需要从其他位置加载哪些符号、在哪里加载特定代码/数据等的段。

您可以通过键入来观察二进制文件中的段

readelf -l [你的二进制文件]

输出将类似于以下内容(我选择 ls 作为二进制文件(:

[ishaypeled@ishay-dev bin]$ readelf -l --wide ./ls

Elf file type is EXEC (Executable file)
Entry point 0x4048bf
There are 9 program headers, starting at offset 64
Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  PHDR           0x000040 0x0000000000400040 0x0000000000400040 0x0001f8 0x0001f8 R E 0x8
  INTERP         0x000238 0x0000000000400238 0x0000000000400238 0x00001c 0x00001c R   0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x000000 0x0000000000400000 0x0000000000400000 0x01b694 0x01b694 R E 0x200000
  LOAD           0x01bdf0 0x000000000061bdf0 0x000000000061bdf0 0x000864 0x0016d0 RW  0x200000
  DYNAMIC        0x01be08 0x000000000061be08 0x000000000061be08 0x0001f0 0x0001f0 RW  0x8
  NOTE           0x000254 0x0000000000400254 0x0000000000400254 0x000044 0x000044 R   0x4
  GNU_EH_FRAME   0x01895c 0x000000000041895c 0x000000000041895c 0x00071c 0x00071c R   0x4
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10
  GNU_RELRO      0x01bdf0 0x000000000061bdf0 0x000000000061bdf0 0x000210 0x000210 R   0x1
 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id 
   06     .eh_frame_hdr 
   07     
   08     .init_array .fini_array .jcr .dynamic .got 

现在让我们检查一下此输出:

在第一个表(程序标头(中:
[类型] - 段类型,本节
的目的是什么[偏移量] - 此段开始
的文件中的偏移量[VirtAddr] - 我们想在进程地址空间中加载此部分的位置(如果应该加载此段,则并非所有段都已加载(
[PhysAddr] - 与我遇到的
所有现代操作系统的 VirtAddr 相同[FileSiz] - 存档的此部分有多大。这是指向您的部分的链接 - 当前段由偏移到偏移+文件Siz
范围内的所有部分组成[MemSiz] - 虚拟内存中的此部分有多大(这不必与文件的大小相同!如果它超出文件中的大小,则超出部分设置为 0(
[Flg] - 权限标志,R 读取 E-执行 W 写入。[对齐] - 内存中所需的内存对齐。

您的重点是 LOAD (PT_LOAD( 类型的段。这些段对节中的数据进行分组,指示加载程序将它们放在进程地址空间中的什么位置,并确定指定它们的权限。

您可以在部分到分段映射

表中看到方便的部分到分段映射。

让我们观察两个 LOAD 段 2 和 3:
我们可以看到段 2 具有读取和执行权限,并且它跨越(除其他外(.text 和 .rodata 部分。

因此,要使用 ELF 操作实现您的目的:

  1. 在文件中找到构成函数的二进制数据(readelf 实用程序是你的朋友(
  2. 通过修改 ELF 标头(我不知道任何工具可以自动执行此操作,您可能必须编写自己的工具(将包含 .text 部分的段拆分为两个连续的 LOAD 段,省略您的函数代码
  3. 通过修改 ELF 标头创建一个仅包含两个函数的新 LOAD 段
  4. 将对旧函数位置的所有引用(如果有(更新为新函数位置

如果你读到这里并理解了所有内容,你应该知道这对于现实生活中的案例来说是一项非常乏味、几乎不可能完成的任务。

选项 2 - 动态链接器操作请注意上面示例中的 INTERP 段类型。这是一个 ASCII 字符串,用于指定应使用哪个动态链接器。
动态链接器角色是分析段并执行所有动态操作,例如在运行时解析符号、从 .so 文件加载段等。

此处可能的操作是修改动态链接器代码(注意:这是系统范围的更改!(,以将函数二进制数据加载到进程地址空间中的特定内存地址中。请注意,这种方法有几个缺点:

  1. 它需要修改动态链接器
  2. 您仍然需要更新 ELF 文件中对函数的所有引用

选项 3 - 动态加载程序操作与选项 2 非常相似,但修改 ld 库工具而不是动态链接器。

结论确切地说,你想做的事情是非常困难的,而且确实是一项乏味的任务。我正在开发一种工具,该工具目前允许将任意函数注入到现有的共享对象文件中,我保证这至少需要几周的工作。
你确定没有其他方法可以实现你想要的吗?为什么需要在单独的地址中使用这两个函数? 也许有一个更简单的解决方案...

最新更新