I am following a tutorial to write a hello world bootloader in assembly and I am using the NASM assembler for an x-86 machine. This is the code I am using :
[BITS 16] ;Tells the assembler that its a 16 bit code
[ORG 0x7C00] ;Origin, tell the assembler that where the code will
;be in memory after it is been loaded
MOV SI, HelloString ;Store string pointer to SI
CALL PrintString ;Call print string procedure
JMP $ ;Infinite loop, hang it here.
PrintCharacter: ;Procedure to print character on screen
;Assume that ASCII value is in register AL
MOV AH, 0x0E ;Tell BIOS that we need to print one charater on screen.
MOV BH, 0x00 ;Page no.
MOV BL, 0x07 ;Text attribute 0x07 is lightgrey font on black background
INT 0x10 ;Call video interrupt
RET ;Return to calling procedure
PrintString: ;Procedure to print string on screen
;Assume that string starting pointer is in register SI
next_character: ;Lable to fetch next character from string
MOV AL, [SI] ;Get a byte from string and store in AL register
INC SI ;Increment SI pointer
OR AL, AL ;Check if value in AL is zero (end of string)
JZ exit_function ;If end then return
CALL PrintCharacter ;Else print the character which is in AL register
JMP next_character ;Fetch next character from string
exit_function: ;End label
RET ;Return from procedure
;Data
HelloString db 'Hello World', 0 ;HelloWorld string ending with 0
TIMES 510 - ($ - $$) db 0 ;Fill the rest of sector with 0
DW 0xAA55 ;Add boot signature at the end of bootloader
I have some difficulty understanding how I can place the complete 'Hello World ' string into one byte using the db command. As I understand it , db stands for define byte and it places the said byte directly in the executable , but surely 'Hello World' is larger than a byte. What am I missing here ?
The pseudo instructions db, dw, dd and friends can define multiple items
db 34h ;Define byte 34h
db 34h, 12h ;Define bytes 34h and 12h (i.e. word 1234h)
They accept character constants too
db 'H', 'e', 'l', 'l', 'o', 0
but this syntax is awkward for strings, so the next logical step was to give explicit support
db "Hello", 0 ;Equivalent of the above
P.S. In general prefer the user-level directives, though for [BITS] and [ORG] is irrelevant.
Related
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
I am having an issue with some assembly code. I am trying to print out a string using a function from a different assembly file. But it doesn't output the string but instead an "S ". How do I fix this? I would like to add that I use the NASM assembler.
code:
string.asm
print_string:
pusha
mov ah, 0x0e
loop:
mov al, [bx]
cmp al, 0
je return
int 0x10
inc bx
jmp loop
return:
popa
ret
boot_sector.asm -
[org 0x7c00]
%include "string.asm"
mov bx, [my_string]
call print_string
my_string:
db 'hello world', 0
times 510 - ($ - $$) db 0
dw 0xaa55
Execution of a boot sector begins with the first byte. In this case, the first instruction is the top of your function, because you put it first.
The code assembles exactly the same as if you had included it manually before assembling. So your boot sector is really:
[org 0x7c00]
print_string:
pusha
...
ret
mov bx, [my_string] ; BX = load first 2 bytes of my_string.
; should have been
; mov bx, my_string ; BX = address of my_string. mov bx, imm16
call print_string
It should be pretty obvious why that doesn't work, and you would have noticed this if you single-stepped your code with the debugger built-in to BOCHS (or any other way of debugging a boot sector). Even just looking at disassembly might have clued you in.
Solution: put the %include after your other code, and avoid having execution fall into it. e.g. put this after the call:
cli ; disable interrupts
hlt ; halt until the next interrupt. (except for NMI)
(If NMI is possible, you can put the hlt inside an infinite loop with jmp.)
This is not your only bug. As #MichaelPetch points out, you were loading 2 bytes from the string instead of putting its address into BX.
I'm working on a larger project but I'm stuck with string manipulation. My assembly file includes math coprocessor operations (it starts the coprocessor with "FINIT") but I don't think it should be interfering at all.
Basically, I have some strings that are 50 bytes long each:
$s db 50 dup (?), '$'
_cte_14 db "hello world", '$', 39 dup (?)
I need to assign the value stored in variable "_cte_14" to variable "$s"
I attempted to use a register to temporarily store the value, like this:
mov cx, _cte_14
mov $s, cx
but I get the "operand types do not match" error.
Since I know the AX, BX, CX, DX registers only hold 16 bits, I thought maybe I need to work with the memory address of the first string character, so I tried:
mov bx, offset _cte_14
mov $s, bx
but the same error shows up.
I'm using TASM to compile for an x86 processor. What would be the right way to accomplish this?
Thanks a lot in advance.
Example for to copy the characters in a loop:
s db 51 dup ('$')
_cte_14 db "hello world"
len = ($ - _cte_14) ; (current location - offset _cte_14)
40 dup ('$')
mov si, offset _cte_14 ; get source offset
mov di, offset s ; get destination offset
mov cl, len ; length of the string
P1:
mov al, [si] ; get byte from DS:SI
mov [di], al ; store byte to DS:DI
dec cl ; decrease counter, set zero flag if zero
jnz P1 ; jump if zero flag is not set
-- Variation with using a string instruction together with a repeat instruction prefix:
mov si, offset _cte_14
mov di, offset s
mov cx, len ; length of the string
cld ; clear direction flag
rep movsb ; copy from DS:SI to ES:DI, increase SI+DI, decrease CX, repeat CX times
Here is my code:
http://pastebin.com/pSncVNPK
[BITS 16] ;Tells the assembler that its a 16 bit code
[ORG 0x7C00] ;Origin, tell the assembler that where the code will
;be in memory after it is been loaded
MOV SI, HelloString ;Store string pointer to SI
CALL PrintString ;Call print string procedure
JMP $ ;Infinite loop, hang it here.
PrintCharacter: ;Procedure to print character on screen
;Assume that ASCII value is in register
AL MOV AH, 0x0E ;Tell BIOS that we need to print one charater on screen.
MOV BH, 0x00 ;Page no.
MOV BL, 0x07 ;Text attribute 0x07 is lightgrey font on black background
INT 0x10 ;Call video interrupt RET ;Return to calling procedure
PrintString: ;Procedure to print string on screen
;Assume that string starting pointer is in register SI
next_character: ;Label to fetch next character from string
MOV AL, [SI] ;Get a byte from string and store in AL register
INC SI ;Increment SI pointer
OR AL, AL ;Check if value in AL is zero (end of string)
JZ exit_function ;If end then return
CALL PrintCharacter ;Else print the character which is in AL register
JMP next_character ;Fetch next character from string
exit_function: ;End label
RET ;Return from procedure
;Data
HelloString db 'Hello World', 0 ;HelloWorld string ending with 0
TIMES 510 - ($ - $$) db 0 ;Fill the rest of sector with 0
DW 0xAA55 ;Add boot signature at the end of bootloader
As you can see the syntax appears to be correct, compiled it into a .bin file, BUT I'm trying to figure out how to test it. Please treat me like I'm a bit slow because I've spent HOURS googling this topic and nothing seems to work, I've even tried using a hex editor as per some tutorial but it didn't work. This seems to be the closest I've gotten is using these instructions:
http://puu.sh/6KzUo.png
from this link: How to make an bootable iso(not cd or flash drive) for testing your own boot loader?
Except I don't quite understand step 6 because VM box won't let me select the img file as a bootable disk.
Thanks!
If you just need to add a Floppy Disk into the disk controller, this is how to do it:
Click on the Floppy Controller. An icon of a floppy with a green plus sign should come up on the left of your selection. Click on this small icon.
A dialog should now come up:
Select "Choose Disk"
The file selection box will come up---at this point, choose your .img file from the file selection box.
From this point you should be able to boot the virtual machine from the floppy disk and test your bootloader.
I have to define a function in assembly that will allow me to loop through a string of declared bytes and print them using a BIOS interrupt. I'm in 16 bit real mode. This is an exercise on writing a little bootloader from a textbook, but it seems that it was only a draft and it's missing some stuff.
I have been given the following code:
org 0x7c00
mov bx, HELLO_MSG
call print_string
mov bx, GOODBYE_MSG
call print_string
jmp $ ;hang so we can see the message
%include "print_string.asm"
HELLO_MSG:
db 'Hello, World!', 0
GOODBYE_MSG:
db 'Goodbye!', 0
times 510 - ($ - $$) db 0
dw 0xaa55
My print_string.asm looks like this:
print_string:
pusha
mov ah, 0x0e
loop:
mov al, bl
cmp al, 0
je return
int 0x10
inc bx
jmp loop
return:
popa
ret
I have some idea of what I'm doing, but the book doesn't explain how to iterate through something. I know how to do it in C but this is my first time using assembly for something other than debugging C code. What happens when I boot through the emulator is that it prints out a couple of lines of gibberish and eventually hangs there for me to see my failure in all it's glory. Hahaha.
Well, it looks like it loads the address of the string into the BX register before calling the function.
The actual function looks like it is trying to loop through the string, using BX as a pointer and incrementing it (inc bx) until it hits the ASCII NUL at the end of the string (cmp al, 0; je return)...
...but something is wrong. The "mov al, bl" instruction does not look correct, because that would move the low 8 bits of the address into al to be compared for an ASCII NUL, which does not make a lot of sense. I think it should be something more like "mov al, [bx]"; i.e. move the byte referenced by the BX address into the AL register -- although it has been a long time since I've worked with assembly so I might not have the syntax correct.
Because of that bug, the 10h interrupt would also be printing random characters based on the address of the string rather than the contents of the string. That would explain the gibberish you're seeing.
I think the issue is you cannot count on the int preserving any of your registers, so you need to protect them. Plus, what Steven pointed out regarding loading of your string address:
; Print the string whose address is in `bx`, segment `ds`
; String is zero terminated
;
print_string:
pusha
loop:
mov al, [bx] ; load what `bx` points to
cmp al, 0
je return
push bx ; save bx
mov ah, 0x0e ; load this every time through the loop
; you don't know if `int` preserves it
int 0x10
pop bx ; restore bx
inc bx
jmp loop
return:
popa
ret