wake-up-neo.com

Welche Funktion haben die Push / Pop-Anweisungen für Register in der x86-Assembly?

Wenn ich über Assembler lese, stoße ich oft auf Leute, die schreiben, dass sie Push ein bestimmtes Register des Prozessors haben und Pop es später erneut tun, um den vorherigen Zustand wiederherzustellen.

  • Wie kann man ein Register pushen? Wo ist es dran? Warum ist das nötig?
  • Kommt es auf eine einzelne Prozessoranweisung an oder ist es komplexer?
72
Ars emble

Drücken Ein Wert (der nicht unbedingt in einem Register gespeichert ist) bedeutet, dass er in den Stapel geschrieben wird.

poppen bedeutet, dass alles, was sich oben auf dem Stapel befindet, wiederhergestellt wird in ein Register. Das sind grundlegende Anweisungen:

Push 0xdeadbeef      ; Push a value to the stack
pop eax              ; eax is now 0xdeadbeef

; swap contents of registers
Push eax
mov eax, ebx
pop ebx
118
Linus Kleen

So schiebst du ein Register. Ich nehme an, wir sprechen über x86.

Push ebx
Push eax

Es wird auf Stapel geschoben. Der Wert des Registers ESP wird auf die Größe des übertragenen Werts dekrementiert, wenn der Stapel in x86-Systemen nach unten wächst.

Es wird benötigt, um die Werte zu erhalten. Die allgemeine Verwendung ist

Push eax           ;   preserve the value of eax
call some_method   ;   some method is called which will put return value in eax
mov  edx, eax      ;    move the return value to edx
pop  eax           ;    restore original eax

Ein Push ist eine einzelne Anweisung in x86, die intern zwei Aktionen ausführt.

  1. Speichern Sie den übertragenen Wert unter der aktuellen Adresse des Registers ESP.
  2. Dekrementieren Sie das ESP -Register auf die Größe des übertragenen Werts.
37
Madhur Ahuja

Wo ist es angeschoben?

esp - 4. Etwas präziser:

  • esp wird um 4 subtrahiert
  • der Wert wird auf esp verschoben

pop kehrt dies um.

Die System V-ABI weist Linux an, rsp auf einen sinnvollen Stack-Speicherort zu verweisen, wenn das Programm gestartet wird: Was ist der Standard-Registrierungsstatus, wenn das Programm gestartet wird (asm, linux)? Was ist das? Sie sollten normalerweise verwenden.

Wie kann man ein Register pushen?

Minimal GNU GAS-Beispiel:

.data
    /* .long takes 4 bytes each. */
    val1:
        /* Store bytes 0x 01 00 00 00 here. */
        .long 1
    val2:
        /* 0x 02 00 00 00 */
        .long 2
.text
    /* Make esp point to the address of val2.
     * Unusual, but totally possible. */
    mov $val2, %esp

    /* eax = 3 */
    mov $3, %ea 

    Push %eax
    /*
    Outcome:
    - esp == val1
    - val1 == 3
    esp was changed to point to val1,
    and then val1 was modified.
    */

    pop %ebx
    /*
    Outcome:
    - esp == &val2
    - ebx == 3
    Inverses Push: ebx gets the value of val1 (first)
    and then esp is increased back to point to val2.
    */

Das obige auf GitHub mit ausführbaren Behauptungen .

Warum wird das benötigt?

Es ist wahr, dass diese Anweisungen einfach über mov, add und sub implementiert werden könnten.

Der Grund dafür ist, dass diese Kombinationen von Anweisungen so häufig sind, dass Intel beschlossen hat, sie uns zur Verfügung zu stellen.

Der Grund, warum diese Kombinationen so häufig sind, ist, dass sie das Speichern und Wiederherstellen der Werte von Registern im Speicher vorübergehend vereinfachen, damit sie nicht überschrieben werden.

Um das Problem zu verstehen, kompilieren Sie etwas C-Code von Hand.

