Assembly Avanzato

Capitolo 2 Il BIOS - Basic Input Output System

Non appena si fornisce l'alimentazione elettrica ad un computer appartenente alla famiglia hardware dei PC 80x86, viene attivato un procedimento standard il cui scopo è quello di svolgere le seguenti importantissime fasi:

1) autodiagnosi e inizializzazione dell'hardware;
2) installazione delle ISR per l'interfaccia a basso livello con l'hardware;
3) lancio del sistema operativo.

Tutto questo lavoro viene svolto da un software (ovviamente scritto in Assembly) che risiede su una apposita memoria ROM denominata ROM BIOS e disposta fisicamente in un chip del computer; la sigla BIOS è l'acronimo di Basic Input Output System (sistema primario per le operazioni di I/O con l'hardware).
Gli aspetti relativi al BIOS assumono quindi una notevole importanza generale, non solo per i cosiddetti "programmatori di sistema" che si dedicano alla scrittura dei SO; analizziamo quindi in dettaglio le tre fasi elencate in precedenza.

2.1 Il POST

La delicatissima fase di autodiagnosi e inizializzazione dell'hardware assume, come è facile intuire, una importanza vitale per il computer; il software del BIOS che svolge tale fase, prende il nome di POST che è l'acronimo di Power On Self Test (autodiagnosi all'accensione del PC).

Prima di analizzare le fasi che caratterizzano il POST, dobbiamo occuparci di un aspetto molto interessante che ci permette di capire la tecnica attraverso la quale il BIOS prende il controllo all'accensione del computer; a tale proposito, è necessario premettere che esistono alcune differenze tra vecchi e nuovi modelli di CPU (e tra vecchie e nuove architetture dei PC).

Il primo aspetto da considerare riguarda il fatto che una qualsiasi CPU della famiglia 80x86, viene sempre inizializzata in modalità reale; come sappiamo, in tale modalità le CPU utilizzano un address bus a 20 linee attraverso il quale possono indirizzare un massimo di 220 byte, pari ad 1 Mb di RAM (cioè, tutti gli indirizzi fisici compresi tra 00000h e FFFFFh).
Affinché i programmi che girano in modalità reale possano accedere ai servizi del BIOS, è necessario quindi che il BIOS stesso venga mappato in un'area della RAM compresa tra 00000h e FFFFFh; la posizione di tale area è stata stabilita attraverso una convenzione con i produttori dei BIOS.
Nei vecchi PC di categoria XT, equipaggiati con CPU di classe 8088, 8086 e 80186, al BIOS viene riservata un'area da 8 Kb posizionata proprio alla fine del primo Mb di RAM tra gli indirizzi fisici FE000h e FFFFFh; a tali indirizzi fisici possiamo associare, ad esempio, gli indirizzi logici compresi tra FE00h:0000h e FE00h:1FFFh.
A partire dai PC di categoria AT, equipaggiati con CPU di classe 80286 o superiore, al BIOS viene riservata un'area da 64 Kb posizionata proprio alla fine del primo Mb di RAM tra gli indirizzi fisici F0000h e FFFFFh (la cosiddetta regione F); a tali indirizzi fisici possiamo associare, ad esempio, gli indirizzi logici compresi tra F000h:0000h e F000h:FFFFh.
In sostanza, la parte della RAM riservata al BIOS si comporta, in realtà, come una vera e propria ROM; per i programmi che intendono usufruire dei servizi del BIOS, tale area risulta quindi accessibile solamente in lettura!
La Figura 1, già presentata nella sezione Assembly Base, mostra graficamente la posizione della ROM BIOS nella RAM (PC di classe AT).

Figura 1 - Mappa della memoria RAM
(PC IBM compatibili)

Un'altra convenzione relativa alla famiglia hardware dei PC 80x86 ha stabilito che la prima istruzione in assoluto che la CPU esegue all'accensione (o al riavvio) del computer, deve trovarsi rigorosamente all'inizio dell'ultimo paragrafo di memoria indirizzabile dalla CPU stessa; tutto dipende, quindi, dall'ampiezza dell'address bus.

Una CPU (come la 8086) con address bus a 20 linee, può indirizzare un massimo di 1 Mb di RAM compresa tra gli indirizzi fisici 00000h e FFFFFh; in tal caso, l'ultimo paragrafo di memoria è quello compreso tra gli indirizzi fisici FFFF0h e FFFFFh.
La prima istruzione eseguita dalla CPU all'accensione (o al riavvio) del computer si viene a trovare quindi all'indirizzo fisico FFFF0h; a tale indirizzo fisico possiamo associare, ad esempio, l'indirizzo logico FFFFh:0000h.
Per soddisfare la convenzione appena enunciata, i registri di "indirizzamento" delle CPU con address bus a 20 linee si "autoinizializzano" in questo modo:

CS = FFFFh, IP = 0000h, SS = 0000h, SP = 0000h, DS = 0000h, ES = 0000h

Di conseguenza, all'accensione (o al riavvio) del computer, la CPU tenta di eseguire l'istruzione che si trova all'indirizzo logico CS:IP=FFFFh:0000h; se vogliamo sapere quale istruzione è presente a tale indirizzo possiamo servirci, ad esempio, del programma DEBUG disponibile in ambiente DOS. La Figura 2 illustra l'output prodotto dal comando u (unassemble) di DEBUG su un vecchio PC dotato di CPU 8088.

Figura 2 - Dump ASM di FFFFh:0000h
-u FFFF:0000

FFFF:0000   EA5BE000F0  JMP   F000:E05B
FFFF:0005   3035        XOR   [DI], DH
FFFF:0007   2F          DAS
FFFF:0008   3239        XOR   BH, [BX+DI]
FFFF:000A   2F          DAS
FFFF:000B   3836FFFE    CMP   [FEFF], DH
FFFF:000F   EF          OUT   DX, AX
FFFF:0010   1A5735      SBB   DL, [BX+35]
FFFF:0013   025C07      ADD   BL, [SI+07]
FFFF:0016   7000        JO    0018
FFFF:0018   C0          DB    C0
FFFF:0019   F9          STC
FFFF:001A   00F0        ADD   AL, DH
FFFF:001C   5C          POP   SP
FFFF:001D   07          POP   ES
FFFF:001E   7000        JO    0020

Come possiamo notare, all'indirizzo logico FFFFh:0000h è presente l'istruzione:

JMP F000:E05B

Si tratta quindi di un FAR jump all'indirizzo logico F000h:E05Bh; tale indirizzo logico può essere scritto anche come FE00h:005Bh e quindi appartiene proprio all'area di memoria da 8 Kb riservata alla ROM BIOS dei PC di classe XT!
Come si può intuire, all'indirizzo logico FE00h:005Bh inizia il codice del BIOS relativo al POST; da questo momento parte quindi la fase di autodiagnosi e inizializzazione dell'hardware.

Una CPU (come la 80386, 80486, 80586) con address bus a 32 linee, può indirizzare un massimo di 232=4 Gb di RAM compresa tra gli indirizzi fisici 00000000h e FFFFFFFFh; in tal caso, l'ultimo paragrafo di memoria è quello compreso tra gli indirizzi fisici FFFFFFF0h e FFFFFFFFh.
Questa situazione fa nascere subito un problema legato al fatto che difficilmente un normale PC con address bus a 32 linee dispone di ben 4 Gb di RAM; come è possibile allora che la CPU possa eseguire una istruzione che si trova all'indirizzo fisico FFFFFFF0h?
Il problema è stato risolto facendo in modo che tale indirizzo venga mappato, non in RAM, bensì su una memoria EPROM; all'interno della EPROM, il produttore del PC può inserire tutte le necessarie istruzioni di inizializzazione.

Per poter accedere all'indirizzo fisico FFFFFFF0h, la CPU si "autoinizializza" in una particolare modalità chiamata big real mode; in pratica, la CPU lavora in modalità reale, ma può gestire componenti Offset a 32 bit in modo da poter indirizzare sino a 4 Gb di memoria fisica!
Come vedremo nella apposita sezione di questo sito, in big real mode o in protected mode, un registro di segmento non contiene una componente Seg, bensì un cosiddetto selettore di segmento la cui struttura è illustrata in Figura 3.

Figura 3 - Selettore di segmento

Per il momento ci interessa sapere che i 13 bit del campo INDICE contengono l'indice di uno tra i possibili 213=8192 elementi di una apposita tabella presente in memoria; ciascun elemento prende il nome di descrittore di segmento e, come si intuisce dal nome, contiene la descrizione completa del segmento di programma a cui il selettore di segmento fa riferimento.
Una delle informazioni contenute nel descrittore di segmento è il cosiddetto base address che specifica l'indirizzo fisico a 32 bit da cui inizia il relativo segmento di programma; i vari indirizzi fisici a cui la CPU deve accedere si ottengono sommando il base address al contenuto a 32 bit del registro puntatore utilizzato. Nel caso, ad esempio, dell'indirizzo specificato dalla coppia DS:ESI, la CPU accede al descrittore di segmento associato a DS e somma il relativo base address con il contenuto di ESI.

Premesso che per indicare il base address associato ad un determinato SegReg la Intel utilizza una sintassi del tipo CS.BASE, DS.BASE, etc, in fase di accensione (o riavvio) del computer i registri di "indirizzamento" di una CPU con address bus a 32 linee si "autoinizializzano" in questo modo:

CS = F000h, CS.BASE = FFFF0000h, EIP = 0000FFF0h
SS = 0000h, SS.BASE = 00000000h, ESP = 00000000h
DS = 0000h, DS.BASE = 00000000h
ES = 0000h, ES.BASE = 00000000h
FS = 0000h, FS.BASE = 00000000h
GS = 0000h, GS.BASE = 00000000h

In sostanza, CS contiene il selettore F000h che punta ad un segmento di programma con base address pari a FFFF0000h, mentre EIP vale 0000FFF0h; di conseguenza, all'accensione (o al riavvio) del computer, la CPU tenta di eseguire l'istruzione che si trova all'indirizzo logico:

CS.BASE + EIP = FFFF0000h + 0000FFF0h = FFFFFFF0h

Come è stato spiegato in precedenza, tale indirizzo è mappato in una EPROM la quale contiene le istruzioni di inizializzazione della CPU; in particolare, tali istruzioni stabiliscono anche se la CPU debba poi passare in modalità reale o protetta.

2.1.1 La sequenza del POST

La prima istruzione eseguita dalla CPU cede quindi il controllo al POST; a questo punto inizia la fase di autodiagnosi e inizializzazione dell'hardware. Questa fase comporta un numero piuttosto lungo di controlli e verifiche e non può essere certo dettagliatamente descritta in un tutorial; del resto, il dovere primario di un vero programmatore Assembly è quello di studiare tutta la documentazione tecnica relativa agli argomenti che ha intenzione di approfondire.
In particolare, se vogliamo avere una panoramica dettagliata su tutto ciò che accade durante il POST, possiamo fare riferimento agli appositi manuali tecnici forniti dai produttori dei BIOS. Nella sezione Links Utili di questo sito è presente un link alla Home Page della Phoenix, noto produttore dei BIOS che equipaggiano quasi tutti i PC; dal sito della Phoenix si consiglia di effettuare il download dei documenti biospostcode.pdf (PhoenixBIOS - POST Tasks and Beep Codes) e biosawardpostcode.pdf (AwardBIOS - Post Codes & Error Messages).

Eventuali problemi hardware rilevati dal POST, vengono adeguatamente segnalati all'utente attraverso diversi metodi; in generale, qualunque problema viene segnalato sul monitor attraverso un apposito messaggio di errore. Se il problema riguarda la parte video, allora il POST si serve di appositi segnali acustici inviati all'altoparlante di sistema; i due documenti descritti in precedenza, illustrano tutte le informazioni relative a questi aspetti.

Ad ogni controllo svolto dal POST, corrisponde un ben preciso codice di errore da 1 byte; prima di dare inizio ad un nuovo controllo, il POST invia il relativo codice di errore ad un dispositivo di I/O raggiungibile attraverso la porta hardware 80h (chiamata Manufacturing Diagnostic Port).
In caso di errore con conseguente blocco del computer, la porta 80h contiene quindi il codice del test che ha rilevato il problema; in genere, queste informazioni sono riservate ai centri di assistenza tecnica, i quali dispongono di apparecchiature capaci di visualizzare (attraverso un display) l'ultimo codice prodotto dal POST.
Se tutto procede correttamente, l'ultimo codice di errore inviato dal POST è quello relativo alla fase di boot (lancio del SO); a seconda del tipo di BIOS installato sul proprio computer, tale codice può assumere valori del tipo 00h, F7h, FFh.
Se vogliamo conoscere il valore esatto, possiamo servirci, ad esempio, del comando:

i 80

(input from port) fornito dal programma DEBUG; si tenga presente comunque che, se si lavora con un emulatore DOS, tale valore potrebbe essere privo di senso.

2.2 Installazione delle ISR del BIOS

Terminata la diagnostica e l'inizializzazione dell'hardware, parte la seconda fase del BIOS che consiste nella installazione in memoria di una numerosa serie di procedure Assembly; lo scopo di tali procedure è quello di permettere ai programmi di interfacciarsi a basso livello con l'hardware del computer.
Si tratta di procedure di utilità generale, che diventano indispensabili soprattutto quando si opera in condizioni estreme; un caso del genere si presenta, ad esempio, quando si deve scrivere un cosiddetto bootloader (il programma che carica in memoria il SO).

Per capire il procedimento utilizzato dal BIOS per l'installazione di queste procedure, è necessario premettere che, durante il POST, viene anche effettuato il test e l'inizializzazione di un particolare dispositivo chiamato PIC o Programmable Interrupt Controller (controllore programmabile delle interruzioni); come è stato spiegato nella sezione Assembly Base e come vedremo in dettaglio nel prossimo capitolo, il compito del PIC è quello di raccogliere e smistare tutte le richieste che arrivano dalle periferiche che vogliono dialogare con la CPU.
Ciascuna richiesta che arriva da una periferica, viene chiamata Interrupt Request (richiesta di interruzione) o IRQ; al momento opportuno, la CPU interrompe il programma in esecuzione (da cui il nome "interruzione") e soddisfa la richiesta attraverso la chiamata di una apposita procedura che, proprio per questo motivo, viene definita Interrupt Service Routine (procedura di servizio per le interruzioni) o ISR.
La situazione appena descritta si riferisce alle cosiddette interruzioni hardware; si tratta cioè di interruzioni dovute a IRQ provenienti dalle periferiche. Esistono però anche le cosiddette interruzioni software, così chiamate in quanto provocate dai programmi attraverso l'istruzione INT (analizzata nella sezione Assembly Base); anche in questo caso, la CPU soddisfa la richiesta attraverso la chiamata di apposite ISR.
Molte delle ISR, vengono installate proprio dal BIOS prima della fase di boot; altre ISR possono essere in seguito installate dal SO o dai programmi.

L'installazione delle ISR, avviene attraverso un procedimento che risponde a precise convenzioni; in particolare, l'indirizzo logico Seg:Offset di una qualsiasi ISR deve essere posizionato in un'area della RAM compresa tra gli indirizzi fisici 00000h e 003FFh (a cui possiamo associare, ad esempio, gli indirizzi logici compresi tra 0000h:0000h e 0000h:03FFh).
Come si nota in Figura 1, tale area viene denominata Interrupt Vectors Area (area riservata ai vettori di interruzione); tale nome deriva dal fatto che ciascun indirizzo logico Seg:Offset di una ISR viene indicato con il termine Interrupt Vector (vettore di interruzione).

Ciascun vettore di interruzione punta quindi ad una ISR che si trova nella RAM; molti dei vettori di interruzione installati dal BIOS puntano a delle ISR che si trovano nell'area della RAM riservata alla ROM BIOS del computer!

Tornando alla Figura 1 notiamo che l'area riservata ai vettori di interruzione occupa complessivamente:

003FFh - 00000h + 1 = 400h byte = 1024 byte

Ogni vettore di interruzione (cioè, ogni indirizzo logico Seg:Offset) occupa 4 byte, per cui in questi 1024 byte trovano posto:

1024 / 4 = 256 = FFh vettori di interruzione

Per convenzione, i vari vettori di interruzione sono numerati, nell'ordine, 00h, 01h, 02h e così via, sino al n. FFh; come già sappiamo, molti dei vettore di interruzione possono essere chiamati dai programmi (interruzioni software) attraverso l'istruzione INT (che esegue una FAR call alla relativa ISR).

Come si può facilmente immaginare, alcuni dei vettori di interruzione sono riservati rigorosamente al BIOS; altri vettori di interruzione sono riservati al DOS, mentre altri ancora sono disponibili per i programmi.
Se si vuole conoscere l'elenco completo dei 256 vettori di interruzione, si può fare riferimento alla apposita tabella disponibile in questo sito; per quanto riguarda i vettori di interruzione riservati al BIOS, si consiglia di scaricare, dal sito della Phoenix, il documento userman.pdf (PhoenixBIOS User's Manual).
Per una descrizione estremamente dettagliata di tutti i vettori di interruzione e dei servizi ad essi associati, si consiglia di consultare la celebre Ralf Brown's Interrupt List; a tale proposito, si veda la sezione Links Utili di questo sito.

