Lowlevel
Lowlevel => Lowlevel-Coding => Thema gestartet von: misterunknown am 15. March 2016, 13:32
-
Moin,
um etwas Assembler zu lernen habe ich mir (mit einigen Anleitungen für ganz simple Bootloader) ein kleines bootfähiges Programm geschrieben, welches 3 Sachen macht:
- einen Text ausgeben
- in einer Schleife alle Tastatureingaben ausgeben
- wenn ESC gedrückt wird noch einen Text ausgeben und beenden
Das funktioniert auch, bis auf dass die Enter-Taste nur einen Carriage Return ausgibt und keinen Zeilenumbruch macht. Das wollte ich ändern, indem ich prüfe, ob die Enter-Taste gedrückt wurde und wenn ja, erst der Zeilenumbruch ausgegeben wird (0x0A) und dann im Anschluss der Carriage Return (0x0D). Hier ein Ausschnitt:
Linebreak:
mov ah,0x0E ; Print-Funktion nutzen (0x0e)
mov al,0x0A ; Zeilenumbruch ausgeben
int 0x10 ; Interrupt auslösen
ret
Main:
mov ah,00h ; auf Tastendruck warten
int 0x16 ; Interrupt auslösen
cmp al,0x1B ; ESC gedrückt?
je Ende ; => zum Ende springen
push ax ; ax sichern (al = 0x0D)
cmp al,0x0D ; Enter gedrückt?
je Linebreak ; => Zeilenumbruch (0x0A) ausgeben
pop ax ; ax wiederherstellen (damit sollte al wieder 0x0D sein)
mov ah,0x0E ; Print-Funktion einstellen
int 0x10 ; Interrupt auslösen
jmp Main ; Main-Schleife
Das funktioniert nur so lange, bis man Enter drückt. Ist das der Fall wird nur der Zeilenumbruch ausgegeben (0x0A), und dann friert das Programm ein. Ich habe nach einigem hin- und herprobieren keine Idee mehr, was ich falsch mache. Ich habe auch keine Idee, wie ich das debuggen könnte, und hoffe jetzt, dass ihr mir helfen könnt.
Noch einige Informationen zu meinen Tools: Ich nutze zum Kompilieren nasm und zum Testen qemu:
$ nasm -f bin boot.asm -o boot.img
$ qemu-system-x86_64 -fda boot.img
Hier ist der vollständige Code:
;****************************************
; boot.asm - Einfacher Bootloader
;
; 2016 - marco@misterunknown.de
;****************************************
BITS 16 ; 16-Bit Real-Mode
org 0x7C00 ; Wir werden vom BIOS an diese Adresse geladen.
Start: jmp Loader ; Loader
msga db "Testausgabe",10,13,0
msgb db 13,10,13,10,"Ende",13,10,0
;**************************************
; Funktionen
;**************************************
Print:
lodsb ; lädt das nächste Byte von SI nach AL
or al,al ; ist AL null?
jz PrintDone ; wenn ja, dann gehe zu PrintDone
mov ah,0x0E ; Print-Funktion nutzen (0x0e)
int 0x10 ; Interrupt auslösen
jmp Print ; Rücksprungmarke aufrufen
PrintDone:
ret
Linebreak:
mov ah,0x0E ; Print-Funktion nutzen (0x0e)
mov al,0x0A ; Zeilenumbruch ausgeben
int 0x10 ; Interrupt auslösen
ret
;**************************************
; Loader
;**************************************
Loader:
xor ax,ax ; schneller Weg um das Register zu nullen
mov al,03h ; in den Text-Modus wechseln (löscht gleichzeitig den Bildschirm)
int 0x10 ; Interrupt auslösen
mov si,msga ; Nachricht in source-Register laden
call Print ; Print-Funktion aufrufen
;**************************************
; Hauptschleife
;**************************************
Main:
mov ah,00h ; auf Tastendruck warten
int 0x16 ; Interrupt auslösen
cmp al,0x1B ; ESC gedrückt?
je Ende ; => zum Ende springen
push ax ; ax sichern (al = 0x0D)
cmp al,0x0D ; Enter gedrückt?
je Linebreak ; => Zeilenumbruch (0x0A) ausgeben
pop ax ; ax wiederherstellen (damit sollte al wieder 0x0D sein)
mov ah,0x0E ; Print-Funktion einstellen
int 0x10 ; Interrupt auslösen
jmp Main ; Main-Schleife
Ende:
mov si,msgb
call Print
cli ; Interrupts leeren
hlt ; System anhalten
times 510 - ($ - $$) db 0 ; Das Programm muss 512 Bytes groß werden. Dazu wird mit Nullen aufgefüllt.
; 510 kommt daher, weil hinten noch die 2 Byte lange Signatur folgt.
dw 0xAA55 ; Signatur des Bootloaders
; vim: syn=nasm
-
Auf den ersten Blick: Wenn du mit jmp (bzw. je) nach LineBreak springst, dann kommst du mit ret nicht zurück. Was passiert ist, dass er dein gepushtes 0xd vom Stack nimmt und als Rücksprungadresse benutzt.
-
Auf den ersten Blick: Wenn du mit jmp (bzw. je) nach LineBreak springst, dann kommst du mit ret nicht zurück. Was passiert ist, dass er dein gepushtes 0xd vom Stack nimmt und als Rücksprungadresse benutzt.
Das ergibt natürlich Sinn -.- Offensichtlich ist "call" und "jmp" in meinem Kopf das gleiche gewesen.
Ich habe mir jetzt soetwas gebaut:
Linebreak:
mov ah,0x0E ; Print-Funktion nutzen (0x0e)
mov al,0x0A ; Zeilenumbruch ausgeben
int 0x10 ; Interrupt auslösen
mov al,0x0D ; Zeilenvorschub ausgeben
int 0x10 ; Interrupt auslösen
jmp Main
Main:
mov ah,00h ; auf Tastendruck warten
int 0x16 ; Interrupt auslösen
cmp al,0x1B ; ESC gedrückt?
je Ende ; => zum Ende springen
cmp al,0x0D ; Enter gedrückt?
je Linebreak ; => Zeilenumbruch (0x0A) ausgeben
mov ah,0x0E ; Print-Funktion einstellen
int 0x10 ; Interrupt auslösen
jmp Main ; Main-Schleife
Und es funktioniert! Vielen Dank 8-)
Sollte dir oder jemand anderem beim anschauen meines Programms sonst noch auf etwas koschmisches/idiotisches aufgefallen sein bin ich für Tipps dankbar :)
-
Es ist Real-Mode-Assembler-Code. Reicht das nicht, um als komisch und idiotisch durchzugehen? :-D
-
Es ist Real-Mode-Assembler-Code. Reicht das nicht, um als komisch und idiotisch durchzugehen? :-D
Vermutlich, aber für die ersten Schritte in einer virtuellen Maschine sollte das reichen^^ Mein Ziel ist es nicht ein Betriebssystem oder einen vollwertigen Bootloader zu schreiben, sondern mein grundsätzliches Verständnis für das Zusammenspiel von Software und Hardware auszubauen.
-
Mein Ziel ist es nicht ein Betriebssystem oder einen vollwertigen Bootloader zu schreiben, sondern mein grundsätzliches Verständnis für das Zusammenspiel von Software und Hardware auszubauen.
Ich persönlich finde dafür Mikrocontroller eine wesentlich bessere Ausgangsbasis. Also zum Beispiel ein Arduino (ohne die Arduino-Tools, also klassisch mit make und gcc), ein STM32-Discovery, ein LPC-Launchpad oder sowas in der Art. Wenn du die paar Euros übrig hast, kriegst du das günstig im Internet. Da musst du dich zwar auch einarbeiten, aber du bekommst eine relativ einfache Architektur und vor allem eine Plattform, die aus einem Guss und an einer einzigen Stelle vollständig dokumentiert ist. Und du brauchst dich nicht mit beschränkten Kompatiblitätsmodi (Real Mode, A20) rumschlagen, sondern kannst den Kram ausreizen.
Im Gegensatz zum PC kann man solche Boards auch für Echtzeitanwendungen benutzen, dafür hast du weniger schicke Hardware (wenig Flash, wenig RAM, kein VGA-Display, etc). Aber für das Verständnis sind die Dinger prima.
-
Vermutlich Geschmackssache und auch eine Frage davon, ob man wissen will wie x86 funktioniert oder einfach nur irgendeine Architektur. Ich mag klassisches OS-Dev auf dem PC.
Mein Ziel ist es nicht ein Betriebssystem oder einen vollwertigen Bootloader zu schreiben, sondern mein grundsätzliches Verständnis für das Zusammenspiel von Software und Hardware auszubauen.
Du musst dir halt bewusst sein, dass du noch gar direkt auf Hardware zugegriffen hast. Du bist im Moment dabei, was über das Zusammenspiel von DOS-Programmen und dem BIOS zu lernen. Das ist sicherlich historisch interessant, aber heute nicht mehr ganz so praxisrelevant. ;)
Man kann natürlich auch im Real Mode tatsächlich die Hardware ansteuern statt BIOS-Interrupts zu benutzen, aber in dem Fall ist der RM dann eher hinderlich als nützlich, weil er so viele Einschränkungen (vor allem im Hinblick auf die Speichergröße) mit sich bringt. Deswegen empfehlen wir eigentlich immer, GRUB (oder was anderes Multiboot-fähiges) als Bootloader zu benutzen und direkt im PM zu starten.