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
.
Grazie. Questa configurazione è documentata ovunque tu sappia? –
Sono sicuro che dev'essere, ma devo ammettere che l'ho capito usando gdb. –