Wat is de functie van de push / pop-instructies die worden gebruikt op registers in x86-assemblage?

Als ik over assembler lees, kom ik vaak mensen tegen die schrijven dat ze een bepaald register van de processor duwenen het later opnieuw poppenom de vorige staat te herstellen.

>

  • Hoe kun je een register pushen? Waar wordt het opgeschoven? Waarom is dit nodig?
  • Komt dit neer op een enkele processorinstructie of is het complexer?

Antwoord 1, autoriteit 100%

duweneen waarde (niet noodzakelijk opgeslagen in een register) betekent dat deze naar de stapel wordt geschreven.

poppenbetekent het terugzetten van wat zich boven op de stapel bevindt ineen register. Dit zijn basisinstructies:

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

Antwoord 2, autoriteit 30%

Zo druk je op een register. Ik neem aan dat we het over x86 hebben.

push ebx
push eax

Het wordt op de stapel geduwd. De waarde van het esp-register wordt verlaagd naar de grootte van de gepushte waarde naarmate de stapel in x86-systemen naar beneden groeit.

Het is nodig om de waarden te behouden. Het algemene gebruik is

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

Een pushis een enkele instructie in x86, die intern twee dingen doet.

  1. Verlaag het espregister met de grootte van de gepushte waarde.
  2. Sla de gepushte waarde op het huidige adres van het esp-register op.

Antwoord 3, autoriteit 24%

Waar wordt het ingedrukt?

esp - 4. Nauwkeuriger:

  • espwordt afgetrokken door 4
  • De waarde wordt ingedrukt op esp

popREVERSES DIT.

Het SYSTEEM V ABI vertelt Linux om rspte maken naar een verstandige stapellocatie wanneer het programma start: Wat is standaardregisterstatus wanneer het programma lanceert (ASM, Linux)? wat u gewoonlijk moet gebruiken. < / p>

Hoe kunt u een register drukken?

Minimaal GNU-gasvoorbeeld:

.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.
    */

De bovenstaande op GitHub met RunNable Assertions .

Waarom is dit nodig?

Het is waar dat deze instructies eenvoudig kunnen worden geïmplementeerd via mov, addEN sub.

.

De reden dat ze bestaan, is dat die combinaties van instructies zo vaak voorkomen dat Intel besloot ze voor ons te verstrekken.

De reden waarom deze combinaties zo vaak voorkomen, is dat ze het gemakkelijk maken om de waarden van registers tijdelijk op te slaan en te herstellen in het geheugen, zodat ze niet worden overschreven.

Probeer om eens wat C-code met de hand te compileren om het probleem te begrijpen.

Een grote moeilijkheid is om te beslissen waar elke variabele wordt opgeslagen.

Idealiter zouden alle variabelen in registers passen, wat het snelst toegankelijke geheugen is (momenteel ongeveer 100x snellerdan RAM).

Maar we kunnen natuurlijk gemakkelijk meer variabelen dan registers hebben, speciaal voor de argumenten van geneste functies, dus de enige oplossing is om naar het geheugen te schrijven.

We zouden naar elk geheugenadres kunnen schrijven, maar aangezien de lokale variabelen en argumenten van functieaanroepen en -returns in een mooi stapelpatroon passen, voorkomt geheugenfragmentatie, dat is de beste manier om ermee om te gaan. Vergelijk dat met de waanzin van het schrijven van een heap-allocator.

Vervolgens laten we compilers de registertoewijzing voor ons optimaliseren, aangezien dat NP compleet is, en een van de moeilijkste onderdelen van het schrijven van een compiler. Dit probleem wordt toekenning registrerengenoemd en is isomorf met grafiek kleuren.

Als de allocator van de compiler wordt gedwongen om dingen in het geheugen op te slaan in plaats van alleen registers, staat dat bekend als een spill.

Komt dit neer op een enkele processorinstructie of is het complexer?

Het enige wat we zeker weten is dat Intel een pushen een popinstructie documenteert, dus in die zin zijn ze één instructie.

Intern kan het worden uitgebreid tot meerdere microcodes, één om espaan te passen en één om de geheugen-IO te doen, en meerdere cycli te nemen.

Maar het is ook mogelijk dat een enkele pushsneller is dan een equivalente combinatie van andere instructies, omdat deze specifieker is.

Dit is meestal on(der)gedocumenteerd:


Antwoord 4, autoriteit 16%

Pushing en popping registers zijn achter de schermen equivalent aan dit:

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

Let op: dit is x86-64 At&t-syntaxis.

Als een paar wordt gebruikt, kunt u hiermee een register op de stapel opslaan en later herstellen. Er zijn ook andere toepassingen.


Antwoord 5, autoriteit 9%

Bijna alle CPU’s gebruiken stack. De programmastack is een LIFO-techniek met door hardware ondersteund beheer.

Stack is de hoeveelheid programmageheugen (RAM) die normaal gesproken wordt toegewezen aan de bovenkant van de CPU-geheugenheap en groeit (bij PUSH-instructie wordt de stapelaanwijzer verlaagd) in tegenovergestelde richting. Een standaardterm voor invoegen in stapel is PUSHen voor verwijderen van stapel is POP.

Stack wordt beheerd via het voor de stapel bestemde CPU-register, ook wel stack-pointer genoemd, dus wanneer de CPU POPof PUSHuitvoert, zal de stack-pointer een register of constante laden/opslaan in stapelgeheugen en de stapelaanwijzer worden automatisch verlaagd of verhoogd volgens het aantal woorden dat in (van) stapel is geduwd of gepopt.

Via assembler-instructies die we kunnen opslaan om te stapelen:

  1. CPU-registers en ook constanten.
  2. Retouradressen voor functies of
    procedures
  3. Functies/procedures in/uit
    variabelen
  4. Functies/procedures lokaal
    variabelen.

Other episodes