Back to home page

DOS ain't dead

Forum index page

Log in | Register

Back to index page
Thread view  Board view
Laaca

Homepage

Czech republic,
14.08.2021, 07:31
 

How to keep resident only a part of the program (Developers)

I am thinking about very tiny resident utility which hooks the INT16 interrupt. I know that "high-level" languages like pascal or C have something like function Keep (which apparently calls INT21h/AH=31h). But this function keep a whole program in memory.
My problem is that the program must be quite big and complex (it performs some hardware detections and scanning). But after gathering all needed informations is possible to keep in memory only very small module.
All examples about KEEP function look similarly:

{ Keep.PAS }
 {$M $800,0,0 }   { 2K stack, no heap }
 { This program makes a click after each keypress.}
 uses Crt, Dos;
 var
   KbdIntVec : Procedure;

 {$F+}
 procedure Keyclick; interrupt;
 begin
   if Port[$60] < $80 then
     { Clicks after keypress }
     begin
     Sound(5000);
     Delay(1);
     Nosound;
     end;

   inline ($9C); { PUSHF -- Push flags }
   { Call old driver }
   KbdIntVec;
 end;
 {$F-}

 begin
   { Insert into interrupt chain }
   GetIntVec($9,@KbdIntVec);
   SetIntVec($9,Addr(Keyclick));
   Keep(0); { Teminate and keep resident }
 end.


This program works but it remains all int the memory. I would like to keep only the procedure KeyClick but nothing more.

Is possible solution to allocate a additional memory block via INT21h/AH=48h, copy the code of KeyClick into this block and the program end not with Keep but hith Halt?

---
DOS-u-akbar!

tkchia

Homepage

14.08.2021, 08:49
(edited by tkchia, 14.08.2021, 09:02)

@ Laaca
 

How to keep resident only a part of the program

Hello Laaca,

> I am thinking about very tiny resident utility which hooks the INT16
> interrupt. I know that "high-level" languages like pascal or C have
> something like function Keep (which apparently calls INT21h/AH=31h). But

Actually the int 0x21, ah = 0x31 interface does allow one to specify how many paragraphs of memory one wants to keep resident. Turbo C and Watcom C also accept a paragraph count as a parameter to keep(...) and (respectively) _dos_keep(...).

But it might be hard to use this correctly in the context of high-level code. You need to arrange your program binary code in such a way that the resident portions will come before (most of) the transient portions (and you must take library functions into account too...). I am not sure what features Free Pascal (e.g.) has that might facilitate this sort of thing.

> Is possible solution to allocate a additional memory block via
> INT21h/AH=48h, copy the code of KeyClick into this block and the program
> end not with Keep but hith Halt?

That will likely not work --- DOS will free up all memory blocks allocated by your program, including those from int 0x21, ah = 0x48.

Thank you!

---
https://gitlab.com/tkchia · https://codeberg.org/tkchia · 😴 "MOV AX,0D500H+CMOS_REG_D+NMI"

Laaca

Homepage

Czech republic,
14.08.2021, 10:32

@ tkchia
 

How to keep resident only a part of the program

> Actually the int 0x21, ah = 0x31 interface does allow one to specify how
> many paragraphs of memory one wants to keep resident. Turbo C and Watcom C
> also accept a paragraph count as a parameter to keep(...) and
> (respectively) _dos_keep(...).
>
> But it might be hard to use this correctly in the context of high-level
> code.

Exactly. I saw in documentatin that INT 21h/AH=31h has a parameter "number of parameters to keep resident" but I don't know how to use it in the pascal code. BTW, in pascal has the Keep function only argument for the return code.

But I know that many assembler programs CAN do it. They use the INT 21h/AH=31h call and determine which portion of code to thow away and which to keep? Or they use some other mechanism?

---
DOS-u-akbar!

tom

Homepage

Germany (West),
14.08.2021, 11:40

@ Laaca
 

How to keep resident only a part of the program

> But I know that many assembler programs CAN do it. They use the INT
> 21h/AH=31h call and determine which portion of code to thow away and which
> to keep? Or they use some other mechanism?

usually, you arrange your code layout 'manually' like


resident_code
resident_stuff
transient_code
more_stuff


and make sure that resident_stuff never calls anything in more_stuff after going resident. in ASM this easy, in C at least doable.

for a PASCAL example, you might look at the sources of FreeDOS KEYB.EXE hich I think is written in (some strange sort of) Pascal.

RayeR

Homepage

CZ,
14.08.2021, 15:49

