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.
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 currently following a tutorial on OS development, which includes a discussion on bootloaders.
My bootloader is currently in 16-bit real mode, therefore, I am able to use the BIOS interrupts provided (e.g. VGA video interrupts, etc.).
The BIOS provides the video interrupt 0x10 (i.e. video teletype output). The video interrupt has the function 0x0E, which allows me to print a character to the screen.
Here is this basic bootloader:
org 0x7c00 ; Set program start (origin) address location at 0x7c00.
; This program is loaded by the BIOS at 0x7c00.
bits 16 ; We live in 16-bit Real Mode.
start:
jmp loader
bootmsg db "Welcome to my Operating System!", 0 ; My data string.
;-------------------------------------------------------
; Description: Print a null terminating string
;-------------------------------------------------------
print:
lodsb ; Load string byte at address DS:SI and place in AL.
; Then, increment/decrement SI as defined by the Direction Flag (DF) in FLAGS.
or al, al ; Set the zero flag - is AL zero?
jz printdone ; Check if this is the null byte
mov ah, 0eh
int 10h
jmp print
printdone:
ret
loader:
;|---------- Related to my question ----------|
xor ax, ax
mov ds, ax
mov es, ax
;|--------------------------------------------|
mov si, bootmsg
call print
cli ; Clears all interrupts.
hlt ; Halts the system.
times 510 - ($-$$) db 0 ; Make sure our bootloader is 512 bytes large.
dw 0xAA55 ; Boot signature - Byte 511 is 0xAA and Byte 512 is 0x55, indicated a bootable disk.1
As shown in the above code, I have highlighted the following three lines:
xor ax, ax
mov ds, ax
mov es, ax
According to the original source, it says the following:
Setup segments to insure they are 0. Remember that we have ORG 0x7c00. This means all addresses are based from 0x7c00:0. Because the data segments are within the same code segment, null em.
I am a bit confused. From my understanding, the org instruction tells the loader to load this program at address 0x7c00. Why don't we take this as our start address then? Meaning, our two overlapping Data and Code segments are not located at a base address of zero. The base address should be 0x7c0. Why does the author set the base address to 0x0?
mov ax, 07c0h
mov dx, ax
mov es, ax
I have been looking into the org instruction more and other documentation and I understand what is going on.
According to the NASM documentation on the org directive, short for origin:
The function of the ORG directive is to specify the origin address which NASM will assume the program begins at when it is loaded into memory. [...] NASM's ORG does exactly what the directive says: origin. Its sole function is to specify one offset which is added to all internal address references within the section.
Therefore, the NASM compiler assumes that the program will be loaded at the address specified with the origin instruction (i.e. org). The BIOS does exactly this. According to the following, once the BIOS finds a valid boot sector that contains a valid boot signature, the bootloader will be "loaded into memory at 0x0000:0x7c00 (segment 0, address 0x7c00)."
From the quote above, when the NASM documentation says "internal address references," it is referring to all references to concrete memory regions that are being used in the code (e.g. referencing a label, etc.). For example, the line in the bootloader code above: mov si, bootmsg will resolve bootmsg to 0x07c00 + offset, where the offset is determined by the position of the first byte of my string bootmsg (i.e. 'W').
With my code above, if I disassembly the bin file using the ndisasm utility I see the following:
00000000 EB2C jmp short 0x2e
00000002 57
00000003 656C
00000005 636F6D
00000008 6520746F
0000000C 206D79
0000000F 204F70
00000012 657261
00000015 7469
00000017 6E
00000018 67205379
0000001C 7374
0000001E 656D
00000020 2100
00000022 AC lodsb
00000023 08C0 or al,al
00000025 7406 jz 0x2d
00000027 B40E mov ah,0xe
00000029 CD10 int 0x10
0000002B EBF5 jmp short 0x22
0000002D C3 ret
0000002E 31C0 xor ax,ax
00000030 8ED8 mov ds,ax
00000032 8EC0 mov es,ax
00000034 BE027C mov si,0x7c02
00000037 E8E8FF call 0x22
0000003A FA cli
0000003B F4 hlt
00000... ... ...
(I removed the generated instructions from 0x00000002 to 0x00000020, because that is my bootmsg string and is representing data, not code).
As we can see from the output assembly, at the address 0x00000034, my bootmsg has been replaced with 0x7c02 (e.g. 0x7c00 + offset=0x02).
Michael Petch provided some very solid insight too. It is a common misconception to think the bootloader is loaded to 0x7c0:0x0000 (segment 0x07c0, offset 0). Although one could technically use this, it has been standardized to use the segment offset of zero instead (A good practice is to enforce CS:IP at the very start of your boot sector). As Michael has mentioned, if one wants more information, look at section 4 of the following guide on segment offset addressing.
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 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.
For school assignment I have to write a program described below and I would really like some help on how to approach this problem. To be clear, I don't want you to solve this, I just want some guidance on how to do it.
Problem:
Write a boot time program, which will be run in a virtual computer without an operating system. The program has to print out your name and the words "ALT key is pressed" or "ALT key is not pressed" according to status of the ALT key.
Additional hints:
- the program has to be written in 16 bit mode
compiled program including its data must be less than 510 bytes in size
directive "org 0x7c00" specifies the correct address in the memory where the program is loaded
write instructions before the data
program should execute in an endless loop
there is no printf function, you will have to use interrupt 0x10
to read the state of the alt keys you can use the interrupt 0x16
to position the output of text use interrupt 0x10
binary format of the executable should be "bin" (nasm -f bin -o boot.bin code.asm)
resize the binary file to the size of a floppy disk (truncate -s 1474560 boot.bin )
mark the binary file as bootable disk: at location 0x1FE save the value 0x55 and at
location 0x1FF save value 0xAA (use hexadecimal editor, for example: ghex2)
start the virtual machine with your binary file as a floppy disk: (nice -n 19 qemu -fda boot.bin)
I suggest you read this on assembly bootloaders. Taken from that article, here is hello world -
org 7C00h
jmp short Start ;Jump over the data (the 'short' keyword makes the jmp instruction smaller)
Msg: db "Hello World! "
EndMsg:
Start: mov bx, 000Fh ;Page 0, colour attribute 15 (white) for the int 10 calls below
mov cx, 1 ;We will want to write 1 character
xor dx, dx ;Start at top left corner
mov ds, dx ;Ensure ds = 0 (to let us load the message)
cld ;Ensure direction flag is cleared (for LODSB)
Print: mov si, Msg ;Loads the address of the first byte of the message, 7C02h in this case
;PC BIOS Interrupt 10 Subfunction 2 - Set cursor position
;AH = 2
Char: mov ah, 2 ;BH = page, DH = row, DL = column
int 10h
lodsb ;Load a byte of the message into AL.
;Remember that DS is 0 and SI holds the
;offset of one of the bytes of the message.
;PC BIOS Interrupt 10 Subfunction 9 - Write character and colour
;AH = 9
mov ah, 9 ;BH = page, AL = character, BL = attribute, CX = character count
int 10h
inc dl ;Advance cursor
cmp dl, 80 ;Wrap around edge of screen if necessary
jne Skip
xor dl, dl
inc dh
cmp dh, 25 ;Wrap around bottom of screen if necessary
jne Skip
xor dh, dh
Skip: cmp si, EndMsg ;If we're not at end of message,
jne Char ;continue loading characters
jmp Print ;otherwise restart from the beginning of the message
times 0200h - 2 - ($ - $$) db 0 ;Zerofill up to 510 bytes
dw 0AA55h ;Boot Sector signature
;OPTIONAL:
;To zerofill up to the size of a standard 1.44MB, 3.5" floppy disk
;times 1474560 - ($ - $$) db 0