Bölüm 8: Prosedürler

Mikroişlemciler
Prosedür kavramıPROC / ENDPCALL ve RET Yığın ve dönüş adresiParametre geçirmeYığın çerçevesiYazmaçların korunması Örnek programlarSık yapılan hatalar
Sercan KÜLCÜ · Giresun Üniversitesi · Elektrik-Elektronik Mühendisliği

Giriş

Öğrenme Çıktıları

Tanımlama

PROC / ENDP ile prosedür tanımlamak; direktif ile işlemci komutunu ayırt etmek.

CALL / RET

Dönüş adresinin yığına nasıl itildiğini ve RET ile nasıl geri alındığını SP ve IP değerleriyle izlemek.

Parametreler

Yazmaçlar, bellek değişkenleri ve yığınla (BP çerçevesi) prosedüre veri gönderip sonuç almak.

Güvenli prosedür

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 seviyeli dillerdeki fonksiyon / alt program).
  • Programın içinden CALL ile çağrılır.
  • Görevi tamamlayınca RET ile ç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.
ana program…CALL yaz…CALL yaz…RETyaz PROC……RETCALLRET
Tekrar kullanımKod tekrarı ve bellek kullanımı azalır.
OkunabilirlikProgram, adı konmuş küçük görevlere bölünür.
BakımHata tek yerde düzeltilir, ayrı ayrı test edilir.

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 RET bulunmalı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
END

Prosedü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

ÖğeTürüGörevi
PROC / ENDPAssembler direktifiProsedürün başlangıç adresini ve türünü (NEAR/FAR) assembler'a bildirir. Makine koduna çevrilmez, bellekte yer kaplamaz.
CALLİşlemci komutuDönüş adresini yığına iter, prosedürün adresine atlar.
RETİşlemci komutuDönüş adresini yığından alır (POP) ve oraya atlar. Hem prosedürden hem .COM programından dönmek için kullanılır.
ENDAssembler direktifiKaynak dosyanın sonunu bildirir.
CALL ve RET derleyici direktifi değil, işlemcinin yürüttüğü komutlardır (CALL near: E8, RET: C3). Yalnızca PROC, ENDP ve END direktiftir.
derleme: PROC · ENDP · END → bayt yokçalışma: CALL = E8 · RET = C3
RET neden işletim sistemine döner? DOS bir .COM programını başlatırken yığına 0000h iter. Programın son RET'i bu değeri IP'ye alır; CS:0000h'da (PSP'nin başı) INT 20h bulunur ve program sonlanır.

Tanımlama

Modüler Programlama

; ana.asm
EXTRN yaz:NEAR    ; başka dosyada
  ...
  CALL yaz

; araclar.asm
PUBLIC yaz        ; dışarıya açık
yaz PROC NEAR
  ...
  RET
yaz ENDP
  • Büyük programlar prosedürlere, prosedürler de ayrı kaynak dosyalarına (modüllere) bölünür.
  • PUBLIC bir prosedür adını başka modüllere açar; EXTRN adın başka bir modülde tanımlı olduğunu ve türünü (NEAR/FAR) bildirir.
  • Her modül ayrı derlenir ve bir nesne dosyası (.OBJ) olur; bağlayıcı bunları birleştirir ve CALL komutlarındaki adresleri tamamlar.
  • Sık kullanılan nesne dosyaları bir kütüphane (.LIB) dosyasında toplanır; bağlayıcı yalnızca gereken prosedürleri programa alır.
  • emu8086'da ortak kod genellikle INCLUDE ile eklenir: dosyanın metni kaynağa katılır ve tek parça derlenir (emu8086.inc).
ana.asmderleana.objaraclar.asmderlearaclar.objbağlayıcı → .EXE

CALL ve RET

CALL ve RET Adım Adım

kod (CS)0100hCALL m10103hMOV AX, 20106hRET0107hm1: MOV BX, 5010AhRET← IPYığın (SS)FFF8hFFFAhFFFChFFFEh0000hSPyukarı: düşük adresler

Başlangıç: IP = 0100h, SP = FFFEh. Sıradaki komut CALL m1 (3 bayt: E8 04 00).

kod (CS)0100hCALL m10103hMOV AX, 20106hRET0107hm1: MOV BX, 5010AhRET← IPYığın (SS)FFF8hFFFAhFFFCh0103hSPFFFEh0000hyukarı: düşük adresler

CALL, önce dönüş adresini (sonraki komut: 0103h) yığına iter: SP = FFFCh, [FFFCh] = 0103h.

kod (CS)0100hCALL m10103hMOV AX, 20106hRET0107hm1: MOV BX, 5010AhRET← IPYığın (SS)FFF8hFFFAhFFFCh0103hSPFFFEh0000hyukarı: düşük adresler

Sonra IP ← 0103h + 0004h = 0107h: prosedürün ilk komutu.

kod (CS)0100hCALL m10103hMOV AX, 20106hRET0107hm1: MOV BX, 5010AhRET← IPYığın (SS)FFF8hFFFAhFFFCh0103hSPFFFEh0000hyukarı: düşük adresler

MOV BX, 5 yürütüldü, IP = 010Ah. Yığın değişmedi.

kod (CS)0100hCALL m10103hMOV AX, 20106hRET0107hm1: MOV BX, 5010AhRET← IPYığın (SS)FFF8hFFFAhFFFChFFFEh0000hSPyukarı: düşük adresler

RET: IP ← [SP] = 0103h, SP = FFFEh. Akış, CALL'ın hemen arkasından sürer.

kod (CS)0100hCALL m10103hMOV AX, 20106hRET0107hm1: MOV BX, 5010AhRET← IPYığın (SS)FFF8hFFFAhFFFChFFFEh0000hSPyukarı: düşük adresler

MOV AX, 2 → IP = 0106h. Buradaki RET 0000h'yı alır ve programı sonlandırır.

CALL ve RET

CALL / RET Türleri

KomutKodBaytYığına / yığındanAçıklama
CALL nearE8 rel163IPAynı kesimde doğrudan çağrı (varsayılan).
CALL reg/memFF /22–4IPDolaylı yakın çağrı: CALL BX, CALL [tablo+SI].
CALL far9A ofs seg5CS, sonra IPBaşka kesimdeki prosedür; CS:IP birlikte değişir.
RETC31IPYakın dönüş.
RETFCB1IP, sonra CSUzak dönüş (FAR prosedür).
RET nC2 n163IP, SP += nDö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. Tek kesimli .COM örneklerinde prosedürler NEAR'dır.
CALL near: SP − 2FFFA····FFFCdönüş IPFFFE0000hSPCALL far: SP − 4FFFAdönüş IPFFFCdönüş CSFFFE0000hSP