2.2.1 Esempio pratico per le ISR del BIOS

Consultando il manuale utente del proprio BIOS, si possono ricavare informazioni sulle varie ISR disponibili; attraverso tali ISR si possono scrivere una enorme quantità di procedure estremamente compatte ed efficienti.
Vediamo un esempio pratico che si riferisce all'output di una stringa sul video (e che ci tornerà molto utile nel seguito); a tale proposito, ci serviamo di una ISR fornita dalla INT 10h, denominata Video BIOS Services (servizi BIOS per il video).

Il servizio n. 13h della INT 10h prende il nome di Write string e permette di visualizzare una stringa sullo schermo; tale servizio è descritto dalla Figura 4.

Figura 4 - Servizio n. 13h della INT 10h
INT 10h - Servizio n. 13h - Write string:
   visualizza una stringa sullo schermo.

Argomenti richiesti:
   AH    = 13h (servizio Write string)
   ES:BP = indirizzo logico Seg:Offset della stringa
   CX    = lunghezza della stringa
   DH    = riga di output
   DL    = colonna di output
   BH    = pagina video
   BL    = attributi video
   AL    = modalità di scrittura
           0 = solo caratteri
           1 = solo caratteri + aggiornamento posizione cursore
           2 = caratteri + attributi
           3 = caratteri + attributi + aggiornamento posizione cursore

