Temel işaretçi ve yığın işaretçisi tam olarak nedir? Neye işaret ediyorlar?


225

DrawSquare () 'in DrawLine () öğesini çağırdığı wikipedia'dan gelen bu örneği kullanarak ,

alternatif metin

(Bu şemada altta yüksek adresler ve üstte düşük adresler bulunduğunu unutmayın.)

Kimse bana açıklayabilir neyi Could ebpve espbu bağlamda nelerdir?

Gördüğüm kadarıyla, yığın işaretçisinin her zaman yığının üstünü ve temel işaretçiyi geçerli işlevin başlangıcını gösterdiğini söyleyebilirim? Ya da ne?


edit: Bu Windows programları bağlamında demek

edit2: Ve nasıl da eipçalışır?

edit3: MSVC ++ aşağıdaki kodu var:

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

Hepsi dwords gibi görünüyor, böylece her biri 4 bayt alıyor. Böylece, 4 baytlık var_4 değerine kadar bir boşluk olduğunu görebiliyorum. Onlar neler? Wikipedia'nın resminde görülebileceği gibi, bunun dönüş adresi olduğunu varsayıyorum?


(Editörün notu: Michael'ın cevabından uzun bir alıntı kaldırıldı, bu soruya ait değil, ancak bir takip sorusu düzenlendi):

Bunun nedeni, işlev çağrısının akışının:

* Push parameters (hInstance, etc.)
* Call function, which pushes return address
* Push ebp
* Allocate space for locals

Benim sorum (son, umarım!) Şimdi, tam olarak prolog sonuna kadar çağırmak istediğiniz işlevin argümanlarını pop an ne olur? Ebp, esp'nin o anlarda nasıl geliştiğini bilmek istiyorum (Prologun nasıl çalıştığını zaten anladım, sadece yığındaki ve prologdan önce argümanları ittikten sonra ne olduğunu bilmek istiyorum).


23
Dikkat edilmesi gereken önemli bir nokta, yığının bellekte "aşağı doğru" büyümesidir. Bu, yığın işaretçisini yukarı hareket ettirmek için değerini azalttığınız anlamına gelir.
BS

4
EBP / ESP ve EIP'nin ne yaptığını ayırt etmenin bir ipucu: EBP ve ESP verilerle ilgilenirken, EIP kodla ilgilenir.
mmmmmmmm

2
Grafiğinizde ebp (genellikle) "kare işaretçisi", esp "yığın işaretçisi" dir. Bu, yerellere [ebp-x] üzerinden ve yığın parametrelerine [ebp + x] üzerinden yığın işaretçisinden bağımsız olarak (sık sık bir işlev içinde değişen) erişmeye izin verir. Adresleme ESP aracılığıyla yapılabilir, EBP diğer işlemler için serbest bırakılır - ancak bu şekilde hata ayıklayıcılar çağrı yığını veya yerel değerlerini söyleyemez.
peterchen

4
@Ben. Acayip değil. Bazı derleyiciler yığın karelerini yığın haline getirir. Yığın büyümesi kavramı tam da budur, anlaşılmasını kolaylaştıran bir kavram. Yığının uygulanması herhangi bir şey olabilir (yığının rastgele parçalarını kullanmak, yığının parçalarının üzerine yazan hainleri belirleyici olmadığı için çok daha zor hale getirir).
Martin York

1
iki kelimeyle: yığın işaretçisi push / pop işlemlerinin çalışmasına izin verir (böylece push ve pop nereye veri koyacağını / alınacağını bilir). temel işaretçi, kodun daha önce yığın üzerine itilmiş olan verilere bağımsız olarak başvurmasını sağlar.
tigrou

Yanıtlar:


229

esp dediğiniz gibi, yığının en üstünde.

ebpgenellikle espişlevin başlangıcında olarak ayarlanır . Fonksiyon parametrelerine ve yerel değişkenlere sırasıyla sabit bir ofset ekleyerek ve çıkararak erişilir ebp. Tüm x86 çağrı kuralları ebp, işlev çağrıları arasında korunuyor olarak tanımlanır . ebpkendisi aslında bir hata ayıklayıcıda yığın yürüyüş ve diğer çerçeveler yerel değişkenleri çalışmak için görüntüleme sağlayan önceki karenin temel işaretçisini işaret eder.

Çoğu işlev prologları şuna benzer:

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.

Daha sonra fonksiyonda kodunuz olabilir (her iki yerel değişkenin 4 bayt olduğunu varsayarsak)

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

Etkinleştirebileceğiniz FPO veya kare işaretçisi ihmal optimizasyonu bunu gerçekten ortadan kaldıracak ve ebpbaşka bir kayıt olarak kullanacak ve yerel halktan doğrudan erişebilecektir esp, ancak hata ayıklayıcı daha önceki işlev çağrılarının yığın çerçevelerine doğrudan erişemediğinden bu hata ayıklamayı biraz daha zorlaştırır.

DÜZENLE:

Güncellenmiş sorunuz için, yığındaki eksik iki giriş:

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

Bunun nedeni, işlev çağrısının akışının:

  • Push parametreleri ( hInstancevb.)
  • Dönüş adresini iten çağrı fonksiyonu
  • it ebp
  • Yerliler için yer ayırın

1
Açıklama için teşekkürler! Ama şimdi biraz kafam karıştı. Diyelim ki bir işlev çağırıyorum ve prologunun ilk satırındayım, yine de ondan tek bir satır yürütmeden. Bu noktada ebp'in değeri nedir? Yığın, o noktada, itilen argümanların dışında bir şey içeriyor mu? Teşekkürler!
devoured elysium

3
EBP sihirli bir şekilde değişmez, bu nedenle işleviniz için yeni bir EBP belirleyene kadar arayanların değeri yine de olur. Ve argümanların yanı sıra, yığın aynı zamanda eski EIP (dönüş adresi)
MSalters

3
Güzel cevap. Her ne kadar epilogda ne olduğunu söylemeden tamamlanamaz: "ayrıl" ve "ret" talimatları.
Calmarius

2
Bence bu görüntü akışın ne olduğu konusunda bazı şeyleri netleştirmeye yardımcı olacak. Ayrıca yığının aşağı doğru büyüdüğünü de unutmayın. ocw.cs.pub.ro/courses/_media/so/laboratoare/call_stack.png
Andrei-Niculae Petre

Benim mi, yoksa yukarıdaki kod parçacığında tüm eksi işaretleri eksik mi?
BarbaraKwarc

96

ESP, yığının üzerine bir kelime veya adres itildiğinde veya atıldığında değişecek olan geçerli yığın işaretçisidir. EBP, derleyicinin ESP'yi doğrudan kullanmaktan ziyade bir işlevin parametrelerini ve yerel değişkenlerini takip etmesinin daha kolay bir yoludur.

Genel olarak (ve bu derleyiciden derleyiciye değişebilir), çağrılan bir işleve ilişkin tüm bağımsız değişkenler çağrı işlevi tarafından yığına iletilir (genellikle işlev prototipinde bildirildikleri ters sırada, ancak bu değişir) . Daha sonra işlev çağrılır, bu da dönüş adresini (EIP) yığına iter.

Fonksiyona girişte, eski EBP değeri istif üzerine itilir ve EBP ESP değerine ayarlanır. Daha sonra ESP, işlevin yerel değişkenleri ve geçici aralıkları için alan ayırmak üzere azaltılır (yığın bellekte aşağı doğru büyüdüğü için). Bu noktadan sonra, işlevin yürütülmesi sırasında, işleve ilişkin argümanlar istif üzerinde EBP'den pozitif ofsetlerde bulunur (çünkü işlev çağrısından önce itildikleri için) ve yerel değişkenler EBP'den negatif ofsetlerde bulunur (çünkü fonksiyon girişinden sonra yığına tahsis edilmişlerdir). Bu nedenle EBP'ye çerçeve işaretçisi denir , çünkü işlev çağrısı çerçevesinin ortasını gösterir .

Çıkıştan sonra, tüm işlevin yapılması gereken ESP'yi EBP değerine ayarlamak (yerel değişkenleri yığından ayırır ve yığının üstündeki EBP girişini ortaya çıkarır), ardından eski EBP değerini yığından çıkarır, ve sonra işlev geri döner (dönüş adresini EIP'ye atmak).

