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
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • What assembler do you use? – fuz Nov 06 '17 at 16:51
  • Try `test al,al` instead of `cmp al,0`. It is possibly that your assembly dialect interpretes `0` as a memory operand if you don't write `offset` or something like that before. – fuz Nov 06 '17 at 16:52
  • I use NASM for an assembler and I tried what you suggested but it didn't seem to work, unless I did something wrong. – pgrafhamster Nov 06 '17 at 17:33
  • Okay; look at the documentation for interrupt 10h/0eh again. Check what the bx register is used for. I don't see you setting it up correctly. This might cause your issue. Consider moving the string to `si`. This also allows you to use `lodsb` to fetch the next byte. – fuz Nov 06 '17 at 17:40
  • On top of everything else: `mov bx, [my_string]` moves the two bytes from `my_string` to _BX_ not the address. I think you wanted `mov bx, my_string` to move the **address** of `my_string` into _BX_ – Michael Petch Nov 06 '17 at 20:05
  • 1
    As well after `call print_string` you are effectively at the end of your code. You need to prevent it from continuing and executing the data and then running whatever is in memory beyond that (which may cause). A bootloader can put the machine in an infinite loop with something like `jmp $` or preferred: `cli` followed by `hlt` . The latter turns off external interrupts and then waits for an interrupt that won't come (with the exception of hardware ones like an NMI). In essence it pretty much makes the code stop executing on the `hlt` instruction. – Michael Petch Nov 06 '17 at 20:10
  • 2
    It is also a good idea to explicitly set the segment registers (especially _DS_) since it isn't guaranteed to be zero (and using code with an origin of of 0x7c00 needs it). You should at least do an `xor ax, ax` then `mov ds, ax` at the top of your code. I have bootloader tips that discuss this in the [Stackoverflow Answer](https://stackoverflow.com/a/32705076/3857942) – Michael Petch Nov 06 '17 at 20:18

1 Answers1

2

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.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • I tried doing your solution, but it only caused the program to Spaz out. – pgrafhamster Nov 06 '17 at 19:00
  • @pgrafhamster: Then fixing this bug has exposed a new bug. Did you remember to put a `hlt` after the `call`? Also look at Fuz's comments on your question. **Use a debugger to single-step your code and see what it's doing.** It's crazy *not* to do that in this day and age, when computers are easily powerful enough to simulate another computer. – Peter Cordes Nov 06 '17 at 19:03
  • 1
    Peter, on top of the `include` you might want to take a look at this line again: `mov bx, [my_string]` – Michael Petch Nov 06 '17 at 20:06
  • 1
    Minor nit: unless you installed an interrupt handler and intend to have them serviced in the do nothing loop you can stick a `cli` before the halt. If you are looking to save bytes then `jmp $` is probably the choice. – Michael Petch Nov 06 '17 at 22:00
  • @MichaelPetch: thanks, updated. Is `cli` / `hlt` without `jmp` "safe enough" for toy boot sectors? That would be the same code-size as `jmp $`. It can wake up on NMI. – Peter Cordes Nov 06 '17 at 22:03
  • 1
    `cli` `hlt` is IMHO safe enough. If you are going to get an NMI while in the boot code I'm venturing to guess you may have other concerns. `JMP $` has some peculiar side effects in some older types of emulators - it may prevent a screen refresh.So if you updated the screen before such a `JMP $` some emulators might not even display the last virtual screen update. – Michael Petch Nov 06 '17 at 22:17