2 Kasım 2021 Salı

Disk Sistemi Nedir, Ne Değildir? #4.5: FAT32


Merhaba. Kısa bir ara verdiğim dosya sistemlerine FAT32'yle devam ediyorum. Önceki yazıda FAT32'ye değindim ama ayrıntılı incelememiştim. Bu yazıda FreeDOS'u FAT32 diske kurup inceleyeceğim ve MSDOS'la karşılaştıracağım.

FAT32, daha önce bahsettiğim gibi, disk büyüklüklerinin 90'larda FAT16'nın getirdiği 2 GB sınırının üstüne çıkmasıyla geliştirildi ve Ağustos 1996'da Win95 OSR2 (ve birlikte gelen MS-DOS 7.1) ile son kullanıcıya sunuldu. Dolaysıyla tek satılan hiçbir MS-DOS sürümü FAT32'yi desteklemiyor. Ve Microsoft, WinXP'den sonraki işletim sistemlerinde, FAT32 disklere kurulumu desteklemeyeceğini de açıkladı. FAT32, yapısal olarak FAT16'ya benzer. Bu sayede DOS çekirdeğindeki dosya sistemi rutinleri tekrardan yazılmamış ve FAT32 desteği yalnızca 5KB civarında bir kodla çekirdeğe eklenmiştir [1].

FAT32'yi ele almaya en temelde MBR'den başlarsam, burada FAT32'yle ilgili tek fark, bölümleme tablosundaki bölüm türüdür. FAT32 formatlı CHS diskler 0x0B ve LBA destekli diskler 0x0C ile işaretlidir. Bunu serinin ikinci yazısında yazmıştım.

FAT32 boot sektörünün ilk 36 byte'ı FAT16'yla aynı, ama FAT32'de fazladan alanlar var. Buna da serinin üçüncü yazısında değindim. Eklenen aynalama flag'ları, kök dizin kümesi ve FSINFO sektörü göstericisi gibi alanlarla FAT'in yetenekleri geliştirilmiştir. Sektördeki kod ise FAT16'nınkinden biraz farklıdır.

Anlattıklarımın teoride kalmaması için önce sanal bir FreeDOS kurdum. Bu kurulumu bir önceki yazıda anlattığımdan detaylara inmeyeceğim. Yalnız, diskin 2 GB'dan büyük tek bölüm olması iyi olur ve bölümlerken fdisk'in FAT32 desteği mutlaka açık olmalı. Aşağıdaki komutla makinanın bölümleme tablosuna baktım:

hexdump -C FreeDOS.vdi | less

Aşağıdaki çıktıda görüleceği gibi bölüm türü 0x0B, yani FAT32 CHS.

002001c0  01 00 0b fe bf 09 3f 00  00 00 4b f5 7f 00 00 00  |......?...K.....|

Diskin CHS olmasının nedeni 8 GB'den küçük olması. Daha büyük diskli bir makinaya kurduğumda bu değer 0x0C idi.

FAT32'deki asıl farklılık doğal olarak boot sektörde. FAT32 boot sektör veri yapısını (DOS 7.1 EBPB - Extended BIOS Parameter Block) konuyla ilgili yazıda iki tabloya bölerek vermiştim. Şimdi tek parça olarak tekrar gözden geçirirsek:

Sektör OffsetiBüyüklükAçıklama
0x003 byteAsıl koda JMP içerir
0x038 byteOEM Adı
0x0BwordSektörün içerdiği byte
0x0DbyteKümenin içerdiği sektör sayısı
0x0EwordAyrılmış (reserved) sektör sayısı
0x10byteFAT (dosya yerleşim tablosu) sayısı
0x11wordAyrılmışNot1
0x13wordAyrılmışNot2
0x15byteMedya tanımlayıcı
0x16wordAyrılmışNot3
0x18wordSilindirin içerdiği sektör sayısı
0x1AwordKafa sayısı
0x1CdwordGizli sektörlerin sayısı
0x20dwordToplam sektör sayısıNot4
0x24dwordFAT başına sektör sayısı
0x28wordAynalama flag'larıNot5
0x2AwordSürümNot6
0x2CdwordKök dizin kümesi
0x30wordFSINFO sektörü
0x32wordYedek boot sektörü
0x3412 byteAyrılmışNot7
0x40byteFiziksel sürücü numarası
0x41byteAyrılmışNot8
0x42byteGenişletilmiş imza (0x28 veya 0x29)
0x43dwordBölüm seri numarası
0x4711 byteBölüm etiketi
0x528 byteDosya sisteminin türüNot9
Wikipedia'dan derlenmiştir. (madde1, madde2)