Çağıran işleve geri döndükten sonra, diğer işlevi çağırmadan hemen önce yığına aktardığı işlev bağımsız değişkenlerini kaldırmak için ESP'yi artırabilir. Bu noktada, yığın, çağrılan işlevi çağırmadan önceki durumundadır.


15

Haklısın. Yığın işaretçisi yığında üst öğeyi ve temel işaretçi işlev çağrılmadan önce yığının "önceki" üstünü işaret eder .

Bir işlevi çağırdığınızda, herhangi bir yerel değişken yığın üzerinde saklanır ve yığın işaretçisi artırılır. İşlevden döndüğünüzde, yığındaki tüm yerel değişkenler kapsam dışına çıkar. Bunu, yığın işaretçisini temel işaretçiye (işlev çağrısından önceki "önceki" üst) geri ayarlayarak yaparsınız.

Bellek tahsisini bu şekilde yapmak çok , çok hızlı ve etkilidir.


14
@Robert: İşlev çağrılmadan önce yığının "önceki" ifadesini söylediğinizde, işlevi çağırmadan hemen önce yığının üzerine itilen parametreleri ve arayan EIP'yi görmezden gelirsiniz. Bu okuyucuları şaşırtabilir. Diyelim ki standart bir yığın çerçevesinde EBP, ESP'nin işleve girdikten hemen sonra işaret ettiği yeri gösteriyor .
wigy

