From: raxco!galaxy.dnet!gleeve@uunet.UU.NET
Newsgroups: vmsnet.internals
Subject: Proposed methods for stealing DDT/FDT/etc.
Date: 10 Sep 92 21:21:31 GMT
Organization: Macro32<==>Vmsnet.Internals Gateway
X-Gateway-Source-Info: Mailing List

Subj:   RE: moving DDT for CDdriver etc.

   When building I/O intercept drivers to steal driver entry
points, it is a problem to figure out how to tell a bit of intercept
code where its' data is. If the intercept code is inside a device
driver, one would like to find the driver UCB, but this is difficult
in general. One can build a hash table of some sort, or perhaps
search the I/O database for the unit of your intercept driver that
points to the "victim" device, or use some otherwise unused UCB
fields in the victim driver to point to the intercept UCB. These
methods are either slow or vulnerable to collisions with other
code, however.
   Paul Sorensen made the suggestion to put a DDT copy physically
into the UCB of the intercept driver so the victim device UCB
pointer ucb$l_ddt will now point to a known offset within the intercept
UCB. This gives an apparently robust way of locating the intercept
UCB for one interceptor. The present discussion has to do with some
extensions to the idea so that multiple intercept drivers handling
I/O to a single device can be handled.
   Such suggestions must be public and freely usable by anyone or
they are useless. The suggestions here can be used for stealing
FDT entries or start-io entries of normal drivers, and for other
entry stealing where it is of any interest.
   While the code is not tested yet, I am offering it for general
interest now rather than later; I expect to use it myself shortly
and will correct any bugs I find. The ideas came from me, from Jon
Pinkley, and from Paul.

   The idea of pointing ucb$l_ddt at a DDT that physically resides
in a UCB of some other device like CDdriver is a wonderful way to
make finding the corresponding device fast and reliable.
   It suffers from the disadvantage that it works for ONE driver
only as proposed. That is, if I come along and try to steal the
DDT again and happen to use the original driver's UCB$L_DDT
link, the new DDT winds up in MY UCB, and CDdriver will find the
DDT in the wrong place.
   The example code here involves some chaining, but should scale
well and provide fairly fast access to intercept data, while not
breaking every time a second unrelated application tries to patch
the same driver structures.

   Where a DDT or any of its fields need to be modified, the suggestion
here is to put a copy of that DDT into the UCB of the intercept driver
that holds the code that is being inserted. Using a driver for such
patched code makes it very simple to track where the code is, and the
UCB of the driver provides a convenient spot for locating data that
must be kept track of for each unit. Process-specific or channel
specific structures' listheads can also be kept there.
   This note is offered to the public in the hope that a standard
can be worked out, or that it can be used as one, as is, so that
code that must steal DDTs or FDTs can interoperate no matter who
the author is. So far Jon Pinkley and Glenn Everhart have contributed
to the macro32-list discussions on this, and the code examples are
provided by one of us (gce) to assist in the use of the ideas.

   The examples would live as pieces of a device driver for some
intercept device. The relevant parts of UCB extension definitions,
of the DPT_STORE macro, and the bits of code are provided here.
These bits of code serve to steal a DDT from some victim driver and
place it in the UCB of the intercept device, to locate the intercept
UCB when executing intercept code with R5 pointing to the victim
device's UCB, and to remove an intercepted DDT from the chain of
these when it is necessary to disconnect. The FDT code replacement
is embedded in the example code, so it can be seen how such FDT
additions can be made using these methods. In practice the DDT and
the FDT are obtained via the UCB pointer, which is why the DDB
pointer is left intact.

                        NOTE!!!

   THIS CODE IS ALPHA EXAMPLES at this point, not final and tested
code. It is intended to be approximately correct, but another note
will be issued when it is tested. In particular it needs to have
"XX" replaced by proper device name characters here and there,
and needs several more $xxxDEF calls. It may also need assorted
bugfixes.


   The notion is to produce a UCB extension that looks like this:

        .long   ident-pattern        ; unique to driver that owns this ucb
        .long   intercept-DDT-address; This is modified by another intercept
                                     ; driver that intercepts the victim UCB
                                     ; after we do.  This allows us to find
                                     ; the our interceptor's DDT.
        .long   previous-DDT-address ; where to find the next startio addr
        .long   intercept-signature   ; same for all intercept drivers