Not1: FAT32'den önceki sistemlerde bu alan, kök dizinde yer alabilecek dizin ve dosya girdilerinin üst sınırını tutar. Bu sistemlerde kök dizin normal dizinler gibi genişleyemez. FAT32, bu kısıtı kaldırdığından bu alan sıfırdır.
Not2: Eski FAT sürümlerinde bu alan sektör sayısını tutar. FAT32'de değeri sıfırdır (sektör sayısı buraya sığmayacağından), yerine 0x20 offsetindeki değer kullanılır.
Not3: Eski FAT'larda bu alan, FAT'ın içerdiği sektör sayısını tutar ancak bu değer yine bir word'e sığmayacağından 0x24 offsetindeki dword kullanılır.
Not4: Eğer bu alan sıfırsa işletim sistemi toplam sektör sayısını MBR'deki bölüm kaydından alır.
Not5: Normalde dosya yerleşim tablosu en az iki kopya tutulur ve yapılan her işlem iki kopyaya da işlenir. Bu flag'le yalnız bir tablonun aktif olması sağlanabilir.
Not6: Bu alan tanımlı olsa da kullanılmamaktadır ve değeri hep sıfırdır.
Not7: Bu alanın adı Microsoft dökümantasyonunda "boot file name" olarak görünüyor. Normalde boot sektörün yükleyeceği dosya adı boot kodunda yer alır. Bu veriyi sabit bir alanda tutmak için ayrılmış olabileceği akla geliyor.
Not8: Bu byte her zaman sıfır olmalıdır. Ancak Windows NT'de 0. ve 1. bitler "dirty bit" olarak kullanılır. FSINFO bölümünde ayrıntılı ele alınacaktır.
Not9: Bazı işletim sistemleri, toplam sektör sayısı dword'u aştığı durumda, burayı sektör sayısını saklamak için kullanır.

Yukarıdaki hexdump komutunun çıktısında, MBR'den sonra boot sektör geliyor. Elbette MBR sıfırıncı sektördeyken, boot sektör 63. sektörde ama hexdump'a -v parametresini vermediğimden, sıfırdan oluşan alanlar '*' karakteriyle gösteriliyor.

00207e00  eb 58 90 46 52 44 4f 53  35 2e 31 00 02 08 20 00  |.X.FRDOS5.1... .|
00207e10  02 00 00 00 00 f8 00 00  3f 00 ff 00 3f 00 00 00  |........?...?...|
00207e20  4b f5 7f 00 ee 1f 00 00  00 00 00 00 02 00 00 00  |K...............|
00207e30  01 00 06 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00207e40  80 00 29 f7 16 04 38 46  52 45 45 44 4f 53 32 30  |..)...8FREEDOS20|
00207e50  31 36 46 41 54 33 32 20  20 20 fc fa 29 c0 8e d8  |16FAT32   ..)...|


OEM Adı: FRDOS5.1
Byte/Sektör: 512 byte
Sektör/Küme: 8
Ayrılmış Sektörler: 32
FAT Sayısı: 2
Medya Tanımlayıcı: 0xF8 (Sabit disk)
Sektör/Silindir: 0x3F = 63
Kafa: 0xFF = 255
Gizli Sektörler: 0x3F = 63
Toplam Sektör Sayısı: 0x7F F54B = 8 385 867
--- Buradan sonrası FAT16'yla ortak olmayan kısım ---
Sektör/FAT: 0x1FEE = 8174
Aynalama Flag'ları: 0
Sürüm: 0
Kök Dizin Kümesi: 2
FSINFO Sektörü: 1*
Yedek Boot Sektör: 6*
Fiziksel Sürücü Numarası: 0x80
Genişletilmiş İmza: 0x29
Bölüm Seri Numarası: 3804-16F7
Bölüm Etiketi: FREEDOS2016
Dosya Sistemi: FAT32

* Bu alanlar yazının devamında ele alınacak.

Elbette bu verileri böyle okumak zor, bunun yerine disk editörde görüntüleyelim:


Maalesef tüm bilgiler ekrana sığmadı. Bu arada bir kurnazlık yapıp, disk editörü açmadan önce aşağıdaki kodla karakterleri 8x16 pikselden 8x14 piksele çevirdim:

mov ax,1111
mov bl,0
int 10

Kaynak stackoverflow. Bu cevapta yukarıdaki kod için 25 satır modu diyor, halbuki 28 satır olmalıydı. Belki VBox BIOS'uyla ilgili bir uyumsuzluktan farklı olabilir. Aynı cevapta AX=1112h değeriyle 43 satır moduna geçip tüm sektörü bir ekrana sığdırabilirdim ancak okumak zorlaştığı için vazgeçtim.