7

DÜZENLEME: Daha iyi bir açıklama için bkz x86 Demontajı / Fonksiyonlar ve Stack Çerçeveler montaj x86 hakkında kitabu içinde. Visual Studio kullanarak ilginizi çekebilecek bazı bilgiler eklemeye çalışıyorum.

Arayan EBP'sinin ilk yerel değişken olarak depolanmasına standart yığın çerçevesi denir ve bu, Windows'daki neredeyse tüm çağrı kuralları için kullanılabilir. Arayan veya arayanın iletilen parametreleri ayırıp ayırmadığı ve kayıtlarda hangi parametrelerin geçirildiği farklar vardır, ancak bunlar standart yığın çerçevesi sorununa diktir.

Windows programlarından bahsetmişken, muhtemelen C ++ kodunuzu derlemek için Visual Studio kullanabilirsiniz. Microsoft'un, dbghlp kitaplığını ve yürütülebilir dosya için PDB dosyasını kullanmadan yığını yürümeyi neredeyse imkansız kılan Frame Pointer Omission adlı bir optimizasyon kullandığını unutmayın.

Bu Çerçeve İşaretçisi Atlama, derleyicinin eski EBP'yi standart bir yerde saklamadığı ve EBP kaydını başka bir şey için kullanmadığı anlamına gelir, bu nedenle yerel değişkenlerin belirli bir işlev için ne kadar alana ihtiyacı olduğunu bilmeden arayan EIP'yi bulmakta zorlanırsınız. Elbette Microsoft, bu durumda bile yığın yürüyüşleri yapmanızı sağlayan bir API sağlar, ancak PDB dosyalarındaki sembol tablosu veritabanına bakmak bazı kullanım durumları için çok uzun sürer.

Derleme birimlerinizde FPO'dan kaçınmak için, / O2 kullanmaktan kaçınmanız veya projelerinizdeki C ++ derleme bayraklarına açıkça / Oy- eklemeniz gerekir. Muhtemelen Release konfigürasyonunda FPO kullanan C veya C ++ çalışma zamanı ile bağlantı kuruyorsunuz, bu nedenle dbghlp.dll olmadan yığın yürüyüşleri yapmakta zorlanacaksınız.


EIP'nin yığın üzerinde nasıl saklandığını anlamıyorum. Kayıt olmamalı mı? Bir kayıt yığının üzerinde nasıl olabilir? Teşekkürler!
devysed elysium

Arayan EIP, CALL komutunun kendisi tarafından yığının üzerine itilir. RET komutu yığının üstünü alır ve EIP'ye koyar. Arabellek taşması varsa, bu gerçek ayrıcalıklı bir iş parçacığından kullanıcı koduna atlamak için kullanılabilir.
wigy

@devouredelysium EIP kaydının içeriği (veya değeri ), kaydın kendisini değil yığının üzerine koyulur (veya üzerine kopyalanır).
BarbaraKwarc

İçin @BarbaraKwarc teşekkürler değeri sıfat daha girdi. Cevabımda OP'nin neyi kaçırdığını göremedim. Gerçekten de, kayıtlar oldukları yerde kalır, sadece değerleri CPU'dan RAM'e gönderilir. AMD64 modunda, bu biraz daha karmaşık hale gelir, ancak bunu başka bir soruya bırakın.
peruk

Bu amd64 ne olacak? Merak ediyorum.
BarbaraKwarc

6