@ tom
 

How to keep resident only a part of the program

> and make sure that resident_stuff never calls anything in more_stuff after
> going resident. in ASM this easy, in C at least doable.

I see problem there when resident code would call anything from runtime library that would be freed then. To avoid this probably the best to write TSR part in inline ASM or check compiled code if TSR part doesn't have any depencies.

---
DOS gives me freedom to unlimited HW access.

Laaca

Homepage

Czech republic,
14.08.2021, 20:18

@ RayeR
 

How to keep resident only a part of the program

> I see problem there when resident code would call anything from runtime
> library that would be freed then. To avoid this probably the best to write
> TSR part in inline ASM or check compiled code if TSR part doesn't have any
> depencies.

It is no problem at all. I plan to write the TSR part in inline assembler.

But I am considering another attitude. At first to load tiny assembler driver hooking the keyboard interrupt. Then to call a heavy pascal program which will locate the TSR module in memory and patch the code. (kind of self-modyfying code)

---
DOS-u-akbar!

RayeR

Homepage

CZ,
16.08.2021, 03:22

@ Laaca
 

How to keep resident only a part of the program

> But I am considering another attitude. At first to load tiny assembler
> driver hooking the keyboard interrupt. Then to call a heavy pascal program
> which will locate the TSR module in memory and patch the code. (kind of
> self-modyfying code)

Yes, it could be possible but you will have to fit in preallocated space. Maybe you can allocate more at the beginning and then free memory that is not longer needed but it will cause some fragmentation.

---
DOS gives me freedom to unlimited HW access.

tom

Homepage

Germany (West),
14.08.2021, 11:28

@ tkchia
 

How to keep resident only a part of the program

> Hello Laaca,
>
> > I am thinking about very tiny resident utility which hooks the INT16
> > interrupt. I know that "high-level" languages like pascal or C have
> > something like function Keep (which apparently calls INT21h/AH=31h). But
>
> Actually the int 0x21, ah = 0x31 interface does allow one to specify how
> many paragraphs of memory one wants to keep resident. Turbo C and Watcom C
> also accept a paragraph count as a parameter to keep(...) and
> (respectively) _dos_keep(...).
>
> But it might be hard to use this correctly in the context of high-level
> code. You need to arrange your program binary code in such a way that the
> resident portions will come before (most of) the transient portions (and
> you must take library functions into account too...). I am not sure what
> features Free Pascal (e.g.) has that might facilitate this sort of thing.
>
> > Is possible solution to allocate a additional memory block via
> > INT21h/AH=48h, copy the code of KeyClick into this block and the program
> > end not with Keep but hith Halt?
>
> That will likely not work --- DOS will free up all memory blocks
> allocated by your program, including those from int 0x21, ah = 0x48.