Eine Hauptschwierigkeit besteht darin, zu entscheiden, wo jede Variable gespeichert wird.

Im Idealfall passen alle Variablen in Register, auf die am schnellsten zugegriffen werden kann (derzeit ungefähr 100x schneller als RAM).

Aber natürlich können wir leicht mehr Variablen als Register haben, speziell für die Argumente verschachtelter Funktionen. Die einzige Lösung besteht also darin, in den Speicher zu schreiben.

Wir könnten in jede Speicheradresse schreiben, aber da die lokalen Variablen und Argumente von Funktionsaufrufen und Rückgaben in ein Nice-Stack-Muster passen, das Speicherfragmentierung verhindert, ist dies der beste Weg, um damit umzugehen. Vergleichen Sie das mit dem Wahnsinn, einen Heap-Allokator zu schreiben.

Dann lassen wir Compiler die Registerzuordnung für uns optimieren, da dies NP vollständig und einer der schwierigsten Teile beim Schreiben eines Compilers ist. Dieses Problem wird Registerzuordnung genannt , und es ist isomorph zu graph coloring .

Wenn der Allokator des Compilers gezwungen ist, Dinge im Speicher statt nur in Registern zu speichern, wird dies als spill bezeichnet.

Fällt dies auf eine einzelne Prozessoranweisung zurück oder ist es komplexer?

Wir wissen nur, dass Intel eine Anweisung Push und eine Anweisung pop dokumentiert, also handelt es sich um eine Anweisung in diesem Sinne.

Intern könnte es auf mehrere Mikrocodes erweitert werden, von denen einer zum Ändern von esp und einer zum Ausführen der Speicher-E/A und für mehrere Zyklen benötigt wird.

Es ist aber auch möglich, dass ein einzelnes Push schneller ist als eine entsprechende Kombination anderer Anweisungen, da es spezifischer ist.

Dies ist meist un (der) dokumentiert:

Push- und Popping-Register sind hinter den Kulissen gleichbedeutend mit:

Push reg   <= same as =>      sub  $8,%rsp        # subtract 8 from rsp
                              mov  reg,(%rsp)     # store, using rsp as the address

pop  reg    <= same as=>      mov  (%rsp),reg     # load, using rsp as the address
                              add  $8,%rsp        # add 8 to the rsp

Beachten Sie, dass dies die x86-64 At & t-Syntax ist.

Als Paar verwendet, können Sie ein Register auf dem Stapel speichern und später wiederherstellen. Es gibt auch andere Verwendungen.

16
gowrath

Fast alle CPUs verwenden Stack. Der Programmstack ist LIFO Technik mit Hardware unterstützt verwalten.

Stack ist die Größe des Programmspeichers (RAM), der normalerweise am oberen Rand des CPU-Speicherheaps zugewiesen wird und in entgegengesetzter Richtung wächst (beim Push-Befehl wird der Stack-Zeiger verringert). Ein Standardbegriff zum Einfügen in einen Stapel ist Push und zum Entfernen aus dem Stapel ist [~ # ~] pop [~ # ~] .

Der Stack wird über das für den Stack vorgesehene CPU-Register verwaltet, das auch als Stack-Zeiger bezeichnet wird. Wenn die CPU also einen [~ # ~] Pop [~ # ~] oder Push ausführt Der Stapelzeiger lädt/speichert ein Register oder eine Konstante im Stapelspeicher und der Stapelzeiger wird automatisch verkleinert oder vergrößert, je nachdem, wie viele Wörter in den Stapel geschoben oder aus dem Stapel geschoben wurden.

Über Assembler-Anweisungen können wir speichern, um zu stapeln:

  1. CPU-Register und auch Konstanten.
  2. Adressen für Funktionen oder Prozeduren zurückgeben
  3. Funktionen/Prozeduren in/out Variablen
  4. Funktionen/Prozeduren lokale Variablen.
11
GJ.