debug etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
debug etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

11 Ağustos 2019 Pazar

Disk Sistemi Nedir, Ne Değildir? #2: MBR


Merhaba. Önceki yazıda disk donanımına değinmiştim. Bu yazı biraz daha yazılım ağırlıklı olacak ama kesmeler ve GÇ portlarıyla ilgili bir yazı ne kadar donanımdan bağımsız olabilirse o kadar. Diskin en temel veri yapısından başlayalım.

Master Boot Record (MBR)
MBR, sabit disklerin ilk sektöründe bulunan, disk bölümlerini (partition) tutan ve bir miktar da kod içeren bir yapı. Bilgisayar açılırken BIOS devreye girer ve işi bittiğinde CMOS'taki boot sequence ayarlarına göre depolama cihazlarının hangisi erişilebilirse onun ilk sektörünü belleğe okur. 1980'lerden beri bu süreç pek değişmedi. Boot edilen ortam disket sürücüyse, okunan boot sektör; sabit diskse MBR'ydi. BIOS için boot sektörüyle MBR'nin farkı yoktur. BIOS, boot etmeye uygun ilk sürücünün birinci sektörünü belleğin 0000:7C00 adresine kopyalar ve işletim sisteminin yüklenmesi için buraya bir far jmp yapar. 1980'lerden bahsettiğimden ve gerçel mod (real mode) adresleme kullandığımdan açıktır ki henüz korumalı modla ilgimiz yok.

MBR'de bulunan kod, hangi disk bölümünün boot etmeye uygun olduğunu saptar, bu bölümün ilk sektörünü alıp belleğe yükler ve oraya dallanır. MBR, disk bölümlerini bir tabloda tutar.

Bu yazıda MBR'yi okumak için Windows'ta HxD adlı editörü kullandım. HxD'yi yönetici olarak çalıştırıp "Open Disk" (Ctrl + Shift + D) dedim ve fiziksel disklerden birini seçtim. "Open as Readonly" olmasını tavsiye ederim.

Linux kullanıcıları daha şanslı çünkü aşağıdaki komut yeterli:

sudo dd if=/dev/sda count=1 bs=512 | hexdump -C -v

Assembly öğrenmeye çalışırken 3-4 ay uğraşıp MBR'yi görüntüleyebilmek benim için büyük bir başarı gibi gelmişti. Sene bindokuzyüz...


HxD'den aldığım ekran görüntüsü yukarıda. Görüntüyü ben renklendirdim. Kırmızı alan kod, mavi alan disk bölümleme tablosu ve yeşil alan MBR imzası. Sektör doğru olsa bile BIOS 0x55 0xAA'yı görmeden boot etmez. Peki neden 0x55 0xAA? Çünkü (0101 0101 1010 1010)2.

Benzer çıktı linux'ta şöyle:


Hem Windows hem Linux'ta GRUB olduğundan yukarıdaki çıktılar GRUB MBR kodu içeriyor. Kodu incelemeden önce, mavi işaretlediğim bölümleme tablosuna bakalım. CHS ve LBA'ya önceki yazıda değindim. Önceki yazıda debug.exe ile yaptığım örnekte de kod ve imza kolayca görülebiliyor.


Disk Bölümleme Tablosu
Bölümleme tablosu, MBR'nin 01BEh offsetinden başlayan 4x16 byte'lık alandır. Her birincil (primary) disk bölümü 16 byte uzunlukta bir kayıtla ifade edilir. Genişletilmiş (extended) disk bölümlerini şimdilik konu dışında tutarsam, bir önceki ifadeye göre 4'ten fazla (birincil) disk bölümü oluşturulamaz. Aşağıdaki tabloda kayıtların yapısı görülüyor:

OffsetBüyüklükAçıklama
0x00byteBootable Flag
0x013 byteBaşlangıç CHS adresi
0x04byteBölümün türü
0x053 byteBitiş CHS adresi
0x08dwordBaşlangıç LBA adresi
0x0CdwordBölümdeki sektör sayısı

Bootable Flag, açılışta o disk bölümünden boot edileceğini gösteriyor. Her MBR'de bir ve yalnız bir tane bulunması gerekir. Bu değer 00h ise bölüm boot edilebilir değildir. 80h, boot edilebilir olduğunu gösterir ancak bazı MBR kodları yalnız yedinci bite bakar. [01h, 7Fh] arası değerler geçersizdir.

CHS adresi kısmını çok kısa atlıyorum.

Bölüm türü alanı, bölümdeki dosya sistemini belirtse de bunun her zaman önemi yoktur. İşletim sistemi ve yükleyiciler bunu çoğunlukla dikkate almaz. Bu değerin bir standardı yoktur. 80'lerde Microsoft ve IBM tarafından kullanılan bazı değerler vardı. minix ve linux yazılırken o zamana kadar kullanılmamış değerler rastgele seçildi ve sonradan de facto standartlaştı. Bununla ilgili ayrıntılı bilgi ve liste Wikipedia'da var: https://en.wikipedia.org/wiki/Partition_type

Başlangıç LBA adresi, başlangıç CHS adresinin LBA'ya çevrilmesiyle elde edilen değerdir. Bölümdeki sektör sayısının görevi adından da anlaşılabilir. Bu değer 512 byte'la çarpılınca disk bölümünün büyüklüğü bulunur ama bu biçimlendirilebilir (formatlanabilir) alan değil ham alandır. İkisinin farkına boot sektörüne geçince değineceğim.

CHS adresleri karışık bir biçimde tutulur. Üç byte'lık alanın ilk byte'ı kafa numarasıdır. İkinci byte'ın düşük anlamlı altı biti sektör numarasını tutar. Üçüncü byte silindir numarasının düşük anlamlı sekiz bitidir. İkinci byte'ın yüksek anlamlı iki biti de silindir numarasının yüksek anlamlı iki bitini içerir. Aşağıda bunu görselleştirmeye çalıştım:

1. Byte 2. Byte 3. Byte
H7H6 H5H4 H3H2 H1H0 C9C8 S5S4 S3S2 S1S0 C7C6 C5C4 C3C2 C1C0

Peki bu tanıdık geliyor mu? Int 13h! 1. Byte DH'ya, 2. Byte CL'ye ve 3. Byte CH'ye. Birbirine mükemmel bir şekilde uyuyor.

Önceki yazıda CHS, 8 GB üstü disklerde yetersiz yazmıştım:
Yaygara yapmadan daha iyimser bir yaklaşımla; MBR kodunun, disk bölümünün bitiş CHS adresiyle ilgilenmediğini, boot sektörünü yüklemek için koda yalnız başlangıç CHS adresinin yeteceğini varsayarak, yalnızca diskin başındaki 8 GB içinde boot edilebilir bölümler oluşturulabilir sonucuna varılır. CHS'yle 8 GB'ın ötesindeki bölümlerden boot edilemez!

Peki 8 GB'ın ötesinde bölüm nasıl oluşturulacak? Her kayıtta 32-bit'lik bir başlangıç LBA adresi var. Eğer bootloader CHS yerine LBA'ya bakarsa; LBA, 232 * 512 byte = 2 TB desteklediğinden; diskin başındaki 2 TB alanda bulunan bölümlerden boot edilebilir. Üstelik sektör sayısı da 32 bit olduğundan 2 TB'lik bir disk bölümü bu sınırın sonuna yerleştirilerek teoride 4 TB'ye kadar olan disklerde MBR kullanılabilir. Teoride diyorum çünkü eski MBR kodlarının bu uç değerlerle sorunsuz çalışacağının garantisi yok.

Önceki yazıda bulunan debug.exe'li ekran görüntüsünü aşağıda tekrar ekledim. Buradaki byte'ları anlamaya çalışalım.


80 | 01 01 00 | 06 | 0F 3F FF | 00 00 00 C1 | EF 03 00 00
00 | 00 41 00 | 06 | 0F 7F FF | 00 F0 03 00 | 00 F0 03 00

Yukarıda iki disk bölümü var. Birinci bölüm boot edilebilir. Her iki bölüm de FAT16 olarak işaretlenmiş. Birinci bölümün CHS adresi (0, 1, 1) - (255, 15, 63), 63. sektörden başlıyor ve 257 985 sektör uzunlukta. İkinci bölümün CHS adresi (256, 0, 1) - (511, 15, 63), 258 048. sektörden başlıyor ve 258 048 sektör uzunlukta.

Bu tablo neden önemli? Kendi işletim sistemimi yazıyor olsam hiçbir önemi yok. Bunu dikkate almayıp kendim daha iyi bir tablo oluşturacağım diyebilirim ama diğer tüm işletim sistemi üreticileri bu yapı üzerinde anlaştığından kendi tablo yapımı kullanırsam diğer işletim sistemleriyle uyumlu çalışamam.

Bölümleme Tablosuna da LBA'sına da Lanet Olsun...
Evet, hala kod yazmaya geçemedim ama bu bölümde artık örneklere başlayacağım. Önce deneme için iki sanal makina oluşturdum. İlkine önceki yazıdaki örnek için VirtualBox'ta DOS6.22 kurmuştum. Bunun için 32 MB RAM ve 500 MB sabit disk yeterli. Bu makinada bir tane ağ kartı var, bunu host-only yaptım (Settings -> Network -> Enable Network Adapter, Attached to: Host-only Adapter). DOS disket imajları internetten bulunabilir. Bir de Damn Small Linux (DSL) iso'su kullandım. Ubuntu, Knoppix vb. live dağıtımlar 32 MB RAM'le çalışmıyor. DSL 50MB'lık bir iso'ya sahip. Artık geliştirilmediğinden buradan veya şuradan indirilebilir. DSL'in en yakın alternatifi TinyCore Linux, iso olarak 14MB ama en az 64 MB RAM'le çalışıyor.

Makinayı DOS disketiyle açtıktan sonra kurulum, bir disk bölümü oluşturuyor ama ben kurulumdan çıkıp fdisk'le elle bir bölüm oluşturdum. fdisk'te:

1 - 1 - N - 25% - Esc


Bunu yaptıktan sonra ekrandaki uyarı hiçbir bölümün aktif olmadığını belirtiyor. Yani şu anda hiçbir bölüm kaydının ilk byte'ı 80h değil. 2 - 1 - Esc ile bu uyarı kayboldu. Ardından tekrar bir birincil bölüm oluşturmak istediğimde hata aldım. Maalesef fdisk birden fazla birincil bölüm oluşturamıyor. Genişletilmiş bölüm oluşturulabilir ama buna yazının sonunda değineceğim. Esc'e basıp ana menüden çıktım ve disketi çıkarıp makinayı DSL ile açtım. Bu arada, disk bölümleri oluşturulduktan veya silindikten sonra, DOS yeniden başlatılmazsa yeni disk bölümlerini göremez.

DSL'in terminalinde sudo fdisk -l diyip oluşturduğum bölümü gördüm. Bir disk bölümü daha ekledim:

sudo fdisk /dev/hda
n - p - 2 - (Enter) - 512
t - 2 - 6
p
w

Bilgisayarı tekrar DOS disketiyle açınca C:'yi ve D:'yi bulup formatladı. Standart bir Microsoft kurulumu, ardarda Enter'a basarak ve arada disketleri değiştirerek tamamlandı. Bu arada, son disketi sürücüde unutup "Non-system disk error" alanlara uyarı: Eskiden MBR/boot sektör virüsü bulaşmış disketlerle bilgisayarı açınca, boot sırasında virüs kendini belleğe yazıp aktifleşir, bu hatayı gösterir ve kullanıcının bir tuşa basmasını beklerdi. Bundan korunmak için bir tuşa basmak yerine bilgisayarı yeniden başlatmak gerekirdi. Virüsü temizlemek için de fdisk /mbr komutuyla sabit diskin MBR kodu yeniden yazılırdı.

DOS kurulduktan sonra debug.exe ile MBR'ye tekrar baktım:

Makinayı bir daha DSL'le açtım. Ekranın sağında makinanın IP'si görünmeli. IP'yi, hosttan, sanal makinaya dosya kopyalarken kullanacağım. Benim hostum da linux olduğundan scp ile dosya kopyalayabiliyorum. Windows kullanıcıları sanal makinada DSL -> System -> Daemons -> ssh -> start ile sshd'yi başlatıp WinSCP kullanmak zorunda (Cygwin de iş görebilir).

Kopyalayacağım dosya Norton Utilities paketindeki disk editor. DISKEDIT.EXE adında iki farklı sürümü var. Biri Norton Utilities v8.0 (1994) paketinden diğeri de Norton Utilities 2000 paketinden. İlk sürüm CHS'yi desteklerken, ikinci sürümün dosyası daha büyük ve LBA'yı destekliyor. Disk editörle çalışmak çok pratik, bu nedenle bu dosyayı internetten bulabilenler kendilerini şanslı saymalı.

Disk editörü kopyalayıp bilgisayarı yeniden başlattım ve DOS'u açtım. Editörü başlatıp Alt+D ile sürücüleri görüntüledim. Tür (type) olarak "Physical Disk" seçip sabit diski açtığımda sektörleri HxD gibi gösterdi. Ardından "View as Partition Table" (F6) seçtiğimde yukarıda elle hesaplamak zorunda kaldığım bütün bilgiler göründü. Disk editör daha sonra boot sektörü ve FAT için de işe yarayacak. Bu yüzden diskte durmasında yarar var.

Norton Utilities'in iso'su olanların, DOS'ta CD sürücüyü tanıtmaları gerekiyor. Okurlardan DOS kullanmamış olanlar, CD sürücü tanıtmanın gerekliliğine hayret edebilir ama o zaman öyleydi. Bu işlemi ayrı bir yazıda anlatıp, buraya bağlantısını vereceğim.

Ekran görüntüsüne göre ikinci disk bölümü 511. silindirin sonunda bitiyor. Alt+I'ye basıp çıkan menüde "Drive Info"yu seçince diskin özelliklerini de gösterdi (yanda).

Drive Number: 80 Hex (Birinci fiziksel disk)
Int 13x: Yes (BIOS, LBA destekliyor)
Sides: 16 (Kafalar)
Tracks: 1015 (Silindirler)
Sectors/track: 63 (Silindir başına sektör sayısı)
Total Sectors: 1 024 000 (LBA adresi)

Bu bilgiler Int 13h/08 alt fonksiyonuyla da alınabilir:

Silindir sayısı 3F6h, sektör sayısı 3Fh, kafa sayısı 0Fh. Yazmaçlardaki değerlerin anlamları Ralf Brown'un belgelerinde var (Int13h/08). Yukarıda DL=02 oldu çünkü test için makinaya bir disk daha takmıştım.

Şimdi, diskin rastgele bir sektörüne "denizyildizi" yazacağım (tamamen rastgele bir kelime). Örn. CHS 600,0,1'e yazayım. Bunun LBA karşılığı 604 800 (CHS'den LBA'ya dönüşüm formülü bir önceki yazıda var). Disk editörü açıp Alt+P'ye bastım ve çıkan diyalogda LBA adresini yazdım. Diske yazabilmek için Tools -> Configuration altında Read-Only'nin yanındaki işareti kaldırıp OK'a bastım. Tab'la sağa geçince heksadesimal yerine metin yazılıyor. Alt+X'e ve sonra değişiklikler diske yazılsın mı diyalogunda "yaz"a bastım.

Aynısını debug.exe'yle yapmak için ufak bir kod yazdım ve Int13h/43h altfonsiyonunu kullandım. 42h altfonksiyonuyla birlikte ikisi Int13h genişlemeleri olarak geçiyor.