while true (this wouldn't work), you can make it work by using the undocumented feature that DOS_realloc(para, size) also sets the PSP of the allocated memory, and on exit(), DOS will only free allocated memory with your PSP.

so a sequence like

needed_bytes = 1024;

resident_seg = DOS_alloc(needed_bytes);

my_PSP = DOS_GET_PSP();

DOS_SET_PSP(0x60); program 0x60 will never exit

DOS_realloc(resident_seg, needed_bytes);

DOS_SET_PSP(my_PSP);

memcpy(MK_FP(resident_seg,0), my code, needed_bytes);

point interrupt vector to the copied code
...

exit()


would work in principle (remove the large program from memory, leave needed_bytes in memory).

of course, your code would really quickly implode because you are calling the runtime library (sound(), delay()) and you have little to no control over where the runtime will be loaded.

time to change the language:-D

Pascal is just not the right language for this kind of stuff.

ecm

Homepage E-mail

Düsseldorf, Germany,
15.08.2021, 13:47

@ tkchia
 

How to keep resident only a part of the program

> > Is possible solution to allocate a additional memory block via
> > INT21h/AH=48h, copy the code of KeyClick into this block and the program
> > end not with Keep but hith Halt?
>
> That will likely not work --- DOS will free up all memory blocks
> allocated by your program, including those from int 0x21, ah = 0x48.

You can make it work by hacking the MCB to be owned by someone other than your process. Tom mentions a way to do that but I think it is cleaner to just access the MCB directly instead of setting the current PSP to another, possibly invalid value.

In my tsr example I first relocate the process (to avoid memory fragmentation) then allocate a new block for the resident portion using service 48h, then I hack the MCB's owner and name fields to make the resident block survive the final process termination (using service 4Ch). I described this in some detail in https://retrocomputing.stackexchange.com/questions/20001/how-much-of-the-program-segment-prefix-area-can-be-reused-by-programs-with-impun/20006#20006

By the way, if you do use service 31h you should close all file handles manually, unless you intend to use them while resident.

---
l

bretjohn

Homepage E-mail

Rio Rancho, NM,
27.08.2021, 21:22

@ ecm
 

How to keep resident only a part of the program

I use ecm's TSR installation method (or at least a slight variation of it) in most of my TSR's nowadays. It works really well. It even allows you to automatically install the program into upper memory without needing to use LOADHI. I'm not sure you could do it (at least not easily) in anything other than ASM, though. ASM is more difficult to use, but can give you complete control over everything, including the relationships of which parts of the program get installed in which parts of memory.

I will say that INT 21.31 is probably the easiest way to install a TSR, but it is not the best option (by far).

marcov

14.08.2021, 13:18

@ Laaca
 

How to keep resident only a part of the program

> I am thinking about very tiny resident utility which hooks the INT16
> interrupt. I know that "high-level" languages like pascal or C have
> something like function Keep (which apparently calls INT21h/AH=31h). But
> this function keep a whole program in memory.
> My problem is that the program must be quite big and complex (it performs
> some hardware detections and scanning). But after gathering all needed
> informations is possible to keep in memory only very small module.
> All examples about KEEP function look similarly:

Did you walk through the TSR section of SWAG ?

CandyMan

27.08.2021, 17:46

@ Laaca
 

How to keep resident only a part of the program

If you want to write resident programs then use assembler. Here is my old program, replacement for the DOS keyboard driver, written in fasm. The resident part is between KeyStart and KeybSize.
Here code:
                org     100h
                mov     dx,Information+1
                call    Print
                mov     ax,3509h
                int     21h
                mov     word [KeyStart+Old09h+0],bx
                mov     word [KeyStart+Old09h+2],es

                mov     ah,$AD          ; Disable Controller
                call    SetStat

                in      al,21h          ;\
                or      al,00000010b    ;  > Disable IRQ1
                out     21h,al          ;/

                mov     ah,20h          ; Read Command Byte
                call    GetEcho
                push    ax

                mov     ah,$AA          ; Keyboard Self Test
                call    GetEcho
                jnz     NotAnswer
                cmp     al,55h
                jnz     NotAnswer

                mov     ah,$F4          ; Enable Keyboard
                call    SetOut

                mov     ah,$F3          ; Set Typematic Rate&Delay
                call    SetOut
                mov     ah,0            ; Max Rate&Delay
                call    SetOut

NotAnswer:      pop     bx              ; Restore Command Byte
                or      bl,1
                mov     ah,60h          ; Set Command Byte
                call    SetEcho

                mov     ah,$AE          ; Enable Controller
                call    SetStat

                in      al,21h          ;\
                and     al,11111101b    ;  > Enable IRQ1
                out     21h,al          ;/

                call    GetMem
                jc      Quit
                xor     di,di
                mov     cx,KeybSize
                mov     si,KeyStart
                rep movsb

                push    es
                pop     ds

                mov     ax,2509h
                mov     dx,New09h
                int     21h
                ret
Quit:           mov     dx,NoMem
Print:          mov     ah,9
                int     21h
                ret

Allocate:       mov     ah,48h
                mov     bx,Paragraphs
                int     21h
                ret

GetMem:         mov     ax,5802h
                int     21h
                xor     ah,ah
                mov     si,ax
                cmp     al,1
                je      LoadUpper
                mov     ax,5803h
                mov     bx,1
                int     21h
                jc      NoUpper
LoadUpper:      call    Allocate
                pushf
                push    ax
                mov     ax,5803h
                mov     bx,si
                int     21h
                pop     ax
                popf
                jnc     IsOK

NoUpper:        mov     ah,4Ah
                mov     bx,-1
                push    ds
                pop     es
                int     21h
                sub     bx,Paragraphs+1
                jc      MemEnd
                mov     ah,4Ah
                int     21h
                jc      MemEnd
                call    Allocate
                jc      MemEnd
IsOK:           push    ax
                dec     ax
                mov     es,ax
                inc     ax
                mov     di,1
                cld
                stosw
                add     di,5
                mov     si,Information
                movsw
                movsw
                movsw
                movsw
                pop     es
MemEnd:         ret

; Wait Not Data in port 60h\64h
WaitNoData:     xor     cx,cx
.WaitRetry:     in      al,64h
                out     $EB,al
                test    al,00000010b
                jz      .Ok
                loop    .WaitRetry
                inc     cx
.Ok:            ret

; Wait No TimeOut (Data transmit completed)
WaitNoTime:     xor     cx,cx
.WaitTime:      in      al,64h
                test    al,00100000b
                jz      .Ok
                loop    .WaitTime
                inc     cx
.Ok:            ret

; Wait Data in port 60h
WaitOutput:     mov     ah,0Ch
.CheckAgain:    xor     cx,cx
.CheckOutput:   in      al,64h
                out     $EB,al
                test    al,00000001b
                jnz     .IsOutput    ; Ok!
                loop    .CheckOutput
                dec     ah           ; \ Wait error!
                jnz     .CheckAgain  ; / Wait again
.IsOutput:      lahf                 ; \
                xor     ah,40h       ;   > Negate ZF
                sahf                 ; /
                ret

; Set Command in AH to Status Register
SetStat:        cli
                call    WaitNoData
                jnz     .Err
                mov     al,ah
                out     64h,al
                call    WaitNoData
.Err:           sti
                ret

; Set Command in AH to Output Register
SetOut:         cli
                call    WaitNoTime
                jnz     .Err
                in      al,60h
                call    WaitNoData
                jnz     .Err
                mov     al,ah
                out     60h,al
                call    WaitNoData
                call    WaitOutput
                jnz     .Err
                in      al,60h
                cmp     al,$FA
.Err:           sti
                ret

; Set Command in AH to Status Register &
; get echo\command from Output Register
GetEcho:        cli
                call    SetStat
                jnz     .Err
                call    WaitOutput
                jnz     .Err
                in      al,60h
.Err:           sti
                ret

; Set Command in AH to Status Register &
; set echo\command in BL from Output Register
SetEcho:        cli
                call    SetStat
                jnz     .Err
                call    WaitNoData
                jnz     .Err
                mov     al,bl
                out     60h,al
                call    WaitNoData
.Err:           sti
                ret

ReadInput:      cli
                mov     bx,$FF0F
                call    WaitNoData
                jnz     .Err
.Retry:         mov     ah,$C0
                call    GetEcho
                jnz     .Err
                cmp     al,bh
                je      .Ok
                mov     bh,al
                xor     cx,cx
                loop    $
                dec     bl
                jnz     .Retry
.Err:           inc     cx
.Ok:            sti
                ret

Information     db      '$KEYB-PL v1.1',13,10,36
NoMem           db      13,10,'Not enough memory',13,10,36

KeyStart:       org     0
KeyTable:       db      $1E,$2E,$12,$26,$31,$18,$1F,$2C,$2D
                db      $A5,$86,$A9,$88,$E4,$A2,$98,$BE,$AB
                db      $A4,$8F,$A8,$9D,$E3,$E0,$97,$BD,$8D
New09h:         push    ax bx ds si
                push    40h
                pop     ds
                mov     ax,[1Ch]
                mov     si,ax
                inc     ax
                inc     ax
                cmp     ax,[82h]
                jne     @1
                mov     ax,[80h]
@1:             cmp     ax,[1Ah]
                je      Return
                xchg    bx,ax

                mov     ax,[17h]
                test    al,8
                je      Return
                xchg    ah,al
                test    al,2
                jne     Return

                in      al,60h
                cmp     al,31h  ; Max z KeyTable
                ja      Return
                push    di
                push    cx
                push    es
                push    cs
                pop     es
                cld
                mov     cx,9
                xor     di,di   ; mov di,KeyTable
                repnz scasb
                pop     es
                pop     cx
                jne     Exit
                xchg    ah,al

                test    al,40h
                je      @2
                and     al,3    ; test al,3
                jne     @4
                jmp     @3
@2:             and     al,3    ; test al,3
                je      @4
@3:             add     di,9
@4:             cbw             ; xor ah,ah
                mov     al,[cs:di+8]
                mov     [si],ax
                mov     [1Ch],bx
                mov     al,20h
                out     20h,al
                pop     di si ds bx ax
                iret
Exit:           pop     di
Return:         pop     si ds bx ax
                db      $EA
Old09h          dd      ?
KeybSize:
Paragraphs      = ($+15)/16

bretjohn

Homepage E-mail

Rio Rancho, NM,
27.08.2021, 21:47

@ Laaca
 

How to keep resident only a part of the program

From a performance perspective, this would not be a good way to implement a keyclick program. You have the sound being turned on, waiting, and turning off all inside an INT 09 (hardware IRQ) handler, which is not a good idea. The keyboard is a very high priority IRQ (the only one being higher is the clock at INT 08) and the machine will "stall" inside the IRQ 09 handler waiting for the sound to stop. This could cause some serious performance issues in other programs.

There are a few different ways around this. What may be the easiest/best in this situation would be to simply start the sound within the INT 09 handler and then stop it after a delay using one of the timer interrupts (INT 08 or INT 70h).

Laaca

Homepage

Czech republic,
08.03.2022, 12:24

@ Laaca
 

How to keep resident only a part of the program

I succeeded to solve this problem!
It is possible to create a big program in the Turbopascal and keep only a tiny TSR in the memory and unloaded the rest.
The practical implementation I used here.

The principle is to write a small assembler procedure which will be used as a TSR. In this procedure keep a area which will be modified on the fly to store there some data.
Then move this procedure/module just after PSP of the main program. (PSP+256)
Now is it a little bit dangerous because a part of the pascal system library is overwritten so it is not safe to call many RTL functions.
In assembler redirect the calling interrupt vector to PSP+256
Then call the DOS function 31h and into DX store the 16+TSR_size/16.
And it works :-)

