Prawdopodobnie jesteś tutaj aby nauczyć się jak zacząć przygodę z OSDEVem. To jest szybki poradnik aby zacząć wyświetlać tekst na swoim własnym ekranie. Zaczniemy tutaj z pisaniem prostego assemblera, który normalnie jest uznawany jako bootloader. Polecam zainstalowanie jakiegoś IDE takiego jak Visual Studio Code lub innych. W tym przypadku użyjemy VS Code.
Do tego poradnika narazie potrzebujemy dwóch programów:
nasm – Potrzebny do kompilowania kodu
qemu – Potrzebny do uruchamiania systemu
Teraz pokaże kod który pisze Hello, World! na ekranie –
bits 16
org 0x7c00
global start
start:
push cs
pop ds
mov si, msg
print:
lodsb
test al, al
jz .done
mov ah, 0x0e
int 0x10
jmp print
.done:
hlt
jmp .done
msg: db "Hello, World!", 0
times 510-($-$$) db 0
dw 0xAA55
To może być skomplikowane, więc wyjaśnie wszystkie linie tutaj:
bits 16 – Potrzebny, aby kompilator wiedział że jesteśmy w trybie 16-bitowym
org 0x7c00 – Ta linia jest bardzo ważna, bo linker zazwyczaj ustawia nasz kod na adresie 0x0, więc nadpisujemy BIOS, i wtedy nie ma co odpalić, bo nie ma czym.
global start – Ta linia powoduje że BIOS wie gdzie znajduje się punkt wejściowy dla systemu
start – Definicja “funkcji”
push cs – Jest taka rzecz jak stack, która ma jakieś dane, i używa tzw. stack pointera (sp), i kiedy my wykonujemy push (coś) to wartość tego coś jest wrzucona na adres na który wskazuje stack pointer, i obniża wartość sp o 4.
pop ds – Pop (coś) to jest to samo jak z push (coś), tylko na odwrót. Pop (coś) zyskuje dane z sp i wartość tych danych znajduje się w tym czymś.
mov si, msg – Instrukcja mov powoduje, że wartość pierwszej rzeczy podanej, jest równa drugiej rzeczy, czyli w tym przypadku SI ma wartość “Hello, World!”.
print – Definicja “funkcji” która służy za pokazywanie liter na ekran
lodsb – Ta instrukcja jest trochę skomplikowana, ale bardzo przydatna. Działa mniej więcej tak:
mov al, [si] inc si
Czyli: bierze bajt spod adresu wskazywanego przez rejestr SI, zapisuje go do AL, a potem zwiększa SI, żeby wskazywał na następny znak w pamięci.
test al, al – Ta instrukcja sprawdza, czy rejestr AL ma wartość 0. Nie zmienia jego wartości, tylko ustawia flagi procesora.
jz .done – Jeśli wynik testu był równy zero (czyli trafiliśmy na znak 0 kończący string), skaczemy do etykiety .done.
mov ah, 0x0e – Ustawiamy funkcję BIOS-u. 0x0E to tryb „teletype output”, który wypisuje znak na ekranie i automatycznie przesuwa kursor.
int 0x10 – Wywołanie przerwania BIOS-u odpowiedzialnego za obsługę ekranu. BIOS bierze znak z rejestru AL i wypisuje go.
jmp print – Skaczemy z powrotem do etykiety print, aby wypisać kolejny znak.
.done: – Etykieta kończąca pętlę wypisywania tekstu.
hlt – Instrukcja zatrzymująca procesor do momentu następnego przerwania. Ponieważ nie mamy jeszcze systemu operacyjnego, po prostu „zamrażamy” CPU.
jmp .done – Prosta pętla nieskończona. Gdyby procesor się obudził, wróci z powrotem do hlt.
msg: db “Hello, World!”, 0 – Definicja danych. Tworzymy w pamięci tekst zakończony bajtem 0, który informuje nasz kod, gdzie kończy się napis.
times 510-($-$$) db 0 – Dopełniamy sektor zerami tak, aby cały bootloader miał dokładnie 512 bajtów.
dw 0xAA55 – Sygnatura bootloadera. BIOS sprawdza te dwa bajty, aby upewnić się, że sektor jest bootowalny. Bez tego kod się nie uruchomi.
Po skompilowaniu tego kodu i uruchomieniu go w QEMU zobaczysz na ekranie klasyczne Hello, World!. To jest pierwszy krok w OSDEV-ie – od tego momentu masz pełną kontrolę nad maszyną.
Aby skompilować ten kod, można użyć instrukcji: nasm -f bin plik.asm -o os.bin, gdzie plik.asm to jest wasz plik który stworzyliście i gdzie macie kod. Po tym wystarczy uruchomić: qemu-system-i386 -drive format=raw,file=os.bin, i gotowe! Jeżeli qemu się zamknie od razu, to prawdopodobnie zapomnieliście sygnatury bootloadera.
I to jest koniec na ten poradnik. W następnym poradniku pokażę jak zainstalować cross compiler, i pokazać coś więcej na ekranie.