CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 6 of 6

Thread: Hooking the system timer interrupt in DOS?

  1. #1
    Join Date
    Jan 2010
    Posts
    20

    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

  2. #2
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,675

    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.

  3. #3
    Join Date
    Jan 2010
    Posts
    20

    Re: Hooking the system timer interrupt in DOS?

    Quote Originally Posted by Eri523 View Post
    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.

  4. #4
    Join Date
    Jan 2010
    Posts
    20

    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.

  5. #5
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,675

    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...

    Quote Originally Posted by getpagesize View Post
    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.

  6. #6
    Join Date
    Jan 2010
    Posts
    20

    Re: Hooking the system timer interrupt in DOS?

    Quote Originally Posted by Eri523 View Post
    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
  •  


Windows Mobile Development Center


Click Here to Expand Forum to Full Width




On-Demand Webinars (sponsored)