The question is: Do I need to keep the PSP? Is it safe to perform the relocation not to PSP+256 but to PSP+128? Or to PSP+0?

---
DOS-u-akbar!

tom

Homepage

Germany (West),
08.03.2022, 12:51

@ Laaca
 

How to keep resident only a part of the program

> The question is: Do I need to keep the PSP?
yes, at least part of it is used on exit() 'terminate program'
lime parent PSP, INT 23 address and similar.


> Is it safe to perform the
> relocation not to PSP+256 but to PSP+128? Or to PSP+0?
PSP+80 should be enough as you probably don't use FCB's, and have used your command line (@PSP+128) a long time ago.

tkchia

Homepage

08.03.2022, 14:20

@ tom
 

How to keep resident only a part of the program

Hello Laaca, hello tom,

> > Is it safe to perform the
> > relocation not to PSP+256 but to PSP+128? Or to PSP+0?
> PSP+80 should be enough as you probably don't use FCB's, and have used your
> command line (@PSP+128) a long time ago.

Actually, if the program does not use FCBs, one can probably throw away everything above PSP:0x5c (where the first default FCB starts).

(Or perhaps even everything above PSP:0x50. There is a "int 0x21; retf" sequence at PSP:0x50, but other than that there is nothing between PSP:0x50 and PSP:0x5c.)

