nasm bootloader, why does where i define ths string matter? - nasm

Im experimenting with writing a bootloader in nasm, at the moment it just prints a string.
[BITS 16]
[org 0x7c00]
myString:
db 'Hello World', 0x00
mov bp, 0x8000
mov sp, bp
mov bx, myString
call printString
jmp $
printString:
pusha
mov ah , 0x0e
printStringA:
mov al , [bx]
cmp al, 0x00
je printStringB
int 0x10
add bx, 0x01
jmp printStringA
printStringB:
popa
ret
times 510 -( $ - $$ ) db 0
dw 0xaa55
that works fine, but if i move the string definition to here:
[BITS 16]
[org 0x7c00]
mov bp, 0x8000
mov sp, bp
myString:
db 'Hello World', 0x00
mov bx, myString
call printString
jmp $
printString:
pusha
mov ah , 0x0e
printStringA:
mov al , [bx]
cmp al, 0x00
je printStringB
int 0x10
add bx, 0x01
jmp printStringA
printStringB:
popa
ret
times 510 -( $ - $$ ) db 0
dw 0xaa55
it prints out garbage, im running this in bochs under windows if that helps.

You are assembling to raw machine code. There are no data and text sections. Everything is interpreted as code, including what you insert using db. Hence both code snippets are wrong.
If you finish with an endless loop (as in your example) or a halt instruction, the data can safely be put after the code as it will never be reached. Otherwise you must arrange for the data to be skipped over.
You also need to set the segment registers correctly at the start.
Here is a corrected version, with early declaration of data:
[BITS 16]
[ORG 0]
;;; Set CS and DS
jmp 0x07c0:start
start:
mov ax, cs
mov ds, ax
;;; set SP
mov bp, 0x8000
mov sp, bp
;;; skip over data
jmp L1
myString:
db 'Hello World', 0x00
L1:
mov bx, myString
...
Note that in your first example, the data was interpreted as code.
db 'Hello World', 0x00
is assembled as
48 65 6c 6c 6f 20 57 6f 72 6c 64 00
and corresponds to:
dec ax
gs insb
insb
outsw
and [bx+0x6f],dl
jc short 0x76
fs
db 0x00
In effect this gets executed before your code. It is pure luck that this fragment doesn't prevent your code from working.

Related

NASM ASSEMBLY - Print "Hello World"

I've created a string and turned it into an array. Looping through each index and moving to the al register so it can print out to the vga. The problem is, it prints the size of the string with no problem, but the characters in gibberish. Can you please help me figure out what the problem is in the code. It will be highly appreciated.
org 0
bits 16
section .text
global _start
_start:
mov si, msg
loop:
inc si
mov ah, 0x0e
mov al, [si]
or al, al
jz end
mov bh, 0x00
int 0x10
jmp loop
end:
jmp .done
.done:
jmp $
msg db 'Hello, world!',0xa
len equ $ - msg
TIMES 510 - ($ - $$) db 0
DW 0xAA55
bootloader code
ORG 0x7c00
BITS 16
boot:
mov ah, 0x02
mov al, 0x01
mov ch, 0x00
mov cl, 0x02
mov dh, 0x00
mov dl, 0x00
mov bx, 0x1000
mov es, bx
int 0x13
jmp 0x1000:0x00
times 510 - ($ - $$) db 0
dw 0xAA55
The bootloader
Before tackling the kernel code, let's look at the bootloader that brings the kernel in memory.
You have written a very minimalistic version of a bootloader, one that omits much of the usual stuff like setting up segment registers, but thanks to its reduced nature that's not really a problem.
What could be a problem is that you wrote mov dl, 0x00, hardcoding a zero to select the first floppy as your bootdisk. No problem if this is indeed the case, but it would be much better to just use whatever value the BIOS preloaded the DL register with. That's the ID for the disk that holds your bootloader and kernel.
What is a problem is that you load the kernel to the segmented address 0x1000:0x1000 and then later jump to the segmented address 0x1000:0x0000 which is 4096 bytes short of the kernel. You got lucky that the kernel code did run in the end, thanks to the memory between these two addresses most probably being filled with zero-bytes that (two by two) translate into the instruction add [bx+si], al. Because you omitted setting up the DS segment register, we don't know what unlucky byte got overwritten so many times. Let's hope it was not an important byte...
mov bx, 0x1000
mov es, bx
xor bx, bx <== You forgot to write this instruction!
int 0x13
jmp 0x1000:0x0000
What is a problem is that you ignore the possibility of encountering troubles when loading a sector from the disk. At the very least you should inspect the carry flag that the BIOS.ReadSector function 02h reports and if the flag is set you could abort cleanly. A more sophisticated approach would also retry a limited number of times, say 3 times.
ORG 0x7C00
BITS 16
; IN (dl)
mov dh, 0x00 ; DL is bootdrive
mov cx, 0x0002
mov bx, 0x1000
mov es, bx
xor bx, bx
mov ax, 0x0201 ; BIOS.ReadSector
int 0x13 ; -> AH CF
jc ERR
jmp 0x1000:0x0000
ERR:
cli
hlt
jmp ERR
times 510 - ($ - $$) db 0
dw 0xAA55
The kernel
After the jmp 0x1000:0x0000 instruction has brought you to the first instruction of your kernel, the CS code segment register holds the value 0x1000. None of the other segment registers did change, and since you did not setup any of them in the bootloader, we still don't know what any of them contain. However in order to retrieve the bytes from the message at msg with the mov al, [si] instruction, we need a correct value for the DS data segment register. In accordance with the ORG 0 directive, the correct value is the one we already have in CS. Just two 1-byte instructions are needed: push cs pop ds.
There's more to be said about the kernel code:
The printing loop uses a pre-increment on the pointer in the SI register. Because of this the first character of the string will not get displayed. You could compensate for this via mov si, msg - 1.
The printing loop processes a zero-terminating string. You don't need to prepare that len equate. What you do need is an explicit zero byte that terminates the string. You should not rely on that large number of zero bytes thattimes produced. In some future version of the code there might be no zero byte at all!
You (think you) have included a newline (0xa) in the string. For the BIOS.Teletype function 0Eh, this is merely a linefeed that moves down on the screen. To obtain a newline, you need to include both carriage return (13) and linefeed (10).
There's no reason for your kernel code to have the bootsector signature bytes at offset 510. Depending on how you get this code to the disk, it might be necessary to pad the code up to (a multiple of) 512, so keep times 512 - ($ - $$) db 0.
The kernel:
ORG 0
BITS 16
section .text
global _start
_start:
push cs
pop ds
mov si, msg
mov bx, 0x0007 ; DisplayPage=0, GraphicsColor=7 (White)
jmp BeginLoop
PrintLoop:
mov ah, 0x0E ; BIOS.Teletype
int 0x10
BeginLoop:
mov al, [si]
inc si
test al, al
jnz PrintLoop
cli
hlt
jmp $-2
msg db 'Hello, world!', 13, 10, 0
TIMES 512 - ($ - $$) db 0

