From: gleeve@galaxy.dnet
Newsgroups: comp.os.vms
Subject: Proposed methods for stealing DDT/FDT/etc.
Date: 10 Sep 92 21:22:10 GMT
Organization: The Internet

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>(r11)
					;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