x86-Programm zum Ausführen eines Exponenten

Sep 11 2020

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 tun push rbp mov 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

3 Quuxplusone Sep 11 2020 at 13:45

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.