Why is my floppy disk reading code failing?

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."

disassembly error when using pop after a call

In the code below I have lines 27-37 that work well and the same code called by a label (line 58) crash when executing the pop instructions.
There is another label (line 48) which produces the same result without using pop and this works.
I'm in the context of building a x86_64 operating system.
Compile command : nasm -f elf
Linking command : ld -T link.ld -melf_i386
Iso generated using Grub 2.02 : grub-mkrescue
Run with VirtualBox version 5.2.32_Ubuntur132056
Following pop Instruction not supported in 64-bit mode using NASM? I tried with pop and/or push instructions in uppercase and got same result.
loader: ; the loader label (defined as entry point in linker script)
mov esp, kernel_stack + STACK_SIZE ; point esp to the start of the stack (end of memory area)
push dword 0x71
push dword 'C'
push dword 0x50 ; first pushes to test directly the function
pop edx
pop eax
pop ebx
mov ah, bl
mov ebx, FRAMEBUFFER
add ebx, edx
mov byte [ebx], al
mov byte [ebx + 1], ah ; should print a C with a blue foreground and a light grey background on the 40'th case of the frame buffer
push dword 0x71
push dword 'C'
push dword 0x52
call fb_write_chara
.loop:
nop
jmp .loop ; loop forever
fb_write_chara: ; void fb_write_char(unsigned int offset, char character, unsigned char color)
mov ah, [esp + 12] ; color
mov al, [esp + 8] ; character
mov edx, [esp + 4] ; offset
mov ebx, FRAMEBUFFER
add ebx, edx ; ebx is now at the right position
mov byte [ebx], al ; write char
mov byte [ebx + 1], ah ; write color
ret
fb_write_char: ; void fb_write_char(unsigned int offset, char character, unsigned char color)
pop edx
pop eax
pop ebx
mov ah, bl
mov ebx, FRAMEBUFFER
add ebx, edx ; ebx is now at the right position
mov byte [ebx], al ; write char
mov byte [ebx + 1], ah ; write color
ret
Result when executing with fb_write_chara:
eax=00007143 ebx=000b8052 ecx=00000000 edx=00000052 esi=00000000 edi=00000000
eip=00100033 esp=00101ff4 ebp=00000000 iopl=0 nv up di pl nz na pe nc
cs=0010 ds=0018 es=0018 fs=0018 gs=0018 ss=0018 eflags=00200002
0010:00100033 90 nop
State of the registers when trying with fb_write_char:
eax=00004352 ebx=001b8033 ecx=00000000 edx=00100033 esi=00000000 edi=00000000
eip=00000077 esp=00101ffc ebp=00000000 iopl=0 rf nv up di pl nz na pe nc
cs=0010 ds=0018 es=0018 fs=0018 gs=0018 ss=0018 eflags=00210002
0010:00000077 f0 c7 ef 00 f0 ed 57 Illegal opcode
CALL pushes the return address (.loop in your case) before jumping to fb_write_char. So your first POP pops the return address. When RET runs, it uses the only thing left on the stack, which is 0x71 in your case, as the return address. Of course the problem will crashes.
When you use stack to pass parameters, use [ESP + 4] to access the last pushed parameter, [ESP + 8] to access the second-last pushed parameter, and so on. To clean up the stack, use RET 12 (callee cleanup) or use RET and ADD ESP, 12 after CALL (caller cleanup).
Read more about call stack before you continue to code in assembly: https://en.wikipedia.org/wiki/Call_stack

int 10h 13h bios string output not working

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

Clearing out a string variable

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.

Resources