Bölüm 8.1: Yığın
Giriş
Öğrenme Çıktıları
8086 yığınının SS:SP ile nasıl adreslendiğini ve aşağı doğru büyüdüğünü açıklamak.
Geçerli işlenen türlerini bilmek ve her komutta SP'nin nasıl değiştiğini adım adım izlemek.
Yığını yazmaç değerlerini korumak, geri yüklemek ve takas etmek için doğru sırayla kullanmak.
Bayrak yazmacını yığında saklamak ve bir işlemin bozduğu bayrakları geri yüklemek.
Dengesiz PUSH/POP, taşma ve yanlış sıralama hatalarını tanımak ve önlemek.
Programları tek adım çalıştırırken yazmaç ve yığın pencerelerini yorumlamak.
Yığın Kavramı
Yığın (Stack)
- Yığın, geçici verileri saklamak için ayrılmış bir bellek alanıdır.
CALLkomutu prosedürün dönüş adresini yığına koyar;RETbu değeri yığından alır ve o adrese döner.INTbir kesmeyi çağırdığında benzer bir işlem olur: bayrak (durum) yazmacı, kod kesimi (CS) ve bağıl konum (IP) yığına saklanır;IRETile kesmeden dönülür.PUSH: 16 bitlik bir değeri yığına koyar.POP: 16 bitlik bir değeri yığından alır.
PUSH AL geçersizdir.Yığın Kavramı
LIFO Algoritması
- LIFO, Last In First Out (son giren ilk çıkar) ifadesinin kısaltmasıdır.
- Yığına eklenen son öğe, yığından çıkan ilk öğe olur.
- Yığına konan değerler sırasıyla 1, 2, 3, 4, 5 ise ilk alınan değer 5 olur; ardından 4, 3, 2 ve en son 1 alınır.
Başlangıçta yığın boş.
PUSH 1: 1 yığının üstüne konur.
PUSH 2: 2 yığının üstüne konur.
PUSH 3: 3 yığının üstüne konur.
PUSH 4: 4 yığının üstüne konur.
PUSH 5: 5 yığının üstüne konur.
POP → 5 çıkar. Çıkış sırası şimdiye kadar: 5
POP → 4 çıkar. Çıkış sırası şimdiye kadar: 5, 4
POP → 3 çıkar. Çıkış sırası şimdiye kadar: 5, 4, 3
POP → 2 çıkar. Çıkış sırası şimdiye kadar: 5, 4, 3, 2
POP → 1 çıkar. Çıkış sırası şimdiye kadar: 5, 4, 3, 2, 1
Yığın Kavramı
Yığın Belleği: SS ve SP
- Yığın belleği SS (Stack Segment, yığın kesimi) ve SP (Stack Pointer, yığın işaretçisi) yazmaçlarını kullanır.
- SS:SP her zaman yığının üstündeki (en son konan) sözcüğü gösterir.
- Başlangıç değerlerini genellikle işletim sistemi (yükleyici) belirler: .COM'da SS = CS, SP = FFFEh; .EXE'de başlıktaki değerler.
- BP de varsayılan olarak SS kesimini kullanır; yığındaki verilere erişmek için idealdir.
Yığın kesimi SS = 0700h (emu8086'da .COM programı için tüm kesim yazmaçları 0700h).
Kesim değeri 16 ile çarpılır (onaltılıkta sağa bir 0 eklenir): 07000h.
Bağıl konum olarak yığın işaretçisi SP = FFFEh eklenir.
Yığının üstü fiziksel olarak 16FFEh adresindedir (20 bit).
Yığın Kavramı
Yığın Aşağı Doğru Büyür
- .COM programında kod, veri ve yığın aynı 64 KB kesimi paylaşır.
- Program 0100h'den yukarı doğru, yığın FFFEh'den aşağı doğru yerleşir; aradaki boş alan ikisine de yeter.
- Her PUSH SP'yi 2 azaltır, her POP 2 artırır.
- Çok fazla PUSH yapılırsa yığın kodun/verinin üzerine yazabilir: yığın taşması (stack overflow).
PUSH ve POP
PUSH Komutu
| Biçim | Geçerli işlenenler | Örnek |
|---|---|---|
PUSH REG | AX, BX, CX, DX, DI, SI, BP, SP | PUSH AX |
PUSH SREG | DS, ES, SS, CS | PUSH DS |
PUSH memory | [BX], [BX+SI+7], 16 bitlik değişken… | PUSH [BX], PUSH sayi |
PUSH immediate | 5, −24, 3Fh, 10001101b… | PUSH 3Fh |
PUSH immediate komutu 8086/8088'de yoktur; 80186 ile gelmiştir. emu8086 bunu kabul eder ama gerçek 8086 için MOV AX, 5 + PUSH AX yazılmalıdır.PUSH AL, PUSH byte_degisken hatalıdır. Bayraklar için ayrı komut vardır: PUSHF.Ayrıntı: 8086'da PUSH SP, SP azaltıldıktan sonraki değeri yığına yazar; 80286 ve sonrası azaltmadan önceki değeri yazar.
PUSH ve POP
POP Komutu
| Biçim | Geçerli işlenenler | Örnek |
|---|---|---|
POP REG | AX, BX, CX, DX, DI, SI, BP, SP | POP BX |
POP SREG | DS, ES, SS (CS hariç) | POP DS |
POP memory | [BX], [BX+SI+7], 16 bitlik değişken… | POP sonuc |
RETF, IRET, uzak JMP/CALL ile birlikte değişir.POP 5 anlamsızdır: alınan değer bir yere yazılmalıdır.POPF bayrak yazmacının tamamını yığından yükler.Yaygın deyim: PUSH CS + POP DS → DS'ye CS'nin değeri kopyalanır (kesim yazmaçları arasında doğrudan MOV yoktur).
PUSH ve POP
PUSH ve POP Nasıl Çalışır?
- SP yazmacından 2 çıkarılır.
- Kaynak değeri SS:SP adresine yazılır.
- SS:SP adresindeki değer hedefe yazılır.
- SP yazmacına 2 eklenir.
Sıra önemlidir: PUSH önce azaltır sonra yazar; POP önce okur sonra artırır. Böylece SP her an dolu olan en üst hücreyi gösterir.
Başlangıç: SP = FFFEh, AX = 1234h. (.COM'da FFFEh'de DOS'un koyduğu 0000h vardır.)
PUSH AX ① SP ← SP − 2 = FFFCh. Yeni üst hücre henüz yazılmadı.
② Kaynak değer SS:SP adresine yazılır: [FFFCh] ← 1234h.
POP BX ① SS:SP'deki değer hedefe yazılır: BX ← 1234h.
② SP ← SP + 2 = FFFEh. FFFCh'deki 1234h silinmez; yalnızca artık yığının dışında kalır (soluk).
PUSH ve POP
Yığında Bayt Sırası (Little-Endian)
- 8086 bir sözcüğü belleğe düşük bayt önce yazar:
PUSH AX(AX = 1234h) sonrası [FFFCh] = 34h, [FFFDh] = 12h. - emu8086'nın stack penceresi sözcükleri gösterir; memory penceresi baytları gösterir. İkisi aynı veriyi farklı biçimde sunar.
- SP her zaman çift bir değer olarak tutulmalıdır; tek adreste sözcük erişimi 8086'da ek bir bellek döngüsü harcar.
PUSH ve POP
Ne Değişir, Ne Değişmez?
| Komut | SP | Bellek | Hedef | Bayraklar |
|---|---|---|---|---|
PUSH kaynak | −2 | [SS:SP] ← kaynak | — | değişmez |
POP hedef | +2 | okunur, silinmez | hedef ← [SS:SP] | değişmez |
PUSHF | −2 | [SS:SP] ← FLAGS | — | değişmez |
POPF | +2 | okunur | FLAGS ← [SS:SP] | hepsi yüklenir |
CALL (yakın) | −2 | [SS:SP] ← dönüş IP | IP ← hedef | değişmez |
RET (yakın) | +2 | okunur | IP ← [SS:SP] | değişmez |
INT n | −6 | FLAGS, CS, IP | CS:IP ← vektör | IF = TF = 0 |
IRET | +6 | okunur | IP, CS, FLAGS | hepsi yüklenir |
Bu yüzden bir karşılaştırmanın sonucu (bayraklar) PUSH/POP arasında güvenle taşınabilir; bayrağı saklamak gerekiyorsa PUSHF/POPF kullanılır.
Örnekler · Kodu adım adım çalıştır
İlk Örnek: PUSH AX / POP BX
emu8086 .COM başlangıcı: CS = DS = SS = ES = 0700h, IP = 0100h, SP = FFFEh.
MOV AX, 42: 42 onluk sayıdır → AX = 002Ah (42h değil!).
PUSH AX: SP = FFFEh − 2 = FFFCh; [FFFCh] ← 002Ah.
POP BX: BX ← [FFFCh] = 002Ah; SP tekrar FFFEh. Değer AX'ten BX'e yığın üzerinden kopyalandı.
RET: yığındaki 0000h alınır, IP = 0000h → PSP'deki INT 20h programı sonlandırır.
ORG 100h MOV AX, 42 ; AX'e 42 ata PUSH AX ; AX'i yığına koy POP BX ; yığından BX'e al RET ; işletim sistemine dön END
Örnekler · Kodu adım adım çalıştır
LIFO Sırası: 1, 2, 3 Girer → 3, 2, 1 Çıkar
Başlangıç: SP = FFFEh.
AX = 0001h.
PUSH: SP = FFFCh, [FFFCh] = 0001h.
AX = 0002h (AX'in eski değeri yığında güvende).
PUSH: SP = FFFAh, [FFFAh] = 0002h.
AX = 0003h.
PUSH: SP = FFF8h; yığında üç değer var.
POP BX → 3 (son giren ilk çıkar). SP = FFFAh.
POP BX → 2. SP = FFFCh.
POP BX → 1. SP = FFFEh; yığın başlangıç hâline döndü.
MOV AX, 1 ; AX'e 1 ata PUSH AX ; 1'i yığına koy MOV AX, 2 ; AX'e 2 ata PUSH AX ; 2'yi yığına koy MOV AX, 3 ; AX'e 3 ata PUSH AX ; 3'ü yığına koy POP BX ; BX ← 3 POP BX ; BX ← 2 POP BX ; BX ← 1
Örnekler · Kodu adım adım çalıştır
Bir Yazmacın Değerini Saklamak
Amaç: AX'i geçici olarak başka bir iş için kullanıp sonra eski değerine döndürmek.
AX = 1234h.
AX yığına saklandı: [FFFCh] = 1234h.
AX başka bir iş için kullanıldı: AX = 5678h.
POP AX: AX eski değerine döndü → 1234h.
RET: 0000h'ye dönüş, program biter.
ORG 100h MOV AX, 1234h PUSH AX ; AX'in değerini sakla MOV AX, 5678h ; AX'i değiştir POP AX ; eski değeri geri yükle RET END
Prosedürlerin girişinde kullandıkları yazmaçları PUSH, çıkışta POP etmesi bu desenin aynısıdır.
Örnekler · Kodu adım adım çalıştır
Yığın ile İki Yazmacı Takas Etmek
emu8086'da yükleme sonrası: CX = 000Bh (programın bayt sayısı: 3+3+1+1+1+1+1 = 11), SP = FFFEh.
AX = 1212h.
BX = 3434h.
PUSH AX: [FFFCh] = 1212h, SP = FFFCh.
PUSH BX: [FFFAh] = 3434h, SP = FFFAh.
POP AX: üstteki 3434h AX'e gelir.
POP BX: sıradaki 1212h BX'e gelir. Takas tamam!
RET: program sonu.
ORG 100h MOV AX, 1212h ; AX ← 1212h MOV BX, 3434h ; BX ← 3434h PUSH AX ; AX'i yığına koy PUSH BX ; BX'i yığına koy POP AX ; AX ← BX'in eski değeri POP BX ; BX ← AX'in eski değeri RET END
Aynı sırayla POP edilseydi (önce BX) değerler yerinde kalırdı. Takasın sırrı LIFO'dur.
Örnekler · Kodu adım adım çalıştır
Yığın Kullanımı: Üç Yazmaç
.BIN olarak yüklendi: CS = SS = 0100h, IP = 0000h, SP = FFFEh. Yığın başlangıçta boş.
AX = 0002h.
BX = 0004h.
CX = 0008h.
push ax: SP = FFFCh, [FFFCh] = 0002h.
push bx: SP = FFFAh, [FFFAh] = 0004h.
push cx: SP = FFF8h, [FFF8h] = 0008h.
pop ax: AX ← 0008h (üstteki).
pop bx: BX ← 0004h (ortadaki; değişmedi).
pop cx: CX ← 0002h. Sonuç: AX ile CX'in değerleri yer değiştirdi.
mov ax, 2 mov bx, 4 mov cx, 8 push ax push bx push cx pop ax pop bx pop cx
Kaynak: emu8086 ile tek adım çalıştırma (single step). Yığın penceresi SS:SP çevresini gösterir.
Örnekler · Kodu adım adım çalıştır
Yığınla Diziyi Ters Çevirme
metin = 'ABC' bellekte 0117h adresinde. Hedef: ekrana ters sırada yazmak.
SI = 0117h (metin dizisinin adresi).
Döngü sayacı CX = 3.
AL ← [SI] = 'A' (41h).
PUSH AX: 'A' yığına (AH = 00h ile birlikte, 0041h).
SI = 0118h: sonraki karakter.
LOOP: CX = 2 → geri dön.
AL ← [SI] = 'B' (42h).
PUSH AX: 'B' yığına (AH = 00h ile birlikte, 0042h).
SI = 0119h: sonraki karakter.
LOOP: CX = 1 → geri dön.
AL ← [SI] = 'C' (43h).
PUSH AX: 'C' yığına (AH = 00h ile birlikte, 0043h).
SI = 011Ah: sonraki karakter.
LOOP: CX = 0 → döngü biter.
İkinci döngü için CX = 3.
POP DX: DX = 0043h → DL = 'C' (son giren ilk çıkar).
AH = 02h: DOS karakter yazma işlevi.
INT 21h: ekrana C yazılır; AL = DL = 43h olur.
LOOP: CX = 2 → geri dön.
POP DX: DX = 0042h → DL = 'B' (son giren ilk çıkar).
AH = 02h: DOS karakter yazma işlevi.
INT 21h: ekrana B yazılır; AL = DL = 42h olur.
LOOP: CX = 1 → geri dön.
POP DX: DX = 0041h → DL = 'A' (son giren ilk çıkar).
AH = 02h: DOS karakter yazma işlevi.
INT 21h: ekrana A yazılır; AL = DL = 41h olur.
LOOP: CX = 0 → döngü biter.
Ekranda CBA: dizi yığın sayesinde ters çevrildi. RET ile program biter.
ORG 100h MOV SI, OFFSET metin MOV CX, 3 koy: MOV AL, [SI] PUSH AX ; karakteri sakla INC SI LOOP koy MOV CX, 3 yaz: POP DX ; DL = karakter MOV AH, 2 INT 21h ; DL'yi ekrana yaz LOOP yaz RET metin DB 'ABC' ; 0117h
Örnekler
Yazmaçlar Ne Olur?
Kod çalıştıktan sonra AX, BX, CX ve SP ne olur? (Başlangıç: SP = FFFEh)
MOV AX, 1111h MOV BX, 2222h MOV CX, 3333h PUSH AX PUSH BX PUSH CX POP AX POP CX PUSH AX POP BX POP BX
PUSH'lardan sonra yığın (üstten alta): 3333h, 2222h, 1111h; SP = FFF8h.
POP AX → AX = 3333h; POP CX → CX = 2222h; SP = FFFCh.
PUSH AX (3333h), POP BX → BX = 3333h; ardından POP BX → BX = 1111h.
Sonuç: AX = 3333h, BX = 1111h, CX = 2222h, SP = FFFEh (4 PUSH, 4 POP → dengeli).
Örnekler
Takas Yöntemlerinin Karşılaştırması
PUSH AX PUSH BX POP AX POP BX4 bayt · 4 bellek erişimi. Her yazmaç çifti ve kesim yazmaçları için çalışır.
MOV CX, AX MOV AX, BX MOV BX, CX6 bayt · bellek erişimi yok, ama CX'in değeri bozulur.
XCHG AX, BX1 bayt (93h) · en kısa ve en hızlı yol. Kesim yazmaçlarıyla kullanılamaz.
PUSH DS / POP ES).PUSH ve POP · Kodu adım adım çalıştır
Bayrakları Saklamak: PUSHF / POPF
Başlangıç: bayraklar temiz (IF = 1).
AX = FFFFh.
FFFFh + 1 = 1 0000h → AX = 0000h, CF = 1, ZF = 1.
PUSHF: FLAGS sözcüğü (F257h: CF, PF, AF, ZF, IF ve 8086'nın hep 1 olan bitleri) yığına.
BX = 5.
5 + 3 = 8 → CF = 0, ZF = 0. Önceki sonuç bayrakları kayboldu…
…POPF ile geri geldi: CF = 1, ZF = 1.
Program biter.
ORG 100h MOV AX, 0FFFFh ADD AX, 1 ; CF=1, ZF=1 PUSHF ; bayrakları sakla MOV BX, 5 ADD BX, 3 ; CF=0, ZF=0 POPF ; bayraklar geri RET
Kesme yordamları bayrakları kendileri saklamaz; INT bunu otomatik yapar. Normal kodda bir sonucu korumak için PUSHF/POPF gerekir.
Yığın Güvenliği
Yığın Güvenliği
- PUSH ve POP komutları programın çalışması süresince geçici verileri saklar.
- Yığının bütünlüğü eşit sayıda PUSH ve POP ile korunmalıdır (her yolda, her dalda!).
- Eşit sayıda PUSH ve POP yapılmazsa yığın bozulur; işletim sistemine geri dönüş yapılamayabilir.
RETyığının üstünde bir dönüş adresi bekler. .COM programında DOS, yığına başlangıçta 0000h koyar: RET ile CS:0000h'ye (PSP) gidilir; orada duranINT 20hprogramı sonlandırır.
Yığın Güvenliği · Kodu adım adım çalıştır
Hata Örneği: Dengesiz PUSH
Program sonunda RET ile DOS'a dönülmesi bekleniyor.
AX = 1234h.
AX yığına kondu ama hiç POP edilmeyecek.
RET üstteki sözcüğü dönüş adresi sanar: IP = 1234h. CPU 0700:1234h'deki rastgele baytları komut olarak çalıştırmaya başlar → program çöker ya da donar.
ORG 100h MOV AX, 1234h PUSH AX ; POP unutuldu! RET ; IP ← ?
Düzeltme: RET'ten önce POP AX eklenir ya da hiç PUSH yapılmaz. Her dalın (JMP/Jcc) PUSH/POP sayısı ayrı ayrı sayılır.
Yığın Güvenliği
Yığın Taşması ve Alt Taşma
- Sonsuz özyineleme, döngü içinde POP'suz PUSH.
- SP küçüldükçe yığın aşağı inip kod ve verinin üzerine yazar (.COM'da aynı kesim).
- 8086 bunu denetlemez; hata yoktur, yalnızca bozulma vardır.
- PUSH edilmemiş değeri POP etmek.
- SP = FFFEh iken POP → SP = 0000h (16 bitte sarar); sonraki PUSH FFFEh'ye yazar.
- Okunan değer çöptür (ya da DOS'un 0000h'si).
.STACK 100h gibi bir direktifle ayrı ve yeterli bir yığın kesimi ayrılır.Yığın Güvenliği
İyi Uygulamalar
MOV BP, SP kullanılır.Yığın Güvenliği
emu8086 ile Yığını İzlemek
B8 12 12 = MOV AX, 1212h. Little-endian burada görülür.Laboratuvar · 8086 komutları adım adım
Laboratuvar: Yığın
Özet
Özet
| Kavram | Hatırlanacak nokta |
|---|---|
| Adres | Yığının üstü = SS:SP; fiziksel adres SS × 16 + SP. |
| Büyüme yönü | Aşağı: PUSH SP −= 2, POP SP += 2. Birim her zaman sözcük. |
| PUSH / POP | REG, SREG, bellek (POP CS yok). PUSH sabit yalnızca 80186+ / emu8086. |
| LIFO | Değerler ters sırada döner → saklarken ayna sırası, takasta aynı sıra. |
| PUSHF / POPF | Bayrak yazmacını saklar ve geri yükler; PUSH ve POP bayraklara dokunmaz. |
| Güvenlik | Eşit sayıda PUSH/POP; .COM'da son RET DOS'un 0000h'sini alır. |
Özet
Alıştırmalar
- SS = 2000h, SP = 0100h iken
PUSH AXsonrası yazılan baytların fiziksel adresleri nedir? MOV AX,1 / MOV BX,2 / MOV CX,3 / PUSH AX / PUSH BX / PUSH CX / POP BX / POP CX / POP AXsonunda AX, BX, CX?- Yalnızca PUSH/POP ile DS'nin değerini ES'ye kopyalayın.
- SP = FFFEh iken
PUSH AX / PUSH BX / PUSHF / POP CXyürütülüyor. SP kaç olur, CX'e ne gelir? - AX'i ve bayrakları bir işlem boyunca koruyan PUSH/POP dizisini yazın.
Cevaplar
- SP = 00FEh → 20000h + 00FEh = 200FEh (AL) ve 200FFh (AH).
- AX = 1, BX = 3, CX = 2: AX yerinde kalır, BX ile CX takas edilir.
PUSH DS+POP ES.- SP = FFFAh; CX = PUSHF anındaki bayrak yazmacı. Yığında AX ve BX'in kopyaları kalır.
PUSH AX,PUSHF…POPF,POP AX(ayna sırası).
Özet
Kaynaklar
- Intel, The 8086 Family User's Manual, 1979 — PUSH, POP, CALL, RET, INT, IRET komut tanımları.
- B. B. Brey, The Intel Microprocessors, 8. baskı, Pearson, 2009 — Bölüm 4 ve 6.
- K. R. Irvine, Assembly Language for x86 Processors, 7. baskı, Pearson, 2014 — yığın işlemleri ve prosedürler.
- emu8086 yardım belgeleri: 8086 Assembler Tutorial, Part 9 (The Stack).