How to Use Multi-Task DDT DDT is a symbolic assembly language level debugger for PDP11 which normally is included in a task image by specifying its name on the input side of a taskbuild with the /DA switch to indicate that it is a user debug aid. It allows one to examine code and see it, or enter it as code, to enter symbols, and resolve addresses relative to symbols. Thus, one normally needs only a map to use it, not necessarily assembly listings. (One may predefine a short file with global symbols and append it to the DDT symbol table and avoid even the need to use a link map.) One pays for this power in memory; DDT may require as much as 6K of memory, which may not be available in a large task image. Even the DEC debugger, ODT, which is a strictly octal debugger, requires 1K to 2K of task space. Thus it is comparatively frequent to have a program too big to debug. That's unfortunate because the big programs are usually the ones most needing to have a debug aid to help sort out why they don't work. DDT22M (M for Multi-tasking, 22 for 22 bit addressing) is a special task which includes a version of DDT and a control program in one task, and a very short (about 150 words minimum, or about 200 words for support of the ability to debug floating point with examination and display of floating point contexts in floating accumulators) "kernel" debugger in the task that is to be debugged. It operates by going around some of the walls placed by the operating system in RSX and IAS systems. In this scheme, DDT is able to examine or modify the target task's address space directly, and controls execution of the target by interaction with the kernel debugger. This frees all examination and display functions from the target task's address space and permits debug of all but the most pathologically huge programs. (DDT can perform examines/deposits in a task by self- mapping as described above, or by a pure mode as below that is not sensitive to task motion in memory.) The DDT system may, if desired, be assembled to permit access to the target task via sends and receives (executing MOV or MOVB instructions in the target's context) to allow access to the target's space without dependence on physical memory locations and without sensitivity to swapping/shuffling activity. The variable SR.CTL will control this; set it nonzero to use mapping via send-data/receive-data, zero to use the DDT internal pseudo mapping. This method is safer than the "pure DDT mapping" mode above, but is notably slower (by a factor of 3 more or less). This is not important for execution from one breakpoint to another, but single stepping is slower. Single stepping is used for memory watchpoints too. Thus, DDT mapping mode should be turned on in those cases unless the system is swapping heavily. The system is normally generated with DDTBLD.CMD which builds it as described below: To generate the system, DDT22 must be assembled with the parameter MU$DBG defined, and linked with DDTMU. The task name must be DDT22M, so only one such task may exist at a time. The DDT22M task is normally built with floating point support too if the machine has FPP instructions (or the Floating Point Emulator available on DECUS RSX SIG tapes for RSX11M or IAS). This permits examination or modification of memory in Floating Point format. The debug kernel may be assembled with $D$FPU defined if the target task's floating accumulators are to be available to the debugger. The debug kernel DDTKNL must be assembled, with or without $D$FPU defined, to link into the target task. There is a command file DDTBLD.CMD which builds a suitable DDT and the debug kernel. NOTE: if using RSX11M+, define K$ID when assembling DDT22, and if using either RSX11M or RSX11M+, it is desirable to define R$XMDB and assemble with [1,1]EXEMC/ML when assembling DDT22, and with only the definition when assembling DDTMU. The file DDTPRM should be used and edited for your system. DDT22M is linked with DDTMU and DDT22/DA as its main inputs. The target task is linked with DDTKNL/DA as an input file. Given these preparations, you are ready to debug your code. Note that DDTKNL will trap all odd address traps, illegal instructions, segmentation errors, and breakpoints. It, AND DDT22, must be edited to handle non-RSX EMT or TRAP instructions. (See the source; such changes are not recommended.) Be sure to use the /FP switch in taskbuild if your DDTKNL has $D$FPU defined. Now install DDT22M or use a command like RUN DDT22M/TASK=DDT22M to run the task with the required task name DDT22M. The DDT task will announce its presence and sit there. Type $G ($ means "escape" hereafter and may be eaten by the terminal for some terminals (like LA120, VT100, VT52) but will still work even though it does not echo. This starts the DDT22M task up and leaves the terminal free. DDT may be built to issue its own $G optionally. If it is, you need type nothing. However, be SURE that the task name is right. Normally it will be DDT22M, but the code in DDTKNL determines what the name MUST be. DDTKNL MUST know the task name of your debugger, and the debugger must be active before your task starts. Note: IAS users must install DDT22M; the /TASK= switch fails there. Now you may start your target task in whatever way you like. When your target task comes in, DDT will be re-activated. If you built with R$XMDB defined, DDT is automatically mapped to your task at every message from it (which permits use of one copy of DDT22M for several tasks; the RCVBUF area has the RAD50 task name in its start. DDT will print the task name you are examining and keep breakpoints straight for many tasks.) The DDT22M task may map with either of 2 address spaces now. Initially it is in its own space. To allow examination of the target's space, type the $UM command. To return to the DDT space, type $UV. (Remember "$"="escape", not just dollar sign.) Normal procedure is to type $UM, then set breakpoints (or single step mode), then type $G first to start your program. You MUST use the $UM command before beginning the target task if any breakpoints are to be set. When you hit a breakpoint, DDT symbols R0 through R5, SP, PC, and U.PSW are the target's registers, and DDT symbols AC0 thru AC5 and FPSTAT are the target's floating accumulators if DDTKNL had floating support. These may be examined as you would under normal DDT. To examine DDT's registers, you must use the $UV command and examine D.R0 thru D.R5, D.SP, and D.PC. Remember to use the $UM command before proceeding. The normal $P command will proceed from breaks normally. Single stepping may be done when not in $UM mode, but this is not recommended since you could forget what you're looking at. Symbol U.DSW is the task's $DSW at the breakpoint also. Once in DDT you may at any time get DDT to exit by typing ^Z or BYE$G. However, if your target task exits (normally or otherwise) DDT22M won't know. Normal action is to abort DDT22M from the console when done with it. The command to do this is ABO DDT22M which will end the session. (There is a hook in DDTMU and DDTKNL that permits you to install a task that will send an exit message to DDT22M to get it to exit; see the source if you don't care to abort DDT22M. Almost all DDT commands work in this system. Even the i$X command works, executing instructions in the target's context. Also, once you type the initial $G to DDT22M when it first starts, all subsequent $G or $P commands are interpreted as target addresses, and the DDT22M task itself is in an idle loop awaiting intertask messages from the target. UNDER P/OS The following commnds work with the toolkit to get the multi-task DDT to run: 1. Link so the task name is just DDT22M for the MU part (in DDTBLD.CMD) 2. To run, INSTALL [DDT]DDT22M/TASK=DDT22M ;install the DDT task wherever it is SPAWN .DDT22M ;SPAWN a command to run it DDT-22/RSX/SYM/... 3. Type ;;gg (which will echo as ;$gg) so that DCL will see one of the semicolons and DDT22M the other, and likewise the G commands. This will get DDT22M going. 4. Type return to get your $ prompt and go ahead and run the target task (linked to DDTKNL). You can now debug normally. APPENDIX The DDT22M sources have been modified so that if $$SDT is defined in DDTMU, a "system" debugger results. This debugger will type the task name out whenever a new task is seen to have sent a message to DDT and will save and restore the breakpoint information by task name for several tasks, automatically inserting the correct values for DDT to use with each breakpoint entry from a DDTKNL module. Thus, the user knows at all times which task he is talking to, and may control several tasks. It has further been provided that a task restart will reinitialize breakpoints, so that DDT22M may be left running, with symbols defined, across several runs of a task. This may be more convenient than having to re-enter symbolic locations every run. Memory watchpoints as well as breakpoints are remembered on a per task basis and the address of a watchpoint at a break will be shown. DDT now has the capacity to read symbol table files and look up symbols. This capacity exists in all RSX/IAS DDT versions but is most recommended for the system debugger since it has more address space. The $UO and $UQ commands open and close the symbol table file; the $UO will prompt and use a GCML$ call to read the lun. A side effect of defining SM$FIL to allow this is that the luns .ODTL1 and .ODTL2 are used by DDT for I/O. .ODTL1 is used for the terminal and .ODTL2 is used for the .STB file. These luns are added by TKB whenever a debug aid is included. Also added is the $UZ command to allow memory modification breakpoints. These are simulated by infinite single stepping and break when any of up to 8 addresses' contents change. The logic is simple minded and does not really tell what changed. However, a simple application would be to monitor $SEQC for a program from F4P to allow a single-statement breakpoint. It is slow, but needs no compiler modification. There is also a preprocessor that can make a faked up .STB file from FORTRAN listings and maps and allow DDT to know all line numbers, statement labels, and variable names in the FORTRAN program. There is also some support for a breakpoint at $ALBP2 to avoid having DDT reset any instructions in the wrong overlay. This allows one to monitor overlays and insert breaks for new overlays at the time of overlay load. The $UK and $UU commands are extended so $1UK loads KNL I APRs and simple $UK loads KNL D APRs. $UU is not normally so modified (it's an assembly conditional) but if K$ID is defined, the kernel mod is generally there. A bit of protection against access attempts to nonexistent memory is available if the symbol MAXBK is defined as the highest legal 32 word block in the machine. It defaults to 256KW (20000 octal) but may be set anywhere. DDT will simply not do any access if APRs are found invalid. DDT is not totally infallible in this calculation, and it will give no error indication, but at least it should not crash or access invalid regions very much. The DDT Symbol Table File Reader will cache its symbols (LRU) in the normal symbol table, in a special area. This should reduce file accesses reasonably. When changing tasks to debug, the normal procedure will be to open a new .STB file as needed. However, due to the caching, this may not be necessary after the first time; experiment. You may want to go into the source and enlarge the cache too, or possibly shrink it. Cached symbols may be killed as any others, but beware of this. They normally are "held" in the table as nonzero but small values to prevent the space from being used for normal symbols. A killed symbol may be re-used by console definition of a new symbol and then be overwritten by the .STB cache logic if any cached cells are removed. The 1$UO logic will erase the cache, though, while the $UQ logic will not. Be aware that DDT does not change symbol table files automatically. If you are debugging several tasks, you should change .STB files yourself where you need to, possibly clearing the cache of symbols to avoid confusion. To minimize the resources needed for DDT's intertask communication, DDT will normally send messages to tasks sending DDT messages, but the tasks will, in DDTKNL, receive and send messages only to DDT22M, thus leaving othes queued. Also, a generation option allows ASTs to be disabled during breakpoints (when DDTKNL may reasonably expect messages from DDT) so thatno message-in ASTs will be activated elsewhere. The ASTs are re-activated upon dequeue of a message from DDT. Note that if ASTs were disabled on entry to a break, though, they will not be reenabled by DDTKNL. This slightly enlarges the DDTKNL task, but for system-wide debug is so useful as to be mandatory for avoidance of interference with intertask communication in the application being debugged. There is nevertheless an effect that DDTKNL will have on communication not directed to it. Once a message AST is queued to the target task due to a DDT message, that AST is not removed from the task by the system, even though DDTKNL has read the data packet. Therefore it is necessary for a task that is receiving message ASTs to ensure there is really data present for it to act on before doing anything else. It is evidently necessary also for the task to continue attempting to receive data until there is an error. A simple attempt to read data and use it based on the existence of an AST will become erroneous if DDTKNL is included. However, a well designed system will have a check that will prevent damage. Multiple versions of DDT22M may be made up with different task names. Only DDTKNL.MAC and DDTMU.MAC need be edited, and the DDT22M literal task names in SDAT$ and RCVD$ macros replaced by some other task name, which must be the name of the debugger used with that pair of tasks. Actually, DDTMU need not, I think, be altered, but DDTKNL will expect to know the task name it is talking to and will always need to be altered. A note to remenber: the memory break points (or "watchpoints") are really not designed to be used for several tasks at a time. You may use them, and they are replicated on a per task basis as are breakpoints, but since they are handled via single stepping, it should be recalled that it is VERY EXPENSIVE in CPU time to have watchpoints active and it is highly recommended that they be used for at most one task at a time. The DDT variable U.DSW will normally contain the contents of $DSW at a break and may be located automatically by that name. The variable $DSW is the value of $DSW in the DDT22M task context, which is not normally very meaningful. A consideration to be added possbly at some time in DDT is a mode flag to go with each variable to indicate which virtual map it corresponds to. The expectation is that normally DDT22M will be left in $UM mode and variables local to DDT (like JOBSA) will seldom be accessed. Once DDT22M is started, one may query PC to find where a task is while still in $UM mode; only the initial startup lacks this. DDT may be built to support conditional breakpoints. If these are enabled, the arrays D.BMSK and D.BTST are important. They are word arrays. If the word in D.BMSK corresponding to the breakpoint number (e.g., B0 is D.BMSK, B1 is D.BMSK+2) is nonzero, THEN the word at the location DDT will open at the break will be ANDed with that D.BMSK element. If the result is different from the value in D.BTST, the break will be ignored. Where DDT is communicating with another task, 14 RSX directives are required per breakpoint/proceed pair plus breakpoint open overhead. This is done by DDT without help from RSX where exclusive use of send/receive directives is not in use. If these directives are in exclusive use, some 16 additional RSX directives (minimum!) are executed with each breakpoint (8 to restore the original instruction at the break, 8 to put back the BPT at the proceed). Because of this overhead, it is desirable to allow DDT to do its own mapping where it is feasible. However, in a loaded system, it is probably best to allow DDT to use send/receive always, run slowly, and be SURE no shuffling takes place DDT dowsn't know about. DDT may be assembled then to not actually try to reference any I/O page locations if that is desired. NOTE for P/OS Users: On the PRO 350 and maybe micro/pdp11, the escape character must be generated as Ctrl-[ (i.e., hold down Ctrl and type [) since the F11 key (also labelled Esc) usually generates an escape sequence DDT is not prepared to parse. P/OS normally is not supplied with its executive .STB file, so DDT running on P/OS will normally use send/receive communications only so it need not refer to POSSYS.STB to be built. On a Pro, this should not cause any trouble. DDT will still do more for you than just about any other debugger around. IAS users should however also look over the BUG debugger on the Spring '84 RSX SIG tape. APPENDIX B: BACKTRACING You can now get DDT to keep track of where it has been. By specifying that you want this in your DDTBLD, DDT defines the following locations within its space (i.e. in $UV mode): MT.BUF Buffer of 32 PCs saved at each breakpoint trap (i.e., in single instruction mode, at each instruction). MT.PRT Next location to be filled in in MT.BUF. Locations are filled in circularly; the oldest saved PC is here and the newest is just before it. MT.TOP End of MT.BUF (so you can tell where the end is easily). If you put DDT into a single step mode (or a watchpoint mode which assumes single step) and put a breakpoint where you want it to stop, then tell it to proceed with a large proceed count, DDT will stop at the breakpoint and the MT.BUF array can be examined to see how it got to the breakpoint. Very handy where your program is crashing somewhere and you can't figure how it got there. GCE 5/6/1985