2

Been trying to test out a simple ISA Option Rom program compiled with either FASM or NASM that just prints out a simple 'Hello World' message.

Problem is instead of the string being printed I get a couple unexpected characters when testing it out in QEMU. The attributes work however and change the text color but can't figure this out after hours of Google searching.

Best guess is that ORG command needs to be set because the wrong memory address is being copied into AL from SI using LODSB. Any ideas??

use16        ; ISA module operates in the 16-bit segment.

DB      55h, 0AAh          ; Boot signature
DB      01h               ; Block size in sectors (200h each)



xor ax, ax ; make it zero
mov ds, ax

start:

mov si, text_string     ; Put string position into SI
call print_string       ; Call our string-printing routine

.bounce:
jmp .bounce                   ; Jump here - infinite loop!

print_string:                   ; Routine: output string in SI to screen

.repeat:
   ;mov ah, 09h             ; int 10h 'print char' function
   ; mov bh, 0x00
   ; mov bl, 0x03
   ; mov cx, 1h

lodsb                   ; Get character from string

or al,al
jz .done

mov ah, 0x0E
int 0x10

; If char is zero, end of string
; int 10h                 ; Otherwise, print it

mov bh, 00h
mov ah, 03h
int 10h
mov ah, 02h
mov bh, 00h
inc dl
int 10h
jmp .repeat

.done:
mov al, 'D'
mov bh,0x00
mov cx,1
mov ah,0ah
int 10h

ret

text_string db 'Hello World!',,0
times 512-($-$$) db 0   
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • Did you try using a debugger? QEMU can act as a gdb remote, or use BOCHS which has a built-in debugger. Check what the starting values of DS and CS are; setting DS=0 might not be the right thing depending on how you're running this. – Peter Cordes Feb 27 '18 at 04:53
  • No not using a debugger. I'm new to assembly. Did some research and for those designing a boot loader its setting the ORG address correctly for QEMU to work. Since I'm using Qemu with the '-option-rom' directive my guess is its pulling garbage from the wrong address space. I have tried following the example here: https://stackoverflow.com/questions/33974115/bios-int-10h-printing-garbage-on-qemu but can't get it to work. I mean I can create a bootloader, convert it into an IMG file w/ DD and load it with Qemu just fine and see my string printed but not for an option rom! – david moheban Feb 27 '18 at 15:44
  • Writing asm without using a debugger is like trying to build a robot while blindfolded. The time it takes you set things up so you can debug will be paid back many times over. Being able to examine registers and single step is *gold*. – Peter Cordes Feb 27 '18 at 17:42
  • Well been playing around with BOCHS and trying to get it going. Not as easy as Qemu to get up and going. Requires configuration. Not only that it crashes when I put in my homemade option rom in there. Bit tricky. Hope your right. – david moheban Feb 28 '18 at 02:36
  • Ok Finally got Bochs going in debug mode. Turned out you have to run in with admin rights or it won't load the VM. In anycase I'm having another problem now in that when I try to load my ISA ROM, by adding it in the 'Memory submenu', Bochs will complain 'Out of expansion rom space'. Maybe I need to give it more memory? – david moheban Feb 28 '18 at 19:56
  • I don't do 16-bit development, so IDK. Maybe ask a new question (or google more) about using BOCHS to run ISA option ROMs. (I'm surprised BOCHS would need root privileges for anything unless it defaults to trying to pass through your real serial ports or something.) – Peter Cordes Feb 28 '18 at 20:08
  • Ok I solved my garbage string display problem. I had to enter this at the start of my program: pusha cli mov ax,cs mov ds,ax mov es,ax – david moheban Mar 02 '18 at 00:39
  • So you should accept MichaelPetch's answer, it explains the segment setup. Are you sure you needed `cli`, not `cld`, though? Why does disabling interrupts help? – Peter Cordes Mar 02 '18 at 00:41

2 Answers2

4

The primary problem is that you initialize DS to 0. When the BIOS passes control to your init routine it will do a FAR CALL to the option ROM location +3. The FAR Call will set CS to the segment the option ROM was loaded at and set IP (instruction pointer) to 3. 3 is the offset just past the signature and size bytes.

By setting DS to zero you'll be accessing your string relative to the 0x0000 segment. You want to use the segment at which the option ROM is loaded. To do that you initialize DS to the value of the CS register. Instead of:

xor ax, ax ; make it zero
mov ds, ax

You do this:

mov ax, cs              ; CS contains segment we are running in
mov ds, ax              ;    so copy it to DS

You should also set the direction flag for string instructions to forward using CLD. You can't guarantee the BIOS set it that way before calling our option ROM code.

As I've never written an option ROM, and I couldn't find any specific documentation on calling convention I was uncertain if you needed to preserve all the registers you change. I looked at the option ROMs in my own PC using the ree program under Linux. What I noticed is that they use pusha and popa to save and restore all the general purpose registers and they push/pop to save/restore individual segment registers. It is probably good practice to do this in your own option ROM. One requirement that dates back to the old Phoenix BIOS is that after the size byte there needs to be a NEAR JMP to the entry point of the initialization code.

