2009-11-26 11 views
5

Attualmente sto giocando con l'assembly ARM su Linux come esercizio di apprendimento. Sto usando l'assembly 'bare', cioè senza libcrt o libgcc. Qualcuno può indicarmi informazioni su quale sarà lo stack-pointer e gli altri registri all'inizio del programma prima che venga chiamata la prima istruzione? Ovviamente pc/r15 punta a _start, e il resto sembra essere inizializzato a 0, con due eccezioni; sp/r13 punta a un indirizzo molto al di fuori del mio programma e r1 punta a un indirizzo leggermente più alto.Stato iniziale dei registri di programma e stack su Linux ARM

Così ad alcune domande solidi:

  • Qual è il valore di R1?
  • Il valore in sp è uno stack legittimo assegnato dal kernel?
  • In caso contrario, qual è il metodo preferito di allocazione di una pila; usando brk o allocare una sezione .bss statica?

Qualsiasi suggerimento sarebbe apprezzato.

risposta

2

Ecco quello che uso per ottenere un programma Linux/ARM ha iniziato con il mio compilatore:

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

Come si può vedere, ottengo solo l'argc, argv, e roba environ dalla pila a [sp] .

Un piccolo chiarimento: il puntatore dello stack punta a un'area valida nella memoria del processo. r0, r1, r2 e r3 sono i primi tre parametri della funzione chiamata. Li popolo con argc, argv e environ, rispettivamente.

+0

Grazie. Questa configurazione è documentata ovunque tu sappia? –

+0

Sono sicuro che dev'essere, ma devo ammettere che l'ho capito usando gdb. –

0

Non ho mai usato ARM Linux ma suggerisco di guardare il codice sorgente di libcrt e vedere cosa fanno, o usare gdb per passare a un eseguibile esistente. Non dovresti aver bisogno del codice sorgente, basta semplicemente passare attraverso il codice assembly.

Tutto ciò che è necessario scoprire dovrebbe avvenire nel primo codice eseguito da un eseguibile binario.

Spero che questo aiuti.

Tony

3

Ecco il uClibc crt. Sembra suggerire che tutti i registri non sono definiti ad eccezione di r0 (che contiene un puntatore a funzione da registrare con atexit()) e sp che contiene un indirizzo stack valido.

Quindi, il valore visualizzato in r1 non è probabilmente un argomento su cui poter fare affidamento.

Alcuni dati vengono messi in pila per voi.

+0

Link utile, grazie. –

5

Poiché questo è Linux, è possibile osservare come viene implementato dal kernel.

I registri sembrano essere impostati dalla chiamata a start_thread alla fine di load_elf_binary (se si utilizza un sistema Linux moderno, verrà quasi sempre utilizzato il formato ELF). Per ARM, i registri sembrano essere impostati come segue:

Chiaramente si dispone di uno stack valido.Penso che i valori di r0 - r2 siano spazzatura, e dovresti invece leggere tutto dallo stack (vedrai perché lo penso dopo). Ora, diamo un'occhiata a ciò che è in pila. Quello che leggerete dalla pila è riempito da create_elf_tables.

Una cosa interessante da notare è che questa funzione è indipendente dall'architettura, quindi le stesse cose (principalmente) verranno messe in pila su ogni architettura Linux basata su ELF. Quanto segue è in pila, nell'ordine che avrebbe letto:

  • Il numero di parametri (questo è argc in main()).
  • Un puntatore a una stringa C per ogni parametro, seguito da uno zero (questo è il contenuto di argv in main(); argv punterebbe al primo di questi puntatori).
  • Un puntatore a una stringa C per ogni variabile ambiente, seguito da uno zero (questo è il contenuto del raramente visto envp terzo parametro di main(); envp indicherebbero al primo di questi puntatori).
  • Il "vettore ausiliario", che è una sequenza di coppie (un tipo seguito da un valore), terminato da una coppia con uno zero (AT_NULL) nel primo elemento. Questo vettore ausiliario contiene alcune informazioni interessanti e utili, che è possibile vedere (se si utilizza glibc) eseguendo qualsiasi programma collegato dinamicamente con la variabile di ambiente LD_SHOW_AUXV impostata su 1 (ad esempio LD_SHOW_AUXV=1 /bin/true). Questo è anche il punto in cui le cose possono variare leggermente a seconda dell'architettura.

Dal momento che questa struttura è la stessa per ogni architettura, si può guardare per esempio il disegno a pagina 54 del SYSV 386 ABI per avere una migliore idea di come le cose si incastrano (notare, tuttavia, che il tipo di vettore ausiliario le costanti su quel documento sono diverse da ciò che Linux usa, quindi dovresti guardare le intestazioni Linux per loro).

Ora è possibile vedere perché il contenuto di r0 - r2 è spazzatura. La prima parola nella pila è argc, la seconda è un puntatore al nome del programma (argv[0]) e la terza probabilmente è zero per te perché hai chiamato il programma senza argomenti (sarebbe). Credo che sono impostati in questo modo per i più anziani a.out formato binario, che, come si può vedere a create_aout_tables mette argc, argv, e envp nello stack (in modo che finirebbe in r0 - r2 nell'ordine previsto per una chiamata a main()).

Infine, perché è stato r0 zero per te invece di uno (argc dovrebbe essere uno se hai chiamato il programma senza argomenti)? Sto indovinando che qualcosa di profondo nella macchina di syscall lo ha sovrascritto con il valore di ritorno della chiamata di sistema (che sarebbe zero da quando l'exec è riuscita). È possibile vedere in kernel_execve (che non utilizza il meccanismo syscall, poiché è ciò che il kernel chiama quando vuole eseguire dalla modalità kernel) che sovrascrive deliberatamente r0 con il valore restituito di do_execve.