程序启动时默认的寄存器状态是什么(汇编语言,Linux)?

23
当程序启动(Linux ELF可执行文件)时,eaxebx等寄存器中是否有零,还是可以有其他值存在?
在我的机器上,这些寄存器被清零,但当编写汇编程序时,在新进程中是否可以依靠这样的行为,我能否信赖?

2
在正常情况下,您应该明确地初始化它们。因此,它们的初始状态并不重要。 - Brent Bradburn
1
在什么情况下,保存这么多代码会有所作用?只需初始化它们。如果 EAX 的高位不重要,则可以使用 movb al,1 初始化它,但不必担心空间。 - Ira Baxter
3
示例场景(例如):我知道movb al,1或类似于xor这样的指令 - 但这只是几个字节的操作码 - 因此,如果可以避免使用它们,我会这样做。 - user1190294
1
ARM的类似问题:https://dev59.com/XkrSa4cB1Zd3GeqPW4Xx - Ciro Santilli OurBigBook.com
1
就像你永远不应该期望未初始化的变量为零一样,你也永远不应该期望寄存器在使用之前处于某种状态,也不应该期望 RAM 处于某种状态。除了明确定义的传递参数外,你永远不应该在写入之前读取某些内容。 - old_timer
显示剩余2条评论
3个回答

24

这完全取决于每个平台的ABI。由于您提到了 eaxebx,让我们来看看对于 x86(截至 Linux v5.17.5)的情况。在 fs/binfmt_elf.c 中,load_elf_binary() 内核会检查ABI是否在程序加载时指定了任何寄存器值的要求

/*
 * The ABI may specify that certain registers be set up in special
 * ways (on i386 %edx is the address of a DT_FINI function, for
 * example.  In addition, it may also specify (eg, PowerPC64 ELF)
 * that the e_entry field is the address of the function descriptor
 * for the startup routine, rather than the address of the startup
 * routine itself.  This macro performs whatever initialization to
 * the regs structure is required as well as any relocations to the
 * function descriptor entries when executing dynamically links apps.
 */

然后调用ELF_PLAT_INIT,该宏在arch/xxx/include/elf.h中为每种架构定义。对于x86架构,它执行以下操作

#define ELF_PLAT_INIT(_r, load_addr)        \
    do {                                    \
        _r->bx = 0; _r->cx = 0; _r->dx = 0; \
        _r->si = 0; _r->di = 0; _r->bp = 0; \
        _r->ax = 0;                         \
    } while (0)

当你的静态链接ELF二进制文件在Linux x86上加载时,可以指望所有寄存器值都等于零。但是这并不意味着你应该这样做。 :-)


动态链接

请注意,执行动态链接的二进制文件实际上会在进程中运行动态链接器代码,然后才能到达你的_start(ELF入口点)。正如ABI允许的那样,这可能会在寄存器中留下垃圾,除了堆栈指针ESP / RSP和atexit hook EDX / RDX之外。


3
这种清零操作仍在发生(2015年),但不是ABI所要求的。(请查看Ciro在Basile答案下的评论。) - Peter Cordes
1
还要注意,在动态链接的可执行文件中,动态链接器在 _start 之前运行,并且允许ABI在寄存器中留下垃圾。只有静态链接的可执行文件在执行到 _start 时才将其寄存器清零。 - Peter Cordes
1
更具体地说,将所有寄存器(除了%esp)设置为0是在Linux 2.2.0中引入的(请参见https://asm.sourceforge.net/articles/startup.html)。在此之前,只有%edx在`ELF_PLAT_INIT`中显式设置为0(或atexit),而%eax在其他地方隐式设置。 - pts

12
对于 Linux 上的 AMD64 或 x86-64 系统(64 位),x86-64 ABI 定义了寄存器的初始内容。
类似的规范也适用于 i386 ABIARM ABI 等。
请参阅维基百科关于 ELFABI 的页面。

10

x86-64 System V ABI

3.4.1 "初始栈和寄存器状态" (Basile连接到此PDF版本):

  1. %rsp 指向栈

    堆栈指针保存最低地址的字节的地址,它是在进程入口时保证16字节对齐的

  2. %rdx 是一个函数指针,如果非零应该由应用程序与 atexit 注册。

    应用程序应该注册的函数指针。

  3. %rbp 没有特定值,但用户空间应将其设置为基础帧。

    该寄存器的内容在进程初始化时未指定,但用户代码应通过将帧指针设置为零来标记最深的堆栈帧。

  4. 其他一切都未定义。

因为LSB这样说,Linux遵循这个规范。


1
%rdx允许为空,在当前Linux中,从静态链接的可执行文件启动新进程时,它为0。但是,在执行动态链接的可执行文件时,动态链接器在_start之前运行,因此它可以在%rdx中设置一个值(根据gdb /bin/bash)。我不确定_start此时是否仍应将其视为函数指针,但可能是的,因为此时的其他所有内容都遵循ABI。 - Peter Cordes
2
i386 ABI(http://www.sco.com/developers/devspecs/abi386-4.pdf)对应的32位寄存器有相同的要求:`%esp`,`%edx`,`%ebp`,其他所有内容都是未定义的。 - pts
更新:是的,非零的%rdx将是一个指向运行动态链接库析构函数的函数指针。在动态可执行文件中,ld-linux.so将其传递给主可执行文件的_start,以便它可以使用atexit进行注册。ABI的进程入口保证在动态可执行文件中完全适用于_start。(包括大多数寄存器实际上可以保存垃圾;这种情况与静态可执行文件不同,在静态可执行文件中,内核实际上会将寄存器清零。) - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接