18 Aralık 2021 Cumartesi

Linux from Scratch ve LVM İçin initrd Oluşturmak


Merhaba. Uzun zamandır elektronikle ilgili yazmak istediysem de, maalesef yine olmadı. Bugünkü konum, beni iki hafta uğraştıran bir konfigürasyon. Daha doğrusu initrd oluşturmak. Peki neden böyle bir şeye ihtiyaç duydum?

Öncelikle LVM'den başlayayım. LVM'le ilk başlarda kötü deneyimlerim olsa da, 2014'ten beri o kadar alıştım ki, şu an bence LVM'siz linux kurulumu düşünülemez. Birden çok diske yayılmış bölümlerim yok ama bence en güzel özelliği diski taşıma ve yeniden boyutlandırma konusundaki esnekliği.

Konunun diğer tarafı Linux from Scratch (LFS) projesi. LFS, kullanıcılara linux çekirdeğini, bash kabuğunu ve linux'ta temel düzeyde çalışabilmek için gerekli araçları, sıfırdan yani kaynak kodlarından nasıl derleneceğini adım adım anlatan bir proje. Elbette çalışan bir derleyiciyle başlamak gerekiyor. gcc'yi cross compiler olarak derledikten sonra, temel araçlar bununla derleniyor ve minimal bir chroot (change root) ortamı oluşturuluyor. Son adımdaysa linux çekirdeği derleniyor ve grub ile boot edilebilir duruma getiriliyor. Bu projeden, Viktor Engelmann'ın videoları sayesinde haberim oldu. Ben burada onunkinden farklı bir yol izleyeceğim. Zaten ikimiz de LFS dökümantasyonundan, (LFS terminolojisiyle "kitap"tan) kısmen farklı bir yol izliyoruz ancak yapılacaklar kitabı temel alıyor. Benim izleyeceğim bir kaç adım (B)eyond LFS kitabından olacak.

Kitap, systemd ve initd sürümü olarak ikiye ayrılıyor ama ikisi arasında sekizinci bölümdeki paketlere kadar fark yok. Bu yazıda kitabın v11.0 sürümünü temel alıp ikisini de derleyeceğim. LFS'in sayfasında "Download" ve "Read Online" olmak üzere kitabın iki farklı sürümü var. Download bölümündeki .pdf dosya daha derli toplu olsa da, .html sürümü komutları kopyalayıp yapıştırmak için daha rahat. Bu arada projede zamanın çoğu paket derlemekle geçtiği için (tüm LFS'i derlemek benim makinamda 16 saat civarında sürdü, testler hariç), tek tek komutları tekrarlamak yerine yalnız kitaptan farklı yaptığım adımlara değineceğim.

Not: Bu yazıda verdiğim bağlantılar, yazıldığı tarih itibariyle kitabın son sürümüne bağlı. Dolayısıyla, v11'den daha güncel bir sürüm çıktığında, bazı bağlantılar farklı bölümleri açacaktır.


Sistem Gereksinimleri

Kitapta 10 GB'lik bir bölümün paketleri derlemek için yeterli olduğu, ama günlük kullanım için 30 GB'lik bir bölümün gerekeceği yazıyor. Çekirdeğin çok fazla özelliğini açıp (Fedora çekirdeğini) derlerken disk kullanımı 20 GB'yi geçtiği oldu.

Ben LFS için iki diskli bir sanal makina'nın (VM) birinci diskine işletim sistemi kurup, paketleri ikinci diskte derleyeceğim. Bunun avantajı, yanlışlıkla (chroot'ta olmadığımı unutup) kendi sistemimi bozma riskinin bulunmaması. Kod derlemek, yüksek disk performansı gerektiren bir iş. Bu nedenle, en iyisi SSD'de çalışmak. VM, zaten hostunun kendi disk performansına yakın bir performans verecektir. VM'in bir avantajı da, fiziksel diskte yeni bölüm ayıracak kadar boş yer yoksa bile izole bir ortam sağlayabilmesi.

Viktor Engelmann, videolarında paketleri USB belleğe indirip derliyor. Ben bunu mantıklı bulmuyorum. Dönen diskler bile USB belleklerden daha fazla performans sağlıyor. USB'de çalışmak bu iş için verimli değil. Eğer LFS, USB'den boot edilecekse, herşey diskte derlenip grub adımından önce USB belleğe kopyalanır ve önyükleyici en sonda USB'ye yazılabilir.

Projede disk dışında bir gereksinim bulunmuyor. Elbette hızlı bir işlemci, derleme süresini kısaltacaktır ama yavaş bir işlemcide de aynı sonucu almak mümkün.

Birinci diske kurulacak işletim sistemi konusunda da bir kısıtlama yok. Ben kendimi RedHat tabanlı sistemlerle rahat hissettiğim ve uzun zamandır CentOS 8 Stream'i denemek istediğim için bunu kullanacağım.


Sanal Makina Kurulumu ve Diskleri Oluşturmak

Yazıyı gereksiz uzatmamak için kurulumun detaylarına girmeyeceğim. Sanallaştırmayı hem VBox hem vmware'de denedim. Önceki yazıda, çekirdeği vmware için derlerken fazladan iki özelliğin aktifleştirilmesi gerektiğini anlattım, buna yeri geldiğinde tekrar değinirim. 2 GB RAM'li ve 20 GB diskli bir VM oluşturdum. Kurulumda temel olarak "Minimal Install" ve ek paket olarak "Development Tools"u seçtim (yanda). Tüm ayarlar aşağıdaki görseldeki gibi. Kurulumda LVM kendiliğinden oluşturuluyor, ben elle ayarlamadım. Benim asıl ilgilendiğim LFS'teki LVM yapısı, ve bunu elle oluşturacağım. Ama önce 40 GB'lık bir disk daha eklemeliyim. VBox'ta sanal makina çalışırken disk eklenemiyor (Sonradan gelen düzeltme: aslında eklenebiliyormuş). Vmware'de diski, VM çalışırken ekleyip echo "- - -" > /sys/class/scsi_host/host2/scan komutuyla tanıtmak veya makinayı yeniden başlatmak gerekiyor. Bende disk host2'ye bağlıydı (VBox için de aynı komut uygulanır).

CentOS'ta sshd çalışır olarak geliyor. Makinanın IP'sini bulup ssh ile bağlandım. Komutları kopyalayıp terminale yapıştırmak konsola elle girmekten daha kolay. VM, NAT'la bağlıysa "port forwarding"i ayarlamak gerekiyor. Buna da eski yazıların birinde değinmiştim.

Benim LVM şablonum, diskin başında 512 MB'lik bir /boot bölümü, kalanında LVM bölümü ve LVM içinde 4'er GB'lik /var, /home bölümleriyle 2'şer GB'lik /tmp ve swap, kalan alanda da kök dizin. Kök dizin harici bölümlerde toplam kullanım 60 MB'ı aşmıyor. Haliyle 12.5 GB'lik alan boş kalıyor. Yukarıda yazdığım gibi kök dizin kullanımı 16 GB'yi bulabilir. Bu yüzden 40 GB'lık görece büyük bir disk ekledim. Kurulum bittiğinde disk dosyasının net büyüklüğü 28.8 GB'dı.

