Bölüm 7: Prosedürler
Giriş
Öğrenme Çıktıları
PROC / ENDP ile prosedür tanımlamak; direktif ile işlemci komutunu ayırt etmek.
Dönüş adresinin yığına nasıl itildiğini ve RET ile nasıl geri alındığını SP ve IP değerleriyle izlemek.
Yazmaçlar ve bellek değişkenleriyle prosedüre veri gönderip sonuç almak.
Yazmaçları PUSH/POP ile korumak, yığını dengeli tutmak ve tipik hatalardan kaçınmak.
Giriş
Prosedür Nedir?
- Prosedür, belirli bir görevi gerçekleştiren kod parçasıdır (yüksek düzey dillerdeki fonksiyon / alt program).
- Programın içinden
CALLile çağrılır. - Görevi tamamlayınca
RETile çağrıldığı noktanın hemen arkasına geri döner. - Aynı kod tek kez yazılır, birçok yerden çağrılır.
- Programı daha yapılandırılmış (structured) ve anlaşılır hâle getirir.
Tanımlama
Prosedür Tanımlama
ad PROC ; başlangıç (varsayılan NEAR) ; prosedür kodu buraya yazılır RET ad ENDP ; bitiş
ad: prosedürün adıdır; başta ve sonda aynı olmalıdır.- Ad, etiket kurallarına uyar (rakamla başlayamaz).
- Prosedür içinde en az bir
RETbulunmalıdır.
Prosedür nereye yazılır?
ORG 100h
CALL p1
RET ; ana program biter
p1 PROC ; RET'ten sonra:
; ... ; sırayla yürütülmez
RET
p1 ENDP
ENDProsedürler ana programın RET'inden sonra konursa yanlışlıkla "düşerek" yürütülmez. Önce yazılacaksa üzerinden JMP ile atlanmalıdır.
Tanımlama
Direktifler ve Komutlar
| Öğe | Türü | Görevi |
|---|---|---|
| PROC / ENDP | Assembler direktifi | Prosedürün başlangıç adresini ve türünü (NEAR/FAR) assembler'a bildirir. Makine koduna çevrilmez, bellekte yer kaplamaz. |
| CALL | İşlemci komutu | Dönüş adresini yığına iter, prosedürün adresine atlar. |
| RET | İşlemci komutu | Dönüş adresini yığından alır (POP) ve oraya atlar. Hem prosedürden hem .COM programından dönmek için kullanılır. |
| END | Assembler direktifi | Kaynak dosyanın sonunu bildirir. |
INT 20h bulunur ve program sonlanır.CALL ve RET
Yığın Hatırlatma
- Yığın, SS:SP ile gösterilen ve LIFO çalışan bir bellek bölgesidir; aşağı doğru (düşük adreslere) büyür.
PUSH kaynak: SP ← SP − 2, sonra 16 bitlik değer [SS:SP]'ye yazılır.POP hedef: [SS:SP]'deki değer okunur, sonra SP ← SP + 2.- 8086'da yığına her zaman word (2 bayt) itilir.
- .COM programı başladığında SP = FFFEh ve yığının tepesinde DOS'un ittiği 0000h vardır.
- CALL ve RET, dönüş adresini saklamak için bu yığını kullanır.
CALL ve RET
CALL ve RET Adım Adım
Başlangıç: IP = 0100h, SP = FFFEh. Sıradaki komut CALL m1 (3 bayt: E8 04 00).
CALL, önce dönüş adresini (sonraki komut: 0103h) yığına iter: SP = FFFCh, [FFFCh] = 0103h.
Sonra IP ← 0103h + 0004h = 0107h: prosedürün ilk komutu.
MOV BX, 5 yürütüldü, IP = 010Ah. Yığın değişmedi.
RET: IP ← [SP] = 0103h, SP = FFFEh. Akış, CALL'ın hemen arkasından sürer.
MOV AX, 2 → IP = 0106h. Buradaki RET 0000h'yı alır ve programı sonlandırır.
CALL ve RET
CALL / RET Türleri
| Komut | Kod | Bayt | Yığına / yığından | Açıklama |
|---|---|---|---|---|
| CALL near | E8 rel16 | 3 | IP | Aynı kesimde doğrudan çağrı (varsayılan). |
| CALL reg/mem | FF /2 | 2–4 | IP | Dolaylı yakın çağrı: CALL BX, CALL [tablo+SI]. |
| CALL far | 9A ofs seg | 5 | CS, sonra IP | Başka kesimdeki prosedür; CS:IP birlikte değişir. |
| RET | C3 | 1 | IP | Yakın dönüş. |
| RETF | CB | 1 | IP, sonra CS | Uzak dönüş (FAR prosedür). |
| RET n | C2 n16 | 3 | IP, SP += n | Dönüşte yığındaki n baytlık parametreyi de atar. |
ad PROC NEAR (varsayılan) ya da ad PROC FAR yazıldığında assembler, prosedür içindeki RET'i uygun biçimde (C3 / CB) kodlar. Bu bölümdeki örneklerin hepsi NEAR'dır.CALL ve RET · Kodu adım adım çalıştır
Kod İzleme: Basit Prosedür
Program 0100h'dan başlar. ORG, PROC, ENDP, END hiç bayt üretmez.
CALL m1: 0103h yığına itilir, IP = 0107h.
BX = 0005h.
RET: 0103h yığından alınır → IP = 0103h, SP = FFFEh.
AX = 0002h.
RET: 0000h alınır → IP = 0000h, SP = FFFEh + 2 = 0000h. PSP'deki INT 20h programı bitirir. Sonuç: AX = 2, BX = 5.
ORG 100h CALL m1 ; 0100h: E8 04 00 MOV AX, 2 ; 0103h: B8 02 00 RET ; 0106h: C3 → DOS m1 PROC MOV BX, 5 ; 0107h: BB 05 00 RET ; 010Ah: C3 → çağırana m1 ENDP END
CALL ve RET
Bellekteki Makine Kodu
- Örnek program bellekte yalnızca 11 bayt tutar:
E8 04 00 B8 02 00 C3 BB 05 00 C3. CALL m1hedefi mutlak adres olarak değil, sonraki komuta göre uzaklık (rel16) olarak saklar. Bu yüzden kod bellekte başka bir yere taşınsa da çalışır.- 16 bitlik değerler bellekte küçük uçlu (little-endian) saklanır: önce düşük bayt (04), sonra yüksek bayt (00).
- Prosedürün adı (
m1) yalnızca assembler için vardır; makine kodunda yalnızca 0107h adresi kalır.
Parametreler
Parametre Geçirme Yolları
number1, result gibi ortak değişkenleri okur/yazar. Kolay ama prosedür o değişkenlere bağımlı kalır.PUSH ile itilir, prosedür BP ile okur, RET n ile temizlenir. Yüksek düzey dillerin yöntemi (Bölüm 8).Parametreler
Yazmaçlarla Parametre
- Örnekte AL ve BL yazmaçları iki parametreyi temsil eder.
m2prosedürü AL ve BL'yi çarpar, sonucu AX yazmacına koyar.CALL m2prosedürü çağırır;RETçağrıyı yapan yere döner.- m2 iki kez çağrılıyor: ikinci çağrıda AL, birinci çağrının sonucunu taşır, çünkü MUL AX'i (dolayısıyla AL'yi) değiştirir.
| MUL kaynak | İşlem | Sonuç |
|---|---|---|
| 8 bit | AL × kaynak | AX |
| 16 bit | AX × kaynak | DX:AX |
MUL işaretsiz çarpar; sonucun üst yarısı (AH ya da DX) 0 değilse CF = OF = 1 olur. Kaynak sabit olamaz: MUL 2 geçersizdir.
Parametreler · Kodu adım adım çalıştır
Kod İzleme: Yazmaçlarla Parametre
AL = 1 (MOV AL, 1 = B0 01, 2 bayt).
BL = 2.
1. çağrı: dönüş adresi 0107h itilir, IP = 010Bh.
MUL BL: AX = 1 × 2 = 0002h. AH = 0 → CF = OF = 0.
RET → IP = 0107h: ikinci CALL.
2. çağrı: bu kez dönüş adresi 010Ah itilir. Aynı prosedür, farklı dönüş noktası.
MUL BL: AL artık 2 → AX = 2 × 2 = 0004h.
RET → IP = 010Ah.
RET: DOS'a dönülür. Sonuç AX = 0004h.
ORG 100h MOV AL, 1 ; 1. parametre MOV BL, 2 ; 2. parametre CALL m2 ; 0104h: E8 04 00 CALL m2 ; 0107h: E8 01 00 RET ; 010Ah: DOS'a dön m2 PROC ; 010Bh MUL BL ; AX = AL * BL RET ; çağırana dön m2 ENDP END
Parametreler
Yazmaçların Korunması
Sorun
Prosedür, çağıranın kullandığı bir yazmacı habersizce değiştirirse (ör. döngü sayacı CX), çağıran kod bozulur.
Belgeleme
; m2: AL * BL hesaplar ; Girdi : AL, BL (işaretsiz) ; Çıktı : AX = AL * BL ; Bozar : CF, OF
Çözüm: PUSH / POP
yaz PROC PUSH AX ; kullanacaklarını PUSH SI ; sakla ; ... AX ve SI kullanılır POP SI ; TERS sırayla POP AX ; geri yükle RET yaz ENDP
Sonuç döndüren yazmaç (ör. AX) korunmaz; aksi hâlde sonuç kaybolur.
Örnek Programlar
Merhaba Dünya Mesajı
- Prosedür kullanarak ekrana mesaj yazdırma.
LEA SI, msg: msg adlı dizgenin adresini SI yazmacına yükler (parametre = adres).CALL print_me: print_me prosedürünü çağırır.RET: işletim sistemine geri döner.print_me, null (0) ile biten bir dizgeyi karakter karakter yazdırır.b.[SI]emu8086'nınBYTE PTR [SI]kısaltmasıdır:CMP [SI], 0işlem boyutunu belirtmez, bu yüzden boyut açıkça yazılır.
ORG 100h LEA SI, msg ; msg adresi → SI CALL print_me RET ; DOS'a dön print_me PROC ; ............ print_me ENDP msg DB 'Hello World!', 0 END
Örnek Programlar · Kodu adım adım çalıştır
Kod İzleme: print_me
SI, dizgenin ilk karakterini ('H') gösterir.
CALL print_me: dönüş adresi (4. satırdaki RET) yığına itilir.
[SI] = 'H' (48h) ≠ 0 → ZF = 0, JE atlamaz.
AL = 48h, AH = 0Eh, INT 10h → H.
SI = msg+1, next_char'a dön.
Aynı döngü: 'e' yazılır. AL = 65h, SI = msg+2.
Döngü 3 tur daha: "llo" yazılır. AL = 6Fh, SI = msg+5.
Döngü 6 tur daha: " World" yazılır (boşluk da bir karakterdir: 20h). AL = 64h, SI = msg+11.
Aynı döngü: '!' yazılır. AL = 21h, SI = msg+12.
[SI] = 0 → ZF = 1, JE stop atlar.
Prosedürün RET'i dönüş adresini alır → 4. satıra dönülür.
Ana programın RET'i DOS'a döner. Ekranda: Hello World!
ORG 100h LEA SI, msg ; SI = msg adresi CALL print_me RET ; DOS'a dön print_me PROC next_char: CMP b.[SI], 0 ; sıfır mı? JE stop ; evet: bitir MOV AL, [SI] ; sıradaki ASCII karakter MOV AH, 0Eh ; teletype işlevi INT 10h ; AL'yi ekrana yaz ADD SI, 1 ; sonraki karakter JMP next_char ; başa dön stop: RET ; çağırana dön print_me ENDP msg DB 'Hello World!', 0 END
Örnek Programlar · Kodu adım adım çalıştır
Kod İzleme: İki Değişken Toplama
Parametreler bellek değişkenlerinde: number1 = 5, number2 = 3.
CALL AddNumbers (0100h, 3 bayt): dönüş adresi 0103h itilir.
AX = number1 = 0005h.
AX = 5 + 3 = 0008h; sonuç sıfır değil, elde yok.
result = 0008h (bellekte 08 00).
RET → IP = 0103h.
RET → DOS. DW ? başlangıç değeri belirsiz demektir (emu8086 0 koyar).
ORG 100h start: CALL AddNumbers ; prosedürü çağır RET ; DOS'a dön AddNumbers PROC MOV AX, number1 ; AX = ilk sayı ADD AX, number2 ; AX = AX + ikinci sayı MOV result, AX ; sonucu sakla RET ; prosedürü bitir AddNumbers ENDP number1 DW 5 ; ilk sayı 5 number2 DW 3 ; ikinci sayı 3 result DW ? ; sonucu tutar
Örnek Programlar · Kodu adım adım çalıştır
Kod İzleme: Adres ve Sayaçla Parametre
Bölüm 7'deki dizi toplamı, şimdi yeniden kullanılabilir bir prosedür.
CX = 5 eleman.
CALL topla: dönüş adresi yığına.
AL = 0.
+5 → AL = 5; SI ilerler, CX = 4.
+4 → AL = 9; SI ilerler, CX = 3.
+5 → AL = 14; SI ilerler, CX = 2.
+2 → AL = 16; SI ilerler, CX = 1.
+1 → AL = 17; SI ilerler, CX = 0 → döngü biter.
RET: çağırana dönülür, sonuç AL'de.
m = 11h = 17.
Program biter. Aynı prosedür başka bir dizi için yalnızca SI ve CX değiştirilerek çağrılabilir.
ORG 100h LEA SI, vector ; parametre 1: adres MOV CX, 5 ; parametre 2: adet CALL topla MOV m, AL ; dönüş değeri AL'de RET topla PROC ; Girdi: SI, CX MOV AL, 0 ; Çıktı: AL = toplam dongu: ADD AL, [SI] INC SI LOOP dongu RET topla ENDP vector DB 5, 4, 5, 2, 1 m DB 0
Örnek Programlar
İç İçe Çağrılar
main, A'yı çağırmak üzere. SP = FFFEh.
CALL A: 0103h itilir, SP = FFFCh.
A içinden CALL B: 0115h itilir, SP = FFFAh. Yığında iki dönüş adresi var.
B'nin RET'i en üstteki 0115h'yı alır: A'ya döner (LIFO).
A'nın RET'i 0103h'yı alır: main'e döner. Son çağrılan ilk döner.
Örnek Programlar
CALL / RET Simülatörü
CALL ile prosedür çağırın, "komut yürüt" ile IP'yi ilerletin, RET ile dönün. Yığındaki dönüş adreslerini ve SP'yi izleyin.
Sık Yapılan Hatalar · Kodu adım adım çalıştır
Kod İzleme: PUSH / POP Dengesizliği
AX = 1234h.
CALL: dönüş adresi 0106h itilir.
PUSH AX: 1234h, dönüş adresinin üstüne itilir.
RET yığının tepesindekini alır: IP = 1234h! İşlemci rastgele bir adrese atlar, program çöker ya da öngörülemez davranır.
ORG 100h MOV AX, 1234h ; 0100h: B8 34 12 CALL hatali ; 0103h: dönüş = 0106h RET ; 0106h hatali PROC PUSH AX ; AX saklandı... ; ... işlemler ... RET ; POP AX unutuldu! hatali ENDP
Sık Yapılan Hatalar
Sık Yapılan Hatalar
p1 PROC … p2 ENDP → assembler hatası.Sık Yapılan Hatalar
Prosedür mü, Makro mu?
| Prosedür (CALL) | Makro (Bölüm 8) | |
|---|---|---|
| Kod | bellekte bir kez bulunur | her kullanıldığı yere kopyalanır |
| Çalışma | CALL / RET ve yığın kullanır | çağrı maliyeti yok, doğrudan yürütülür |
| Boyut | küçük program | çok kullanılırsa büyük program |
| Hız | CALL + RET ek süre | daha hızlı |
| Parametre | yazmaç, bellek, yığın | assembler düzeyinde metin parametreleri |
putc, print gibi emu8086.inc öğeleri makrodur; print_string, scan_num gibileri ise DEFINE_… ile eklenen prosedürlerdir ve CALL ile çağrılır.Özet
Özet
Özet
Alıştırmalar
- 0120h adresindeki
CALL pkomutu 0100h'daki p'yi çağırıyor. Makine kodu nedir? Yığına ne itilir? - m2 örneğinde ikinci
CALL m2'den önceMOV AL, 3eklenseydi son AX ne olurdu? - SP = FFFEh iken art arda 3 CALL yapılıyor (hiç RET yok). SP kaç olur?
- CX'i bozan bir prosedürü LOOP içinde çağırmak neden hatalıdır? İki çözüm önerin.
- AL'deki küçük harfi büyük harfe çeviren
buyukprosedürünü yazın (girdi/çıktı AL). - print_me'yi, yazdığı karakter sayısını CX'te döndürecek şekilde değiştirin.
İpuçları / cevaplar
- Sonraki adres 0123h; 0100h − 0123h = −23h = FFDDh →
E8 DD FF. Yığına 0123h itilir. - İlk çağrı AX = 2; sonra AL = 3 → AX = 3 × 2 = 0006h.
- Her CALL 2 bayt: FFFEh − 6 = FFF8h.
- LOOP sayacı bozulur, döngü yanlış sayıda döner. Prosedürde
PUSH CX/POP CXya da çağırmadan önce çağıranda sakla. cmp al,'a'/jb son/cmp al,'z'/ja son/sub al, 20h/son: ret- Başta
MOV CX, 0, her karakterdeINC CX; CX çıktı olduğu için korunmaz.
Özet
Kaynaklar
- emu8086 belgeleri: 8086 Assembler Tutorial for Beginners (Part 8: Procedures).
- Intel, The 8086 Family User's Manual, 1979: CALL, RET ve yığın işlemleri.
- Barry B. Brey, The Intel Microprocessors, 8. baskı, Pearson, 2009: Bölüm 6.3 "Procedures".
- Kip R. Irvine, Assembly Language for x86 Processors, 7. baskı, Pearson, 2014: Bölüm 5 "Procedures".