La Figura 4 ci permette di osservare che ciascuna ISR può fornire numerosi servizi; tali servizi possono essere selezionati attraverso un apposito valore passato, in genere, nel registro AH.
Le ISR spesso richiedono uno o più argomenti (entry arguments) che devono essere passati attraverso i registri generali; gli stessi registri generali vengono utilizzati dalle ISR per contenere eventuali valori di ritorno (exit arguments).

Prima di vedere un esempio pratico, analizziamo il concetto di attributo video; con tale termine si indica il colore di sfondo (background) e di primo piano (foreground) del testo da stampare.
Il BIOS inizializza la scheda video in modalità testo (o alfanumerica); in tale modalità, lo schermo viene suddiviso in una matrice di celle disposte su 25 righe (numerate da 0 a 24) e 80 colonne (numerate da 0 a 79).
Ad ogni cella vengono riservati 2 byte di memoria; il primo byte contiene il codice ASCII del carattere da stampare, mentre il secondo byte contiene, appunto, gli attributi video del carattere stesso.
La Figura 5 illustra la struttura del byte degli attributi video.

Figura 5 - Attributi video

Sia per lo sfondo, sia per il primo piano, possiamo ottenere differenti colori attivando (1) o disattivando (0) le tre componenti fondamentali Red (rosso), Green (verde) e Blue (blu), per un totale di 23=8 colori; per ciascun colore, il bit IN (intensità) permette di selezionare una intensità alta (1) o bassa (0), per cui i colori complessivi diventano 2*8=16.
Su alcuni BIOS (soprattutto quelli meno recenti) il bit in posizione 7 viene definito BL o blinking (lampeggiamento); tale bit permette di attivare (1) o disattivare (0) il lampeggiamento dello sfondo.

Tornando alla Figura 4, nel registro AL bisogna inserire un valore che rappresenta la modalità di scrittura.
I valori 0 e 1 indicano che la stringa è composta da soli caratteri, mentre gli attributi video (uguali per tutti i caratteri) vengono specificati dal registro BL; la posizione del cursore viene aggiornata solo per AL=1.
I valori 2 e 3 indicano che la stringa è composta da una sequenza di coppie (carattere, attributo) con la possibilità quindi di specificare un attributo video differente per ogni carattere (il valore in BL viene ignorato); la posizione del cursore viene aggiornata solo per AL=3.

Fatte queste premesse, supponiamo di avere un blocco dati referenziato da DS e contenente le seguenti informazioni:

MyString    db    "Stringa da visualizzare con il servizio BIOS n. 13h della INT 10h"
strLen      equ   $ - MyString

