tag picard musicbrainz mac kid3 español editar easytag linux assembly arm

picard - Estado inicial del programa registra y apila en Linux ARM



musicbrainz picard español (4)

Actualmente estoy jugando con el ensamblado de ARM 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.

Entonces a algunas preguntas sólidas:

  • ¿Cuál es el valor en r1?
  • ¿El valor en sp es una pila legítima asignada por el kernel?
  • Si no, ¿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 apreciada.


Aquí está el crt de uClibc . Parece sugerir que todos los registros están indefinidos excepto r0 (que contiene un puntero a función para ser registrado con atexit() ) y sp que contiene una dirección de pila válida.

Entonces, el valor que ve en r1 probablemente no sea algo en lo que pueda confiar.

Algunos datos se colocan en la pila por ti.


Esto es lo que uso para iniciar un programa Linux / ARM 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 puedes ver, acabo de obtener las cosas argc, argv y 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.


Nunca he usado ARM Linux, pero sugiero que mire la fuente de la libcrt y vea qué hacen, o use gdb para ingresar a 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


Como esto es Linux, puede ver cómo el kernel lo implementa.

Los registros parecen estar configurados por la llamada a start_thread al final de load_elf_binary (si está usando 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 tienes una pila válida. Creo que los valores de r0 - r2 son basura, y deberías leer todo en la pila (verás por qué pienso esto más adelante). Ahora, veamos qué hay en la pila. Lo que leerás de la pila se llena 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 Linux basada en ELF. Lo siguiente está en la pila, en el orden en que lo leerías:

  • La cantidad 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 apuntaría al primero de estos punteros).
  • Un puntero a una cadena C para cada variable de entorno, seguido de un cero (este es el contenido del tercer parámetro de envp de main() ; envp señalaría el primero de estos punteros).
  • El "vector auxiliar", que es una secuencia de pares (un tipo seguido de un valor), termina 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 usa glibc) al ejecutar 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.

Como esta estructura es igual para todas las arquitecturas, puede buscar, por ejemplo, en el dibujo de la página 54 de SYSV 386 ABI para tener una mejor idea de cómo encajan las cosas (tenga en cuenta que el vector auxiliar constantes de ese documento son diferentes de lo que Linux usa, por lo que debe mirar los encabezados de Linux para ellos).

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 sea cero para ti porque llamaste al programa sin argumentos (sería argv[1] ) . Supongo que están configurados de esta manera para el a.out binario a.out más antiguo, que como puedes ver en create_aout_tables pone argc , argv y envp en la pila (para que terminen en r0 - r2 en el orden esperado para un llamar a main() ).

Finalmente, ¿por qué era r0 cero para ti en lugar de uno ( argc debería ser uno si argc 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 el mecanismo 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 .