Anfangszustand des Programmregister und Stack auf Linux ARM
Frage
Ich spiele derzeit mit ARM Montage auf Linux als Lernübung. Ich verwende ‚nackten‘ Montage, das heißt keine libcrt oder libgcc. Kann mich jemand auf Informationen hinweist, über das, was die Stapel-Zeiger und andere Register angeben, werden zu Beginn des Programms vor dem ersten Befehl aufgerufen werden? Offensichtlich pc / r15 Punkte bei _start, und der Rest erscheinen auf 0 initialisiert werden, mit zwei Ausnahmen; sp / r13 auf eine Adresse weit außerhalb meines Programms, und r1 verweist auf eine etwas höhere Adresse.
So zu einigen soliden Fragen:
- Was ist der Wert in r1?
- Ist der Wert in sp ein legitimen Stapel vom Kernel zugewiesen?
- Wenn nicht, was die bevorzugte Methode ist, einen Stapel zuzuordnen; mit brk oder einem statischen .bss Abschnitt zuweisen?
würde Alle Zeiger geschätzt werden.
Lösung
Hier ist, was ich ein Linux / ARM-Programm zu erhalten verwenden begann mit meinem Compiler:
/** 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"
);
Wie Sie sehen können, habe ich nur die argc, argv und environ Sachen aus dem Stapel an [sp].
Eine kleine Klarstellung: Der Stapelzeiger zeigt auf einen gültigen Bereich in dem Prozess Speichern. r0, r1, r2 und r3 sind die ersten drei Parameter für die Funktion aufgerufen wird. Ich bevölkern sie mit argc, argv und environ sind.
Andere Tipps
Da diese Linux, kann man sich anschaut, wie sie vom Kernel implementiert ist.
Die Register scheinen durch den Aufruf von start_thread
am Ende der load_elf_binary
(wenn Sie ein modernes Linux-System verwenden, wird es fast immer das ELF-Format verwenden). Für ARM, scheinen die Register gesetzt werden, wie folgt:
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
Offensichtlich haben Sie einen gültigen Stapel. Ich denke, dass die Werte von r0
-r2
Spam eingestuft werden, und Sie sollten stattdessen alles vom Stapel lesen (Sie werden sehen, warum ich diese später denken). Nun wollen wir sehen, was auf dem Stapel ist. Was Sie aus dem Stapel gelesen werden durch create_elf_tables
gefüllt .
Eine interessante Sache, hier zu bemerken ist, dass diese Funktion Architektur-unabhängig ist, so dass die gleichen Dinge (meistens) auf jeder ELF-basierte Linux-Architektur auf den Stapel gelegt werden. Im Folgenden ist auf dem Stapel in der Reihenfolge Sie sie lesen würden:
- Die Anzahl der Parameter (dies ist
argc
inmain()
). - Ein Zeiger auf einen C-String für jeden Parameter, gefolgt von einer Null (dies ist der Inhalt des in
argv
main()
;argv
zum ersten diesen Zeigern zeigen würde) .
- Ein Zeiger auf ein C-String für jede Umgebungsvariable, gefolgt von einer Null (dies ist der Inhalt der selten gesehene
envp
dritten Parameter vonmain()
;envp
auf den ersten diesen Zeiger zeigen würde). - Die „Hilfsvektor“, das eine Folge von Paaren (eine Art gefolgt von einem Wert) ist, die durch ein Paar mit einer Null (
AT_NULL
) in dem ersten Elemente beendet. Dieser Hilfsvektor hat einige interessante und nützliche Informationen, die Sie sehen können (wenn Sie glibc verwenden) mit einem dynamisch-Linked-Programm mit demLD_SHOW_AUXV
Umgebungsvariable läuft gesetzt (zum Beispiel1
)LD_SHOW_AUXV=1 /bin/true
. Das ist auch, wo die Dinge ein wenig abhängig von der Architektur variieren können.
Da diese Struktur die gleiche für jede Architektur ist, können Sie in der Zeichnung auf Seite 54 der SYSV 386 ABI bekommen eine bessere Vorstellung davon, wie die Dinge zusammenpassen (beachten Sie jedoch, dass der Hilfsvektortyp Konstanten auf dem Dokument unterscheidet von dem, was Linux verwendet, so dass Sie auf der Linux aussehen sollten Header für sie).
Jetzt können Sie sehen, warum der Inhalt r0
-r2
Müll sind. Das erste Wort in dem Stapel ist argc
, die zweite ein Zeiger auf die Programmnamen ist (argv[0]
), und die dritte war wahrscheinlich Null für Sie, weil Sie das Programm ohne Argumente aufgerufen (es argv[1]
wäre). Ich denke, sie werden auf diese Weise für die ältere a.out
Binärformat eingerichtet, die, wie Sie unter create_aout_tables
setzt argc
, argv
und envp
im Stapel (so sie würden in r0
-r2
in der Reihenfolge für einen Anruf erwartet am Ende main()
).
Schließlich, warum Null für Sie statt einer r0
(argc
sein sollten, wenn Sie das Programm ohne Argumente genannt)? Ich vermute, etwas tief in der syscall Maschinen es mit dem Rückgabewert des Systemaufruf überschrieben (der Null sein würde, da die exec gelungen). sehen in Sie können kernel_execve
( die es nicht verwendendie syscall Maschinen, da es ist, was der Kernel aufruft, wenn er will aus dem Kernel-Modus exec), dass es r0
mit dem Rückgabewert von do_execve
absichtlich überschrieben.
Hier ist der uClibc crt . Es scheint darauf hinzudeuten, dass alle Register außer r0
nicht definiert sind (die einen Funktionszeiger enthält mit atexit()
registriert werden) und sp
, die eine gültige Stapeladresse enthält.
So ist der Wert, den Sie in r1
sehen, ist wahrscheinlich nicht etwas, das Sie sich verlassen können.
Einige Daten auf dem Stapel für Sie gegeben.
Ich habe noch nie ARM Linux verwendet, aber ich schlage vor, Sie entweder für die libcrt an der Quelle und sehen, was sie tun, oder gdb in eine vorhandene ausführbare Datei zu treten. Sie sollten den Quellcode Schritt nur durch den Assembler-Code nicht benötigen.
Alles, was Sie innerhalb des ersten Code, um herauszufinden, durch jede ausführbare Binärdatei ausgeführt passieren sollte.
Hope, das hilft.
Tony