1

I wrote a 16 bit print function using BIOS ints but it infinitely printed the same gibberish so i went into the Bochs debugger and went through each instruction in my function and noticed that the instruction mov dl,[bx] that i had written got replaced by mov dl,ds:[edi], machine code 67 8A 17.

I'm aware that the ds segment selector being added is normal but why did bx get replaced with di?

Here is the full code for the print_str function:

;BX = ptr to null terminaetd string
print_str:
    pusha
    next_char:
    mov dl,[bx]
    test dl,dl
    jz print_str_return
    mov ah,0x0E
    mov al,dl
    int 0x10
    inc bx
    jmp next_char
    print_str_return:
    popa
    ret

This only happens when I call the function; if I copy the function's body to where the call instruction should be, the assembler does not replace bx with edi. I'm very confused by this.

The code for boot.asm looks like this:

[extern kernel_entry]
[bits 16]

mov [BOOT_DRIVE], dl
mov ax,0
mov ds,ax
mov es,ax
mov ss,ax

;Set up a temporary stack (~65kB)
mov ax,0xFFFF
mov sp,ax
mov bp,sp

...

mov bx,msg
call print_str

...

%include "printer.asm"

msg: db "test string",0  
...

Here is what the instruction looks like according to the bochs debugger. Bogs debugger

di holds a gibberish value while bx holds the real address of the string but the assembler decided to replace bx with edi?

MottLx
  • 53
  • 1
  • 7
  • Test the code bytes against the command encoding. Is the assembler or the disassembler in the wrong? – Seva Alekseyev Jun 13 '22 at 17:07
  • These are completely different instructions (different dest reg as well -- dl vs al), so it is probably someone overwriting the code, changing the instruction (changing just the second byte from 0x07 to 0x15) – Chris Dodd Jun 13 '22 at 17:12
  • @SevaAlekseyev I did an objdump and found that `bx` does get changed to `di` by the assembler. – MottLx Jun 13 '22 at 17:15
  • 2
    Why do you think it is the assembler that is making the change? Its most likely something else you are doing after the assembler that is corrupting things. – Chris Dodd Jun 13 '22 at 17:17
  • Please make a full example, including commands you use to build and run. Could be the ROM-BIOS modified some data it thinks it should do, in your loader. – ecm Jun 13 '22 at 18:05
  • 1
    Oops you did not correctly identify the problem. It is `edi`, **not** `di`. This appears like the disassemblers you are using expect the code to have a 32-bit addressing mode but this flips what you actually encoded. `a16 mov dl, [bx]` in a 16-bit `cs` is the same encoding as `a32 mov dl, byte [edi]` in a 32-bit `cs`. – ecm Jun 13 '22 at 18:08
  • 2
    Do you have any `bits 32` directives in your program? – ecm Jun 13 '22 at 18:10
  • 1
    Your question doesn't accurately describe the problem. [EDI] is a different encoding than [DI], since 16-bit ModRM uses different encodings than 32/64-bit. [Why don't x86 16-bit addressing modes have a scale factor, while the 32-bit version has it?](https://stackoverflow.com/q/55657904) . Something that could give you `[di]` would be very different from the 16/32-bit mixup here. Bochs should be disassembling in the current mode, so as ecm says, it's likely a `bits` problem in your source. Your machine code does in fact have a `67` prefix in the machine code, which had to get there somehow. – Peter Cordes Jun 13 '22 at 18:47
  • Edited to fix that for you. Next time, copy/paste the actual disassembly from Bochs, instead of distorting it. – Peter Cordes Jun 13 '22 at 18:54
  • 1
    @PeterCordes I didn't see that the machine code dump is included in the bochs screenshot over on the very far right >>>. Indeed that includes an `asize` mode-changing prefix (here, `a32`). To expand on my prior point, a prefix-induced `a32` in a 16-bit `cs` is also the same as when `a16` is encoded with an `asize` prefix for running in a 32-bit `cs`. So it appears that the function was assembled in a part set to `bits 32` but runs in a 16-bit `cs`. – ecm Jun 13 '22 at 19:01
  • 2
    The r/m value 111 encodes [bx] in 16-bit code and [edi] in 32-bit code. Your code was assembled as 16-bit but is executing as 32-bit (or vice versa, because the 67 is an operand size prefix which means "it's backwards day!") – Raymond Chen Jun 13 '22 at 19:10
  • @ecm Yes, there is a [bits 32] directive right after the i set up the GDT and go into pm a few lines below the `call print_str` instruction. – MottLx Jun 13 '22 at 19:10
  • @RaymondChen: Bochs knows about modes, so it should be disassembling according to the current mode. Since it's showing 32-bit address-size for an instruction with a `67` prefix, in this case we can conclude the CPU is in a 16-bit mode, probably real mode. That's consistent with the OP's description of moving the function around in their source file, presumably across a `bits 32` directive. So it's a problem of assembling for the wrong mode, not running in the wrong mode. – Peter Cordes Jun 13 '22 at 19:32
  • not quite a duplicate of [Is there a way to prevent MASM changing instructions?](https://stackoverflow.com/q/63432332) - that doesn't mention a `bits` directive, but it's same problem in reverse (they wanted 32-bit machine code, but told the assembler to put 16-bit machine code in a 32-bit object file, for their instructions using 32-bit registers.) – Peter Cordes Jun 13 '22 at 19:35

1 Answers1

1

I solved the issue.

The [bits 32] directive was the culprit. The assembler was apparently encoding my print_str function as 32 bit instructions (because all my includes are at the end of the file and the directive is right above them). All i did to fix this is re-add the [bits 16] directive before my includes but after the 32 bit protected mode code. Here is the code:

cli ;Disable interrupts
lgdt [gdtr_data] ;Load the GDT

;Set first bit (protected mode enable) in control register 0
mov eax,cr0
or eax,0x1
mov cr0,eax

jmp CODE_SEGMENT:post_flush   
post_flush:     

;-----------------------------------------------32 bit Protected mode-----------------------------------------------
[bits 32]

;Set up a new kernel stack (~630 kB)
mov eax,0x9FC00
mov ebp,eax
mov esp,ebp

call kernel_entry ;Jump to kernel, 'void kernel_entry()' is defined on src/MKernel/entry.c 

jmp $

[bits 16]
%include "printer.asm"
...

From what the comments have pointed out, it seems like the way x86 encodes the 16 bit instruction mov dl,ds:[bx] is identical to the way it would encode the 32 bit instruction mov dl,ds:[edi] which was exactly my problem.

MottLx
  • 53
  • 1
  • 7
  • 2
    I encourage you to put the "bits" directive **in** the include file, so the meaning of the contents of the include file isn't altered by where it is included. (This advice applies to all your include files that contain code.) – prl Jun 13 '22 at 20:16