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

时间:2009-11-26 10:12:01

标签: linux assembly arm

我目前正在Linux上使用ARM程序集作为学习练习。我正在使用'裸'汇编,即没有libcrt或libgcc。任何人都能指出有关堆栈指针和其他寄存器在调用第一条指令之前在程序开始时的状态的信息吗?显然pc / r15指向_start,其余的似乎初始化为0,但有两个例外; sp / r13指向远离我程序的地址,r1指向略高的地址。

所以提出一些可靠的问题:

  • r1中的值是什么?
  • sp中的值是由内核分配的合法堆栈吗?
  • 如果没有,分配堆栈的首选方法是什么;使用brk还是分配静态.bss部分?

任何指针都会受到赞赏。

4 个答案:

答案 0 :(得分:6)

由于这是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架构上都会将相同的东西(大部分)放在堆栈上。以下是堆栈,按您阅读的顺序:

  • 参数数量(argc中的main())。
  • 指向每个参数的C字符串的一个指针,后跟零(这是argvmain()的内容; argv将指向这些指针中的第一个)。
  • 指向每个环境变量的C字符串的一个指针,后跟一个零(这是envp中很少见main()个第三个参数的内容; envp将指向这些指针中的第一个)。
  • “辅助向量”,它是一对对的序列(一个类型后跟一个值),由第一个元素中的零(AT_NULL)对终止。这个辅助向量有一些有趣且有用的信息,你可以通过运行LD_SHOW_AUXV环境变量设置为1的任何动态链接程序来看到(如果你使用的是glibc)(例如{{1 }})。根据架构的不同,这也可能会有所不同。

由于这个结构对于每个架构都是相同的,你可以在SYSV 386 ABI的第54页的图纸上查找例子,以更好地了解事物如何组合在一起(但请注意,辅助矢量)该文档的类型常量与Linux使用的不同,因此您应该查看它们的Linux头文件。

现在你可以看到为什么LD_SHOW_AUXV=1 /bin/true - r0的内容是垃圾了。堆栈中的第一个单词是r2,第二个单词是指向程序名称(argc)的指针,第三个单词可能为零,因为你调用了没有参数的程序(它会是argv[0])。我猜他们已经采用这种方式设置旧的argv[1]二进制格式,正如您在create_aout_tablesa.outargcargv所看到的那样堆栈(因此它们最终会按照envp调用的预期顺序进入r0 - r2

最后,为什么main()为零而不是一个(如果你没有参数调用程序,r0应该为1)?我猜测系统调用机器中的某些东西用系统调用的返回值覆盖它(自exec成功以来它将为零)。您可以在kernel_execve中看到(它不使用系统调用机制,因为它是内核在从内核模式执行时调用的内容),它故意用argc覆盖返回值r0 1}}。

答案 1 :(得分:3)

这是uClibc crt。似乎建议所有寄存器都是未定义的,除了r0(包含要注册atexit()的函数指针)和sp,其中包含有效的堆栈地址。

因此,您在r1中看到的价值可能不是您可以信赖的。

有些数据会放在堆栈上。

答案 2 :(得分:2)

以下是我使用编译器启动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填充它们。

答案 3 :(得分:0)

我从未使用过ARM Linux,但我建议您查看libcrt的源代码并查看它们的用途,或者使用gdb进入现有的可执行文件。您不需要只需单步执行汇编代码即可获得源代码。

您需要找到的所有内容都应该在任何二进制可执行文件执行的第一个代码中发生。

希望这有帮助。