Domanda

Attualmente sto giocando con ARM assembly su Linux come esercizio di apprendimento. Sto usando un assembly "bare", ovvero senza libcrt o libgcc. Qualcuno può indicarmi informazioni su quale stato faranno il puntatore dello stack e altri registri all'inizio del programma prima che venga chiamata la prima istruzione? Ovviamente pc / r15 punti a _start, e il resto sembra essere inizializzato su 0, con due eccezioni; sp / r13 indica un indirizzo molto al di fuori del mio programma e r1 indica un indirizzo leggermente più alto.

Quindi, per alcune solide domande:

  • Qual è il valore in r1?
  • Il valore in sp è uno stack legittimo allocato dal kernel?
  • In caso contrario, qual è il metodo preferito per allocare uno stack; usando brk o allocare una sezione .bss statica?

Eventuali puntatori sarebbero apprezzati.

È stato utile?

Soluzione

Ecco cosa uso per avviare un programma Linux / ARM 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 puoi vedere, ho appena preso le cose argc, argv e environment dallo stack su [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 ambi, rispettivamente.

Altri suggerimenti

Dato che si tratta di Linux, puoi vedere come viene implementato dal kernel.

I registri sembrano essere impostati dalla chiamata su start_thread alla fine di load_elf_binary (se stai usando un moderno sistema Linux, userà quasi sempre il formato ELF). Per ARM, i registri sembrano essere impostati come segue:

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

Chiaramente hai uno stack valido. Penso che i valori di r0 - r2 siano spazzatura e dovresti invece leggere tutto dallo stack (vedrai perché lo penso più tardi). Ora diamo un'occhiata a ciò che è nello stack. Quello che leggerai dallo stack è compilato da create_elf_tables .

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

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

Poiché questa struttura è la stessa per ogni architettura, è possibile cercare ad esempio il disegno a pagina 54 del SYSV 386 ABI per avere un'idea migliore di come le cose si incastrano (si noti, tuttavia, che le costanti del tipo di vettore ausiliario su quel documento sono diverse da quelle utilizzate da Linux, quindi si dovrebbe guardare Linux intestazioni per loro).

Ora puoi vedere perché i contenuti di r0 - r2 sono spazzatura. La prima parola nello stack è argc , la seconda è un puntatore al nome del programma ( argv [0] ), e la terza probabilmente era zero per te perché hai chiamato il programma senza argomenti (sarebbe argv [1] ). Immagino che siano impostati in questo modo per il vecchio formato binario a.out , che come puoi vedere in create_aout_tables inserisce argc , argv e envp nello stack (quindi finirebbero in r0 - r2 nell'ordine previsto per una chiamata a main () ).

Infine, perché r0 era zero per te anziché uno ( argc dovrebbe essere uno se hai chiamato il programma senza argomenti)? Immagino che qualcosa di profondo nel macchinario di syscall lo abbia sovrascritto con il valore di ritorno della chiamata di sistema (che sarebbe zero poiché l'esecutore è riuscito). Puoi vedere in kernel_execve (che non utilizza il macchinario syscall, poiché è ciò che il kernel chiama quando vuole eseguire dalla modalità kernel) che sovrascrive deliberatamente r0 con il valore restituito do_execve .

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

Quindi, il valore che vedi in r1 probabilmente non è qualcosa su cui puoi contare.

Alcuni dati vengono inseriti nello stack per te.

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

Tutto ciò che devi scoprire dovrebbe accadere nel primo codice eseguito da qualsiasi eseguibile binario.

Spero che questo aiuti.

Tony

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top