ucb$a_victimddt:
        .blkb   ddt$k_length         ; note this changed in V5.5

As a particular example, one might have a UCB extension that would look
like this:

        $ucbdef

        $defini UCB
.=ucb$k_lcl_disk_length
;   other $def macros as desired
;
; DDT intercept fields
; following must be contiguous.
$def    ucb$l_uniqid    .blkl   1       ;driver-unique ID, gets filled in
                                        ; by DPT address for easy following
                                        ; by SDA
$def    ucb$l_intcddt   .blkl   1       ; Our interceptor's DDT address if
                                        ; we are intercepted
$def    ucb$l_prevddt   .blkl   1       ; previous DDT address
$def    ucb$l_icsign    .blkl   1       ; unique pattern that identifies
                                        ; this as a DDT intercept block
$def    ucb$a_vicddt    .blkb   ddt$k_length
                                        ; space for victim's DDT

        $defend UCB

; Then at the DPT_STORE macros, some like this after the
; DPT_STORE REINIT call.

        DPT_STORE UCB,UCB$L_UNIQID,L,XX$DPT     ;store DPT address
                                                ; (change "XX" to device
                                                ; mnemonic correct values)
        DPT_STORE UCB,UCB$L_ICSIGN,L,X^F012F024 ; Add unique pattern (that might
                                                ; bring back some memories in
                                                ; DOS-11 users)

; HISTORICAL NOTE: under DOS-11, one would get F012 and F024 errors
; on odd address and illegal instruction traps. If we don't have
; this magic number HERE, on the other hand, we're likely to see
; bugchecks in VMS due to uncontrolled bashing of UCB fields!

The driver dispatch table points at various driver addresses. If
this sort of thing is used, one should be aware that reloads of
the victim driver will cause trouble, and reloads of intercept
drivers can. It is probably wise to inhibit reloading of drivers
whose DDTs are moved by this sort of code. The same is probably wise
for intercept drivers unless they can be VERY careful about their
FDT fields, and those of anyone who has stolen them, and about
other entries, when they are reloaded. (If I reset the DDT FDT
pointer (ddt$l_fdt) to point into my intercept driver, someone
intercepts that and gets a pointer to my FDT code, and then I
reload my intercept driver and move the FDT code, clearly the
external pointer to me can cause problems. I can however unlink
everything and relink later if my driver is reloaded.)


   With such a standard in force, if I find the DDT from ucb$l_ddt
of the victim device, I can check ident-pattern against my
own program's pattern, and if it matches I just go ahead and use
offsets from there to find my driver's UCB and thus its data
(e.g. the CDdriver UCB). If it does not match, I chain using
the previous-DDT-address and keep looking for my pattern, and
when I find it (in usually one or two tries at most), I have
found my UCB and can go on from there.
   The pattern choice is arbitrary, but in this example is set
to the DPT address since that makes SDA dumps show the intercept
driver name, facilitating its use.
        For such a standard to work, it must be used by anyone stealing
the DDT for whatever purpose. This note is written in the hope this
will be done and will impact user code as little as possible.

   I would suggest a similar strategy be used for stealing
FDT table bits also. These can be made to coexist rather nicely
if one has a structure somewhat along these lines for FDT routines
to be added to a driver's FDT chain. In the examples here, the
FDT chain just gets added to automatically in virtue of moving
the DDT and chains "by itself" because that is the nature of the
FDT scan.

 Note the identifier isn't really needed for functions but can be used
for manipulations of FDT code if the FDT pieces live outside a driver.
  Inside a driver they can be given the value of the DPT address as in
the example code here.

; offsets to FDT chaining data
fdt_prev = -4
fdt_idnt = -8
        .long   ident-pattern; unique for each application
        .long   old-fdt-table-address-from-before-patch
xx_functable:
newfdt: functab ,<mask for disk legal functions>
        functab ,<mask for disk buffered functions>
        functab myswitch,<mask of all 1's>
myfdtbgn=.
        functab mypatch1,<mask>
        functab mypatch2,<mask>
        ...
        functab mypatchn,<mask>
myfdtend=.
        functab to_orig,<mask of all 1's>