A questo punto, nel blocco codice del programma possiamo richiedere la visualizzazione della stringa MyString con le seguenti istruzioni:

   push     ds                      ; copia DS
   pop      es                      ; in ES
   mov      bp, MyString            ; ES:BP punta a MyString
   mov      cx, strLen              ; CX = lunghezza stringa
   mov      dh, 6                   ; riga 6
   mov      dl, 8                   ; colonna 8
   mov      bl, 00011110b           ; giallo su blu
   mov      bh, 0                   ; pagina video zero
   mov      al, 00h                 ; solo caratteri
   mov      ah, 13h                 ; servizio n. 13h
   int      10h                     ; chiama la ISR

Volendo creare una stringa formata da coppie (carattere, attributo), possiamo scrivere, ad esempio:

MyString db 'T', 1Eh, 'e', 1Fh, 's', 04h, 't', 01h

In tal caso, bisogna ricordare che la lunghezza della stringa comprende i soli caratteri, per cui dobbiamo scrivere:

strLen equ ($ - MyString) / 2

Nel seguito del capitolo e nei capitoli successivi vedremo altri esempi relativi ai servizi del BIOS e sarà anche chiarito il concetto di pagina video.

2.3 La BDA - BIOS Data Area

Numerosissime informazioni ricavate dal BIOS durante il POST, vengono memorizzate in una apposita area della RAM accessibile a tutti i programmi; tale area prende il nome di BDA o BIOS Data Area (area dati del BIOS) e, come si vede in Figura 1, per convenzione deve essere compresa tra gli indirizzi fisici 00400h e 004FFh (a cui possiamo associare, ad esempio, gli indirizzi logici compresi tra 0040h:0000h e 0040h:00FFh).

La Ralf Brown's Interrupt List contiene una descrizione dettagliata della BDA nel file memory.lst; si veda la sezione Links Utili di questo sito.

Per leggere il contenuto della BDA possiamo utilizzare un metodo diretto che consiste nel caricare il valore 0040h in un registro di segmento (ad esempio, ES) in modo da poter scrivere poi istruzioni del tipo:

mov ax, [es:OffsetBDA]

Vediamo un esempio pratico basato sul fatto che all'offset 0010h della BDA è presente una WORD contenente una serie di informazioni di sistema relative all'hardware installato sul computer; come si ricava anche dal manuale utente del BIOS, il significato di tale WORD è illustrato in Figura 6.

Figura 6 - BDA Equipment Information
BitSignificato
0 Riservato
1 Coprocessore matematico presente
2 Mouse PS/2 presente
3 Riservato
4 Modalità video iniziale:
 (00b = EGA/VGA, 01b = 40x25 CGA, 10b = 80x25 CGA, 11b = Monocromatico)
5
6 Numero floppy disk drives:
 (00b = 1, 01b = 2, 10b = 3, 11b = 4)
7
8 Riservato
9 Numero porte seriali:
 (001b = 1, 010b = 2, 011b = 3, 100b = 4, etc)
10
11
12 Porta giochi installata
13 Riservato
14 Numero porte parallele:
 (01b = 1, 10b = 2, etc)
15

In base a quanto esposto in Figura 6, l'istruzione:

mov ax, [es:O010h]

carica in AX la Equipment Information WORD della BDA (ovviamente, ES=0040h)!

In alternativa al metodo appena illustrato, possiamo utilizzare la INT 11h del BIOS; la chiamata di questo vettore di interruzione restituisce in AX le identiche informazioni visibili in Figura 6.

Nota importante.
Chi utilizza DOSEmu deve ricordarsi di attivare e configurare le varie periferiche attraverso l'apposito file $HOME/.dosemurc; tale file contiene anche le istruzioni sulla sintassi da utilizzare. Ad esempio:

$_cpu = "80586"
$_mathco = (on)
$_mouse_internal = (on)
$_mouse = "imps2"
$_mouse_dev = "/dev/psaux"
$_joy_device = "/dev/js0"
$_com3 = "/dev/ttyS2 irq 4"
$_com4 = "/dev/ttyS3 irq 3"


e così via.


2.4 La memoria CMOS

Prima che inizi la fase di boot per il lancio del SO, l'utente ha la possibilità di premere un apposito tasto che gli permette di accedere al menu di configurazione del BIOS; a seconda del modello di PC, il tasto da premere può essere [Del] o [Canc] (assemblati), [F10] (HP/Compaq), [F2] (tasto standard) o un altro tasto spesso evidenziato mediante un messaggio sul monitor.

Il menu di configurazione del BIOS, presente solo sui PC di classe AT o superiore, è riservato ad utenti piuttosto esperti; infatti, attraverso tale menu è possibile modificare le impostazioni hardware del proprio PC!

Un esempio pratico riguarda la cosiddetta "sequenza di boot"; si tratta della sequenza di memorie di massa (floppy disk, hard disk, CD, DVD) analizzate dal BIOS alla ricerca del codice per il lancio del SO. L'utente ha la possibilità di indicare al BIOS l'ordine esatto di scansione delle varie memorie di massa; più avanti vedremo un esempio dettagliato relativo a questo importante aspetto.

Quando usciamo dal menu di configurazione, il BIOS ci chiede se vogliamo mantenere o meno le nuove impostazioni che abbiamo eventualmente selezionato; in caso di risposta affermativa, la nuova configurazione verrà salvata in una apposita memoria RAM denominata CMOS.
Questa particolare memoria è presente solo a partire dai PC di classe AT; il suo nome deriva dal fatto che si tratta di una memoria RAM realizzata con i flip flop in tecnologia CMOS.
Come abbiamo visto nella sezione Assembly Base, tale tecnologia viene impiegata per la realizzazione di piccole memorie RAM ad alta velocità di accesso; in particolare, la memoria CMOS occupava appena 64 byte sui primi PC di classe AT ed è stata portata a 128 byte sui PC successivi.

La Figura 7 illustra lo schema a blocchi semplificato del circuito comprendente la memoria CMOS e l'orologio in tempo reale o Real Time Clock (RTC) del computer; le informazioni relative alla configurazione hardware vengono memorizzate nella parte colorata in rosso.

Figura 7 - Memoria RAM CMOS e RTC

Ad ogni riavvio del computer, il BIOS legge proprio le informazioni contenute nella CMOS e le utilizza per la configurazione dell'hardware; a maggior ragione quindi, è necessario evitare l'inserimento di informazioni errate in questa importante memoria!

Il circuito di Figura 7 ha anche l'importante compito di aggiornare continuamente l'orologio/calendario del computer; questo lavoro è svolto dal dispositivo chiamato RTC che memorizza le informazioni nella apposita area della CMOS.
Ai pin +3V e Gnd viene collegata una batteria ricaricabile il cui scopo è quello di permettere la conservazione delle informazioni anche a computer spento; è molto importante quindi che tale batteria venga mantenuta permanentemente in stato di carica (ciò si ottiene evitando che il PC rimanga spento per lunghissimi periodi di tempo).

