Usare bochs per fini di debug!

Bene il debugging come molti sanno è una parte molto difficile e noiosa.  Quando si parla di osdev è ancora peggiore. Infatti, moltissimi strumenti a disposizione per il debugging risultano o piu difficili da usare, o inutili. In questo caso un grosso aiuto può venirci proprio da bochs, che ha un debugger interno utilizzabile anche mediante l’ausilio di una Interfaccia grafica. Come molti di voi gia sapranno bochs è facilmente reperibile nella maggior parte dei  repository delle piu svariate distribuzioni (da debian, a ubuntu, ad Archlinux, fedora etc). Purtroppo quella versione il piu delle volte non va bene, infatti è compilata senza il supporto per il debug, quindi la prima cosa da fare è scaricare i sorgenti di bochs. Per farlo potete andare a questo sito: http://sourceforge.net/projects/bochs/files/bochs/2.4.5/ Scaricate la versione con i sorgenti (il .tar.gz o .tgz) Quindi portatevi dove é stato scaricato il file e scompattatelo: tar -xzf bochs-2.4.5.tar.gz ora portiamoci nella cartella dove lo abbiamo scompattato e cominciamo la fase di compilazione. Per prima cosa diamo il configure con i seguenti argomenti:

/configure --enable-iodebug --enable-disasm --enable-debugger

Spieghiamo brevemente le tre opzioni:

  1. –enable-iodebug abilita delle speciali porte di I/O che il S/O puo utilizzare per mandare delle stampe sul terminale
  2. –enable-disasm abilita il disassembler interno
  3. –enable-debugger abilita il debugger interno

Una volta terminata la configure diamo il solito:

make

Se tutto è andato bene abbiamo due possibilità, la prima è proseguire con l’installazione e quindi dare:

make install

oppure lasciare le cose come stanno, e usare bochs chiamandolo utilizzando il path assoluto. N.b. Per dare il make install bisogna essere root. Una volta terminata l’installazione possiamo iniziare a utilizzare bochs. Notate che il vostro progetto per poter essere lanciato con questo emulatere deve contenere un file chiamato .bochsrc. Per poter avviare la gui del debug dobbiamo editare tale file e posizionarci alla riga:

display_library = x

e aggiungere l’opzione gui_debug, quindi avremo:

display_library = x, options="gui_debug"

Dopo di che possiamo lanciare bochs e iniziare a debuggare. Passiamo ora a vedere il funzionamento del debugger. Una volta lanciato bochs, la schermata si presenterà simile alla seguente: Se non dobbiamo modificare nulla a livello di configurazione possiamo partire con la scelta di default (la 6), ci ritroveremo davanti una schermata simile alla seguente: Spieghiamo brevemente a cosa servono le finestre (anche alcune sono intuitive):

  • La numero 1 è la finestra dell’emulatore vero e proprio (dove viene eseguito il nostro OS)
  • La numero 2 invece è la finestra del terminale dove verranno eseguite eventuale stampe di debug inviate dal nostro sistema operativo.
  • La numero 3 invece è la finestra del debugger.