; myswitch -
;   Based on state of "myonoff" variable either enable or disable
; my FDT processing, allowing the FDT chain to remain always intact.
; This needs to be the first of a chain of FDT entries added to the
; FDT processing of a driver.
myswitch:
        tstl    myonoff
        bneq    1$
        rsb
1$:     addl2   #<myfdtend-myfdtbeg>,r8
        rsb

; to_orig -
;  This entry continues FDT processing at the point after the new
; entries by returning to the original FDT chain at the point where
; that chain begins. (It is presumed that FDT entries will always be
; added ahead of existing ones due to the nonreturning nature of
; FDT processing.) This is done instead of simply duplicating the
; DEC FDT entries because in this way multiple FDT patches can
; coexist, as would be impossible if this trick were not used. As
; can be seen, its overhead is minimal.
to_orig:
        movl    newfdt+fdt_prev,r8      ;point to original FDT point
        addl2   #<16-12>,r8      ;pass the 2 entry masks
                                 ;back up since sysqioreq adds 12
        rsb                      ;off to the previous FDT routines.
myonoff: .long   0       ;my global switch, can be before or
                         ;after the rest, doesn't matter since only
                         ;my code knows about it.





        Now in order for an intercept driver to steal entries from
a victim driver, it would use code something like the following,
in kernel mode. The initialization is outside the scope of the
discussion.

; steal DDT from host. Assumes that the intercept UCB address
; is in R5 (that is, the UCB in which we will place the DDT copy),
; and that the UCB of the device whose DDT we are stealing is
; pointed to by R11. All registers are preserved explicitly so that
; surrounding code cannot be clobbered. R0 is returned as a status
; code so that if it returns with low bit clear, it means something
; went wrong so the bash did NOT occur. This generally means some other
; code that does not follow this standard has grabbed the DDT already.
; The following example assumes the code lives in a driver so the
; unique ID field and magic number are set already.

        pushr   #^m<r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
; Acquire victim's fork lock to synchronize all this.
        movl    #ss$_normal,r0          ;assume success
        forklock lockaddr=ucb$b_flck(r11),-
         savipl=-(sp),preserve=YES
; find the current DDT address from the UCB (leaving the copy in
; the DDB alone)
        movl    ucb$l_ddt(r11),r10      ;point at victim's DDB
; see if this DDT is the same as the original
        movl    ucb$l_ddb(r11),r9       ;the ddb$l_ddt is the original
        cmpl    ddb$l_ddt(r9),r10       ;bashing driver the first time?
        beql    1$                      ;if eql yes
; driver was bashed already. Check that the current basher followed the
; standard. Then continue if it looks OK.
p.magic=x^F012F024
        cmpl    <ucb$l_icsign-ucb$a_vicddt>(r10),#p.magic
                                        ;does the magic pattern exist?
; if magic pattern is missing things are badly messed.
        beql    2$                      ;if eql looks like all's well
        movl    #2,r0                   ;say things failed
        brw     100$                    ;(brb might work too)
2$:
; set our new ddt address in the previous interceptor's slot
        movab   ucb$a_vicddt(r5),<ucb$l_intcddt-ucb$a_vicddt>(r10)
                                        ;store next-DDT address relative
                                        ;to the original victim one
1$:
        movl    r10,ucb$l_prevddt(r5)   ;set previous DDT address up
        clrl    ucb$l_intcddt(r5)       ;clear intercepting DDT initially
3$:
        pushl   r5
        movc3   #ddt$k_length,(r10),ucb$a_vicddt(r5)    ;copy the DDT
        popl    r5                      ;get UCB pointer back (movc3 bashes it)
;
; Here make whatever mods to the DDT you need to.
;
; FOR EXAMPLE make the following mods to the FDT pointer
; (These assume the standard proposed for FDT pointers)
        movab   ucb$a_vicddt(r5),r8     ;get a base register for the DDT
        movl    ddt$l_fdt(r10),xx_functable+fdt_prev    ;save old FDT address
        movl    ucb$l_uniqid(r5),xx_functable+fdt_idnt ;save unique ID also
        movab   xx_functable,ddt$l_fdt(r8) ;point at our FDT table
        clrl    myonoff                 ;turn my FDTs on