mov cx,000C      ; 12 karakter
mov si,0120      ; 0120h'dan
mov di,0200      ; 0200h'ya
repz movsb       ; kopyala
xor ax,ax
mov cl,FA        ; 0FAh word daha
repz stosw       ; kopyala
inc si           ; sonraki buffer DAP
push ds
pop ax           ; DS'i al DAP'daki alana
mov word ptr [si+06],ax ; yaz
mov ax,4300
mov dl,80
int 13           ; int 13h'u cagir
int 3
db 00
db 'denizyildizi',0
db 10 00 01 00 00 02 00 00 80 3A 09 00 00 00 00 00

Son satır DAP (Disk Address Packet) adlı veri yapısı. Sırasıyla, 10h DAP uzunluğu, 00 ayrılmış, 00 01 okunacak/yazılacak sektör sayısı, (0000:0200)16 kullanılacak buffer. Segmentin 0000 olması yanıltmasın, kod oraya kendi segmentini yazacak. (09 3A 80)16 da LBA adresi (qword). Bunu g komutuyla çalıştırdım ve kod belirttiğim sektöre veriyi yazdı. Yazdığım veriyi yine LBA'yla ama denetleyicinin portlarını kullanarak okuyacağım. Önceki yazıda kullandığım kodu küçük farklarla yeniden yazdım (kolaylık için yine ofsetleri ekledim):

0100    mov ax,0001
0103    mov dx,01F2
0106    out dx,al   ; Sektor sayisi = 1
0107    inc dx      ; dx = 01F3
0108    mov al,80
010A    out dx,al   ; LBA adres1 = 80h
010B    inc dx      ; dx = 01F4
010C    mov al,3A
010E    out dx,al   ; LBA adres2 = 03A
010F    inc dx      ; dx = 01F5
0110    mov al,09
0112    out dx,al   ; LBA adres3 = 09h
0113    inc dx      ; dx = 01F6
0114    mov al,E0   ; LBA modu, master disk
0116    out dx,al   ; LBA adres4 = 0h
0117    inc dx      ; dx = 1F7
0118    mov al,20
011A    out dx,al   ; 20h ATA Sektor okuma komutu
011B    in al,dx    ; Durum yazmacini oku
011C    test al,58  ; 0101 1000: Drive ready | Seek complete | Buffer ready
011E    jz 011B     ; Dataya erisene kadar bekle
0120    mov dx,01F0
0123    mov bx,0200
0126    in ax,dx    ; Datayi oku (word)
0127    mov [bx],ax ; Bellege kopyala
0129    inc bx
012A    inc bx
012B    cmp bx,0400 ; 0200h byte
012F    jnz 0126
0131    int 3       ; Breakpoint

Tekrar d 0200 komutuyla kontrol ettiğimde yazdığım veriyi doğru olarak okudum.


Mantıksal Disk Bölümleri
Bu konuya başlamadan önce VirtualBox'ta 32 MB RAM ve 1GB diskli ikinci makinayı oluşturdum (aynı şekilde host only adapter ile). FreeDOS.org'dan CD iso'sunu indirip makinaya taktım. Legacy ile CD arasında yalnız boot modunda fark var, içerikte bir fark yok. Elle disk bölümleme için "Install to harddisk" dedim, sonra dili seçip "No - Return to DOS"u seçtim ve fdisk'i açtım. FreeDOS'un fdisk'i DOS6.22'den farklı olarak FAT32 desteğiyle geliyor. Fat32, 1996'da ortaya çıktı ve Win95 OSR2 ile kullanıma girdi. Ayrıntısına sonraki yazılarda gireceğim. Bu kısımla ilgili not: fdisk, bölüm oluşturulurken diskin LBA desteğini test eder. LBA desteklemeyen disklerde bölüm türü byte'ı FAT16 ve FAT32 için sırasıyla 06h ve 0Bh olurken, destekleyenlerde 07h ve 0Ch olur.

Yukarıdaki görselde görülen FAT32 desteği açılsın mı sorusu aslında 2GB'den büyük diskler için gerekli, "N" diyip geçtim. Bir tane 340MB birincil bölümünü "1 - 1 - N - 340 - (Enter) - (Esc)" kombinasyonuyla oluşturdum ve ana menüdeki (2) seçeneğiyle, bölümü aktifleştirdim. Öncekinden farklı olarak "1 - 2 - (Enter) - (Esc)" tuş kombinasyonuyla uzatılmış veya mantıksal (extended veya logical) bir bölüm oluşturdum. fdisk, uzatılmış bölümde mantıksal sürücü oluşturmak isteyip istemediğimi soracak. Buna 340 MB verip Enter'a bastım. Geri kalan alanda son bir disk oluşturmak için tekrar Enter'a bastım. Burada bir hatadan ötürü fdisk hala 2MB alan var zannediyor ve Enter'a basılırsa bölümleme tablosu hatalı yazılıyor. Bu nedenle Esc'e bastım.


fdisk'in dördüncü seçeneğine girdiğimde diskler yukarıdaki gibiydi. fdisk'ten çıkıp bilgisayarı yeniden başlattım ve kuruluma devam ettim. "Do you want to format your drive?"a Yes dedim ve "Full installation"ı seçip kurulumu başlattım. Bitince de CD'yi çıkarıp makinayı yeniden başlattım.

Şu anda FreeDOS'ta 3 sabit disk sürücüsü (C:, D: ve E:) olmasına rağmen diskeditörle veya debug.exe'yle MBR'de iki disk bölümü görüyorum. O halde üçüncü nerede? (Diskeditör varsa buna da kopyalayın)

Diskeditör'le baktığımda diskte 520 silindir görünüyordü. (bu bölümde eski sürüm diskeditörü kullandım, bir nedeni yok). İlk bölüm 173. silindirde bitiyor. O halde birincil bölüm doğru. İkinci bölüm EXTEND olarak görünüyor. İkinci bölümün olduğu CHS (173,0,1) adresinde başka bir bölümleme tablosu daha var. EXTEND seçiliyken Enter'a basınca, o sektör bölümleme tablosu biçiminde gösterilecek.

Bu bölümleme tablosu öncekine benziyor ama aynısı değil. Sağ alt köşede (173,0,1) görüyorum yani MBR'da değilim. F2'yle Hex görünümüne geçtiğimde bu sektörde kod yok ve imza var! Bu tablodaki ilk girdi (173,1,1)'den 346. silindire kadar bir bölüm ve ikinci girdi onun bittiği yerde tekrar bir uzatılmış (EXTEND) bölüm. Bu 'Extend'i seçip Enter'a basınca (346,0,1) sektöründe kodu bulunmayan bir bölümleme tablosu daha görünüyor ve tablodaki bölüm, (346,1,1)'den diskin sonuna kadar. Anlaşılacağı üzere uzatılmış bölüm, içindeki bölümleri bağlı listede tutan bir yapı.

Bildiğim kadarıyla, uzatılmış bölümlerin boot edilebilir olmaması için teoride bir engel yok ama MBR'deki kodun, ilk tabloda aktif bir bölüm yoksa uzatılmış bölümler içinde aktif olanı araması gerek. Bu da 512 byte'la sınırlı olması gereken kodun uzaması anlamına geliyor. Standardı belirleyen mi istememiş yoksa başka bir neden mi var, emin değilim. Bu konuda Wikipedia'nın Extended Boot Record maddesinde başka örnekler bulunabilir.


MBR Kodu Nasıl Çalışır?
MBR kodunu incelemek için şimdiye kadar kurduğum iki makinayı da DSL ile açıp aşağıdaki komutla MBR'lerini kaydettim:

dd if=/dev/hda of=mbr.bin count=1 bs=512

Güncel linux dağıtımı olan bir makinada aynı komut;

dd if=/dev/sda of=mbr.bin count=1 bs=512

Sonra linux bir makinada;
objdump -M intel -D -b binary -m i8086 --adjust-vma=0x7C00 mbr.bin > mbr.asm
komutuyla bu çıktıları disassemble ettim.