Diski aşağıdaki komutla hızlıca bölümlendirdim:

echo -ne "n\np\n1\n\n+512M\nn\np\n2\n\n\nt\n2\n8E\np\nw\n" | sudo fdisk /dev/sdb

Sonuç, komutun çıktısından kontrol edilebilir:

Device     Boot   Start      End  Sectors  Size Id Type
/dev/sdb1          2048  1050623  1048576  512M 83 Linux
/dev/sdb2       1050624 83886079 82835456 39.5G 8e Linux LVM

Sonra sırayla LVM içindeki bölümleri oluşturdum:

sudo pvcreate /dev/sdb2
sudo vgcreate vg_lfs /dev/sdb2
sudo lvcreate -n lv_var  -L 4G vg_lfs
sudo lvcreate -n lv_home -L 4G vg_lfs
sudo lvcreate -n lv_swap -L 2G vg_lfs
sudo lvcreate -n lv_tmp  -L 2G vg_lfs
sudo lvcreate -n lv_root -l100%FREE vg_lfs
sudo lvscan

Son komutla hem yeni bölümleri hem de CentOS'un bölümlerini görmeliyim. Bölümler oluşturulduktan sonra formatlanmaları gerek:

sudo mkfs.ext4 /dev/sdb1
sudo mkfs.ext4 /dev/vg_lfs/lv_root
sudo mkfs.ext4 /dev/vg_lfs/lv_tmp
sudo mkfs.ext4 /dev/vg_lfs/lv_home
sudo mkfs.ext4 /dev/vg_lfs/lv_var
sudo mkswap    /dev/vg_lfs/lv_swap

LFS kitabı, kurulumda ext4 dosya sistemi kullanıldığını varsayıyor (bölüm 2.5).


LFS Çalışma Ortamını Oluşturalım

İlk olarak bölüm 2.2'deki betiği kaydedip çalıştırdığımda, yalnızca python3 ile makeinfo bulunamadı. Python'ı sudo dnf install python3 komutuyla yükledim. Diğer pakete sonra geleceğim.

export LFS=/mnt/lfs komutuyla LFS değişkenini oluşturup, bunu .bash_profile'a da ekledim (bölüm 2.6). Elbette ilgili dizini de oluşturdum. Diskleri mount etmem gerek (bölüm 2.7) ama dört bölümü elle mount etmemek için bir betik hazırladım:

#!/bin/bash

if [[ x$LFS == "x" ]]; then
    echo '$LFS' variable is empty.
    exit 1
fi

STEP=1
for PARTITION in "/" "var" "home" "tmp"; do
    if [[ $PARTITION == "/" ]]; then
        LVMNAME="root";
    else
        LVMNAME=$PARTITION;
    fi

    echo "[ $STEP / 5 ] Mounting $LVMNAME partition"
    if [ ! -d "$LFS/$PARTITION" ]; then
        sudo mkdir -pv "$LFS/$PARTITION";
        sudo chown $USER:$GROUPS "$LFS/$PARTITION";
    fi

    sudo mount "/dev/vg_lfs/lv_$LVMNAME" $LFS/$PARTITION
    sudo chown $USER:$GROUPS "$LFS/$PARTITION";

    STEP=$((STEP+1))
done

echo "[ 5 / 5 ] Activating swap.."
sudo swapon /dev/vg_lfs/lv_swap  2> /dev/null

Betiği tek bir kullanıcı çalıştıracaksa $USER ve $GROUPS yerine kullanıcı adı ve grubu yazılabilir. Bir de son adımda swap'i aktifleştirmeye bence gerek yok.


LFS Paketleri

Paket denildiğinde .rpm veya .deb dosyaları anlaşılmasın. Bizimkiler kaynak kod paketleri. Bunları indirmeden önce, eksik bir kaç paketi daha kurmak gerek. wget, vim-enhanced ve son olarak atladığım makeinfo. Bu, texinfo paketinde geliyor ama paketin bulunduğu "powertools" reposu aktif değil. Bu yüzden şöyle yaptım:

sudo dnf install --enablerepo="powertools" texinfo wget vim-enhanced

Sonra sources dizinini oluşturup (bölüm 3.1), wget listesiyle dosyaları buraya indirdim ve tabii ki md5'lerini kontrol ettim. İndirmede bazen sorun olursa, wget'e --no-check-certificate parametresini vermek gerekiyor.

Bölüm 4.2'deki komutları çalıştırmak için root'a geçtim ama LFS değişkenini root'ta tanımlamadığımdan önce bunu tanımladım, sonra komutları çalıştırdım. VM'de olduğum için lfs kullanıcısı (4.3) oluşturmayacağım. Dizinlerin sahipliğini kendi kullanıcıma aktadım. Verilen .bashrc'yi, lfs_env.sh adıyla ev dizinime (root değil) kaydettim ve source'la yükledim. VM'i reboot edersem bunu çalıştıracağım.

Beşinci ve altıncı bölümleri olduğu gibi uyguladım. 7.2'deki ve 7.3.2'ye kadar olan komutları çalıştırdım. Bundan sonraki komutlar chroot ortamına her girişte çalışacağı ve çıkışta da mount edilen kaynakların ters sırada umount edilmeleri gerektiği için, verilen komutlardan bir betik oluşturdum:

#!/bin/bash

if [[ x$LFS == "x" ]]; then
    echo '$LFS' variable is empty.
    exit 1
fi

mount -v --bind /dev $LFS/dev
mount -v --bind /dev/pts $LFS/dev/pts
mount -vt proc proc $LFS/proc
mount -vt sysfs sysfs $LFS/sys
mount -vt tmpfs tmpfs $LFS/run

if [ -h $LFS/dev/shm ]; then
  mkdir -pv $LFS/$(readlink $LFS/dev/shm)
fi

chroot "$LFS" /usr/bin/env -i HOME=/root  TERM="$TERM"  PS1='(lfs chroot) \u:\w\$ ' PATH=/bin:/usr/bin:/sbin:/usr/sbin /bin/bash --login +h

umount -v $LFS/run
umount -v $LFS/sys
umount -v $LFS/proc
umount -v $LFS/dev/pts
umount -v $LFS/dev

chroot'dan çıkarken özellikle /dev/pts unmount edilmediğinde, sanal makinada yeni bir terminal açılamıyor ve makinayı yeniden başlatmak gerekiyor.

chroot'a girip gerekli dizin ve dosyaları oluşturmaya devam ettim. Bu arada bölüm 7.6'da /etc/passwd ve /etc/group dosyaları, systemd ile sysVinit'in farklılaştıkları ilk nokta.

Betik, chroot'dan çıkışta kaynakları unmount ettiğinden, bölüm 7.14'teki umount'lara gerek kalmadı. Ayrıca kurulumu sanal makinada yaptığımdan yedeklemeyi de snapshot'la yapabilirim. Bu nedenle bu adımlara da gerek yok.

8.25'te shadow'u cracklib desteğiyle derlerken "undefined reference to `FascistCheck'" hatasını aldım. Aşağıdaki komutla bir kere daha konfigüre edip derlediğimde sorun olmadı:

