0

I have made a very simple x86 assembly real mode OS that uses in 0x16 for the keyboard input. I have tested the OS in both VirtualBox and Qemu, both times the keyboard input works just fine. However when I try to run the OS on a real 286 based PC-AT clone, it is able to boot, load sectors to memory (via int 0x13), then print the first string but it does not respond to input from the keyboard. I checked the x86 instructions listing and every instruction I have in my code does seem to be supported by the 186 and 286. And since it runs in real mode, the 286 being 16 bit shouldn't be a problem? Is there something I am missing when compiling that is causing this issue? Or is there some quirk in the PC-AT that is causing the issue? I am very new to programming (this is one of the first larger things I have written that wasn't just following a tutorial) so I am not too familiar with stuff so I maybe it is some error/oversight in my code?

Also to note, the code on the 286 PC doesn't seem to be crashing or aything, just not reading the keyboard, since locklights can still be triggered and PC restarts on ctrl-alt-del

Here is the part of the code that boots and reads the keyboard:

            [org 0x7c00]
    xor ah, ah
    mov al, 0x03
    int 0x10
     
    mov ah, 0x0e
    mov al, 0x0a
    int 0x10
     
    xor ax, ax
    mov es, ax
    mov ds, ax
     
    mov ah, 2
    mov al, 18
    mov ch, 0
    mov cl, 2
    mov dh, 0
    mov bx, 0x7e00
     
    int 0x13
     
    mainLoop:
    mov bx, intro
    call printString
    call newLine
     
    mov bx, cmdBuffer
    xor cx, cx
    waitForKey:
        xor ah, ah
        int 0x16
        
        push bx
        push cx
        mov bx, badChar
        mov cl, 0
        cmpLoop:
            inc bx
            cmp ah, [bx]
            je popBack
            cmp cl, [bx]
            jne cmpLoop
        pop cx
        pop bx 
        jmp continue
        popBack:
            pop cx
            pop bx
            jmp waitForKey
        
        continue:
        cmp al, 0x0d
        je enterKey
        cmp al, 0x08
        je backspaceKey
        
        cmp cx, 126
        je waitForKey
        
        mov ah, 0x0e
        push bx
        mov bl, [colourByte]
        int 0x10
        pop bx
        
        mov [bx], al
        inc bx
        inc cx
     
        jmp waitForKey
;rest of code

Interestingly, this slightly older version of the code does work on the 286: https://pastebin.com/391FWSNT

(both written for NASM)

Skeledog
  • 21
  • 5
  • 1
    You have failed to set up segment registers. It is possible that your 286 machine starts the boot loader at `07c0:0000` instead of `0000:7c00`. – fuz Mar 06 '22 at 00:05
  • @fuz I have heard about the segment registers before but never really understood them to be honest and am not sure how to properly use them. What should I set them to in this case? – Skeledog Mar 06 '22 at 00:36
  • Set all segment registers to zero and then do a far jump to some label in segment `0000h` to set up `CS`. Lots of boot sectors you can find online have the right code, just copy what they do. – fuz Mar 06 '22 at 00:38
  • @fuz so I tried settings ss, ds, and, and cs to 0 via xor ax, ax then mov ax to all of them and it still seems to be working in the VM, what do you mean by make a far jump to somewhere in 0000h? just set a label at some random place in the code and set CS to it? sorry for asking so many questions, I am very new to assembly and coding as a whole. I tried copying and pasting the first 8 lines of MikeOS into mine but that just caused a hang on boot with nothing happening in the VM – Skeledog Mar 06 '22 at 01:30
  • @fuz So I posted an update in my original comment that makes me feel like it may not be related to the segment registers since the issue is no longer present when I remove the je instructions for backspace and enter keys. What could be causing that issue? – Skeledog Mar 06 '22 at 02:12
  • You cannot set `CS` with a `mov` instruction. `CS` can only be set using a far jump, which is why you need to do one. As for the jumps, I don't know. As you haven't posted a [mcve] (your boot loader is missing code needed to assemble it) and especially the jump targets of the jumps you claim cause the issue have been removed, I have no way to tell what the problem is. – fuz Mar 06 '22 at 03:44
  • @fuz Okay, I created a new pastebin where I made it so it can be assembled and ran, just removed some of the commands and strings to make it shorter: https://pastebin.com/w1PzaBtX – Skeledog Mar 06 '22 at 04:09
  • Do not post this to paste bin but instead [edit] your question and add all relevant code. Stack Overflow questions must be self-contained. – fuz Mar 06 '22 at 12:02
  • One possible thing I see is that loading the second half of the boot loader might have failed. At some code to check if the initial `int 0x13` call to load that second half succeeded. – fuz Mar 06 '22 at 12:05
  • @fuz I think you may be paritally right. In the older code the does work the main difference is that all the code is contained in the boot sector. I tried moving the backspace and enter functions out of the boot sector on the older code and it didn't work anymore. But int 13 has to be at least partially working since it is able to print the first string, which is defined outside of the boot sector. So how could it be that code outside of the boot sector is not working right but strings are? (And the not working is still only present on the 286 machine) – Skeledog Mar 06 '22 at 15:05
  • @fuz I added a small label at the bottom of my code (so well after the boot sector) that would just print a single character to the screen, I called it right after int 13 and it does work on both the VM and 286, keyboard still doesn't. Could it be an issue with the location of the stack not being defined? (I have tried to get the stack to be defined to a location but have not had much sucess) – Skeledog Mar 06 '22 at 16:04
  • Well duh of course the code is reached, but did the `int 13h` actually succeed? Try to check the result. – fuz Mar 06 '22 at 16:09
  • @fuz I added a jnc right after the int 13 so it would print a character to the screen if the carry flag was high and it does not print anything on either vm or 286, so I assume that int 13 is working? – Skeledog Mar 06 '22 at 16:43
  • Okay, that sounds good. I'll investigate that in detail once I'm home. – fuz Mar 06 '22 at 17:18
  • @fuz Did you see any clear issues in the code? – Skeledog Mar 10 '22 at 01:44
  • Please don't edit an answer into the question. Roll that back, and post it as an actual answer. – Peter Cordes Mar 17 '22 at 22:40