Yazıyı uzatmamak için assembly kodunu satır satır incelemek yerine DOS6.22 ve FreeDOS'un çıktılarını açıklama satırlarıyla Google Drive'a yükledim. Genel olarak yapılan ikisinde de aynı: Stack ayarlamasından sonra sektör kendini DOS'ta 0:0600h ve FreeDOS'ta 1FE0h:7C00h adresine kopyalıyor. Çünkü sonradan yüklenecek boot sektör kodunun da 7C00h adresine yerleşmesi gerek. BIOS için boot sektör ve MBR kodlarının farkının olmadığını, BIOS'un görevinin boot edilecek ortamın ilk sektörünü 0:7C00h'a okuyup, sonundaki 55h AAh byte'larını kontrol etmek ve bu adrese dallanmak olduğunu yazmıştım. Eğer boot edilen ortam disketse MBR bulunmadığından 0:7C00h'a boot sektör okunur.

DOS'ta bir ve yalnız bir 80h girdisi olup olmadığı kontrol edilirken, FreeDOS'ta en yüksek değerli biti 1 olan ilk girdiye ait boot sektör okunuyor. Sektör okunamadıysa "read error" veriyor. Bundan başka herhangi bir kontrol yok.

Disk okuma konusunda DOS, bölümleme tablosundan CHS değerlerini aldığı sektörü, DI'de tuttuğu bir sayacı kullanarak beş kere okumaya çalışıyor. Beşinci sefer okunamazsa "Error loading OS" hatasını veriyor. DOS'ta LBA desteği bulunmazken, FreeDOS önce LBA desteğini kontrol ediyor. Destekleniyorsa 7CCDh'da tuttuğu DAP'la sektörü okuyor. Desteklenmiyorsa DOS'taki kodun hemen hemen aynısı çalışıyor. Sektör okunduktan sonra imza kontrolü yapılıyor (FreeDOS'ta bu, fonksiyonun dönüşünde) ve program 0:7C00h adresine atlıyor.

Aslında bu konunun devamında GRUB, GPT ve UEFI'yi ele almak gerekiyor ama UEFI benim de çok ayrıntılı bilmediğim bir teknoloji olduğundan bunları sonraki bir yazıda toparlayacağım. Bu yazıda anlattıklarım her ne kadar 1980'lerin teknolojisi olsa da, UEFI kullanılmadığında bugün de çalıştırılan kod, ufak farklar dışında aynı. Buraya kadar MBR'i bitirmiş olduk. Sonraki yazıda MBR'den sonraki adım olan boot sektörünü ele alacağım.

6 Mayıs 2019 Pazartesi

Disk Sistemi Nedir, Ne Değildir? #1: Diskler


Merhaba. Bir süredir disk sistemi ve dosya yerleşim tablosuyla (FAT) ilgili yazmayı düşünüyordum ama boot sektörüne değinmeden FAT'ten bahsetmek, MBR'ye ve daha da önemlisi donanıma değinmeden de boot sektöründen konuşmak anlamlı gelmediği için konuyu en baştan ele almaya karar verdim. En baştan demişken BIOS'un nerede bitip MBR'nin nerede başladığından da bahsedeceğim ama BIOS'u derinlemesine ele almayacağım (belki başka yazıda).


Diski Adreslemek: CHS, LBA ve Int 13h
Diskte Silindir Kafalar
ve Sektörler
CHS, Cylinder, Head ve Sector kelimelerinin baş harflerinden oluşan bir kısaltma. Çok eski (1970'lerden) disklerle birlikte standartlaşmış ve yapı farklılaşsa da geriye doğru uyumluluktan ötürü kullanılmaya devam edilmiş. Yanda dönen bir diskin iç yapısı görülüyor. Bu disk 4 plaktan (platter) oluşmuş ve erişim için 8 kafa (head) var. Her plakta iç içe halkalar var. Bunlar silindirler. Silindire "track" de deniyor ama ben silindiri kullanacağım. Son olarak, her silindir belli açıda dilimlere bölündüğünde, dilimlerin silindiri kestiği alanlara sektör deniyor. Bu sektörler 512 byte büyüklükte. Günümüzde 4KB'lik sektörler kullanılıyor ama disk denetleyici uyumluluk yüzünden işletim sistemine 512 byte raporluyor. Şimdilik 1980'lerde olduğumuzu varsayalım (keşke). CHS adreslemede 3 tane sayı gerekiyor. Dikkat edilmesi gereken silindir ve kafa numaraları sıfırdan başlarken sektör numaraları her zaman birden başlamalıdır.

Aşağıda daha açıklayıcı bulduğum bir görsel var:

https://superuser.com/questions/974581/chs-to-lba-mapping-disk-storage
Cluster (küme) birkaç sektörden oluşan bir yapı. Dosya sisteminin en küçük birimine deniyor. Bu yazıda değinmeyeceğim çünkü henüz donanımı tanımlıyorum, dosya sistemine gelemedim. Bazı kaynaklarda sektör yerine kullanıldığını gördüm ancak kesinlikle yanlış. Bir küme bir sektöre eşit olabilir (örn. disketlerde) ama zorunda değildir. 

5 MB'lık IBM disk
forklift'le taşınıyor.
Kaynak: thenextweb.com
CHS'nin basit ama kullanışsız bir mantığı var. Dikkatli okuyucu dış silindirlerdeki sektörlerin içtekilere göre büyük olacağını fark etmiştir. CHS, dış sektörlerin verimsiz kullanılmasına yol açıyor. 1970'lerde kullanılan bir kaç on MB'lık ve bir kaç onbin dolar fiyatlı disklerde yapı böyleymiş ve bunlara CAV (constant angular velocity - sabit açısal hız) diskler deniyor. Açısal hız sabit olduğundan kafa her sektör üzerinden aynı sürede geçiyor. Yeri gelmişken, CD ve DVD'lerin CLA (constant linear velocity - sabit doğrusal hız) olduklarını belirteyim. CD'yi okumaya başladığında yavaş döner sona doğru dış sektörlerde hızlanırdı. 1990'larda (hatta 80'lerin sonunda) disklerde zone bit recording teknolojisi geliştiriliyor. ZBR'de geniş dış silindirler fazla sayıda sektöre bölünmüşken iç silindirlerde daha az sektör var. Geriye doğru uyumluluk için denetleyici işletim sisteminden CHS adreslerini alıyor ve arka planda CHS'yi fiziksel sektöre dönüştüren bir formül uyguluyor.

Bu arada hızlıca bir flashback'le günümüze gelelim. SSD'lerde artık ne CHS ne de CAV/ZBR'den söz edilebilir. Dolayısıyla CHS'nin modasının ne kadar geçtiği anlaşılabilir.

LBA adreslemede yalnız doğrusal sektör numarası veriliyor. Örn. 1.44MB'lik High Density (HD) disketlerde 18 silindir, 2 kafa ve 80 sektör vardı (Double Density'lerde (DD) 9 silindir). Son sektörün CHS adresi (17, 1, 80) iken LBA adresi 2879'dur (18 * 2 * 80 = 2880, LBA'de ilk sektör sıfır).

c, silindir; h, kafa ve s, sektör numarası olsun. Nhead, diskteki kafa sayısı ve NSPC bir silindirdeki sektör sayısı olmak üzere CHS'den LBA'ya dönüşüm aşağıdaki formülle yapılır:

LBA(c, h, s) = (c * Nhead + h) * NSPC + (s - 1)    [ 1 ]

LBA'nın bir avantajı aşağıdaki gibi karmaşık yapıda sektörleri olan disklere izin vermesi ama tek avantajı bu değil.

Kaynak: https://venam.nixers.net/blog/unix/2017/11/05/unix-filesystem.html