LDFLAGS=-lcrack ./configure --sysconfdir=/etc --with-libcrack --with-group-name-max-length=32

8.26'daki "make -k check" adımı çok uzun sürüyor. Öyle ki testi yatmadan önce başlattım, sabah kalktığımda hala tamamlanmamıştı. Anladığım kadarıyla 350K'dan fazla test var ve testlerin bir kısmı stres testi. Ayrıca kitapta bazı testlerin başarısız olduğunun bilindiği yazılmış. Test sonuçlarına şuradan ulaşılabilir. Bendeki test sonuçları da hemen hemen bunlarla aynıydı. Bu bölümün sonunda basit bir derleme testi var. Belki sadece o testi yapmak yeterli olabilir ama kitap "make check" adımının kesinlikle atlanmamasını istiyor.

Bölüm 8.69'da artık systemd ve sysVinit paketleri farklılaşıyor.

Son olarak 8. bölümün sonunda lfs_enter_chroot.sh betiğinde bash'a verdiğim +h parametresini kaldırıp tekrar kaydettim.

Dokuzuncu bölüm initd/systemd ayarları. Yani bu bölüm iki kitapta tamamen farklı. Bu bölümde kitaptakileri birebir uyguladım. Türkçe klavye için initd'de /etc/sysconfig/console veya systemd'de /etc/vconsole.conf dosyasına KEYMAP=trq girdim, herhangi birşey girmeyince İngilizce klavye yükleniyor. /tmp için ayrı bir bölüm ayırdığımdan systemd kitabında 9.10.3'ü atladım.

Bölüm 10.2'de fstab ayarlanacağı için bu bölüm önemli. En başta oluşturduğum bölümleri burada fstab'a girmem gerek. /boot için ayırdığım bölüm ikinci diskte olduğundan şu anda aygıtın adı /dev/sdb1. Ama LFS'i boot etmek için birinci diski sanal makinadan ayırdığım zaman, bu bölüm /dev/sda1 olacak. Bu nedenle bu adlandırmayı kullanamam. Linux'ta her diskin eşsiz ve değişmez bir UUID'i bulunur. Bu değeri kullanmalıyım ki, /boot her zaman mount edilebilsin. Bunun için ls -la /dev/disk/by-uuid çıktısında sdb1'e linkli değeri bulurum veya:

lsblk -o NAME,MAJ:MIN,RM,SIZE,RO,TYPE,MOUNTPOINT,UUID

komutuyla diskleri UUID'leriyle birlikte listelerim. lsblk çıktısı daha ayrıntılı ama nedense chroot içinde UUID sütunu boş. Bu nedenle, komutu chroot'un dışında çalıştırdım ve değeri not alıp fstab'i oluşturdum:

/dev/mapper/vg_lfs-lv_root   /       ext4   defaults  1  1
/dev/mapper/vg_lfs-lv_var    /var    ext4   defaults  0  0
/dev/mapper/vg_lfs-lv_home   /home   ext4   defaults  0  0
/dev/mapper/vg_lfs-lv_tmp    /tmp    ext4   defaults  0  0
/dev/mapper/vg_lfs-lv_swap   swap    swap   pri=1     0  0
UUID=01234567-89ab-cdef-0123-456789abcdef /boot ext4 defaults 0 0

ve elbette initd sürümü için proc, sysfs, devpts vb. girdilerin de eklenmesi gerekiyor. Bu girdiler kitapta zaten olduğundan burada yer vermedim.


Linux Kernel'ini Derlemek

Bütün paketleri hazırladıktan sonra artık iş kerneli (çekirdeği) derlemeye kaldı. Her iki kitabın 10.3 bölümü çekirdeği derlemekle ilgili. Ben öncelikle make mrproper ve ardından make defconfig komutlarıyla çekirdeğin en standart özelliklerini seçtim. Bir önceki yazıda bunları yeterince detaylı açıklamıştım. Diğer özellikleri düzenlemek için make menuconfig'i kullanacağım.

make menuconfig metin arayüzü
systemd ayarları
initd sürümü için defconfig'de fazla değişikliğe gerek yok. uevent helper'ın kapalı ve devtmpfs desteğinin açık olması yeterli. Ama systemd sürümü için biraz daha fazla seçeneği değiştirmek gerekiyor (yanda).

Systemd'nin Errata'sında da belirtiliyor, yandaki CONFIG_SECCOMP'un yeri yanlış ama sorun değil, çünkü defconfig'de bu özellik zaten açık. Bu özellik yine de menuconfig içinde aratılabilir veya .config'den bakılabilir:

(lfs chroot) root:/sources/linux-5.13.12# grep -n SECCOMP  .config
687:CONFIG_HAVE_ARCH_SECCOMP=y
688:CONFIG_HAVE_ARCH_SECCOMP_FILTER=y
689:CONFIG_SECCOMP=y
690:CONFIG_SECCOMP_FILTER=y
691:# CONFIG_SECCOMP_CACHE_DEBUG is not set

Bu arada, bu yalnız benim kişisel görüşüm ama kendi deneyimim (irc) ve posta listesi arşivine dayanarak LFS destek kanallarındakilerin biraz ters insanlar olduklarını düşündüğüm için, bu tür hataları kendime saklamayı tercih ediyorum.

Konuya döneyim. Hedefim LFS'i LVM desteğiyle kurmaktı. LFS, en temel linux kurulumunu içeriyor, örn. pencere yöneticisi yok. Temel kurulumdan sayılmayan LVM de, (B)eyond LFS adında ayrı bir projenin parçası. BLFS kitabının LVM bölümünde, LVM için açılması gereken diğer özelliklerin listesi verilmiş. Bunları açıp modüler olanları da çekirdeğe dahil edeceğim (kişisel tercih). Bu arada LVM için Gentoo dökümanlarının ne önerdiğine bakıp, BLFS'te olmayıp Gentoo'da önerilen bir kaç özelliği daha aktifleştirdim. Eksik özelliklerden ötürü kerneli tekrar derlemektense, bir kaç KB büyük bir kernelle boot etmeyi tercih ederim.

Son olarak vmware'le çalışan okurların, bir önceki yazının ana konusunu oluşturan "Fusion MPT device support"u da aktifleştirmesi gerek. Bu, özetle vmware'deki SCSI denetleyicinin sürücüsü ve bu olmadan, çekirdek sabit diski bulamayıp boot edemiyor. Bu özellik "Device Drivers" menüsü altında bulunabilir.

Tüm bu adımları tamamladıktan sonra, LFS bölüm 10.3.1'e dönüp kerneli make'le derledim ve ardından modülleri yükledim. Benim /boot bölümü /dev/sdb1'de olduğu için mount /dev/sdb1 /boot ile boot bölümünü mount edip; vmlinuz (kernel), System.map ve config dosyalarını kopyaladım.


Önyükleyici: GRUB

Kerneli derleyip /boot'a kopyaladıktan sonra sıra GRUB'u ayarlamaya geldi. Bu aşamada iki seçeneğim var: GRUB'u /dev/sdb'ye kurabilirim (aslında başta planım buydu) veya CentOS'un GRUB'ına (/dev/sda) ekleyebilirim. Birinci seçeneğin avantajı, diskin CentOS'tan bağımsız çalışacak biçimde ayarlanıyor olması. İkincinin avantajıysa kolay olması.

