-
February 19th, 2011, 07:38 PM
#1
Hooking the system timer interrupt in DOS?
I'm trying to write a TSR (Terminate and Stay Resident) program in DOS. I have an old machine running real DOS 6.2 mostly for nostalgic purposes that I'd like to write my own slowdown utility for.
My first step was to simply try use some example code to see if I could assemble and run a TSR, then try adapt that as I haven't written a TSR before.
The problem is that it simply locks up the system, locking the keyboard and the display simply prints the prompt after execution with apparently no control returned to DOS, or maybe it does return but has somehow mangled the system otherwise.
I don't remember where I got the code from, but the code is not mine, and I do not understand the "clean up the stack" part below the "Do something here" comment. I have tried to replace the entire "new" interrupt with a mere ret instruction, but the result was still a lock up.
Of interest is perhaps that I tried to change the interrupt hooked from the system timer to the keyboard (16h), and that yielded that the prompt was printed ad inf. with no control whatsoever of the keyboard, as if the enter key was pressed continuously by the user.
What is wrong with this program? I really am having a hard time trying to find some example code in assembly for a TSR in DOS. If anyone knows of some assembly code that works (or even if you don't know whether it works or not) please let me know where to find it.
Code:
.model tiny
.186
.code
org 100h
start: jmp short Install
olddosint dw 00h,00h
newDOSInt:
pushf ; create our own descriptor
push cs ; (could be a call far instead)
push offset Back ;
jmp far cs:olddosint ; jmp to old interrupt handler
Back:
pushf ; save flags returned by orig int 21h
; Do something here.
cli ; clean up the stack
push ax ; so that it will ret to the
push bp ; correct place
mov bp,sp ;
mov ax,[bp-6] ;
mov [bp-8],ax ;
mov ax,[bp-4] ;
mov [bp-6],ax ;
pop bp ;
pop ax ;
popf ; restore flags ret'd by orig 21h
sti ;
retf 2 ;
Install:
mov ax,3508h ; Save old interrupt vector 21h
int 21h
mov [olddosint],bx
mov [olddosint+2],es
push cs
pop ds
mov dx,offset cs:newdosint ; set new interrupt vector 21h
mov ax,2508h ; to newdosint
int 21h
mov es,cs:[002Ch] ; free environment block
mov ah,49h
int 21h
mov dx,offset cs:Install ; get paragraphs needed
sub dx,offset cs:Start
shr dx,04
add dx,17 ; add 16 paras for PSP + 1 extra
mov ax,3100h ; termination and stay resident
int 21h
.end start
-
February 19th, 2011, 09:15 PM
#2
Re: Hooking the system timer interrupt in DOS?
I don't think that a "slowdown utility" is an appropriate toy for first TSR experiments. Though its task looks trivial on first sight, its beaviour actually depends on several border conditions not under your control and there's much more to observe than you probably think.
Also, you're trying to hook INT 08h which is one of the real hardware interrupts and hooking them involves some extra considerations, mostly regarding the hardware PIC. At least you should call the original interrupt handler at the end of your routine, delegating the hardware handling to it. But in case of the ticker interrupt this actually isn't even required in most cases: As they anticipated many developers would want to hook it, the BIOS creators introduced INT 1Ch which actually is a typical software interrupt but gets called by the INT 08h hardware interrupt handler on every tick. It is considerably easier to implement.
Actually, I can't fully comprehend the misbehaviour of your program either. It looks like some kind of infinite recursion, though. Are you aware that a program using the tiny memory model needs to reside in a .com file instead of an .exe file? Failure to observe this results in an address-mess-up that well can be the cause of such mischief. The ML assemler program has a command line option /AT to instruct it to generate a .com file. Did you use it? (This is assuming you are using MASM which is suggested by the syntax used in your assembly program.)
I don't undersand that "clean up the stack" code either. You're indexing down the stack there (using negative offsets from BP), accessing stack space that already has been released. You can't safely make any assumptions about what's stored there under normal circumstances and therefore it makes no sense in most cases. Too bad you didn't write that code yourself and thus know what you intended...
I actually have written several TSRs myself in the past but I don't have them anymore. Most of them would probably have been too complex for an introductory example anyway.
I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.
This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.
-
February 19th, 2011, 10:22 PM
#3
Re: Hooking the system timer interrupt in DOS?
Originally Posted by Eri523
I don't think that a "slowdown utility" is an appropriate toy for first TSR experiments. Though its task looks trivial on first sight, its beaviour actually depends on several border conditions not under your control and there's much more to observe than you probably think.
Also, you're trying to hook INT 08h which is one of the real hardware interrupts and hooking them involves some extra considerations, mostly regarding the hardware PIC. At least you should call the original interrupt handler at the end of your routine, delegating the hardware handling to it. But in case of the ticker interrupt this actually isn't even required in most cases: As they anticipated many developers would want to hook it, the BIOS creators introduced INT 1Ch which actually is a typical software interrupt but gets called by the INT 08h hardware interrupt handler on every tick. It is considerably easier to implement.
Actually, I can't fully comprehend the misbehaviour of your program either. It looks like some kind of infinite recursion, though. Are you aware that a program using the tiny memory model needs to reside in a .com file instead of an .exe file? Failure to observe this results in an address-mess-up that well can be the cause of such mischief. The ML assemler program has a command line option /AT to instruct it to generate a .com file. Did you use it? (This is assuming you are using MASM which is suggested by the syntax used in your assembly program.)
I don't undersand that "clean up the stack" code either. You're indexing down the stack there (using negative offsets from BP), accessing stack space that already has been released. You can't safely make any assumptions about what's stored there under normal circumstances and therefore it makes no sense in most cases. Too bad you didn't write that code yourself and thus know what you intended...
I actually have written several TSRs myself in the past but I don't have them anymore. Most of them would probably have been too complex for an introductory example anyway.
Thank you for a very useful post.
I did indeed fail to realize that I should obviously have assembled this as a COM file. However that didn't help, I get a slightly different garbled output but still a continuous one -- it seems to be random garbage from some memory location instead of the prompt now (when I'm hooking the keyboard).
I'm using TASM on this computer, I had to change the syntax slightly on a few lines to make it assemble.
I'm assembling and linking with the following commands:
Code:
tasm hook.asm
tlink /t hook.obj
Have I forgotten anything else trivial?
Last edited by getpagesize; February 19th, 2011 at 10:28 PM.
-
February 19th, 2011, 10:56 PM
#4
Re: Hooking the system timer interrupt in DOS?
The complete program is as follows (edited to assemble on TASM):
Code:
.model tiny
.186
.data
olddosint dw 00h,00h
.code
org 100h
start:
jmp short Install
newdosint:
pushf
push cs
push offset Back
jmp far [cs]:olddosint
Back:
sti
iret
Install:
mov ax, 351Ch
int 21h
mov [olddosint], bx
mov [olddosint+2], es
push cs
pop ds
mov dx, offset cs:newdosint
mov ax, 251Ch
int 21h
mov es, cs:[002Ch]
mov ah, 49h
int 21h
mov dx, offset cs:Install
sub dx, offset cs:Start
shr dx, 04
add dx, 17
mov ax, 3100h
int 21h
end start
I have also tried with "ret" and "retf 2" instead of "iret".
I have also tried to include the "stack cleanup" code.
The result is the same.
Although of interest is perhaps that:
When I hook the keyboard, I am able to do a hot reboot with ctrl+alt+delete and the rate at which the garbage is printed is affected by pressing keys.
When I hook 1Ch I sometimes experience that the system locks completely, with no error, and sometimes I get a divide error and then it locks completely.
Last edited by getpagesize; February 19th, 2011 at 10:58 PM.
-
February 21st, 2011, 06:23 PM
#5
Re: Hooking the system timer interrupt in DOS?
I had several looks at the code in your most recent post since you posted it and am still missing the striking idea. But I have some things you might simply try out...
Originally Posted by getpagesize
I have also tried with "ret" and "retf 2" instead of "iret".
The RET is definitely wrong. Even if it is inside a far proc and therefore effectively gets assembled to a RETF, it will leave the flags pushed by the caller on the stack. This would never work.
Whether RETF 2 or IRET is the right choice depends on the situation your interrupt routine is called in.
The RETF 2 is indicated if you want to return some info to the caller in the flags, e.g. if you're writing an INT 21H handler. This RETF 2 is usually preceded by STI, in particular if your handler is supposed to be the outermost INT handler (which is the common case), in order not to let the calling app continue with disabled interrupts all the way. In scenarios like this it may even be appropriate to have the STI at the beginning of your handler instead of the end. But this pattern is never used for captured hardware interrupts.
IRET is the instruction of choice when handling a hardware interrupt because it restores the caller's flags and this is important in this scenario because you'll never know what was being executed when the interrupt was triggered. An STI just before an IRET might seem redundant because the flags will instantly be overwritten by the IRET anyway, but if there's an interrupt pending in the PIC it will get fired immideately which may cause havoc if your handler has only been called as a nested handler from an outer hardware int handler.
And this is what might screw up your program when you're hooking INT 1Ch. I would suggest you remove the STI and return with an IRET. The INT 1Ch is a software interrupt, but it doesn't need to return anything in the flags so IRET is ok. You'll never know at which stage the INT 08h handler calls INT 1Ch. I've never seen any documentation about this detail and I'm afraid it isn't even standardized at all, so chances are that different BIOSes will not handle this consistently. So it's safer to assume that re-enabling interrupts in the INT 1Ch handler is unsafe, due to the reasons pointed out above.
How to handle the keyboard interrupt depends on what exactly you mean by "hook the keyboard". There's INT 09h which is a hardware interrupt and INT 16h which is a software interrupt implementing a BIOS API. The two have quite different requirements.
I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.
This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.
-
February 22nd, 2011, 12:48 PM
#6
Re: Hooking the system timer interrupt in DOS?
Originally Posted by Eri523
I had several looks at the code in your most recent post since you posted it and am still missing the striking idea. But I have some things you might simply try out...
The RET is definitely wrong. Even if it is inside a far proc and therefore effectively gets assembled to a RETF, it will leave the flags pushed by the caller on the stack. This would never work.
Whether RETF 2 or IRET is the right choice depends on the situation your interrupt routine is called in.
The RETF 2 is indicated if you want to return some info to the caller in the flags, e.g. if you're writing an INT 21H handler. This RETF 2 is usually preceded by STI, in particular if your handler is supposed to be the outermost INT handler (which is the common case), in order not to let the calling app continue with disabled interrupts all the way. In scenarios like this it may even be appropriate to have the STI at the beginning of your handler instead of the end. But this pattern is never used for captured hardware interrupts.
IRET is the instruction of choice when handling a hardware interrupt because it restores the caller's flags and this is important in this scenario because you'll never know what was being executed when the interrupt was triggered. An STI just before an IRET might seem redundant because the flags will instantly be overwritten by the IRET anyway, but if there's an interrupt pending in the PIC it will get fired immideately which may cause havoc if your handler has only been called as a nested handler from an outer hardware int handler.
And this is what might screw up your program when you're hooking INT 1Ch. I would suggest you remove the STI and return with an IRET. The INT 1Ch is a software interrupt, but it doesn't need to return anything in the flags so IRET is ok. You'll never know at which stage the INT 08h handler calls INT 1Ch. I've never seen any documentation about this detail and I'm afraid it isn't even standardized at all, so chances are that different BIOSes will not handle this consistently. So it's safer to assume that re-enabling interrupts in the INT 1Ch handler is unsafe, due to the reasons pointed out above.
How to handle the keyboard interrupt depends on what exactly you mean by "hook the keyboard". There's INT 09h which is a hardware interrupt and INT 16h which is a software interrupt implementing a BIOS API. The two have quite different requirements.
Thanks again for the advice.
I tried to remove the STI prior to the IRET but there seems to still be something seriously wrong.
I get a "Divide overflow" when I execute the program and the system subsequently locks up completely.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|