Arrencada (I) · BIOS
Introducció
A la sessió de teoria hem seguit el camí que fa una màquina des del reset fins al primer codi que controlem. En aquest laboratori el recorrerem amb les nostres mans: construirem un bootloader de dues etapes per a BIOS i el veurem funcionar amb un depurador.
BIOS ──llegeix LBA 0──► 0x7C00 Stage 1
│
│ INT 13h (AH = 0x42)
▼
LBA 2048 ─────────────► 0x8000 Stage 2Tot es fa amb QEMU: no s’arrenca ni es modifica cap ordinador real. Treballem amb un sol fitxer, disk.img, que QEMU presenta al BIOS com si fos un disc.
En aquest laboratori QEMU emula una màquina x86 amb BIOS. El nostre Stage 1 i Stage 2 s’executen en mode real de 16 bits: no estem fent un programa x86-64. Emularem amb qemu-system-i386.
- Construir un boot sector de 512 B amb la signatura
55 AA, escriure’l al sector 0 d’una imatge de disc i comprovar-lo ambhexdump. - Arrencar-lo amb QEMU i observar amb GDB què fa el BIOS: on carrega el codi i com hi transfereix el control.
- Implementar un Stage 1 que carregui un Stage 2 des de
LBA 2048ambINT 13h AH = 0x42i hi salti. - Distingir entre carregar i executar codi.
nasm (inclou ndisasm), qemu-system-i386, gdb, i les utilitats dd, hexdump i wc.
Instal·leu els paquets amb el gestor del vostre sistema. A Debian/Ubuntu, l’ordre exacta depèn de si el processador de la vostra màquina (no el de la VM: QEMU emula x86 igualment en tots dos casos) és x86_64 o ARM, perquè el paquet de GDB que necessiteu no és el mateix:
sudo apt-get update
sudo apt-get install nasm qemu-system-x86 gdb build-essential -ysudo apt-get update
sudo apt-get install nasm qemu-system-x86 gdb-multiarch build-essential -yEl gdb de les distribucions ARM sol incloure suport només per a l’arquitectura del maquinari on corre; gdb-multiarch hi afegeix la resta, incloent-hi x86.
Preparació
Creeu una carpeta de treball i comproveu que teniu les eines:
mkdir -p ~/lab-arrencada
cd ~/lab-arrencada
nasm --version
qemu-system-i386 --version
gdb --versionmkdir -p ~/lab-arrencada
cd ~/lab-arrencada
nasm --version
qemu-system-i386 --version
gdb-multiarch --versionFeu tot el laboratori en aquesta carpeta. Cada fitxer que crearem (boot.asm, second_boot.asm, disk.img, …) hi ha de ser.
| Element | Què fa |
|---|---|
mov dst, src |
copia src a dst |
xor ax, ax |
posa AX a 0 |
lodsb |
AL = [DS:SI] i SI = SI + 1 |
test al, al + jz etiqueta |
salta si AL és 0 |
int 0x10 / int 0x13 |
crida un servei del BIOS (vídeo / disc) |
call etiqueta / ret |
crida i retorn d’una subrutina (fa servir la pila) |
cli / sti |
desactiva / activa les interrupcions |
hlt |
atura la CPU fins a la propera interrupció |
jmp 0x0000:etiqueta |
salt far: també fixa CS |
db / dw / dq |
reserva 1 / 2 / 8 bytes de dades |
[org 0x7c00] |
l’adreça que NASM utilitza per calcular les adreces del codi i de les dades (a la teoria, la link address; aquí no hi ha enllaçador: NASM genera directament un binari pla) |
times 510 - ($ - $$) db 0 |
omple de zeros fins al byte 510 |
Registres que farem servir: AX (AH/AL), BX, DS, ES, SS, SP, SI i DL.
Part A — El disc i Stage 1
El disc virtual
Per a QEMU, un disc és un fitxer. Creeu-ne un de 10 MiB ple de zeros:
dd if=/dev/zero of=disk.img bs=512 count=20480
ls -lh disk.img
hexdump -C -n 64 disk.imgdd només és l’eina que copia bytes: el que ens interessa és el resultat, un fitxer de 20480 sectors de 512 bytes.
dd escriu on li digueu. Comproveu sempre que of= és un fitxer (disk.img) i mai un dispositiu com /dev/sda: si us equivoqueu, podeu destruir dades reals.
Què hi ha ara al sector 0? Podria arrencar aquest disc? (hexdump substitueix les línies repetides per un *.)
Pots veure el raonament
Només zeros. El BIOS necessita que el sector 0 acabi en 55 AA per considerar-lo arrencable; aquest disc no l’arrencaria (ho comprovareu a l’activitat posterior).
Stage 1
Aquest és el nostre primer boot sector. Guardeu-lo com a boot.asm:
[org 0x7c00]
bits 16
jmp 0x0000:start ; far jump: força CS = 0x0000
start:
cli ; sense interrupcions mentre canviem SS:SP
xor ax, ax
mov ds, ax ; DS = 0
mov es, ax ; ES = 0
mov [boot_drive], dl ; guardem la unitat d'arrencada (DL)
mov ss, ax ; SS = 0
mov sp, 0x7c00 ; pila: creix cap a adreces més baixes
sti
mov si, msg_stage1
call print
.halt:
hlt ; dorm fins a la propera interrupció
jmp .halt
; print: escriu la cadena de DS:SI (acabada en 0) amb INT 10h, AH = 0x0E
print:
.next:
lodsb ; AL = [DS:SI], SI = SI + 1
test al, al ; AL == 0 ?
jz .done
mov ah, 0x0e ; teletype
mov bx, 0x0007 ; pàgina 0, color gris
int 0x10
jmp .next
.done:
ret
boot_drive db 0
msg_stage1 db "Stage 1: hola!", 13, 10, 0
times 510 - ($ - $$) db 0
dw 0xaa55Recordeu la sessió de teoria:
jmp 0x0000:startés el far jump que fixaCS = 0.- Inicialitzem
DS,ES,SSiSPperquè no podem assumir cap valor, i guardemDL(la unitat d’arrencada). printescriu una cadena ambINT 10h; per poder fercall/retnecessitem la pila que acabem de configurar.boot_driveés una dada: va després del codi que s’executa, mai enmig.
Assemblar i escriure al sector 0
Un boot sector és simplement un binari de 512 bytes que acaba escrit al sector 0 del disc. Assemblem el fitxer i el copiem allà.
nasm -f bin -o boot.bin boot.asm
wc -c boot.bin
dd if=boot.bin of=disk.img bs=512 count=1 conv=notrunc
hexdump -C -s 510 -n 2 disk.imgSortida esperada:
512 boot.bin
000001fe 55 aa |U.|dd if=FITXER of=disk.img bs=512 seek=SECTOR conv=notrunc copia FITXER al disc a partir del sector SECTOR (el 0 si no hi ha seek). conv=notrunc evita que dd escurci disk.img. No cal memoritzar-la: la reutilitzarem canviant només FITXER i SECTOR.
Al codi hem escrit dw 0xaa55, però hexdump mostra 55 aa. Per què?
Pots veure el raonament
Perquè x86 és little-endian: el byte menys significant (0x55) es guarda primer. La signatura, en memòria i al disc, és la seqüència de bytes 55 AA.
Primera execució
qemu-system-i386 -drive file=disk.img,format=rawqemu-system-i386 -drive file=disk.img,format=raw -nographicSi treballeu només per terminal, afegiu -nographic: la sortida apareix a la terminal i es surt amb Ctrl+A i després X.
Hauríeu de veure, a la finestra de QEMU, el missatge Stage 1: hola!. Tanqueu la finestra per sortir.
Part B — Mirar l’arrencada amb GDB
Preparació
Creeu un petit fitxer d’ajuda perquè GDB mostri només els registres que ens interessen:
cat > lab.gdb <<'EOF'
set pagination off
define regs
info registers cs eip esp edx ds es ss
end
EOFNecessiteu dues terminals a la carpeta de treball.
Terminal 1: arrenqueu QEMU aturat i amb un servidor de depuració:
qemu-system-i386 -drive file=disk.img,format=raw -S -sqemu-system-i386 -drive file=disk.img,format=raw -nographic -S -s-Sno deixa que la CPU comenci a executar-sobre el servidor de GDB al port 1234.- La finestra queda negra: la CPU està aturada al reset.
Terminal 2: connecteu GDB.
gdb -qgdb-multiarch -qDins de GDB, el mateix en tots dos casos:
source lab.gdb
target remote :1234GDB pot mostrar avisos sobre l’executable o l’OS ABI. Són normals: ignoreu-los. No cal fer set architecture: en connectar amb target remote, GDB (també gdb-multiarch) detecta sol que l’altre extrem és un i386 i ho mostra amb un missatge del tipus The target architecture is set to "auto" (currently "i386").
Demostració guiada: el reset
Abans de deixar córrer la CPU, mirem on és. Dins de GDB:
info registers cs eip
monitor info registersmonitor envia l’ordre a QEMU; la segona sortida és llarga, busqueu-hi la línia que comença per CS =.
Sortida esperada (extracte):
cs 0xf000 61440
eip 0xfff0 0xfff0
...
CS =f000 ffff0000 0000ffff 00009b00
La CPU ha de llegir la primera instrucció a base de CS + EIP. Quina adreça és? Coincideix amb la diapo del reset vector?
Pots veure el raonament
0xFFFF0000 + 0xFFF0 = 0xFFFFFFF0, la de la teoria. GDB només mostra el selector (0xf000); la base (0xffff0000) és una part oculta del registre que només veiem amb el monitor de QEMU.
B1. Arribar a 0x7C00
Posem un punt d’aturada a l’adreça on el BIOS carrega el boot sector i deixem córrer la CPU:
hbreak *0x7c00
continueTrigarà un segon o dos (el BIOS està fent la seva feina) i GDB s’aturarà: Breakpoint 1, 0x00007c00 in ?? ().
Abans d’executar regs: quin valor creieu que tenen DL, DS, ES, SS i SP en aquest moment? Podem assumir-los?
regsSortida esperada (els valors exactes poden variar):
cs 0x0 0
eip 0x7c00 0x7c00
esp 0x6f08 0x6f08
edx 0x80 128
ds 0x0 0
es 0x0 0
ss 0x0 0Pots veure el raonament
DL = 0x80: el BIOS ens dona la unitat d’arrencada (el primer disc dur). És una convenció que sí podem fer servir, i per això la guardem aboot_drive.DLconté el número de la unitat des d’on la BIOS ha arrencat el programa; en QEMU, si arrenquem des del primer disc dur, normalmentDL = 0x80. A més,DLés la part baixa de EDX (els 8 bits menys significatius), per aixòmov [boot_drive], dlcopia aquest valor i el guarda abans que el puguem necessitar més endavant.CS = 0,eip = 0x7c00: el BIOS ha saltat a0x7C00ambCS = 0.DS,ESiSSvalen 0, però només perquè QEMU ho fa així: un altre BIOS podria deixar-los amb altres valors. Per això no ho assumim i els inicialitzem.SP = 0x6f08, no0x7c00: la pila inicial no és la nostra.mov sp, 0x7c00és una elecció d’aquest bootloader, no una regla del BIOS.
B2. El codi és a la RAM
Mirem els primers bytes de 0x7C00 i els comparem amb el fitxer boot.bin i amb les seves instruccions:
x/16bx 0x7c00
shell hexdump -C -n 16 boot.bin
shell ndisasm -b 16 -o 0x7c00 boot.bin | head -9Sortida esperada (extracte):
0x7c00: 0xea 0x05 0x7c 0x00 0x00 0xfa 0x31 0xc0
0x7c08: 0x8e 0xd8 0x8e 0xc0 0x88 0x16 0x2e 0x7c
00000000 ea 05 7c 00 00 fa 31 c0 8e d8 8e c0 88 16 2e 7c |..|...1........||
00007C00 EA057C0000 jmp 0x0:0x7c05
00007C05 FA cli
00007C06 31C0 xor ax,ax
00007C08 8ED8 mov ds,ax
00007C0A 8EC0 mov es,ax
00007C0C 88162E7C mov [0x7c2e],dl
00007C10 8ED0 mov ss,ax
00007C12 BC007C mov sp,0x7c00
00007C15 FB sti
Els bytes de la RAM coincideixen amb els del fitxer? Què demostra això?
Pots veure el raonament
Coincideixen: el BIOS ha copiat el sector 0 del disc a 0x7C00 i hi ha transferit el control. El primer byte, 0xEA, és l’opcode del far jump; 05 7c és l’offset (0x7c05, en little-endian) i 00 00 el segment.
No feu servir x/i per desassemblar: en aquest mode, GDB pot interpretar el codi de 16 bits com si fos de 32 i mostrar instruccions absurdes. Per veure les instruccions correctes, feu servir ndisasm -b 16 -o <adreça> <fitxer>.
B3. Quan passa SP a 0x7C00?
A la sortida de ndisasm trobeu l’adreça de la instrucció mov sp,0x7c00 (0x7c12). Aturem la CPU just abans, mirem la pila, fem una instrucció (si) i tornem a mirar:
hbreak *0x7c12
continue
regs
si
regsQuin valor tenia SP abans de mov sp, 0x7c00? I després? Què demostra sobre la pila inicial?
Pots veure el raonament
Abans, esp = 0x6f08; després, esp = 0x7c00 (i eip = 0x7c15). La pila que ens deixa el BIOS no és la nostra: en construïm una nosaltres, i és el que necessita call/ret.
EIPindica on continua l’execució després de la instrucciómov sp, 0x7c00, i és0x7c15, que és la següent instrucció (sti).ESPindica on apunta la pila. Abans de la instrucció, apunta a0x6f08, que és la pila que ens deixa el BIOS. Després de la instrucció, apunta a0x7c00, que és la pila que hem creat, coincidint amb l’adreça on tenim el codi. Recorda que la pila en x86 creix cap a adreces més baixes, així que quan fempush,ESPes decrementa.
Deixeu córrer el programa i tanqueu:
continueVeureu el missatge a la finestra de QEMU. Per sortir: quit a GDB (responeu y) i tanqueu la finestra de QEMU (o Ctrl+C a la terminal 1).
Part C — Stage 2 i INT 13h
Fins ara el BIOS ha carregat un sector. Volem que el nostre Stage 1 carregui un segon programa, més gran, des d’una altra posició del disc.
Stage 2 anirà a LBA 2048 del disc i el carregarem a 0x8000 de la RAM. Recordeu: 0x7C00 és la convenció del BIOS; 0x8000 i LBA 2048 són una elecció nostra. LBA 2048 és el sector 2048 del disc (2048 × 512 B = 1 MiB), no l’adreça 0x2048.
Stage 2 al disc
Guardeu-lo com a second_boot.asm:
[org 0x8000] ; Programa carregat a 0x8000
bits 16 ; Mode real de 16 bits
mov si, msg ; SI apunta al primer caràcter de la cadena
.next:
lodsb ; al = [DS:SI], SI++ -> Agafa caràcter i avança
test al, al ; AL == 0 ? -> fi de la cadena?
jz .halt
mov ah, 0x0e ; funció per escriure un caràcter a la pantalla
mov bx, 0x0007 ; pàgina 0, color gris
int 0x10 ; escriu el caràcter a la pantalla
jmp .next
.halt:
hlt
jmp .halt
msg db "Stage 2: el control ha arribat aqui!", 13, 10, 0Assembleu-lo i escriviu-lo al sector 2048 (la mateixa recepta d’abans, ara amb seek):
nasm -f bin -o second_boot.bin second_boot.asm
wc -c second_boot.bin
dd if=second_boot.bin of=disk.img bs=512 seek=2048 conv=notrunc
hexdump -C -s 1048576 -n 32 disk.img1048576 és 2048 × 512, el byte del disc on comença el sector 2048.
Sortida esperada:
59 second_boot.bin
00100000 be 14 80 ac 84 c0 74 09 b4 0e bb 07 00 cd 10 eb |......t.........|
00100010 f2 f4 eb fd 53 74 61 67 65 20 32 3a 20 65 6c 20 |....Stage 2: el |
Ara Stage 2 és al disc. Si arrenqueu disk.img amb el Stage 1 actual, s’executarà Stage 2? Prediu-ho i comproveu-ho:
qemu-system-i386 -drive file=disk.img,format=rawqemu-system-i386 -drive file=disk.img,format=raw -nographicPots veure el raonament
No: només surt Stage 1: hola!. El BIOS només carrega el sector 0; qui ha de llegir Stage 2 és Stage 1, i el nostre encara no ho fa. Que Stage 2 existeixi al disc no vol dir que estigui carregat.
El DAP: què li diem al BIOS?
INT 13h, funció 0x42, llegeix del disc amb LBA. Li hem de passar un Disk Address Packet (DAP), una estructura de 16 bytes:
dap: ; Disk Address Packet (16 bytes)
db 0x10, 0 ; mida del DAP, reservat
dw 1 ; nombre de sectors a llegir
dw 0x8000, 0x0000 ; destí: offset, segment
dq 2048 ; LBA inicialEls registres: AH = 0x42, DL = unitat d’arrencada, DS:SI apunta al DAP. Si hi ha un error, el BIOS posa CF = 1.
Sense executar res: què li estem demanant al BIOS amb aquest DAP? Expliqueu-ho amb una frase.
Pots veure el raonament
Llegeix 1 sector, des de LBA 2048, i deixa’l a 0000:8000. Aquesta és la idea que cal recordar; els camps són només la manera de dir-ho.
Stage 1 carrega Stage 2
Substituïu boot.asm per aquesta versió. El DAP ja hi és; us falten 3 TODO:
[org 0x7c00]
bits 16
jmp 0x0000:start ; far jump: força CS = 0x0000
start:
cli ; sense interrupcions mentre canviem SS:SP
xor ax, ax
mov ds, ax ; DS = 0
mov es, ax ; ES = 0
mov [boot_drive], dl ; guardem la unitat d'arrencada (DL)
mov ss, ax ; SS = 0
mov sp, 0x7c00 ; pila: creix cap a adreces més baixes
sti
mov si, msg_stage1
call print
.load_stage2:
mov si, dap ; DS:SI → DAP
; TODO 1: DL = unitat d'arrencada (variable boot_drive) i AH = funció de lectura
int 0x13
; TODO 2: si la lectura ha fallat (CF = 1), salta a .boot_failed
; TODO 3: transfereix el control a Stage 2 (segment 0x0000, offset 0x8000)
.halt:
hlt
jmp .halt
.boot_failed:
mov si, msg_fail
call print
jmp .halt
print:
.next:
lodsb
test al, al
jz .done
mov ah, 0x0e
mov bx, 0x0007
int 0x10
jmp .next
.done:
ret
dap: ; Disk Address Packet (16 bytes)
db 0x10, 0 ; mida del DAP, reservat
dw 1 ; nombre de sectors a llegir
dw 0x8000, 0x0000 ; destí: offset, segment
dq 2048 ; LBA inicial
boot_drive db 0
msg_stage1 db "Stage 1: carregant Stage 2...", 13, 10, 0
msg_fail db "Error carregant Stage 2!", 13, 10, 0
times 510 - ($ - $$) db 0
dw 0xaa55Ajuda per als TODO:
- 1.
DLes carrega des deboot_drive;AHval0x42. - 2. Després de
int 0x13, siCF = 1la lectura ha fallat (jc). - 3. Un salt far a
0x0000:0x8000.
Aneu pas a pas. Ompliu només els TODO 1 i 2, assembleu i escriviu el nou Stage 1 (només el sector 0):
nasm -f bin -o boot.bin boot.asm
dd if=boot.bin of=disk.img bs=512 count=1 conv=notrunc
ls -l disk.imgqemu-system-i386 -drive file=disk.img,format=rawqemu-system-i386 -drive file=disk.img,format=raw -nographicStage 1 ja llegeix Stage 2 a 0x8000, però encara no hi salta. Què veureu? On és Stage 2 ara mateix? Què falta?
Pots veure el raonament
Només surt Stage 1: carregant Stage 2.... Stage 2 ja és a la RAM (a 0x8000), però ningú no hi ha transferit el control: falta el salt. Ho comprovarem amb GDB a la Part D.
Ara afegiu el TODO 3 i torneu a executar les mateixes ordres (assemblar, escriure el sector 0 i arrencar):
Sortida esperada a QEMU:
Stage 1: carregant Stage 2...
Stage 2: el control ha arribat aqui!
Si ls -l disk.img no mostra 10485760 bytes, heu oblidat conv=notrunc i dd ha escurçat el disc: torneu a la Part A.
Pots veure la solució
[org 0x7c00]
bits 16
jmp 0x0000:start ; far jump: força CS = 0x0000
start:
cli ; sense interrupcions mentre canviem SS:SP
xor ax, ax
mov ds, ax ; DS = 0
mov es, ax ; ES = 0
mov [boot_drive], dl ; guardem la unitat d'arrencada (DL)
mov ss, ax ; SS = 0
mov sp, 0x7c00 ; pila: creix cap a adreces més baixes
sti
mov si, msg_stage1
call print
.load_stage2:
mov si, dap ; DS:SI → DAP
mov dl, [boot_drive] ; DL = unitat d'arrencada
mov ah, 0x42 ; AH = lectura estesa (LBA)
int 0x13
jc .boot_failed ; CF = 1 → error
jmp 0x0000:0x8000 ; transferim el control a Stage 2
.halt:
hlt
jmp .halt
.boot_failed:
mov si, msg_fail
call print
jmp .halt
print:
.next:
lodsb
test al, al
jz .done
mov ah, 0x0e
mov bx, 0x0007
int 0x10
jmp .next
.done:
ret
dap: ; Disk Address Packet (16 bytes)
db 0x10, 0 ; mida del DAP, reservat
dw 1 ; nombre de sectors a llegir
dw 0x8000, 0x0000 ; destí: offset, segment
dq 2048 ; LBA inicial
boot_drive db 0
msg_stage1 db "Stage 1: carregant Stage 2...", 13, 10, 0
msg_fail db "Error carregant Stage 2!", 13, 10, 0
times 510 - ($ - $$) db 0
dw 0xaa55Fixeu-vos que DL es carrega des de boot_drive en lloc d’escriure mov dl, 0x80. Per què és millor?
Pots veure el raonament
Perquè el BIOS ens dona la unitat d’arrencada a DL: no sempre és 0x80 (podria ser un altre disc). Un bootloader ha de conservar aquesta informació, no suposar-la.
Part D — Carregar no és executar
Ara podem veure amb GDB la diferència entre carregar i executar.
Terminal 1:
qemu-system-i386 -drive file=disk.img,format=raw -S -so béqemu-system-i386 -drive file=disk.img,format=raw -nographic -S -s.Terminal 2: obriu GDB (
gdb -qogdb-multiarch -q, segons el que hàgiu instal·lat) i, dins de GDB:
source lab.gdb
target remote :1234
hbreak *0x7c00
continue
Som al principi de Stage 1 i encara no ha llegit res.
Què creieu que hi ha a 0x8000 ara mateix? Comproveu-ho:
x/16bx 0x8000
Pots veure el raonament
Zeros. Stage 2 és al disc, no a la RAM: encara no l’ha carregat ningú. (Que siguin zeros és cert a QEMU; en una màquina real la RAM podria tenir qualsevol valor.)
Ara deixem que Stage 1 faci la lectura i s’aturi just abans d’executar Stage 2:
hbreak *0x8000
continue
regs
x/16bx 0x8000
shell hexdump -C -n 16 second_boot.bin
Sortida esperada (extracte):
cs 0x0 0
eip 0x8000 0x8000
esp 0x7c00 0x7c00
edx 0x80 128
0x8000: 0xbe 0x14 0x80 0xac 0x84 0xc0 0x74 0x09
0x8008: 0xb4 0x0e 0xbb 0x07 0x00 0xcd 0x10 0xeb
00000000 be 14 80 ac 84 c0 74 09 b4 0e bb 07 00 cd 10 eb |......t.........|
Per saber quina instrucció hi ha a 0x8000:
shell ndisasm -b 16 -o 0x8000 second_boot.bin | head -4
00008000 BE1480 mov si,0x8014
00008003 AC lodsb
00008004 84C0 test al,al
00008006 7409 jz 0x8011
Responeu:
- Quin valor tenen
CSiIPquan GDB s’atura? - Quina instrucció s’executarà a
0x8000? - Quina diferència hi ha entre carregar Stage 2 i executar Stage 2?
- Quin registre conserva la unitat d’arrencada? Com ho sabeu?
Pots veure el raonament
CS = 0ieip = 0x8000: hem arribat amb el far jumpjmp 0x0000:0x8000.mov si, 0x8014: apunta a la cadenamsgde Stage 2.- Carregar és copiar el codi a la RAM:
INT 13hha portat els bytes deLBA 2048a0x8000. Executar és que la CPU comenci a llegir-hi instruccions: només passa amb eljmp. Entre els dos moments el codi és a la RAM però inert. DL: continua valent0x80a0x8000; Stage 1 no l’ha trencat.
Ampliació: [org]
Hem vist que mov si, msg s’ha assemblat com mov si, 0x8014: una adreça absoluta que depèn de [org 0x8000]. En aquest laboratori l’org coincideix amb l’adreça on esperem que Stage 1 carregui Stage 2. Comprovem què passa si no hi coincideix.
Feu una còpia de Stage 2, canvieu [org 0x8000] per [org 0x0] i assembleu-la a un altre fitxer:
cp second_boot.asm sb_org0.asm
# editeu sb_org0.asm: [org 0x8000] → [org 0x0]
nasm -f bin -o sb_org0.bin sb_org0.asm
hexdump -C -n 3 sb_org0.bin
hexdump -C -n 3 second_boot.binQuè canvia als tres primers bytes? Escriviu la versió incorrecta al disc i, abans d’executar, prediu què veureu:
dd if=sb_org0.bin of=disk.img bs=512 seek=2048 conv=notrunc
qemu-system-i386 -drive file=disk.img,format=rawPots veure el raonament
Els bytes passen de be 14 80 a be 14 00: la instrucció mov si, imm16 porta l’adreça de msg dins l’instrucció. Amb [org 0x0], SI apunta a 0x0014 (la taula de vectors d’interrupció) i no al missatge: només surt Stage 1: carregant Stage 2... i cap missatge de Stage 2. L’adreça que assumeix NASM (org) ha de coincidir amb l’adreça on es carrega el codi (0x8000).
Per deixar-ho tot com estava, torneu a escriure la versió bona:
dd if=second_boot.bin of=disk.img bs=512 seek=2048 conv=notruncTrenca-ho
Trenqueu l’arrencada de tres maneres i relacioneu cada símptoma amb la diapo de diagnòstic. Per cada cas: prediu, comproveu i digueu en quina etapa ha fallat: el firmware, Stage 1 o Stage 2. Treballeu sobre una còpia de disk.img.
E1. Sense signatura
cp disk.img disk_e1.img
printf '\000\000' | dd of=disk_e1.img bs=1 seek=510 count=2 conv=notrunc
hexdump -C -s 510 -n 2 disk_e1.img
qemu-system-i386 -drive file=disk_e1.img,format=rawQuè veureu
El firmware rebutja el sector 0 com a no arrencable i no executa res nostre. El missatge exacte depèn de la implementació de BIOS (a QEMU, Boot failed: not a bootable disk). Falla el firmware: és la família de símptomes de No bootable device de la diapo.
E2. Lectura fallida
Editeu boot.asm i canvieu l’LBA del DAP (dq 2048) per dq 30000, fora dels 20480 sectors del disc. Assembleu-lo i escriviu-lo en una còpia del disc:
nasm -f bin -o boot_e2.bin boot.asm
cp disk.img disk_e2.img
dd if=boot_e2.bin of=disk_e2.img bs=512 count=1 conv=notrunc
qemu-system-i386 -drive file=disk_e2.img,format=rawQuè veureu
Stage 1: carregant Stage 2... i Error carregant Stage 2!. INT 13h ha retornat CF = 1, i el nostre jc .boot_failed ho ha detectat. Falla Stage 1 (no pot llegir), i ho sap dir: és el que fa .boot_failed. Recordeu tornar a posar 2048 a boot.asm.
E3. Stage 2 desaparegut
cp disk.img disk_e3.img
dd if=/dev/zero of=disk_e3.img bs=512 seek=2048 count=1 conv=notrunc
qemu-system-i386 -drive file=disk_e3.img,format=rawAra Stage 1 llegeix amb èxit el sector 2048, però el missatge de Stage 2 no surt. On ha fallat? Com ho comprovaríeu amb GDB?
Pots veure el raonament
Només veieu Stage 1: carregant Stage 2...: Stage 1 ha funcionat, i el problema és després. Amb GDB: hbreak *0x8000, continue i x/16bx 0x8000: són tot zeros. La lectura ha anat bé, però el que ha carregat és un sector buit; la CPU executa zeros sense imprimir res. Falla Stage 2 (no existeix): un missatge que falta també és una pista sobre l’etapa on som.