FSINFO Sektörü
FAT32'de boot sektörden başka iki önemli sektör daha var. Bunlardan biri FSINFO sektörü yani dosya sistemi bilgisi. Buna, boot sektör yazısında kısaca değindim. FAT32'ye kadar, diskteki boş alan, FAT'deki boş kümeler sayılarak hesaplanırdı. Teknik ayrıntıları şimdilik ihmal edersek, FAT16'da küme sayısı en fazla 216 = 65 536 iken, FAT32'de bu ≈268M'ye çıktı. Bu durum, önceki boş alan hesabının 4096 kat yavaş çalışması demekti. Bu sorunu çözmek için FAT32'de boş ve dolu sektör sayılarını tutan FSINFO sektörü tanımlanmıştır. Her ne kadar bu sektörün boot sektörde bir göstericisi olsa da, göstericinin değeri çoğunlukla 1 olup, boot sektörden sonraki sektörde bulunur ve formatı aşağıdaki gibidir:

Sektör OffsetiBüyüklükAçıklama
0x004 byteSektör imzası 'RRaA'
0x04480 byteAyrılmış
0x1E44 byteSektör imzası 'rrAa'
0x1E8dwordBoş küme sayısı
0x1ECdword
Dolu küme sayısı
0x1F012 byteAyrılmış
0x1FC4 byteSektör imzası 0x0,0x0,0x55,0xAA
Wikipedia'dan derlenmiştir

Boş ve dolu küme sayısı, disk düzgün unmount edilmediğinde gerçeği yansıtmayabilir. Windows NT'de bir FAT32 disk takıldığında, boot sektörde 0x41. byte'ın sıfırıncı biti set edilir (dirty bit) ve unmount edildiğinde resetlenir. Bir disk takıldığında, bu bit sıfır değilse diskin düzgün kaldırılmadığı anlaşılır. Bu durumda boş ve dolu küme sayıları gerçeği yansıtmıyor olabilir ve kullanıcıdan CHKDSK'i çalıştırması istenir. Benzer şekilde GÇ hatası olursa birinci bit set edilir ve disk tekrar takıldığında kullanıcıdan yüzey taraması yapması istenir. Bu alanlara formatlama sırasında 0xFFFF FFFF değeri yazılır. Bu değer geçersiz olup işletim sisteminin gerçek değerleri hesaplayarak buraya yazması beklenmektedir.

Aşağıda, bu alanlara ait hexdump çıktısı bulunuyor:

00208000  52 52 61 41 00 00 00 00  00 00 00 00 00 00 00 00  |RRaA............|
00208010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
002081e0  00 00 00 00 72 72 41 61  be dd 0f 00 ed 18 00 00  |....rrAa........|
002081f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|


Bu çıktıya göre diskte 0x0F DDBE = 1 039 806 tane boş küme var. Boot sektörden, her kümede 512 byte'lık 8 sektör olduğundan yola çıkarak, boş alan 4061.742 MB bulunur:


Ve diskteki veri, 0x18ED = 6381 dolu kümede 24.925 MB'tır. Elbette bu değer dosyaların toplam büyüklüğü değil, diskteki toplam kullanılan alandır. Aradaki fark ve "slack space" kavramına FAT yazısının kümeler başlığında değinmiştim.


Yedek Boot Sektör
FAT32'de boot sektörün bozulmasına karşı altıncı sektörde bir yedeği saklanıyor. Teoride bu, herhangi bir sektörde bulunabilir ama MS resmi dökümantasyonunda 6'dan başka bir sektörde olmasını önermiyor. Yalnızca Win95'in boot sektör kodu burayı okumaya çalışıyor (FreeDOS ve WinXP okumuyor). Bunu tutan gösterici, zaten okunamayan bir sektördeyse tutmanın bir anlamı da yok gibi. Virüslerin boot sektörü sıfırlamasına karşı koruma sağlasa da, yeni nesil virüslerin yedeği de sıfırlamamasına engel olmuyor. Sadece boot sektör bozulduğunda disk kurtarma programlarının işine yarayabilir. Bu arada, yedek boot sektörden sonra yedek FSINFO sektörü de bulunuyor.

