x86-Programm zum Ausführen eines Exponenten
Aus Gründen der Kontinuität bestand meine letzte Frage darin, Zahlen zusammen mit einem Funktionsaufruf hinzuzufügen: Beispiel x86-64-Programm . In diesem Fall habe ich versucht, die in der vorherigen Antwort genannten Lektionen anzuwenden (Stapelausrichtung, edxstatt zu verwenden, rdxwo die Ergebnisse passen). Ich habe versucht, inline mit dem Code zu kommentieren:
# We are going to calculate 7^2 + 2^4 = 49 + 16 = 65
# The exponent function supports whole numbers (long) >= 0
.section .data
base_1: .long 7
exp_1: .long 2
base_2: .long 2
exp_2: .long 4
.section .text
.globl _start
_start:
# We will do the first function call, 7^2
mov base_1(%rip), %edi
mov exp_1(%rip), %esi
call exp
# needs to be 16-byte aligned before the next function call so do two pushes
pushq $0 pushq %rax # Now do the second function call mov base_2(%rip), %edi mov exp_2(%rip), %esi call exp # We have the return value in %eax so let's add this with our previous function's value popq %rdi add %eax, %edi mov $60, %eax
syscall
exp:
# Initialize %eax to 1
mov $1, %eax exp_op: cmp $0, %esi
je exp_ret
imul %edi, %eax
dec %esi
jmp exp_op
exp_ret:
ret
Ich habe einige spezifische Fragen zum Code:
- Ist eine rohe
push/popgemeinsame? Oder ist die Konvention immer zu tunpush rbpmov rsp rbp...pop rbp. Warum wird eine Methode der anderen vorgezogen? - Was wäre, wenn ich das Ergebnis der ersten Berechnung in einem Register speichern wollte: Gibt es Register, die zwischen Funktionsaufrufen garantiert erhalten bleiben? Oder wird die
push...popMethode deshalb so oft angewendet? - Schließlich scheint mein
expok? Es fühlt sich etwas seltsam an, drei Beschriftungen in einer Funktion zu haben. Wie könnte das verbessert werden?
Antworten
Ich bin kein Experte, aber was solls, hier ist ein Kommentar zur Bewertung:
# needs to be 16-byte aligned before the next function call so do two pushes
pushq $0
pushq %rax
Dieser Kommentar ist gut, aber ich würde ihn umformulieren. Zwei Acht-Byte-Pushs ergeben 16 Byte und ändern die Stapelausrichtung nicht. Daher schließe ich, dass einer dieser Vorstöße signifikant und der andere unbedeutend ist - aber Ihr Kommentar sagt mir nicht, welcher welcher ist! Also könnte man stattdessen sagen
# one extra push to preserve 16-byte stack alignment
pushq $0
# push the result of `exp`
pushq %rax
Sie können den generierten Code verkleinern, indem Sie die unbedeutende Konstante entfernen $0:
# push the result of `exp`, plus one extra push to preserve 16-byte stack alignment
pushq %rax
pushq %rax
Jetzt muss der Leser nicht einmal herausfinden, welcher Push der entscheidende ist, da beide Pushs dasselbe bewirken!
Aber warum ist es wichtig, die 16-Byte-Ausrichtung bei Anrufen beizubehalten? Das ist keine Anforderung an die Maschine . Sie scheinen zu versuchen, einem bestimmten ABI zu folgen , beispielsweise für die Interoperabilität mit C oder C ++. In Ihrer externen Dokumentation sollte klarer sein, welchem ABI Sie folgen möchten.
Wenn Sie dann versuchen, mit C-Code zusammenzuarbeiten, können Sie Ihren Code verbessern, indem Sie angeben, welche seiner Bezeichnungen als externe Einstiegspunkte gedacht sind und welche nur interne lokale Bezeichnungen sind. Es scheint, als ob Sie beabsichtigen exp, von einem anderen Code aus aufgerufen zu werden - es ist ein Einstiegspunkt -, aber zB exp_opist er nicht aufrufbar und exp_rettechnisch aufrufbar, sondern fungiert nur als No-Op. Sie können sie irgendwie als "lokale Implementierungsdetails, nicht für den externen Verbrauch" markieren.
Ja, technisch gesehen tun Sie dies bereits durch Exportieren .globl _startund nicht .globl exp- aber es gibt immer noch einen großen Unterschied zwischen der aufrufbaren Funktion expund der lokalen Bezeichnung, exp_opder sich nicht in Ihrem Namensschema widerspiegelt. Wenn ich das tun würde, .globl expwürde ich hinzufügen und exp_op, exp_retin etwas wie Lexp1, Lexp2oder umbenennen L1_looptop, L2_loopend.