MastersProgression
MastersProgression

Reputation: 37

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

Upvotes: 0

Views: 507

Answers (1)

Sep Roland
Sep Roland

Reputation: 39676

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

Upvotes: 3

Related Questions