FAT32 desteğinin, Win95 OSR2 ile başlayıp WinXP'ye kadar devam ettiğini yukarıda yazdım. İncelemek istediysem de VBox'ta OSR2'yi kurmayı beceremedim (sanırım desteklenmiyor, aygıtları tararken mavi ekran alıyor), ben de vmware'de kurdum. Kurulumu özetlersem (biraz karışık): bootdisk.com adresinden Win95b boot disketini indirdim. İTÜ yazılım sunucusundan aldığım kurulum dosyaları ve disket imajıyla, k3b'de boot edilebilir bir .iso oluşturdum. Vmware'de işletim sistemini Win95 seçip 128 MB RAM, 12 GB diskli ve disket sürücülü bir sanal makina kurdum, bunu oluşturduğum iso ile başlattım. A: sürücüsündeyken FDISK'le diskin tamamında bir bölüm oluşturdum. Makinayı tekrar CD'yle başlatıp diski formatladım. Makina açılırken Esc'e basıp CD'yi seçmek için hızlı davranmak gerek, yoksa makina sabit diskten açılıp "Missing operating system"de kalıyor. Açıldıktan sonra diski formatladım, içinde "setup" adında bir dizin oluşturup, CD'deki dosyaları buraya kopyaladım ve bu dizinden SETUP.EXE'yi çalıştırdım. CD'yi kopyalamayınca, sonradan aygıt sürücüsü ararken, kurulumun yapıldığı dizine ulaşmaya çalışıyor. Geri kalan adımlarsa standart.

Bunu kurmaya değer mi? Açıkçası boot sektörün veri alanında bir fark yok. Disk büyüklüğü dışında FreeDOS ile hemen hemen aynı. Benzer şekilde, 32 ve 64-bit WinXP için de aynısını söyleyebilirim. Boot sektör, Win95'te disk editörle açılabiliyor ama program Windows'u algılayıp salt okunur moda geçiyor ve bunun değiştirilmesine izin vermiyor. WinXP'de sabit disk, HxD ile okunabiliyor ancak WinXP'yle herhangi bir sayfaya bağlanmak, TLS uyumsuzluğundan ötürü olanaksız. Bu nedenle WinXP'de dosya paylaşımını açıp HxD'nin kurulum dosyasını makinaya kopyaladım.

