Reputation: 23
I'm relatively new to assembly programming and was wondering why my code does not print the expected strings. This project is supposed to be a bootloader when finished. I am compiling using the command nasm -f bin boot.asm -o boot.bin
. There are no errors during compilation.
boot.asm
bits 16
org 0x7C00
%include "print.asm"
%include "text.asm"
boot:
mov si, boot_string_00
call print
mov si, boot_string_01
call print
times 510 - ($-$$) db 0
dw 0xAA55
print.asm
print:
mov ah, 0x0E
.print_loop:
lodsb
or al, al
je .print_done
int 0x10
jmp .print_loop
.print_done:
cli
ret
text.asm
boot_string_00: db "Placeholder OS Title v0.0.1", 0
boot_string_01: db "Loading Operating system", 0
Expected Output:
PlaceHolder OS Title v0.0.1Loading Operating System
Actual Output:
S
Also, I was wondering how i could implement newlines in assembly so that i could just use '\n' in my strings.
Upvotes: 2
Views: 185
Reputation: 39166
When using a directive like %include "print.asm"
, NASM will insert the contents of the file print.asm right where you have written the line. The same is true for %include "text.asm"
. The expanded source text thus becomes:
bits 16
org 0x7C00
print: \ %include "print.asm"
mov ah, 0x0E |
|
.print_loop: |
lodsb |
or al, al |
je .print_done |
int 0x10 |
jmp .print_loop |
|
.print_done: |
cli |
ret /
boot_string_00: db "Placeholder OS Title v0.0.1", 0 \ %include "text.asm"
boot_string_01: db "Loading Operating system", 0 /
boot:
mov si, boot_string_00
call print
mov si, boot_string_01
call print
times 510 - ($-$$) db 0
dw 0xAA55
Now, when BIOS has finished loading your bootsector code at the address 0x7C00, it will transfer control to that same address in memory. The first instruction that the CPU encounters will be mov ah, 0x0E
and so our printing loop starts. Problem is, we haven't yet setup the pointer to the message. The intend was for the code to start executing at the boot label, and the includes made that go wrong.
A quick solution would be to have a jmp boot
instruction as the very first instruction beneath the org 0x7C00
directive. But why waste 2 or 3 bytes when we could just as well, and better, place the includes below the rest of the code? That's going to be the preferred solution:
bits 16
org 0x7C00
boot:
mov si, boot_string_00
call print
mov si, boot_string_01
call print
%include "print.asm"
%include "text.asm"
times 510 - ($-$$) db 0
dw 0xAA55
Let's expand the includes again and verify that the problem is resolved.
bits 16
org 0x7C00
boot:
mov si, boot_string_00
call print
mov si, boot_string_01
call print
print: \ %include "print.asm"
mov ah, 0x0E |
|
.print_loop: |
lodsb |
or al, al |
je .print_done |
int 0x10 |
jmp .print_loop |
|
.print_done: |
cli |
ret /
boot_string_00: db "Placeholder OS Title v0.0.1", 0 \ %include "text.asm"
boot_string_01: db "Loading Operating system", 0 /
times 510 - ($-$$) db 0
dw 0xAA55
Both messages print fine, but, as you can see, once the second call print
returns, the code falls through in the print routine and starts printing an empty message (because the SI
register is pointing at the first zero-byte inserted by the times
directive).
A far worse problem is, that because this (third) time, the ret
instruction has no sensible return address on the stack, the computer will crash, but in a dangerous way because there's no telling about where execution will go to!
The solution is to prevent the code falling through in the print subroutine. Because our program has nothing more to do, we will insert a halting loop so the CPU doesn't waste precious cycles in a tight loop. The preferred way is cli
hlt
jmp $-2
.
bits 16
org 0x7C00
boot:
mov si, boot_string_00
call print
mov si, boot_string_01
call print
cli
hlt
jmp $-2
%include "print.asm"
%include "text.asm"
times 510 - ($-$$) db 0
dw 0xAA55
BH
register to contain the desired DisplayPage and the BL
register the desired GraphicsColor for when the screen is in a graphics mode.cli
instruction in this code.AL
register for zero, would better use test al, al
.DS
segment register. Make sure the calling code has xor ax, ax
mov ds, ax
in accordance with the org 0x7C00
directive.print:
mov bx, 0x0007 ; DisplayPage BH=0, GraphicsColor BL=7
jmp .begin ; This is what makes 'empty messages' OK
.print:
mov ah, 0x0E ; BIOS.Teletype
int 0x10
.begin:
lodsb
test al, al
jnz .print
ret
Also, I was wondering how i could implement newlines in assembly so that i could just use '\n' in my strings.
In NASM you can define string litterals in 3 different ways. Using single quotation marks ', using double quotation marks ", or using backticks `.
Only with the backticks can you include escape sequences in the text. In order to insert newlines \r\n
, your text.asm would need to become:
boot_string_00: db `Placeholder OS Title v0.0.1\r\n`, 0
boot_string_01: db `Loading Operating system\r\n`, 0
to produce
Placeholder OS Title v0.0.1 Loading Operating system
Now that the newlines are embedded in the messages, you could consider simplifying the code and output the 2-line message as a whole, for a total savings of 7 bytes:
boot_string: db `Placeholder OS Title v0.0.1\r\nLoading Operating system\r\n`, 0
\r\n
sequence, and not simply \n
?For reference a quote from the Wikipedia article about escape sequences:
\n produces one byte, despite the fact that the platform may use more than one byte to denote a newline, such as the DOS/Windows CR-LF sequence, 0x0D 0x0A. The translation from 0x0A to 0x0D 0x0A on DOS and Windows occurs when the byte is written out to a file or to the console, and the inverse translation is done when text files are read.
The \n
(newline) escape sequence only inserts the linefeed byte (10), but since this is bootloader code, and not DOS/Windows, BIOS will require both the carriage return byte (13) and the linefeed byte (10) in order to perform a move to the beginning of the next line. That's why we need to write \r\n
.
Upvotes: 2
Reputation: 195
You included stuff at the top of your bootloader, where it will executes first. Instead include extra functions where they aren't in the main path of execution and are only reached by call
.
This should work, placing the %include
directives where it's safe to put extra function or data, just like if you were writing them all in one file.
boot.asm:
[bits 16]
[org 0x7c00]
boot:
xor ax, ax
mov ds, ax ; set up DS to make sure it matches our ORG
mov si, boot_string_00
call println
mov si, boot_string_01
call println
finish: ; fall into a hlt loop to save power when we're done
hlt
jmp finish
%include "printf.asm" ; not reachable except by call to labels in this file
%include "text.S"
times 510-($-$$) db 0
dw 0xaa55
printf.asm:
print:
mov ah, 0x0E ; call number for int 0x10 screen output
print_loop:
lodsb
test al, al
je print_done
int 0x10
jmp print_loop
print_done:
ret
println:
call print
mov si, line_end
call print
ret
text.S:
boot_string_00: db "Placeholder OS Title v0.0.1", 0
boot_string_01: db "Loading Operating system", 0
line_end: db 0xD, 0xA, 0
Upvotes: 4