I am writing a bootloader in x86-16 assembly on a floppy disk, meaning that I will have to read from the disk in order to load more of my code, however, every time I attempt to read the disk and check the disk status code, I always get 0x04 - sector not found. I have attempted to read with CX set to 0x0000, 0x0001, and 0x0101, but I don't really know what to do, or what I am doing wrong.
INT 13,1 - Disk Status Codes
INT 13,2 - Read Disk Sectors
BITS 16
%include "C:\x86asm\nasm\nasm.asm"
;%macro pad 1-2 0
; times %1 - ($ - $$) db %2
;%endmacro
;%idefine PUSHW PUSH STRICT WORD
;%idefine PUSHD PUSH STRICT DWORD
[org 0x7C00]
; https://stanislavs.org/helppc/ ;; BIOS
; https://c9x.me/x86/ ;; Instruction Set
; https://wiki.osdev.org/ ;; OS Development
; absolute address = (segment << 4) + address
; to simulate NES/SNES style memory mapping (banks), only use bits[9..15] ($x000)
JMP 0x0000:_start ; ensure CS = $0000
_start:
CLI ; clear interrupt flag (disable
; interrupts, opposite of 6502)
CLD ; clear direction flag
PUSH CS
POP DS ; prepare for puts / puthexbyte
PUSH CS
POP SS ; set up stack segment
MOV SP, 0x7C00
;; set graphics ;;
MOV AX, 0x0012 ; set screen mode
INT 0x10
MOV AH, 0x0B
MOV BX, 0x0201
INT 0x10
MOV DX, 0x0D22 ; display "LOADING..."
MOV BX, 0x0007
MOV SI, loadstr
CALL putsl
MOV AH, 0x86 ; wait a moment...
MOV CX, 0x000F
INT 0x15
;; load floppy disk ;;
MOV BP, 0x0800 ; if fail, read x times
.loadfailure:
SUB BP, 0x0100 ; decrement # of tries left
MOV AH, 0x02 ; print # of tries left
XOR DX, DX
INT 0x10
MOV DX, BP
CALL puthexbyte
MOV AH, 0x00 ; reset disk system
MOV DL, 0x00
INT 0x13
MOV AX, 0x0201 ; read
MOV CX, 0x0101
XOR DX, DX
PUSHW 0x1800 ; write to $18000 ~ $1FFFF
POP ES
XOR BX, BX
INT 0x13
PUSHF
MOV DH, AH ; show error code
CALL puthexbyte
POPF
JNC .loadsuccess
TEST BP, BP
JNE .loadfailure
MOV DX, 0x0D0F ; read fail;
MOV SI, badload ; print msg
CALL putsl
MOV AH, 0x86 ; wait
MOV CX, 0x001E
INT 0x15
INT 0x18 ; boot windows
.loadsuccess:
MOV DX, 0x0D0F ; read success;
MOV SI, nosystem ; DOS not finished,
CALL putsl ; tell user
MOV AH, 0x86 ; wait
MOV CX, 0x001E
INT 0x15
JMP 0x1000:0x8000 ; boot test
putsl:
; [DX] : (Y,X)
; (AH)
MOV AH, 0x02
INT 0x10 ; set cursor position
puts:
; [DS:SI] : String
; (AX, BX, SI)
MOV AH, 0x0E ; teletype mode
MOV BX, 0x000F ; set white text attribute
.putsloop:
LODSB ; load character from [DS:SI++]
TEST AL, AL ; check if NULL (x86 MOV does not
JE .endputs ; change zero-flag unlike 6502 LDA/LDX/LDY)
INT 0x10 ; print character
JMP .putsloop ; loop until NULL
.endputs:
RET
puthexbyte:
; [DH] : Value
; (AX, DH, BX)
MOV AH, 0x0E
MOV BX, asciihex
MOV AL, DH
SHR AL, 4
XLAT
MOV BX, 0x000F
INT 0x10 ; print character
MOV BX, asciihex
MOV AL, DH
AND AL, 0x0F
XLAT
MOV BX, 0x000F
INT 0x10
RET
asciihex:
db "0123456789ABCDEF", 0
loadstr:
db "LOADING...", 0
badload:
db "Unable to load disk. Attempting to load Windows...", 0
nosystem:
db "Operating System is incomplete. Loading Windows...", 0
tststr:
db "Disk read success. INT 0x20 set success. Jump success.", 0
pad 0x01FE ; pad to end of boot sector
dw 0xAA55 ; floppy disk boot sector signature
; code
PUSHW 0x0000 ; test setting INT
POP DS ; (tested; works when in
MOV [4 * 0x20], DWORD putsl ; boot sector)
MOV DX, 0x0D0D ; test INT by
MOV SI, tststr ; printing
INT 0x20 ; string
MOV AH, 0x86 ; wait
MOV CX, 0x001E
INT 0x15
INT 0x18 ; boot windows
pad 0x0400 ; pad to end of sector 1
;incbin "os.bin"
pad 0x00167FFF ; pad to disk end
db 0x00
Edit:
An explanation on how to convert disk sectors and tracks (ie. CX in INT 13,2) into a "linear address" would be greatly appreciated, as the method I am using to get my code onto a floppy disk has me opening the program HxD and manually copying and pasting my binary onto the the disk.
Also, the disk I am using is 'unformatted' (as far as Windows is concerned).
Also, also, if this changes anything, my PC's BIOS is (msinfo32)"American Megatrends Inc. 5.35, 2008-12-16."
Related
When I start the OS with "qemu-system-x86_64 boot.bin" it is working perfectly fine but when I try to start it with "qemu-system-x86_64 -cdrom OS.iso" it say "No bootable device". The same when I am trying to start it with my computer from a flash drive.
boot.asm:
bits 16
org 0x7c00
start:
call x16_clearscreen
mov si, msg
call x16_printstring
call x16_getkey
call x16_reboot
hlt
jmp $
msg db "Press any key to restart...", 0
%include"video.asm"
%include"keyboard.asm"
%include"power.asm"
times 510-($-$$) db 0
dw 0xaa55
video.asm:
bits 16
x16_setvideomode:
int 0x10
ret
x16_setbackcolor:
mov ah, 0x0b
mov bh, 0x00
int 0x10
ret
x16_printchar:
mov ah, 0x0e
int 0x10
ret
x16_printstring:
.loop:
lodsb
cmp al, 0
je .end
call x16_printchar
jmp .loop
.end:
ret
x16_clearscreen:
mov ah, 0x00
mov al, 0x02
int 0x10
ret
keyboard.asm:
bits 16
x16_getkey:
mov ah, 0x00
int 0x16
ret
power.asm:
bits 16
x16_reboot:
int 0x19
i am using nasm and this is my code :
org 0x7c00
bits 16
section .data
zeichen dw 'hello2'
section .text
mov ax,0x7c00
mov es,ax
mov bh,0
mov bp,zeichen
mov ah,13h
mov bl,00h
mov al,1
mov cx,6
mov dh,010h
mov dl,01h
int 10h
jmp $
times 510 - ($-$$) hlt
dw 0xaa55
it does put the cursor on the right position but it prints nothing.
i load this file with qemu-system-i386.
The int10 ah=13h is a string output and in register es:bp has to be the address of the string
For future reference, since i have been trying to get this working for a long time now, here is a working version!
org 0x7c00
bits 16
xor ax, ax
mov es, ax
xor bh, bh
mov bp, msg
mov ah, 0x13
mov bl, [foreground]
mov al, 1
mov cx, [msg_length]
mov dh, [msg_y]
mov dl, [msg_x]
int 0x10
hlt
foreground dw 0xa
msg db 'Beep Boop Meow'
msg_length dw $-msg
msg_x dw 5
msg_y dw 2
times 510 - ($-$$) db 0
dw 0xaa55
here is a version closest to original.
org 0x7c00
bits 16
; video page number.
mov bh, 0
; ES:BP is the pointer to string.
mov ax, 0x0
mov es, ax
mov bp, msg
; attribute(7 is light gray).
mov bl, 0x7
; write mode: character only, cursor moved.
mov al, 1
; string length, hardcoded.
mov cx, 6
; y coordinate
mov dh, 16
; x coordinate
mov dl, 1
; int 10, 13
mov ah, 0x13
int 0x10
; keep jumping until shutdown.
jmp $
msg dw 'hello2'
times 510 - ($-$$) db 0
dw 0xaa55
I am trying to write a simply bootloader in assembler.
The bootloader copies sector 2 from a floppy to address 0x5000 (segment 0x500, offset 0x0), jumps to the segment and prints a message.
However, when I change the segment address to 0x1000, the message does not get printed anymore. I suspect the org 0x10000 instruction has a problem, which might be related to segmentation. I tried org 0x1000:0 too, but the message won't be printed.
Here is my bootloader code, which gets written to the first sector of the floppy:
[BITS 16]
org 0x7C00
start:
mov ah, 0x02 ; Read sectors from drive
mov al, 1 ; Read 1 sector
mov ch, 0 ; Cylinder 0
mov cl, 2 ; Sector 2
mov dh, 0 ; Head 0
mov bx, sect2dest;
mov es, bx
mov bx, 0x0
int 0x13
jmp sect2dest:0;
data:
sect2dest equ 0x500
The magic identifier in the end is written by a custom linking script, so don't worry about that.
Here is my sector two, which should print a message:
[BITS 16]
org 0x5000
sect2:
mov ah, 0x13
mov al, 1
mov bl, 0x17
mov cx, msg_len
mov dh, 0
mov dl, 0
mov bh, 0
mov bp, 0
mov es, bp
mov bp, msg
int 0x10
jmp $
msg db 13,10,"Hello, World!"
msg_len equ $ - msg
As mentioned above, when I try writing sector 2 to any address larger than 0xFFFF, the message doesn't get printed.
Consider that bp is 16 bit, so if you use an ORG of 10000h any offset won't fit in it.
I was expecting the assembler to raise a warning but a quick test shown otherwise.
Remember also that generally is best to avoid challenging the BIOS, thought I don't know how it is actually handled, I would avoid to print a string that strides two segments.
Since you are putting zero in es, make sure that the ORG is at most 10000h-[msg_len], so that the whole string is reachable within es.
I actually want to print the content of the dx register with nasm. Thereby the content is a 16 bit hex digit such as 0x12AB.
Therefore I've first implemented a function which is able to print a string:
print_string:
pusha
mov ah, 0xe
print_character:
mov al, [bx]
inc bx
or al, al
jz print_done
int 0x10
jmp print_character
print_done:
popa
ret
You can use this function in this way:
mov bx, MSG
call print_string
MSG:
db 'Test',0
Now i want to have a function, which converts the hex to a string, so that print_string is capable to print it. I was thinking about something like that:
print_hex:
pusha
mov bx, HEX_OUT
; HEX_OUT is a kind of template string
; now i want to get the hex of dx into this template in order to be able to print it
; However I'm not sure how to manage this
call print_string
popa
ret
HEX_OUT:
db '0x0000', 0
Unfortunately I'm not sure how I get the hex from dx into bx, respectively the HEX_OUT. Can someone help me or does someone have an idea?
I want to use it at the end like this:
mov dx, 0x12AA
call print_hex
Thanks you already in advance!
UPDATE:
As mentioned I could achieve the separating and printing like this:
print_hex:
pusha
mov bx, PREFIX
call print_string
next_character:
mov bx, dx
and bx, 0xf000
shr bx, 4
add bh, 0x30
cmp bh, 0x39
jg add_7
print_character_hex:
mov al, bh
mov ah, 0x0e
int 0x10
shl dx, 4
or dx, dx
jnz next_character
popa
ret
add_7
add bh, 0x7
jmp print_character_hex
PREFIX:
db '0x', 0
I tried something like this to print it with my function and the buffer:
print_hex:
;Added this here
mov cx, HEX_OUT + 2
print_character_hex:
mov [cx], bh
Though I can't assemble this due to "invalid effective address". What do I need to do in order to accomplish this?
This adapts the accepted answer to include Kyle G.'s fix for the 0 issue.
; vim: :set ft=nasm:
[bits 16]
; subroutine to print a hex number.
;
; ```
; dx = the hexadecimal value to print
; ```
;
; Usage
;
; ```
; mov dx, 0x1fb6
; call print_hex
; ```
;
; used as an answer here: https://stackoverflow.com/a/27686875/7132678
;
print_hex:
; push all registers onto the stack
pusha
; use si to keep track of the current char in our template string
mov si, HEX_OUT + 2
; start a counter of how many nibbles we've processed, stop at 4
mov cx, 0
next_character:
; increment the counter for each nibble
inc cx
; isolate this nibble
mov bx, dx
and bx, 0xf000
shr bx, 4
; add 0x30 to get the ASCII digit value
add bh, 0x30
; If our hex digit was > 9, it'll be > 0x39, so add 7 to get
; ASCII letters
cmp bh, 0x39
jg add_7
add_character_hex:
; put the current nibble into our string template
mov [si], bh
; increment our template string's char position
inc si
; shift dx by 4 to start on the next nibble (to the right)
shl dx, 4
; exit if we've processed all 4 nibbles, else process the next
; nibble
cmp cx, 4
jnz next_character
jmp _done
_done:
; copy the current nibble's ASCII value to a char in our template
; string
mov bx, HEX_OUT
; print our template string
call print_string
; pop all arguments
popa
; return from subroutine
ret
add_7:
; add 7 to our current nibble's ASCII value, in order to get letters
add bh, 0x7
; add the current nibble's ASCII
jmp add_character_hex
; our global template string. We'll replace the zero digits here with the
; actual nibble values from the hex input.
HEX_OUT:
db '0x0000', 0
Also, worth noting that this is an exercise from https://www.cs.bham.ac.uk/~exr/lectures/opsys/10_11/lectures/os-dev.pdf, section 3.5.1, question 5, page 23.
All right, I was able to manage it. Thank you for your help! Here is the working code:
print_hex:
pusha
mov si, HEX_OUT + 2
next_character:
mov bx, dx
and bx, 0xf000
shr bx, 4
add bh, 0x30
cmp bh, 0x39
jg add_7
add_character_hex:
mov al, bh
mov [si], bh
inc si
shl dx, 4
or dx, dx
jnz next_character
mov bx, HEX_OUT
call print_string
popa
ret
add_7:
add bh, 0x7
jmp add_character_hex
HEX_OUT:
db '0x0000', 0
You can use this code to print a hex number
mov cx,12
mov dx,1f34h
st:
push dx
shr dx,cl
and dl,15
print:
add dl, 0x30
cmp dl, 0x39
jle go
add dl, 0x7
go:
mov ah, 0x2
int 0x21
sub cl,4
pop dx
cmp cl,0
jl end_code
jmp st
end_code:
I have writen this little experiement bootstrap that has a getline and print_string "functions". The boot stuff is taken from MikeOS tutorial but the rest I have writen myself. I compile this with NASM and run it in QEMU.
So the actual question: I've declared this variable curInpLn on line 6. What ever the user types is saved on that variable and then after enter is hit it is displayed to the user with some additional messages. What I'd like to do is to clear the contents of curInpLn each time the getline function is called but for some reason I can't manage to do that. I'm quite the beginner with Assmebly at the moment.
You can compile the code to bin format and then create a floppy image of it with: "dd status=noxfer conv=notrunc if=FILENAME.bin of=FILENAME.flp" and run it in qemu with: "qemu -fda FILENAME.flp"
BITS 16
jmp start
welcomeSTR: db 'Welcome!',0
promptSTR: db 'Please prompt something: ',0
responseSTR: db 'You prompted: ',0
curInpLn: times 80 db 0 ;this is a variable to hold the input 'command'
curCharCnt: dw 0
curLnNum: dw 1
start:
mov ax, 07C0h ; Set up 4K stack space after this bootloader
add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
mov ss, ax
mov sp, 4096
mov ax, 07C0h ; Set data segment to where we're loaded
mov ds, ax
call clear_screen
lea bx, [welcomeSTR] ; Put string position into SI
call print_string
call new_line
.waitCMD:
lea bx, [promptSTR]
call print_string
call getLine ; Call our string-printing routine
jmp .waitCMD
getLine:
cld
mov cx, 80 ;number of loops for loopne
mov di, 0 ;offset to bx
lea bx, [curInpLn] ;the address of our string
.gtlLoop:
mov ah, 00h ;This is an bios interrupt to
int 16h ;wait for a keypress and save it to al
cmp al, 08h ;see if backspace was pressed
je .gtlRemChar ;if so, jump
mov [bx+di], al ;effective address of our curInpLn string
inc di ;is saved in bx, di is an offset where we will
;insert our char in al
cmp al, 0Dh ;see if character typed is car-return (enter)
je .gtlDone ;if so, jump
mov ah, 0Eh ;bios interrupt to show the char in al
int 10h
.gtlCont:
loopne .gtlLoop ;loopne loops until cx is zero
jmp .gtlDone
.gtlRemChar:
;mov [bx][di-1], 0 ;this needs to be solved. NASM gives error on this.
dec di
jmp .gtlCont
.gtlDone:
call new_line
lea bx, [responseSTR]
call print_string
mov [curCharCnt], di ;save the amount of chars entered to a var
lea bx, [curInpLn]
call print_string
call new_line
ret
print_string: ; Routine: output string in SI to screen
mov si, bx
mov ah, 0Eh ; int 10h 'print char' function
.repeat:
lodsb ; Get character from string
cmp al, 0
je .done ; If char is zero, end of string
int 10h ; Otherwise, print it
jmp .repeat
.done:
ret
new_line:
mov ax, [curLnNum]
inc ax
mov [curLnNum], ax
mov ah, 02h
mov dl, 0
mov dh, [curLnNum]
int 10h
ret
clear_screen:
push ax
mov ax, 3
int 10h
pop ax
ret
times 510-($-$$) db 0 ; Pad remainder of boot sector with 0s
dw 0xAA55 ; The standard PC boot signature
I haven't written code in Assembly for 20 years (!), but it looks like you need to use the 'stosw' instruction (or 'stosb'). STOSB loads the value held in AL to the byte pointed to by ES:DI, whereas STOSSW loads the value held in AX to the word pointed to by ES:DI. The instruction automatically advances the pointer. As your variable curInpLn is 80 bytes long, you can clear it with 40 iterations of STOSW. Something like
xor ax, ax ; ax = 0
mov es, ds ; point es to our data segment
mov di, offset curInpLn ; point di at the variable
mov cx, 40 ; how many repetitions
rep stosw ; zap the variable
This method is probably the quickest method of clearing the variable as it doesn't require the CPU to retrieve any instructions from the pre-fetch queue. In fact, it allows the pre-fetch queue to fill up, thus allowing any following instructions to execute as quickly as possible.