Thank you!

---
https://gitlab.com/tkchia · https://codeberg.org/tkchia · 😴 "MOV AX,0D500H+CMOS_REG_D+NMI"

bretjohn

Homepage E-mail

Rio Rancho, NM,
08.03.2022, 19:19

@ tkchia
 

How to keep resident only a part of the program

As far as to how much of the PSP you need to keep after the TSR is installed, the answer is usually 0. Except in very rare cases, the PSP is only used while the TSR is being installed but is not used by the TSR itself. So, you can overwrite the PSP with whatever you want. I've used the PSP for various different things in my TSR's, the most useful probably being as a TSR Stack (complicated TSR's should always provide their own stack space).

You do need to make sure you don't overwrite the PSP before the TSR is actually installed, though. That can be a little tricky in high-level languages like Pascal since you don't necessarily know exactly when or how the PSP is used.

rr

Homepage E-mail

Berlin, Germany,
08.03.2022, 17:32

@ Laaca
 

How to keep resident only a part of the program

> I succeeded to solve this problem!

:yes:

> It is possible to create a big program in the Turbopascal and keep only a
> tiny TSR in the memory and unloaded the rest.
> The practical implementation I used here.
>
> The principle is to write a small assembler procedure which will be used as
> a TSR. In this procedure keep a area which will be modified on the fly to
> store there some data.
> Then move this procedure/module just after PSP of the main program.
> (PSP+256)
> Now is it a little bit dangerous because a part of the pascal system
> library is overwritten so it is not safe to call many RTL functions.
> In assembler redirect the calling interrupt vector to PSP+256
> Then call the DOS function 31h and into DX store the 16+TSR_size/16.
> And it works :-)

A bit late, because you already solved it, but https://github.com/FDOS/keyb might be of interest.

---
Forum admin

Back to index page
Thread view  Board view
22049 Postings in 2034 Threads, 396 registered users, 124 users online (0 registered, 124 guests)
DOS ain't dead | Admin contact
RSS Feed
powered by my little forum