Önce birinci seçenek: grub-install /dev/sdb komutunu girdim. grub.cfg dosyası kitaptakinden biraz farklı olacak:

set default=0
set timeout=5

insmod ext2
set root=(hd0,1)

menuentry "GNU/Linux, Linux 5.13.12-lfs-11.0-systemd" {
    linux   /vmlinuz-5.13.12-lfs-11.0-systemd root=/dev/vg_lfs/lv_root ro
}

set root'la ayarlanan "grub root" sıfırıncı diskin ilk bölümü. Grub açısından LFS diski, henüz sıfırıncı değil ama CentOS'un kurulduğu diski çıkarınca öyle olacak. /boot bölümü ayrı olduğundan linux satırında /boot'a gerek yok. Çekirdeğe girilen root parametresiyse, root disk bölümünün aygıt yolu (device path). Bu arada yukarıda systemd için olan ayarlar var. initd için, menuentry ve linux satırlarında "-systemd" ifadesi olmaycak.

Bunu kaydettikten sonra chroot'tan çıkıp, sanal makinayı kapattım ve birinci diski sanal makinadan çıkarttım. Makinayı tekrar açtığımda, az önce oluşturduğum grub menüsünü gördüm ve bu girdiden boot ederken kernel panic aldım. Yeey!. Tamam, her ne kadar sevinilecek bir durum olmasa da, bu iki şeyi gösteriyor: (1) grub doğru şekilde ayarlanmış, (2) kernel dosyaları düzgün derlenmiş ve /boot'a kopyalanmış.

O halde neden kernel panic aldım. Call trace'de görüldüğü gibi mount_block_root fonksiyonunda, kök dizine mount edeceği (grub'da root= ile verilen) diski bulamamış. Neden? Çünkü LVM henüz aktif olamadı. Burada yapacak birşey olmadığından çıkardığım diski geri takıp CentOS'a döndüm.

Peki her defasında LFS'i açmak için diski sökmek ve sorun olduğunda CentOS'a geri dönmek için geri takmak zorunda mıyım? Hayır! CentOS'tayken /etc/grub.d/40_custom dosyasının sonuna şu satırları ekledim:

menuentry "GNU/Linux, Linux 5.13.12-lfs-11.0-systemd" {
  set root=(hd1,1)
  linux   /vmlinuz-5.13.12-lfs-11.0-systemd root=/dev/mapper/vg_lfs-lv_root ro
}

Bu, yukarıda andığım ikinci grub seçeneği ve aslında ilk konfigürasyonun aynısı, sadece set root, menuentry içinde. Örnek yine systemd için. initd için "-systemd" kısmı olmayacak. Sonra eklediğim satırı, CentOS'un grub.cfg'sine aktardım:

GRUB_DISABLE_OS_PROBER=true  grub2-mkconfig -o /boot/grub2/grub.cfg

OS prober diğer işletim sistemlerini otomatik bulup grub'a ekleyen güzel bir özellik ama bir bug yüzünden düzgün çalışmıyor. Şimdi, işletim sistemleri arasında geçiş yapmak kolaylaştığına göre kaldığım yerden devam edebilirim.


initramfs

BLFS kitabındaki LVM bölümünü (veya systemd LVM bölümünü) açtım. About LVM'in sondan ikinci paragrafında, kök dosya sisteminde LVM kullanabilmek için initramfs gerektiği yazıyor. initramfs, temel bazı programları ve konfigürasyonları içeren sıkıştırılmış sanal bir disk. Initial RAM Filesystem'in kısaltması. Bu dosya (varsa) önyükleyici tarafından kök dizine açılıyor. İçindeki programlar ve conf dosyaları sistemin açılmaya devam etmesi için gereken işleri yapıyor. Bir çok dağıtımla gelen "rescue kernel" da aslında kabuğu içeren basit bir initramfs.

BLFS'in kendi initramfs oluşturma betiği var. LVM desteğini initramfs'e eklemek için öncelikle lvm yüklü olmalı. Bunun için de;

1) Önce LVM'in önkoşulu olan libaio
2) which sadece LVM için değil, sorun bulmada da çok işe yarayan bir araç
3) mdadm. Testleri atlanabilir, zaten kitaptaki komutla çalışmıyorlar.*
4) initramfs'i sıkıştırmak için cpio
5) LVM. Bunun da testleri uzun sürüyor ve bazıları sorunlu. LVM'i kitaptaki --with-thin* ve --with-cache* parametreleri yanısıra --with-vdo=none ile konfigüre ettim. Kritik olmasa da systemd için LVM'de fazladan bir komut var.
6) ve initramfs betiği yüklü olmalı.

* Test etmedim ama sanıyorum mdadm olmadan da bu sistem çalışabilir.

initramfs betiği iki parçadan oluşuyor. Birinci parça betiğin kendisi ve ikinci parça init.in adında, initramfs'e init adıyla kopyalanacak olan dosya. LFS v10.1'de bu betik sorunluydu (bug). initramfs'te de mutlaka bulunması gereken coreutils ve util-linux'un bileşenlerini (ls, cp, mount, umount vb.), /bin yerine /usr/bin'de aradığı için hata verip sonlanıyordu. Betiğin /usr/lib'de aradığı ama aslında /lib'de olan dosyalarda da aynı sorun vardı. Çözüm olarak dosyaları /usr/bin ve /usr/lib'de olmaları gereken yere linklemiştim. Bu bug v11'de düzeltilmiş.

Betiğe kernel sürümünü parametre olarak girdiğim için, kernel modüllerini (.ko dosyaları) de initramfs'e ekleyerek dosyayı oluşturdu.

(lfs chroot) root:~# mkinitramfs 5.13.12
Creating initrd.img-5.13.12... done.

Dosyayı /boot'a kopyaladım (bölüm bağlı olmalı) ve chroot'tan çıkmadan önce /boot/grub/grub.cfg'yi aşağıdaki gibi değiştirdim:

menuentry "GNU/Linux, Linux 5.13.12-lfs-11.0-systemd" {
  linux /vmlinuz-5.13.12-lfs-11.0-systemd root=/dev/vg_lfs/lv_root ro
  initrd  /initrd.img-5.13.12
}

CentOS'un grub'unu kullandığım için aslında yukarıda yaptığım konfigürasyon yalnız CentOS diski söküldüğü zaman çalışacak. chroot'tan çıkıp, eklediğim satırı /etc/grub.d/40_custom dosyasına da ekleyip, tekrar şu komutu çalıştırdım:

GRUB_DISABLE_OS_PROBER=true  grub2-mkconfig -o /boot/grub2/grub.cfg

Makinayı yeniden başlattım. LFS'i seçtim ve sonuç:

ve systemd'li kurulum için aynı sonuç:

systemd'li makinada çalışmayan bazı servisler olsa genel olarak ikisi de sorunsuz açılıyor.

4 Aralık 2021 Cumartesi

Linux Kernel'ini Vmware için Derlemek


