Bölüm 7: Prosedürler

Mikroişlemciler
Prosedür kavramıPROC / ENDPCALL ve RET Yığın ve dönüş adresiParametre geçirmeYazmaç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 ve bellek değişkenleriyle 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 düzey 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.
Not: Kaynak sunumda CALL ve RET de "derleyici direktifi" başlığı altındaydı; aslında bunlar işlemcinin yürüttüğü komutlardır (CALL near: E8, RET: C3). Yalnızca PROC, ENDP ve END direktiftir.
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.

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.
Yığın (SS)FFF8hFFFAhFFFChFFFEh0000hSPyukarı: düşük adresler

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

0100hE8CALL0101h040102h000103hB8MOV AX,20104h020105h000106hC3RET0107hBBm1: MOV BX,50108h050109h00010AhC3RETrel16 = 0107h − 0103h = 0004h → 04 00 (küçük uçlu)
  • Ö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 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ı

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 düzey dillerin yöntemi (Bölüm 8).
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

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.

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ı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, 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

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

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

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

Prosedür mü, Makro mu?

Prosedür (CALL)Makro (Bölüm 8)
Kodbellekte bir kez bulunurher kullanıldığı yere kopyalanır
ÇalışmaCALL / RET ve yığın kullanırçağrı maliyeti yok, doğrudan yürütülür
Boyutküçük programçok kullanılırsa büyük program
HızCALL + RET ek süredaha hızlı
Parametreyazmaç, bellek, yığınassembler 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

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.
GüvenlikYazmaçları PUSH/POP ile koru, yığını dengede tut, girdi/çıktıyı belgele.

Ö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 sakla.
  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 7: Prosedürler
Sonraki bölüm: Bölüm 8: Yığın
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.