The Art of
ASSEMBLY LANGUAGE PROGRAMMING

Chapter Twenty (Part 5)

Table of Content

Chapter Twenty (Part 7) 

CHAPTER TWENTY:
THE PC KEYBOARD (Part 6)
20.7 - Simulating Keystrokes
20.7.1 - Stuffing Characters in the Type Ahead Buffer
20.7.2 - Using the 80x86 Trace Flag to Simulate IN AL, 60H Instructions
20.7 Simulating Keystrokes

At one point or another you may want to write a program that passes keystrokes on to another application. For example, you might want to write a keyboard macro TSR that lets you capture certain keys on the keyboard and send a sequence of keys through to some underlying application. Perhaps you'll want to program an entire string of characters on a normally unused keyboard sequence (e.g., ctrl-up or ctrl-down). In any case, your program will use some technique to pass characters to a foreground application. There are three well-known techniques for doing this: store the scan/ASCII code directly in the keyboard buffer, use the 80x86 trace flag to simulate in al, 60h instructions, or program the on-board 8042 microcontroller to transmit the scan code for you. The next three sections describe these techniques in detail.

20.7.1 Stuffing Characters in the Type Ahead Buffer

Perhaps the easiest way to insert keystrokes into an application is to insert them directly into the system's type ahead buffer. Most modern BIOSes provide an int 16h function to do this (see "The Keyboard BIOS Interface"). Even if your system does not provide this function, it is easy to write your own code to insert data in the system type ahead buffer; or you can copy the code from the int 16h handler provided earlier in this chapter.

The nice thing about this approach is that you can deal directly with ASCII characters (at least, for those key sequences that are ASCII). You do not have to worry about sending shift up and down codes around the scan code for tn "A" so you can get an upper case "A", you need only insert 1E41h into the buffer. In fact, most programs ignore the scan code, so you can simply insert 0041h into the buffer and almost any application will accept the funny scan code of zero.

The major drawback to the buffer insertion technique is that many (popular) applications bypass DOS and BIOS when reading the keyboard. Such programs go directly to the keyboard's port (60h) to read their data. As such, shoving scan/ASCII codes into the type ahead buffer will have no effect. Ideally, you would like to stuff a scan code directly into the keyboard controller chip and have it return that scan code as though someone actually pressed that key. Unfortunately, there is no universally compatible way to do this. However, there are some close approximations, keep reading...

20.7.2 Using the 80x86 Trace Flag to Simulate IN AL, 60H Instructions

One way to deal with applications that access the keyboard hardware directly is to simulate the 80x86 instruction set. For example, suppose we were able to take control of the int 9 interrupt service routine and execute each instruction under our control. We could choose to let all instructions except the in instruction execute normally. Upon encountering an in instruction (that the keyboard ISR uses to read the keyboard data), we check to see if it is accessing port 60h. If so, we simply load the al register with the desired scan code rather than actually execute the in instruction. It is also important to check for the out instruction, since the keyboard ISR will want to send and EOI signal to the 8259A PIC after reading the keyboard data, we can simply ignore out instructions that write to port 20h.

The only difficult part is telling the 80x86 to pass control to our routine when encountering certain instructions (like in and out) and to execute other instructions normally. While this is not directly possible in real mode, there is a close approximation we can make. The 80x86 CPUs provide a trace flag that generates an exception after the execution of each instruction. Normally, debuggers use the trace flag to single step through a program. However, by writing our own exception handler for the trace exception, we can gain control of the machine between the execution of every instruction. Then, we can look at the opcode of the next instruction to execute. If it is not an in or out instruction, we can simply return and execute the instruction normally. If it is an in or out instruction, we can determine the I/O address and decide whether to simulate or execute the instruction.

In addition to the in and out instructions, we will need to simulate any int instructions we find as well. The reason is because the int instruction pushes the flags on the stack and then clears the trace bit in the flags register. This means that the interrupt service routine associated with that int instruction would execute normally and we would miss any in or out instructions appearing therein. However, it is easy to simulate the int instruction, leaving the trace flag enabled, so we will add int to our list of instructions to interpret.

The only problem with this approach is that it is slow. Although the trace trap routine will only execute a few instructions on each call, it does so for every instruction in the int 9 interrupt service routine. As a result, during simulation, the interrupt service routine will run 10 to 20 times slower than the real code would. This generally isn't a problem because most keyboard interrupt service routines are very short. However, you might encounter an application that has a large internal int 9 ISR and this method would noticeably slow the program. However, for most applications this technique works just fine and no one will notice any performance loss while they are typing away (slowly) at the keyboard.

The following assembly code provides a short example of a trace exception handler that simulates keystrokes in this fashion:

.xlist
                include         stdlib.a
                includelib      stdlib.lib
                .list

cseg            segment para public 'code'
                assume  ds:nothing

byp             textequ <byte ptr>

; ScanCode must be in the Code segment.

ScanCode        byte    0



;****************************************************************************
;
; KbdSim- Passes the scan code in AL through the keyboard controller
; using the trace flag. The way this works is to turn on the
; trace bit in the flags register. Each instruction then causes a trace
; trap. The (installed) trace handler then looks at each instruction to
; handle IN, OUT, INT, and other special instructions. Upon encountering
; an IN AL, 60 (or equivalent) this code simulates the instruction and
; returns the specified scan code rather than actually executing the IN
; instruction. Other instructions need special treatment as well. See
; the code for details. This code is pretty good at simulating the hardware,
; but it runs fairly slow and has a few compatibility problems.


KbdSim          proc    near

                pushf
                push    es
                push    ax
                push    bx


                xor     bx, bx          ;Point es at int vector tbl
                mov     es, bx          ; (to simulate INT 9).
                cli                     ;No interrupts for now.
                mov     cs:ScanCode, al ;Save output scan code.

                push    es:[1*4]        ;Save current INT 1 vector
                push    es:2[1*4]       ; so we can restore it later.



; Point the INT 1 vector at our INT 1 handler:

                mov     word ptr es:[1*4], offset MyInt1
                mov     word ptr es:[1*4 + 2], cs



; Turn on the trace trap (bit 8 of flags register):

                pushf
                pop     ax
                or      ah, 1
                push    ax
                popf


; Simulate an INT 9 instruction. Note: cannot actually execute INT 9 here
; since INT instructions turn off the trace operation.


                pushf
                call    dword ptr es:[9*4]


; Turn off the trace operation:


                pushf
                pop     ax
                and     ah, 0feh        ;Clear trace bit.
                push    ax
                popf


; Disable trace operation.


                pop     es:[1*4 + 2]    ;Restore previous INT 1
                pop     es:[1*4]        ; handler.


; Okay, we're done. Restore registers and return.

VMDone:         pop     bx
                pop     ax
                pop     es
                popf
                ret
KbdSim          endp



;----------------------------------------------------------------------------
;
; MyInt1- Handles the trace trap (INT 1). This code looks at the next
; opcode to determine if it is one of the special opcodes we have to
; handle ourselves.


MyInt1          proc    far
                push    bp
                mov     bp, sp          ;Gain access to return adrs via BP.
                push    bx
                push    ds

; If we get down here, it's because this trace trap is directly due to
; our having punched the trace bit. Let's process the trace trap to
; simulate the 80x86 instruction set.
;
; Get the return address into DS:BX

NextInstr:      lds     bx, 2[bp]

; The following is a special case to quickly eliminate most opcodes and
; speed up this code by a tiny amount.

                cmp     byp [bx], 0cdh  ;Most opcodes are less than
                jnb     NotSimple       ; 0cdh, hence we quickly
                pop     ds              ; return back to the real
                pop     bx              ; program.
                pop     bp
                iret

NotSimple:      je      IsIntInstr      ;If it's an INT instruction.

                mov     bx, [bx]        ;Get current instruction's opcode.
                cmp     bl, 0e8h        ;CALL opcode
                je      ExecInstr
                jb      TryInOut0

                cmp     bl, 0ech        ;IN al, dx instr.
                je      MayBeIn60
                cmp     bl, 0eeh        ;OUT dx, al instr.
                je      MayBeOut20
                pop     ds              ;A normal instruction if we get
                pop     bx              ; down here.
                pop     bp
                iret

TryInOut0:      cmp     bx, 60e4h       ;IN al, 60h instr.
                je      IsINAL60
                cmp     bx, 20e6h       ;out 20, al instr.
                je      IsOut20

; If it wasn't one of our magic instructions, execute it and continue.

ExecInstr:      pop     ds
                pop     bx
                pop     bp
                iret

; If this instruction is IN AL, DX we have to look at the value in DX to
; determine if it's really an IN AL, 60h instruction.

MayBeIn60:      cmp     dx, 60h
                jne     ExecInstr
                inc     word ptr 2[bp]          ;Skip over this 1 byte instr.
                mov     al, cs:ScanCode
                jmp     NextInstr

; If this is an IN AL, 60h instruction, simulate it by loading the current
; scan code into AL.

IsInAL60:       mov     al, cs:ScanCode
                add     word ptr 2[bp], 2       ;Skip over this 2-byte instr.
                jmp     NextInstr


; If this instruction is OUT DX, AL we have to look at DX to see if we're
; outputting to location 20h (8259).

MayBeOut20:     cmp     dx, 20h
                jne     ExecInstr
                inc     word ptr 2[bp]          ;Skip this 1 byte instruction.
                jmp     NextInstr

; If this is an OUT 20h, al instruction, simply skip over it.

IsOut20:        add     word ptr 2[bp], 2       ;Skip instruction.
                jmp     NextInstr


; IsIntInstr- Execute this code if it's an INT instruction.
;
; The problem with the INT instructions is that they reset the trace bit
; upon execution. For certain guys (see above) we can't have that.
;
; Note: at this point the stack looks like the following:
;
;       flags
;
;       rtn cs -+
;               |
;       rtn ip  +-- Points at next instr the CPU will execute.
;       bp
;       bx
;       ds
;
; We need to simulate the appropriate INT instruction by:
;
;       (1)     adding two to the return address on the stack (so it returns
;               beyond the INT instruction.
;       (2)     pushing the flags onto the stack.
;       (3)     pushing a phony return address onto the stack which simulates
;               the INT 1 interrupt return address but which "returns" us to
;               the specified interrupt vector handler.
;
; All this results in a stack which looks like the following:
;
;       flags
;
;       rtn cs -+
;               |
;       rtn ip  +-- Points at next instr beyond the INT instruction.
;
;       flags   --- Bogus flags to simulate those pushed by INT instr.
;
;       rtn cs -+
;               |
;       rtn ip  +-- "Return address" which points at the ISR for this INT.
;       bp
;       bx
;       ds


IsINTInstr:     add     word ptr 2[bp], 2       ;Bump rtn adrs beyond INT instr.
                mov     bl, 1[bx]
                mov     bh, 0
                shl     bx, 1                   ;Multiply by 4 to get vector
                shl     bx, 1                   ; address.

                push    [bp-0]                  ;Get and save BP
                push    [bp-2]                  ;Get and save BX.
                push    [bp-4]                  ;Get and save DS.

                push    cx
                xor     cx, cx                  ;Point DS at interrupt
                mov     ds, cx                  ; vector table.

                mov     cx, [bp+6]              ;Get original flags.
                mov     [bp-0], cx              ;Save as pushed flags.

                mov     cx, ds:2[bx]            ;Get vector and use it as
                mov     [bp-2], cx              ; the return address.
                mov     cx, ds:[bx]
                mov     [bp-4], cx

                pop     cx
                pop     ds
                pop     bx
                pop     bp
                iret
;
MyInt1          endp




; Main program - Simulates some keystrokes to demo the above code.

Main            proc

                mov     ax, cseg
                mov     ds, ax

                print
                byte    "Simulating keystrokes via Trace Flag",cr,lf
                byte    "This program places 'DIR' in the keyboard buffer"
                byte    cr,lf,0

                mov     al, 20h         ;"D" down scan code
                call    KbdSim
                mov     al, 0a0h        ;"D" up scan code
                call    KbdSim

                mov     al, 17h         ;"I" down scan code
                call    KbdSim
                mov     al, 97h         ;"I" up scan code
                call    KbdSim

                mov     al, 13h         ;"R" down scan code
                call    KbdSim
                mov     al, 93h         ;"R" up scan code
                call    KbdSim

                mov     al, 1Ch         ;Enter down scan code
                call    KbdSim
                mov     al, 9Ch         ;Enter up scan code
                call    KbdSim



                ExitPgm
Main            endp


cseg            ends

sseg            segment para stack 'stack'
stk             byte    1024 dup ("stack ")
sseg            ends

zzzzzzseg       segment para public 'zzzzzz'
LastBytes       db      16 dup (?)
zzzzzzseg       ends
                end     Main

Chapter Twenty (Part 5)

Table of Content

Chapter Twenty (Part 7) 

Chapter Twenty: The PC Keyboard (Part 6)
29 SEP 1996