Menggunakan contoh ini berasal dari wikipedia, di mana DrawSquare () memanggil DrawLine (),
(Perhatikan bahwa diagram ini memiliki alamat tinggi di bagian bawah dan alamat rendah di bagian atas.)
Adakah yang bisa menjelaskan kepada saya apa ebp
dan esp
dalam konteks ini?
Dari apa yang saya lihat, saya akan mengatakan penunjuk tumpukan selalu menunjuk ke atas tumpukan, dan penunjuk dasar ke awal fungsi saat ini? Atau apa?
sunting: Maksud saya ini dalam konteks program windows
edit2: Dan bagaimana cara eip
kerjanya juga?
edit3: Saya memiliki kode berikut dari MSVC ++:
var_C= dword ptr -0Ch
var_8= dword ptr -8
var_4= dword ptr -4
hInstance= dword ptr 8
hPrevInstance= dword ptr 0Ch
lpCmdLine= dword ptr 10h
nShowCmd= dword ptr 14h
Semuanya tampaknya dword, sehingga masing-masing mengambil 4 byte. Jadi saya bisa melihat ada celah dari hInstance ke var_4 dari 4 byte. Apakah mereka? Saya menganggap itu adalah alamat pengirim, seperti yang dapat dilihat pada gambar wikipedia?
(catatan editor: menghapus kutipan panjang dari jawaban Michael, yang tidak termasuk dalam pertanyaan, tetapi pertanyaan lanjutan telah diedit):
Ini karena aliran pemanggilan fungsi adalah:
* Push parameters (hInstance, etc.)
* Call function, which pushes return address
* Push ebp
* Allocate space for locals
Pertanyaan saya (terakhir, saya harap!) Sekarang adalah, apa sebenarnya yang terjadi sejak saya memunculkan argumen fungsi yang ingin saya panggil hingga akhir prolog? Saya ingin tahu bagaimana ebp, esp berkembang pada saat-saat itu (saya sudah mengerti bagaimana prolog bekerja, saya hanya ingin tahu apa yang terjadi setelah saya mendorong argumen di stack dan sebelum prolog).
Jawaban:
esp
seperti yang Anda katakan, bagian atas tumpukan.ebp
biasanya diatur keesp
awal fungsi. Parameter fungsi dan variabel lokal diakses dengan menambahkan dan mengurangi, masing-masing, offset konstan dariebp
. Semua konvensi panggilan x86 didefinisikanebp
sebagai terpelihara di seluruh panggilan fungsi.ebp
itu sendiri sebenarnya menunjuk ke basis pointer frame sebelumnya, yang memungkinkan stack berjalan di debugger dan melihat variabel lokal frame lain untuk bekerja.Sebagian besar fungsi prolog terlihat seperti:
Kemudian nanti dalam fungsi Anda mungkin memiliki kode seperti (anggap kedua variabel lokal adalah 4 byte)
Pengoptimalan penghilangan FPO atau frame pointer yang dapat Anda aktifkan sebenarnya akan menghilangkan ini dan digunakan
ebp
sebagai register lain dan langsung mengakses penduduk lokalesp
, tetapi ini membuat proses debug sedikit lebih sulit karena debugger tidak lagi dapat secara langsung mengakses frame tumpukan panggilan fungsi sebelumnya.EDIT:
Untuk pertanyaan Anda yang diperbarui, dua entri yang hilang dalam tumpukan adalah:
Ini karena aliran pemanggilan fungsi adalah:
hInstance
, dll.)ebp
sumber
ESP adalah penunjuk tumpukan saat ini, yang akan berubah setiap kali suatu kata atau alamat didorong atau dilempar ke / dari tumpukan. EBP adalah cara yang lebih nyaman bagi kompiler untuk melacak parameter fungsi dan variabel lokal daripada menggunakan ESP secara langsung.
Secara umum (dan ini dapat bervariasi dari kompiler ke kompiler), semua argumen ke fungsi yang dipanggil didorong ke stack oleh fungsi panggilan (biasanya dalam urutan terbalik yang dinyatakan dalam prototipe fungsi, tetapi ini bervariasi) . Kemudian fungsinya dipanggil, yang mendorong alamat pengirim (EIP) ke stack.
Setelah masuk ke fungsi, nilai EBP lama didorong ke stack dan EBP diatur ke nilai ESP. Kemudian ESP dikurangi (karena tumpukan tumbuh ke bawah dalam memori) untuk mengalokasikan ruang untuk variabel lokal sementara dan fungsi. Sejak saat itu, selama pelaksanaan fungsi, argumen ke fungsi terletak pada tumpukan di offset positif dari EBP (karena mereka didorong sebelum pemanggilan fungsi), dan variabel lokal terletak pada offset negatif dari EBP (karena mereka dialokasikan pada stack setelah entri fungsi). Itu sebabnya EBP disebut frame pointer , karena itu menunjuk ke pusat frame panggilan fungsi .
Saat keluar, semua fungsi yang harus dilakukan adalah mengatur ESP ke nilai EBP (yang mendelokasi variabel lokal dari stack, dan mengekspos entri EBP di bagian atas stack), kemudian memunculkan nilai EBP lama dari stack, dan kemudian fungsi kembali (muncul alamat pengirim ke EIP).
Setelah kembali ke fungsi panggilan, ia kemudian dapat menambah ESP untuk menghapus argumen fungsi yang didorongnya ke stack sesaat sebelum memanggil fungsi lainnya. Pada titik ini, tumpukan kembali dalam keadaan yang sama seperti sebelum memanggil fungsi yang dipanggil.
sumber
Anda benar. Penunjuk tumpukan menunjuk ke item teratas pada tumpukan dan penunjuk dasar menunjuk ke atas "sebelumnya" dari tumpukan sebelum fungsi dipanggil.
Saat Anda memanggil suatu fungsi, variabel lokal apa pun akan disimpan di stack dan penunjuk stack akan bertambah. Ketika Anda kembali dari fungsi, semua variabel lokal di stack keluar dari cakupan. Anda melakukan ini dengan mengatur penunjuk tumpukan kembali ke penunjuk dasar (yang merupakan "sebelumnya" atas sebelum panggilan fungsi).
Melakukan alokasi memori dengan cara ini sangat , sangat cepat dan efisien.
sumber
EDIT: Untuk deskripsi yang lebih baik, lihat x86 Disassembly / Fungsi dan Stack Frames di WikiBook tentang perakitan x86. Saya mencoba menambahkan beberapa info yang Anda mungkin tertarik menggunakan Visual Studio.
Menyimpan EBP pemanggil sebagai variabel lokal pertama disebut bingkai tumpukan standar, dan ini dapat digunakan untuk hampir semua konvensi pemanggilan pada Windows. Terdapat perbedaan apakah pemanggil atau callee mendeallocate parameter yang dikirimkan, dan parameter mana yang dilewatkan dalam register, tetapi ini ortogonal dengan masalah standar stack frame.
Berbicara tentang program Windows, Anda mungkin menggunakan Visual Studio untuk mengkompilasi kode C ++ Anda. Ketahuilah bahwa Microsoft menggunakan pengoptimalan yang disebut Frame Pointer Omission, yang membuatnya hampir tidak mungkin untuk melakukan stack tanpa menggunakan perpustakaan dbghlp dan file PDB untuk dieksekusi.
Penghapusan Frame Pointer ini berarti bahwa kompiler tidak menyimpan EBP lama di tempat standar dan menggunakan register EBP untuk sesuatu yang lain, oleh karena itu Anda kesulitan menemukan pemanggil EIP tanpa mengetahui berapa banyak ruang yang dibutuhkan variabel lokal untuk fungsi yang diberikan. Tentu saja Microsoft menyediakan API yang memungkinkan Anda melakukan stack-walk bahkan dalam kasus ini, tetapi mencari database tabel simbol dalam file PDB membutuhkan waktu terlalu lama untuk beberapa kasus penggunaan.
Untuk menghindari FPO di unit kompilasi Anda, Anda harus menghindari penggunaan / O2 atau perlu menambahkan / Oy- secara eksplisit ke flag kompilasi C ++ di proyek Anda. Anda mungkin terhubung dengan runtime C atau C ++, yang menggunakan FPO dalam konfigurasi Release, jadi Anda akan kesulitan untuk melakukan stack walks tanpa dbghlp.dll.
sumber
Pertama-tama, penunjuk tumpukan menunjuk ke bagian bawah tumpukan karena tumpukan x86 dibangun dari nilai alamat tinggi ke nilai alamat lebih rendah. Penunjuk tumpukan adalah titik di mana panggilan berikutnya untuk mendorong (atau memanggil) akan menempatkan nilai berikutnya. Pengoperasian itu setara dengan pernyataan C / C ++:
Pointer dasar berada di atas bingkai saat ini. EPP umumnya menunjuk ke alamat pengirim Anda. EPP + 4 menunjuk ke parameter pertama dari fungsi Anda (atau nilai ini dari metode kelas). ebp-4 menunjuk ke variabel lokal pertama dari fungsi Anda, biasanya nilai lama dari ebp sehingga Anda dapat mengembalikan frame pointer sebelumnya.
sumber
Sudah lama sejak saya melakukan pemrograman Majelis, tetapi tautan ini mungkin berguna ...
Prosesor memiliki koleksi register yang digunakan untuk menyimpan data. Beberapa di antaranya adalah nilai langsung sementara yang lain menunjuk ke area dalam RAM. Register memang cenderung digunakan untuk tindakan spesifik tertentu dan setiap operan dalam perakitan akan membutuhkan sejumlah data dalam register tertentu.
Penunjuk tumpukan sebagian besar digunakan saat Anda memanggil prosedur lain. Dengan kompiler modern, banyak data akan dibuang terlebih dahulu di stack, diikuti oleh alamat kembali sehingga sistem akan tahu ke mana harus kembali setelah diperintahkan untuk kembali. Penunjuk tumpukan akan menunjuk ke lokasi berikutnya di mana data baru dapat didorong ke tumpukan, di mana ia akan tetap sampai muncul kembali.
Register basis atau register segmen hanya menunjuk ke ruang alamat sejumlah besar data. Dikombinasikan dengan regiser kedua, pointer Base akan membagi memori dalam blok besar sementara register kedua akan menunjuk pada item dalam blok ini. Pointer basis untuk itu menunjuk ke basis blok data.
Perlu diingat bahwa Assembly sangat spesifik untuk CPU. Halaman yang saya tautkan menyediakan informasi tentang berbagai jenis CPU.
sumber
Edit Ya, ini sebagian besar salah. Ini menggambarkan sesuatu yang sama sekali berbeda jika ada yang tertarik :)
Ya, penunjuk tumpukan menunjuk ke atas tumpukan (apakah itu lokasi tumpukan kosong pertama atau yang penuh terakhir yang saya tidak yakin). Pointer basis menunjuk ke lokasi memori instruksi yang sedang dieksekusi. Ini ada pada level opcodes - instruksi paling dasar yang bisa Anda dapatkan di komputer. Setiap opcode dan parameternya disimpan di lokasi memori. Satu baris C atau C ++ atau C # dapat diterjemahkan ke satu opcode, atau urutan dua atau lebih tergantung pada seberapa kompleksnya. Ini ditulis ke dalam memori program secara berurutan dan dieksekusi. Dalam keadaan normal, penunjuk dasar bertambah satu instruksi. Untuk kontrol program (GOTO, IF, dll) dapat ditambahkan beberapa kali atau hanya diganti dengan alamat memori berikutnya.
Dalam konteks ini, fungsi disimpan dalam memori program di alamat tertentu. Ketika fungsi dipanggil, informasi tertentu didorong pada tumpukan yang memungkinkan program menemukan kembali ke tempat fungsi dipanggil dari serta parameter ke fungsi, maka alamat fungsi dalam memori program didorong ke dalam pointer dasar. Pada siklus jam berikutnya komputer mulai menjalankan instruksi dari alamat memori itu. Kemudian di beberapa titik akan KEMBALI ke lokasi memori SETELAH instruksi yang disebut fungsi dan melanjutkan dari sana.
sumber
esp singkatan dari "Extended Stack Pointer" ..... ebp untuk "Something Base Pointer" .... dan eip untuk "Something Instruction Pointer" ...... Pointer stack menunjuk ke alamat offset segmen stack . Base Pointer menunjuk ke alamat offset segmen ekstra. Instruksi Pointer menunjuk ke alamat offset segmen kode. Sekarang, tentang segmen ... mereka adalah divisi 64KB kecil dari area memori prosesor ..... Proses ini dikenal sebagai Segmentasi Memori. Saya harap posting ini bermanfaat.
sumber