1 Answers1

2

I found the issues:

  1. For NASM to generate machine code compatible with the 80286 I had to define CPU 286 along with BITS 16
  2. The BIOS on these ancient machines does not seem to support multitrack reads and writes and since I am trying to read more than 18 sectors from the floppy, I needed to segment the one int 0x13 into 2 and change the head in between like this:
mov ah, 2
mov al, 17
mov cx, 2   ;cl=2 ch=0
xor dh, dh
mov bx, 0x7e00

int 0x13

mov ah, 2

mov cx, 1    ;cl=1 ch=0
mov dh, 1
mov al, 8
mov bx, 0xa000

int 0x13
Skeledog
  • 21
  • 5
  • 2
    FYI, `mov cx, 2` is a more compact way to achieve CH=0, CL=2. There's no advantage to xor-zeroing for 8-bit registers on most CPUs, and [downsides on some modern CPUs](https://stackoverflow.com/questions/45660139/how-exactly-do-partial-registers-on-haswell-skylake-perform-writing-al-seems-to) vs. `mov ch,0`. (xor-zeroing is a good thing for full registers, though.) But if CL and CH are logically separate args, you might choose to not do that optimization. Still, that's what comments are for, and in fact one should generally comment to say which magic number is which, e.g. comment arg names – Peter Cordes Mar 20 '22 at 03:39
  • @PeterCordes Thanks, I will change it to that. I understand the concept behind mov a 16 bit register instead of 2 8 bit but in practice whenever the high register is not 0 it gets really confusing. (Hex is always confusing for me tbh) As for the downsides on modern CPUs, that stuff makes very little sense to me. I am still incredibly new to assembly and programming (this is one of the first things I have ever coded outside of a railroaded tutorial) So all that to say, the code is probably unoptimised or messy in a variety of ways, so do let me know if there is anything that shoul be changed. – Skeledog Mar 22 '22 at 01:02
  • 1
    Well if you're a beginner more interested in correctness and readable code, then don't worry about the CPU architecture details. But you really should learn how hex works, with each digit representing 4 bits. So for example `mov cx, 0x0102` sets CH=0x01 = 1 (the high 8 bits of that 16-bit number), and CL=0x02 = 2. Understanding the 8-bit halves of 16-bit numbers is often important or at least useful in asm. – Peter Cordes Mar 22 '22 at 01:23
  • 1
    `cpu 286` only errors out on (some) invalid instructions. `bits 16` is the default for `-f bin` output already. Adding these directives cannot be necessary to fixing your issue. (The `cpu 286` directive may turn up errors you made otherwise but the directive itself won't change anything.) – ecm Mar 22 '22 at 03:40
  • 1
    @ecm Thanks for letting me know about the defaulting to 16 bit on bin format, I will probably still leave it in just to be thorough. The description of the CPU 286 thing makes sense but for some reason that is a make or break between the program running or not on the old 286 machine. Do you think NASM has some kind of optimisation it does that may insert 386 instructions if not defined otherwise? Or could something else be happening? – Skeledog Mar 24 '22 at 02:36
  • @Skeledog: Can you compare the listing files to see if they differ somehow? Only thing that comes to mind is near conditional jumps, which can use either an `0Fh` prefixed 386+ instruction or a workaround involving two instructions on a 286 or earlier machine. – ecm Mar 24 '22 at 10:33