Not: Güncel Fedora, istemci olarak (XP'nin desteklediği) SMB1 protokolüne izin vermediğinden bağlanabilmek için smb.conf içinde [global] stanzasında "client min protocol = NT1" eklemek gerekiyor*.


Dosya Yerleşim Tablosu
Tablonun yeri FAT16 gibi, gizli sektörlerle ayrılmış sektörler toplanarak bulunuyor. Başka bir deyişle, boot sektörden gizli sektör sayısı kadar ileride.

fat_start = hidden_sectors + reserved_sectors (1)

FAT16'dan farklı olarak, FAT32'de kök dizinin sabit olmadığına değinmiştim. Dolayısıyla root_dir_start'ın hesaplanmasına gerek yok.

data_start = fat_start + number_of_FATs * sectors_per_FAT (2)


Son olarak küme numarasını sektör numarasına dönüştüren fonksiyon aynı:

clus2sect(c) = (c - 2) * sectors_per_cluster + data_start (3)


Dizin ağacını elde edebilmek için, kök dizin kümesine ait gösterici boot sektörden okunur ve (3) formülünde yerine konarak fiziksel yeri bulunur. Dizin ağacına değinmeden önce yerleşim tablosuna göz atalım. Boot sektörü 63. sektörde (gizli sektörler) ve bunun için 32 sektör ayrılmış (ayrılmış sektörler). (1) formülünden, FAT, 63 + 32 = 95. sektörde. .vdi dosya başlığı 0x200000 byte uzunlukta yani 512 * 4096. O halde dosya başlığı 512 byte'lık 4096 blok ve MBR+Boot sektör, 95 blok daha olmak üzere 4191. blokta FAT bulunuyor:

dd if=~/VirtualBox\ VMs/FreeDOS/FreeDOS.vdi \
bs=512 skip=4191 | hexdump -C | less

00000000  f8 ff ff 0f ff ff ff 0f  03 00 00 00 04 00 00 00  |................|
00000010  05 00 00 00 ff ff ff 0f  00 00 00 00 ff ff ff 0f  |................|
[SNIP]


Girdilerin mantığı, FAT'ın önceki sürümleriyle aynı olduğundan tüm blokları incelemeye gerek yok. Burada sadece ilk sekiz girdiyi alıntıladım. Bu arada github'daki fatread koduna FAT32 desteğini de ekledim*:

Küme0: 0xFFF FFF8 (0x0000)
Küme1: 0xFFF FFFF (0x0004)
Küme2: 0x3 (0x0008)
Küme3: 0x4 (0x000C)
Küme4: 0x5 (0x0010)
Küme5: 0xFFF FFFF (0x0014)
Küme6: 0x0 (0x0018)
Küme7: 0xFFF FFFF (0x001C)
...

Sıfırıncı girdi her zamanki gibi medya tanımlayıcı veya FAT ID, ancak dikkat edilirse 32 bitlik değerin ilk 4 biti sıfır. Birinci kümede yine dosya sonu (EOF) imi var. İkinci küme üçü, üçüncü dördü, dördüncü beşi ve beşinci küme EOF'u gösteriyor. Boot sektöründen, kök dizininin ikinci kümede başladığını hatırlayalım. O halde kök dizin bulundu. Altıncı küme boş ve yedinci kümede bir EOF daha var.

FAT32'de girdiler 32 bit olsa da, bunların yalnız düşük anlamlı 28 biti kullanılır. Yüksek anlamlı nibble ayrılmıştır. Dolayısıyla küme sayısının teorik üst sınırı 228 = 268 435 456'dır. 12 değerin özel anlamı olduğu için pratik sınır, teoriğin 12 eksiğidir. Bu değerler, eski FAT sürümlerine benzer şekilde, 0x0FFF FFF8 ile 0x0FFF FFFF arası EOF imi, 0x0FFF FFF7 bozuk küme, 0x0 boş küme ve ayrılmış 0x1 ile 0x0FFF FFF6 değerleridir. Son olarak 0x0FFF FFF0 ile 0x0FFF FFF5 arası değerlerin de uyumluluk nedeniyle kullanılmaması tavsiye edilmektedir.

Bu arada birinci girdinin 27. biti, boot sektörün 0x41. byte'ı gibi dirty bit olarak kullanılabilir. Eğer mount sırasında bu bit sıfırsa, işletim sistemi diski taramaya çalışır veya en azından FSINFO'daki değerlerin güvenilmez olduğunu bilir. Benzer şekilde 26. bit de GÇ hatalarında kullanılır.

*Not: FAT32 tablosu büyük bir alan (örn. 2 * 8174 sektör) kaplar. .vdi diskte de fazla veri yoksa FAT içeriği çoğunlukla sıfır olacaktır. Bu boş alanlar .vdi dosyada saklanmadığı için ("Preallocated" değilse), fatread, boş (sparse) alanları dikkate almaz ve büyük küme numaralarında hatalar oluşur. Örn. FreeDOS diskinin 259840. kümesinde FAT ID görünüyor çünkü program aslında ikinci FAT'ı okuyor:



Dizinler ve Dizin Tablosu
Boot sektörden, kök dizinin ikinci kümede olduğu biliyorum ve yukarıda beşinci kümeye kadar devam ettiğini de buldum. fat_start = 95 ve sectors_per_FAT = 8174'tü. O halde (2) formülünden data_start = 16443 ve (3) formülünden clust2sect(2) = 16443 bulunur. Ancak aşağıda da görüldüğü gibi bir sorun var:

dd if=~/VirtualBox\ VMs/FreeDOS/FreeDOS.vdi bs=512 skip=$((4096+16443)) | hexdump -C | head
00000000  2d 2d 2d 2d 2d 2d 2d 2d  2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
00000010  2d 2d 2d 2d 2d 2d 2d 2d  2d 2d 2d 0d 0a 54 61 64  |-----------..Tad|

Yukarıdaki * imli not aslında sorunu açıklıyor: Boş alanlar .vdi dosyada tutulmadığından, hesap doğru olsa da yanlış bir sektör görülüyor. Öyleyse sanal makinanın içinden DISKEDIT'le bakmam gerek. Aslında C:\'deyken DISKEDIT'i başlatınca kök dizini açıyor ama hesabın doğru olduğunu göstermek için Alt+P'ye basıp 16443 yazınca da aynı veriler görülüyor. (F2: Hexdecimal görünüm)

Kök dizinin içeriği

Kök dizinin başında "FREEDOS2016" girdisinin olduğunu gördüm. hexdump -C ~/VirtualBox\ VMs\FreeDOS\FreeDOS.vdi | less komutuyla dosyayı açıp "/FREEDOS2016" diye aratarak dosyanın 0x407600 ofsetinde girdiyi buldum.

Buradaki girdiler de önceki FAT sürümleriyle benzer. En önemli değişiklik, küme numaraları 32 bit olduğundan, küme numarasının düşük anlamlı word'u 0x1A'da yüksek anlamlı word'u 0x12'de bulunuyor. Ayrıca MS-DOS 7 ve WinNT ile gelen bazı yeni özellikler var. FAT yazısındaki tabloya bu özellikleri ekleyip güncelledim:

OffsetBüyüklükAçıklama
0x008 byteDosya adı
0x083 byteDosya uzantısı
0x0B1 byteDosya özellikleri
0x0C1 byteMSDOS: Ayrılmış
WinNT: Büyük/küçük harf bilgisi
0x0D1 byteOluşturma zamanı (milisaniye)
0x0EwordOluşturma zamanı
0x10word
Oluşturma tarihi
0x12wordErişim tarihi
0x14wordKüme num. (31..16 bitler)
0x16word
Değiştirme zamanı
0x18word
Değiştirme tarihi
0x1AwordKüme num. (15..0 bitler)
0x1CdwordDosya büyüklüğü

0x0C byte'ı MS-DOS ve Win95'te dikkate alınmaz. WinNT'de (ve XP'de) üçüncü bit dosya adının ve dördüncü bit uzantının küçük harf olduğunu gösterir:

