【问题标题】:Initial state of program registers and stack on Linux ARMLinux ARM 上程序寄存器和堆栈的初始状态
【发布时间】:2010-12-20 15:23:21
【问题描述】:

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

所以对于一些扎实的问题:

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

任何指针将不胜感激。

【问题讨论】:

    标签: linux assembly arm


    【解决方案1】:

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

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

    希望这会有所帮助。

    托尼

    【讨论】:

      【解决方案2】:

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

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

      一些数据已为您放入堆栈。

      【讨论】:

        【解决方案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 填充它们。

        【讨论】:

        • 谢谢。此设置是否记录在您知道的任何地方?
        • 我确定一定是这样,但我不得不承认我是使用 gdb 解决的。
        • 如果程序在没有参数的情况下被调用,有没有办法在一条指令中获得 ENVP,比如 ldr rN, sp, #XXX ?
        【解决方案4】:

        既然是Linux,大家可以看看内核是如何实现的。

        寄存器似乎是通过在load_elf_binary 末尾调用start_thread 设置的(如果您使用的是现代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) 对终止。这个辅助向量有一些有趣且有用的信息,您可以通过运行任何动态链接程序并将LD_SHOW_AUXV 环境变量设置为1(例如LD_SHOW_AUXV=1 /bin/true)来查看这些信息(如果您使用的是glibc)。这也是因架构而异的地方。

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

        现在你可以看到为什么r0-r2 的内容是垃圾了。堆栈中的第一个字是argc,第二个是指向程序名称的指针(argv[0]),第三个对您来说可能是零,因为您调用的程序没有参数(它将是argv[1]) .我猜它们是为旧的a.out 二进制格式设置的,正如您在create_aout_tables 看到的那样,将argcargvenvp 放入堆栈(所以它们最终会在@ 987654355@-r2 按预期调用main() 的顺序)。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-05-20
          • 1970-01-01
          • 2015-06-23
          • 2015-12-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多