2009-04-28:

Boot menu i blip

asm:easy:boot:blog
boot menuPewnego razu Xa, który bardzo lubi eksperymentować z OS'ami, chciał zainstalować dodatkowy system operacyjny - nazwijmy go "Gamma System" - na swoim komputerze, na którym był już pewien system - nazwijmy go "Alpha OS". Traf chciał że za równo "Gamma System" jak i "Alpha OS" nie miały boot loaderów które pozwoliły by wybierać który system ma być bootowany, a w dodatku, każdy loader był na tyle indywidualny, że to właśnie on musiał zostać użyty - więc powstał problem. Oczywiście, jak to zwykle bywa, użycie LILO czy GRUB odpadało z przyczyn różnych. W związku z czym dostałem questa - zrobić żeby to jakoś działało.

Na początek, jeszcze przed instalacją drugiego systemu, Xa dostał polecenie skopiowania 1MB z początku dysku (czyli MBR & co.), tak na wszelki wypadek :), po czym przystąpił do instalacji. Zainstalowany system oczywiście nadpisał MBR swoim boot loaderem, zupełnie ignorując boot loader poprzedniego systemu.

Po zakończonej instalacji zarchiwizowałem obecne MBR (1MB, tak na wszelki wypadek), i skopiowałem stary MBR (446 bajtów, bez tablicy partycji) na miejsce obecnego, po czym zrebootowałem kompa - a stary system się uruchomił poprawnie - ten prosty eksperyment pokazał że tak na prawdę wystarczy podmieniać MBRy żeby wybierać który system ma się zbootować. Dodam że do podmiany MBR korzystałem z polecenia dd odpalanego z Live CD z Ubuntu:

Archiwizacja (samego MBR): dd if=/dev/sda of=/tmp/old.mbr bs=1 count=446
Archiwizacja (1MB): dd if=/dev/sda of=/tmp/old.mbr bs=1024 count=1024
Wrzucenie innego MBR: dd if=/tmp/some.mbr of=/dev/sda bs=1 count=446

W związku z czym stwierdziłem że stworzę bardzo proste Boot Menu, które wyświetli logo Xa (to ofc najważniejsza funkcjonalność), a następnie pozwoli wybrać który OS ma się uruchomić, przywróci odpowiedni MBR, i go uruchomi.

Jeżeli chodzi o toolkit, to wybrałem następujący:
* Netwide Assembler - jest imo najwygodniejszy do pisania takich rzeczy
* dd - do wrzucania mojego tworu na dysk (wersja pod Windows)
* Bochs + Bochs Enhanced Debugger 1.05 (by Chourdakis Michael) - do testowania i debugowania
* Sun VirtualBox - do testowania
* MinGW g++ - do stworzenia konwertera logo

Na pierwszy ogień pójdzie logo - logo miało wyświetlać się w trybie tekstowym (ponieważ nie chciało mi się w tryb graficzny wchodzić), w związku z czym dostałem od Xa bitmapkę (którą skonwertowałem do BMP) wielkości 80x25x16 kolorów. Jak wiadomo (lub nie), w trybie tekstowym również można modyfikować paletę kolorów (tylko że kolory mają nie-kolejne numery, o tym za chwilę) - więc Xa mógł użyć dowolne 16 kolorów.
Ponieważ miałem zamiar skopiować logo bezpośrednio do pamięci obrazu trybu tekstowego (B800:0000), potrzebna była konwersja logo do formatu wykorzystywanego w tym miejscu - jest on trywialny - każdy znak opisywany jest dwoma bajtami:
- pierwszy bajt to kod ascii znaku (użyłem wszędzie 0xDB, czyli po prostu pełnego prostokątu)
- drugi bajt to atrybut, a konkretniej: cztery górne bity to kolor tła, a cztery dolne to kolor znaku (kolor tła ustawiłem na 0)

Konwerter jest dość prosty, i wygląda następująco (dodam że BMP jest zapisywane od dołu do góry - skorygowałem to ręcznie w Irfan View): bmp2txt.cpp