Her şeyden önce, x86 yığınları yüksek adres değerlerinden düşük adres değerlerine kadar oluşturulduğundan, yığın işaretçisi yığının altını gösterir. Yığın işaretçisi sonraki itme (veya arama) aramasının bir sonraki değeri yerleştireceği noktadır. İşlemi C / C ++ ifadesine eşdeğerdir:

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

Temel işaretçi geçerli çerçevenin üstündedir. ebp genellikle dönüş adresinizi gösterir. ebp + 4 işlevinizin ilk parametresini (veya bir sınıf yönteminin bu değerini) gösterir. ebp-4, işlevin ilk yerel değişkenini, genellikle ebp'in eski değerini gösterir, böylece önceki kare işaretçisini geri yükleyebilirsiniz.


2
Hayır, ESP yığının altına işaret etmez . Bellek adresleme şemasının bununla hiçbir ilgisi yoktur. Yığının daha düşük veya daha yüksek adreslere ulaşması önemli değildir. Yığının "üstü" her zaman bir sonraki değerin nereye itileceği (yığının üstüne konacak) ya da, son itilen değerin nereye konulduğu ve o anda nereye koyacağı diğer mimarilerde bulunur. Bu nedenle, ESP her zaman yığının üstünü işaret eder.
BarbaraKwarc

1
Alt ya da taban burada istifin diğer yandan, bir ilk (ya da en eski ) değeri sürülmemiş ve daha sonra son değerleri ile kaplamış. EBP için "temel işaretçi" adının geldiği yer: bir alt rutinin mevcut yerel yığının tabanını (veya altını) göstermesi gerekiyordu.
BarbaraKwarc

Barbara, Intel x86'da yığın UPSIDE DOWN. Yığının üst kısmı, yığının üzerine itilen ilk öğeyi içerir ve daha sonra her bir öğe üst öğenin AŞAĞINA itilir. Yığının altı, yeni öğelerin yerleştirildiği yerdir. Programlar 1k'dan başlayarak belleğe yerleştirilir ve sonsuza kadar büyür. Yığın sonsuzda başlar, gerçekçi olarak maksimum eksi ROM'lar ve 0'a doğru büyür. ESP, değeri itilen ilk adresten küçük olan bir adrese işaret eder.
jmucchiello

1

Montaj programlamasını yaptığımdan bu yana uzun zaman geçti, ancak bu bağlantı faydalı olabilir ...

İşlemcinin veri depolamak için kullanılan bir kayıt koleksiyonu vardır. Bunların bazıları doğrudan değerlerdir, diğerleri ise RAM içindeki bir alanı gösterir. Kayıtlar belirli eylemler için kullanılma eğilimindedir ve montajdaki her işlenen belirli kayıtlarda belirli miktarda veri gerektirir.

Yığın işaretçisi çoğunlukla diğer yordamları çağırırken kullanılır. Modern derleyicilerle, ilk önce yığına bir grup veri dökülür, ardından dönüş adresi gelir, böylece sistem geri dönmesi söylendiğinde nereye geri döneceğini bilir. Yığın işaretçisi, yığına yeni verilerin gönderilebileceği bir sonraki yeri gösterecek ve burada tekrar geri açılana kadar kalacaktır.

Baz kayıtları veya segment kayıtları, büyük miktarda verinin adres alanını gösterir. İkinci bir kayıt cihazı ile birleştirildiğinde, Temel işaretçi belleği büyük bloklara bölerken ikinci kayıt bu blok içindeki bir öğeye işaret eder. Bunun için taban işaretçileri veri bloklarının tabanını gösterir.

Assembly'nin CPU'ya özgü olduğunu unutmayın. Bağlantı verdiğim sayfa farklı CPU tipleri hakkında bilgi veriyor.


Segment kayıtları x86'da ayrıdır - gs, cs, ss'dir ve bellek yönetimi yazılımı yazmazsanız onlara asla dokunmazsınız.
Michael

ds aynı zamanda bir segment kaydıdır ve MS-DOS ve 16 bitlik kod günlerinde, kesinlikle 64 KB RAM'i gösteremedikleri için bu segment kayıtlarını zaman zaman değiştirmeniz gerekir. Yine de DOS, 20 bit adres işaretçileri kullandığı için belleğe 1 MB'a kadar erişebilir. Daha sonra 32-bit sistemlerimiz var, bazıları 36-bit adres kayıtları ve şimdi 64-bit kayıtlar. Yani artık bu segment kayıtlarını değiştirmenize gerek kalmayacak.
On Brink'i