;
; Finally clobber the victim device's DDT pointer to point to our new
; one.
        movab   ucb$a_vicddt(r5),ucb$l_ddt(r11)
; Now the DDT used for the victim device unit is that of our UCB
; and will invoke whatever special processing we need. This processing in
; the example here causes the intercept driver's FDT routines to be
; used ahead of whatever was in the original driver's FDTs. Because
; the DDT is modified using the UCB pointer only, target device units
; that have not been patched in this way continue to use their old
; DDTs and FDTs unaltered.
;
; Processing complete; release victim's fork lock
100$:
        forkunlock lockaddr=ucb$b_flck(r11),newipl=(sp)+,-
         preserve=YES
        popr    #^m<r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>



To locate the intercept UCB address from the DDT address, it is
best to hold the forklock, so that in case someone tries to
modify the list the lookups will not get clobbered. This code
can be run from basically any desired IPL at start inside an
intercept driver (DEC does it at fdt time in some examples).
   Note that (omitting the error check code) the search loop
is only

2$: cmpl ...
    beql 1$
    movl ...
    brb 2$
1$:

   which is really quite efficient.


If we are in a bit of code that runs in an intercept driver, something
like the following sequence could locate the intercept driver UCB
and return its address in R11, assuming the standard above has been
followed:

; Assumes that R5 is the UCB address of the device that has had some
; code intercepted and that we are in some bit of code that knows
; it is in an intercept driver. Also assumes R11 may be used as
; scratch registers (as is true in FDT routines). Control returns at
; label "err" if the DDT appears to have been clobbered by
; something not following this standard, if conditional "chk.err"
; is defined.
;       Entry: R5 - victim device UCB address
;       Exit: R11 - intercept driver UCB address

        pushl   r10
        movl    ucb$l_ddt(r5),r10       ;get the DDT we currently have
        movab   xx$dpt,r11              ;magic pattern is DPT addr.
; lock this section with forklock so we can safely remove
; entries at fork also. Use victim device forklock.
        forklock lock=ucb$b_flck(r5),savipl=-(sp),preserve=YES
2$:     cmpl    <ucb$l_uniqid-ucb$a_vicddt>(r10),R11
                                        ;this our own driver?
        beql    1$                      ;if eql yes, end search
        .if     df,chk.err
p.magic=x^F012F024
        cmpl    <ucv$l_icsign-ucb$a_vicddt>(r10),#p.magic
        bneq    err                     ;exit if this is nonstd bash
        .endc   ;chk.err
; follow DDT block chain to next saved DDT.
        movl    <ucb$l_prevddt-ucb$a_vicddt>(r10),r10
                                        ;point R10 at the next DDT in the
                                        ;chain
        .if     df,chk.err
        bgeq    err                     ; (error check if not negative)
        .endc   ;chk.err
        brb     2$                      ;then check again
1$:
; At this point R10 contains the DDT address within the intercept
; driver's UCB. Return the address of the intercept driver's UCB next.
        movab   <0-ucb$a_vicddt>(r10),r11       ;point R11 at the intercept UCB
        forkunlock lock=ucb$b_flck(r5),newipl=(sp)+,preserve=YES
        popl    r10

; xx$dpt addr is appl magic number, and the name must be defined
; within the intercept driver. (replace "xx" by the appropriate
; characters.)



To remove an entry from this DDT chain, we must synchronize again at
fork IPL. The back chain can then be used to replace the entry in the
UCB with the one needed. If we assume we enter with R5 as the victim
driver UCB address, an entry can be removed like this:
;
; Entry: R5 points at victim device UCB and current driver is the one
; desiring to remove its entry from the DDT chain. Thus its xx$dpt: address
; is the one being sought. ("Current driver" here means the intercept
; driver.)
;   It is assumed that the driver knows that the DDT chain was patched
; so that its UCB contains an entry in the DDT chain
        pushr   #^m<r0,r1,r2,r3,r10,r11>
        movl    ucb$l_ddt(r5),r10       ;get the DDT we currently have
        movl    ucb$l_ddb(r5),r1        ;get ddb of victim
        movl    ddb$l_ddt(r1),r1        ;and real original DDT
        movl    r10,r0                  ;save ucb$l_ddt addr for later
        movab   xx$dpt,r11              ;magic pattern is DPT addr.