CALL ve RET

Uzak Prosedür: Yığın Düzeni

  • Uzak (FAR) prosedür başka bir kod kesimindedir; ona ulaşmak için hem CS hem IP değişmelidir.
  • CALL FAR önce CS'yi, sonra IP'yi iter: yığında 4 bayt. RETF önce IP'yi, sonra CS'yi çeker.
  • Yakın CALL yalnızca IP'yi (2 bayt) iter, RET yalnızca IP'yi çeker.
  • Prosedürün türü ile dönüş komutu uyuşmalıdır: PROC FAR yazıldığında assembler içteki RET'i RETF (CB) olarak kodlar.
  • .COM programı tek kesimde çalıştığı için yakın çağrı yeterlidir; birden çok kod kesimi olan .EXE programlarında uzak çağrı gerekir.
CALL (yakın)FFFCIPSPFFFE····CALL FARFFFAIPSPFFFCCSFFFE····2 bayt4 bayt
Yığındaki sıra RETF'in çekme sırasıyla uyumludur: en son itilen IP, yığının tepesindedir ve ilk çekilen odur.

CALL ve RET · Kodu adım adım çalıştır

Kod İzleme: PUSH CS + CALL ile RETF

AX0005hIP0103hCS0700hSPFFFEhyığınFFFA····FFFC····FFFE0000hSP

AX = 5. Bu kod, CALL FAR'ın yığına yaptığı iki itmeyi iki ayrı komutla taklit eder.

AX0005hIP0104hCS0700hSPFFFChyığınFFFA····FFFC0700hSPFFFE0000h

PUSH CS: kod kesimi (bu çalıştırmada 0700h) yığına itilir; uzak çağrının ilk yarısı.

AX0005hIP010BhCS0700hSPFFFAhyığınFFFA0107hSPFFFC0700hFFFE0000h

Yakın CALL uzak dönüş adresini (0107h) iter ve 010Bh'ye atlar. Yığın artık uzak çağrının düzenindedir: tepede IP, altında CS.

AX000AhIP010DhCS0700hSPFFFAhyığınFFFA0107hSPFFFC0700hFFFE0000h

SHL AX, 1: AX = 000Ah.

AX000AhIP0107hCS0700hSPFFFEhyığınFFFA····FFFC····FFFE0000hSP

RETF iki sözcük çeker: IP = 0107h, CS = 0700h; SP 4 artarak FFFEh olur.

AX000BhIP010AhCS0700hSPFFFEhyığınFFFA····FFFC····FFFE0000hSP

Akış ADD AX, 1'den sürer: AX = 000Bh. Gerçek CALL FAR (9A, 5 bayt) aynı iki itmeyi tek komutla yapar.

ORG  100h
MOV  AX, 5
PUSH CS          ; uzak çağrının ilk yarısı
CALL uzak        ; IP itilir
ADD  AX, 1
RET
uzak PROC
SHL  AX, 1
RETF             ; önce IP, sonra CS
uzak ENDP

CALL ve RET · Kodu adım adım çalıştır

Kod İzleme: Tablo ve Yazmaçla Dolaylı CALL

AX0006hSI0000hBX0000hIP0103h0114htablo+00116htablo+2SIbir_ekle = 0114h · iki_kat = 0116hyığınFFFC····FFFE0000hSP

AX = 6. tablo, iki prosedürün adresini tutan bir sözcük dizisidir (0114h, 0116h).

AX0006hSI0002hBX0000hIP0106h0114htablo+00116htablo+2SIbir_ekle = 0114h · iki_kat = 0116hyığınFFFC····FFFE0000hSP

SI = 2: tablodaki 2. eleman (her eleman 2 bayt).

AX0006hSI0002hBX0000hIP0116h0114htablo+00116htablo+2SIbir_ekle = 0114h · iki_kat = 0116hyığınFFFC010AhSPFFFE0000h

CALL tablo[SI]: hedef adres bellekteki [tablo+2] = 0116h'den okunur. Dönüş adresi 010Ah yığına, IP = 0116h.

AX000ChSI0002hBX0000hIP0118h0114htablo+00116htablo+2SIbir_ekle = 0114h · iki_kat = 0116hyığınFFFC010AhSPFFFE0000h

iki_kat: SHL AX, 1 → AX = 12.

AX000ChSI0002hBX0000hIP010Ah0114htablo+00116htablo+2SIbir_ekle = 0114h · iki_kat = 0116hyığınFFFC····FFFE0000hSP

RET: IP = 010Ah, ana programa dönüldü.

AX000ChSI0002hBX0114hIP010Dh0114htablo+00116htablo+2SIbir_ekle = 0114h · iki_kat = 0116hyığınFFFC····FFFE0000hSP

BX = OFFSET bir_ekle = 0114h.

AX000ChSI0002hBX0114hIP0114h0114htablo+00116htablo+2SIbir_ekle = 0114h · iki_kat = 0116hyığınFFFC010FhSPFFFE0000h

CALL BX: hedef yazmaçtan alınır. Dönüş 010Fh yığına, IP = 0114h.

AX000DhSI0002hBX0114hIP0115h0114htablo+00116htablo+2SIbir_ekle = 0114h · iki_kat = 0116hyığınFFFC010FhSPFFFE0000h

bir_ekle: AX = 13.

AX000DhSI0002hBX0114hIP010Fh0114htablo+00116htablo+2SIbir_ekle = 0114h · iki_kat = 0116hyığınFFFC····FFFE0000hSP

RET → 010Fh. Hangi prosedürün çağrılacağı çalışma anında SI ya da BX ile seçildi.

MOV  AX, 6
MOV  SI, 2       ; 2. işlem (× 2)
CALL tablo[SI]   ; bellekle dolaylı
MOV  BX, OFFSET bir_ekle
CALL BX          ; yazmaçla dolaylı
RET
tablo DW bir_ekle, iki_kat
bir_ekle: INC AX
RET
iki_kat: SHL AX, 1
RET

CALL ve RET

Atlama Tablosuyla Menü

MOV  AH, 1
INT  21h          ; AL = '1'..'3'
SUB  AL, '1'      ; 0..2
CMP  AL, 2
JA   gecersiz     ; aralık denetimi
MOV  BL, AL
MOV  BH, 0
SHL  BX, 1        ; her adres 2 bayt
CALL islem[BX]
gecersiz: RET
islem DW topla, cikar, carp
  • Menüdeki her seçenek bir prosedürdür; adresleri islem tablosunda sırayla durur.
  • Tuş, tablo indisine çevrilir: '1' → 0, '2' → 1, '3' → 2. Adresler 2 bayt olduğundan indis 2 ile çarpılır (SHL BX, 1).
  • JA işaretsiz karşılaştırır: '0' tuşu 0 − 1 = FFh verir ve 2'den büyük sayıldığı için o da reddedilir. Tek denetim iki yönü de kapatır.
  • Aralık denetimi olmasaydı geçersiz bir tuş tablonun dışındaki baytları adres sanıp oraya çağrı yapardı.
