【问题标题】:Using ".init_array" section of ELF file使用 ELF 文件的“.init_array”部分
【发布时间】:2016-06-20 00:41:21
【问题描述】:

当程序启动时(Linux上)需要运行一段代码时,如何正确使用可执行文件(ELF32-i386)的.init_section?我有以下代码(GNU Assembler),它有ctor 初始化函数,这个函数的地址放在.init_array 部分:

.intel_syntax noprefix
.data
s1: .asciz "Init code\n"
s2: .asciz "Main code\n"
.global _start
.global ctor
.text
ctor:
    mov     eax, 4           # sys_write()
    mov     ebx, 1           # stdout
    mov     ecx, offset s1 
    mov     edx, 10
    int     0x80
    ret

.section .init_array
    .long ctor

.text
_start:
    mov     eax, 4
    mov     ebx, 1
    mov     ecx, offset s2
    mov     edx, 10
    int     0x80

    mov     eax, 1
    mov     ebx, 0
    int     0x80

这段代码是用:

as -o init.o init.asm
ld -o init init.o

运行生成的可执行文件时,仅打印“主代码”字符串。如何正确使用 .init_array 部分?

EDIT1:我想使用 .init_array 因为有多个源文件有自己的初始化代码。可以在启动时“手动”调用所有这些代码,并在每次将源文件添加到项目中或从项目中删除时对其进行修改,但 .init_array 似乎就是为这种情况而设计的:

在将控制权转移到应用程序之前,运行时链接器 处理在应用程序中找到的任何初始化部分和任何 加载的依赖项。初始化部分 .preinit_array, .init_array 和 .init 由链接编辑器在动态时创建 对象已构建。

运行时链接器执行地址包含在 .preinit_array 和 .init_array 部分。这些功能是 执行的顺序与它们的地址出现在 数组。

如果在没有 gcc 的情况下创建可执行文件,链接器似乎不会执行启动代码。我尝试编写自己的标准初始化例程,该例程读取 .init_array 部分中的函数指针并调用它们。它适用于一个文件,其中一个可以标记部分的结尾,例如,用零。但是对于多个文件,这个零可以重新定位在该部分的中间。如何正确确定由多个源文件组合而成的节的大小?

【问题讨论】:

  • gnu ld 默认链接器脚本确实在某些系统上自动提供符号__init_array_start 和__init_array_end,因此您也许可以使用它们。 YMMV。
  • @Jester,谢谢,这可能是公认的答案。

标签: linux assembly elf gnu-assembler


【解决方案1】:

如果您按照自己的方式制作静态链接裸可执行文件,并在_start 入口点使用您自己的代码,您的代码就会从该点运行。如果你想让某事发生,你的代码必须让它发生。没有魔法。

使用节将来自多个源文件的启动代码组合在一起很有用,因此所有启动代码都是冷的并且可能被分页,或者至少不需要 TLB 条目。

因此,您可以通过将函数放在那里并从在 _start 之后某个时间运行的代码中调用它们来“正确使用”部分。

在您的代码示例中,.init_array 看起来是一个函数指针列表。我假设标准 CRT 启动文件读取 ELF 文件并找到该部分的长度,然后遍历它对这些函数进行间接调用。由于您正在编写自定义代码,因此只需调用一个可以执行所有操作的 init 函数会更快。


动态链接:

“运行时链接器”是动态二进制文件的 ELF 解释器。它在 _start 之前在您的进程中运行代码,所以是的,显然它确实处理了 ELF 部分并让魔法发生。


因此,作为对您的编辑的回应,您的选择是:自己实现对.init_array 的处理,或者创建动态可执行文件。我很确定这个过程已经在其他问题中讨论过了,而且我没有时间为仍然没有链接 libc 的动态可执行文件研究正确的命令行。 (尽管您可能只想使用gcc -nostartfiles 或其他东西。)

如果您遇到困难,请发表评论。无论如何,我可能会在以后有更多时间时更新此内容,或者随时在工作命令中进行编辑。

【讨论】:

  • @AlBerger:无论如何,这是获取更多信息的更好地方。有趣的是,我不知道让构造函数运行的确切机制。更新了我的答案。
  • 我实现了从 .init_array 读取函数指针的启动机制,但仅当存在具有 .init_array 部分的单个源文件时。当有多个文件时,问题是如何确定 .init_array 节的大小(如何知道节中有多少个函数指针)。也许有一个标准和简单的方法,但我不知道(我是Assembler的新手。)。
  • @AlBerger:所以您不想制作动态可执行文件?我认为您想自己阅读 ELF 标头。请注意,当您的可执行文件被映射到内存中时,_start 在任何地址,之前文件的字节都在内存中,至少在该页面中。我不确定依赖它是否是个好主意。但对于自定义解决方案,您可能只需将标记值 (0) 作为最后一个元素。在您的构建脚本/Makefile 中,确保最后链接带有 NULL 条目的文件。那么你就不需要读取 ELF 标头的启动代码了。
  • 不幸的是,最后一个文件中的哨兵引入了额外的依赖项来控制和记住。如果没有类似 API 或其他直接方法来确定节大小,我想我会使用某种内部计数器,它在将函数指针添加到 .init_array 的文件中递增。
  • 关于动态链接,我也试过了:用“-lc -e main -I/lib/ld-linux.so.2”链接,但是启动代码没有被调用。我想必须与 gcc 链接才能自动执行启动代码。
【解决方案2】:

对于普通的 C 程序,.init_array 被一个在 main 调用之前从_start 调用的函数遍历。 this 网站上有一个很好的描述。

所以我看到了两种方法:您可以简单地链接到 glibc 起始代码。或者你必须自己找出另一种机制来解决这个问题。

【讨论】:

    猜你喜欢
    • 2012-05-15
    • 2014-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-08
    • 2011-05-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多