Ricerca tra la vecchia roba

Creare un bootloader

Posted: Novembre 18th, 2007 | Author: | Filed under: Programmazione | 1 Comment »

Non mi dilungherò troppo sul fatto che dovrei fare altro e che, benché fuori faccia un freddo very masai, dovrei uscire, trovarmi una girl con la quale fare fiki fiki e procreare per il bene dell’Italia e del suo PIL, invece no! nel fine settimana vengo preso da crisi mistiche e mi getto in attività di basso livello con il mio amante di silicio: questa settimana mi sono ripromesso di creare un bootloader.

Un bootloader non è altro che un programma che si preoccupa di caricare il kernel (e in alcuni impostare l’ambiente necessario al funzionamento) di un sistema operativo, sfruttando le chiamate a basso livello del BIOS del computer su cui deve girare; nel mio caso tratterò il caso di un computer della famiglia x86. 

Quando un sistema di questo tipo si avvia, vengono eseguite istruzioni collocate in memoria alla locazione 0xf000:0xfff0 del BIOS;lì è contenuta una istruzione di salto che trasferisce il controllo alle operazioni di power-on-self-test (POST) le quali eseguono dei test sulle periferiche connesse. A questo punto il BIOS cerca una periferica da cui eseguire il boot ed in particolare ne cerca una che abbia la segnatura 0xaa55 negli ultimi due byte del primo settore; trovato questo benedetto settore lo carica alla locazione di memoria 0x7c00:0x0000 ed inizia ad eseguirne il codice. Durante tutto questo periodo, il processore sta vivendo nella cosidettà modalità reale, cioé esso si comporta come se fosse un 8086 con registri a 16bit capaci di indirizzare solamente 1MB di memoria grazie all’utilizzo dell’indirizzamento segmento:offset; in pratica siccome per indirizzare 1MB sono necessari 20bit ed i registri sono di 16, si utilizzano due registri che indicano il segmento e l’offset all’interno di questo e si sommano tra loro moltiplicando il valore del registro di segmento per 16 (che equivale in base esadecimale ad aggiungergli uno zero), cioé in pratica se nel registro %cs sto tenendo il valore del segmento dati (facciamo finta che sia 0x1234) ed il registro %eip contiene l’offset dell’istruzione da eseguire (facciamo finta che sia 0x5678) il codice eseguito sarà all’indirizzo (cosidetto lineare)

Segmento  0x12340
Offset    0x05678
=================
Ind.Reale 0x179c8

 Ovviamente c’è della rindondanza nell’indicazione degli indirizzi in questo modo e, cosa più importante, permette solo di indirizzare 1MB di memoria che mi pare poco visto che io ne ho 2GB! per ovviare a questo problema esiste la modalità protetta con cui la memoria viene ancora divisa in segmenti ma in maniera da utilizzare fino a 4GB, ma questo magari nella prossima puntata!

Arriviamo al codice scritto in sintassi AT&T (può essere utile visto che di solito sono scritti in sintassi Intel) per creare un bootloader che esegue l’operazione elementare di preoccuparsi di individuare il driver da cui è stato caricato e di caricare il secondo settore dove si trova il codice (inutile) che visualizza tramite dei caratter,un ciclo continuo.

BOOTSEG=0x07c0      # This are segments so are realy 0xfc00
LOADSEG=0x07e0      # 0x7c00+0x200 (0x0200 = 512 byte)
/*real mode code*/
.code16

.global start# the entry point have to be global

start:
        jmp $BOOTSEG, $start2 # Canonicalizing %cs:%eip
start2:
        movw    %cs, %ax
        movw    %ax, %ds
        sti
        cld

        movw    $boot_msg, %si
        call    print_msg

        movw    $loading_msg,%si
        call    print_msg

        movw    $operation_drive_msg, %si
        call    print_msg

