Apa sebenarnya pointer dasar dan stack pointer? Apa yang mereka tunjukkan?

225

Menggunakan contoh ini berasal dari wikipedia, di mana DrawSquare () memanggil DrawLine (),

teks alternatif

(Perhatikan bahwa diagram ini memiliki alamat tinggi di bagian bawah dan alamat rendah di bagian atas.)

Adakah yang bisa menjelaskan kepada saya apa ebpdan espdalam 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 eipkerjanya 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).

melahap elysium
sumber
23
Satu hal penting yang perlu diperhatikan adalah bahwa tumpukan tumbuh "ke bawah" dalam memori. Ini berarti bahwa untuk memindahkan penunjuk tumpukan ke atas Anda mengurangi nilainya.
BS
4
Satu petunjuk untuk membedakan apa yang EBP / ESP dan EIP lakukan: EBP & ESP menangani data, sementara EIP menangani kode.
mmmmmmmm
2
Dalam grafik Anda, ebp (biasanya) adalah "frame pointer", khususnya "stack pointer". Hal ini memungkinkan untuk mengakses penduduk lokal melalui [ebp-x] dan menumpuk parameter melalui [ebp + x] secara konsisten, terlepas dari penunjuk tumpukan (yang sering berubah dalam suatu fungsi). Adressing dapat dilakukan melalui ESP, membebaskan EBP untuk operasi lain - tetapi dengan cara itu, para pengadu tidak dapat mengatakan tumpukan panggilan atau nilai-nilai lokal.
peterchen
4
@ Ben. Tidak nesacerily. Beberapa kompiler menempatkan stack frames ke heap. Konsep tumpukan tumbuh turun hanya itu, sebuah konsep yang membuatnya mudah dimengerti. Implementasi stack dapat berupa apa saja (menggunakan bongkahan acak dari tumpukan akan membuat peretasan yang menimpa bagian dari tumpukan menjadi lebih sulit karena tidak terlalu deterministik).
Martin York
1
dalam dua kata: stack pointer memungkinkan operasi push / pop bekerja (jadi push dan pop tahu di mana harus meletakkan / mendapatkan data). penunjuk dasar memungkinkan kode untuk secara independen mereferensikan data yang telah didorong sebelumnya di stack.
tigrou

Jawaban:

229

esp seperti yang Anda katakan, bagian atas tumpukan.

ebpbiasanya diatur ke espawal fungsi. Parameter fungsi dan variabel lokal diakses dengan menambahkan dan mengurangi, masing-masing, offset konstan dari ebp. Semua konvensi panggilan x86 didefinisikan ebpsebagai terpelihara di seluruh panggilan fungsi. ebpitu 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:

push ebp      ; Preserve current frame pointer
mov ebp, esp  ; Create new frame pointer pointing to current stack top
sub esp, 20   ; allocate 20 bytes worth of locals on stack.

Kemudian nanti dalam fungsi Anda mungkin memiliki kode seperti (anggap kedua variabel lokal adalah 4 byte)

mov [ebp-4], eax    ; Store eax in first local
mov ebx, [ebp - 8]  ; Load ebx from second local

Pengoptimalan penghilangan FPO atau frame pointer yang dapat Anda aktifkan sebenarnya akan menghilangkan ini dan digunakan ebpsebagai register lain dan langsung mengakses penduduk lokal esp, 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:

var_C = dword ptr -0Ch
var_8 = dword ptr -8
var_4 = dword ptr -4
*savedFramePointer = dword ptr 0*
*return address = dword ptr 4*
hInstance = dword ptr  8h
PrevInstance = dword ptr  0C
hlpCmdLine = dword ptr  10h
nShowCmd = dword ptr  14h

Ini karena aliran pemanggilan fungsi adalah:

  • Parameter dorong ( hInstance, dll.)
  • Fungsi panggilan, yang mendorong alamat kembali
  • Dorong ebp
  • Alokasikan ruang untuk penduduk setempat