Il BIOS fornisce numerosi servizi finalizzati a leggere in modo sicuro le informazioni presenti nella CMOS; a tale proposito, si veda il manuale utente citato in precedenza.
Nei capitoli successivi, il circuito di Figura 7 verrà analizzato in dettaglio.

Nota importante.
Impostando l'hardware in modo non corretto, si può provocare il malfunzionamento del computer; nei casi meno gravi, la situazione può essere risolta riavviando il PC e selezionando le impostazioni di fabbrica nel menu di configurazione del BIOS.
Bisogna ribadire quindi che solamente gli utenti esperti possono accedere a tale menu; se, a causa di impostazioni errate, si riscontrano problemi persistenti sul proprio computer, si può rendere necessario il ricorso ad un centro di assistenza tecnica!


2.5 La sequenza di boot

L'ultima fase fondamentale svolta dal BIOS durante l'avvio del computer, consiste in una chiamata alla INT 19h (System - Bootstrap loader) che esegue la scansione delle varie memorie di massa (floppy disk, hard disk, CD, DVD, etc) alla ricerca del codice per il lancio del SO; l'ordine seguito dal BIOS per effettuare tale scansione, prende il nome di sequenza di boot.
Sui vecchi PC, la sequenza comprende prima di tutto la scansione dei floppy disk; se non viene trovato il codice relativo al lancio del SO, si passa alla scansione degli hard disk. Se anche la scansione degli hard disk dà esito negativo, il BIOS mostra un messaggio di errore sullo schermo!
Sui nuovi PC, alla sequenza appena descritta è stata aggiunta anche la scansione di eventuali CD/DVD e persino di dispositivi Iomega Zip e schede di rete; come è stato spiegato in precedenza, la sequenza di boot può essere alterata dall'utente attraverso il menu di configurazione del BIOS. In questo modo si può chiedere al BIOS di iniziare la scansione dai dispositivi CD/DVD; ciò rende possibile il boot direttamente da CD/DVD, come accade, ad esempio, quando si deve installare Windows XP o quando si vuole eseguire una distribuzione "live" di Linux!

Per capire il metodo seguito nella scansione delle varie memorie di massa, analizziamo il caso dei floppy disk e degli hard disk; per i dettagli relativi ai CD/DVD avviabili, si può consultare il documento specs-cdrom.pdf (Phoenix IBM - "El Torito" Bootable CD-ROM Format Specification Version 1.0) scaricabile dal sito della Phoenix.
In riferimento quindi ai floppy disk e agli hard disk, in seguito alla cosiddetta formattazione, il BIOS suddivide fisicamente ogni superficie di un disco in tanti cerchi concentrici denominati tracce; le varie tracce vengono rappresentate con gli indici 0, 1, 2, etc, a partire da quella più esterna.
Ogni traccia viene suddivisa in tante parti denominate settori; i vari settori vengono rappresentati con gli indici 0, 1, 2, etc. Il settore 0, come vedremo più avanti, è riservato; i settori disponibili per la lettura/scrittura dei file sono quindi quelli con indici 1, 2, 3, etc.

In base alle considerazioni appena esposte, la superficie di ogni disco risulta organizzata secondo lo schema mostrato in Figura 8.

Figura 8 - Tracce e settori di un disco

Le varie coppie (traccia, settore) rappresentano una sorta di coordinate cartesiane che permettono di accedere in modo casuale a qualsiasi settore del disco; come sappiamo, il termine "accesso casuale" indica il fatto che il tempo di accesso ad un qualsiasi settore scelto a caso, è costante e quindi indipendente dalla posizione in cui si trova il settore stesso.

La lettura/scrittura di un disco avviene attraverso apposite testine magnetiche denominate heads e rappresentate con gli indici 0, 1, 2, etc; ogni faccia di un disco viene acceduta attraverso una delle testine magnetiche presenti.
Nel caso dei floppy disk a doppia faccia (nel senso che entrambe le facce possono memorizzare informazioni), sono presenti due testine, una per ogni faccia; quella superiore viene denominata head 0, mentre quella inferiore viene denominata head 1.
Gli hard disk sono costituiti da uno o più dischi a doppia faccia, sovrapposti tra loro in modo da formare una pila; le varie testine magnetiche, una per ogni faccia, vengono quindi denominate head 0, head 1, head 2, etc.
In un hard disk costituito da una pila di dischi a doppia faccia, le tracce aventi lo stesso indice formano un cosiddetto cilindro; ad esempio, tutte le tracce 0 dei vari dischi che formano un hard disk, rappresentano il cilindro 0 (ciò vale anche per i floppy disk dove, ad esempio, il cilindro 0 è formato dalla traccia 0 della faccia A e dalla traccia 0 della faccia B).

I SO suddividono logicamente la struttura di Figura 8 in modo da ottenere un cosiddetto file system; grazie al file system i programmi vedono un disco, come quello di Figura 8, sotto forma di vettore lineare di celle.
Ogni cella può essere costituita da un numero di byte che, in genere, è multiplo di 512; ciò permette di velocizzare notevolmente le operazioni di I/O su disco.

Il settore 0 relativo alla traccia 0, cilindro 0, head 0, faccia A di un disco (il primo disco, nel caso degli hard disk), prende il nome di Master Boot Record (settore principale di boot) o MBR e assume una importanza particolare; infatti, durante la sequenza di boot, il BIOS controlla il MBR di ogni disco alla ricerca di un eventuale bootloader.
Con il termine bootloader si indica un piccolo programma da 512 byte che contiene il codice necessario per il lancio del SO; per identificare un bootloader, il BIOS utilizza un meccanismo molto semplice che consiste nel verificare se gli ultimi 2 byte contenuti in un MBR (da 512 byte) assumono il valore AA55h!
In caso affermativo, il BIOS legge questo blocco da 512 byte e lo carica in memoria all'indirizzo fisico 07C00h a cui possiamo associare, ad esempio, l'indirizzo logico 0000h:7C00h; a questo punto lo stesso BIOS carica l'indirizzo logico 0000h:7C00h in CS:IP e cede il controllo alla CPU.
La CPU esegue quindi le istruzioni contenute nel bootloader; tali istruzioni, ovviamente, svolgono tutto il lavoro necessario per il caricamento in memoria del SO!

Nota importante.
È fondamentale tenere presente che il BIOS utilizza appositi codici per identificare i vari floppy disk drive e gli hard disk; in particolare:

i floppy disk drive sono indicati, nell'ordine, dai codici 00h, 01h, 02h, etc;

gli hard disk sono indicati, nell'ordine, dai codici 80h, 81h, 82h, etc.

Il codice del dispositivo da cui è avvenuto il boot viene caricato dal BIOS nel registro DL; tale codice è quindi a disposizione del bootloader!

Dalle considerazioni appena esposte, si intuisce che la scrittura di un piccolo bootloader "didattico" non presenta alcuna difficoltà; in effetti, si tratta di una esperienza molto interessante che ogni programmatore Assembly non può fare a meno di provare.