Merhaba. Bu yazıda linux kernel (çekirdek) derleme hakkında genel bilgiler verip, karşılaştığım bir sorunun çözümünü ele alacağım. Bu aslında sonraki yazının bir bölümü olacaktı, ama sorun o kadar uğraştırdı ki, ayrı bir yazıda ele almaya değeceğini düşündüm. Başlık zaten sonraki yazı hakkında ipucu verecektir.


Linux'ta Kernel Derlemek

Kernel derlemek, özel bir cihazla çalışmıyorsanız (örn. gömülü (embedded) sistem veya standart kernel'ın desteklemediği çok yeni bir donanımla) veya LFS gibi deneysel veya Gentoo gibi kernel'ın derlenmiş olarak gelmediği bir dağıtım kullanmadıkça, gündelik kullanımda yapılması gereken birşey değil. Öte yandan, öğrenmek veya sisteme en düşük seviyede hakim olmak için bence bilinmesi mutlak gerekli. Zaten bir kaç tane önkoşulu yerine getirip, temelleri bildikten sonra, herhangi bir kaynak kodu derlemekten daha kolay.

Ben bu yazıda kernel v5.13.12'yi derleyeceğim. İhtiyacım olan öncelikle kaynak kod. Bu arada kernel sürümünün önemli olduğu tek yer .config dosyası, buna da ileriki paragraflarda değineceğim. Tüm kernel'lar www.kernel.org/pub adresinde linux/kernel alt dizininden ulaşılabilir. Daha küçük olduğu için yukarıda .xz dosyanın bağlantısını verdim ama aynı dosyanın .gz sürümü de bulunabilir.

Bu dosyayı indirip açtım. Elbette bir dosyayı derlemek için make, gcc ve gcc-c++ gibi olması zorunlu programlar makinamda zaten yüklüydü. Eksik olan tek paket ncurses-devel'di. Bu aslında zorunlu değil, make menuconfig için gerekli ama menuconfig, kernel özelliklerini seçerken kolaylık sağlıyor. kernel'in bağımlılıkları bunlarla sınırlı değil, v5.13 sürümü için ayrıntılı liste kernel.org dökümantasyonunda var. Ama listedeki tüm paketlerin yüklü olması zorunlu değil, örn. PPP'yle ilgili birşey derlemeyecekseniz, pppd'ye gerek yok veya OpenSSL yalnız kernel modülleri imzalanacaksa gerekli.

Derlemeye geçmeden önce bazı ön bilgiler: make clean bilindiği gibi derlenen dosyaları silip kodu baştan derlemeye hazır hale getirir. linux'a özgü make mrproper ise .config dosyasını ve diğer ayarları da siler [1]. Bu yüzden ben de başlamadan önce bu komutla dizini temizledim ve sonra make defconfig ile en standart ayarları aktifleştirdim. Sözünü ettiğim bu ayarlar .config dosyasında tutuluyor, dolayısıyla defconfig aslında standart .config dosyası oluşturuyor. Bu ayarlar bir metin düzenleyiciyle görülebilir veya make menuconfig komutuyla, ncurses TUI kullanarak düzenlenebilir. .config dosyasında 4845 satır olduğundan tüm ayarları bu dosyadan elle yapmak hiç mantıklı değil.

.config dosyası her kernel sürümü için farklıdır, çünkü her minör sürümde bir kaç özellik eklenir veya çıkarılır. Haliyle, o sürüme uyumlu olmayan bir .config dosyası kullanarak derleyemem. Ama daha eski bir kernel'a ait .config dosyasını, yeni kernel'a make oldconfig komutuyla uyarlayabilirim. Bu komut, kullanıcıya yalnız .config'de olmayan (o kernel'a yeni eklenmiş) seçenekleri sorar. Tabi ki eski .config derken, v2.6 config'ini v5.13'e uyarlamak pek mantıklı iş değil.

make menuconfig metin arayüzü

make menuconfig komutunu çalıştırdım ve yukarıdaki menü çıktı. Ana menüdeki "--->" işaretli satırlar bir alt menüyü gösteriyor. Bu menüdeki tüm satırlar (eğer bir alt menüsü yoksa) .config'deki bir satıra karşılık geliyor. Kernel belgelerinde, açılması gereken özellikler yanında [*] ve açılmaması gerekenler yanında [ ] işaretiyle veriliyor. [M] ise özelliğin modüler olduğu anlamına geliyor, bunu daha sonra açıklayacağım. Örn. aşağıdaki görselde LFS'i systemd ile çalıştırmak için gereken kernel seçenekleri verilmiş:

LFS systemd kernel seçenekleri


Örneğin "Auditing Support", "Control Group support" ve "Configure standard kernel ..." gibi özellikler "General setup" altında; "open by fhandle syscalls" ise "Configure standard kernel ..."ın altında. Satır sonunda kareli parantez içinde verilen adlar, özelliğin .config içindeki maddesi (item). Bu arada, "Processor type and features" altında olması gereken "Enable seccomp ..."u bulamadım (kaynak hatalı). Bu özellik, Kernelconfig.io'ya göre v5.13'te "General architecture-dependent options" altında, ama eski sürümlerde mesela v5.9.9'da "Processor type and features" altında. Bu maddeler, menuconfig'deyken '/'a basıp, adlarıyla aratılabilir. Ancak ben menuconfig'den değil, .config dosyasından da bu ayarı değiştirebilirim (aşağıda). Bu arada özelliklerin genelde (her zaman değil) ilk harfi, aynı zamanda onun klavye kısa yolu, yani "Processor type..."a gidip "E" ye basarak, "E" ile başlayıp o an ekranda görünen seçenekler arasında gezebilirim.

$ grep -n SECCOMP .config
687:CONFIG_HAVE_ARCH_SECCOMP=y
688:CONFIG_HAVE_ARCH_SECCOMP_FILTER=y
689:CONFIG_SECCOMP=y
690:CONFIG_SECCOMP_FILTER=y
691:# CONFIG_SECCOMP_CACHE_DEBUG is not set

Bu değer 689. satırda zaten açılmış. Gerekirse 'y'yi 'n' yapıp kapabilirim. Tüm bu özellikler .config dosyasında var. Sözünü ettiğim [M] içinse aşağıda bir örnek var:

BLFS: LVM için kernel seçenekleri


Bazı kernel seçeneklerinde bulunan M, özelliğin kernel modülü olarak derlenmesi anlamına geliyor. Bütün kernel fonksiyonları, /boot dizininde derlenmiş durumda bulunan vmlinuz-... adlı kernel dosyasında bulunmak zorunda değildir. Bu dosyayı olabildiğince küçük tutmak için, bazı fonksiyonlar modül olarak /usr/lib/modules altında tutulur, ve yalnız ihtiyaç olursa yüklenir. Bu sayede, örn. LVM diski olmayan bir makinada LVM modüllerinin veya ses kartı olmayan bir sanal makinada ses kartına ait modüllerin boşuna bellekte tutulması gerekmez.