Konwerter oprócz konwersji pliku graficznego, eksportuje również paletę kolorów (do formatu który mogę w nasmie include'nąć jako dane). Paleta ma następujący format:
- numer koloru w palecie (patrz tabelka po prawej - zrobiłem ją ponad 9 lat temu, stąd podpis z teamem 'mystic' a nie 'vexillium' ;>)
- red (od 0 do 63)
- green (od 0 do 63)
- blue (od 0 do 63)

Sprawa logo jest więc załatwiona, teraz samo Boot Menu. Najpierw pseudo-kod / algorytm / zasada działania:
1) kod w MBR (zwany dalej mbr1) ma wczytać boot menu (mbr2) do pamięci pod adres 0000:2000
2) i tam skoczyć...
3) mbr2 ma ustawić paletę (porty 3C8 i 3C9)
4) wyświetlić logo (tj. skopiować je pod adres B800:0000)
5) napisać kilka rzeczy na ekranie (menu!)
6) odczytać naciśnięcie (prawidłowego) klawisza
7) wczytać pod 0000:7C00 odpowiedni oryginalny MBR (446 bajtów)
8) ustawić odpowiedniej partycji flagę bootowalności (i zdjąć drugiej)
9) przywrócić oryginalną paletę kolorów
10) przywrócić środowisko (rejestr DL - numer dysku z którego następuje bootownie)
11) i skoczyć pod adres 0000:7C00 (czyli do odpowiedniego MBR)

Oryginalny MBR oczywiście będzie "myślał" że to BIOS go odpalił.

Oczywiście mbr1 zostanie umieszczony na początku dysku (w pierwszym sektorze), natomiast mbr2 będzie w innym miejscu, i to kapkę większe. A konkretniej:
3000h - tam trafi kod mbr2
3C00h - tu będzie oryginalny MBR pierwszego OSu (446 bajtów)
3E00h - tu będzie oryginalny MBR drugiego OSu (446 bajtów)
4000h - a tutaj trafi logo (80*25*2 bajtów)
Czyli podsumowując, mbr2 rozciąga się od 3000h do 5000h (czyli od 19 do 28 sektora dysku włącznie).

Czas na trochę assemblerowego kodu! Na początek MBR1:

[bits 16]
[org 0x7c00]

start:

; Setup CS
jmp 0000:7c05h

; Disable INTs for a sec
cli

; Setup stack
xor ax, ax
mov ss, ax
mov sp, 0800h ; stack at 0000:2000 (a good place for the stack)

; Setup other regs
mov es, ax
mov ds, ax

; INTs are good to go
sti

; Setup direction
cld

; Load the HD:3000 -> HD:4FFF to 0000:2000 -> 0000:3FFF
mov bx, 2000h
mov ax, 0210h
mov cx, 19h  ; 1 + 3000/200 == 1 + 18 + 19
xor dx, dx
mov dl, 80h
int 13h

mov ax, 2000h
jmp ax

times (446-($-start)) db 0


Tutaj cudów raczej nie ma. Jeżeli chodzi o użyty int 13h / 02h, to odsyłam do Ralfa. Zaznaczę jednak że w tym miejscu powinno znaleźć się również zapamiętanie rejestrów - natomiast ja to zignorowałem i hardcode'nąłem wartości odczytane z BOCHS'a (brzydko, ale robiłem to dla Xa, a nie uniwersalnie ;>).

Druga część, czyli mbr2, jest kapkę dłuższa (co ważniejsze partie mają jakiś komentarz):

[bits 16]
[org 2000h]

%define BITMAP_ADDR 3000h

start:

; Setup the palette
xor esi, esi
mov si, palette_splash
mov cx, 16
load_pal_1:
call set_rgb
loop load_pal_1

; Copy the bitmap
mov si, BITMAP_ADDR
mov ax, 0b800h
mov es, ax
mov cx, 80*25
xor di, di
rep movsw

; Output some strings
mov al, 0x30
mov si, str_choose
mov cx, 20
mov di, (10 * 80 + 53) * 2
call puts

mov al, 0x31
mov si, str_os1
mov cx, 11
mov di, (12 * 80 + 53) * 2
call puts

mov si, str_os2
mov cx, 15
mov di, (13 * 80 + 53) * 2
call puts

mov si, str_ichoose
mov cx, 12
mov di, (15 * 80 + 53) * 2
call puts

; Key pressed?
menu:
xor ax, ax
push di
int 16h
pop di
mov ah, 30h
mov [es:di], ax

cmp al, '1'
je boot_os1

cmp al, '2'
je boot_os2

; Loop
jmp menu

%define PART1_FLAG (0x7C00+0x1BE+16*0)
%define PART2_FLAG (0x7C00+0x1BE+16*1)
%define PART3_FLAG (0x7C00+0x1BE+16*2)
%define PART4_FLAG (0x7C00+0x1BE+16*3)
%define PART_BOOTABLE 0x80
%define PART_NOTBOOTABLE 0x00

