Apa fungsi instruksi push / pop yang digunakan pada register di perakitan x86?

105

Saat membaca tentang assembler, saya sering menjumpai orang yang menulis bahwa mereka mendorong register prosesor tertentu dan memunculkannya lagi nanti untuk memulihkan keadaan sebelumnya.

  • Bagaimana Anda bisa mendaftar? Di mana itu didorong? Mengapa ini dibutuhkan?
  • Apakah ini bermuara pada instruksi prosesor tunggal atau lebih kompleks?
Ars emble
sumber
4
Peringatan: semua jawaban saat ini diberikan dalam sintaks perakitan Intel; push-pop di AT & T sintaks misalnya menggunakan post-fix seperti b, w, l, atau quntuk menunjukkan ukuran memori dimanipulasi. Contoh: pushl %eaxdanpopl %eax
Hawken
5
@hawken Pada sebagian besar assembler yang dapat menelan sintaks AT&T (terutama gas), ukuran postfix dapat dihilangkan jika ukuran operan dapat disimpulkan dari ukuran operand. Ini adalah kasus untuk contoh yang Anda berikan, seperti %eaxyang selalu berukuran 32 bit.
Gunther Piez

Jawaban:

155

mendorong nilai (tidak harus disimpan dalam register) berarti menuliskannya ke stack.

popping berarti memulihkan apa pun yang ada di atas tumpukan ke dalam register. Itu adalah instruksi dasar:

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
Linus Kleen
sumber
5
Operan eksplisit untuk push dan pop adalah r/m, bukan hanya mendaftar, jadi Anda bisa push dword [esi]. Atau bahkan pop dword [esp]untuk memuat dan kemudian menyimpan nilai yang sama kembali ke alamat yang sama. ( github.com/HJLebbink/asm-dude/wiki/POP ). Saya hanya menyebutkan ini karena Anda mengatakan "belum tentu mendaftar".
Peter Cordes
2
Anda juga dapat popmasuk ke area memori:pop [0xdeadbeef]
SS Anne
Halo, apa perbedaan antara push / pop dan pushq / popq? Saya di macos / intel
SteakOverflow
47

Inilah cara Anda mendorong register. Saya berasumsi kita berbicara tentang x86.

push ebx
push eax

Itu didorong di tumpukan. Nilai dariESP register diturunkan ke ukuran nilai yang didorong saat tumpukan tumbuh ke bawah dalam sistem x86.

Itu diperlukan untuk melestarikan nilai-nilai. Penggunaan umum adalah

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

A pushadalah instruksi tunggal di x86, yang melakukan dua hal secara internal.

  1. Kurangi ESPregister dengan ukuran nilai yang didorong.
  2. Simpan nilai yang didorong di alamat ESPregister saat ini.
Madhur Ahuja
sumber
40

Di mana itu didorong?

esp - 4. Lebih tepatnya:

  • esp akan dikurangi 4
  • nilai didorong ke esp

pop membalikkan ini.

System V ABI memberi tahu Linux untuk rspmenunjukkan lokasi stack yang masuk akal ketika program mulai berjalan: Apa status register default ketika program diluncurkan (asm, linux)? yang biasanya Anda gunakan.

Bagaimana Anda bisa mendaftar?

Contoh GNU GAS Minimal:

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

Di atas di GitHub dengan pernyataan yang dapat dijalankan .

Mengapa ini dibutuhkan?

Memang benar bahwa instruksi tersebut dapat dengan mudah diimplementasikan melalui mov, adddan sub.

Alasan mereka ada, adalah karena kombinasi instruksi tersebut sangat sering, sehingga Intel memutuskan untuk menyediakannya untuk kami.

Alasan mengapa kombinasi tersebut sangat sering, adalah karena kombinasi tersebut memudahkan untuk menyimpan dan memulihkan nilai register ke memori sementara sehingga tidak ditimpa.

Untuk memahami masalahnya, coba kompilasi beberapa kode C secara manual.

Kesulitan utama, adalah memutuskan di mana setiap variabel akan disimpan.

Idealnya, semua variabel akan masuk ke dalam register, yang merupakan memori tercepat untuk diakses (saat ini sekitar 100x lebih cepat dari RAM).

Tetapi tentu saja, kita dapat dengan mudah memiliki lebih banyak variabel daripada register, khususnya untuk argumen fungsi bersarang, jadi satu-satunya solusi adalah menulis ke memori.

Kita bisa menulis ke alamat memori apa pun, tetapi karena variabel lokal dan argumen pemanggilan dan pengembalian fungsi cocok dengan pola tumpukan yang bagus, yang mencegah fragmentasi memori , itulah cara terbaik untuk mengatasinya. Bandingkan dengan kegilaan menulis pengalokasi heap.

