Поскольку это 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
.
Следует отметить одну интересную вещь: эта функция не зависит от архитектуры, поэтому одни и те же вещи (в основном) будут помещаться в стек в каждой архитектуре Linux на основе ELF. Следующее находится в стеке, в том порядке, в котором вы читаете это:
- Количество параметров (это
argc
в main()
).
- Один указатель на строку C для каждого параметра, за которым следует ноль (это содержимое
argv
в main()
; argv
будет указывать на первый из этих указателей).
- Один указатель на строку C для каждой переменной среды, за которой следует ноль (это содержимое редко видимого
envp
третьего параметра main()
; envp
будет указывать на первый из этих указателей) .
- «Вспомогательный вектор», представляющий собой последовательность пар (тип, за которым следует значение), оканчивающихся парой с нулем (
AT_NULL
) в первом элементе. Этот вспомогательный вектор содержит некоторую интересную и полезную информацию, которую вы можете увидеть (если вы используете glibc), запустив любую динамически связанную программу с переменной окружения LD_SHOW_AUXV
, установленной в 1
(например, LD_SHOW_AUXV=1 /bin/true
). Здесь также все может немного отличаться в зависимости от архитектуры.
Поскольку эта структура одинакова для любой архитектуры, вы можете, например, взглянуть на рисунок на стр. 54 SYSV 386 ABI , чтобы получить лучшее представление о том, как все сочетается (обратите внимание, однако, что что константы вспомогательного векторного типа в этом документе отличаются от того, что использует Linux, поэтому вам следует взглянуть на них в заголовках Linux).
Теперь вы можете понять, почему содержимое r0
- r2
является мусором. Первое слово в стеке - argc
, второе - указатель на имя программы (argv[0]
), а третье, вероятно, было нулевым для вас, потому что вы вызвали программу без аргументов (это будет argv[1]
) , Я предполагаю, что они настроены таким образом для более старого двоичного формата a.out
, который, как вы можете видеть в create_aout_tables
, помещает argc
, argv
и envp
в стек (так они должны были бы в r0
- r2
в порядке, ожидаемом для вызова на main()
).
Наконец, почему для вас r0
был ноль вместо одного (argc
должен быть равен единице, если вы вызвали программу без аргументов)? Я предполагаю, что что-то глубоко в механизме системного вызова переписало это с возвращаемым значением системного вызова (которое будет равно нулю, так как exec преуспел). В kernel_execve
(который не использует механизм системного вызова, поскольку ядро вызывает его, когда он хочет выполнить из режима ядра) вы можете видеть, что он намеренно перезаписывает r0
возвращаемым значением do_execve
.