boot_os1:
; Black the screen
xor ax, ax
mov di, 0
mov cx, 80*25
rep stosw

mov byte [PART1_FLAG], PART_NOTBOOTABLE
mov byte [PART3_FLAG], PART_BOOTABLE
mov si, OS1_loader ; OS1 loader
jmp continue_boot


boot_os2:
; Black the screen
xor ax, ax
mov di, 0
mov cx, 80*25
rep stosw

mov byte [PART1_FLAG], PART_BOOTABLE
mov byte [PART3_FLAG], PART_NOTBOOTABLE
mov si, OS2_loader ; OS2 loader
jmp continue_boot

continue_boot:

; Copy the loader
xor ax, ax
mov es, ax
mov ds, ax
mov di, 0x7c00     ; MBR mem addr
mov cx, 446        ; MBR size - MBR partition list
rep movsb

; Restore the palette
xor esi, esi
mov si, palette_org
mov cx, 16
load_pal_2:
call set_rgb
loop load_pal_2

; Jump to the loader
xor bx, bx
xor cx, cx
xor dx, dx
mov dl, 0x80
xor si, si
mov si, 0xfdba
xor bp, bp
jmp 0x7c00

; DONE!

; Output string
puts:
 movsb
 stosb
 loop puts
 ret
 

set_rgb:
 mov dx, 3c8h
 outsb
 mov dx, 3c9h
 outsb
 outsb
 outsb
 ret

str_choose: db "Choose your destiny!" ; 20
str_os1     : db "1. Alpha OS" ; 11
str_os2     : db "2. Gamma System" ; 15
str_ichoose : db "I choose... " ; 12

palette_splash:
%include "splash.pal"

palette_org:
db  0, 0 , 0 , 0
db  1, 0 , 0 , 42
db  2, 0 , 42, 0
db  3, 0 , 42, 42
db  4, 42, 0 , 0
db  5, 42, 0 , 42
db  20, 42, 42, 0
db  7, 42, 21, 42
db  56, 0 , 0 , 21
db  57, 21, 21, 63
db  58, 21, 63, 21
db  59, 21, 63, 63
db  60, 63, 21, 21
db  61, 63, 21, 63
db  62, 63, 63, 21
db  63, 63, 63, 63

; Padd
times (0xC00-($-start)) db 0
OS1_loader:
times (0xE00-($-start)) db 0
OS2_loader:


Kod nie jest specjalnie skomplikowany, tak że osoby zainteresowane zachęcam do samodzielnej analizy :)
Na pewno przyda się opis int 16h / 00h, opis portów 3c8 i 3c9 (trudno mi dobrego linka znaleźć), oraz opis MBR / tablicy partycji.

I na koniec skrypt kompilujący:

@echo off

echo Creating test HDD...
copy xahdd.img.clean xahdd.img

echo Converting splash...
bmp2txt > splash.pal

echo Embedding splash at HD:4000h
dd if=out.asc of=xahdd.img bs=1 count=4000 seek=16384 2>nul

echo Copying original MBR (partition table for tests)
dd if=os2.mbr of=xahdd.img bs=1 count=512 2>nul

echo Assemble mbr1
nasm mbr1.asm

echo Embedding mbr1 at HD:0000h
dd if=mbr1 of=xahdd.img bs=1 count=446 2>nul

echo Assemble mbr2
nasm mbr2.asm

echo Embedding mbr2 at HD:3000h
dd if=mbr2 of=xahdd.img bs=1 count=4096 seek=12288 2>nul

echo Embedding OS1 MBR at HD:3E00h
dd if=os1.mbr of=xahdd.img bs=1 count=446 seek=15872 2>nul

echo Embedding OS2 MBR at HD:3C00h
dd if=os2.mbr of=xahdd.img bs=1 count=446 seek=15360 2>nul

echo Copying hdd to bochs directory
copy /y xahdd.img bocsh\xahdd.img


I chyba tyle jeżeli o ten temat chodzi. Dodam że boot menu chodzi ślicznie :)

Na koniec dodam jeszcze że za namową Piotrka Koniecznego założyłem sobie konto na blipie - zainteresowanych zapraszam do subskrypcji / dodania mnie do pokemonów / co tam się w zasadzie robi :)

Add a comment:

Nick:
URL (optional):
Math captcha: 9 ∗ 4 + 9 =