Hiçbir modern işletim sistemi 386 segment kullanmıyor
Ana Betts

@ Paul: YANLIŞ! YANLIŞ! YANLIŞ! 16 bitlik segmentlerin yerini 32 bitlik segmentler alır. Korumalı modda bu, belleğin sanallaştırılmasına izin verir ve temel olarak işlemcinin fiziksel adresleri mantıksal adreslerle eşlemesine izin verir. Ancak, uygulamanızın içinde, işletim sistemi belleği sizin için sanallaştırdığı için işler hala düz görünüyor. Çekirdek, korumalı modda çalışır ve uygulamaların düz bellek modelinde çalışmasına izin verir. Ayrıca bkz. En.wikipedia.org/wiki/Protected_mode
Wim ten Brink

@ Workshop ALex: Bu bir teknik özellik. Tüm modern işletim sistemleri tüm segmentleri [0, FFFFFFFF] olarak ayarlar . Bu gerçekten önemli değil. Bağlantılı sayfayı okursanız, tüm süslü şeylerin segmentlerden çok daha ayrıntılı olan sayfalarla yapıldığını göreceksiniz.
MSalters

-4

Düzenle Evet, bu çoğunlukla yanlış. Herkesin ilgilenmesi durumunda tamamen farklı bir şey açıklar :)

Evet, yığın işaretçisi yığının üstünü işaret eder (ister ilk boş yığın konumu ister emin olmadığım son dolu konum). Temel işaretçi, yürütülmekte olan komutun bellek konumunu işaret eder. Bu opcode seviyesindedir - bir bilgisayarda alabileceğiniz en temel talimat. Her opcode ve parametreleri bir bellek konumunda saklanır. Bir C veya C ++ veya C # satırı, ne kadar karmaşık olduğuna bağlı olarak bir opcode'a veya iki veya daha fazla diziye çevrilebilir. Bunlar sırayla program belleğine yazılır ve yürütülür. Normal şartlar altında temel işaretçi bir komut arttırılır. Program kontrolü için (GOTO, IF, vb.) Birden çok kez artırılabilir veya sadece bir sonraki bellek adresiyle değiştirilebilir.

Bu bağlamda, fonksiyonlar belirli bir adresteki program belleğinde saklanır. İşlev çağrıldığında, programın işlevin çağrıldığı yere geri döndüğünü bulmasını sağlayan yığına belirli bilgiler itilir ve işleve ait parametreler daha sonra program belleğindeki işlevin adresi temel işaretçi. Bir sonraki saat döngüsünde bilgisayar bu bellek adresinden talimatlar vermeye başlar. Sonra bir noktada işlevi çağıran komuttan sonra hafıza konumuna dönecek ve oradan devam edecektir.


Ebp'in ne olduğunu anlamakta biraz zorlanıyorum. 10 satır MASM kodumuz varsa, bu satırları çalıştırdığımızda ebp her zaman artacaktır?
yutulmuş elysium

1
@Devoured - Hayır. Bu doğru değil. eip artacak.
Michael

Dediğim şey doğru ama EBP için değil, IEP için değil mi?
devoured elysium

2
Evet. EIP, talimat göstergesidir ve her komut yürütüldükten sonra örtük olarak değiştirilir.
Michael

2
Oooh benim hatam. Farklı bir işaretçi düşünüyorum. Sanırım gidip beynimi yıkarım.
Stephen Friederichs

-8

esp "Genişletilmiş Stack Pointer" anlamına gelir ..... "Something Base Pointer" için ebp .... ve "Something Talimat Pointer" için eip ...... Stack Pointer, yığın segmentinin ofset adresini işaret eder . Temel İşaretçi, ekstra segmentin ofset adresini işaret eder. Yönerge İşaretçisi, kod segmentinin ofset adresini gösterir. Şimdi, segmentler hakkında ... işlemcilerin bellek alanının küçük 64KB bölümler ..... Bu işlem Bellek Segmentasyon olarak bilinir. Umarım bu yazı yardımcı olmuştur.


3
Bu eski bir sorudur, ancak sp yığın işaretçisini, bp taban işaretçisini ve ip ise yönerge işaretçisini temsil eder. Herkesin başındaki e sadece 32 bit işaretçi olduğunu söylüyor.
Hyden

1
Burada bölümleme önemsizdir.
BarbaraKwarc
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.