Kemudian kita biarkan kompiler mengoptimalkan alokasi register untuk kita, karena itu adalah NP lengkap, dan salah satu bagian tersulit dalam menulis kompilator. Masalah ini disebut alokasi register , dan itu isomorfik untuk pewarnaan graf .

Ketika pengalokasi kompiler dipaksa untuk menyimpan sesuatu dalam memori dan bukan hanya register, itu dikenal sebagai spill .

Apakah ini bermuara pada instruksi prosesor tunggal atau lebih kompleks?

Yang kita tahu pasti adalah bahwa Intel mendokumentasikan a pushdan popinstruksi, jadi mereka adalah satu instruksi dalam pengertian itu.

Secara internal, ini dapat diperluas ke beberapa mikrokode, satu untuk memodifikasi espdan satu untuk melakukan IO memori, dan mengambil banyak siklus.

Tapi mungkin juga itu single push lebih cepat daripada kombinasi setara dari instruksi lain, karena lebih spesifik.

Ini sebagian besar tidak (der) didokumentasikan:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
4
Anda tidak perlu menebak tentang bagaimana push/ popmendekode menjadi uops. Berkat penghitung kinerja, pengujian eksperimental dimungkinkan, dan Agner Fog telah melakukannya dan menerbitkan tabel instruksi . Pentium-M dan CPU yang lebih baru memiliki single-uop push/ popterima kasih kepada mesin stack (Lihat pdf microarch Agner). Ini termasuk CPU AMD terbaru, berkat kesepakatan pembagian paten Intel / AMD.
Peter Cordes
@Peteres luar biasa! Jadi penghitung kinerja didokumentasikan oleh Intel untuk menghitung operasi mikro?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Selain itu, variabel lokal yang ditumpahkan dari reg biasanya akan tetap menjadi panas di cache L1 jika salah satunya benar-benar digunakan. Tetapi membaca dari register secara efektif gratis, tanpa latensi. Jadi ini jauh lebih cepat daripada cache L1, tergantung bagaimana Anda ingin mendefinisikan istilah. Untuk penduduk lokal hanya-baca yang tumpah ke tumpukan, biaya utamanya hanyalah beban tambahan uops (terkadang operan memori, terkadang dengan movmuatan terpisah ). Untuk variabel non-const yang tumpah, perjalanan bolak-balik penerusan toko memiliki latensi ekstra (~ 5c ekstra vs. meneruskan secara langsung, dan instruksi toko tidak murah).
Peter Cordes
Ya, ada penghitung untuk total uops di beberapa tahapan pipeline yang berbeda (masalah / eksekusi / pensiunkan), jadi Anda dapat menghitung fused-domain atau unfused-domain. Lihat jawaban ini sebagai contoh. Jika saya menulis ulang jawaban itu sekarang, saya akan menggunakan ocperf.pyskrip pembungkus untuk mendapatkan nama simbolis yang mudah untuk penghitung.
Peter Cordes
26

Register pendorong dan popping berada di belakang layar yang setara dengan ini:

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

Perhatikan bahwa ini adalah sintaks x86-64 At & t.

Digunakan sebagai pasangan, ini memungkinkan Anda menyimpan register di stack dan memulihkannya nanti. Ada kegunaan lain juga.

gowrath
sumber
5
Ya, urutan tersebut meniru push / pop dengan benar. (kecuali push / pop tidak mempengaruhi bendera).
Peter Cordes
2
Lebih baik Anda menggunakan lea rsp, [rsp±8]daripada add/ subuntuk meniru efek push/ poppada flag dengan lebih baik.
Ruslan
13

Hampir semua CPU menggunakan stack. Tumpukan program adalah teknik LIFO dengan pengelolaan yang didukung perangkat keras.

Stack adalah jumlah memori program (RAM) yang biasanya dialokasikan di bagian atas heap memori CPU dan bertambah (pada instruksi PUSH, penunjuk stack berkurang) ke arah yang berlawanan. Istilah standar untuk memasukkan ke dalam tumpukan adalah PUSH dan untuk dihapus dari tumpukan adalah POP .

Stack dikelola melalui register CPU yang dimaksudkan oleh stack, juga disebut stack pointer, jadi ketika CPU melakukan POP atau PUSH , penunjuk tumpukan akan memuat / menyimpan register atau konstan ke dalam memori tumpukan dan penunjuk tumpukan akan otomatis berkurang xor bertambah sesuai jumlah kata yang didorong atau dimunculkan ke (dari) tumpukan.

Melalui instruksi assembler yang dapat kami simpan untuk ditumpuk:

  1. Register CPU dan juga konstanta.
  2. Kembalikan alamat untuk fungsi atau prosedur
  3. Fungsi / prosedur masuk / keluar variabel
  4. Fungsi / prosedur variabel lokal.
GJ.
sumber