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

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

所以对于一些可靠的问题:

  • r1中的值是多less?
  • 内核分配的合法堆栈的值是多less?
  • 如果不是,分配堆栈的首选方法是什么? 使用brk或分配一个静态.bss节?

任何指针将不胜感激。

Solutions Collecting From Web of "Linux ARM上的程序寄存器和堆栈的初始状态"

下面是我用我的编译器启动的一个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" ); 

正如你所看到的,我只是从堆栈中获得了argc,argv和environ的东西。

稍加说明:堆栈指针指向进程内存中的有效区域。 r0,r1,r2和r3是被调用函数的前三个参数。 我分别用argc,argv和environ填充它们。

既然这是Linux,你可以看看它是如何实现的内核。

这些寄存器似乎是通过在start_thread结尾调用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 

显然你有一个有效的堆栈。 我认为r0r2的价值是垃圾,你应该从堆栈中读取所有的东西(你会明白为什么我会这么想)。 现在,我们来看看堆栈中的内容。 您将从堆栈中读取的内容由create_elf_tables填充。

这里需要注意的一点是,这个函数是独立于架构的,所以在每个基于ELF的Linux架构上,相同的东西(大部分)将被放在堆栈上。 以下是堆栈中的顺序,您可以阅读它:

  • 参数的数量(这是argc main() argc )。
  • 一个指向每个参数C字符串的指针,后跟一个零(这是main()argv的内容; argv指向这些指针中的第一个)。
  • 一个指向每个环境变量的C字符串的指针,后面是一个零(这是main()罕见的envp第三个参数的内容; envp指向这些指针中的第一个)。
  • “辅助向量”是一对序列(一个类型后跟一个值),在第一个元素中以零( AT_NULLAT_NULL 。 这个辅助矢量有一些有趣和有用的信息,你可以看到(如果你使用的是glibc),通过运行LD_SHOW_AUXV环境变量设置为1 (例如LD_SHOW_AUXV=1 /bin/true )的任何动态链接的程序。 这也是根据架构不同而有所不同的地方。

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

现在你可以看到为什么r0r2的内容是垃圾。 堆栈中的第一个单词是argc ,第二个单词是指向程序名( argv[0] )的指针,第三个单词可能是零,因为你没有参数调用这个程序(它会是argv[1] ) 。 我想他们是以这种方式为旧的a.out二进制格式设置的,正如你可以在create_aout_tables看到的那样, argcargvenvp放在堆栈中(所以它们将按照预期的顺序在r0r2中结束调用main() )。

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

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

所以,你在r1看到的价值可能不是你可以依赖的东西。

有些数据放在堆栈上供您使用。

我从来没有使用ARM Linux,但我建议你或者看看libcrt的源代码,看看它们做什么,或者使用gdb来进入现有的可执行文件。 你不应该只需要通过汇编代码的源代码。

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

希望这可以帮助。

托尼