📄 keyeval.asm
字号:
; This is an example of an active TSR that counts keyboard interrupts
; once activated. Every minute it writes the number of keyboard
; interrupts that occurred in the previous minute to an output file.
; This continues until the user removes the program from memory.
;
;
; Usage:
; KEYEVAL filename - Begins logging keystroke data to
; this file.
;
; KEYEVAL REMOVE - Removes the resident program from
; memory.
;
;
; This TSR checks to make sure there isn't a copy already active in
; memory. When doing disk I/O from the interrupts, it checks to make
; sure DOS isn't busy and it preserves application globals (PSP, DTA,
; and extended error info). When removing itself from memory, it
; makes sure there are no other interrupts chained into any of its
; interrupts before doing the remove.
;
; The resident segment definitions must come before everything else.
ResidentSeg segment para public 'Resident'
ResidentSeg ends
EndResident segment para public 'EndRes'
EndResident ends
.xlist
.286
include stdlib.a
includelib stdlib.lib
.list
; Resident segment that holds the TSR code:
ResidentSeg segment para public 'Resident'
assume cs:ResidentSeg, ds:nothing
; Int 2Fh ID number for this TSR:
MyTSRID byte 0
; The following variable counts the number of keyboard interrupts
KeyIntCnt word 0
; Counter counts off the number of milliseconds that pass, SecCounter
; counts off the number of seconds (up to 60).
Counter word 0
SecCounter word 0
; FileHandle is the handle for the log file:
FileHandle word 0
; NeedIO determines if we have a pending I/O opearation.
NeedIO word 0
; PSP is the psp address for this program.
PSP word 0
; Variables to tell us if DOS, INT 13h, or INT 16h are busy:
InInt13 byte 0
InInt16 byte 0
InDOSFlag dword ?
; These variables contain the original values in the interrupt vectors
; we've patched.
OldInt9 dword ?
OldInt13 dword ?
OldInt16 dword ?
OldInt1C dword ?
OldInt28 dword ?
OldInt2F dword ?
; DOS data structures:
ExtErr struct
eeAX word ?
eeBX word ?
eeCX word ?
eeDX word ?
eeSI word ?
eeDI word ?
eeDS word ?
eeES word ?
word 3 dup (0)
ExtErr ends
XErr ExtErr {} ;Extended Error Status.
AppPSP word ? ;Application PSP value.
AppDTA dword ? ;Application DTA address.
; The following data is the output record. After storing this data
; to these variables, the TSR writes this data to disk.
month byte 0
day byte 0
year word 0
hour byte 0
minute byte 0
second byte 0
Keystrokes word 0
RecSize = $-month
; MyInt9- The system calls this routine every time a keyboard
; interrupt occus. This routine increments the
; KeyIntCnt variable and then passes control on to the
; original Int9 handler.
MyInt9 proc far
inc ResidentSeg:KeyIntCnt
jmp ResidentSeg:OldInt9
MyInt9 endp
; MyInt1C- Timer interrupt. This guy counts off 60 seconds and then
; attempts to write a record to the output file. Of course,
; this call has to jump through all sorts of hoops to keep
; from reentering DOS and other problematic code.
MyInt1C proc far
assume ds:ResidentSeg
push ds
push es
pusha ;Save all the registers.
mov ax, ResidentSeg
mov ds, ax
pushf
call OldInt1C
; First things first, let's bump our interrupt counter so we can count
; off a minute. Since we're getting interrupted about every 54.92549
; milliseconds, let's shoot for a little more accuracy than 18 times
; per second so the timings don't drift too much.
add Counter, 549 ;54.9 msec per int 1C.
cmp Counter, 10000 ;1 second.
jb NotSecYet
sub Counter, 10000
inc SecCounter
NotSecYet:
; If NEEDIO is not zero, then there is an I/O operation in progress.
; Do not disturb the output values if this is the case.
cli ;This is a critical region.
cmp NeedIO, 0
jne SkipSetNIO
; Okay, no I/O in progress, see if a minute has passed since the last
; time we logged the keystrokes to the file. If so, it's time to start
; another I/O operation.
cmp SecCounter, 60 ;One minute passed yet?
jb Int1CDone
mov NeedIO, 1 ;Flag need for I/O.
mov ax, KeyIntCnt ;Copy this to the output
shr ax, 1 ; buffer after computing
mov KeyStrokes, ax ; # of keystrokes.
mov KeyIntCnt, 0 ;Reset for next minute.
mov SecCounter, 0
SkipSetNIO: cmp NeedIO, 1 ;Is the I/O already in
jne Int1CDone ; progress? Or done?
call ChkDOSStatus ;See if DOS/BIOS are free.
jnc Int1CDone ;Branch if busy.
call DoIO ;Do I/O if DOS is free.
Int1CDone: popa
pop es
pop ds
iret
MyInt1C endp
assume ds:nothing
; MyInt28- Idle interrupt. If DOS is in a busy-wait loop waiting for
; I/O to complete, it executes an int 28h instruction each
; time through the loop. We can ignore the InDOS and CritErr
; flags at that time, and do the I/O if the other interrupts
; are free.
MyInt28 proc far
assume ds:ResidentSeg
push ds
push es
pusha ;Save all the registers.
mov ax, ResidentSeg
mov ds, ax
pushf ;Call the next INT 28h
call OldInt28 ; ISR in the chain.
cmp NeedIO, 1 ;Do we have a pending I/O?
jne Int28Done
mov al, InInt13 ;See if BIOS is busy.
or al, InInt16
jne Int28Done
call DoIO ;Go do I/O if BIOS is free.
Int28Done: popa
pop es
pop ds
iret
MyInt28 endp
assume ds:nothing
; MyInt16- This is just a wrapper for the INT 16h (keyboard trap)
; handler.
MyInt16 proc far
inc ResidentSeg:InInt16
pushf
call ResidentSeg:OldInt16 ;Call original handler.
pushf ;Must preserve flags
dec ResidentSeg:InInt16 ; for caller.
popf
retf 2 ;Fake IRET to keep flags.
MyInt16 endp
; MyInt13- This is just a wrapper for the INT 13h (disk I/O trap)
; handler.
MyInt13 proc far
inc ResidentSeg:InInt13
pushf
call ResidentSeg:OldInt13 ;Call original handler.
pushf ;Must preserve flags
dec ResidentSeg:InInt13 ; for caller.
popf
retf 2 ;Fake iret to keep flags.
MyInt13 endp
; ChkDOSStatus- Returns with the carry clear if DOS or a BIOS routine
; is busy and we can't interrupt them.
ChkDOSStatus proc near
assume ds:ResidentSeg
les bx, InDOSFlag
mov al, es:[bx] ;Get InDOS flag.
or al, es:[bx-1] ;OR with CritErr flag.
or al, InInt16 ;OR with our wrapper
or al, InInt13 ; values.
je Okay2Call
clc
ret
Okay2Call: clc
ret
ChkDOSStatus endp
assume ds:nothing
; PreserveDOS- Gets a copy's of DOS' current PSP, DTA, and extended
; error information and saves this stuff. Then it sets
; the PSP to our local PSP and the DTA to PSP:80h.
PreserveDOS proc near
assume ds:ResidentSeg
mov ah, 51h ;Get app's PSP.
int 21h
mov AppPSP, bx ;Save for later
mov ah, 2Fh ;Get app's DTA.
int 21h
mov word ptr AppDTA, bx ;Save for later.
mov word ptr AppDTA+2, es
push ds
mov ah, 59h ;Get extended err info.
xor bx, bx
int 21h
mov cs:XErr.eeDS, ds
pop ds
mov XErr.eeAX, ax
mov XErr.eeBX, bx
mov XErr.eeCX, cx
mov XErr.eeDX, dx
mov XErr.eeSI, si
mov XErr.eeDI, di
mov XErr.eeES, es
; Okay, point DOS's pointers at us:
mov bx, PSP
mov ah, 50h ;Set PSP.
int 21h
push ds ;Set the DTA to
mov ds, PSP ; address PSP:80h
mov dx, 80h
mov ah, 1Ah ;Set DTA call.
int 21h
pop ds
ret
PreserveDOS endp
assume ds:nothing
; RestoreDOS- Restores DOS' important global data values back to the
; application's values.
RestoreDOS proc near
assume ds:ResidentSeg
mov bx, AppPSP
mov ah, 50h ;Set PSP
int 21h
push ds
lds dx, AppDTA
mov ah, 1Ah ;Set DTA
int 21h
pop ds
push ds
mov si, offset XErr ;Saved extended error stuff.
mov ax, 5D0Ah ;Restore XErr call.
int 21h
pop ds
ret
RestoreDOS endp
assume ds:nothing
; DoIO- This routine processes each of the I/O operations
; required to write data to the file.
DoIO proc near
assume ds:ResidentSeg
mov NeedIO, 0FFh ;A busy flag for us.
; The following Get Date DOS call may take a while, so turn the
; interrupts back on (we're clear of the critical section once we
; write 0FFh to NeedIO).
sti
call PreserveDOS ;Save DOS data.
mov ah, 2Ah ;Get Date DOS call
int 21h
mov month, dh
mov day, dl
mov year, cx
mov ah, 2Ch ;Get Time DOS call
int 21h
mov hour, ch
mov minute, cl
mov second, dh
mov ah, 40h ;DOS Write call
mov bx, FileHandle ;Write data to this file.
mov cx, RecSize ;This many bytes.
mov dx, offset month ;Starting at this address.
int 21h ;Ignore return errors (!).
mov ah, 68h ;DOS Commit call
mov bx, FileHandle ;Write data to this file.
int 21h ;Ignore return errors (!).
mov NeedIO, 0 ;Ready to start over.
call RestoreDOS
PhasesDone: ret
DoIO endp
assume ds:nothing
; MyInt2F- Provides int 2Fh (multiplex interrupt) support for this
; TSR. The multiplex interrupt recognizes the following
; subfunctions (passed in AL):
;
; 00- Verify presence. Returns 0FFh in AL and a pointer
; to an ID string in es:di if the
; TSR ID (in AH) matches this
; particular TSR.
;
; 01- Remove. Removes the TSR from memory.
; Returns 0 in AL if successful,
; 1 in AL if failure.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -