17

I have two snippets of 8086 assembly code, both of which are supposed to do the same thing: make the mouse appear on the screen.

Show_Mouse:
    push ax
    mov ax,0        ;Reset Mouse
    int 33h
mov ax,1        ;Display Mouse
int 33h
pop ax
ret

Show_Mouse:
    push ax
    mov ax,0        ;Reset Mouse
    int 33h

    inc ax          ;Display Mouse
    int 33h
    pop ax
    ret

The first one does what it's supposed to do when called as a subroutine, it makes the mouse appear on screen. However, the second one, whose only change is inc ax, does not!

I'm familiar with the concept of memory-mapped ports on other systems like the NES, which have to be written to in a very specific way or the write won't "take" (e.g. the command INC $2005 will not increase the scroll offset by 1, you actually have to use STA to write to it), but this scenario in MS-DOS is quite peculiar, since it's a software interrupt that takes the value in AX as an argument. As such, it shouldn't matter that I used INC to get AX to equal 1. Right?

user3840170
  • 23,072
  • 4
  • 91
  • 150
puppydrum64
  • 1,638
  • 5
  • 18

3 Answers3

45

When calling the mouse driver interrupt with AX = 0, it returns 0xFFFF in AX if a mouse driver is installed.

So if it is installed, the code with INC AX will increment AX back to 0 and then it will just reset the mouse driver a second time.

It is very typical that interfaces that use software interrupts give you back a status code in AL or AX, so this is no exception.

Justme
  • 31,506
  • 1
  • 73
  • 145
30

Calling an interrupt service is more like invoking a system call than it is like writing to a memory-mapped register.

That is, when you invoke a software interrupt, there is no guarantee that the register values will be the same that they have been before the interrupt call. In fact, most of the time, they will not be, unless the interrupt service routine is a no-op placeholder. Modifying register state is how the interrupt handler will communicate its presence, adherence to the expected calling protocol and any potential result to the caller. (Hardware interrupt handlers are another story: since they can be invoked at unpredictable times, they must restore all registers to their original values before returning.) The comparison to syscalls is not a mere metaphor, as software interrupts have in fact been used as a syscall mechanism on a number of x86 operating systems: interrupt 0x80 in Linux, interrupt 0x21 in MS-DOS, and the VxDcall/VxDjmp pseudo-instruction in VMM32 (Windows 3.x Enhanced Mode and 9x), which is a specially-formatted interrupt 0x20 instruction.

Ralf Brown’s entry on interrupt 0x33 service 0 has this to say on the AX register:

Return: AX = status

  • 0000h hardware/driver not installed
  • FFFFh hardware/driver installed

which means that the AX register will be only left unmodified if the mouse driver is absent, demonstrating the principle above. If you increment the FFFFh value returned in AX by service 0, you get zero (thanks to 16-bit wrap-around), which means you end up invoking service 0 again. This is easily seen in a DEBUG session:

C:\>debug
-a
1E8A:0100 mov ax,0
1E8A:0103 int 33
1E8A:0105 inc ax
1E8A:0106 int 33
1E8A:0108
-rax
AX 0000
:abcd
-r
AX=ABCD  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000  
DS=1E8A  ES=1E8A  SS=1E8A  CS=1E8A  IP=0100   NV UP EI PL NZ NA PO NC 
1E8A:0100 B80000        MOV     AX,0000                            
-p

AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1E8A ES=1E8A SS=1E8A CS=1E8A IP=0103 NV UP EI PL NZ NA PO NC 1E8A:0103 CD33 INT 33
-p

AX=FFFF BX=0005 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1E8A ES=1E8A SS=1E8A CS=1E8A IP=0105 NV UP EI PL NZ NA PO NC 1E8A:0105 40 INC AX
-p

AX=0000 BX=0005 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1E8A ES=1E8A SS=1E8A CS=1E8A IP=0106 NV UP EI PL ZR AC PE NC 1E8A:0106 CD33 INT 33

The same RBIL entry notes that the interrupt 0x33 call also modifies BX. You should probably make Show_Mouse preserve the value of the BX register, or document that the call may modify it. Since you use the stack to preserve AX, I presume you intend to do the former.

user3840170
  • 23,072
  • 4
  • 91
  • 150
  • Thank you, I wasn't aware that the interrupt returned an error code. Perhaps I can use that to display an error message like so: mov ax,0 int 33h jz MouseDriverNotInstalled – puppydrum64 Aug 21 '21 at 17:42
  • 3
    You will need to place test ax, ax before the jz. RBIL only mentions that the interrupt may update the AX register, not condition flags. (If interrupt 0x33 is a pure no-op, it certainly will not update them.) – user3840170 Aug 21 '21 at 17:46
  • 7
    @puppydrum64, Re, "I wasn't aware that the interrupt returned an error code." The name, "software interrupt," is misleading, but we're stuck with it. Software interrupts don't interrupt the program; they are requested by the program. They usually accept args and/or return results. They are much more like function calls than like hardware interrupts, which happen without warning, and which must not disturb the program. The thing called "software interrupt" in x86 nomenclature has better names in some other architectures ("trap," "syscall," "supervisor call (SVC)," ...) – Solomon Slow Aug 22 '21 at 10:56
  • I see! Honestly I should have known better - I'm used to BIOS calls on the NEOGEO and GBA which often use certain registers or have values returned in certain registers, and the inner workings of which are often hidden from the programmer. My assumption was that such a routine would also set the flags accordingly, which can't be guaranteed either. – puppydrum64 Aug 22 '21 at 15:12
  • 3
    An x86 int instruction pushes condition flags on the stack, and the corresponding iret restores them, so unless the interrupt handler specifically modifies the flags word, the condition codes will generally be preserved. Although it’s pretty common to modify condition codes anyway; CF is often used as an error indicator, for example. But if it’s not part of the documented API, better not rely on it. – user3840170 Aug 22 '21 at 15:30
  • "That is, when you invoke a software interrupt, there is no guarantee that the register values will be the same that they have been before the interrupt call." The dreaded Side Effect, cursed by Comp Sci professors the world over! – RonJohn Aug 23 '21 at 14:46
6

Simple answer: Because you don't know the value stored in AX after a return.

A lot of system routines change the value (not only the AX register but generally any register). See system call documentation, sometimes there are remarks like "Preserves: CX, DX. Returns: BX=0, BP=unpredictable, AX=return value" or so.

Sometimes you have guaranteed the value in some register, but the best practice is to define all the information system call needs. Otherwise, you are doing a "premature optimization".

Martin Maly
  • 5,535
  • 18
  • 45