When finished an option ROM initialization routine you return back to the BIOS using retf (FAR return) so that the BIOS can continue scanning for other option ROMS and complete the bootup sequence.

I fixed up your code a bit since there were some glitches in the print routine. This code should work:

use16                       ; ISA module operates in the 16-bit segment.

DB      55h, 0AAh           ; Boot signature
DB      01h                 ; Block size in sectors (200h each)
jmp start                   ; NearJMP part of Phoenix BIOS specification

start:
    pushf                   ; Save the flags as we modify direction bit
    pusha                   ; Save all general purpose registers
    push ds                 ; we modify DS so save it

    cld                     ; Ensure forward string direction
    mov ax, cs              ; CS contains segment we are running in
    mov ds, ax              ;    so copy it to DS

    mov si, text_string     ; Put string position into SI
    call print_string       ; Call our string-printing routine
    pop ds                  ; Restore all registers and flags we saved
    popa
    popf

    retf                    ; Far return to exit option init routine

print_string:               ; Routine: output string in SI to screen
    mov ah, 0eh             ; BIOS tty Print
    xor bx, bx              ; Set display page to 0 (BL)
    jmp .getch
.repeat:
    int 10h                 ; print character
.getch:
    lodsb                   ; Get character from string
    test al,al              ; Have we reached end of string?
    jnz .repeat             ;     if not process next character
.end:
    ret

; String ends with 0dh (Carriage return) and 0ah (linefeed) to
; advance cursor to the beginning of next line
text_string db 'Hello World!', 0dh, 0ah, 0
times 512-($-$$) db 0

Note: The code uses pusha/popa instructions that are only available on 80186+ processors. If you are targeting 8086/8088 then you'll need to individually push and pop each register you modify.


It is also possible to not use DS register segment and override LODSB with a CS override. It could be modified to be cs lodsb. By doing that you don't need to save and restore DS because DS would remain unmodified. You also wouldn't have the need to copy CS to DS. You can similarly drop the need to save and restore the flags and set the direction bit with CLD if you replace cs lodsb with:

    mov al, [cs:si]
    inc si 

The simplified code could look like:

use16                       ; ISA module operates in the 16-bit segment.

DB      55h, 0AAh           ; Boot signature
DB      01h                 ; Block size in sectors (200h each)
jmp start                   ; NearJMP part of Phoenix BIOS specification

start:
    pusha                   ; Save all generel purpose registers
    mov si, text_string     ; Put string position into SI
    call print_string       ; Call our string-printing routine
    popa                    ; Restore all general purpose registers

    retf                    ; Far return to exit option init routine

print_string:               ; Routine: output string in SI to screen
    mov ah, 0eh             ; BIOS tty Print
    xor bx, bx              ; Set display page to 0 (BL)
    jmp .getch
.repeat:
    int 10h                 ; print character
.getch:
    mov al, [cs:si]
    inc si
    test al,al              ; Have we reached end of string?
    jnz .repeat             ;     if not process next character
.end:
    ret

; String ends with 0x0d (Carriage return) and 0x0a (linefeed) to
; advance cursor to the beginning of next line
text_string db 'Hello World!', 0dh, 0ah, 0
times 512-($-$$) db 0

My Bochs configuration file that I used to test on Debian Linux is:

# configuration file generated by Bochs
plugin_ctrl: unmapped=1, biosdev=1, speaker=1, extfpuirq=1, parallel=1, serial=1, iodebug=1
config_interface: textconfig
display_library: x
memory: host=32, guest=32
romimage: file="/usr/local/share/bochs/BIOS-bochs-latest", address=0x0, options=none
vgaromimage: file="/usr/local/share/bochs/VGABIOS-lgpl-latest"
# no floppyb
ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
ata0-master: type=none
ata0-slave: type=none
ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15
ata1-master: type=none
ata1-slave: type=none
ata2: enabled=0
ata3: enabled=0
optromimage1: file="optrom.bin", address=0xd0000
pci: enabled=1, chipset=i440fx
vga: extension=vbe, update_freq=5, realtime=1
cpu: count=1:1:1, ips=4000000, quantum=16, model=bx_generic, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0
cpuid: level=6, stepping=3, model=3, family=6, vendor_string="GenuineIntel", brand_string="              Intel(R) Pentium(R) 4 CPU        "
cpuid: mmx=1, apic=xapic, simd=sse2, sse4a=0, misaligned_sse=0, sep=1, movbe=0, adx=0
cpuid: aes=0, sha=0, xsave=0, xsaveopt=0, x86_64=1, 1g_pages=0, pcid=0, fsgsbase=0
cpuid: smep=0, smap=0, mwait=1
print_timestamps: enabled=0
debugger_log: -
magic_break: enabled=0
port_e9_hack: enabled=0
private_colormap: enabled=0
clock: sync=none, time0=local, rtc_sync=0
# no cmosimage
# no loader
log: -
logprefix: %t%e%d
debug: action=ignore
info: action=report
error: action=report
panic: action=ask
keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none
mouse: type=ps2, enabled=0, toggle=ctrl+mbutton
speaker: enabled=1, mode=system
parport1: enabled=1, file=none
parport2: enabled=0
com1: enabled=1, mode=null
com2: enabled=0
com3: enabled=0
com4: enabled=0