Int 13h, BIOS'un disk erişimi için sunduğu bir kesme servis rutini (ISR). Bu ISR, 80'lerde CHS kullanıyordu ve büyüyen diskler karşısında zamanla yetersiz kalmaya başlamıştı. Int 13h'ün bir çok fonksiyonu var ama bu yazıda ben 02 ve 03'e yoğunlaşacağım. 02, diskten belleğe okumaya, 03 bellekten diske yazmaya yarıyor. Kesme çağırılmadan önce giriş değerleri aşağıdaki gibi olmalı:

AH = 02h/03h (okuma/yazma)
AL = Okunacak/yazılacak sektör sayısı ( >0 )
CH = Silindir numarasının düşük 8-biti
CL = Sektör numarası 1-63 (bit 0 .. 5) +
Silindir numarasının yüksek 2-biti (bit 6 .. 7)
DH = Kafa numarası
DL = Sürücü numarası: Sabit diskler için 7. bit 1 olmalı.
Örn. Birinci disket sürücü: 00h, ikinci disket sürücü: 01h
Birinci sabit disk: 80h, ikinci sabit disk: 81h
ES:BX: Okunacak yazılacak veri için gösterici


Int 13h için bir örnek hazırladım. debug.exe, Windows XP'de var ama o bile bu kadar düşük seviye bir işleme izin vermiyor. Bunun için bir sanal makinaya DOS6.22 kurdum (sanırım zamanında İTÜ yazılım sunucusundan indirmiştim). Kurulumu sonraki yazıda ele alacağım. Şimdilik yalnız ekran görüntülerini ekledim:

debug.exe ile disk okumak
Yukarıda diskin ilk sektörünü okudum. Okunan byte'ların anlamına sonraki yazıda değineceğim. Bu arada yukarıda, sektörün başı ve sonu olmak üzere iki ekran görüntüsü var.

Int 13h'ü kullanmak istemeseydim, erişimi IDE denetleyicisinin portlarına erişerek yapmam gerekecekti. Yazının devamında bununla ilgili bir örnek vereceğim.

Şimdi bir hesap yapalım. CHS adresleme için toplam 3 byte ayrılmış: CH/CL/DH. 8+6+10=24 bit. 512 byte * 224 = 8 GB. Üstelik sektör numarası 0 olamadığından aslında bundan daha az: 218 * 63 * 512 byte = 7.875 GB. Yani CHS, 8 GB'dan büyük diskleri adreslemede yetersiz! LBA'nın adresleme konusunda da avantajı var ama bunlara girmeden önce ATA standartlarına bakmamız gerek.


(Paralel) ATA Standardı
Yukarıda disk erişimine BIOS tarafından baktık. O tarafta işlerin karışık olması yetmezmiş gibi donanım tarafı da karışık. 1986'da Western Digital ilk IDE/ATA standardını duyurduğunda adresleme için 22-bit ayrılmıştı: yanlış bilmiyorsam 10-bit silindir, 4-bit kafa ve 8-bit sektör. 1994'te EIDE/ATA-2 standardında silindir 16-bit oldu ve adresleme 28-bit'e çıktı. Üstelik CHS'nin yanısıra 28-bit LBA adresler de destekleniyordu. Ancak IBM, BIOS kesmeleri ve diğer (MBR) standartları oluşturmuştu ve buna göre (örn. int 13h) silindir 10-bit, kafa 8-bit ve sektör 6-bit'di. Ortak bölenler alındığında CHS, 10+4+6 = 20-bit ve sektör başına 512 byte'la 512 MB'ye kadar olan diskleri destekleyebiliyordu. Aslında sektör numaraları da birden başladığından bundan daha da az.*

Standartları değiştirmek geriye doğru uyumluluk nedeniyle imkansızdı. Neyse ki o yıllarda LBA'nın temelleri atılıyordu ve IBM'in standardındaki bir saçmalık bu soruna geçici bir çözüm sağladı: IBM disk kafalarına 8-bit ayırmıştı ama 3.5 inç form faktörlü 1 inç yüksekliğindeki sabit diske 128 plak veya 256 kafanın nasıl sığdırılabileceğini düşünmemişti. Bunun pratikte imkansız olması sayesinde 512 MB'den büyük disklerin silindir sayısını 10-bit'le sınırlamak için işletim sistemine diskte varolandan fazla sayıda kafa raporlanıyordu. Int 13h kodunda bu sayılar [1] formülünde yerine koyulup LBA'ya dönüştürülerek erişiliyordu, elbette BIOS'ta da LBA'nın aktifleştirilmesi koşuluyla.**

*: Bu paragraf için ayrıntılı bilgi:
https://en.wikipedia.org/wiki/Logical_block_addressing#Enhanced_BIOS
https://en.wikipedia.org/wiki/Parallel_ATA

**: Bu paragraf için ayrıntılı bilgi:
https://en.wikipedia.org/wiki/Logical_block_addressing#LBA-assisted_translation


Örnek:
2 GB'lik bir diskin fiziksel yapısında 8 kafa, 8320 silindir ve her silindirde 63 sektör olduğunu varsayalım. İşletim sistemi, int 13h'le disk parametrelerini sorguladığında dönecek değer 128 kafa, 520 silindir ve 63 sektör olacak. Çarpım değişmedi. Aynı işletim sistemi, diske GÇ portları üzerinden doğrudan erişmek isteseydi 16'dan büyük kafa numaralarını porta nasıl göndereceğini bilemeyecekti.

Bu diskte CHS 100,17,17 adresine erişilmek istendiğini düşünelim. [1]'deki formülde değerler yerine konulduğunda:

LBA = (100 * 128 + 17) * 63 + (17 - 1) = 807 487 sektörüne erişilecek.

1990'larda ATA-2 standardı 28-bit'le 128GB'a kadar diskleri destekleyebiliyor ama IBM'in mevcut arabirimi LBA dönüşümüyle 8 GB'a kadar diskleri destekleyebiliyordu (IBM, MBR'de LBA için 32-bit ayırmıştı. Gelecek yazıda değineceğim). 1990'ların ortasında IBM ve Microsoft, 42h ve 43h gibi fonksiyonlarla int 13h'ün yeteneklerini genişlettiler. Diğer yandan MBR'deki LBA kaydı 32-bit olduğundan ATA standardının da ilerlemek için alanı vardı. 2003'te ATA-6 standardıyla disklerde fiziksel adresleme 48-bit'e çıktı ve aynı zamanda CHS adresleme tarihe karıştı.


BIOS Diske Nasıl Erişir? Disk Denetleyicisi
IDE/ATA disk denetleyicisinin portlarıyla ilgili dökümantasyon Ralf Brown Interrupt List'in D bölümünde ports.b dosyasında bulunabilir. Eskiden anakartlarda iki EIDE disk denetleyicisi oluyordu. Bunlara da master/slave olmak üzere ikişer disk bağlanabiliyordu. Denetleyicilerin taban (base) adresi sırayla 01F0h ve 0170h'ydi. Portlar şu şekilde:

Port | GÇ | Aciklama
-----+----+----------------------------------------------------
01F0 | RW | Veri yazmacı
01F1 | R- | Hata yazmacı++
01F1 | -W | (Write Precompensation Cylinder divided by 4)+
01F2 | RW | Sektör Sayısı
01F3 | RW | Sektör Numarası (CHS)
     |    | 0-7. adres bitleri (LBA)
01F4 | RW | Silindir numarası düşük anlamlı byte (CHS)
     |    | 15-8. adres bitleri (LBA)
01F5 | RW | Silindir numarası yüksek anlamlı byte (CHS)
     |    | 23-16. adres bitleri (LBA)
01F6 | RW | Sürücü ve kafa numarası
     |    | 27-24. adres bitleri (LBA)
01F7 | R- | Durum yazmacı++
01F7 | -W | Komut yazmacı++


+: Bu değerin yeni disklerde anlamı yok. Sıfır olabilir. Ne olduğunu açıklayacağım.
++: Bu yazmaçlardaki bitlerin anlamları için Ralf Brown Interrupt Listesi'ndeki tabloya bakınız.

