AHS@PSUVM.BITNET (06/28/88)
June 26, 1988 -- Fourth and final dissassembly -- How KBDL swaps the keys.
Tom Almy, toma@tekgvs.TEK.COM, writes of his set of KBD*.com programs:
All of these [AT only] programs were written by me, using the CFORTH
Forth Compiler, and are placed in public domain for the benefit of
other PC users who are frustrated with these keyboard designs.
I am not supplying sources, the programs are small enough that DEBUG
can be used to reverse-engineer them.
Tom Almy
toma@tekgvs.TEK.COM
-----------------------------------
Earlier, I attempted several dissassemblies of: KBDL.COM 332 6-15-88 7:03a.
My motivation was to see if Tom knew a better way than using interrupt 9 -- as
is done in a similar program, IBMFIX, published by PC Magazine -- to move keys
around the keyboard. The short answer is: Tom knows.
Here is my final dissassembly, hopefully. (Note: A few hours after I
completed this commented dissassembly, I downloaded a dissassembly sent by Tom.
Thank you Tom. Tom's dissassebly was not commented, except for the
manipulation of return addresses following a call. The two dissassemblies,
made independently, do agree).
The problem that threw me off in my previous dissassemblies was: What is the
code that is being executed after returning from the call made a label L017F
(at offset 017F) ???
This was solved by explanations supplied first by Kchula-Rrit, thank you
Kchula-Rrit, and later by Tom Almy himself in his first and second replies,
thank you Tom.
I had one other question. What is so special about this code that it will run
successfully only on an AT ??. This was solved; it is explained in the
dissassembly of the new interrupt handler.
In sum: Making the dissassembly required some tidbits of information beyond
the information provided by Debug's U command. (The Trace command is of
limited and difficult use when dissassembling a TSR). I expect that when I
rewrite this code in assembly, I will need: the 38h=56 bytes of the handler,
plus roughly 35 bytes to set the vectors, and roughly 35 bytes to install the
code in memory, or roughly 56+40+24=126 bytes. That is, to install the TSR,
I will need: very roughly 35+35=70 bytes of straighforward code versus
332-56=276 bytes of not exactly straighforward code, but probably good compiled
Forth code -- I understand that AI has made promises but has not yet fully
delivered in compilerdom -- by the way, how large and straightforward would a
QBasic EXE be?. When I am done, I think I will post both the COM and assembly.
Finally, and most importantly, my sincere thanks to Tom Almy for showing us how
to use interrupt 15.
---------------------------------------------------------------------
TITLE KBDL
RET_NEAR MACRO
DB 0C3H
ENDM
.RADIX 16
S0000 SEGMENT
ASSUME DS:S0000, SS:S0000 ,CS:S0000 ,ES:S0000
ORG 0100H
L0100: JMP L010B
DB 4C,2
L0105 DW 0000
L0107 DW 0000,000A
L010B: MOV SP, FF9A
MOV WORD PTR L0105, FF98
MOV BP, FFFE
MOV L0107, BP
CLD
CALL L017F
MOV AX, 4C00 ;exit program, with errorlevel 00
INT 21
;The following DW is for data write and data immediate
L0124 DW 0000 ;For Offst of old vect 15
;Will be moved into PSP at offst 80
;The following DW is for data write from offset 01DA.
;Since it is not data read, nor data immediate, is it executed ??
;No, but it is moved to PSP to be used there, as part of the mov 3C
L0126 DW 0000 ;For Segmt of old vect 15
;Will be moved into PSP at offst 82
;(At offst 84 begins new hdlr 15)
;New int 15 hdlr ;Offset: 0128
;Moved in PSP at offst 84 by t mov 3C (160-128=38h is enough)
;
;Int 15 is not documented in Duncan's Advanced MS DOS
;However, thanks to Ralph Brown, we learn that:
;
; Ralf Brown
; Arpa: ralf@cs.cmu.edu
; UUCP: {uunet,ucbvax,harvard}!cs.cmu.edu!ralf
; BIT: ralf%cs.cmu.edu@cmuccvma
; FIDO: Ralf Brown at 1:129/31 (more subject to change than the others
;
; Last edited 1/30/88
;
; -----------------------------------------------------------
; INT 15 - HOOK - KEYBOARD INTERCEPT (AT model 3x9,XT2,XT286,CONV,PS)
; AH = 4Fh
; AL = scan code
; CF set
; Return: AL = scan code
; CF set
; Note: Called by INT 9 handler to translate scan codes
; -----------------------------------------------------------
;
;Thus, this TSR should work with the following machines:
; AT-3x9, XT2, XT286, Convertible, PS2
;
;New int 15 hdlr ;Offset: 0128
;Moved in PSP at offst 84 by t mov 3C (160-128=38h is enough)
CMP AH, 4F ;Is it a kbd intercept ?
JNZ L015A ;No
CMP AL, 3A ;Is it t scancode of CapsLock ?
JNZ L014B ;No
;It is a CapsLock
PUSH ES
CS:
MOV ES, [007E] ;Map ES onto DOS segmt
;([7E] was earlier set to 00)
;Not exactly a straightforward
;manner to set ES to 00
ES:
TEST BYTE PTR [0417], 02 ;Is LeftShiftKey down ?
JNZ L0141 ;No
;LeftShiftKey is down, and CapsLock was pressed
MOV AL,1D ;Make keypress a Ctrl keypress
;It is CapsLock but LeftShift was not pressed
L0141: MOV [007D],AL ;Store resultg Ctrl or CapsLock
;at 7D in PSP
POP ES
STC
CS:
JMP FAR [0080] ;Execute old hdlr 15
;It was not CapsLock
L014B: CMP AL,BA ;Is it t scancode of the ??1??-key
;Is BA t scancode of "CapsLockRelease"?
;BA=(80+3A)=(80+CapsLockScanCode)
;My book stops at scancode 53h.....
JNZ L015A ;No
;It is key ??1??
;It is a ?CapsLockRelease
MOV AL,[007D] ;Make it the Ctrl or CapsLock
;that was earlier stored at 7D in PSP
ADD AL,80 ;Make it now into a ??2?? keypress
;But BA=(80+3A)=(80+CapsLockScanCode)
STC ;?Make it now into a ?CtrlRelease or
;a ?CapsLockRelease
CS:
JMP FAR [0080] ;Execute old hdlr 15
;It was not a kbd intercept
;It was not key ??1??
;It was not a ?CapsLockRelease
L015A: STC
CS:
JMP FAR [0080] ;Execute old hdlr 15
;End of new int 15 hdlr
;The following code is called from offset 01F8
L0160: POP SI
POP AX
POP DX
POP BX
PUSH DS
MOV DS,BX
MOV AH,25 ;Get vector
INT 21
POP DS
JMP SI
L016E: MOV AH,35 ;Set vector
INT 21
MOV AX,BX
MOV BX,ES
RET_NEAR ;Byte: C3
L0177: MOV DX,0C
MOV AX,T3100 ;Exit but remain memory residt
INT 21
L017F: CALL L01FE ;Bytes: E8 7C 00
;Called from: L011C
;Now, THE question is: -------------------------------------------------------
; What is the code that follows this call and
; that we shall execute when we return from this call ???
; Answer: execution does not return here, but at the end of the text,
; at CALL L0210, because the call's return addr was manipulated
; by the routine at L01FE
;Here is the source
;0180 7C 00 33 43 41 50 53 4C-4F 43 4B 20 2D 3E 20 43 | 3CAPSLOCK -> C
;0190 54 52 4C 2C 20 20 4C 45-46 54 53 48 49 46 54 20 TRL, LEFTSHIFT
;01A0 2B 20 43 41 50 53 4C 4F-43 4B 20 2D 3E 20 43 41 + CAPSLOCK -> CA
;01B0 50 53 4C 4F 43 4B E8 57-00 E8 42 00 0B 42 79 20 PSLOCKhW hB By
;Now, how do we ever reach those two calls?
;Answer: By manipulating the stack in the routine at L01FE
;However, this is only one of several return address generated by routine L01FE
CALL L0210 ;Bytes: E8 57 00
CALL L01FE ;Bytes: E8 42 00
;Now, comes more text.
;Here is the source:
;01B0 50 53 4C 4F 43 4B E8 57-00 E8 42 00 0B 42 79 20 PSLOCKhW hB By
;01C0 54 6F 6D 20 41 6C 6D 79-E8 45 00 C7 06 7E 00 00 Tom AlmyhE G^
;Here is the text: "By Tom Almy"
DB 42,79
DB 20,54,6F,6DH,20
DB 41,6C,6DH,79
;which ends here.
;Now, how do we ever reach the following line ??
;There are no explicit jumps or calls to offset 01C8.
;Answer: By now we know, the CALL L01FE manipulated the call's return address.
;However, this is only one of several return address generated by routine L01FE
CALL L0210 ;Offset: 01C8
;;L0210: MOV AX,0DH ;A copy of the routine is placed here for clarity
;; CALL L021F
;;;;L021C DB 1,0 ;Another copy for clarity
;;;;
;;;;L021E DB 0
;;;;
;;;;L021F: MOV L021E,AL ;AL=0D
;;;; MOV AH,40 ;Write byte at L021E to handle in BX
;;;; MOV CX,1
;;;; MOV DX,OFFSET L021E
;;;; MOV BX,L021C
;;;; INT 21
;;;; RET_NEAR
;; MOV AX,0A
;; JMP L021F
;;
;;L021C DB 1,0
;;
;;L021E DB 0
;;
;;L021F: MOV L021E,AL ;AL=0A
;; MOV AH,40 ;Write byte at L021E to handle in BX
;; MOV CX,1
;; MOV DX,OFFSET L021E
;; MOV BX,L021C
;; INT 21
;; RET_NEAR
MOV WORD PTR DS:7E, 0 ;In PSP, set [7E]=00 = DOS segmt
MOV AX,15 ;15 in AL when exctg funct 35
CALL L016E ;---- To execute Funct 35 ---------------
;----- AL is overwritn aftr funct ---
;; ;1<<<<<<<<<<<<<<<<<<<<<
;;L016E: MOV AH,35 ;Get vect, AL=15 ;A copy placed here for clarity
;; INT 21
;; MOV AX,BX ;AX= Offst of old vect 15
;; MOV BX,ES ;BX= Segmt of old vect 15
;; RET_NEAR ;Byte: C3
MOV L0124,AX ;Offst of old vect 15
MOV L0126,BX ;Segmt of old vect 15 ;Offset: 01DA
;; ;A copy of the routine placed here for clarity
;;;The following DW is for data write and data immediate
;;
;;L0124 DW 0000 ;Offst of old vect 15
;;
;;;The following DW is for data write from offset 01DA.
;;
;;L0126 DW 0000 ;Segmt of old vect 15
3 MOV DX,OFFSET L0124 ;Offst of old vect 15
3 PUSH DX
2 MOV DX,80
2 PUSH DX
1 MOV DX,3C ;Nuber of bytes to be moved
1 PUSH DX
CALL L023E ;Offset: 01EA
;;L023E: ;A copy of the routine placed here for clarity
;;CRA POP BX ;Remove t call's return address (CRA)
;; 1 POP CX ;3C
;; 2 POP DI ;80
;; 3 POP SI ;Offst of old vect 15, ie: L0124
;; JCXZ L024A ;Offset: 0242
;; MOV AX,DS
;; MOV ES,AX ;Make ES = DS
;; REPZ MOVSB ;Mov 3C bytes of code to [80] into PSP's cmdline area
;; ;
;; ;Code being stuffed into PSP:
;; ;
;; ;At 7D The Resulting CapsLock or Ctrl keypress
;; ;At 7E Value=00 = DOS segmt
;; ;At 80 Offst of old vect 15
;; ;At 84 New int 15 hdlr
;; ;At (80+3C) End of hdlr
;;
;;;Next line is jumped at from offset 0242
;;
;;L024A: JMP BX ;BX=CRA. Was set at L023E
;; ;This jmp is t actual return from the call
3 MOV AX,CS
3 PUSH AX
2 MOV DX,84
2 PUSH DX
1 MOV DX,15 ;The int to be re-vectored
1 PUSH DX
CALL L0160 ;Offset: 01F8
;;L0160: ;A copy of the routine is placed here for clarity
;;CRA POP SI ;Remove t call's return address (CRA)
;; 1 POP AX ;15 the int to be re-vectored
;; 2 POP DX ;84 Offst of new hdlr 15 in PSP cmdline area
;; ; (at 80 we store offst o old int 15 vect)
;; ; When was new hdlr 15 placed in PSP ???
;; ; It was moved there by t mov 3C
;; 3 POP BX ;CS
;;1 PUSH DS ;Save t true DS addr
;; MOV DS,BX ;DS=CS=PSP
;; MOV AH,25 ;Set vector ;2<<<<<<<<<<<<<<<<<<<<<<
;; INT 21
;;1 POP DS
;; JMP SI ;SI=CRA. Was set at L0160
JMP L0177
L01FE: POP BX
MOV AL,[BX]
XOR AH,AH
INC BX
PUSH BX
PUSH AX
ADD BX,AX
POP AX
POP DX
PUSH BX
MOV BX,DX
JMP L0231 ;Offset: 020D
L0210: MOV AX,0DH
CALL L021F
MOV AX,0A
JMP L021F
L021C DB 1,0
L021E DB 0
L021F: MOV L021E,AL
MOV AH,40 ;Write byte at L021E to handle in BX
MOV CX,1
MOV DX,OFFSET L021E
MOV BX,L021C
INT 21
RET_NEAR
;Jump to next line is from offset 020D
L0231: MOV CX,AX
MOV DX,BX
MOV BX,L021C
MOV AH,40
INT 21
RET_NEAR
;Next line is called from offset 01EA
L023E: POP BX
POP CX
POP DI
POP SI
JCXZ L024A ;Offset: 0242
MOV AX,DS
MOV ES,AX
REPZ MOVSB
;Next line is jumped at from offset 0242
L024A: JMP BX ;Bytes: FF E3
S0000 ENDS
;
END L0100