0x00: TEST000.TXT
0x08: test000.TXT
0x10: TEST000.txt
0x18: test000.txt

Saat ve tarih formatı FAT16'yla aynıdır. Önceki yazıdan hatırlarsak; Sa: saat, Dk: dakika ve Sn: Saniye olmak üzere:

Offset 0x0F, 0x17
Offset 0x0E, 0x16
Sa4Sa3Sa2Sa1Sa0Dk5Dk4Dk3Dk2Dk1Dk0Sn4Sn3Sn2Sn1Sn0

Aynı şekilde Yi: yıl ve Gü: gün olmak üzere:

Offset 0x11, 0x13, 0x19Offset 0x10, 0x12, 0x18
Yi6Yi5Yi4Yi3Yi2Yi1Yi0Ay3Ay2Ay1Ay0Gü4Gü3Gü2Gü1Gü0

Saat veri yapısında saniye için 5-bit ayrıldığından, zaman çözünürlüğünün iki saniye olduğunu yazmıştım. 0x0D byte'ı 10 milisaniyelik birimleri temsil eder. Bununla çözünürlük 10ms'ye inmiş olur. Elbette bu byte'ın yalnız 0..199 arası değerleri anlamlıdır.

Örnek:
0b706e40  4e 54 4c 44 52 20 20 20  20 20 20 27 08 00 00 10  |NTLDR      '....|
0b706e50  8e 38 50 53 01 00 00 10  8e 38 8f 1c c0 d0 03 00  |.8PS.....8......|

NTLDR'nin dosya özellikleri 0x27 yani; arşiv, gizli, salt-okunur ve sistem dosyası. 0x0C'deki değer 0x08, yani dosya adı küçük harf. Uzantı zaten yok. Dosya oluşturma ve değiştirme zamanı aynı: 0x1000. Saatin ikinci biti 1, yani 02:00. Tarihler de aynı: 0x388E = 0011100 0100 01110 = 1980+28/04/14. Erişim tarihi ben dosyanın özelliklerine baktığım için 0x5350 = 0101001 1010 10000 = 1980+41/10/16. Dosyanın yer aldığı küme 0x011C8F ve boyutu 0x3D0C0 = 250048 byte.

Uzun Dosya Adı (LFN) Desteği
FAT32, VFAT yani uzun dosya adı desteği konusunda FAT16'ya ek herhangi bir değişiklik içermediğinden, ayrıntılı bilgi önceki yazıda bulunabilir.


FAT32 Boot Kodu

a. FreeDOS Boot Kodu
FreeDOS boot kodunu sanal makinamdan alıp github'daki kodlarla karşılaştırdım ve LBA destekli boot32lb.asm'nin çalıştığını buldum. Bunu indirip kendi yorumlarımı, başında "; --" ile ekledim. Dosya buradan indirilebilir. Yazının geri kalanındaki satır numaraları, açıklamalarımı eklediğim dosyaya göre olacak.

Kod standart olarak 0:0x7C00 adresine yüklenir (satır 54) ve buradan real_start (s.117) satırına atlar. 60. ile 116. satırlar arasında boot sektördeki verilerin göstericileri tanımlıdır. FAT16 kodundaki gibi burada da kernel 0x60:0 adresine yükleneceğinden, kod kendini 0x1FE0:0'a kopyalar ve çalışmaya oradan devam eder (s.123-129). 131. satırdaki göstericide çekirdeğin yükleneceği adres var. 141. satırda ekrana "Loading FreeDOS" yazılır ve ardından gelen calc_params'ta fat_start ve data_start, (1) ve (2) formüllerinden hesaplanır (s.155 ve s.163)

