Apa tujuan register penunjuk bingkai EBP?

95

Saya seorang pemula dalam bahasa assembly dan telah memperhatikan bahwa kode x86 yang dipancarkan oleh kompiler biasanya menyimpan penunjuk bingkai bahkan dalam mode rilis / dioptimalkan ketika itu dapat menggunakan EBPregister untuk sesuatu yang lain.

Saya mengerti mengapa penunjuk bingkai dapat membuat kode lebih mudah untuk di-debug, dan mungkin diperlukan jika alloca()dipanggil dalam suatu fungsi. Namun, x86 memiliki register yang sangat sedikit dan menggunakan dua di antaranya untuk menyimpan lokasi frame stack ketika satu sudah cukup tidak masuk akal bagi saya. Mengapa menghilangkan penunjuk bingkai dianggap sebagai ide yang buruk bahkan dalam versi yang dioptimalkan / rilis?

dsimcha
sumber
19
Jika menurut Anda x86 memiliki register yang sangat sedikit, Anda harus memeriksa 6502 :)
Sedat Kapanoglu
1
C99 VLA juga bisa mendapatkan keuntungan darinya.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
2
stackoverflow.com/questions/1395591/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Bukankah penunjuk bingkai membuat penunjuk tumpukan menjadi berlebihan? . TL; DR: 1. penyelarasan tumpukan non-sepele 2. alokasi tumpukan ( alloca) 3. kemudahan implementasi runtime: penanganan pengecualian, kotak pasir, GC
Alexander Malakhov

Jawaban:

102

Frame pointer adalah pointer referensi yang memungkinkan debugger mengetahui di mana variabel lokal atau argumen berada dengan offset konstan tunggal. Meskipun nilai ESP berubah selama eksekusi, EBP tetap sama sehingga memungkinkan untuk mencapai variabel yang sama pada offset yang sama (seperti parameter pertama akan selalu pada EBP + 8 sementara offset ESP dapat berubah secara signifikan karena Anda akan mendorong / popping sesuatu)

Mengapa kompiler tidak membuang penunjuk bingkai? Karena dengan penunjuk bingkai, debugger dapat mengetahui di mana variabel dan argumen lokal menggunakan tabel simbol karena dijamin akan berada pada offset konstan ke EBP. Jika tidak, tidak ada cara mudah untuk mengetahui di mana variabel lokal berada pada titik mana pun dalam kode.

Seperti yang disebutkan Greg, ini juga membantu pelepasan tumpukan untuk debugger karena EBP menyediakan daftar bingkai tumpukan yang ditautkan terbalik sehingga memungkinkan debugger untuk mengetahui ukuran bingkai tumpukan (variabel lokal + argumen) dari fungsi tersebut.

Kebanyakan kompiler menyediakan opsi untuk menghilangkan penunjuk bingkai meskipun itu membuat debugging menjadi sangat sulit. Opsi itu tidak boleh digunakan secara global, bahkan dalam kode rilis. Anda tidak tahu kapan Anda perlu men-debug kerusakan pengguna.

Sedat Kapanoglu
sumber
10
Kompiler mungkin tahu apa yang dilakukannya pada ESP. Poin lainnya valid, +1
erikkallen
8
Debugger modern dapat melakukan pelacakan latar belakang bahkan dalam kode yang dikompilasi -fomit-frame-pointer. Pengaturan itu adalah default di gcc terbaru.
Peter Cordes
2
@SedatKapanoglu: Bagian data mencatat info yang diperlukan: yosefk.com/blog/…
Peter Cordes
3
@SedatKapanoglu: .eh_frame_hdrbagian ini juga digunakan untuk pengecualian waktu proses. Anda akan menemukannya (dengan objdump -h) di sebagian besar binari pada sistem Linux, Ini sekitar 16k untuk /bin/bash, vs. 572B untuk GNU /bin/true, 108k untuk ffmpeg. Ada opsi gcc untuk menonaktifkan pembuatannya, tetapi ini adalah bagian data "normal", bukan bagian debug yang stripdihapus secara default. Jika tidak, Anda tidak dapat menelusuri kembali melalui fungsi pustaka yang tidak memiliki simbol debug. Bagian itu mungkin lebih besar dari push/mov/popinstruksi yang digantikannya, tetapi memiliki biaya runtime yang mendekati nol (misalnya cache uop).
Peter Cordes
3
Mengenai "seperti parameter pertama akan selalu pada EBP-4": Bukankah parameter pertama pada EBP + 8 (pada x86)?
Aydin K.
31

Hanya menambahkan dua sen saya ke jawaban yang sudah bagus.

Ini adalah bagian dari arsitektur bahasa yang baik untuk memiliki rangkaian bingkai tumpukan. BP menunjuk ke frame saat ini, di mana variabel subrutin-lokal disimpan. (Penduduk setempat berada di offset negatif, dan argumen berada di offset positif.)

Gagasan bahwa ini mencegah register yang sangat baik digunakan dalam pengoptimalan menimbulkan pertanyaan: kapan dan di mana pengoptimalan sebenarnya bermanfaat?

