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 Sorenson 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$s_ppdbgn ;add any more prepended stuff after this $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 ; NOTE: Jon Pinkley suggests that the DDT size should be encoded in part of this ; unique ID so that incompatible future versions will be guarded against. $def ucb$s_ppdend $def ucb$a_vicddt .blkb ddt$k_length ; space for victim's DDT ; Make the "unique magic number" depend on the DDT length, and on the ; length of the prepended material. If anything new is added, be sure that ; this magic number value changes. magic=x^F012F000 + ddt$k_length + <256*> p.magic=x^F012F000 + ddt$k_length + <256*> $defend UCB ; Then at the DPT_STORE macros, some like this after the ; DPT_STORE REINIT call. DPT_STORE UCB,UCB$L_UNIQID,D,DPT$TAB ;store DPT address ; (change "XX" to device ; mnemonic correct values) DPT_STORE UCB,UCB$L_ICSIGN,L,magic ; 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 , functab , functab myswitch, myfdtbgn=. functab mypatch1, functab mypatch2, ... functab mypatchn, myfdtend=. functab to_orig, ; 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 #,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 ; 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^F012F000 + ddt$k_length + <256*> cmpl (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),(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 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 (r10),R11 ;this our own driver? beql 1$ ;if eql yes, end search .if df,chk.err p.magic=x^F012F000 + ddt$k_length + <256*> ;p.magic=x^F012F024 cmpl (r10),#p.magic bneq err ;exit if this is nonstd bash .endc ;chk.err ; follow DDT block chain to next saved DDT. movl (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 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 DPT$TAB,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 (r10),R11 ;this our own driver? beql 1$ ;if eql yes, end search .if df,chk.err p.magic=x^F012F000 + ddt$k_length + <256*> ;p.magic=x^F012F024 cmpl (r10),#p.magic bneq err ;exit if this is nonstd bash .endc ;chk.err ; follow DDT block chain to next saved DDT. movl (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 (r10) ;were we intercepted? bgeq 3$ ;if geq no, skip back-fixup ; we were intercepted. Fix up next guy in line. movl (r10),r11 ;point at interceptor movl (r10),(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 (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 (r10),(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 (r10),ucb$l_ddt(r5) 4$: forkunlock lock=ucb$b_flck(r5),newipl=(sp)+,preserve=YES popr #^m 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