Ich habe ein Programm geschrieben, das AT & T-Syntax zur Verwendung mit GNU Assembler verwendet:
.data
format: .ascii "%d\n"
.text
.global main
main:
mov $format, %rbx
mov (%rbx), %rdi
mov $1, %rsi
call printf
ret
Ich verwendeGCC, um Folgendes zu erstellen und zu verknüpfen:
gcc -o main main.s
Ich führe es mit diesem Befehl aus:
./Main
Wenn ich das Programm starte, erhalte ich einen seg-Fehler. Durch die Verwendung von gdb wird printf
nicht gefunden angezeigt. Ich habe ".extern printf" ausprobiert, was nicht funktioniert. Jemand schlug vor, den Stapelzeiger vor dem Aufruf von printf
zu speichern und vorRETwiederherzustellen. Wie mache ich das?
Es gibt eine Reihe von Problemen mit diesem Code. Die von Linux verwendete AMD64 System V ABI - Aufrufkonvention erfordert einige Dinge. Vor einem CALL muss der Stack mindestens 16-Byte (oder 32-Byte) ausgerichtet sein:
Das Ende des Eingabeargumentbereichs muss an einer 16-Byte-Grenze (32, wenn __m256 Auf Stack übergeben wird) ausgerichtet sein.
Nachdem die C-Laufzeit Ihre main
-Funktion aufgerufen hat, ist der Stapel um 8 falsch ausgerichtet, da der Rückkehrzeiger von CALL auf dem Stapel platziert wurde. Um sich an der 16-Byte-Grenze neu auszurichten, können Sie einfach Push any Allzweckregister auf den Stack setzen und POP am Ende ausschalten.
Die Aufrufkonvention erfordert außerdem, dass AL die Anzahl der Vektorregister enthält, die für eine variable Argumentfunktion verwendet werden:
% al wird verwendet, um die Anzahl der Vektorargumente anzugeben, die an eine Funktion übergeben werden, für die eine variable Anzahl von Argumenten erforderlich ist
printf
ist eine variable Argumentfunktion, daher muss AL gesetzt werden. In diesem Fall übergeben Sie keine Parameter in einem Vektorregister. Sie können AL auf 0 setzen.
Dereferenzieren Sie auch den $ - Formatzeiger, wenn es sich bereits um eine Adresse handelt. Das ist also falsch:
mov $format, %rbx
mov (%rbx), %rdi
Dies nimmt die Adresse des Formats und setzt es in RBX. Dann nehmen Sie die 8 Bytes an dieser Adresse in RBX und setzen sie in RDI ein. RDI muss ein Zeiger für eine Zeichenfolge sein, nicht die Zeichen selbst. Die zwei Zeilen könnten ersetzt werden durch:
lea format(%rip), %rdi
Hierbei wird die relative RIP-Adressierung verwendet.
Sie sollten auch NUL Ihre Zeichenfolgen beenden. Anstelle von .ascii
können Sie .asciz
auf der x86-Plattform verwenden.
Eine funktionierende Version Ihres Programms könnte folgendermaßen aussehen:
# global data #
.data
format: .asciz "%d\n"
.text
.global main
main:
Push %rbx
lea format(%rip), %rdi
mov $1, %esi # Writing to ESI zero extends to RSI.
xor %eax, %eax # Zeroing EAX is efficient way to clear AL.
call printf
pop %rbx
ret
Sie sollten auch von der 64-Bit-Linux-ABI wissen, dass die aufrufende Konvention auch Funktionen erfordert, die Sie schreiben, um die Beibehaltung bestimmter Register zu berücksichtigen. Die Liste der Register und ob sie aufbewahrt werden sollten, lautet wie folgt:
Jedes Register, das Yes
in der Spalte Preserved across Register enthält, muss unbedingt in Ihrer Funktion erhalten bleiben. Die Funktion main
ist wie jede andere C Funktion.
Wenn Sie Strings/Daten haben, von denen Sie wissen, dass sie nur gelesen werden, können Sie sie mit .rodata
anstelle von .section .rodata
in den Abschnitt .data
einfügen.
Im 64-Bit-Modus: Wenn Sie einen Zieloperanden haben, der ein 32-Bit-Register ist, wird die CPU das Register über das gesamte 64-Bit-Register auf Null setzen. Dadurch können Bytes bei der Befehlsverschlüsselung gespeichert werden.
Es ist möglich, dass Ihre ausführbare Datei als positionsunabhängiger Code kompiliert wird. Möglicherweise erhalten Sie eine Fehlermeldung ähnlich der folgenden:
verschiebung R_X86_64_PC32 gegen das Symbol `printf @@ GLIBC_2.2.5 'kann nicht verwendet werden, wenn ein gemeinsam genutztes Objekt erstellt wird; mit -fPIC neu kompilieren
Um dies zu beheben, müssen Sie die externe Funktion printf
folgendermaßen aufrufen:
call [email protected]
Dadurch wird die externe Bibliotheksfunktion über die Procedure Linkage Table (PLT) aufgerufen.
Sie können sich den aus einer äquivalenten c-Datei generierten Assemblycode anzeigen lassen.
Ausführen von gcc -o - -S -fno-asynchronous-unwind-tables test.c
mit test.c
#include <stdio.h>
int main() {
return printf("%d\n", 1);
}
Dies gab den Assembly-Code aus:
.file "test.c"
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
pushq %rbp
movq %rsp, %rbp
movl $1, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
popq %rbp
ret
.size main, .-main
.ident "GCC: (GNU) 6.1.1 20160602"
.section .note.GNU-stack,"",@progbits
Damit erhalten Sie ein Beispiel für einen Assembly-Code, der printf aufruft, den Sie dann ändern können.
Verglichen mit Ihrem Code sollten Sie zwei Dinge ändern:
mov $format, %rdi
erfolgen.mov $0, %eax
hinzufügen.Das Anwenden dieser Modifikationen ergibt etwa Folgendes:
.data
format: .ascii "%d\n"
.text
.global main
main:
mov $format, %rdi
mov $1, %rsi
mov $0, %eax
call printf
ret
Und dann drucken Sie es aus:
1