FAT16'da reserved_sectors çoğunlukla 1 olup, boot sektörden hemen sonra FAT başlar. FAT32'de arada FSINFO ve yedek boot sektör olduğundan bu değer birden büyüktür. Kök dizin göstericisi boot sektörde bulunduğundan, FAT16'daki gibi hesaplanmasına gerek yoktur.

169-178 satırları arasında ilginç bir kod var. Bir döngüde AX'teki 512 değeri bytes_per_sector ile karşılaştırılıp, eşit değilse AX'in iki katı alınır. Her adımda 278. satırdaki kaydırma işleminin parametresi bir arttırılır (self modifying code) ve bu parametre, küme numaralarının FAT'teki yerini hesaplarken kullanılır. Kısaca kod 512 byte'dan büyük sektörleri destekler gibi görünüyor.

Satır 189'da kök dizin kümesinin sektörü bulunur ve bu readDisk fonksiyonuyla okunur (s.194). 201'den 212. satıra kadar, kök dizin girdilerinde KERNEL.SYS aranır. Arama sırasında DI'nin değeri arttırılıp, bu değer bytes_per_sector'u geçince sonraki sektör okunur (s.216) ve aynı anda DX'teki sectors_per_cluster değeri azaltılır. Eğer kümedeki tüm sektörler okunduysa, 216. satırda DX=0 olacağından next_cluster fonksiyonu EAX'teki kümenin ardışığını bulur ve 188. satırdan itibaren tüm adımlar KERNEL.SYS bulunana kadar tekrar eder. Dosya bulunursa küme numarası EAX'e yüklenir (ff_done satırı). Bu, convert_cluster ile (s.232) sektör numarasına dönüştürülüp, kümenin tamamı readDisk ile (s.236) sektör sektör okunur. Dosyanın ardışık kümesi, EoF imi içeriyorsa 232. satırdaki fonksiyon çağrısı carry döndüreceğinden, dosyanın sonuna kadar okunduğu anlaşılır ve boot_success satırında programın çalışması çekirdeğe devredilir.

Tek tek fonksiyonların ayrıntılarına burada değinmeyeceğim. Kodun yorumlarında bunları açıklamaya çalıştım.


b. Windows Boot Kodu
Windows boot koduyla ilgili araştırma yaparken UNI Magdeburg'dan Jens Elkner'in kişisel alanında boot sektör koduyla ilgili kaynaklar buldum. Kaynak kod kapalı olduğu için kısaca buradaki Win95'e ait kodun üzerinden geçeceğim. Bu bölümde satır numaraları yerine offset adreslerine referans verdim.

Windows boot koduyla ilgili en önemli ayrıntı, iki parçadan oluşması. Boot sektör, Win95'te üçüncü, WinXP'lerde onikinci sektörde bulunan başka bir kodu yüklemekle görevli.

Win95'e bakıldığında, DOS'tan kalan kodu devralmış görünüyor. Kodun başında, kullanılıp kullanılmadığı bile belirsiz bir disket parametre tablosundaki değerler değiştiriliyor (0x7C6E, 0x7C81 vb.). Ardından boot ortamı disketse (0x7C8E), parametreler işlenmek üzere 0x7CB5 adresine dallanılıyor. Eğer ortam diskse (MBR varsa) MBR okunuyor, o bölüme ait bölüm kaydı bulunuyor (boot sektördeki gizli sektörler = bölüm kaydındaki başlangıç LBA adresi) ve boot eden bölümün türü 2 ile OR'lanarak (0x7CAA) 0x7C02'ye (NOP komutunun üzerine) yazılıyor. Bu değer, 0x7D40 adresinde 0xE ile karşılaştırılacak. Çünkü, LBA destekleniyorsa bölüm türü 0xC veya 0xE'dir (0xC OR 2 = 0xE) ve o halde kodda int 0x13'ün 0x42 fonksiyonu kullanılır. MS'in LBA desteğini kodla test etmemesi son derece garip ama 95'te çok eski olmayan her bilgisayarın LBA desteklediğini düşünüyorum.

0x7CC4'te CX=3 oluyor. Bu değer, 0x7D31'deki read_disk fonksiyonuna 2 olarak girecek. Böylece boot sektörden sonraki iki sektör, yani FSINFO ve ikinci parça kod belleğe okunuyor. Burada okuma başarısız olursa (0x7CD2), kod yedek boot sektörü (0x7CD9) okumaya çalışıyor. İkinci parça, 0x8000 adresine sorunsuz yüklenebildiyse oraya atlıyor. Bu arada, 0x7CD4'teki komutun hiçbir işlevi yok ama 0xF8'den (medya tanımlayıcı) dolayı belki verinin özel bir anlamı vardır. Örn. bir yerde değişken olarak kullanılıyor olabilir.