Michael
sumber
1
Terima kasih untuk penjelasannya! Tapi saya sekarang agak bingung. Mari kita asumsikan saya memanggil fungsi dan saya berada di baris pertama prolognya, masih tanpa mengeksekusi satu baris pun dari itu. Pada titik itu, apa nilai ebp? Apakah tumpukan memiliki sesuatu pada titik itu selain argumen yang didorong? Terima kasih!
melahap elysium,
3
EBP tidak diubah secara ajaib, jadi sebelum Anda membuat EBP baru untuk fungsi Anda, Anda masih memiliki nilai penelepon. Dan selain argumen, tumpukan juga akan memegang EIP tua (alamat pengirim)
MSalters
3
Jawaban bagus. Meskipun tidak bisa lengkap tanpa menyebutkan apa yang ada di epilog: instruksi "pergi" dan "ret".
Calmarius
2
Saya pikir gambar ini akan membantu memperjelas beberapa hal tentang apa alirannya. Juga perlu diingat tumpukan tumbuh ke bawah. ocw.cs.pub.ro/courses/_media/so/laboratoare/call_stack.png
Andrei-Niculae Petre
Apakah ini saya, atau semua tanda minus hilang dari cuplikan kode di atas?
BarbaraKwarc
96

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.

David R Tribble
sumber
15

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.

Robert Cartaino
sumber
14
@ Robert: Ketika Anda mengatakan "sebelumnya" bagian atas tumpukan sebelum fungsi dipanggil, Anda mengabaikan kedua parameter, yang didorong ke tumpukan sebelum memanggil fungsi dan pemanggil EIP. Ini mungkin membingungkan pembaca. Anggap saja dalam bingkai stack standar, EBP menunjuk ke tempat yang sama di mana ESP menunjuk tepat setelah memasuki fungsi.
Wigy
7

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.

berbulu
sumber
Saya tidak mengerti bagaimana EIP disimpan di stack. Bukankah seharusnya itu daftar? Bagaimana cara register berada di stack? Terima kasih!
melahap elysium
Penelepon EIP didorong ke stack oleh instruksi CALL itu sendiri. Instruksi RET hanya mengambil bagian atas tumpukan dan memasukkannya ke dalam EIP. Jika Anda memiliki buffer berlebih, fakta ini mungkin digunakan untuk melompat ke kode pengguna dari utas istimewa.
Wigy
@devouredelysium Isi (atau nilai ) register EIP diletakkan di (atau disalin ke) tumpukan, bukan register itu sendiri.
BarbaraKwarc
@BarbaraKwarc Terima kasih untuk nilai masukan -Mampu. Saya tidak bisa melihat apa yang OP hilang dari jawaban saya. Memang, register tetap di tempatnya, hanya nilainya dikirim ke RAM dari CPU. Dalam mode amd64, ini menjadi sedikit lebih kompleks, tetapi biarkan itu untuk pertanyaan lain.
Berang
Bagaimana dengan amd64 itu? Saya penasaran.
BarbaraKwarc
6

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 ++:

 // push eax
 --*esp = eax
 // pop eax
 eax = *esp++;

 // a function call, in this case, the caller must clean up the function parameters
 move eax,some value
 push eax
 call some address  // this pushes the next value of the instruction pointer onto the
                    // stack and changes the instruction pointer to "some address"
 add esp,4 // remove eax from the stack

 // a function
 push ebp // save the old stack frame
 move ebp, esp
 ... // do stuff
 pop ebp  // restore the old stack frame
 ret

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.