01F6h göründüğünden biraz daha karışık. Geriye doğru uyumluluk nedeniyle 5 ve 7. bitler her zaman 1 olmak zorunda (bunlar sektörü 512 byte olmayan MFM disklerde kullanılıyordu). 6. bit sıfırsa adres CHS, birse LBA olarak işlem görür. 4. bit sıfırsa işlem master, birse slave diske aittir. 3-0. bitler adres bitleridir. Buraya kadarki bilgi aslında kodu yazmak için yeterli ama henüz elimizde kodu yazabileceğimiz bir yer yok. İsteyen okur bir DOS veya FreeDOS sanal makina kurup kendi deneyebilir.

Okumak kolay olsun diye satırın başına offset adreslerini ve solda açıklamaları yazdım. Kodu debug.exe'ye a (Assemble) komutuyla girmek gerekiyor.

0100    mov ax,0001
0103    mov dx,01F2
0106    out dx,al    ; Sektor sayisi = 1
0107    inc dx       ; dx = 01F3
0108    out dx,al    ; Sektor numarasi = 1
0109    inc dx       ; dx = 01F4
010A    dec ax       ; ax = 0000
010B    out dx,al    ; Silindir D = 0
010C    inc dx       ; dx = 01F5
010D    out dx,al    ; Silindir Y = 0
010E    inc dx       ; dx = 01F6
010F    mov al,A0    ; CHS modu, master disk
0111    out dx,al
0112    inc dx       ; dx = 1F7
0113    mov al,20
0115    out dx,al    ; 20h ATA Sektor okuma komutu
0116    in al,dx     ; Durum yazmacini oku
0117    test al,58   ; 0101 1000: Drive ready | Seek complete | Buffer ready
0119    jz 0116      ; Dataya erisene kadar bekle
011B    mov dx,01F0
011E    mov bx,0200
0121    in ax,dx     ; Datayi oku (word)
0122    mov [bx],ax  ; Bellege kopyala
0124    inc bx
0125    inc bx
0126    cmp bx,0400  ; 0200h byte
012A    jnz 0121
012C    int 3        ; Breakpoint


Kodu çalıştırmak için g (Go) komutunu verip, d (Dump) ile belleği kontrol ettiğimde sorunsuz (int 13h çıktısıyla aynı) okunduğunu görüyorum:


Bu kod debugger'da çalıştırmak için uygun. Sondaki int 3, int 20 ile değiştirildikten sonra aşağıdaki komutlarla kaydedilebilir:

rbx
0000
rcx
002E
nreadmbr.com
w

Kodun ekrana çıktı vermediği unutulmamalıdır. Yüklemek için dosya adı, ya debug.exe'ye parametre olarak verilir veya n komutuyla girilir ve l (Load) komutuyla yüklenir. LBA kodunu bir sonraki yazıya bırakıyorum.


Biraz Daha Teori
Write Precompensation Cylinder (WPC): Yazının başlarında ZBR olmayan CHS disklerde dış sektörlerin daha büyük alanı olduğunu yazdım. Bu disklerde üretici bir silindir belirler ve bundan dıştaki silindirlere yazarken yazma kodlamasını biraz değiştirmesi gerekir. Bu kodlama değişimine "write precompensation" adı verilir. Bu silindiri BIOS mu raporlar yoksa kullanıcı mı girer bilmiyorum. Böyle bir diskim olmadı.

Çok eski disklerde kafalar bir step motora bağlıydı. Step motorların sıcaklık hassasiyeti düşük olduğundan "voice coil motor" adında bir teknoloji kullanılmaya başlandı. Aşağıdaki video nasıl çalıştığına dair bir fikir verecektir:

Bu motorlar, uygulanan akım oranında kafayı sürüklüyor, akım kesildiğinde de bir yayla, kafa otomatik olarak boş bir alana taşınıyordu.

Landing Zone (LZ): Step motorlu disklerde bilgisayar kapatılmadan önce kafaların LZ adı verilen, içinde data olmayan bir silindirin üstüne taşınması yani "park" edilmesi gerekirdi. Kafaları park edilmemiş diskler hareket ettirilirse, kafa veri silindirleri üzerinde hareket ederek diski çizebilirdi. Int 13h'ün 19h altrutini bu park etme işlemini yapar. Günümüzde park etme gereksiz, dolayısıyla LZ de anlamsızdır.

Voice coil motorlar, step motorlardan daha az hassas oldukları için üreticiler şöyle bir çözüm buldular. Örn. 3 plakalı bir diskin bir yüzüne kafaların daha hassas yer belirlemesi için belli işaretler koydular. En üstteki kafa bu işaretlere göre yerini belirleyip gerekiyorsa geri beslemeyle konumunu düzeltiyordu. Böylelikle ilginç bir şekilde tek sayıda kafası olan diskler ortaya çıktı.

Kaynak: https://www.brainbell.com/tutors/A+/Hardware/_Actuator_Arms.htm

Donanımla şimdilik işim bitti. Sonraki yazıda artık MBR'den söz edeceğim.

6 Temmuz 2013 Cumartesi

Eğrisiyle Doğrusuyla Debug.exe


(Bu yazıyı daha önce yazdığım ve kısıtlı bir çevrede dağıttığım bir belgenin küçük bir kısmından derledim. Internet'te bu yazının bazı bölümlerini aynen içeren bir doküman bulursanız o benim olabilir.)

debug.exe, DOS zamanlarından beri herkeste bulunan; Windows'la birlikte hala dağıtılan (32 bit Windows7'de var.) bir araç. Peki ne işe yarar bu araç? Nasıl kullanılır?

Bu araç elbetteki koddaki bug'ları otomatik temizlemiyor. Adından anlaşılacağı üzere bu bir debugger. Yani programın çalışmasını mikroişlemci düzeyinde inceleyip varsa hataları görmek, tek tek komut komut çalıştırıp kısmen de olsa değişiklikler yapmak olanaklı. Windows için güncel debugger'ları takip edemedim ama 2000'lerin başında Win32Dasm ve SoftICE kullanırdık. Bunlara da yeri gelirse kısaca değinirim ancak baştan belirteyim debug.exe DOS1.0 zamanlarında kalma (1980'lerin başından) bir program. 80286 komut setini bile desteklemiyor. EAX, EDI gibi yazmaçlara erişilemiyor. Daha iyi işler yapabilmek için daha güçlü bir debugger'a gereksinimi olanların TASM paketi içerisindeki Turbo Debugger'ı yada FreeDOS'un debugger'ini kullanmaları daha mantıklı.

Komut satırında debug dediğiniz zaman debug'un kendi iptidai - ile belirtilen komut satırına düşülüyor. Debug'dan sonra "debug \WINDOWS\system32\format.com" şeklinde dosya adı da vermek mümkün hatta format.com'un komut satırından aldığı parametreler de dosya adından sonra eklenebilir. Debug.exe'nin komut satırına düştükten sonra kendi tek harfli komutlarını bilmek gerekiyor. Bu komutlar komut satırında ? yazarak ulaşabileceğiniz bir avuç komut. Şimdi bunları sırasıyla açıklayayım.

* a (Assemble): a komutu girildiği zaman debug size CS:0100 (CS: Code Segment. Kodun bellekteki adresini tutan bir yazmaç. Dolayısıyla siz a yazdığınızca CS'nin içeriği kaçsa o sayıyı gösterecek. Bunu işletim sistemi 640Kb'lik bellekteki boş alana göre ayarlar.) adresini gösterir ve sizden x86 assembly dili komutlarını girmenizi bekler. 0100h offset adresi de DOS'ta bir .com çalıştırılabilir dosya yüklendiğinde onun ilk komutunun bulunduğu yerdir. Segmentin 0000h ile 0100h arasında PSP (Program Segment Prefix) adı verilen başlık bulunur. Komut yazma modunda komut yazmadan enter'a basınca çıkılır. a'dan sonra herhangi bir 16 bitlik sayı girilirse komutun girilmesine o satırdan devam edilir.