La numero 1 non penso che abbia bisogno di ulteriori spiegazioni. La due e la tre ci fanno entrare nel merito delle varie possibilità di debug offerte da bochs. Partiamo dall piu semplice. Ci sono molte componenti di un sistema operativo che non devono avere dell’output su schermo (per esempio il gestore del timer, o il gestore dei #PF)  per motivi molte volte diversi tra loro:

  • perchè genererebbero quantità di output spropositate (come per esempio il gestore dell’IRQ del timer)
  • perche semplicemente devono essere trasparenti all’utente (per esempio il #PF)
  • perche noi non vogliamo farlo sapere all’utente 🙂

Ma per fini di debug potrebbe tornarci alcune volte utile sapere se in siamo entrati in una certa funzione, se si é verificata una certa condizione, etc. Bochs mette a disposizione delle speciali porte hardware che permettono di stampare del testo direttamente su stdout del computer. Bochs ci mette a disposizone la porta 0xE9. Se vogliamo scrivere del testo direttamente su stdout del computer (quello che esegue bochs 🙂 ) basta inviare i dati che vogliamo stampare su quella porta, con la funzione outb (probabilmente se state scrivendo il vostro os vi sarete gia premuniti di una funzione outportb simile alla seguente:

outportb(destination port, data);

I dati ovviamente andranno inviati byte per byte. Se per esempio vogliamo far vedere la lettera ‘c’ su stdout basta fare:

outportb(0XE9, 'c')

Quindi per comodità ci converrà scrivere una funzione che che stampa una stringa inviandola byte per byte alla porta 0xE9. Per esempio in DreamOS (il mio sistema operativo) é stata definita la seguente funzione:

void dbg_bochs_print( const unsigned char *msg )
{
    register unsigned int i;
    for ( i = 0; msg[i] != '\0'; i++ )
        outportb(0xE9, msg[i]);
return;
}

Che come vedete si occupa di stampare la stringa msg, passata come argomento della funzione.

Passiamo ora a vedere come controllare l’esecuzione del programma. Di seguito trovate una screenshot di come appare la finestra del debug:

Vediamo a cosa servono le varie sezioni:

A. Mostra il contenuto dei registri. Di default solo i registri dati (eax, ebx, etc) l’eip,  eflags e i registri dello stack vengono visualizzati. Sono comunque attivabili (tramite il menu Options) anche tutti gli altri (i segment registers, control registers, System registers, etc).

B. Questa colonna Mostra il codice in esecuzione e viene evidenziata l’istruzione in esecuzione. Facendo doppio click su di una istruzione viene automaticamente attivato il breakpoint su di essa.v

C. Questa colonna ha vari utilizzi. In base alla scelta che facciamo nel menu View. Le opzioni disponibili sono: Stack, Linear Memory Dump, Physical Memory Dump, IDT, GDT, Breakpoints, Pagetable, Current Memdump. Queste opzioni credo si spieghino da sole.

D. Mostra i messaggi del programma di risposta ai comandi inseriti nella casella E.

E. Casella di input per spedire comandi al debugger di bochs.

Come avrete potuto notare se lanciamo bochs, questo carica la gui, la finestra dell’emulazione e resta in attesa (senza iniziare ancora l’emulazione vera e propria). Per lanciarla ci basta inserire nella casella di input il comando ‘c’ (o continue).

Ma prima di fare questo vediamo un po di comandi utili per gestire i breakpoint:

  1. pbreak address -> Con questo comando aggiungiamo un breakpoint a partire dall’indirizzo fisico in address.
  2. vbreak address -> Aggiunge un break all’indirizzo virtuale address
  3. info break -> Ci ritorna la lista di breakpoint configurati
  4. bpe n -> Abilita il break point numero n (il numero possiamo prenderlo con info break).
  5. bpd n -> Disabilita (ma non elimina) il breakpoiont numero n
  6. delete n -> Elimina il breakpoint numero n.

Inoltre se facciamo doppio click su una delle istruzioni che sono contenute nella parte B della screenshot precedente, abilitiamo il breakpoint su quell’istruzione.

A questo punto una volta configurati tutti i breakpoint che ci servono possiamo lanciare l’esecuzione del sistema operativo. Ovviamente quello che succederá sará che appena si incontra una istruzione per il quale abbiamo inserito il breakpoint l’esecuzione si fermerá e tutti i dati contenuti nella finestra di debug verranno aggiornati, ovvero in A verranno caricati i valori correnti dei vari registri in B verrá caricato il segmento di codice in formato assembler che si sta eseguendo. Mentre la parte C resterá invariata. Il contenuto dipende da cosa scegliamo di mostrare. Le opzioni disponibili sono:

  • Lo Stack
  • La GDT
  • La IDT
  • La page Table
  • Una porzione della memoria

Inoltre possiamo anche decidere se visualizzare altri registri che sono nascosti nel riquadro A, quali:

  • CRx registers
  • Segment Registers
  • System Registers
  • MMX/SSE Registers
  • Debug Registers

Per far riprendere l’esecuzione ci basta inserire il comando c ma se vogliamo invece possiamo dire all’emulatore di eseguire solo l’istruzione successiva o le successive. Utilizzando le istruzioni della famiglia step*, vediamone alcune:

  • step (o abbreviato s) -> Permette di avanzare di una istruzione
  • stepi offset (abbreviato si offset) -> Avanza di un numero di istruzioni indicato da offset

Da notare che usando step/stepi quando l’esecuzione si ferma nuovamente tutte le informazioni visualizzate vengono aggiornate con i valori risultanti dall’esecuzione.

Vediamo infine un ultima categoria di breakpoint, chiamata i magic breakpoint, un altra comodissima feature messa a disposizione da bochs. Per abilitarla dobbiamo editare il file .bochsrc del nostro progetto e inserire la seguente riga:

magic_break: enabled=1

Ora grazie a questa feature di bochs possiamo chiamare un breakpoint direttamente da codice, mediante l’istruzione:

xchg bx, bx

Che altri non fa che scambiare bx con se stesso, ma bochs questa istruzione la riconosce come un magic breakpoint, e quindi ferma l’esecuzione.

Bene ho fatto una carrellata di come usare bochs per fare debug. Con questo è tutto.

Buon debug 🙂

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *