Como esto es Linux, puede ver cómo el kernel lo implementa.
Los registros parecen configurados por la llamada al start_thread
al final de load_elf_binary
(si está utilizando un sistema Linux moderno, casi siempre usará el formato ELF). Para ARM, los registros parecen estar configurados de la siguiente manera:
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
Claramente usted tiene una pila válida.Creo que los valores de r0
- r2
son basura, y en su lugar deberías leer todo de la pila (verás por qué pienso esto más adelante). Ahora, veamos qué hay en la pila. Lo que leerá de la pila se completa con create_elf_tables
.
Una cosa interesante de notar aquí es que esta función es independiente de la arquitectura, por lo que las mismas cosas (la mayoría) se colocarán en la pila en cada arquitectura de Linux basada en ELF. Lo siguiente está en la pila, en el orden que lo lea:
- El número de parámetros (esto es
argc
en main()
).
- Un puntero a una cadena C para cada parámetro, seguido de un cero (este es el contenido de
argv
en main()
; argv
señalaría el primero de estos punteros).
- Un puntero a una cadena C para cada variable de entorno, seguido de un cero (este es el contenido del
envp
tercer parámetro de envp
que rara vez se ve, señalaría el primero de estos punteros).
- El "vector auxiliar", que es una secuencia de pares (un tipo seguido de un valor), terminado por un par con un cero (
AT_NULL
) en el primer elemento. Este vector auxiliar tiene información interesante y útil, que puede ver (si está utilizando glibc) ejecutando cualquier programa vinculado dinámicamente con la variable de entorno LD_SHOW_AUXV
establecida en 1
(por ejemplo LD_SHOW_AUXV=1 /bin/true
). Aquí también es donde las cosas pueden variar un poco dependiendo de la arquitectura.
Desde esta estructura es la misma para todas las arquitecturas, se puede ver por ejemplo en el dibujo de la página 54 de la SYSV 386 ABI para tener una mejor idea de cómo encajan las cosas (nota, sin embargo, que el tipo de vector auxiliar las constantes en ese documento son diferentes de las que usa Linux, por lo que debe buscar los encabezados de Linux para ellas).
Ahora puede ver por qué los contenidos de r0
- r2
son basura. La primera palabra en la pila es argc
, la segunda es un puntero al nombre del programa (argv[0]
), y la tercera probablemente fue cero porque usted llamó al programa sin argumentos (sería argv[1]
). Supongo que se establecen de esta manera durante la mayor a.out
formato binario, que como se puede ver en create_aout_tables
pone argc
, argv
y envp
en la pila (por lo que terminaría en r0
- r2
en el orden esperado para una llamada a main()
).
Finalmente, ¿por qué r0
fue cero para usted en lugar de uno (argc
debería ser uno si llamó al programa sin argumentos)? Supongo que algo en lo profundo de la maquinaria de syscall lo sobreescribió con el valor de retorno de la llamada al sistema (que sería cero desde que el ejecutivo lo logró). Puede ver en kernel_execve
(que no utiliza la maquinaria syscall, ya que es lo que el kernel llama cuando quiere ejecutar desde el modo kernel) que sobrescribe deliberadamente r0
con el valor de retorno de do_execve
.
Gracias. ¿Esta configuración está documentada en cualquier lugar que conozcas? –
Estoy seguro de que debe ser así, pero tengo que admitir que lo descubrí usando gdb. –