TuşBXÇağrılan
10topla
22cikar
34carp
0 · 9—hiçbiri (RET)

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

0100hE8CALL0101h040102h000103hB8MOV AX,20104h020105h000106hC3RET0107hBBm1: MOV BX,50108h050109h00010AhC3RETrel16 = 0107h − 0103h = 0004h → 04 00 (little-endian)
  • Örnek program bellekte yalnızca 11 bayt tutar: E8 04 00 B8 02 00 C3 BB 05 00 C3.
  • CALL m1 hedefi 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 little-endian (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.

CALL ve RET

CALL ve RET'in Süresi

Komut (8086)Saat turu
CALL yakın19
CALL yazmaç16
CALL FAR28
RET8
RETF18
PUSH yazmaç11
POP yazmaç8

Çağrı yükü hesabı

  • İki yazmaç saklayan bir prosedürün işi dışındaki yükü: 19 + 8 + 2 × (11 + 8) = 65 tur.
  • 5 MHz'lik 8086'da bir tur 200 ns: 65 × 200 ns = 13 µs.
  • Prosedür 1000 kez çağrılırsa yük 13 ms olur.
  • Makro bu yükü taşımaz ama her kullanımda gövdeyi kopyalar.
Kısa ve çok sık çalışan işler (tek karakter yazma) makroya, uzun ya da az çağrılan işler prosedüre uygundur. Tablodaki değerler yazmaç ve doğrudan biçimler içindir; bellek işlenenli biçimlere etkin adres hesabının süresi eklenir.

Parametreler

Parametre Geçirme Yolları

1. YazmaçlarlaEn kolay ve en hızlı yol. Ör. AL ve BL iki parametre, sonuç AX'te. Yazmaç sayısı sınırlıdır.
2. Bellek değişkenleriyleProsedür number1, result gibi ortak değişkenleri okur/yazar. Kolay ama prosedür o değişkenlere bağımlı kalır.
3. Adres (işaretçi) ileDizinin adresi SI/DI/BX'te gönderilir; uzun veriler kopyalanmadan işlenir (print_me).
4. YığınlaParametreler PUSH ile itilir, prosedür BP ile okur, RET n ile temizlenir. Yüksek seviyeli dillerin yöntemi.
çağıranprosedürAL, BL (yazmaç)number1 (bellek)SI → dizi (adres)PUSH → [BP+4] (yığın)
Hangi yol seçilirse seçilsin, prosedürün başına girdi, çıktı ve bozulan yazmaçlar açıklama olarak yazılmalıdır.

Parametreler

Değerle mi, Adresle mi?

Değerle geçirme

Prosedüre verinin kopyası verilir (AX = 5). Prosedür kopyayı değiştirse de bellekteki asıl değişken değişmez. Tek sayı gibi küçük veriler için hızlı ve güvenlidir.

Adresle geçirme

Verinin adresi verilir (SI = OFFSET a). Prosedür [SI] üzerinden asıl değişkeni okur ve değiştirebilir. Diziler, dizgiler ve prosedürün değiştirmesi gereken değişkenler için.

MOV  AX, a        ; değer: AX'te a'nın kopyası
CALL iki_kat      ; AX iki katına çıkar, a aynı kalır
LEA  SI, a        ; adres: SI = OFFSET a
CALL iki_kat_m    ; [SI] iki katına çıkar, a değişir
LEA SI, a ile MOV SI, OFFSET a aynı sonucu verir: a'nın adresi. MOV SI, a ise adresi değil, a'nın içindeki değeri yükler; adresle geçirmede sık yapılan bir karışıklıktır.

Parametreler · Kodu adım adım çalıştır

Kod İzleme: Adresle Takas

SI0115hDI0117hAX0000hSPFFFEhbellek1234ha (0115)00FFhb (0117)

SI ve DI, a ve b değişkenlerinin adreslerini taşır (0115h, 0117h): prosedüre değerler değil, yerler gönderilir.

SI0115hDI0117hAX0000hSPFFFChbellek1234ha (0115)00FFhb (0117)

CALL takas: dönüş adresi 010Bh yığına, SP = FFFCh.

SI0115hDI0117hAX0000hSPFFFAhbellek1234ha (0115)00FFhb (0117)

takas AX'i kullanacak: önce saklar (SP = FFFAh).

SI0115hDI0117hAX1234hSPFFFAhbellek1234ha (0115)00FFhb (0117)

MOV AX, [SI]: a'nın değeri (1234h) okunur.

SI0115hDI0117hAX00FFhSPFFFAhbellek1234ha (0115)1234hb (0117)

XCHG AX, [DI]: AX ile b tek komutta yer değiştirir: b = 1234h, AX = 00FFh.

SI0115hDI0117hAX00FFhSPFFFAhbellek00FFha (0115)1234hb (0117)

MOV [SI], AX: a = 00FFh. Takas bellekte tamamlandı.

SI0115hDI0117hAX0000hSPFFFEhbellek00FFha (0115)1234hb (0117)

POP AX ve RET: AX eski değerine (0000h) döner, SP = FFFEh; a ile b'nin değişmiş hâli kalır.

ORG  100h
LEA  SI, a
LEA  DI, b
CALL takas
RET
takas PROC        ; Girdi: SI, DI (adres)
PUSH AX
MOV  AX, [SI]
XCHG AX, [DI]
MOV  [SI], AX
POP  AX
RET
takas ENDP
a    DW 1234h
b    DW 00FFh

Parametreler

Yazmaçlarla Parametre

  • Örnekte AL ve BL yazmaçları iki parametreyi temsil eder.
  • m2 prosedürü AL ve BL'yi çarpar, sonucu AX yazmacına koyar.
  • CALL m2 prosedü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İşlemSonuç
8 bitAL × kaynakAX
16 bitAX × kaynakDX: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.

ALBLm2: MUL BLAXgirdi yazmaçları → prosedür → çıktı yazmacı

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 · Kodu adım adım çalıştır

Kod İzleme: Sonucu CF ile Döndürmek

AL37hBL00hSPFFFEhCF0AL = 55

rakam prosedürü AL'deki karakteri sayıya çevirir; başarıyı CF ile bildirir (DOS hizmetleri de böyle yapar). AL = '7' = 37h.

AL07hBL00hSPFFFChCF0AL = 7

CALL rakam; SUB AL, '0': 37h − 30h = 7.

AL07hBL00hSPFFFChCF1AL = 7

CMP AL, 10: 7 − 10 ödünç ister → CF = 1 (değer 0…9 aralığında).

AL07hBL00hSPFFFChCF0AL = 7

CMC: CF = 0 → “rakamdır”.

AL07hBL07hSPFFFEhCF0AL = 7sonuç: rakam

RET; çağıran CF'ye bakabilir (JC/JNC). BL = 7 saklandı.

AL48hBL07hSPFFFChCF0AL = 72

AL = 'x' = 78h; ikinci çağrı: 78h − 30h = 48h (72).

AL48hBL07hSPFFFChCF0AL = 72

CMP AL, 10: 72 ≥ 10, ödünç yok → CF = 0.

AL48hBL07hSPFFFChCF1AL = 72

CMC: CF = 1 → “rakam değil”. AL bozuldu (48h); bu durumda AL'nin değeri kullanılmamalıdır.

AL48hBL07hSPFFFEhCF1AL = 72sonuç: rakam değil

RET: sonuç bayrakta. Çağıran JC hata ile ayırt edebilir.

MOV  AL, '7'
CALL rakam       ; CF = 0 → AL = değer
MOV  BL, AL
MOV  AL, 'x'
CALL rakam       ; CF = 1 → rakam değil
RET
rakam PROC       ; Girdi AL, Çıktı CF
SUB  AL, '0'
CMP  AL, 10      ; 0..9 → ödünç, CF = 1
CMC              ; rakamsa CF = 0
RET
rakam ENDP

Parametreler

Birden Çok Sonuç Döndürmek

bolkalan PROC   ; Girdi: AX, BL (bölen)
CMP  BL, 0      ; Çıktı: AL = bölüm
JE   hata       ;        AH = kalan
DIV  BL         ;        CF = 1: bölen 0
CLC
RET
hata: STC
RET
bolkalan ENDP
  • Yazmaç çifti: 32 bitlik sonuç DX:AX'te (16 bitlik MUL gibi), bölüm ile kalan AL ve AH'de.
  • Bayrak + yazmaç: CF başarıyı, yazmaç değeri bildirir; çağıran önce JC ile bakar.
  • Bellek: sonuçlar adresi verilen bir alana yazılır; sayıları sınırsızdır.
GirdiSonuç
AX = 100, BL = 7AL = 0Eh (14), AH = 02h, CF = 0
AX = 100, BL = 0CF = 1, AX değişmez (0064h)
AX = 1000, BL = 2bölüm 500 AL'ye sığmaz: bölme hatası (INT 0)
Bu prosedürde iki RET vardır; yığına hiçbir şey itilmediği için güvenlidir. PUSH yapan prosedürlerde ise tüm yollar tek bir çıkış etiketinde birleştirilir.

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.

Parametreler · Kodu adım adım çalıştır

Kod İzleme: PUSH CX ile Sayaç Koruma

CX0002hAX0000hSPFFFEhyığınFFFA····FFFC····FFFE0000hSP▌

Ana döngü 2 satır yazacak: sayaç CX = 2. yildiz prosedürü de iç döngüsünde CX kullanıyor.

CX0002hAX0000hSPFFFChyığınFFFA····FFFC0106hSPFFFE0000h▌

CALL yildiz: dönüş adresi 0106h yığına, SP = FFFCh.

CX0002hAX0000hSPFFFAhyığınFFFA0002hSPFFFC0106hFFFE0000h▌

PUSH CX: çağıranın sayacı (2) yığına saklandı.

CX0003hAX0E23hSPFFFAhyığınFFFA0002hSPFFFC0106hFFFE0000h▌

İç sayaç CX = 3; AX = 0E23h (AH = 0Eh teletype, AL = '#').

CX0000hAX0E23hSPFFFAhyığınFFFA0002hSPFFFC0106hFFFE0000h###▌

İç döngü üç kez INT 10h: ###. LOOP sonunda CX = 0.

CX0002hAX0E23hSPFFFChyığınFFFA····FFFC0106hSPFFFE0000h###▌

POP CX: çağıranın sayacı geri geldi, CX = 2.

CX0002hAX0E23hSPFFFEhyığınFFFA····FFFC····FFFE0000hSP###▌

RET: 0106h'ye dönülür, SP = FFFEh.

CX0001hAX0E23hSPFFFEhyığınFFFA····FFFC····FFFE0000hSP###▌

Ana LOOP satir: CX = 1 → ikinci satır için yeniden çağrı.

CX0001hAX0E23hSPFFFEhyığınFFFA····FFFC····FFFE0000hSP######▌

İkinci çağrı aynı şekilde: ### yazılır, CX = 1 geri yüklenir.

CX0000hAX0E23hSPFFFEhyığınFFFA····FFFC····FFFE0000hSP######▌

LOOP: CX = 0 → döngü biter. Ekranda 6 karakter. PUSH/POP olmasaydı dönüşte CX = 0 olur, LOOP onu FFFFh yapıp neredeyse sonsuz dönerdi.

MOV  CX, 2       ; ana döngü: 2 satır
satir: CALL yildiz
LOOP satir
RET
yildiz PROC
PUSH CX          ; çağıranın sayacı
MOV  CX, 3       ; iç sayaç
MOV  AX, 0E23h   ; AH = 0Eh, AL = '#'
ic:  INT 10h
LOOP ic
POP  CX          ; sayaç geri
RET
yildiz ENDP

Parametreler

Çağıran mı Korur, Çağrılan mı?

ÖlçütÇağrılan korur (prosedürde PUSH/POP)Çağıran korur (CALL'ın çevresinde)
Kod nerede?prosedürde bir kezher çağrı noktasında
Gereksiz saklamaçağıran o yazmacı kullanmasa da saklanıryalnızca gerçekten gereken yazmaçlar
Unutma riskidüşük: tek yerde yazılıryüksek: her çağrıda tekrar gerekir
Uygun olduğu durumkütüphane prosedürleriçıktı yazmacındaki eski değerin gerektiği yer
dongu: PUSH CX         ; çağıran: sayaç
       CALL sayi_oku   ; sonuç CX'te
       ADD  toplam, CX
       POP  CX         ; sayaç geri
       LOOP dongu
Uygulamadaki kural: prosedür kullandığı yazmaçları korur, çıktı yazmaçlarını korumaz. Çıktı yazmacındaki eski değer gerekiyorsa onu çağıran saklar.

Parametreler · Kodu adım adım çalıştır

Kod İzleme: Tek Çıkış Noktası

SI0120hCX0005hBX0000hCF03091721344SIyığınFFF8····FFFA····FFFC····FFFE0000hSP

bul prosedürü SI'daki diziden CX eleman içinde AL'yi (7) arar; sonucu CF ve BX ile döndürür.

SI0120hCX0005hBX0000hCF03091721344SIyığınFFF8····FFFA····FFFC010ChSPFFFE0000h

CALL bul: dönüş adresi 010Ch yığına.

SI0120hCX0005hBX0000hCF03091721344SIyığınFFF80120hSPFFFA0005hFFFC010ChFFFE0000h

Prosedür CX ve SI'yı değiştireceği için ikisini de saklar; SP = FFF8h. BX = 0 (indis).

SI0121hCX0004hBX0001hCF13091721344SIyığınFFF80120hSPFFFA0005hFFFC010ChFFFE0000h

dizi[0] = 3 ≠ 7: SI ve BX ilerler, LOOP CX'i 4'e indirir.

SI0122hCX0003hBX0002hCF03091721344SIyığınFFF80120hSPFFFA0005hFFFC010ChFFFE0000h

dizi[1] = 9 ≠ 7: BX = 2, CX = 3.

SI0122hCX0003hBX0002hCF03091721344SIyığınFFF80120hSPFFFA0005hFFFC010ChFFFE0000h

dizi[2] = 7: ZF = 1, JE buldu atlar. Döngü erken bitiyor ama yığında hâlâ iki saklı değer var.

SI0122hCX0003hBX0002hCF13091721344SIyığınFFF80120hSPFFFA0005hFFFC010ChFFFE0000h

STC: CF = 1 → “bulundu”. Bulunamasaydı CLC ve JMP son aynı yere gelirdi.

SI0120hCX0005hBX0002hCF13091721344SIyığınFFF8····FFFA····FFFC010ChSPFFFE0000h

Her iki yol da son etiketinde birleşir: POP'lar her durumda çalışır, SI ve CX eski değerlerine döner.

SI0120hCX0005hBX0002hCF13091721344SIyığınFFF8····FFFA····FFFC····FFFE0000hSP

RET doğru adrese (010Ch) döner: CF = 1, BX = 2. JE'nin yerinde doğrudan RET olsaydı tepedeki SI değeri (0120h) dönüş adresi sanılırdı.

ORG  100h
LEA  SI, dizi
MOV  CX, 5
MOV  AL, 7
CALL bul         ; CF = 1: bulundu
RET
bul PROC         ; Girdi: SI, CX, AL
PUSH CX
PUSH SI
XOR  BX, BX      ; Çıktı: BX = indis
ara: CMP [SI], AL
JE   buldu
INC  SI
INC  BX
LOOP ara
CLC              ; bulunamadı
JMP  son
buldu: STC
son: POP SI      ; tek çıkış noktası
POP  CX
RET
bul ENDP
dizi DB 3, 9, 7, 1, 4

Parametreler · Kodu adım adım çalıştır

Yığınla Parametre Geçirme (BP ile)

····FFF4····FFF6····FFF8····FFFA····FFFC0000hFFFESPSS:bağıl konum · küçük adres yukarıda

Çağıran, parametreleri yığına koyar; prosedür bunları BP üzerinden okur.

····FFF4····FFF6····FFF8····FFFA····FFFC0000hFFFESPSS:bağıl konum · küçük adres yukarıda

AX = 7.

····FFF4····FFF6····FFF8····FFFA0007hFFFCSP0000hFFFESS:bağıl konum · küçük adres yukarıda

1. parametre yığında: [FFFCh] = 0007h.

····FFF4····FFF6····FFF8····FFFA0007hFFFCSP0000hFFFESS:bağıl konum · küçük adres yukarıda

AX = 3.

····FFF4····FFF6····FFF80003hFFFASP0007hFFFC0000hFFFESS:bağıl konum · küçük adres yukarıda

2. parametre: [FFFAh] = 0003h.

····FFF4····FFF6010BhFFF8SP0003hFFFA0007hFFFC0000hFFFESS:bağıl konum · küçük adres yukarıda

CALL: dönüş adresi 010Bh → [FFF8h]. IP = 010Fh.

····FFF40000hFFF6SP010BhFFF80003hFFFA0007hFFFC0000hFFFESS:bağıl konum · küçük adres yukarıda

Eski BP saklanır: [FFF6h] = 0000h.

····FFF40000hFFF6SP010BhFFF80003hFFFA0007hFFFC0000hFFFESS:bağıl konum · küçük adres yukarıda

MOV BP, SP: BP = FFF6h sabit bir çerçeve noktası olur.

····FFF40000hFFF6SP010BhFFF80003hFFFA0007hFFFC0000hFFFESS:bağıl konum · küçük adres yukarıda

[BP+6] = [FFFCh] = 0007h → AX = 7.

····FFF40000hFFF6SP010BhFFF80003hFFFA0007hFFFC0000hFFFESS:bağıl konum · küçük adres yukarıda

[BP+4] = [FFFAh] = 0003h → AX = 000Ah.

····FFF40000hFFF6010BhFFF8SP0003hFFFA0007hFFFC0000hFFFESS:bağıl konum · küçük adres yukarıda

BP eski değerine döner; SP = FFF8h (dönüş adresi üstte).

····FFF40000hFFF6010BhFFF80003hFFFASP0007hFFFC0000hFFFESS:bağıl konum · küçük adres yukarıda

RET: IP = 010Bh, SP = FFFAh.

····FFF40000hFFF6010BhFFF80003hFFFA0007hFFFC0000hFFFESPSS:bağıl konum · küçük adres yukarıda

ADD SP, 4: iki parametre atılır, SP = FFFEh. Yığın dengelendi.

····FFF40000hFFF6010BhFFF80003hFFFA0007hFFFC0000hFFFESS:bağıl konum · küçük adres yukarıda

Program biter.

ORG  100h
MOV  AX, 7
PUSH AX          ; 1. parametre
MOV  AX, 3
PUSH AX          ; 2. parametre
CALL TOPLA
ADD  SP, 4       ; parametreleri at
RET
TOPLA PROC
PUSH BP
MOV  BP, SP
MOV  AX, [BP+6]  ; 1. parametre
ADD  AX, [BP+4]  ; 2. parametre
POP  BP
RET
TOPLA ENDP

[BP+0] = eski BP, [BP+2] = dönüş adresi, [BP+4] = son konan parametre. ADD SP, 4 yerine prosedürde RET 4 de yazılabilirdi.

Parametreler

Yığın Çerçevesi ve Yerel Değişkenler

1. parametre (7)[BP+6]2. parametre (3)[BP+4]dönüş adresi[BP+2]eski BP[BP+0]yerel değişken 1[BP−2]yerel değişken 2[BP−4]BPSPküçük adres yukarıda · büyük adres aşağıda
PUSH BP
MOV  BP, SP
SUB  SP, 4        ; yerel alan
MOV  [BP-2], AX   ; yerel 1
...
MOV  SP, BP       ; yerelleri at
POP  BP
RET  4            ; 2 parametreyi at
  • SUB SP, 4 yığında iki sözcüklük yerel alan açar: [BP−2], [BP−4].
  • MOV SP, BP yerel alanı tek komutla geri verir.
  • Parametreler pozitif, yereller negatif BP bağıl konumundadır.

Parametreler · Kodu adım adım çalıştır

Kod İzleme: Yığında Yerel Değişken

AX0009hBP0000hSPFFFEhyığınFFF8····FFFA····FFFC····FFFE0000hSP

Parametre x = 9 AX'te. Prosedür x'i bir yerel değişkende saklayıp x·x + x hesaplayacak.

AX0009hBP0000hSPFFFChyığınFFF8····FFFA····FFFC0106hdönüşSPFFFE0000h

CALL: dönüş adresi 0106h → [FFFCh].

AX0009hBP0000hSPFFFAhyığınFFF8····FFFA0000heski BPSPFFFC0106hdönüşFFFE0000h

PUSH BP: çağıranın BP'si (0000h) saklandı.

AX0009hBPFFFAhSPFFFAhyığınFFF8····FFFA0000heski BP ← BPSPFFFC0106hdönüşFFFE0000h

MOV BP, SP: BP = FFFAh, çerçevenin sabit noktası.

AX0009hBPFFFAhSPFFF8hyığınFFF80000hyerel [BP−2]SPFFFA0000heski BP ← BPFFFC0106hdönüşFFFE0000h

SUB SP, 2: bir sözcüklük yerel alan açıldı; [BP−2] = FFF8h.

AX0009hBPFFFAhSPFFF8hyığınFFF80009hyerel [BP−2]SPFFFA0000heski BP ← BPFFFC0106hdönüşFFFE0000h

MOV [BP−2], AX: yerel = 9. [BP...] adresleri varsayılan olarak SS kesimini kullanır.

AX0051hBPFFFAhSPFFF8hyığınFFF80009hyerel [BP−2]SPFFFA0000heski BP ← BPFFFC0106hdönüşFFFE0000h

MUL AX: AX = 81 (DX = 0). AX değişti ama x yerelde duruyor.

AX005AhBPFFFAhSPFFF8hyığınFFF80009hyerel [BP−2]SPFFFA0000heski BP ← BPFFFC0106hdönüşFFFE0000h

ADD AX, [BP−2]: 81 + 9 = 90 = 005Ah.

AX005AhBPFFFAhSPFFFAhyığınFFF8····FFFA0000heski BP ← BPSPFFFC0106hdönüşFFFE0000h

MOV SP, BP: yerel alan tek komutla bırakıldı, SP = FFFAh.

AX005AhBP0000hSPFFFEhyığınFFF8····FFFA····FFFC····FFFE0000hSP

POP BP, RET: BP = 0, SP = FFFEh, çağırana dönüldü. Sonuç AX = 90.

MOV  AX, 9
CALL kare_art    ; AX = x·x + x
RET
kare_art PROC
PUSH BP
MOV  BP, SP
SUB  SP, 2       ; yerel alan [BP-2]
MOV  [BP-2], AX  ; yerel = x
MUL  AX          ; AX = x·x
ADD  AX, [BP-2]  ; + x
MOV  SP, BP      ; yereli at
POP  BP
RET
kare_art ENDP

Örnek Programlar

Merhaba Dünya Mesajı

  • Prosedür kullanarak ekrana mesaj yazdırma.
  • LEA SI, msg: msg adlı dizginin 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 dizgiyi karakter karakter yazdırır.
  • b.[SI] emu8086'nın BYTE PTR [SI] kısaltmasıdır: CMP [SI], 0 iş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, dizginin 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

Dizi toplamı, yeniden kullanılabilir bir prosedür olarak: SI = dizinin adresi.

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

Parametre Bloğu

LEA  BX, blok
CALL hesap        ; sonuç = a × b + c
RET
hesap PROC        ; Girdi: BX = blok adresi
PUSH AX
PUSH DX           ; MUL DX'i de yazar
MOV  AX, [BX]     ; a
MUL  WORD PTR [BX+2] ; × b
ADD  AX, [BX+4]   ; + c
MOV  [BX+6], AX   ; sonuç
POP  DX
POP  AX
RET
hesap ENDP
blok DW 12, 7, 5, ?
12[BX+0] a7[BX+2] b5[BX+4] c89[BX+6] sonuçBX → blok
  • Tek bir yazmaç (BX) istenen sayıda parametreyi taşır: blok, sabit uzaklıklarla ([BX+2], [BX+4]) okunur.
  • Sonuç da bloğa yazılır: 12 × 7 + 5 = 89 (0059h).
  • Aynı prosedür başka bir blokla, yalnızca BX değiştirilerek çağrılır.
  • Blok düzeni (hangi uzaklıkta ne var) prosedürün açıklamasında belgelenmelidir.

Örnek Programlar

Prosedürü Test Etmek

Bir prosedür yalnızca “olağan” girdiyle değil, sınır durumlarıyla da çalıştırılarak denenir. bul prosedürü (dizi: 3, 9, 7, 1, 4) için sonuçlar:

DurumALCXCFBXDeğerlendirme
ilk eleman3510doğru
son eleman4514doğru
dizide yok8505doğru; BX anlamsızdır, yalnızca CF'ye bakılır
tek elemanlık arama9101doğru: 9 ikinci eleman, aranan aralığın dışında
boş dizi7012yanlış: CX = 0 iken LOOP 65 536 tur döner
Hatanın nedeni

LOOP önce CX'i azaltır, sonra sıfır mı diye bakar: 0 − 1 = FFFFh. Prosedür dizinin ötesindeki baytları da tarar ve oradaki 7'yi “bulur”.

Düzeltme

Döngüden önce JCXZ ile boş dizi ayrılır: CX = 0 ise doğrudan CLC yapılıp son etiketine gidilir. Ayrıca CX ve SI'nın korunduğu her testte denetlenir.

Örnek Programlar

İç İçe Çağrılar

main0100h CALL A0103h …A0110h …0112h CALL B0115h RETB0120h …0122h RETIP = 0100hYığın (SS)FFF8hFFFAhFFFChFFFEh0000hSPyukarı: düşük adresler

main, A'yı çağırmak üzere. SP = FFFEh.

main0100h CALL A0103h …A0110h …0112h CALL B0115h RETB0120h …0122h RETIP = 0110hYığın (SS)FFF8hFFFAhFFFCh0103hSPFFFEh0000hyukarı: düşük adresler

CALL A: 0103h itilir, SP = FFFCh.

main0100h CALL A0103h …A0110h …0112h CALL B0115h RETB0120h …0122h RETIP = 0120hYığın (SS)FFF8hFFFAh0115hSPFFFCh0103hFFFEh0000hyukarı: düşük adresler

A içinden CALL B: 0115h itilir, SP = FFFAh. Yığında iki dönüş adresi var.

main0100h CALL A0103h …A0110h …0112h CALL B0115h RETB0120h …0122h RETIP = 0115hYığın (SS)FFF8hFFFAhFFFCh0103hSPFFFEh0000hyukarı: düşük adresler

B'nin RET'i en üstteki 0115h'yı alır: A'ya döner (LIFO).

main0100h CALL A0103h …A0110h …0112h CALL B0115h RETB0120h …0122h RETIP = 0103hYığın (SS)FFF8hFFFAhFFFChFFFEh0000hSPyukarı: düşük adresler

A'nın RET'i 0103h'yı alır: main'e döner. Son çağrılan ilk döner.

Örnek Programlar

Yığın İhtiyacı Hesabı

mainAPUSH AX, SICPUSH AX, BX, CXBPUSH CX
  • Bir programın yığın gereksinimi, çağrı ağacındaki en derin yol boyunca biriken baytların toplamıdır.
  • Her yakın CALL 2 bayt dönüş adresi, her PUSH 2 bayt ekler.
YolHesapBayt
main → A → B2 + 4 + 2 + 210
main → C2 + 68
  • SP = FFFEh ile başlayan programda SP en çok FFF4h'ye iner.
  • A ve C hiçbir zaman aynı anda yığında değildir: C çağrıldığında A'nın baytları çoktan geri alınmıştır.
Kesmeler de yığını kullanır (bayraklar, CS ve IP için 6 bayt): bir kesme yığının en derin anında gelebileceği için hesaplanan değerin üstünde pay bırakılır.

Örnek Programlar

CALL / RET Simülatörü

CALL düğmeleri prosedür çağırır, "komut yürüt" IP'yi ilerletir, RET dönüşü yapar; yığındaki dönüş adresleri ve SP her adımda görünür.

Özyineleme

Özyineleme

  • Özyineleme: bir prosedürün kendini (doğrudan ya da başka bir prosedür üzerinden) çağırmasıdır.
  • Her çağrı yığına yeni bir dönüş adresi ve kendi sakladığı değerleri koyar: her düzeyin değerleri birbirinden ayrı kalır.
  • Taban durum (durma koşulu) şarttır; yoksa her çağrı 2 bayt daha yer ve yığın taşar.
  • Problem her adımda küçülerek kendine benzer: 472'yi yazmak = 47'yi yazmak + '2' yazmak.
  • Sayı yazdırma, faktöriyel, ağaç ve dizin dolaşma özyinelemenin tipik örnekleridir.
yaz(472)sonra '2' yazaryaz(47)sonra '7' yazaryaz(4)sonra '4' yazartaban durum: bölüm 0 → yeni çağrı yok

Özyineleme · Kodu adım adım çalıştır

Kod İzleme: Özyinelemeli Sayı Yazdırma

AX01D8hDX0000hSPFFFChyığınFFF2····FFF4····FFF6····FFF8····FFFA····FFFC0106hSPFFFE0000hdönüşekran▌

AX = 472 (01D8h); CALL yaz: dönüş adresi 0106h yığına.

AX002FhDX0002hSPFFFAhyığınFFF2····FFF4····FFF6····FFF8····FFFA0002hSPFFFC0106hFFFE0000hbasamakdönüşekran▌

472 / 10: AX = 47 (2Fh), DX = 2. Son basamak (2) yığına itilir.

AX002FhDX0002hSPFFF8hyığınFFF2····FFF4····FFF6····FFF80116hSPFFFA0002hFFFC0106hFFFE0000hdönüşbasamakdönüşekran▌

AX ≠ 0: yaz kendini çağırır; dönüş adresi 0116h (son etiketi) yığına.

AX0004hDX0007hSPFFF4hyığınFFF2····FFF40116hSPFFF60007hFFF80116hFFFA0002hFFFC0106hFFFE0000hdönüşbasamakdönüşbasamakdönüşekran▌

İkinci düzey: 47 / 10 → AX = 4, basamak 7 yığına; yeniden CALL.

AX0000hDX0004hSPFFF2hyığınFFF20004hSPFFF40116hFFF60007hFFF80116hFFFA0002hFFFC0106hFFFE0000hbasamakdönüşbasamakdönüşbasamakdönüşekran▌

Üçüncü düzey: 4 / 10 → AX = 0, basamak 4 yığına. AX = 0: taban durum, JZ son. SP en derin noktada: FFF2h.

AX0E34hDX0004hSPFFF4hyığınFFF2····FFF40116hSPFFF60007hFFF80116hFFFA0002hFFFC0106hFFFE0000hdönüşbasamakdönüşbasamakdönüşekran4▌

POP AX = 4 → '4' yazılır. En son itilen basamak en önce çıkar.

AX0E37hDX0004hSPFFF8hyığınFFF2····FFF4····FFF6····FFF80116hSPFFFA0002hFFFC0106hFFFE0000hdönüşbasamakdönüşekran47▌

RET 0116h'ye, yani ikinci düzeyin son satırına döner: POP AX = 7 → '7'.

AX0E32hDX0004hSPFFFChyığınFFF2····FFF4····FFF6····FFF8····FFFA····FFFC0106hSPFFFE0000hdönüşekran472▌

Birinci düzey: POP AX = 2 → '2'. Ekranda 472.

AX0E32hDX0004hSPFFFEhyığınFFF2····FFF4····FFF6····FFF8····FFFA····FFFC····FFFE0000hSPekran472▌

Son RET 0106h'ye döner; SP = FFFEh. Her düzey kendi basamağını yığında sakladığı için ek değişken gerekmedi.

ORG  100h
MOV  AX, 472
CALL yaz
RET
yaz PROC         ; Girdi: AX (işaretsiz)
XOR  DX, DX
MOV  BX, 10
DIV  BX          ; AX = bölüm, DX = basamak
PUSH DX
OR   AX, AX      ; başka basamak var mı?
JZ   son
CALL yaz         ; önce üst basamaklar
son: POP AX
ADD  AL, '0'
MOV  AH, 0Eh
INT  10h
RET
yaz ENDP

Özyineleme

Özyineleme mi, Döngü mü?

ÖlçütÖzyineleme (yaz)Döngü (bölme + LOOP)
Kodkısa; problemin tanımına yakınsayaç ve ayrı bir yazdırma döngüsü gerekir
Yığınher düzeyde dönüş adresi + basamak: 4 baytbasamak başına 2 bayt
Süreher düzeyde CALL + RET (19 + 8 tur)çağrı yükü yok
Riskderinlik sınırsız büyürse yığın taşaryığın kullanımı sınırlı
Derinlik hesabı

yaz için düzey sayısı = basamak sayısı. 65 535 → 5 düzey: 5 × 2 (dönüş) + 5 × 2 (basamak) = 20 bayt; SP en çok FFEAh'ye iner.

Taban durumun yeri

Denetim CALL'dan önce yapılmalıdır. AX = 0 için de bir basamak (0) yazılır: 0 sayısı ek kod gerektirmeden '0' olarak çıkar.

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 · Kodu adım adım çalıştır

Kod İzleme: Yakın CALL, Uzak Dönüş

AX0005hIP0103hCS0700hSPFFFEhyığınFFFC····FFFE0000hSP

AX = 5. uzak prosedürü RETF ile bitiyor, ama çağrı yakın.

AX0005hIP010AhCS0700hSPFFFChyığınFFFC0106hSPFFFE0000h

Yakın CALL yalnızca IP'yi (0106h) iter: yığında 2 bayt.

AX000AhIP010ChCS0700hSPFFFChyığınFFFC0106hSPFFFE0000h

SHL AX, 1: AX = 000Ah.

AX000AhIP0106hCS0000hSP0000hyığınFFFC····FFFE····

RETF iki sözcük çeker: IP = 0106h ve CS olarak yığında bulduğu değer, burada DOS'un .COM için ittiği 0000h. SP = 0000h.

AX000AhIP0106hCS0000hSP0000hyığınFFFC····FFFE····

İşlemci 0000:0106h'ye atlar: burası kesme vektör tablosudur, program kodu değil. Program çöker; ADD AX, 1 hiç çalışmaz.

ORG  100h
MOV  AX, 5
CALL uzak        ; yakın CALL: yalnız IP
ADD  AX, 1
RET
uzak PROC
SHL  AX, 1
RETF             ; IP ve CS çeker!
uzak ENDP

Sık Yapılan Hatalar

Sık Yapılan Hatalar

RET'i unutmakİşlemci ENDP'yi görmez; prosedürden sonraki kod ya da veri yürütülür.
PUSH / POP dengesizliğiRET yanlış değeri IP'ye alır. Her PUSH'un bir POP'u olmalı ve ters sırada yapılmalı.
Prosedüre "düşmek"Prosedür ana kodun ortasına konup üzerinden atlanmazsa CALL edilmeden yürütülür; sonundaki RET programı bitirir.
CALL yerine JMPJMP dönüş adresi itmez; prosedürün RET'i yığındaki ilgisiz bir değere döner.
Ad uyuşmazlığıp1 PROC … p2 ENDP → assembler hatası.
Sonsuz özyinelemeKendini çağıran prosedürde durma koşulu yoksa her çağrı 2 bayt yer, yığın taşar.

Sık Yapılan Hatalar

Daha Fazla Prosedür Hatası

Erken RETPUSH'tan sonra koşullu bir RET, saklanan değeri dönüş adresi sanır. Bütün yollar POP'ların bulunduğu tek çıkış etiketine gider.
LEA yerine MOVMOV SI, dizi adresi değil dizinin ilk sözcüğünü yükler; prosedür ilgisiz bir adresten okur.
Gizlice bozulan DX16 bitlik MUL ve DIV DX'i de yazar. Açıklamasında “Bozar: DX” yazmayan prosedür çağıranın DX'ini siler.
Tür uyuşmazlığıYakın CALL + RETF ya da CALL FAR + RET: yığın 2 bayt kayar, dönüş yanlış adrese yapılır.
Bayrağı bozmakSonucu CF ile döndüren prosedürde STC/CLC'den sonra ADD, CMP gibi bir komut CF'yi değiştirir. POP ve RET bayraklara dokunmaz.
Taban durumu geç denetlemekÖzyinelemeli prosedürde durma denetimi CALL'dan sonra yapılırsa çağrılar hiç durmaz ve yığın taşar.

Özet

Özet

ProsedürBelirli bir görevi yapan, CALL ile çağrılıp RET ile dönen kod parçası.
PROC / ENDPDirektif; makine kodu üretmez. CALL ve RET ise işlemci komutudur.
CALLSonraki komutun adresini yığına iter (SP −= 2), IP ← hedef.
RETIP ← [SP], SP += 2. .COM'da son RET 0000h'ya → INT 20h.
ParametrelerYazmaç, bellek değişkeni, adres (SI/DI/BX) veya yığın: [BP+4], [BP+6]… ile okunur, RET n ile temizlenir.
GüvenlikYazmaçlar PUSH/POP ile korunur, yığın dengede tutulur, girdi/çıktı belgelenir.

Özet

Alıştırmalar

  1. 0120h adresindeki CALL p komutu 0100h'daki p'yi çağırıyor. Makine kodu nedir? Yığına ne itilir?
  2. m2 örneğinde ikinci CALL m2'den önce MOV AL, 3 eklenseydi son AX ne olurdu?
  3. SP = FFFEh iken art arda 3 CALL yapılıyor (hiç RET yok). SP kaç olur?
  4. CX'i bozan bir prosedürü LOOP içinde çağırmak neden hatalıdır? İki çözüm önerin.
  5. AL'deki küçük harfi büyük harfe çeviren buyuk prosedürünü yazın (girdi/çıktı AL).
  6. print_me'yi, yazdığı karakter sayısını CX'te döndürecek şekilde değiştirin.
İpuçları / cevaplar
  1. Sonraki adres 0123h; 0100h − 0123h = −23h = FFDDh → E8 DD FF. Yığına 0123h itilir.
  2. İlk çağrı AX = 2; sonra AL = 3 → AX = 3 × 2 = 0006h.
  3. Her CALL 2 bayt: FFFEh − 6 = FFF8h.
  4. LOOP sayacı bozulur, döngü yanlış sayıda döner. Prosedürde PUSH CX/POP CX ya da çağırmadan önce çağıranda saklama.
  5. cmp al,'a' / jb son / cmp al,'z' / ja son / sub al, 20h / son: ret
  6. Başta MOV CX, 0, her karakterde INC 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".

Son

Bölüm 8: Prosedürler
Sonraki bölüm: Bölüm 9: Makrolar · Terim sözlüğü
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.