Поскольку это 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. Ниже находится в стеке в порядке, вы читали его:
- число параметров (это в
main()
argc
).
- Один указатель на строку 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
должен быть одним, если вы вызвали программу без аргументов)? Я догадываюсь, что что-то глубоко в машине syscall переписало его с возвращаемым значением системного вызова (которое было бы нулевым с момента успешного выполнения exec). Вы можете увидеть в kernel_execve
(который не использует механизм syscall, поскольку это то, что ядро вызывает, когда он хочет выполнить из режима ядра), что он намеренно перезаписывает r0
с возвращаемым значением do_execve
.
Спасибо. Описывается ли эта установка в любом месте, о котором вы знаете? –
Я уверен, что это должно быть, но я должен признать, что я понял это с помощью gdb. –