Nota importante.
L'unico aspetto da gestire con molta cautela, riguarda il metodo da seguire per installare un bootloader "personale" nel MBR di un disco; si tratta chiaramente di una operazione molto delicata in quanto il MBR viene anche utilizzato dai SO per contenere informazioni relative al tipo di file system presente sul disco!
Tutto ciò significa che dopo l'inserimento del nostro bootloader nel MBR di un disco, il disco stesso risulta illeggibile da parte del SO; proprio per questo motivo, si raccomanda vivamente di effettuare questo tipo di esperimenti, servendosi di un floppy disk e NON dell'hard disk del proprio computer!


2.5.1 Recupero di un MBR danneggiato

Indipendentemente dalle considerazioni svolte in questo capitolo, può capitare che un utente possa danneggiare il MBR del proprio hard disk (ad esempio, a causa di un blackout mentre si stava partizionando il disco); in una situazione del genere, il danno può essere facilmente riparato purché l'utente abbia avuto l'accortezza di premunirsi per tempo.
Il metodo da seguire consiste semplicemente nel dotarsi preventivamente di un apposito disco di emergenza; vediamo allora come si crea questo disco in base al SO utilizzato.

Ambiente DOS puro

In ambiente DOS puro si utilizza un file system di vecchio tipo FAT12 o di nuovo tipo FAT16; in tal caso è sufficiente inserire un floppy disk nel drive e impartire il comando:

format a: /s

L'opzione /s fa in modo che, dopo la formattazione, la parte essenziale del kernel del DOS venga trasferita sul disco, trasformandolo in un "disco DOS avviabile"; a questo punto, dobbiamo anche copiare sul disco il programma FDISK.EXE che, generalmente, si trova nella directory C:\DOS.
Nel caso in cui il MBR del nostro hard disk abbia subito danni, dobbiamo inserire il floppy di emergenza e riavviare il computer; dopo il riavvio, dobbiamo impartire il comando:

fdisk /mbr

Questo comando ripristina il MBR dell'hard disk permettendoci di rientrare in possesso di tutti i dati in esso contenuti!

Ambiente Windows 9x/Me

In ambiente Windows 9x/Me si utilizza un file system di tipo FAT32; il metodo da seguire è identico a quello appena descritto, con l'unica differenza che il comando:

format a: /s