-a0200
1381:0200 mov bx,b800
1381:0203


* c (Compare): c komutu iki bellek parçasını karşılaştırmak için kullanılır. Yalnızca farklı olan byte'lar gösterilir. İstenirse farklı segmentler de verilebilir. Aşağıdaki örnek 0000:0100h ile 0000:0110h arası parçayı DS:0400h'den itibaren karşılaştırır.

-c 0000:0100 0110 0400
0000:0100  BB  00  1381:0400

0000:0102  B8  00  1381:0402
0000:010A  01  00  1381:040A 
0000:010B  1A  00  1381:040B

* d (Dump): d komutu belleğin bir parçasını görüntüler. Tek parametreyle verilirse segmenti DS olarak seçer  ve 128 byte görüntüler. İki parametreyle verilirse verilen iki offset arasını gösterir. Parametresiz verildiğinde en son görüntülediği bellek parçasının ardışığı 128 byte'ı gösterir. Böylece ardarda verilen d komutlarıyla uzun bir bloğa parça parça bakmak olanaklıdır.

-d0110 011F
1383:0110  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................



* e (Enter): e komutuyla bir bellek bölgesine elle byte'lar iki türlü girilebilir:

Aşağıdaki komutla DS:0120h'den itibaren 13h, 16h, 19h ve 1Ch byte'ları sırasıyla yazılır.
-e 0120 13 16 19 1C

Aşağıdaki komutla ES:0200h'dan itibaren 13h, 16h, 19h ve 1Ch byte'ları tek tek yazılır. Sayılar girildikten sonra boşluk karakterine basmak gerekir:
-e ES:0200
1381:0200  00.13   00.16   00.19   00.1C

* f (Fill): f komutu bellek bölgesini verilen karakterlerle belli uzunlukta doldurur.

Aşağıdaki komutla 0100h offsetinden başlayarak 12h, 13h, 14h byte sırası 16 byte boyunca tekrarlanır:
-f 0100L10 12 13 14

* g (Go): g komutu, CS:IP ile gösterilen bellek adresinde hangi komutlar varsa koşulsuz olarak çalıştırır. Program sonlanıp yada kesilip çalışmayı debug.exe'ye geri bırakana kadar sürer. g'den sonra bir bellek offset adresi verilirse debug onu verilen bellek adresine kadar çalıştırıp ardından r komutunu uygular ve tekrar debug.exe'nin komut satırına düşer. Bu bellek adresine breakpoint denir. Debugger'larda breakpoint'ler belirlenerek program parça parça çalıştırılabilir. g komutuna ayrı bir parametre vermek yerine çalışması kesime uğratılacak noktadaki kod parçası 0CCh (INT 3) ile değiştirilebilir.

g komutu için söylenmesi gereken önemli bir şey daha var: Eğer program DOS 1.0 zamanlarından kalma INT 20h kesmesini kullanarak çıkıyorsa debug.exe "Program terminated normally." diyor ve program sorunsuz debug.exe'ye düşüyor. Yanılmıyorsam bu INT 21h,00h için de geçerli. Ancak DOS 2.0 ve sonrası için önerilen program sonlandırma yöntemi INT 21h,4Ch çağırıldığı zaman bu yöntem debug.exe'yi de sonlandırıp DOS komut satırına düşmenize neden olabiliyor (özellikle birden fazla kere çağırıldığında).

* h (Hex): h komutu verilen iki sayıyı toplar ve çıkarır.

-h 2D1 2E8
05B9  FFE9


* i (Input): Bir porttan veri okur. Komutu port numarası takip eder. Windows NT tabanlı tüm işletim sistemlerinde bu veriye pek güvenmemek gerekiyor. Doğrudan portlara erişmek bazı durumlarda olanaksız, örneğin Windows'un diski kontrol eden sürücüsü diske erişmenize izin vermez. Basit görece zararsız portlar (klavye gibi) işletim sistemi üzerinden erişilebilir durumdalar.

-i 60
1C


60h portu klavye denetleyicisinin portudur.

* l (Load): l komutunun iki kullanımı bulunmaktadır. Birinci kullanımda mantıksal bir sektörü (cluster) belleğe okumakta kullanılır. Bunun kullanımı Windows NT tabanlı sistemlerde yine kısıtlı. XP'de yanlış hatırlamıyorsam çekirdek sürücüleri üzerinden okuma yapılabiliyordu, yazmaya izin yoktu. Windows 7'de şöyle şeyler oluyor:

Bu diyalog kutusunda Close diyince debug.exe sonlandırılıyor, Ignore diyince "Disk error reading drive C" hatası veriyor.

Komutun kullanımında, l'den sonra gelen parametre cluster'ın okunacağı bellek adresi, 02h disk sıra numarası. ( 00h = A: , 01h = B:, 02h = C:, 03h = D: ... ) Sonraki sayılar cluster numarası ve okunacak toplam cluster sayısı.

İkinci kullanımında n ile belirtilen dosyayı (çalıştırılabilir olması şart değil) CS:0100h adresine yükler. Eğer dosya bir .exe dosya ise yüklenecek yer CS:0100h olmayabilir. Bu kullanımla ilgili n komutunun açıklamasına da bakılabilir.

* m (Move): m komutu bir bellek parçasını başka bir bellek parçasının üstüne kopyalar.

DS:0200h'dan DS:0400h'a 128 byte kopyala:
-m 0200 l80 0400

* n (Name): n komutu bir dosya yada parametrelerini debug.exe'ye bildirmeye yarar. l ile bir dosya okumak yada w ile bir dosya yazılmak istendiğinde önce n komutuyla dosya bildirilmesi gerekmektedir. Komut satırında debug'dan sonra dosya adı verilebileceğini belirtmiştim. Eğer bu dosya adı komut satırından verilmezse n komutuyla verilip sonra l komutuyla dosya yüklenebilir. n komutu dosyanın olup olmadığını kontrol etmez, yalnızca bir FCB (File Control Block) oluşturur. l komutu da bu FCB'deki dosyayı açmaya çalışır. Dolayısıyla n komutuna ne girilirse girilsin hata vermeyecektir. İlgili hata l komutu verildiği zaman alınır.

-n\Windows\winhlp32.exe
-l


Yazmaya ilgili bilgiler w komutunda daha etraflıca anlatılacaktır.

* o (Output): Bir porta veri yazar. i komutu gibi bunda da komutu port numarası takip eder. Port numarasından sonra porta gönderilecek veri yazılır. Yine Windows NT tabanlı tüm işletim sistemlerinde i komutundaki gibi güvenlik vb. nedenlerden bu komutun sonucuna pek güvenmemek gerekiyor. Veri porta gönderilmemiş olabilir yada güvenlik hatasıyla debug.exe sonlandırılabilir.

71h portu CMOS adresleme, 70h portu da okuma portudur. Aşağıdaki komutlarla CMOS'un 19h byte'ı okunur:
-o 71 19
-i 70
DD

* p (Proceed): p komutu tek başına kullanıldığında CS:IP'teki bir komutu çalıştırır ancak eğer CS:IP'deki komut bir altprogram yada kesme çağrısıysa (CALL yada INT) içine girmeden tamamını çalıştırılır ve çalışmanın sonunda yazmaçların içeriklerini gösterir (r komutunun çıktısı).

p komutundan sonra bir sayı girilirse (p 3 gibi) debug.exe o kadar sayıda komutu çalıştırarak tek tek her seferinde yazmaç içeriklerini gösterir.

p komutundan sonra başında = bulunan bir offset adresi girildiği zaman IP (Instruction Pointer) bu offset adresine gelinceye kadar program çalışır. O adreste komut bitmiyorsa komutun bittiği son yerde çalışma durur. Yani IP'nin son değeri bu değere eşit yada büyüktür.