; lock this section with forklock so we can safely remove
; entries at fork also. Use victim device forklock.
        forklock lock=ucb$b_flck(r5),savipl=-(sp),preserve=YES
2$:     cmpl    <ucb$l_uniqid-ucb$a_vicddt>(r10),R11
                                        ;this our own driver?
        beql    1$                      ;if eql yes, end search
        .if     df,chk.err
p.magic=x^F012F024
        cmpl    <ucv$l_icsign-ucb$a_vicddt>(r10),#p.magic
        bneq    err                     ;exit if this is nonstd bash
        .endc   ;chk.err
; follow DDT block chain to next saved DDT.
        movl    <ucb$l_prevddt-ucb$a_vicddt>(r10),r10
                                        ;point R10 at the next DDT in the
                                        ;chain
        .if     df,chk.err
        bgeq    err                     ; (error check if not negative)
        .endc   ;chk.err
        brb     2$                      ;then check again
1$:
; At this point R10 contains the DDT address within the intercept
; driver's UCB. Return the address of the intercept driver's UCB next.
        tstl    <ucb$l_intcddt-ucb$a_vicddt>(r10)       ;were we intercepted?
        bgeq    3$                      ;if geq no, skip back-fixup
; we were intercepted. Fix up next guy in line.
        movl    <ucb$l_intcddt-ucb$a$_vicddt>(r10),r11  ;point at interceptor
        movl    <ucb$l_prevddt-ucb$a_vicddt>(r10),<ucb$l_prevddt-ucb$a_vicddt>(r
11)
                                        ;copy our prior DDT ptr to next one

3$:
; if we intercepted someone, fix up our intercepted victim to skip by
; us also.
        movl    <ucb$l_prevddt-ucb$a_vicddt>(r10),r2    ;did we intercept
                                        ;original driver?
        cmpl    r2,r1                   ;test if this is original
        beql    5$                      ;if eql yes, no bash
; replace previous intercept address by ours (which might be zero)
        movl    <ucb$l_intcddt-ucb$a$_vicddt>(r10),<ucb$l_intcddt-ucb$a_vicddt>(
r2)
5$:
; Here remove FDT entries from the list if they were modified.
; This needs a scan of the FDT chain starting at the victim's
; ddt$l_fdt pointer and skipping around any entry that has address
; xx_functable:
;  The FDT chain is singly linked. The code here assumes everybody
; plays by the same rules!
; NOTE: Omit this code if we didn't insert our FDT code in the chain!!!
        movl    ddt$l_fdt(r0),r1        ;start of FDT chain
        movab   xx_functable,r2         ;address of our FDT table
        clrl    r3
6$:     cmpl    r1,r2                   ;current fdt point at us?
        beql    7$                      ;if eql yes, fix up chain
        movl    r1,r3                   ;else store last pointer
        movl    fdt_prev(r1),r1         ;and point at next
        bgeq    8$                      ;if not sys addr, no messin'
        brb     6$                      ;look till we find one.
7$:
;r3 is 0 or fdt pointing to our block next
;r1 points at our fdt block
        tstl    r3                      ;if r3=0 nobody points at us
        bgeq    8$                      ;so nothing to do
        movl    fdt_prev(r1),fdt_prev(r3)               ;else point our next-fdt
 pointer at
                                        ;last fdt addr.
8$:
;
; Finally if the victim UCB DDT entry points at ours, make it point at
; our predecessor. If it points at a successor, we can leave it alone.
        cmpl    r10,r0                  ;does victim ucb point at our DDT?
        bneq    4$                      ;if not cannot replace it
        movl    <ucb$l_prevddt-ucb$a_vicddt>(r10),ucb$l_ddt(r5)
4$:
        forkunlock lock=ucb$b_flck(r5),newipl=(sp)+,preserve=YES
        popr    #^m<r0,r1,r2,r3,r10,r11>


   A final note:
   The code here assumes that its standards are followed by all DDT
bashing / FDT bashing. System crashes can occur if this is not so. But
note: they do now. It is not possible to guard against kernel mode
code that bashes vectors in Attila-the-Hun mode. Some bits more checking
can be done for production code, but I hope we can come to closer accord
over how these operations can coordinate.

Glenn Everhart
Everhart@Raxco.com
215 358 5875