Linux ARM上程序寄存器和堆栈的初始状态

7
我目前在Linux上使用ARM汇编进行学习练习。我使用的是“裸”汇编,即没有libcrt或libgcc。有没有人能给我提供关于程序开始执行第一条指令之前堆栈指针和其他寄存器状态的信息?显然,pc/r15指向_start,其余部分似乎被初始化为0,有两个例外:sp/r13指向程序远离的地址,r1指向稍高的地址。
所以有一些具体的问题:
  • r1中的值是多少?
  • sp中的值是否是内核分配的合法堆栈?
  • 如果不是,分配堆栈的首选方法是什么?使用brk还是分配静态.bss节?
非常感谢您的指点。
4个回答

8

由于这是Linux,您可以查看内核如何实现它。

调用start_thread时似乎设置了寄存器,该调用在load_elf_binary结尾处(如果您使用的是现代Linux系统,则几乎总是使用ELF格式)。对于ARM,寄存器似乎设置如下:

r0 = first word in the stack
r1 = second word in the stack
r2 = third word in the stack
sp = address of the stack
pc = binary entry point
cpsr = endianess, thumb mode, and address limit set as needed

显然,您有一个有效的堆栈。我认为r0-r2的值是垃圾,您应该从堆栈中读取所有内容(稍后您将看到我为什么这样认为)。现在,让我们看看堆栈上有什么。您将从堆栈中读取的内容由create_elf_tables填充。

这里有一件有趣的事情要注意,那就是这个函数与架构无关,因此在每个基于ELF的Linux架构上,大部分相同的内容都会被放在堆栈上。以下是按照您将要阅读的顺序排列的堆栈内容:

  • 参数数量(在main()中为argc)。
  • 每个参数的C字符串指针,后跟一个零(这是main()argv的内容;argv将指向这些指针中的第一个)。
  • 每个环境变量的C字符串指针,后跟一个零(这是main()的第三个参数envp的内容,很少见到;envp将指向这些指针中的第一个)。
  • “辅助向量”,它是一系列成对出现的值(类型后跟值),以零(AT_NULL)作为第一个元素终止。此辅助向量具有一些有趣且有用的信息,您可以通过使用设置为1LD_SHOW_AUXV环境变量运行任何动态链接程序来查看它们(例如LD_SHOW_AUXV=1 /bin/true)。这也是可能会因架构而有所不同的地方。

由于这个结构对于每个架构都是相同的,您可以查看SYSV 386 ABI第54页上的图纸,以更好地了解它们如何配合(请注意,然而,该文档上的辅助向量类型常数与Linux使用的不同,因此您应该查看Linux头文件)。

现在您可以看到为什么r0-r2的内容是垃圾。栈中的第一个单词是argc,第二个是指向程序名称的指针(argv[0]),第三个可能是零,因为您没有使用参数调用程序(它将是argv[1])。我猜它们是这样设置的,是为了旧的a.out二进制格式,正如您可以在create_aout_tables中看到的那样,它将argcargvenvp放入栈中(因此它们最终以调用main()所期望的顺序出现在r0-r2中)。
最后,为什么r0对您而言是零而不是一(如果您没有使用参数调用程序,则argc应为一)?我猜测系统调用机制中的某些深层次问题覆盖了它,并将其替换为系统调用的返回值(由于执行成功,因此该值为零)。您可以在kernel_execve中看到(它不使用系统调用机制,因为这是内核从内核模式下想要执行时调用的函数),它有意将r0覆盖为do_execve的返回值。

3

以下是我使用的方法,使用我的编译器启动Linux/ARM程序:

/** The initial entry point.
 */
asm(
"       .text\n"
"       .globl  _start\n"
"       .align  2\n"
"_start:\n"
"       sub     lr, lr, lr\n"           // Clear the link register.
"       ldr     r0, [sp]\n"             // Get argc...
"       add     r1, sp, #4\n"           // ... and argv ...
"       add     r2, r1, r0, LSL #2\n"   // ... and compute environ.
"       bl      _estart\n"              // Let's go!
"       b       .\n"                    // Never gets here.
"       .size   _start, .-_start\n"
);

正如你所看到的,我从[sp]栈中获取了argc、argv和environ的内容。

稍作解释:栈指针指向进程内存中的有效区域。r0、r1、r2和r3是被调用函数的前三个参数。我分别使用argc、argv和environ来填充它们。


谢谢。您知道这个设置有没有任何文档记录吗? - Steve Smith
我相信这一定是真的,但我必须承认我是使用gdb找出来的。 - Richard Pennington
如果程序在没有参数的情况下调用,是否有一种方式可以通过单个指令(例如 ldr rN, sp, #XXX)获取 ENVP? - Zibri

3
这里是 uClibc crt,它似乎表明除了r0(包含要注册的函数指针atexit())和sp(包含有效堆栈地址)之外,所有寄存器都未定义。
因此,您在r1中看到的值可能不是可靠的。
有一些数据放在堆栈上供您使用。

0
我从未使用过ARM Linux,但我建议您查看libcrt的源代码并了解它们的工作方式,或者使用gdb来步进现有的可执行文件。您不需要源代码,只需逐步浏览汇编代码即可。
您需要找到的所有信息都应该发生在任何二进制可执行文件执行的第一个代码中。
希望这可以帮助到您。
Tony

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