check_drive:                  # Bios let us know in %dl what disk is booting
        mov     %dl, %al
        add     $0x30, %al      # ASCII code for number 0
        mov     %al, number_drive_msg
        movw    $number_drive_msg,%si
        call    print_msg

        mov     $0x05, %di      # n of try
        movw    $LOADSEG, %ax
        movw    %ax, %es        # es:bx is where place the
        xor     %bx, %bx        # sector after read it

        call    reset_drive

load_code:                      #
        mov     $0x02, %ah      # ‘Read sectors from drive’ BIOS Service
        mov     $0x01, %al      # I want read 1 sector
        mov     $0x00, %ch      # 0th cylnder
        mov     $0x02, %cl      # starting from 2nd sector
        mov     $0x00, %dh      # 0th head
        int     $0x13
        jnc     loading_successfully
        add     $0x30, %ah
        mov     %ah, error_code
        call    error
        call    reset_drive
        jnz     load_code
        int     $0x18           # execute BASIC rom but it generates a
                                # "BOOT FAILURE" message

loading_successfully:
        mov     $ok_msg, %si
        call    print_msg
        ljmp    $LOADSEG, $0x0000

error:
        # error routine, you have to put the char code
        # of the error in $err_msg
        movw    $error_code, %si
        call    print_msg
        ret

boot_msg:
        .ascii  "packz’s bootloader . . . "
        .ascii  "new features coming soonnrnr"
        .byte   0

loading_msg:
        .ascii  "rntNow loading "
        .byte   0

operation_drive_msg:
        .ascii  "from drive "
        .byte   0

error_code:
        .ascii  ".rn"
        .byte   0

number_drive_msg:
        .ascii  "X "             # to be substitute with n driver
        .byte   0

number_sector_read:
        .ascii  "Xnr"
        .byte   0

ok_msg:
        .ascii  ". successfully"
        .byte   0

err_msg:
        .ascii  "tSome error occoured!!!nr"
        .byte   0

print_msg:                      # this routine print a string pointed
        lodsb                   # by ds:si
        andb    %al, %al        # check for NULL
        jz      done            # lodsb loads a byte in %al
        movb    $0xe, %ah
        movw    $0x0400, %bx
        int     $0x10
        jmp     print_msg
done:
        ret

reset_drive:
        xor     %ax, %ax
        int     $0x13
        dec     %di             # if di == 0 set zero flag
        ret

# fill with zero the remaining byte
.fill 0x1fe – (. – start) , 1, 0
.word 0xaa55
###############################################################################
# This is the second sector of floppy disk: the offset have to be calculated
# from the beginning of this sector.
###############################################################################
# here starts the second sector
second_sector:
        movw    %cs, %ax
        movw    %ax, %ds

        cld                     # Clear Direction Flag (DF=0)

begin_endless:
        movw    $arrow-second_sector, %si

endless:
        lodsb
        and     %al, %al
        jz      begin_endless
        movb    $0x0a, %ah      # print a char without move the cursor (0x0a)
        movb    $0x00, %bh
        movb    $0x07, %bl
        movw    $0x01, %cx
        int     $0x10
        jmp     endless

arrow:
        .ascii  "|/-"
        .byte   0

Questa roba va compilata nella seguente maniera (salvando il codice come boot.s)

as boot.s -o boot.o
ld boot.o -o boot -e start -Ttext 0x00 –oformat binary

e poi copiato ovviamente copiato nel primo settore tramite dd

dd if=boot of=/dev/fd0

(sostituite /dev/fd0 con il device o il file immagine che preferite). 

Se non avete un computer con un floppy potete provarlo in una macchina  virtuale: io l’ho provato su bochs e funge, mentre su virtual box e qemu non funziona il codice nel secondo settore e non capisco se è un mio errore o no…. boh…

Linkografia 


One Comment on “Creare un bootloader”

  1. 1 Fabio said at 4:17 pm on Gennaio 16th, 2008:

    Ciao,
    con bochs funziona!
    Ho creato l’immagine del floppy con bximage ed aggiunta al .bochsrc.Poi un bel
    dd conv=notrunc if=boot_packz of=a.img