Modül dosyaları .ko (kernel object) uzantılı dosyalardır. insmod veya modprobe ile yüklenir, lsmod'la listelenirler. Modüller, kernel'a sonradan eklenecek işlevlerin, kernel'ı tekrar derlemeye gerek kalmadan kullanılmasını sağlar [2]. Modüllere ait ayarlar /etc/modprobe.d/ dizinindeki .conf dosyalarında tutulur. Bu dosyalarla modüllere parametre girilebilir veya yüklenmesi engellenebilir. Kernel modülleri burada ele alabileceğimden çok daha geniş bir konu.

menuconfig yardımıyla veya elle istediğim seçenekleri ayarladıktan sonra make diyip yeni kernel'ı derledim. Tabii ki bu anında olmuyor. Benim bilgisayarımda standart seçeneklerle derlemek, yaklaşık 5dk sürüyor. Bazı seçenekler standartta modül olarak derlendiğinden, make'ten sonra make modules_install ile bu dosyaların o kernel'a karşılık gelen modül dizinine kopyalanması gerekiyor.

Derleme bittikten sonra, arch/x86/boot/bzImage (kernel) dosyası /boot altına vmlinuz-... adıyla kopyalanabilir ve grub yardımıyla açılabilir. Bu arada her dağıtım kernel'ı derlerken kullandığı .config dosyasını da /boot altına config-$(uname -r) adıyla kopyalar. Bu demektir ki, örn. kullandığım linux kernel'ının aynısını derlemek istersem, /boot'taki config dosyasını kernel dizinine .config adıyla kopyalayıp make edebilirim. Veya Fedora'nın v5.13.12 kernel'ını derlemek istersem, build sistemi koji'den kernel paketini aratır, x86_64 mimarisi için olan kernel-core paketini indiririm. Sonra rpm2cpio kernel-core-5.13.12-200.fc34.x86_64.rpm | cpio -idmv komutuyla açarım ve içinden çıkan lib/modules/5.13.12-200.fc34.x86_64/config dosyasını .config olarak linux-5.13.12 dizinine kopyalayıp, derlerim. Sonunda ortaya çıkan kernel Fedora'nın aynısı olacaktır (ek okuma: How to install a kernel from koji). Ancak Fedora gibi her makinada çalışması için üretilmiş bir kernel'da gerekli/gereksiz bir çok özelliğin açık olması, (örn. her ne kadar modüler de olsa Fibre Channel), derleme süresini aşırı arttırır. Öyle ki benim bilgisayarımda 5-6 saatte derlendi.

Bu kadar ön bilgiden sonra benim yaşadığım sorun ve çözümüne değinebilirim.


Vmware için Linux Kernel'ını Derlemek

Önce biri VBox'ta biri vmware'de iki makina kurdum. Bunlar ufak farklar dışında aynıydı. Yukarıda anlattığım gibi, standart config üzerine bir kaç özellik ekleyip kernel'ı derledim, /boot dizinine kopyaladım, initramfs'i ve bunları yükleyecek grub konfigürasyonunu oluşturdum. Buraya kadar herşey sorunsuz ilerledi.

Sonra sanal makinaları yeni kernel'la açtığımda VBox'taki çalışırken, vmware'deki çalışmayıp, initramfs'teki rescue shell'e düştü. Yandaki ekran görüntüsünden de görüleceği gibi, /dev altında sd* diskleri yok ve /dev/mapper boş. Bu olağandışı bir durum. Her iki makinada iki disk, ve disklerden birinde CentOS var. İkisini de CentOS'la tekrar açtım, çünkü sorunu tanılamaya yardımcı olacak lsblk, lspci gibi herhangi bir araç initramfs'te yok (Ubuntu Live da bu işi görecektir).

Aşağıdaki çıktıdan görüleceği gibi her iki makinadaki aygıtlara ve bunların kernel modüllerine lspci -k komutuyla baktım.

Vmware'de ata_piix, vmw_vmci, pcieport gibi genel geçer modüllerin yanısıra mptspi diye bir modül var, hem de SCSI denetleyicisi için. Alt taraftaki VBox çıktısındaysa ilginç herhangi bir sürücü yok. SATA controller olarak standart ahci kullanılıyor, mptspi bulunmuyor. Bu modülü biraz araştırdım. mptbase, mptscsih, mptspi sürücülerine ait konfigürasyon "Fusion MPT ScsiHost drivers for SPI" [3] olarak geçiyor, bu da kernelconfig.io'ya göre "Device Drivers/Fusion MPT device support" menüsü altında bulunuyor ve .config'de adı CONFIG_FUSION. Standart .config'de AHCI açık gelirken FUSION kapalı geliyor. Elbette bu seçenek CentOS'ta açık olduğundan onda bir sorun yaşanmıyor.

$ grep AHCI .config
CONFIG_SATA_AHCI=y
# CONFIG_SATA_AHCI_PLATFORM is not set
# CONFIG_SATA_ACARD_AHCI is not set

$ grep FUSION .config
# CONFIG_FUSION is not set

İşte sorunun kaynağı. Tekrar make menuconfig diyip, bu sürücüyü de seçtim. Makina sürekli vmware altında çalışacağından, modül olarak değil kernel'a dahil ederek bir daha derledim. Yeni kernel'i (arch/x86/boot/bzImage) /boot altına kopyalayıp makinayı yeniden başlattığımda makina düzgünce açıldı.




Kaynaklar:
[1]: Why both make clean and make mrproper are used?
[2]: https://en.wikipedia.org/wiki/Loadable_kernel_module
[3]: https://cateee.net/lkddb/web-lkddb/FUSION_SPI.html
 - : https://www.youtube.com/watch?v=WiZ05pnHZqM

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

21 Ağustos 2021 Cumartesi

VirtualBox'ta FreeDOS Invalid Opcode Problemi ve Makinaya Ağdan Dosya Aktarmak


Merhaba. Daha önceki yazılarda (özellikle disk ve dosya sistemi yazılarında) onlarca kere FreeDOS kurup üzerine konuştum. Bu kurulumlarda önceden hiç sorun yaşamadım.

Bu yılın başında, eski işletim sistemimi silip Fedora kurdum ve repoların da güncel olması sayesinde kullandığım programların son sürümlerini yükleme şansım oldu. Bunlardan biri de VirtualBox. Daha önceden 5.x sürümünü kullanırken şu anda v6.1.20 sürümü yüklü. Bu yazıyı hazırladığım sırada en güncel sürümü v6.1.22'ydi. Ancak bu yeni VBox'ta FreeDOS'u açarken komut satırına bile düşmeden sistem çöktü ve sorunun çözümünü araştırmam gerekti. İşte bu yazıda bunun çözümüne değineceğim.

FAT32 yazısı için yeni bir makina kurmak istiyordum. Makina olarak DOS seçtim. Zaten adında DOS geçen bir sanal makinada DOS otomatik seçiliyor. Diskin garanti FAT32 olması (>2GB) için de 4 GB'lik bir disk oluşturdum. FD12CD.iso'yu takıp boot eder etmez başlıkta sözünü ettiğim "Invalid Opcode" hatasını aldım:


Aslında hata HIMEMX.EXE dosyasında oluşuyor. İlkin boot ederken F8'le adım adım onaylama (trace) moduna girip, hatanın hangi adımda ortaya çıktığını buldum. Ardından makinayı tekrar resetleyip adım adım onaylama modunda bu dosyayı geçtim ancak kurulumdaki metin arayüz de .bat dosya komutlarıyla oluşturulduğundan kurulumda sorun oluyor.

Görünüşe göre bu bilinen bir hata ve ilk olarak 2018'de bildirilmiş. Ben önceki kurulumları eski sürüm VBox'ta yaptığımdan sorun olmamıştı. Bu arada önceden hangi sürümü kullandığımı hatırlamasam da muhtemelen 5.x'ti. Ve arada majör sürüm farkı olmasına rağmen sorun ilk önce VBox v5.2.10'da bildirilmiş.

Çözüm
Hata FreeDOS'ta mı yoksa VBox'ta mı anlayamadım, çünkü FreeDOS, Vmware'de sorunsuz boot ediyor ama diğer yandan hata kaydında FreeDOS v1.3'te* bu sorunun çözüleceği ifade ediliyor. Sorunun iki farklı çözümü var: (1) Makinayı FD12CD.iso yerine FD12LGCY.iso'yla açmak veya (2) çekirdek parametrelerine "raw" ifadesini eklemek. Ben legacy iso'yu kullanarak boot ettiğimde bir uyarı almama rağmen kurulumda sorun yaşamadım. Uyarı şöyleydi: "Unfortunately, this method of installation is not supported on this hardware platform. Please try a different installation method.". Ekran görüntüsü yukarıda (bu uyarıyı vmware'de almadım). Her ne kadar makina uyarı verse de komut satırına düştüğünde "fdisk"le önce diski bölümledim, makinayı yeniden başlattım ve "setup" komutuyla kuruluma başladım.

İkinci seçenekte, makinayı FD12CD.iso ile başlattım ve "Welcome to FreeDOS 1.2" ekranında Tab'a basıp, satır sonunda bir karakter boşluk bırakarak "raw" ekledim ve Enter'la devam ettim. Bu durumda kurulum sihirbazı otomatik başladı ama önce diski bölümlemem gerektiğinden ikinci adımda "Return to DOS"u seçip kurulumdan çıktım.

Disk bölümlemeyi kurulum sihirbazına bırakınca 4 GB'lik diski otomatik olarak 2x 2GB'lık FAT32 bölüme ayırıyor. Ben tek bir bölüm oluşturmak için bölümlemeyi elle yapacağım. Prompt'a düştükten sonra fdisk'i çalıştırıp FAT32 desteğiyle ilgili soruya Y dedim. 1, 1, Enter, Esc tuş kombinasyonuyla 4GB'lik bir disk bölümü oluşturup makinayı yeniden başlattım. Kurulum sihirbazında (Legacy CD'yle boot edildiği durumda önce setup komutunu girip) "Erase and format drive C:"yi ve sonra "Full installation"ı seçtim. Kurulum bittikten sonra CD'yi çıkarıp makinayı yeniden başlattım. Diski elle formatlamanın bir getirisi olmuyor çünkü bilinmeyen bir nedenden dolayı sihirbaz formatlanmış diski kendi tekrar formatlamak istiyor.

Invalid opcode hatasının çözümü bu kadar.

* Yazıyı hazırladığım sırada FreeDOS posta listesinde v1.3 RC4'ün yayınlanacağı duyuruldu. En son kontrol ettiğimde freedos.org da güncellenmişti.


FreeDOS Makinasına Dosya Kopyalamak
İkinci sorunu FreeDOS'a dosya kopyalamak isterken yaşadım. Normalde dışarıdan dosya kopyalamak için makinayı Damn Small Linux (DSL) ile başlatıp SSH ile dosya kopyalıyordum. Yeni Fedora'da güvenli olmayan SSH cipher'ler kaldırıldığı için (sanırım) DSL'e bağlanamadım.

Öncelikle sorunu ele alırken önümde bir kısıt vardı: VBox'ta NAT'lanan sanal makina, hosta (sanallaştıran) ulaşabilirken; host, sanal makinaya ulaşamıyor veya belki ben yapamadım. Vmware'de her sanal ağa karşılık, hostta sanal bir ağ kartı oluşturuluyor ve bu karta atanan IP'yle ilgili ağa ulaşılabiliyorken VBox'un NAT'ında bu olanaklı değil. Bu yüzden DSL kullanırken, sanal makinadan hosta bağlanıyordum (veya Host-only Network oluşturuyordum). Şimdi aynısını yapmak istersem DSL'de bulunan cipher'leri güncellemem gerek. Bu bir kenarda dursun.

Öte yandan daha önce bahsetmemiştim ama FreeDOS aslında zengin ağ araçlarıyla geliyor: Ağ kartı aygıt sürücüsü, TCP/IP stack, ethtools (paket çözümleyicisi), mtcp (dhcp, irc, ftp, netcat, sntp vb. araçlar) ve sshdos. Çok umutlu olmasam da belki FreeDOS'tan Fedora'ya ssh yapabilirim diye düşündüm. ssh yapabilirsem, scp de yapabilirim. Sözünü ettiğim araçları yüklemek için FreeDOS CD'sini (hangisi olduğu fark etmez) taktım ve

fdimples

komutunu verdim. Bu, FreeDOS paket yöneticisinin metin arayüzünü açıyor. Buradan "Networking"e gidip fdnet, mtcp ve sshdos paketlerini seçtim (Tab ve boşluk tuşuyla). curl ve wget de dosya transferi için kullanılabilir ama dosya internette değil diskimde ve httpd yüklemek istemiyorum. FreeDOS'ta rsync de var ama ssh desteklemiyor.


Paketleri seçip Tab, Tab, Enter tuş kombinasyonuyla yükledim ve CD'yi çıkarıp makinayı resetledim. Makina açılırken "VirtualBox network detected" diyip DHCP'den aldığı IP'yi gösterdi. SSH dizinine geçip sshdos komutunu verdiğimde (yanda) yalnızca 3des ve blowfish cipher'larının olduğunu gördüm. Fedora'da /etc/ssh/sshd_config'de "Protocol 2,1" ve "Ciphers 3des-cbc" satırlarını ekleyip sshd'yi yeniden başlatsam da "Unsupported remote protocol version" hatasını geçemedim.

Çözüm
Hosta ssh ile ulaşamıyorum. Hosttan sanal makinaya da ulaşamıyorum. Önce hosttan sanal makinaya ulaşmak için ağ ayarlarını değiştirmeliyim:

Sanal makina ağ ayarları

Bu adımda üç seçeneğim var:
1- Host-only Adapter: Bu, sanal makinayı hostun da içinde bulunduğu bir ağa bağlar fakat önce VBox Manager'la bu ağın oluşturulması gerektiği için bu seçeneği atlıyorum.
2- Bridged Adapter: Sanal makina, hostun bulunduğu ağa doğrudan bağlanır. Bu zahmetsiz bir çözümdür ama iş yeri gibi yüksek güvenlikli ağlarda sorun olabilir. Birden fazla ağ kartı olan bir makinada, hangi kart ağa bağlıysa onu seçmek gerekir. Bir de NAT'tan bu ayara geçiliyorsa, makinanın yeni IP alması için \FDOS\bin\fdnet.bat'ı çalıştırmak veya makinayı yeniden başlatmak gerek.
3- NAT + Port Forwarding: Hosttan sanal makinaya bir port açılır, bu porta gelen paketler sanal makinaya iletilir. Bunu ayarlamak için, sanal makina ağ ayarlarında NAT seçiliyken, Advanced açılır ve "Port Forwarding"den Host ve Guest Port'a boşta olan bir port değeri girilir. Guest, DOS olduğundan Guest Port herhangi bir port olabilir. Host Port seçilirken 1024'ten büyük olması tercih sebebi olup, kullanımdaki portlar linux'ta netstat -tln (windows'ta netstat -anl olması lazım) çıktısından kontrol edilebilir. Örn. ben ikisini de 1500 seçtim.

Ağ ayarlarını değiştirdikten sonra sanal makinaya ulaşabilirim ama veriyi nasıl alacağım? mtcp paketinde FTP sunucusu var ama onu ayarlamaktansa ben netcat kullanmayı tercih ederim. Fedora'da nc zaten yüklüydü (nmap-ncat paketinden). Windows'ta da nmap portable'la geliyor. Bağlantıyı test için telnet yeterli olsa da dosya aktarımı için nc'nin yüklü olması gerek. Önce test için MTCP dizininde

nc -listen 1500

komutunu çalıştırdım. Buradaki 1500, "Guest Port" değeri (eğer Guest Port ve Host Port değerleri birbirlerinden farklıysa). Sonra kendi makinamda (NAT seçeneğinde) aşağıdaki komutu çalıştırdım:

nc localhost 1500

"Bridged Adapter" seçeneğinde, "localhost" yerine makinanın IP'si gelecek. Birşeyler yazıp Enter'a basınca yazılanların DOS'ta görünüyor olması gerek. Ctrl+D ile nc'yi sonlandırıp bu sefer DOS'ta dosya aktarımı için şu komutu girdim:

nc -bin -listen 1500 > DOSYA.ADI

ve linux'ta;

nc localhost 1500 < DOSYA.ADI

komutuyla dosyayı aktardım. Her iki makinada da dosyaların md5'lerini md5sum komutuyla kontrol edebilirim.


Alternatif Çözüm (SSH)
Sanal makinanın ağ bağlantısını önceki gibi (NAT veya Bridged) değiştirdim. NAT için "Guest Port"u 22 yaptım. Bu değer zorunlu değil ama 22 olsa iyi olur. Makinayı DSL ile açtım ve DSL menüsünden sshd'yi çalıştırdım (yanda). Bir de terminalden dsl kullanıcısına sudo passwd dsl komutuyla bir şifre verdim. "Bridged Adapter" konfigürasyonu yapıldıysa, makinanın IP'si masaüstünde sağda görülüyor. NAT konfigürasyonunda bu IP 10.0.2. ile başlayan NAT IP'si olmalı ama zaten NAT için localhost'a bağlanacağım.

Bu arada NAT'ta "Guest Port" 22'den farklıysa, sudo nano /etc/ssh/sshd_config komutuyla ayar dosyasındaki Port satırını değiştirip (örn. Port 1500), sudo /etc/init.d/ssh restart komutuyla değişikliği uygulamak gerekiyor. Bence NAT ayarlarında Guest Port'u 22 yapmak daha zahmetsiz.

Makinalar birbirini pingleyebilse bile SSH her iki yönde de sorunlu. DSL'den hosta bağlanamıyorum. Her ne kadar ssh -Q ciphers komutuyla makinamda desteklenen şifreleme algoritmalarını listeleyip, DSL'de bunları ssh komutuna -c parametresiyle verdimse de bağlanamadım. Bazı algoritmalar için "Unknown cipher type" hatası aldım. Bu da demek oluyor ki, DSL'deki ssh en yeni algoritmaları içermiyor. Fedora, elbette DSL'den daha güncel. DSL'de eksik olan bir cipher'i tamamlamaya uğraşmaktansa, sorunu Fedora tarafında çözmek daha kolay. Umduğum, Fedora'nın hala eski algoritmalarla bağlanmaya izin vermese de bağlantı kurarken bunları desteklemesi. Bu arada DSL'deki ssh de o kadar eski ki, -Q parametresini desteklemiyor. Bağlanmayı deneyelim:

[user@host ~]$ ssh -p 1500 dsl@localhost
Unable to negotiate with 127.0.0.1 port 1500: no matching key exchange method found. Their offer: diffie-hellman-group-exchange-sha1,diffie-hellman-group1-sha1

Bu arada yukarıdaki komut "Bridged adapter" networkte ssh dsl@<VM_IP> şeklinde olmalı. DSL, yukarıdaki çıktıda listelenen key exchange metotlarının kullanılmasını istiyor. O halde, bunu elle girip tekrar deniyorum:

[user@host ~]$ ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 -p 1500 dsl@localhost
Unable to negotiate with 127.0.0.1 port 1500: no matching cipher found. Their offer: aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,arcfour,aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se

Bu sefer de listelenen cipher'ların kullanılmasını istiyor. Bunu de elle giriyorum:

[user@host ~]$ ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 -c aes128-cbc -p 1500 dsl@localhost
The authenticity of host '[localhost]:1500 ([127.0.0.1]:1500)' can't be established.
RSA key fingerprint is SHA256:[SNIP].
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
[SNIP]

Sonunda bağlandı! O halde, aynı parametreleri scp'ye vererek dosyayı kopyalayabilirim, ancak ssh'daki -p parametresi scp'de -P olmalı:

[user@host ~]$ scp -oKexAlgorithms=+diffie-hellman-group-exchange-sha1 -c aes128-cbc -P 1500 dosya.adi dsl@localhost:~
dsl@localhost's password:
dosya.adi                   100%  543   530.9KB/s   00:00  

Son olarak dosyayı diske değil dsl kullanıcısının ev dizinine kopyaladığım için, tekrar DSL'in terminaline girip:

sudo su
mount /dev/hda1 /mnt
cp dosya.adi /mnt
umount /mnt

komutlarıyla dosyayı FreeDOS'un kurulu olduğu diske aktarırım.

Bir diğer alternatif çözümle, FreeDOS'un wikisinde karşılaştım: Eğer sanal disk formatı .vhd ise (MS HyperV formatı) ve libguestfs-tools yüklüyse, sanal disk guestmount komutuyla mount edilebiliyor, ancak bu paket Fedora'da bağlı paketleriyle birlikte 137 MB kurulum alanı istiyor. Windows'lar .vhd dosyaları kendiliğinden disk gibi açabiliyor. Elbette dosyalar qemu-img komutuyla da vhd, vdi (VBox) ve vmdk (vmware) formatları arasında dönüştürülebilir ancak bu aşırı zahmetli olduğundan tercih edilebilir bir çözüm değil.


Kaynaklar:
(metnin içindeki bağlantılar dışında)
http://wiki.freedos.org/wiki/index.php/VirtualBox_-_Chapter_4
http://wiki.freedos.org/wiki/index.php/VirtualBox_-_Chapter_6
How to enable diffie-hellman-group1-sha1 key exchange on Debian 8.0?