由于这是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
)作为第一个元素终止。此辅助向量具有一些有趣且有用的信息,您可以通过使用设置为1
的LD_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
中看到的那样,它将
argc
、
argv
和
envp
放入栈中(因此它们最终以调用
main()
所期望的顺序出现在
r0
-
r2
中)。
最后,为什么
r0
对您而言是零而不是一(如果您没有使用参数调用程序,则
argc
应为一)?我猜测系统调用机制中的某些深层次问题覆盖了它,并将其替换为系统调用的返回值(由于执行成功,因此该值为零)。您可以在
kernel_execve
中看到(它不使用系统调用机制,因为这是内核从内核模式下想要执行时调用的函数),它有意将
r0
覆盖为
do_execve
的返回值。