Optimasi hanya bermanfaat dalam loop ketat yang 1) tidak memanggil fungsi, 2) di mana pencacah program menghabiskan sebagian besar waktunya, dan 3) dalam kode yang benar-benar akan dilihat kompilator (yaitu fungsi non-perpustakaan). Ini biasanya merupakan bagian yang sangat kecil dari keseluruhan kode, terutama dalam sistem yang besar.

Kode lain dapat dipelintir dan diperas untuk menghilangkan siklus, dan itu tidak masalah, karena penghitung program secara praktis tidak pernah ada.

Saya tahu Anda tidak menanyakan hal ini, tetapi menurut pengalaman saya, 99% masalah kinerja tidak ada hubungannya sama sekali dengan pengoptimalan compiler. Mereka ada hubungannya dengan desain berlebihan.

Mike Dunlavey
sumber
Terima kasih @ Mike, saya menemukan jawaban Anda sangat membantu.
sixtyfootersdude
2
Melakukan penunjuk bingkai juga menghemat beberapa instruksi setiap panggilan fungsi, yang merupakan pengoptimalan kecil dengan sendirinya. BTW, penggunaan "mohon pertanyaan" tidak benar; maksud Anda "menimbulkan pertanyaan".
pelantikan
@augurar: Tetap. Terima kasih. Saya sedikit grammar grump sendiri :)
Mike Dunlavey
3
@augurar Bahasa berkembang: "Mengimbau pertanyaan" sekarang berarti "mengajukan pertanyaan". Menjadi nitpicker preskriptif untuk penggunaan usang tidak menambahkan apa-apa.
pengguna3364825
9

Tergantung pada kompilernya, tentunya. Saya telah melihat kode yang dioptimalkan yang dikeluarkan oleh kompiler x86 yang secara bebas menggunakan register EBP sebagai register tujuan umum. (Saya tidak ingat kompiler mana yang saya perhatikan dengannya.)

Penyusun juga dapat memilih untuk mempertahankan register EBP untuk membantu pelepasan tumpukan selama penanganan pengecualian, tetapi sekali lagi ini tergantung pada implementasi kompiler yang tepat.

Greg Hewgill
sumber
Kebanyakan kompiler default -fomit-frame-pointerketika pengoptimalan diaktifkan. (bila ABI mengizinkannya). GCC, clang, ICC, dan MSVC semuanya melakukannya, IIRC, bahkan saat menargetkan Windows 32-bit. Yup, jawaban saya tentang Mengapa lebih baik menggunakan ebp daripada register esp untuk menemukan parameter di stack? menunjukkan bahwa bahkan Windows 32-bit dapat menghilangkan penunjuk bingkai. Linux 32-bit x86 pasti bisa dan bisa. Dan tentu saja 64-bit ABI telah memungkinkan penghilangan frame-pointer sejak awal.
Peter Cordes
4

Namun, x86 memiliki register yang sangat sedikit

Ini benar hanya dalam arti bahwa opcode hanya dapat menangani 8 register. Prosesor itu sendiri sebenarnya akan memiliki lebih banyak register daripada itu dan menggunakan penggantian nama register, pipelining, eksekusi spekulatif, dan kata kunci prosesor lainnya untuk menyiasati batas itu. Wikipedia memiliki paragraf pengantar yang bagus tentang apa yang dapat dilakukan prosesor x86 untuk mengatasi batas register: http://en.wikipedia.org/wiki/X86#Current_implementations .

MSN
sumber
1
Pertanyaan asli adalah tentang kode yang dihasilkan, yang dibatasi secara ketat pada register yang dapat direferensikan oleh opcode.
Darron
1
Ya, tapi inilah mengapa menghilangkan penunjuk bingkai dalam build yang dioptimalkan tidak sepenting saat ini.
Michael
1
Mengganti nama register tidak persis sama dengan sebenarnya memiliki lebih banyak register yang tersedia. Masih banyak situasi di mana penggantian nama register tidak akan membantu, tetapi register yang lebih "biasa" akan membantu.
jalf
1

Menggunakan bingkai tumpukan menjadi sangat murah di perangkat keras apa pun bahkan yang modern. Jika Anda memiliki stack frame yang murah, menyimpan beberapa register tidaklah penting. Saya yakin bingkai tumpukan cepat vs. lebih banyak register adalah trade-off teknik, dan bingkai tumpukan cepat menang.

Berapa banyak yang Anda hemat dengan mendaftar murni? Apakah itu layak?

dwc
sumber
Lebih banyak register dibatasi oleh pengkodean instruksi. x86-64 menggunakan bit dalam byte awalan REX untuk memperluas bagian instruksi yang menentukan register dari 3 menjadi 4 bit untuk register src dan dest. Jika ada ruang, x86-64 mungkin akan menggunakan 32 register arsitektural, meskipun menyimpan / memulihkan banyak pada sakelar konteks mulai bertambah. 15 adalah peningkatan besar dari 7, tetapi 31 adalah peningkatan yang jauh lebih kecil dalam banyak kasus. (tidak menghitung penunjuk tumpukan sebagai tujuan umum.) Membuat push / pop cepat sangat bagus untuk lebih dari sekadar bingkai tumpukan. Ini bukan pengorbanan dengan # reg.
Peter Cordes