0x7D03 ile 0x7D30 arasında, hata mesajlarını gösteren ve bilgisayarı yeniden başlatan kısımlar var. Hata mesajlarına kadar olan alanda, LBA ve CHS disk okuma fonksiyonları bulunuyor. Bu arada hata mesajlarının hemen gerisinde 4 tane garip gösterici var, kendisinden sonraki göreli adresin bir eksiğini tutuyor. Yine MS gariplikleri.

İkinci fazda bir sürü gereksiz CLI/STI bloğu var. 0x8016'ya kadar data_start hesaplanıyor ve [BP-04] adresinde depolanıyor (0x801B). [BP-08]'e ileride kullanılmak üzere -1 yazılıyor (0x801F). 0x803E'de, SHLD komutuyla EDX, 16-bit sola kaydırılıp, EAX'in yüksek anlamlı word'ü DX'e yazılıyor. Yani EAX'teki değer DX:AX'e yazılıyor. Kodda birden fazla yerde bu komut var çünkü hesaplamalarda bazı yerde EAX kullanılırken bazı yerde DX:AX kullanılıyor ve read_disk, sektörü DX:AX'ten alıyor. Onun yerine read_disk'i optimize etseydiniz?! Bu arada 0x8047'deki iki kaydırma işlemi, DX:AX'i EAX'e yazıyor (SHLD'nin tersi). Yeri gelmişken, read_disk'te bir gariplik de, LBA desteği kontrol edilmeden önce DAP paketi oluşturuluyor. E, LBA desteği yoksa bu kullanılmayacak ama?!

0x8028'de okunan kök dizin kümesi, 0x8050 ile 0x8067 arasında sektör numarasına dönüştürülüyor ve kök dizin tablosu 0:0x700 adresine okunuyor (0x8068 ile 0x8073). Tabloda IO.SYS dosyası aratılıyor (0x8081) ve bulunması durumunda 0x8084'ten 0x809F'teki dosyayı belleğe yükleyen bölüme dallanılıyor. Bu bölümde, 0x80A2'de IO.SYS'nin küme numarası alınıyor ve DX:AX'e aktarılıp (0x80CF), bu kümeden sadece dört sektör 0:0x700 adresine okunuyor (0x80D9 ve 0x80DC). 0x80E4 ve 0x80EA adreslerinde sırayla IO.SYS'nin başındaki 'MZ' imzası ve kodun ilk iki karakteri doğrulanıyor. Eğer bunlar tutarlıysa, dosya 0x70:0x200 adresinde çalıştırılıyor, aksi halde "Invalid system disk" hatası çıkıyor (ama neden?). IO.SYS, geri kalan sektörlerini kendisi okumak zorunda.

0x80FD ile 0x811F arasında, DX:AX'te verilen kümenin ardışığı hesaplanıyor. Bu fonksiyon 0x8120'de, verilen kümenin, hangi FAT sektöründe olduğunu bulan alt-fonksiyonu çağırıyor. Bulunan sektör numarası 0x801F adresindeki değişkene yazılıyor. Böylece, örn. IO.SYS'nin ilk kümesi 3 ise, buna ait girdi FAT'in ilk sektöründedir ve büyük olasılıkla dosyanın ardışık kümesi 4 olup, buna ait girdi de aynı yerdedir. Dolayısıyla aynı sektörün tekrar okunması gerekmez. Eğer EAX'teki değer (0x8138), son okunan FAT sektörüyle ([BP-08]'deki) aynıysa 0x813C'deki JE komutuyla fonksiyonun sonuna atlanır.

WinXP'deyse disket parametre tablosundan vazgeçilmiş. Kod, ikinci fazı onikinci sektörden 0x8000'e yükleyip oraya atlıyor. LBA desteği kodla kontrol ediliyor. Koddan gereksiz parçalar atılınca, boot sektörde epey boşluk kalmış. İkinci faz, Win95'le neredeyse aynı ama gereksiz CLI/STI'ler kaldırılmış ve yukarıda yazdığım gibi sektör numarası sürekli olarak EAX'te tutulunca kaydırma işlemlerine gerek kalmamış. İki kod arasında toplam 63 byte fark var. Bu arada WinXP, IO.SYS yerine NTLDR dosyasını 0x2000:0 adresine yüklüyor. Kısacası, Win95'teki gariplikler WinXP'de bulunmuyor ve bu kod daha optimize yazılmış.


[1]: https://en.wikipedia.org/wiki/File_Allocation_Table#FAT32