1

I'm developing code in an x86 boot sector as part of learning OS development. I'm expecting my code to print this to the console:

Hello

I get this instead:

H

Why is it only printing one character and not the entire string? How can I fix this?

This is a snippet of my code:

mov ah, 0x0e
mov bx, string_p
add bx, 0x7c00
mov al, [bx]
int 0x10
 jmp $
string_p:
       db 'Hello',0
"then padding and magic number"
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Drake
  • 11
  • 4
  • What are you running this on? – Leandros Apr 25 '16 at 18:01
  • Bochs in my ubuntu virtual machine – Drake Apr 25 '16 at 18:02
  • 1
    Int `10h` function `0Eh` is an MS-BIOS function which writes a *single* character to the terminal. I see from the tags that you are running an emulation on Linux. I suggest you refer to an MS-BIOS and MS-DOS reference, such as Ralf Brown, which explains these functions. Warning: some string output functions require a `$` terminator not `0`. – Weather Vane Apr 25 '16 at 18:26
  • @WeatherVane : He won't have the MS-DOS `int 21h` interrupts available since he is doing this from the boot sector. So he'll be stuck with the BIOS interrupts or doing direct video memory access. – Michael Petch Apr 25 '16 at 18:28
  • 1
    @MichaelPetch thanks I just noticed "bootsector". There is the Int `10h` function `13h` which might do it, but it would probably be easier to write a loop. – Weather Vane Apr 25 '16 at 18:32
  • 1
    @Weathervane : I reworded the question a bit and added proper tags. At first I missed boot sector in the title too. `int 10h/ah=13h` is a reasonable alternative for most PC/ATs and clone based systems or later. I'd do it with a loop as you suggest so that it could be used on ancient 8086/8088 PCs. – Michael Petch Apr 25 '16 at 18:39
  • Which assembler are you developing with? (NASM?) – Michael Petch Apr 25 '16 at 18:41
  • @WeatherVane MS-DOS required a `$` for their syscalls, this is a simple interrupt in the to print something. – Leandros Apr 25 '16 at 19:04

2 Answers2

4

The interrupt 10H, with register AH set to 0EH (INT 10h/AH=0eh), will print the current character in register AL. Ralf Brown's Interrupt List is considered the Bible of DOS and BIOS interrupts. It's a valuable source of information on what interrupts are available, how they work, and their side effects.

If you use INT 10h/AH=0eh you need to manually advance the string pointer for every character and print them out one at a time. Code like this should work:

org 0x7c00             ; starting address
bits 16                ; 16-Bit mode

main:
  cli                  ; disable interrupts
  cld                  ; clear direction flags
  xor ax, ax           ; set AX to 0
  mov ds, ax           ; set DS to 0
  mov ah, 0x0e         ; call 0EH bios call
  mov si, string       ; move starting address of `string` into SI

loop:
  lodsb                ; load byte at DS into AL, update (increment) SI
  or al, al            ; check if AL is 0 (ORing will do nothing, but set the right flags
  jz hltloop           ; if zero jump to end
  int 0x10             ; do the print call
  jmp loop             ; jump back to loop start

hltloop:
  hlt                  ; halt and catch fire
  jmp hltloop          ; jump back to halt, if an interrupt occurred anyway

string:
       db 'Hello',0

times 510-($-$$) db 0
dw 0xAA55

This example uses the LODSB instruction to read each character of the string. The LODS instructions are documented as:

Loads a byte, word, or doubleword from the source operand into the AL, AX, or EAX register, respectively. The source operand is a memory location, the address of which is read from the DS:ESI or the DS:SI registers (depending on the address-size attribute of the instruction, 32 or 16, respectively). The DS segment may be over-ridden with a segment override prefix.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Leandros
  • 16,805
  • 9
  • 69
  • 108
  • Can you explain it further – Drake Apr 25 '16 at 18:06
  • @Drake What's their to explain? You have to increment the address you currently have in register `BX` by one byte, after calling `int 0x10` until you reach a `0`. – Leandros Apr 25 '16 at 18:07
  • And how should i do that – Drake Apr 25 '16 at 18:08
  • @Drake Something along the lines of what I just edited in, no guarantee it'll work. – Leandros Apr 25 '16 at 18:14
  • 1
    If you are going to use `hlt` instead of `jmp $` I'd recommend at least preceding it by `cli` to turn off interrupts. `hlt` would only go into a halt state until the next interrupt occurs and then proceed executing whateve is in memory after. To also handle the possibility of NMIs I usually do it this way `cli` `hltloop: hlt` `jmp hltloop` – Michael Petch Apr 25 '16 at 18:44
  • 1
    You cannot guarantee that the [Direction Flag](https://en.wikipedia.org/wiki/Direction_flag) will be already cleared for you, which is used by `LODSB` (and similar) to either increment or decrement `SI` after the instruction. Best to save the flags register, then `CLD`, restoring the flags afterwards. Or just leave it as cleared, the default reset state. – Weather Vane Apr 25 '16 at 18:50
  • 1
    your code also assumes that the _DS_ register was also set appropriately. Not sure the OP left any code out of his sample beyond the padding and 0xaa55 (signature) mentioned in the snippet. – Michael Petch Apr 25 '16 at 18:55
  • As I said, it's just a general direction. I haven't written any 16-Bit assembly in ages. Will adjust the example anyway. – Leandros Apr 25 '16 at 18:57
  • Now a complete example, thank's to WeatherVane and @MichaelPetch. – Leandros Apr 25 '16 at 19:08
  • 1
    I am an upvoter. Outside the scope of the question, i'd probably explicitly set up the stack to a known location. For the trivial code here, one can't be bothered (but it is a good idea to do it anyway). I previously shared some [General Bootloader Tips](http://stackoverflow.com/a/32705076/3857942) if one needed more information – Michael Petch Apr 25 '16 at 19:12
  • @MichaelPetch Looks, great, appreciated. – Leandros Apr 25 '16 at 19:14
  • 1
    Well, yes but, no but, you do the questionable `ds` stuff *after* you loaded `ah` to the BIOS function number. I would load `ah` immediately before the `int 10h`, regardless of the documentation. And I suppose @MichaelPetch meant the `cli` to immediately precede `hlt`. But +1. – Weather Vane Apr 25 '16 at 19:15
  • @WeatherVane Good catch, I should reset DS before. That's my mistake. – Leandros Apr 25 '16 at 19:17
  • @WeatherVane Yes, I assumed that. I just put it on top, since it doesn't hurt and we're not making use of hardware generated interrupts anyway. – Leandros Apr 25 '16 at 19:23
  • 1
    I had a chance to verify your code worked (I had to change `jz end` to `jz hltloop`) and it compiled and runs as expected under Bochs, QEMU, VMWare, and one of my Lenovo laptops. I cleaned up the answer a bit. You can reverse any changes you don't like. – Michael Petch Apr 25 '16 at 21:06
0

This is late, but may help someone. I was having the same issue playing around developing an os on ubuntu. Here is what worked for me. I created a print function and called it after moving the address of my string into bx:

print_function:
pusha
mov ah, 0x0e
mov al, [bx]
int 0x10
cmp al, 0
jne increment
jmp the_end

increment:  
add bx , 1
mov al, [bx]    
int 0x10
cmp al, 0
jne increment
jmp the_end 

the_end:    
popa    
ret
  • Two issues with this code: The subsequent int 10h call could be folded into the first one by correctly jumping around. And this displays the terminating NUL character, which is probably not wanted. (It might appear invisible or like a blank though.) – ecm Jul 27 '19 at 18:08