2009-11-26 18 views
5

estoy jugando actualmente con el conjunto del brazo en Linux como un ejercicio de aprendizaje. Estoy usando el ensamblaje 'desnudo', es decir, no libcrt o libgcc. ¿Alguien puede dirigirme a la información sobre el estado del puntero de pila y otros registros al inicio del programa antes de que se llame a la primera instrucción? Obviamente, pc/r15 apunta a _start, y el resto parece haberse inicializado a 0, con dos excepciones; sp/r13 apunta a una dirección que está fuera de mi programa, y ​​r1 apunta a una dirección ligeramente más alta.Estado inicial de los registros del programa y la pila en Linux ARM

Así que para algunas preguntas sólidos:

  • ¿Cuál es el valor de r1?
  • ¿El valor en sp es una pila legítima asignada por el kernel?
  • De no ser así, ¿cuál es el método preferido para asignar una pila; utilizando brk o asignar una sección .bss estática?

Cualquier indicador sería apreciado.

Respuesta

2

Esto es lo que utilizo para obtener un programa Linux/ARM comenzó con mi compilador:

/** 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" 
); 

Como se puede ver, apenas consigo el argc, argv, y esas cosas Environ de la pila en [SP] .

Una pequeña aclaración: El puntero de la pila apunta a un área válida en la memoria del proceso. r0, r1, r2 y r3 son los tres primeros parámetros de la función que se llama. Los rellene con argc, argv y environ, respectivamente.

+0

Gracias. ¿Esta configuración está documentada en cualquier lugar que conozcas? –

+0

Estoy seguro de que debe ser así, pero tengo que admitir que lo descubrí usando gdb. –

0

nunca he utilizado ARM Linux pero sugerimos que lo mira a la fuente para la libcrt y ver lo que hacen, o el uso de GDB al paso en un archivo ejecutable existente. No debería necesitar el código fuente simplemente pasar por el código de ensamblaje.

Todo lo que necesita saber debe ocurrir dentro del primer código ejecutado por cualquier ejecutable binario.

Espero que esto ayude.

, Tony

3

Aquí está la uClibc crt. Parece sugerir que todos los registros no están definidos, excepto r0 (que contiene un puntero de función que estar registrado en atexit()) y sp que contiene una dirección de pila válida.

Por lo tanto, el valor que se ve en r1 probablemente no es algo que se puede confiar.

Algunos datos se colocan en la pila para usted.

+0

Enlace útil, gracias. –

5

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.

Cuestiones relacionadas