deve essere impartito dalla DOS Box di Windows (o dall'apposito programma di Windows per la formattazione dei floppy disk).

Ambiente Windows NT, 2000, XP

In ambiente Windows NT, 2000, Xp si può utilizzare un file system di tipo FAT32 o NTFS; le considerazioni che seguono si riferiscono al caso di un disco NTFS (in tal caso, non è ovviamente possibile utilizzare FDISK).
Il metodo da seguire consiste nel servirsi del CD/DVD di installazione del SO; a tale proposito, bisogna inserire il CD/DVD e riavviare il computer in modo da far partire la fase di installazione di Windows.
Dopo aver caricato in memoria tutti gli strumenti necessari, Windows chiede all'utente se si vuole effettuare una reinstallazione o un ripristino del sistema; naturalmente, bisogna selezionare la seconda opzione.
A questo punto viene mostrato l'elenco di installazioni disponibili; generalmente, la lista che appare è del tipo:

1: C:\WINDOWS

Selezionando il numero dell'installazione da ripristinare, si entra nella cosiddetta console di ripristino; dalla console si può visualizzare l'elenco delle partizioni attraverso il comando:

map

Le varie partizioni vengono mostrate con una sintassi del tipo:

\Device\Harddisk0\Partition1

Supponendo che Harddisk0 contenga il MBR da riparare, dobbiamo allora impartire il comando:

fixmbr \Device\HardDisk0

Ora possiamo impartire il comando:

exit

in modo da uscire dalla console e riavviare il computer (dopo aver tolto il CD/DVD di installazione).

Si tenga presente che, sia FDISK, sia FIXMBR, eliminano un eventuale bootloader (come Lilo) installato da Linux; per ripristinare il bootloader di Linux e per maggiori dettagli su questi importanti aspetti, si consiglia di consultare le numerose informazioni presenti su Internet.

2.5.2 Esempio di bootloader

Passiamo finalmente alla scrittura di un piccolo bootloader "didattico"; lo scopo di questo bootloader è semplicemente quello di ricevere il controllo dal BIOS, mostrare alcune informazioni diagnostiche e chiedere all'utente di riavviare il computer.

Il primo aspetto da affrontare riguarda il fatto che, come è stato spiegato in precedenza, il BIOS carica i 512 byte del bootloader in memoria, all'indirizzo logico 0000h:7C00h; a questo punto, lo stesso BIOS cede il controllo al nostro bootloader e ci lascia "soli con noi stessi"!
Non bisogna dimenticare, infatti, che in questa fase non esiste nessun SO capace di fornirci i suoi servizi; una prima conseguenza di questo aspetto, è che gli eventuali strumenti di cui possiamo aver bisogno, devono essere creati attraverso i servizi del BIOS.
Supponiamo, ad esempio, di voler visualizzare sullo schermo il contenuto esadecimale di un registro a 16 bit; a tale proposito, dobbiamo prima convertire il valore esadecimale in una stringa. Infatti, come abbiamo visto in precedenza, la scheda video viene inizializzata in modalità testo 80x25, per cui sullo schermo possiamo visualizzare solamente simboli appartenenti al set dei codici ASCII!

Una stringa formata esclusivamente da codici ASCII di cifre numeriche prende il nome di stringa numerica; si tratta in sostanza di una stringa formata da una sequenza di caratteri del tipo '0', '1', '2', etc.
Per convertire un numero esadecimale in una stringa numerica, si può utilizzare un metodo semplicissimo; a tale proposito, creiamoci prima le seguenti stringhe:

RegStr      db    "XXXXh"
HexStr      db    "0123456789ABCDEF"

Consideriamo ora il valore esadecimale 8CE9h contenuto in AX; copiamo tale valore in BX e isoliamo il nibble meno significativo con l'istruzione:

and bx, 000Fh

In questo modo si ottiene, ovviamente, BX=0009h; osserviamo ora che:

byte [HexStr + bx] = byte [HexStr + 9] = '9'

Il carattere '9' viene copiato in [RegStr+3].

Facciamo scorrere ora il contenuto di AX di 4 bit verso destra; in questo modo otteniamo AX=08CEh; copiamo tale valore in BX e isoliamo il nibble meno significativo con l'istruzione:

and bx, 000Fh

In questo modo si ottiene, ovviamente, BX=000Eh; osserviamo ora che:

byte [HexStr + bx] = byte [HexStr + Eh] = byte [HexStr + 14] = 'E'

Il carattere 'E' viene copiato in [RegStr+2].

A questo punto appare evidente che, con due ulteriori passi, il numero esadecimale 8CE9h viene facilmente convertito nella stringa numerica "8CE9h"; la procedura che esegue questo lavoro, presuppone che AX contenga il numero da convertire e assume il seguente aspetto:

Hex16toStr:
   
   mov      si, 3                   ; offset 3 in RegStr
   mov      cx, 4                   ; 4 loop (4 cifre esadecimali)
   
hex_loop:
   mov      bx, ax                  ; bx = prossimo nibble
   and      bx, 000Fh               ; isola i 4 bit meno significativi
   mov      dl, [HexStr + bx]       ; converte in ASCII
   mov      [RegStr + si], dl       ; salva in RegStr
   dec      si                      ; prossimo carattere di RegStr
   shr      ax, 4                   ; prossimo nibble da esaminare
   loop     hex_loop                ; controllo loop
   
   retn                             ; near return

Una volta ottenuta la stringa numerica, possiamo visualizzarla attraverso il servizio BIOS n.13h della INT 10h; in particolare, il programma presentato più avanti visualizza la coppia CS:IP e il contenuto del registro DL che rappresenta il codice del disco dal quale è avvenuto il boot.

Un altro importante aspetto da affrontare, riguarda l'indirizzamento dei vari dati del nostro programma; a tale proposito, partiamo dal fatto che il BIOS carica il bootloader in memoria a partire dall'indirizzo logico 0000h:7C00h, pone poi CS:IP=0000h:7C00h e infine cede il controllo alla CPU.
Per l'indirizzamento del codice non c'è quindi nessun problema in quanto il BIOS ha provveduto ad inizializzare correttamente CS:IP; il problema si pone, invece, per l'indirizzamento dei dati.
Ciò accade in quanto, in assenza del SO, non viene effettuata nessuna rilocazione della componente Seg che carichiamo in DS (o in ES) per accedere ai dati; questo delicato lavoro spetta dunque al programmatore!

Assumiamo allora che il nostro bootloader sia contenuto in un eseguibile in formato COM; come sappiamo, in tal caso il file eseguibile contiene esclusivamente il codice macchina del programma (ed è proprio ciò che vogliamo). L'unico segmento di programma presente, sarà quindi caricato in memoria a partire dall'indirizzo logico 0000h:7C00h; tale indirizzo logico corrisponde all'indirizzo fisico, multiplo di 16:

0000h * 10h + 7C00h = 00000h + 7C00h = 07C00h

Come sappiamo, in ambiente DOS i primi 256 byte del segmento unico di un programma COM devono essere riservati al PSP; nel nostro caso, non esiste nessun DOS, per cui possiamo posizionare l'entry point dove vogliamo!

Una prima soluzione consiste allora nell'aprire il segmento unico di programma con la direttiva:

org 7C00h

In questo caso, sappiamo che l'assembler genera un eseguibile dove tutte le componenti offset dei dati risulteranno sommate a 7C00h; a questo punto, per accedere correttamente ai dati stessi, non dobbiamo fare altro che caricare 0000h in DS.

La seconda soluzione parte dal presupposto che l'indirizzo fisico 07C00h corrisponde all'indirizzo logico normalizzato 07C0h:0000h; ciò significa che l'indirizzo logico 07C0h:0000h è perfettamente equivalente all'indirizzo logico 0000h:7C00h!
Possiamo aprire allora il segmento unico di programma con la direttiva:

org 0000h

In questo caso, sappiamo che l'assembler genera un eseguibile dove tutte le componenti offset dei dati risulteranno sommate a 0000h; a questo punto, per accedere correttamente ai dati stessi, non dobbiamo fare altro che caricare 07C0h in DS.

Un ulteriore aspetto interessante riguarda l'eventualità di voler sapere se il valore assunto da IP all'entry point sia veramente 7C00h; a tale proposito, possiamo servirci del seguente codice:

000Eh E8h 0000h      call     near get_ip
0011h             get_ip:
0011h 58h            pop      ax
0012h 2Dh 0011h      sub      ax, (($ - $$) - 1)

Osserviamo che nell'esempio, la CALL (diretta intrasegmento) si trova all'offset 000Eh; questa istruzione salva nello stack l'indirizzo di ritorno 0011h e salta a CS:0011h. Ma l'indirizzo di ritorno 0011h non è altro che il valore da caricare in IP per la prossima istruzione da eseguire; tale valore viene quindi estratto dallo stack e salvato in AX (ovviamente, la POP ha anche lo scopo di sostituire l'istruzione RETN).
Ad AX dobbiamo ora sottrarre l'offset corrente ($-$$) che, per l'istruzione SUB, è 0012h; siccome però la POP ha un codice macchina da 1 byte, dobbiamo sottrarre anche 1 a 0012h ottenendo così 0011h.
Quando il programma è in fase di esecuzione, la CALL provoca un salto a:

CS:IP = 0000h:(0011h + 7C00h) = 0000h:7C11h

L'indirizzo di ritorno salvato nello stack è quindi 7C11h; di conseguenza, l'istruzione SUB produce:

AX = 7C11h - 0011h = 7C00h

Analizziamo infine gli aspetti relativi all'uscita dal programma; la soluzione più semplice consiste nel chiedere all'utente di togliere il floppy disk e riavviare il computer con la sequenza di tasti:

[Ctrl] + [Alt] + [Canc]

Esiste però una soluzione più elegante che consiste in un riavvio automatico attraverso un salto FAR a FFFFh:0000h. Come sappiamo, nei vecchi PC a tale indirizzo è presente un salto FAR alla prima istruzione eseguibile del POST che provoca il reset della CPU con conseguente riavvio (reboot) del computer; nei moderni PC, per compatibilità, in FFFFh:0000h si trovano le istruzioni che provocano il riavvio o lo spegnimento del computer.
Nel caso generale, prima di effettuare il salto FAR, i SO inseriscono un apposito codice a 16 bit (POST reset flag) in un'area della BDA che si trova all'indirizzo logico 0040h:0072h; sono disponibili i seguenti codici:

Figura 9 - BDA POST Reset Flags
ValoreEffetto
0000h Cold boot - Riavvio totale del computer
0064h Burn-in mode
1234h Warm boot - Riavvio senza pulizia della RAM
4321h (Solo PS/2) - Riavvio senza pulizia della RAM
5678h (Solo PC Convertible) System suspended
9ABCh (Solo PC Convertible) Manufacturing test mode
ABCDh (Solo PC Convertible) POST loop mode

Nel caso del nostro bootloader, utilizziamo il codice 1234h per un warm reboot (che equivale alla pressione dei tasti [Ctrl]+[Alt]+[Canc]); il codice 0000h (cold boot) permette, invece, di spegnere il computer (ovviamente, solo per i PC che supportano lo spegnimento via software).

A questo punto abbiamo chiarito tutti i dettagli relativi al nostro bootloader; il conseguente listato è illustrato in Figura 10.

Figura 10 - File BOOTLOAD.ASM

Come si vede in Figura 10, per l'accesso ai dati è stato scelto l'indirizzo logico di riferimento 07C0h:0000h (perfettamente equivalente a 0000h:7C00h); di conseguenza, la direttiva ORG deve specificare il parametro 0000h (nessuna traslazione in avanti degli offset).
Il paragrafo 07C0h viene quindi caricato in DS, ES e SS (NEARSTACK); il registro SP viene inizializzato con il valore massimo possibile FFFEh (nessuno ce lo impedisce).

La macro WRITE_STRING usa il servizio BIOS n.13h della INT 10h per visualizzare una stringa; la macro WAIT_CHAR attende la pressione di un tasto grazie al seguente servizio BIOS n.00h della INT 16h (Keyboard services):

Figura 11 - Servizio n. 00h della INT 16h
INT 16h - Servizio n. 00h - Read keyboard input:
   attende la pressione di un tasto.

Argomenti richiesti:
   AH = 00h (servizio Read keyboard input)
   
Valori restituiti:
   AL = codice ASCII del tasto premuto
   AH = codice di scansione del tasto premuto

Analizzando il listing file del programma di Figura 10, si può notare che siamo al limite dei 512 byte; aggiungendo poche altre istruzioni, tale limite viene superato. In un caso del genere NASM produce un messaggio di errore per indicare che la sottrazione 510-($-$$) ha prodotto un valore negativo; in sostanza, siamo andati oltre l'offset massimo 510 (dopo il quale c'è il codice AA55h)!
Proprio per questo motivo, i moderni bootloader non fanno altro che caricare in memoria un ulteriore programma di dimensioni più consistenti; tale programma, non avendo problemi di spazio, può così effettuare tutte le necessarie inizializzazioni del SO.

2.5.3 Generazione dell'eseguibile

Per l'eseguibile di un bootloader conviene decisamente orientarsi sul formato COM; infatti, sappiamo che per tale formato viene generato il solo codice macchina, senza nessun inutile header che finirebbe anche per alterare le dimensioni (512 byte) del file eseguibile.
Nel caso di NASM possiamo evitare l'uso del linker e ottenere direttamente l'eseguibile finale (formato binario) attraverso il comando:

nasm -f bin bootload.asm -o bootload.bin

Osservando il file BOOTLOAD.BIN così ottenuto possiamo notare che le sue dimensioni sono pari esattamente a 512 byte!

Naturalmente, è anche possibile adattare il listato di Figura 10 in modo da poter generare un eseguibile in formato COM classico; più avanti verranno dati dei chiarimenti per chi usa MASM e TASM.

2.5.4 Installazione del bootloader

Come è stato ampiamente precisato in precedenza, il bootloader presentato in Figura 10 è destinato ad essere installato nel MBR di un floppy disk; chi intende servirsi del proprio hard disk, si assume tutte le responsabilità sulle possibili conseguenze!

Per gli utenti DOS/Windows, esiste un metodo semplicissimo che permette di effettuare l'installazione del bootloader; a tale proposito, bisogna servirsi del comando W (write) nel programma DEBUG (già illustrato nel Capitolo 14 della sezione Assembly Base).
La sintassi del comando W è la seguente:

w address drive sector number

address indica l'indirizzo da cui leggere il blocco sorgente;
drive indica l'unità su cui scrivere i dati;
sector indica il settore iniziale di scrittura;
number indica il numero di blocchi da 512 byte da scrivere.

Per quanto riguarda il drive, abbiamo visto in precedenza che il BIOS assegna il codice 00h al primo lettore floppy disk e 01h al secondo lettore floppy disk; analogamente, il codice 80h indica il primo hard disk e 81h indica il secondo hard disk.
Il settore iniziale di scrittura è ovviamente il n.0; il numero di blocchi da 512 byte che dobbiamo scrivere è 1.
In relazione all'indirizzo da cui leggere il blocco sorgente, bisogna ricordare che DEBUG carica i programmi in formato COM a partire dall'indirizzo CS:0100h; il valore da caricare in CS viene scelto dallo stesso DEBUG e non deve essere assolutamente alterato!

Posizionandoci allora nella directory di lavoro (dove si trova BOOTLOAD.BIN), impartiamo il comando:

debug bootload.bin

Come verifica possiamo impartire il comando u (unassemble) che ci mostrerà il disassemblato delle prime istruzioni del nostro programma; in questo modo si può anche constatare che il programma stesso è stato caricato in memoria a partire dall'indirizzo logico CS:0100h.
A questo punto, inseriamo un floppy disk e da DEBUG impartiamo il comando:

w 100 0 0 1 (per il primo floppy disk)

Il nostro bootloader è ora installato nel MBR del primo floppy disk; infatti, riavviando il computer si può constatare che il BIOS cede il controllo al programma BOOTLOAD.BIN!

Gli utenti Linux possono seguire l'identico metodo appena illustrato; a tale proposito, bisogna eseguire il clone di DEBUG da DOSEmu.
Esiste però un altro metodo molto più semplice e sicuro, che consiste nel servirsi del potente comando dd per la copia di dati binari grezzi da una sorgente ad una destinazione; la sintassi generica di questo comando è:

dd if=input_file of=output_file bs=block_size count=num_blocks

input_file indica la sorgente da cui leggere i dati;
output_file indica la destinazione in cui scrivere i dati;
bs indica la dimensione di ogni singolo blocco da trasferire;
count indica il numero di blocchi da trasferire.

Naturalmente, il comando dd deve essere eseguito da una console Linux e NON da DOSEmu!

Ricordando che in Linux "tutto è un file", avremo if=bootload.bin e of=/dev/fd0 (primo lettore floppy disk); aprendo allora una console e posizionandoci nella directory di lavoro in cui si trova BOOTLOAD.BIN, inseriamo un floppy disk e impartiamo il comando:

dd if=bootload.bin of=/dev/fd0 bs=512 count=1

Tenendo conto del fatto che il valore predefinito per bs è 512, mentre per count è 1, possiamo anche scrivere semplicemente:

dd if=bootload.bin of=/dev/fd0

Nota importante.
Si faccia molta attenzione a non utilizzare disinvoltamente il comando dd; ciò vale, in particolare, quando si specifica l'hard disk come destinazione!
Prima di utilizzare il comando dd è vivamente consigliabile la lettura delle relative "pagine man" (man dd).


2.5.5 Conversione di BOOTLOAD.ASM in versione MASM/TASM

Se si intende convertire il listato di Figura 9 in versione MASM/TASM, è necessario tenere presente che il significato della direttiva ORG varia da assembler ad assembler.
Nel caso di NASM, una direttiva del tipo:

ORG 150

sposta il location counter in avanti di 150 byte a partire dall'offset corrente (in cui si trova la stessa direttiva ORG); nel caso di MASM e TASM, invece, la precedente direttiva posiziona il location counter all'offset 150 del segmento di programma corrente!
Quindi, con MASM e TASM, la parte finale del nostro bootloader può essere scritta in questo modo:

org      510
dw       0AA55h

Bisogna anche ricordare che TASM non permette la creazione di un eseguibile in formato COM con entry point ad un offset diverso da 0100h; la riscrittura di BOOTLOAD.ASM in versione TASM rappresenta un valido esercizio per chi vuole verificare la propria conoscenza dell'Assembly!

Bibliografia

IA-32 Intel Architecture Software Developer's Manual - Volume 3: System Programming Guide
(24547212.pdf)

PhoenixBIOS 4.0 Revision 6 User's Manual
(userman.pdf)

PhoenixBIOS 4.0 Release 6.0 POST Tasks and Beep Codes
(biospostcode.pdf)

Phoenix Technologies, Ltd. AwardBIOS Version 4.51PG - Post Codes & Error Messages
(biosawardpostcode.pdf)

Phoenix IBM - "El Torito" Bootable CD-ROM Format Specification Version 1.0
(specs-cdrom.pdf)

Ralf Brown's Interrupt List