This configuration assumes the optional ROM is in a file optrom.bin and will be loaded at memory address 0xd0000

Option ROMs have to have a checksum computed and placed in the last byte of the image file. QEMU provides a script that can be used for that purpose. To update the checksum of an image you can do:

python signrom.py inputimagefile outputimagefile
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • If someone has a reference with legacy BIOS option ROM best practices and conventions that specifies what has to be saved and restored I'd be more than happy to add such info to the answer and adjust the code if necessary. – Michael Petch Mar 01 '18 at 19:12
  • Your answer I swear just showed up right after I placed my corrections but got to say thank you. Appreciate the insight and help. Thanks. – david moheban Mar 02 '18 at 00:48
  • A copy of a Window/Bochs configuration file that worked for me is here: http://www.capp-sysware.com/misc/stackoverflow/49001298//bochsrc.bxrc .Just replace `s:\optrom.bin` with the name and location of your file – Michael Petch Mar 03 '18 at 18:39
  • Thanks for sharing that windows config file. After correcting path issues and pointing it to a proper boot image it worked! Thanks. – david moheban Mar 04 '18 at 14:20
  • Having a new issue with Qemu in not loading Duet if anyone can help much appreciate it. Thank you.: https://stackoverflow.com/questions/49119624/qemu-unable-to-load-edk2-duet-image – david moheban Mar 07 '18 at 04:00
0

Solved the print to screen issue. Have to precede the code with this:

  pusha
  cli
  mov   ax,cs
  mov   ds,ax
  mov   es,ax

Heres the final code in total for anyone to study:

use16        ; ISA module operates in the 16-bit segment.

ROM_SIZE_IN_BLOCK = 1  ; 1 means ROM size is 1 block (512 bytes)
ROM_SIZE_IN_BYTE = ROM_SIZE_IN_BLOCK * 512

DB      55h, 0AAh          ; Boot signature
DB      01h               ; Block size in sectors (200h each)





start:


      pusha
      cli
      mov   ax,cs
      mov   ds,ax
      mov   es,ax
      call cls


    mov si, text_string     ; Put string position into SI
    call print_string       ; Call our string-printing routine
    ;retf

   .bounce:
    jmp .bounce                   ; Jump here - infinite loop!





print_string:                   ; Routine: output string in SI to screen

.repeat:
    mov ah, 09h             ; int 10h 'print char' function
    mov bh, 0x00
    mov bl, 0x03
    mov cx, 01h
    cld
    lodsb                   ; Get character from string
    cmp al, 0
    je .done


    ; If char is zero, end of string
    int 10h                 ; Otherwise, print it
    mov bh, 00h
    mov ah, 03h
    int 10h
    mov ah, 02h
    mov bh, 00h
    inc dl
    int 10h
    jmp .repeat

 .done:

ret

clrscr:
   mov dh, 0
   mov dl, 0
   call set_cursor
   mov ah, 0x0a
   mov al, ' '
   mov bh, 0
   mov cx, 2000
   int 0x10
   ret


cls:
  pusha
  mov ah, 0x00
  mov al, 0x03  ; text mode 80x25 16 colours
  int 0x10
  popa
  ret

set_cursor:
   mov ah, 0x02
   int 0x10
   ret


text_string db "Hello World!",0

 ;times 512-($-$$) db 0

times (ROM_SIZE_IN_BYTE-$) db 0 ; use 00h as the padding bytes until we reach the ROM size

 ;patch_byte is calculated and automagically inserted below
PREV_CHKSUM = 0
repeat $
   load CHKSUM byte from %-1
   CHKSUM = (PREV_CHKSUM + CHKSUM) mod 0x100
   PREV_CHKSUM = CHKSUM
end repeat
store byte (0x100 - CHKSUM) at ($-1)  ; store the patch_byte         

Still can't get it to work with BOCHS but Qemu loves it now!

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • I tried your code in Bochs and it worked when it was placed at a free memory spot 0xd0000 – Michael Petch Mar 02 '18 at 01:10
  • I wish I could get it to work in Bochs. Can you please share your config file so I can see what I'm setting incorrectly? Thanks. – david moheban Mar 02 '18 at 02:07
  • I added my Bochs config to the bottom of my answer. It is loner because I generated with the bochs config tool. – Michael Petch Mar 02 '18 at 02:15
  • Couldn't get your config file to be accepted by bochs. Perhaps its because I'm running it on Windows. Got to try running it in linux next time. – david moheban Mar 03 '18 at 02:54
  • When you add you option rom to the config (you can do it via the Bochs Windows menu configuation under `Memory`) - what address are you placing it at? Have you tried 0xd0000 as I suggested previously? – Michael Petch Mar 03 '18 at 05:47
  • Hi. Have another question about Qemu and usb if anyone wants to try to answer this one: https://stackoverflow.com/questions/49100056/how-to-passthrough-usb-device-through-qemu-in-windows – david moheban Mar 04 '18 at 20:26