jmucchiello
sumber
2
Tidak, ESP tidak menunjuk ke bagian bawah tumpukan. Skema pengalamatan memori tidak ada hubungannya dengan itu. Tidak masalah apakah tumpukan tumbuh ke alamat yang lebih rendah atau lebih tinggi. "Atas" tumpukan selalu di mana nilai berikutnya akan didorong (diletakkan di atas tumpukan), atau, pada arsitektur lain, di mana nilai dorongan terakhir telah diletakkan dan di mana ia berada saat ini. Oleh karena itu, ESP selalu menunjuk ke atas tumpukan.
BarbaraKwarc
1
Bagian bawah atau pangkal tumpukan, di sisi lain, adalah tempat nilai pertama (atau terlama ) diletakkan, dan kemudian ditutupi oleh nilai yang lebih baru. Di situlah nama "basis penunjuk" untuk EBP berasal: itu seharusnya menunjuk ke basis (atau bawah) dari tumpukan lokal saat ini dari subrutin.
BarbaraKwarc
Barbara, di Intel x86, tumpukannya terbalik. Bagian atas tumpukan berisi item pertama yang didorong ke tumpukan dan setiap item setelah didorong DI BAWAH item teratas. Bagian bawah tumpukan adalah tempat item baru ditempatkan. Program ditempatkan di memori mulai dari 1 k dan tumbuh hingga tak terbatas. Tumpukan dimulai pada tak terhingga, secara realistis maks mem minus ROM, dan tumbuh menuju 0. ESP menunjuk ke alamat yang nilainya kurang dari yang didorong oleh alamat pertama.
jmucchiello
1

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.

Wim sepuluh Brink
sumber
Register segmen terpisah pada x86 - mereka gs, cs, ss, dan kecuali Anda menulis perangkat lunak manajemen memori Anda tidak pernah menyentuhnya.
Michael
ds juga merupakan register segmen dan pada masa MS-DOS dan kode 16-bit, Anda pasti perlu mengubah register segmen ini sesekali, karena mereka tidak pernah dapat menunjuk ke lebih dari 64 KB RAM. Namun DOS dapat mengakses memori hingga 1 MB karena menggunakan pointer alamat 20-bit. Kemudian kami mendapatkan sistem 32-bit, beberapa dengan register alamat 36-bit dan sekarang register 64-bit. Jadi saat ini Anda tidak perlu mengubah register segmen ini lagi.
Wim ten Brink
Tidak ada OS modern yang menggunakan 386 segmen
Ana Betts
@ Paul: SALAH! SALAH! SALAH! Segmen 16-bit digantikan oleh segmen 32-bit. Dalam mode terproteksi, ini memungkinkan virtualisasi memori, yang pada dasarnya memungkinkan prosesor memetakan alamat fisik ke alamat yang logis. Namun, di dalam aplikasi Anda, semuanya masih terasa datar, karena OS telah memvirtualisasi memori untuk Anda. Kernel beroperasi dalam mode terproteksi, memungkinkan aplikasi berjalan dalam model memori datar. Lihat juga en.wikipedia.org/wiki/Protected_mode
Wim ten Brink
@Workshop ALex: Itu masalah teknis. Semua OS modern mengatur semua segmen ke [0, FFFFFFFF]. Itu tidak masuk hitungan. Dan jika Anda akan membaca halaman tertaut, Anda akan melihat bahwa semua hal mewah dilakukan dengan halaman, yang jauh lebih bagus daripada segmen.
MSalters
-4

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.

Stephen Friederichs
sumber
Saya mengalami sedikit kesulitan memahami apa itu ebp. Jika kita memiliki 10 baris kode MASM, itu berarti bahwa ketika kita menjalankan baris-baris itu, ebp akan selalu meningkat?
melahap elysium
1
@Devoured - Tidak. Itu tidak benar. EIP akan meningkat.
Michael
Maksud Anda apa yang saya katakan itu benar tetapi tidak untuk EBP, tetapi untuk IEP, apakah itu?
melahap elysium
2
Iya. EIP adalah pointer instruksi dan dimodifikasi secara implisit setelah setiap instruksi dieksekusi.
Michael
2
Oooh, salahku. Saya sedang memikirkan pointer yang berbeda. Saya pikir saya akan pergi mencuci otak saya.
Stephen Friederichs
-8

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.

Adarsha Kharel
sumber
3
Ini adalah pertanyaan lama, namun, sp adalah singkatan dari stack pointer, bp singkatan dari pointer dasar dan ip untuk pointer instruksi. E pada awal semua orang hanya mengatakan itu pointer 32 bit.
Hyden
1
Segmentasi tidak relevan di sini.
BarbaraKwarc