-p =0107

AX=0900  BX=FFFF  CX=FE00  DX=0000  SP=00B8  BP=0000  SI=0000  DI=0000
DS=13DA  ES=13DA  SS=13EA  CS=13EA  IP=0107   NV UP EI PL NZ NA PO NC
13EA:0107 CD21          INT     21


* q (Quit): q komutu debug.exe'den çıkar. Herhangi bir parametresi yoktur, verilse de dikkate alınmaz. Yani q, quit yada quake yazılsa da debug.exe'den çıkar.

* r (Register): r komutu yazmaç içeriklerini görüntülemek ve değiştirmek için kullanılır. r komutu tek başına bütün yazmaçları görüntüler.

-r
AX=0900  BX=FFFF  CX=FE00  DX=0000  SP=00B8  BP=0000  SI=0000  DI=0000
DS=13DA  ES=13DA  SS=13EA  CS=13EA  IP=0007   NV UP EI PL NZ NA PO NC
13EA:0007 CD21          INT     21

Çıktıyı kısaca açıklarsam, AX, BX, CX ve DX x86 serisi işlemcilerin çeşitli komutlarla kullandığı genel amaçlı yazmaçlardır. Accumulator, Base, Counter, Data gibi açılımları olsa da ( http://en.wikipedia.org/wiki/AX_register#Purpose ) genel olarak bunların içeriklerini değiştirmenin komutlar işlemediği sürece bir etkisi yoktur.

SP, stack pointer anlamına gelir ve işlemcinin o anda kullandığı stack'in (yığın) offset adresini tutar. BP, base pointer anlamına gelir ve göstericilerin tutulması için yardımcı bir yazmaçtır. Genelde argümanlarına yığın üzerinden erişen C gibi yüksek seviyeli dillerde fonksiyonların en başında şuna benzer bir kod bloğu bulunur:

k = fonk1(0x0202, 0x0101);

karşılığında üretilen kod:

mov ax, 0101
push ax
mov ax, 0202
push ax
call @fonk1

@fonk1:
push bp
mov bp,sp
...
pop bp
ret

biçimindedir. Alt program blogunda kısa (segment içi) çağrı için ilk arguman [bp+04] ikincisi [bp+06] ve uzun (segment dışı) çağrı için ilk arguman [bp+06] ve ikincisi [bp+08] adresinde bulunur. Yani alt programda BP yedek bir SP işi görür ama elbette ki bu bir zorunluluk değildir. SP genelde doğrudan değiştirilmez, push ve pop gibi komutlarla değeri kendiliğinden değişir.

SI, source index ve DI destination index anlamına gelir. MOVS, STOS, SCAS ... gibi karakter dizisi üzerinden yapılan işlemlerde okumalar DS:SI üzerinden, yazma işlemleriyse ES:DI üzerinden yapılır.

DS, data segment anlamına gelir. Genelde veriler üzerinde yapılacak işlemlerde verilerin tutulduğu segment DS'de bulunur. CS, code segment anlamındadır. CS, IP (instruction pointer) yazmacıyla birlikte mikroişlemcinin o an çalıştıracağı komutu tutar. SS'in anlamı stack segment'tir adından da anlaşılacağı üzere SP ile birlikte yığını gösterir. Son olarak ES, extra segment'dir ve bütün bu segment yazmaçlarının haricinde bir yazmaça ihtiyaç duyulması durumunda yardımcı segment yazmacı olarak kullanılır. IP yazmacı programcı tarafından yalnızca jmp ve koşullu dallanma komutlarıyla değiştirilebilir. Her komutun çalıştırılması sonucunda bir sonraki komutu gösterecek şekilde mikroişlemci tarafından değiştirilir. Örneğin 2 byte'lık bir komutun çalıştırılması sonucunda değeri 2, 3 byte'lık bir komutun çalıştırılması sonucunda değeri 3 arttırılır.

NV UP gibi ifadeler FLAGS yazmacına aittir. Bununla ilgili Temmuz 2012 tarihinde "Peki Neden Trap Flag?" başlıklı yazıda ayrıntılı bilgi bulunuyor. En alt satırdaysa o anki CS:IP'nin değeri ve o değerin gösterdiği yerdeki mikroişlemci komutları ve bunların Assembly karşılığı bulunur.

r komutuyla yazmacın içeriğinin değiştirilmesi için r'den sonra yazmacın adı yazılır. Enter'a basıldığında yazmaçtaki o anki değer görüntülenir ve ardından yeni değer girilmesi beklenir. Herhangi bir değer girmeden enter'a basılırsa yazmacın içeriği değişmez. FLAGS yazmacının içeriğine rf komutuyla erişilebilir ve içerik bu komutla değiştirilebilir.

-rax
AX 0202
:0303


* s (Search): s komutu başlangıcı ve bitişi verilen bir aralıkta verilen bir byte yada byte dizisini aramaya yarar.

Aşağıdaki komut DS:0100h ile 0200h arasındaki bütün 0b8h 00h 01h dizisini listeler:
-s 0100 0200 b8 00 01
1381:0100
1381:01A4


İstenirse aranacak veri çift tırnak içerisinde belirtilerek karakterle arama da yapılabilir:
-s 0100 0200 "h"

* t (Trace): t komutu da p komutu gibi tek başına kullanıldığında CS:IP'teki bir komutu çalıştırır ancak p'den farklı olarak t komutu, CALL yada INT gibi komutların içerisine girerek onları da çalıştırır. Çıktı olarak p gibi yazmaç içeriklerini gösterir. t komutu da p komutu gibi komut sayısı belirtilerek yada başında = olan bir offset adresiyle kullanılabilir. Parametrelerin etkisi de p komutuyla aynıdır.

* u (Unassemble): u komutu belleğin bir parçasındaki byte'ları alarak bunları Assembly kodlarına geri çevirir ve ekrana sığdığı kadarıyla listeler. Eğer tek parametreyle verilirse bu değeri offset değeri olarak alıp listelemeye bu offsetten başlar. İki parametreyle verildiğinde birinci değer başlangıç ikinci değer bitiş olmak üzere aradaki kodlar listelenir. Parametresiz verildiğinde en son görüntülediği kod bloğunun bitişiğindeki bloğu listeler. Ardarda verilen u komutlarıyla uzun bir bloğa parça parça bakmak olanaklıdır.

* w (Write): w komutunun da l komutu gibi iki kullanımı bulunmaktadır. l ye benzer olarak bellekten mantıksal sektörlere yazılabilir. (Windows NT de kısıtlamalar var.) Bu kullanımda parametreler l komutuyla aynıdır.

Dosya yazdırılacaksa yine n komutuyla birlikte kullanılır, kaç byte yazılacağı da BX:CX yazmaçlarında tutulur. Yazılacak verinin büyüklüğü 16lık sayı sistemine çevirilir. Düşük değerli 4 basamak CX'te yüksek değerli 4 basamak BX'de olmak üzere 32 bite kadar büyüklükte dosyalar yazılabilir.

Son olarak bunlardan başka X ile başlayan EMS ile ilgili komutlar bulunsa da EMS konusunda bilgim olmadığından bunlara değinmeyeceğim.


Dipnot: Yeni yazı maalesef yine çok uzunca bir zaman gecikti. Mayıs ortasında diskim bozuldu, sonrasında önemli bir sınava girdim ki yedekleme, işletim sistemi kurma, geri yüklemeyle ilgilenmekten sınava doğru dürüst çalışamadım bile. Haziran başında ülkenin içerisinde bulunduğu siyasi durumu izlemekten iş yerinde dahi doğru dürüst çalışamadım. Haziran ortasında bir daha diskim bozuldu falan filan derken Nisan'ın 13ünde başladığım yazıyı tamamlamam bugünü buldu. Gecikmeden ötürü herkesten özür dilerim.