GCC链接器:在指定的部分中移动符号

时间:2015-03-24 09:42:19

标签: c gcc linker symbols relocation

可以在特定部分中移动代码中的某些功能 在可执行文件?如果是这样,怎么样?

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

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

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

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

2 个答案:

答案 0 :(得分:3)

你可以做的事情可能做到(不是很好,但理论上应该工作)是使用--function-sections--data-sections,假设你的GCC版本/架构支持这些选项,然后手动调用所有的功能&需要使用链接描述文件在给定文件中的变量。

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

作为一个优势,如果你实际上并不希望整个文件进入神奇的部分,那么你可以选择功能。

答案 1 :(得分:0)

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

选项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 

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

在第一个表(Program Headers)中:
[类型] - 段类型,本节的目的是什么
[偏移] - 此段开始的文件中的偏移量 [VirtAddr] - 我们要在流程地址空间中加载此部分(如果应该加载此段,而不是所有段都被加载)
[PhysAddr] - 与我遇到的所有现代操作系统的VirtAddr相同 [FileSiz] - 此部分存档有多大。这是指向您的部分的链接 - 当前部分包含Offset to Offset + FileSiz的所有部分 [MemSiz] - 虚拟内存中的这个部分有多大(这不一定与文件大小相同!如果它超出文件中的大小,则多余设置为0)
[Flg] - 权限标志,R读取E执行W写入。 [对齐] - 内存中所需的内存对齐。

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

您可以在Section to Segment映射表中看到一个方便的分段映射部分。

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

所以,为了达到使用ELF操作的目的:

  1. 找到使您的函数在文件中的二进制数据(readelf实用程序是您的朋友)
  2. 通过修改ELF标题(我不知道任何自动执行此操作的工具,您可能需要编写自己的标题)将包含.text段的段拆分为两个连续的LOAD段,而忽略不计你的功能代码
  3. 通过修改ELF标题,创建一个仅包含两个函数的新LOAD段
  4. 将旧功能位置的所有引用(如果有)更新为新功能
  5. 如果你读到这里并了解一切,你应该知道这对于现实生活中的案件来说是一项非常繁琐,几乎不可能完成的任务。

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

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

    1. 需要修改动态链接器
    2. 您需要更新ELF文件中对函数的所有引用
    3. 选项3 - 动态加载程序操作 与选项2非常相似,但修改ld库设施而不是动态链接器。

      <强>结论 你想要做的事情非常困难,而且确实是一项繁琐的工作。我正在开发一种工具,允许将任意函数注入到现有的共享对象文件中,我保证这至少可以在几个星期内工作。
      你确定没有其他方法可以实现你想要的吗?为什么在单独的地址中需要这两个功能?也许有一个更简单的解决方案...