Introduction to Debug/iX by Stan Sieler Allegro Consultants, Inc. (c) 2000 2000-02-10 Chapter 1 What is DEBUG/iX? DEBUG/iX is the machine-level debugger that is bundled with MPE/iX. It provides a multiple window view of machine code and data, along with hundreds of commands and functions and a powerful interpreted programming language. Debug/iX is not a source-level debugger. DEBUG/iX is the debugger of choice (and necessity!) when you want to debug the operating system, libraries, optimized code, Native Mode (NM) code, or Compatibility Mode (CM) code. Although using DEBUG/iX requires a good knowledge of PA-RISC architecture and the Procedure Calling Convention, we'll try to fill in any missing background as we go. You may want to read some manuals: Debug/iX Reference Manual part # 32650-90901 http://docs.hp.com/en/32650-90901/index.html HP Assembler Reference Manual part # 92432-90012 http://docs.hp.com/en/92432-90012/index.html Procedure Calling Convention Manual part # 08740-90015 Also available as part of: PA-RISC Run-Time Architecture (HP-UX 11.00) Document http://www.software.hp.com/STK/partner/rad_11_0_32.pdf This text will sometimes refer to DEBUG/iX simply as "Debug" or as the "debugger". Note that MPE/iX was called MPE XL prior to release 4.0 of MPE/iX. We also assume that MPE/iX 5.0 (or later) is being used. Unless specified otherwise, most of the text and examples will assume that you are in "Native Mode" (NM) when debugging, as opposed to "Compatibility Mode" (CM). Abbreviations commonly used herein: CM Compatibility Mode (Classic HP 3000 CISC instructions) NM Native Mode (PA-RISC instructions) $ Prefix indicating hexadecimal constants ($11 = #17 = 9 + 8) % Prefix indicating octal constants (%11 = 9) # Prefix indicating decimal constants (#11 = 9 + 2) 1.01 How is DEBUG/iX Entered? ------------------------------ This section discusses how DEBUG/iX is entered, and how to obtain help on its commands and functions. There are a variety of ways to enter DEBUG/iX: 1) :DEBUG, a Command Interpreter (CI) command. Note: this requires PM capability. 2) RUN program;DEBUG (a CI command). Note: this requires write access to the program file. 3) Passing the string "DEBUG" into the NM COMMAND or HPCICOMMAND intrinsics or the CM COMMAND intrinsic. 4) Specifying the "debug" option when creating a process with the CREATE or CREATEPROCESS intrinsics (NM or CM). Note: this requires write access to the program file. 5) :SETDUMP (CI command) coupled with a subsequent :RUN of a program which aborts. Note: this does not allow continuation of the program. 6) Calling the NM DEBUG or HPDEBUG intrinsics, or the CM DEBUG intrinsic. Some programs (e.g.: QEDIT, EDITOR) allow the user to dynamically call simple procedures, including DEBUG. 7) Executing code (NM or CM) that encounters a breakpoint. (Normally, a breakpoint is set during an earlier interaction with Debug/iX from the same process ... but it is possible that the breakpoint could be "global", or that it was set for your process by another Debug session.) 8) Attempting to call an unsatisfied external routine...and the program was linked or run with UNSAT=DEBUG. The first time that DEBUG/iX is invoked for a particular process, it spends some time looking around and setting up information about its environment. You may notice that it takes much longer to enter DEBUG/iX for a large NM program than for a small NM program. Also, entering Debug for an NM program takes longer than for a CM program. 1.02 Reading The Prompt ------------------------ When DEBUG/iX is ready for input, it prompts with a message like: DEBUG/iX B.79.06 HPDEBUG Intrinsic at: a.009f16a8 hxdebug+$e4 $1 ($30) nmdebug > c The first line tells us the version of DEBUG/iX (which seems to match the version of the kernel of MPE/iX). The next line ("HPDEBUG Intrinsic...") tells us why we entered DEBUG/iX. The third line tells us: - This will be our first input into Debug (the $1) - Our PIN (Process Identification Number) is $30 (hex 30 is decimal 48). - We entered Debug from Native Mode (the "nm"). - We are in debug, not in DAT, SAT, or another debug-like tool (the "debug"). - Numbers entered without a radix prefix ($, #, or %) will be interpreted as hexadecimal (the "$" on the two numbers). 1.03 Exiting DEBUG/iX ---------------------- Once we are in DEBUG/iX, we have several ways of getting out of it: 1) The "C" command will exit DEBUG/iX and continue normally. "C" stands for "Continue". 2) The "ABORT" command will abort our process immediately. (Unless our process is critical or holds a SIR (System Internal Resource), in which case Debug will reject the ABORT with an error message.) 3) The "S" (or "SS") command, which will execute one (or more) instructions and then come back to Debug (assuming we haven't aborted, or called TERMINATE or QUIT or QUITPROG, or hit a breakpoint). ("SS" stands for SingleStep; S is a synonym for SS.) The ABORT command will turn off DEBUG/iX's windows prior to aborting your process, unlike the C and S commands. 1.04 DBUGINIT -------------- When DEBUG/iX is first invoked for a process, it reads and executes the commands in a file called DBUGINIT (if it exists). The formal name of the file is DBUGINIT.logongroup.logonaccount. (This file may be file-equated, if desired.) DBUGINIT must be a simple ASCII file without sequence numbers (i.e.: unnumbered). If an error occurs while processing the commands in DBUGINIT, DEBUG/iX will leave the file open for the rest of the lifetime of the process being debugged, as shown in the example below where DEBUG/iX is being invoked from EDITOR. In the example, the DBUGINIT file was accidentally kept as a numbered file. :editor /a wl "hi" wl "bye" // /k DBUGINIT Now, enter DEBUG/iX (which will try to execute the commands in DBUGINIT)... (remember, we're still in EDITOR) /;debug DEBUG/iX C.16.01 (Yes, for some reason EDITOR/3000 allows you to enter Debug/iX by typing ";debug". If you have PM capability, you could have enter ":debug" instead.) CM DEBUG Intrinsic: PROG %7.2223 HPDEBUG Intrinsic at: a.0096f9c4 hxdebug+$144 hi Unknown command. (error #10105) 00001000 ^ USE file "DBUGINIT.SOURCE.SIELER" interrupted at #1/#2 use USENEXT to continue, or USE CLOSE $3 ($2b) nmdebug > DEBUG/iX reported the problem, and noted that the DBUGINIT file was left "open". A USE CLOSE command would close the file without trying to read any more records from it. A USE 5 command would attempt to read (and execute) the next 5 records. The following is a sample DBUGINIT file: /* Turn on CRON... set cron /* may be undesirable if typeahead is on wl "CRON on" /* open SYMOS file, prevent errors... ignore quiet; { setvar symos_name "symos.os" + str (sysversion,1,1) + str (sysversion, 3, 2) + ".telesup"; env error = 0; ignore quiet; symopen !symos_name; if error = 0 then wl "Opened SYMOS: " + symos_name; deletevar symos_name; } /* enable compiler debugging traps, if any... trap trace_all arm Appendix C shows a longer example DBUGINIT file. 1.05 HELP! ----------- DEBUG/iX has on-line help for all commands, functions, and variables. If you know the name of a command that you want help on, simply enter: HELP command For example, if we want help on the ABORT command, we'd say: HELP ABORT HELP, with no parameters, mentions some commands that provide a lot of information: $28 ($6a) nmdebug > help "HELP" is a COMMAND name. cmd HELP misc nm cm USE: HELP [topic] [options] (Also see the WHELP command) PARMS: topic Any command, function, env var, macro, or user var name. options Any combination of the following: [NO]USE, NO[PARMS], NO[DESC], NO[EXAMPLE], NO[ACCESS], ALL DESC: The HELP command displays help for a specified topic. The topic may be a command name, a function name, an enviromental variable name, a macro name, or a variable name. The options available depend upon the topic. (See also WHELP, ALIASL, CMDLIST, ENVLIST, FUNCLIST, VARLIST, MACLIST.) EXAMPLE: $nmdebug> help dv, use 1.06 Case Sensitivity ---------------------- The only time that DEBUG/iX cares about the case (upper/lower) of your input is when you are entering a Native Mode symbolic address. Due to an unfortunate decision in the early days of MPE XL, names for Native Mode procedures (and other code-oriented symbols) are case sensitive. The thinking was apparently that only by case sensitivity could the operating system differentiate between things like the C "fopen" routine and the FOPEN intrinsic (other techniques existed, but that's another story). So, rather than being robust and searching for your symbolic names in both uppercase and lowercase, DEBUG/iX takes NM symbolic names literally. DEBUG/iX commands, functions, variables, macros, symbolic types, and CM symbolic names may be uppercase, lowercase, or mixed case. One tip to remember: Most intrinsics have uppercase names. Notable exceptions: The TurboIMAGE intrinsics and the V/Plus intrinsics have lowercase names. 1.07 The "LIST" Commands ------------------------- DEBUG/iX has a number of commands that will list things for you. This includes listing: commands, functions, and variables. These commands end with the word "LIST". With two exceptions (LIST and REGLIST), every command that ends in the letters "LIST" can have the "IT" omitted. The DEBUG/iX "LIST" commands are: Command What does it list ------- ----------------- ALIASList * aliases (synonyms) for commands CMDList * DEBUG/iX commands ENVList * environmental (DEBUG/iX pre-defined) variables ERRList most recent errors FUNCList * DEBUG/iX functions (procedures that return values) LOCList * local variables (variables local to macros) MACList * macros (user defined procedures) MAPList + files that have been opened with MAP command PROCList * symbolic names in current program or library SYMList * Pascal/iX type and constant names VARList * Variables ("global" variables) The commands marked with an asterisk (*) expect a wildcarded pattern as their first parameter (e.g.: CMDL @Z@). Ifno pattern is given, "@" is the default. Only the uppercase portion of the above commands is required by debugger. (MAPList is documented as expecting a pattern, but does not accept one!) Two other commands end in "LIST": LIST and REGLIST. These commands differ from the above and will be covered later. One interesting aspect of the CMDLIST, FUNCLIST, ENVLIST, and MACLIST commands is their ability to optionally display all of the available help information for each item. As an example, if you entered: CMDL , , all you would get about 300 pages of output, the equivalent of chapter 4 of the DEBUG/iX manual! Output from any DEBUG/iX command should be interruptible with control-Y. Unfortunately, control-Y is sometimes "lost". If control-Y is not working you will have to decide whether to hit Break and :ABORT or to sit back and wait. Note: Control-Y can often be rejuvenated by executing a CI ":LISTF" command at the next DEBUG/iX prompt (other CI commands might work). The REGLIST command lists the values of over 60 different CPU registers as shown below: mr R1 $1 mr R2 $96f9c4 ... mr R30 $40332f40 mr R31 $2 mr SR0 $a ... mr SR7 $a mr TR0 $75c200 ... mr TR7 $84941018 mr ISR $331 mr IOR $40332a78 mr IIR $489f0000 mr EIEM $ffffffff mr RCTR $ffffffff mr SAR $11 mr PID1 $492 mr PID2 $25c mr PID3 $0 mr PID4 $0 mr IVA $ec000 mr ITMR $60142780 mr CCR $80 mr EIRR $0 mr IPSW $6000f mr PCSF $a mr PCOF $96f9c4 mr PCSB $a mr PCOB $96f9c8 If the output of the REGLIST command could be captured to a disk file, then a USE of that file would rebuild the entire set of registers for the user. (The MR command changes the value of a register.) DEBUG/iX has the ability to direct its output to a disk file, with the LIST command. The syntax for the LIST command is: LIST filename LIST The "LIST filename" form of the command opens a new disk file of the specified name. DEBUG/iX will now send a copy of all input and all output to the file, as well as to the terminal. By default, DEBUG/iX assumes that you want all output to a "LIST" file paginated, so it opens the file with carriage control and writes a page header every 60 lines. This can be defeated, as shown in the following example which prevents carriage control from being associated with the file and disables the page header logic: :file foo; nocctl env list_paging false list *foo Be careful when using the LIST command: DEBUG/iX seems to unconditionally purge any old copy of the specified file before trying to open a new copy. DEBUG/iX also has problems when you use the LIST command to open a spooled printer as shown: :file lp;dev=lp list *foo 1.08 Numbers & Expressions --------------------------- Whenever DEBUG/iX wants a value, you can use an expression. This is a *very* powerful statement, when you think of the ramifications. If Debug is looking for a value, it evaluates your input according to the following (rough) rules: 1) Does it "look" like a number? This determination is based upon the current input base (hex, decimal, or octal). If yes, then it is treated as the smallest form of number that will not lose information. For example "77" (without the quotes) would be treated as an unsigned 16 bit integer whose value is decimal 77, or decimal 63, or decimal 127, depending on the current input base (decimal, octal, or hex). "#77" would be treated as the decimal value 77. "-#20000" would be treated as a signed 16 bit integer. "-#40000" would be treated as a signed 32 bit integer. 2) Does it "look" like a string? Strings start with a double quote (") or a single quote ('), and are terminated by whichever quote character you started the string with. (DEBUG/iX does not care which pair of quotes you use.) If you want to embed the same quote character in a string that you are using as the delimiter, simply double it. For example, the following two strings are functionally identical: "Cat's" 'Cat''s' 3) Is it a local variable? Note: It is usually *not* necessary to put an exclamation mark (!) in front of any kind of variable name. 4) Is it a global variable or an environmental variable? Environmental variables are simply predefined variables controlled by DEBUG/iX. Example: sysversion 5) Is it a macro call? DEBUG/iX does not differentiate between typed and untyped functions. From DEBUG/iX's viewpoint, every macro returns a value. If a macro does not have a "return" statement in it, then the default return is the value 0. 6) Is it a built-in function? Example: asc (123) An expression is the usual definition of something like: ::= | | - | (expression) where was (roughly) described above. Examples of valid expressions: 23 "ABC" strup ("cat") Strlen ("catsup") pin pin+3 pc + (r26 * #23) 1.09 Registers --------------- When you are in Debug/iX, you can access all of the hardware registers (and the simulated CM registers). The DR command lists the major NM registers and their values. A short description of many registers follows. You can get more information about each by using the HELP command followed by the register name, as in "HELP R26". IIR Interrupt Instruction Register This register has the 32-bit opcode (instruction) that was being executed when an interrupt occurred. IOR (see ISR) IPSW Interrupt Processor Status Word The Processor Status Word as of the most recent interrupt. ISR and IOR Interrupt Space Register, and Interrupt Offset Register If an interrupt occurred while trying to access virtual memory, the ISR.IOR is the 64-bit virtual address you were trying to access. PC Program Counter (64-bit virtual address) PCQF Program Counter Queue Front The address of the current instruction being (or about to be executed) Note: the ring level (see PRIV) is seen in the bottom two bits of the PCQF value. I.e, in "user mode", you are at PRIV 3 and the bottom two bits of PCQF will be 3 (binary 11). PCQB Program Counter Queue Back The address of the next instruction to be executed. (Generally, PCQB = PCQF + 4, but not always) Note: the ring level (see PRIV) that the instruction will eventually be executed at is seen in the bottom two bits of the PCQF value. PID1, PID2, PID3, PID4 Protection ID registers. These are 16 bit registers that contain a 15-bit page protection ID and a 1 bit "Write Disable" flag. The DR command reports the PID registers in two forms: 16-bit hex value, and 15-bit hex value with the Write Disable flag broken out. PRIV The execution ring level ("privilege"). The values range from 0 (most privileged) to 3 (least privileged) RCTR Recovery CounTeR This register is used to implement the SingleStep (SS or S) command. (See "Counting Instructions" below) R0, R1, R2, R3, ..., R29, R30, R31 General purpose registers (32 of them) SAR Shift Amount Register (used by a couple of instructions) SR0, SR1, SR2, SR3, SR4, SR5, SR6, SR7 Space Registers SR0 is used by the BLE instruction. SR1,SR2, and SR3 are used for 64-bit addressing. SR4 has the Space ID of the object module (code) you are currently executing. SR5 has the Space ID of your process local data (global data, heap, stack). SR6 and SR7 have the values $b and $a, respectively, and are used to point to MPE/iX operating system data. TR0, TR1, TR2, TR3, TR4 Temporary Registers Registers used for temporary data storage by the operating system. The register names are not case sensitive. 1.10 Nested Procedure Names ---------------------------- The Pascal/iX language allows procedures to be statically nested within other procedures. When DEBUG/iX is displaying the name of a nested procedure, it does so by showing the outermost procedure name, then a dot (.), and then the nested procedure name, as in the following: notify_dispatcher.prep_for_swapin If you want to refer to such a nested procedure symbolically, you can't just type in the name, complete with dots. Instead, the "nmaddr" function must be used: = nmaddr ("notify_dispatcher.prep_for_swapin") The same dot convention is used by SPLash! for subroutines declared inside procedures. 1.11 Comments -------------- DEBUG/iX will ignore the rest of any input line after a "/*". This does not apply to a "/*" found within a quoted string. The following shows some DEBUG/iX commands and comments: wl "Hello there" /* say hi /* this line is a comment /* so is this one wl "A comment starts with /* and has no special end" /* the above "WL" command has no comments on the line! Even though Debug/iX ignores everything after the "/*", some programmers (inspired by Pascal) will end the comment with a trailing "*/". This isn't necessary. 1.12 UNSAT= ------------ Sometimes, you encounter a Native Mode program that has unresolved externals, so the loader refuses to load it: :run foo.pub UNRESOLVED EXTERNALS: ccat_read (LDRERR 512) UNRESOLVED EXTERNALS: ccat_write (LDRERR 512) Unable to load program to be run. (CIERR 625) If you know that the unresolved routines aren't going to be called, you might say "gee...I wish I could tell the loader to run the program anyway". You can...by specifying "UNSAT=". The UNSAT= option on the RUN command (and on the LINK command) allows you to tell the loader the name of a procedure to be called in place of any unresolved procedure. In the above example, if we added "UNSAT=DEBUG", then the loader would replace the calls to ccat_read and ccat_write with calls to DEBUG. Example: :run foo.pub; unsat=DEBUG Now, the program loads and runs. If it should happen to try to call ccat_read or ccat_write, it will pop up into Debug/iX. Note: don't try specifying "HPDEBUG" instead of DEBUG...DEBUG works because it expects no parameters. HPDEBUG expects a number of parameters...and probably not those being passed into ccat_read or ccat_write! $page "Chapter 2: Breakpoints" Chapter 2 Breakpoints A "breakpoint" is a code address that you have told DEBUG/iX to "break" execution at. When your process attempts to execute the instruction at such an address, we say it "hits" the breakpoint. At that point, control is passed to Debug/iX, almost as if you had a direct call to DEBUG at that instruction. Debug runs from the same process that the breakpoint was in ... it is not a separate process! This means, for example, that while you are sitting at a Debug prompt, your process is idle. The B command (for Breakpoint) sets a breakpoint at a specified code address. When your process tries to execute a breakpoint, it will pop up into DEBUG/iX. Breakpoints default to being "local" to your process. This means that a breakpoint like b FOPEN only affects your process. If another process calls FOPEN (the file system intrinsic usually used to open a file), that process will *not* hit the breakpoint. When your process tries to execute the instruction at FOPEN, the breakpoint will trigger. If you have PM capability you can set a breakpoint for another process if you know its PIN (Process Identification Number), and you can setup system-wide breakpoints, that will affect every process. (Note for PM users: you cannot safely set a breakpoint for code that will be running on the Interrupt Control Stack (ICS).) The syntax for the Breakpoint command is: B address [count] [ LOUD | QUIET ] [cmd] "Address" is optionally qualified with a ":pin#" to set a breakpoint for a specified process, or with a ":@" to set a system-wide breakpoint. A simple way to put a breakpoint 2 instructions from where you are now is: b pc + 8 /* native mode b p + 2 /* compatibility mode The "count" parameter specifies how many times the breakpoint must be "hit" before you actually pop into DEBUG/iX. The default is 1, which means you will enter DEBUG/iX every time the breakpoint is hit. If a count of "2" is used, then every other time you hit the breakpoint, you will enter Debug/iX. And, "other every" time you will silently continue without entering DEBUG/iX. By default, breakpoints are "permanent", unlike MPE V, where the breakpoints defaulted to temporary (or, "one shot"). (Of course, permanent for non-system-wide breakpoints really means "for the life of the process".) If "count" is negative, then the breakpoint is deleted when it finally enters DEBUG/iX. One nice usage of this is to set a "temporary" breakpoint at one instruction from where you are now: b pc + 4, -1 /* NM b p + 1, -1 /* CM The "LOUD/QUIET" option (default is LOUD) lets you decide whether or not DEBUG/iX should announce the fact that you have hit a breakpoint. The QUIET option can really speed things up when you are hitting a breakpoint thousands of times. The "cmd" option is a DEBUG/iX command to be executed when you enter DEBUG/iX for the breakpoint. Compound commands (a list of commands separated by semicolons and surrounded by braces ("{}")) are allowed. If a command is specified, then it will be executed. If the command does not have a "c" command within it, then you will be left in DEBUG/iX after the command finishes. The following example shows how the "cmd" option can be very helpful in finding a problem. Let's say that your NM program is opening several hundred files with the FOPEN intrinsic, but is having a problem when a file fails to open. We can setup a breakpoint at the exit from FOPEN that will quietly continue each time until the failing FOPEN is encountered. We can couple this with a breakpoint at the start of FOPEN that will remember the name of the file that is being opened: b FOPEN, , quiet, {var save_file_address = r26; c} b ?FOPEN+8, , quiet, {if r28 <> 0 then c else { wl "Failed to open file: "; dv save_file_address, 10, s} } With the first of the above breakpoints, every time we enter FOPEN the debugger will save the first parameter (which is the address of the name of the file being opened) in a global debugger variable called "save_file_address". The second breakpoint will check that FOPEN has returned a valid (non- zero) file number. If it has not, then we know that the open failed, and display the name of the file with a DV command. We could then, if we have PM capability, do a dynamic call of PRINTFILEINFO for more information about the failure: = nmcall ("PRINTFILEINFO", 0) The "cmd" is free to include calls to macros. This is quite useful, because the size of the "cmd" text is limited. 2.01 Breakpoint At Procedure Return ------------------------------------ It is often desirable to set a breakpoint at the return point from a procedure call. The following shows an NM and a CM method of setting a breakpoint at the return address from a procedure. lev 1; b pc, -1; lev 0 /* NM example lev 1; b p, -1; lev 0 /* CM example (The "lev 0" is optional for both..it has nothing to do with setting the breakpoint.) Another method, only valid for NM code and only fully correct when you are at the entry to a procedure, is: b sr4.r2, -1 With any of the above techniques, the breakpoint can be made permanent by omitting the "-1". 2.02 Trace Breakpoints ----------------------- Some NM compilers (e.g.: Pascal/iX, SPLash!) have the ability to emit tracing breakpoints. These are variants of the normal breakpoint instruction that DEBUG/iX is aware of. If you are running a program that has been compiled with the tracing breakpoint option ($symdebug 'xdb' in Pascal, $break= ... in SPLash!), then whenever your program executes one of these tracing breakpoints the following happens: - An interrupt is generated (just like a normal breakpoint); - The interrupt handler quietly invokes DEBUG/iX; - DEBUG/iX determines which tracing breakpoint was hit (see table below) and checks if you have "armed" that breakpoint with the TRAP command; - If the particular tracing breakpoint is armed, DEBUG/iX announces that you have hit a "Trace Breakpoint" and prompts you for debug input. The announcement looks like: TRACE Break at: 40c.0003d37c fac2+$10 (label) - If the breakpoint is not armed, then DEBUG/iX quietly continues your program, starting with the instruction following the breakpoint. Note: Hitting a tracing breakpoint will cost thousands of cycles of wasted time. Do NOT release (put into production) a program with tracing breakpoints in it! Exercise for the student: time the cost of a tracing breakpoint. Hint: about 40 microseconds per instance, on an HP 3000/968. The tracing breakpoints are: - Enter Program - Begin Procedure - Label - Statements - End Procedure - Exit Program Note: The names of the breakpoints vary widely, and appear in slightly different forms in: DEBUG/iX's HELP TRAP command (and syntax), disassembled code from the DC command (and the nmP window), and in the "Trace Breakpoint" message. This is attributable (probably) to having three different programmers implement these three sections of DEBUG/iX, but don't let the differences in "spelling" worry you. Note that Pascal/iX seems unable to emit a tracing breakpoint after a label. Further, Pascal/iX has no syntax to ask for only a few of the other four types (i.e.: you get all or nothing). SPLash! supports all six types, and allows each to be individually requested. If you are developing a program that uses these breakpoints, you can add the following line to your logon group DBUGINIT file: TRAP T ARM $page "Chapter 3: Tracing your stack" Chapter 3 Tracing Your Stack When you enter DEBUG/iX, you often want to look at the history of how you got there. DEBUG/iX is entered because your code called DEBUG or HPDEBUG, because you hit a breakpoint, or because you triggered a trap that the debugger was watching. DEBUG/iX would like to tell you what procedure you are in, and (perhaps) what procedure called it. The identity of the current procedure is determined from the value of the Program Counter register (pc). The "caller" procedure is a little more difficult to determine. The TR command (for "TRace") will try to list the procedure your code is in, the procedure that called it, the procedure that called that procedure, and so on, all the way back to the outer block of the program. The process of looking at each procedure to determine who called it is called "walking the stack". The result of listing the names of each of these procedures in reverse chronological order is called a "stack trace" Unlike the Classic HP 3000 instruction set, PA-RISC does not have a "procedure call" instruction. This means that the procedure calling mechanism is defined entirely by the software. The "Instruction Set and Procedure Calling Conventions" Manual (HP part # 09740-90015) documents this convention. Although it has a few shortcomings, it works reasonably well for most uses. DEBUG/iX is aware of the Procedure Calling Convention (PCC). In order for DEBUG/iX to be able to determine what procedure called the current procedure, DEBUG/iX must consider information including: - is the current code a procedure or millicode? - is the current procedure a "leaf" procedure? (leaf procedures are those procedures that do not call any other procedures) - has the procedure stored the return address into the stack? - how big is the stack frame for the procedure? Sometimes, DEBUG/iX cannot answer all of these questions. When this happens, DEBUG/iX is unable to completely trace the stack. 3.01 TR Command ---------------- This section discusses the TR command. TR causes DEBUG/iX to print a stack trace for the "current" process. To get a stack trace for another process, first switch to it with the PIN command, then use the TR command. The syntax for the TR command is: TR [ # ], [Interrupts] [Dual] [Full] The first parameter to TR, which we usually omit, tells DEBUG/iX to stop the stack trace after printing that number of markers. This is useful when you are stack tracing a process that is dozens or hundred of procedures "deep". (Of course, such a process probably suffers from severe design problems, which is why you are probably debugging it!) When the number is omitted, DEBUG/iX will trace as far "back" into the stack as possible. Each of the other options for the TR command may be abbreviated to just their first letter. Normally, the TR command will stop if it encounters an "interrupt marker". The "Interrupts" option tells the TR command not to stop when it hits such a marker. (An example will be shown later.) The "Dual" option tells TR to show both the CM and NM sides of the stack trace, if appropriate. Without Dual, only the current mode (NM or CM) is shown. This option is generally used only when you know your process is using both CM and NM code. 3.02 Sample Stack Trace ------------------------ When the demo program in Appendix D is run as follows: :resetdump :run demo.pub it aborts with the following: **** Bound violation or range error (TRAPS 12). ABORT: SAMPLE.PUB.SIELER NM PROG 604.00005b30 parse_info.find_non_blank+$44 PROGRAM TERMINATED IN AN ERROR STATE. (CIERR 976) If a :RESETDUMP command was in effect, then the following occurs when the program aborts: :setdump :run pas2prg **** Bound violation or range error (TRAPS 12). ABORT: PAS2PRG.DEBUGBOO.SIELER PC=734.00005a3c parse_info.find_token+$34 NM* 0) SP=41842370 RP=734.00005c54 parse_info+$94 NM 1) SP=41842370 RP=734.0000610c PROGRAM+$68 NM 2) SP=418421f0 RP=734.00000000 (end of NM stack) R0 =00000000 41841b70 00005c57 8400b400 R4 =d46c8018 00000001 00000000 00000000 R8 =00000000 00000000 00000000 00000000 R12=00000000 00000000 00000000 00000000 R16=00000000 00000000 00000000 00000000 R20=00000001 00000001 00000000 00000000 R24=00000080 41644094 41644090 41644000 R28=00000000 41842370 41842370 00000001 IPSW=0024ff0f=jthlNxbCvmrQPDI PRIV=3 SAR=0002 PCQF=734.5a3f 734.5a43 SR0=0000000a 00000000 00000000 00000000 SR4=00000734 000008d9 0000000b 0000000a TR0=00000000 0000303c 00e62ed4 c010a700 TR4=41844368 00000002 c0202008 0000000f PID1=0134=009a(W) PID2=0000=0000(W) PID3=0000=0000(W) PID4=0000=0000(W) RCTR=00000000 ISR=000008d9 IOR=41634a50 IIR=0ad574c0 IVA=0012b000 ITMR=ea81f798 EIEM=ffffffff EIRR=00000000 CCR=00c0 **** PROCESS ABORT INTERACTIVE DEBUG FACILITY **** $36 ($22) nmdebug > c PROGRAM TERMINATED IN AN ERROR STATE. (CIERR 976) : The data shown by the TR command is worth discussion in more detail. Consider the following stack trace : :editor HP32201A.07.20 EDIT/3000 SUN, JAN 26, 1992, 12:49 PM (C) HEWLETT-PACKARD CO. 1990 /:debug DEBUG/iX B.79.06 HPDEBUG Intrinsic at: a.0096f9c4 hxdebug+$144 $39 ($43) nmdebug > tr PC=a.0096f9c4 hxdebug+$144 * 0) SP=40332b10 RP=a.009787b4 exec_cmd+$7e4 1) SP=40332690 RP=a.0097a244 try_exec_cmd+$c8 2) SP=40332640 RP=a.00977e3c command_interpret+$358 3) SP=403321e8 RP=a.0097af64 xeqcommand+$198 4) SP=40331e80 RP=a.0097e370 prog_execute_cmd+$20 5) SP=40331e00 RP=a.0097e31c ?prog_execute_cmd+$8 export stub: a.0049dda4 COMMAND+$7d4 6) SP=40331dd0 RP=a.0049d5bc ?COMMAND+$8 export stub: a.004c2514 arg_regs+$28 7) SP=403317e8 RP=a.004a85cc nm_switch_code+$978 8) SP=403316b8 RP=a.00497aec Compatibility_Mode (switch marker frame) 9) SP=403312e0 RP=a.00909c64 outer_block+$150 a) SP=403310f0 RP=a.00000000 _traplib_version (end of NM stack) The first line of the TR output: PC=a.0096f9c4 hxdebug+$144 reports where the process is currently executing. PC is the Program Counter. The value is shown as a 64-bit address, with the "a" being the space ID, and the 0096f9c4 being the offset within space $a. Code addresses with a $a as the space ID are within NL.PUB.SYS, the kernel of the operating system. The second line: * 0) SP=40332b10 RP=a.009787b4 exec_cmd+$7e4 tells us a variety of things: - the "*" means: this is the stack marker associated with the program counter (pc). - the "0)" means: this is the "top of stack", or the most current stack marker. The next marker says "1)", meaning that it is one level "down" into the past. - the "SP=" tells us what the value of the top-of-stack pointer (SP) is for this marker. The value for the second marker (#1) is less than for the top marker (#0). The difference is the number of bytes that the top procedure allocated as a stack frame. - the "RP=" tells us where the current procedure will return to. Note: This part of the stack trace is the most misleading. It is tempting to look at a line like: 3) SP=403321e8 RP=a.0097af64 xeqcommand+$198 and think that the stack trace is telling us that when we return to xeqcommand+$198, the stack pointer will be $403321e8. Unfortunately, this is an artifact of a somewhat poor choice in the layout of the TR command's output. The line is actually telling us: when we return to command_interpret+$358 (we got this address from the "2)" line), the SP value will be restored to 403321e8 and the RP value will be restored to a.0097af64, which (in case we wanted to know) happens to be within xeqcommand. In the above stack trace, the ninth marker: 8) SP=403316b8 RP=a.00497aec Compatibility_Mode (switch marker frame) told us that DEBUG/iX found a stack marker that indicated that the process had switched from Compatibility Mode (CM) into Native Mode. When such a switch takes place, a marker is left on the CM stack and on the NM stack. Marker "8)" is an example of such an NM stack marker. Normally, the TR command assumes that you only want to see marker that are in the "current" mode (nm for this example). If you want to see both modes, the ",Dual" option can be used on the TR command: $3a ($43) nmdebug > tr,d PC=a.0096f9c4 hxdebug+$144 NM* 0) SP=40332b10 RP=a.009787b4 exec_cmd+$7e4 NM 1) SP=40332690 RP=a.0097a244 try_exec_cmd+$c8 NM 2) SP=40332640 RP=a.00977e3c command_interpret+$358 NM 3) SP=403321e8 RP=a.0097af64 xeqcommand+$198 NM 4) SP=40331e80 RP=a.0097e370 prog_execute_cmd+$20 NM 5) SP=40331e00 RP=a.0097e31c ?prog_execute_cmd+$8 export stub: a.0049dda4 COMMAND+$7d4 NM 6) SP=40331dd0 RP=a.0049d5bc ?COMMAND+$8 export stub: a.004c2514 arg_regs+$28 NM 7) SP=403317e8 RP=a.004a85cc nm_switch_code+$978 NM 8) SP=403316b8 RP=a.00497aec Compatibility_Mode (switch marker frame) CM SYS % 205.7317 SWITCH'TO'NM'+%4 (MItroC CCG) SUSER1 CM * 0) SYS % 205.7317 SWITCH'TO'NM'+%4 (MItroC CCG) SUSER1 CM 1) SYS % 140.4430 COMMAND+%43 (MItroC CCG) CISEG1 CM 2) PROG % 7.1407 (mItroc CCG) CM 3) PROG % 7.1535 (mItroc CCG) CM 4) PROG % 7.3404 (mItroc CCG) CM 5) PROG % 7.30 (mItroc CCL) CM 6) SYS % 145.0 ?TERMINATE (MItroc CCG) CMSWITCH NM 9) SP=403312e0 RP=a.00909c64 outer_block+$150 NM a) SP=403310f0 RP=a.00000000 _traplib_version (end of NM stack) Or, you can manually switch to the opposite mode (in this example, CM), and ask for a simple TRace: $3b ($43) nmdebug > cm %74 (%103) cmdebug > tr SYS % 205.7317 SWITCH'TO'NM'+%4 (MItroC CCG) SUSER1 * 0) SYS % 205.7317 SWITCH'TO'NM'+%4 (MItroC CCG) SUSER1 1) SYS % 140.4430 COMMAND+%43 (MItroC CCG) CISEG1 2) PROG % 7.1407 (mItroc CCG) 3) PROG % 7.1535 (mItroc CCG) 4) PROG % 7.3404 (mItroc CCG) 5) PROG % 7.30 (mItroc CCL) 6) SYS % 145.0 ?TERMINATE (MItroc CCG) CMSWITCH %75 (%103) cmdebug > = pin %103 %76 (%103) cmdebug > c /:comment: we are back to the EDITOR prompt, and we /:comment: know that :EDITOR's PIN is 67 (octal %103). When we say that a process that is "in Compatibility Mode", we mean that it is either executing CM instructions via the emulator or that it is executing CM instructions that have been translated to NM instructions via the Object Code Translator (OCT). In either case, the emulator or the translated code will often jump into small NM assembly "helper" routines. When we ask DEBUG/iX to trace the stack for a process that happens to be in some of these routines, the TR command may be unable to correlate the helper routine with the original CM (or OCT) instruction address. The following shows an example that was continued from the :EDITOR example immediately above: /:comment: text in catalog.pub.sys, hit BREAK while busy... / /t catalog.pub.sys (** hit the BREAK key **) :comment ... forgot our PIN, find out with :showproc :showproc QPRI CPUTIME STATE JOBNUM PIN (PROGRAM) STEP C152 0:04.308 READY S87 45 :SHOWPROC C200 0:04.975 READY S87 67 (EDITOR.PUB.SYS) :comment: enter DEBUG/iX from the CI's prompt... :comment: use the PIN command to switch to :EDITOR's :comment: process and then do a stack trace... : :debug HPDEBUG Intrinsic at: a.0096f9c4 hxdebug+$144 $41 ($2d) nmdebug > pin #67 Capture_Cmstate (common): Q := (cmQ-SB.va) div 2 -------------------------------------------------------- Unrecoverable error encountered while capturing the CM state. An unexpected escape was detected during an arithmetic operation. The CM state is undefined due to invalid data. The original state save area is probably corrupt or invalid. ------------------------------------------------------- Special CM stack marker cap. CM was interrupted Cap has been removed. ISM used to get CM state. Notice the warning from DEBUG/iX, telling us that it had trouble determining the CM "state" of process #67. Note the ",I" on the TR command. An ordinary TR command would produce a stack trace that stops after the line: --- End Interrupt Marker Frame --- The ",I" tells the TR command to do extra work and keep tracing across the interrupt marker. ",I" is short for ",INTERRUPTS", which refers to Interrupt Stack Markers. $42 ($43) nmdebug > tr,i,d PC=a.00357a44 pm_interrupt_handler.handle_break+$100 NM* 0) SP=403316a0 RP=a.00357cc0 pm_interrupt_handler.handle_pm_service+$68 NM 1) SP=40331610 RP=a.00357d88 pm_interrupt_handler+$78 NM 2) SP=403315d0 RP=a.004ce038 pi_call_0_handler+$70 NM 3) SP=40331550 RP=a.004cdf94 ?pi_call_0_handler+$8 export stub: a.004ce5c4 execute_interrupt+$190 NM 4) SP=40331508 RP=a.002a6688 process_int+$34 NM 5) SP=40331410 RP=a.000e5038 hpe_interrupt_marker_stub --- Interrupt Marker NM 1) SP=403313d8 RP=a.00489494 Errorexit_Routine+$20 --- End Interrupt Marker Frame --- PC=a.00489494 Errorexit_Routine+$20 NM 0) SP=403312e0 RP=a.00497aec Compatibility_Mode (switch marker frame) CM SYS % 211.13304 DBINARY+%310 (mItroc CCG) UTILITY CM * 0) SYS % 211.37776 (mItroc CCE) UTILITY CM 1) PROG % 4.3236 (mItroC CCE) CM 2) PROG % 7.2563 (mItroC CCG) CM 3) PROG % 7.3404 (mItroc CCG) CM 4) PROG % 7.30 (mItroc CCL) CM 5) SYS % 145.0 ?TERMINATE (MItroc CCG) CMSWITCH NM 1) SP=403312e0 RP=a.00909c64 outer_block+$150 NM 2) SP=403310f0 RP=a.00000000 _traplib_version (end of NM stack) $page "Chapter 4: Data: Displaying & Modifying" Chapter 4 Data: Displaying & Modifying This section discusses the DEBUG/iX commands for viewing and modifying data. 4.01 DV Command ---------------- Data can be viewed in a variety of manners. The basic syntax for the Display Virtual command (DV) is: dv address [#words] [base] [#words/line] [#bytes/"word"] Note: DEBUG/iX quietly rounds your address down to the nearest multiple of 4. This can be quite misleading if you were trying to display data starting at an address of the form n*4+2, which is typical of 50% of the shortint variables in a Pascal/iX program. The #words to display defaults to 1. The output base defaults to the current OUTBASE value (typically hex for NM, octal for CM). Base options are: ASCII, Both, Code, Decimal (or #), Hex (or $), Octal (or %), and String. The base may be abbreviated to a single letter. Two bases are particularly useful at times: Both and String. The Both base shows the output in hex (or whatever the current OUTBASE value is) and in ASCII, four 32- bit words per line. The String base displays the data as ASCII text (with nonrenewable displayed as dots) with no blanks every 4 (or 2) characters. If the String base is used without the #words/line option, then DEBUG/iX will simply display the starting address, a quote ("), and then all of your data as ASCII (line after line, with no addresses), followed by a trailing quote (") at the end. If you use the #words/line option with the String base, then it will put a quote (") after that many words, and then (on a new line) display the next address, a quote ("), and continue. A particularly useful combination of String and #words/line is: dv address, #words, S, $e The value of $e for #words/line results in the maximum amount of text that will cleanly fit on an 80-character line. Examples: dv dp + 18 dv dp + 100,#20,,,2 4.02 Other "D" commands ------------------------ DEBUG/iX has many commands to display data, all beginning with "D". These include: dr Displays the register (for your current mode). ddb ddq dds dd dseg.offset [#halfs] [base] [..more..] Displays 16-bit words within the specified data segment. The offset is interpreted as a 16-bit word offset. dz address [base] [#words] [words/line] [bytes/"word"] Displays absolute memory ("Real" addressing), starting at the byte address specified (Note: rounds down to the nearest multiple of 4). dc address [#words] Displays code, starting at the address specified. This command has three quirks: - if address is not a multiple of 4, then DEBUG/iX rounds down to the nearest multiple of 4 and, if #words = 1, increases the #words to 2. - if a breakpoint (for your process or system-wide) exists at any of the addresses displayed, then you will see the actual break instruction, not the original instruction. This differs from doing a "PJ" to the code address in the Program window, which will show the original instruction. - If "address" is an SR4 relative short address (i.e.: if the upper 2 bits of the 32-bit address are 00), and you are not currently "in" your main program (i.e.: if SR4 does not have your program file's space id), then DEBUG/iX goes out of its way to be friendly and quietly uses your original program's SR4 value to make the address a 64-bit value. This can be quite annoying, particularly when you are trying to examine code in a library (or NL.PUB.SYS). The workaround for this "feature" is to specify the address as "sr4.address", which relieves you from having to type in the current sr4 value. da address [#words] [...more...] Displays the Compatibility Mode Absolute memory, which is the MPE/iX simulated version of bank 0 memory on a Classic HP 3000. For a complete list of data "display" commands, do: CMDL ,display 4.03 Modifying Data -------------------- This section briefly discusses techniques for modifying data. The basic command to modify data at a virtual address is the "MV" command. Its syntax is: mv address [#words] [base] [VALUE value2 value3 ... ] If new values are not provided, DEBUG/iX will prompt for them by first displaying the address, then the current value in both hex (or the specified base) and ASCII. Note: Unless the "quiet_modify" environmental variable is set to true, the MV command will echo the old values even if the new value is provided on the command line. You can give a register a new value with the MR command: MR register [value] Like the MV command, MR will prompt for a new value. If you are "sitting" at the start of a procedure that you would like to avoid executing, the MR command can be used to skip over the code in the procedure as follows: mr pc, r2 c The above can be combined with a breakpoint to automatically skip calling a particular routine. For example, to prevent a program from successfully calling the NM QUIT intrinsic, try: b QUIT, , , {mr pc,r2; c} This trick is not applicable to CM code. The following macro will quietly increment the value stored at a specified virtual address: macro inc(xxxx) {Loc save_qm quiet_modify; env quiet_modify true; ignore loud; mv xxx,1,,[xxx] + 1; env quiet_modify save_qm; } $page "Chapter 5: Windows" Chapter 5 Windows DEBUG/iX supports multiple windows, which are horizontal partitions of the top 23 lines of a 24-line terminal screen. It uses the terminal's memory-lock to freeze the window area, allowing your input/output to scroll from line 24 to "above" the display screen. In addition to the default NM windows ("GR" for General Registers, and "nmP" for the program code), if you are at a breakpoint at the entry to a procedure, creating a virtual window with the following command will result in two lines of hex data that show the "left-most" 16 words of arguments (after the arguments in registers R26..R23 are stored into memory): vw psp-$60 rags If your procedure is optimized, then it may not store registers R26..R23 to memory. $page "Chapter 6: Environmental Variables" Chapter 6 Environmental Variables DEBUG/iX has a number of pre-defined variables called "environmental" variables. (In MPE XL 3.0, the number is 88 or 233, depending on which ones you count.) The values of these variables can be accessed just by using their names (i.e.: exactly like any other variable), but you must use the ENV command to change a value. The ENVLIST command lists the environmental variables. Example: ENVLIST ... misc rW PRIV_USER : U16 = $1 win r PWS : U32 = $a misc rw QUIET_MODIFY : BOOL = FALSE win rw SHOW_CCTL : BOOL = FALSE io rw SS_TERM_KEEPLOCK : BOOL = FALSE misc rw SYMPATH_UPSHIFT : BOOL = TRUE misc r SYSVERSION : STR = 'C.16.01' io rw TERM_KEEPLOCK : BOOL = FA ... The first column shows the category (classification) of each variable. The second column shows if the variable can be read or written. "r" means any user can read the value. "R" would mean that only a privileged user could read the value. "w" means that any user can change ("write to") the value. A "W" means that only a privileged user can change the value. The third column lists the names of the variables. The fourth column shows the data type of each variable. The fifth column shows the current value of each variable. Here are a few of the environmental variables, and their meaning: ccode A string with the value "CCG", "CCL", or "CCE". Reflects the condition code setting when you entered DEBUG/iX. debug_recursion When "true", allows DEBUG/iX to be called by DEBUG/iX, but only to a total nesting depth of two. This can be used to investigate how DEBUG/iX works. entry_mode "nm" or "cm", reflecting the mode in which your process entered DEBUG/iX. error The most recent DEBUG/iX error number. filter A string variable, which is used to filter out (suppress) output lines. If filter is non-empty, then any output lines that do not contain the string in the filter variable are suppressed. job_debug A system-wide boolean variable that determines whether or not DEBUG/iX may be entered from a job. If it is true, and a batch job tries to enter DEBUG/iX, it will do its input/output on the hardware console (ldev 20). list_input A boolean variable that determines whether your input should be copied to the "list" file currently in use (if any). list_paging A boolean variable that determines whether a "new page" header should be written to the current "list" file (if any) every 60 (or so) lines. pin PIN of the current process being debugged. You can get a description of the use of most environmental variables by saying: help variablename. 6.01 Filter ------------ The filter variable is quite useful for screening out many lines of output. As an example, if you suspect that you have a single word of 0 (all 32 bits are 0) somewhere in the middle of a block of $4000 bytes of data, you can find it by doing: env filter '00000000' dv address, 1000 env filter '' The only lines that will print from the "dv" command are those that have the string "00000000" somewhere on them. 6.02 Job_debug --------------- If you want to use DEBUG/iX from a job (either as a command or as an intrinsic), you must first enable it by doing the following from a session: :debug env job_debug true c Now, when a batch job attempts to enter DEBUG/iX, its I/O will be done on the system console. As suggested earlier, you should consider locking up the console so that your DEBUG/iX I/O will not be intermixed with CI I/O. An easy way of doing this is: :restore or :pause 3600 $page "Chapter 7: Macros" Chapter 7 Macros DEBUG/iX provides a powerful programming language, with support for procedures and functions, looping, parameters (with optional values and type checking), and variables (both local and global). For some reason, DEBUG/iX refers to the procedures/functions that you write as "macros". This is a terribly misleading name, as they are not macros by any stretch of the definition of that term! The programming language is a cross between Pascal/iX and C. If you change Pascal's "begin" and "end" to "{" and "}", and throw away types, records, the "repeat", "case", and "try" statements, what remains is close to DEBUG/iX's language. 7.01 Library of Simple Macros ------------------------------ Anytime you do something twice in DEBUG/iX, you should consider writing a macro to do it, to save time in the future. As you develop macros, save them in un-numbered ASCII files. If you develop a large library, consider using DEBUG/iX's STORE command to save them in a "compiled" form, suitable for a later RESTORE. Macros are defined with the following syntax, where words in angle brackets (e.g.: ) are names picked by you, and items in brackets (e.g.: [type]) indicate optional items: macro [ <> ] { <> } A carriage return may be entered after the first left brace ({), and DEBUG/iX will continue prompting for input until the matching right brace (}) is found. A simple macro is: macro hi {wl "hello world"} When this macro is exeuted, the phrase "hello world" will be printed: hi "hello world" A sample macro with parameters is: mac twice (n) {wl "Two times ", n, " is: ", n*2} If I am starting a DEBUG/iX session to develop some complex macros, I will do it from within QEDIT as follows: :qedit ... create a flat-ASCII file with some macros in it. kq foo :debug /* call DEBUG/iX from within Qedit use foo /* load the macros from the file useclose /* only needed if you got an error while /* the USE command was reading the file. ... test macros... c /* go back to QEDIT ...edit (file is already in QEDIT's workspace)... kq use foo /* re-load the macros useclose /* only needed if error occurred ... test macros ... c I repeat the above cycle as often as necessary, until I have debugged my macros. DEBUG/iX allows you to run programs from within it, so you could have attempted to invoke your favorite editor from inside DEBUG/iX, edit, exit, and re-load and test your macros. There are two drawbacks to this approach: - expense of re-starting an editor each time you want one. - if the editor is "smart" (like QEDIT, SPOOK, MPEX), it will stay alive when exited (by calling ACTIVATE (0, 3)). Unfortunately, DEBUG/iX doesn't know that it now has an editor as a suspended child process. If you ever run QEDIT (or SPOOK or MPEX) from within a DEBUG/iX session, and then exit back to DEBUG/iX (leaving QEDIT (or etc) suspended), you may be able to re-activate it by doing: = nmcall ("ACTIVATE", qeditpin, 3) /* Note: the above requires that you have PM capability /* and are currently at ring 2. If "= priv" shows 0, 1, or 2, /* then you can probably use the command. (Remember: if you saw the QEDIT (or whatever) PIN in a :SHOWPROC command, it's probably in decimal...don't forget the "#" prefix to tell Debug that the PIN you are entering is in decimal!) Some useful macros are shown in Appendix A. These include: j Jumps through a procedure call. Use it when pc is at a "BL" instruction. (The procedure is still executed, but we don't have to single step through it. Instead, a temporary breakpoint is setup at the instruction that the procedure will return to.) find (typ:str, field:str) Lists only those fields from a Pascal type that contain the string in the parameter "field". E.g.: find ("system_globals_type", "pin") vfind (xxx:ptr, typ:str, field:str) Does an FV command, and lists only those output lines that contain the string in the parameter "field". E.g.: vfind (c0000000, "system_globals_type", "pin") show_hpfopen Displays actual parameters to an HPFOPEN call. 7.02 Macro Writing Guidelines ------------------------------ Writing a macro is like writing a procedure in Pascal: paying attention to coding style will provide long-term benefits. Like Pascal, statements are separated by a semicolon (in C, some statements are terminated by semicolons). Here’s an example: macro show_stuff { loc ktr 0; while ktr < #100 do { dv r4 + ktr, 4, b; /* note the ";" loc ktr ktr + 4; /* ";" not necessary, since next is "}" }; /* ";" above needed to separate "while" stmt from "wl". wl "done with loop"; /* ";" not necessary } In the above example, two of the semilcolons weren’t necessary, but it's still a good idea to put them there because it makes adding a new line AFTER those lines much easier! Note the use of "loc" to define and change the variable "ktr". "loc" creates/changes the value of a local variable, as opposed to "var" which creates/changes the value of a global variable. Use local variables where possible, since they will be deallocated when the macro terminates. 7.02.01 !Variables ------------------- When you are using a variable (local, global, or environmental) within a macro, it does *not* need a leading "!" character, except in rare circumstances. Despite this, if you look at the macros that accompany the SYMOS file for various releases of MPE/iX, you will see this abuse many times. This is possibly attributable to a lack of understanding about the need for the "!", or to changes made to DEBUG/iX over the years. The "!" is only necessary when you want DEBUG/iX to evaluate the value of a variable (or macro) within a quoted string that is being used in a symbolic command/function (i.e.: FT, FV, and the SYM@ functions). (Or, when you are using a variable name that looks like a hex constant (e.g.: fac).) Thus, the following two statements are equivalent: var ktr ktr + 1; var ktr !ktr + 1; 7.02.02 Numeric Constants -------------------------- If you are using a numeric constant that is not in the range -7..7, then you should *always* qualify it with a base prefix (e.g., "$", "#", or "%"). If you fail to do this, your macro will act strangely when used while an input base is in effect other than what you expected. The following macro demonstrates the problems that a lack of a base-prefix can cause: macro ten {return 10} = ten, # /* call "ten", print result in decimal #16 cm /* switch to compatibility mode = ten, # /* call "ten", print result in decimal #8 Without a leading "#", the constant "10" is *NOT* what we would normally think of as decimal ten. Instead, it is a number composed of two digits: a one (1) and a zero (0). At the time when an integer value is needed, the string "10" is converted to an internal binary value according to the current default input base (see the environmental variables INBASE, NM_INBASE, and CM_INBASE). If the current input base is decimal, then the number is 1 * #10 + 0 * 1, or the value #10. If the current input base is hexadecimal (16), then the number is 1 * #16 + 0 * 1, or the value #16 (or $10). 7.02.03 Error Handling ----------------------- When writing any kind of code, you should always be aware of the possibility of errors. This is true in DEBUG/iX as well as in Pascal/iX, or C, or any other language. Pascal/iX provides the TRY/RECOVER facility to catch unintentional errors. DEBUG/iX has the "ignore" command, similar to the CI's :CONTINUE statement. The following DEBUG/iX code fragment shows how to catch and detect errors: ... env error 0; /* forget about any prior errors ignore quiet; /* don't let any error in the next stmt /* kill us /* (quiet --> don't report any error) { /* we are doing a compound statement ... /* The code in question. /* any error within the braces will pop us /* out to just after the "}". }; if error <> 0 then { /* non-0 means an error /* occurred. wl errmsg (error); /* report error message ... act on the error }; 7.02.04 Parameter Default Values --------------------------------- Parameters to macros may be declared with default values, as in the following example: mac add_constant (yyy, xxx = 10) {return xxx + yyy} However, unlike constants within the body of a macro, the default value expression is evaluated at macro definition time. Thus, the above macro would behave as follows: set hex mac add_constant (yyy, xxx = 10) {return xxx + yyy} = add_constant (0), # #16 set dec = add_constant (0), # #16 Thus, the "10" default value was evaluated as a hex number at macro definition time. 7.02.05 Macro Return Values ---------------------------- Every macro returns a value. If you aren't using a DEBUG/iX command that expects a result, DEBUG/iX quietly throws away the value. If your macro did not have a "return" statement, then the value of the macro is 0. $page "Chapter 8: Finding Parameters" Chapter 8 Finding Parameters Note: this section deals only with NM code. The Procedure Calling Convention defines the basic method of passing parameters to a procedure. The short description, which works for many cases, is: the first parameter is in register R26, the second is in register R25, the third parameter is in R24, and the fourth parameter is in R23. Functional Results (to be written) $page "Chapter 9: Compatibility Mode" Chapter 9 Compatibility Mode This section briefly discusses some Compatibility Mode aspects of using DEBUG/iX. Note: split-stack CM debugging is not discussed. 9.01 Intrinsic/Procedure names ------------------------------- CM procedure (and intrinsic) names should be qualified with a "?", as in: ?FOPEN If a procedure/intrinsic name is used without a leading "?", then DEBUG/iX interprets that as the address of the first instruction of the entire procedure ... including subroutines and stack building code. Procedures written in Pascal/V, or those in SPL/V that have alternate entry points or subroutines will have different values for their names and ?names, as shown in the following example (taken from MPE XL 3.0): cm = FOPEn /* upper/lower case doesn't matter SYS %146.4536 = ?fopen /* upper/lower case doesn't matter SYS %146.4542 In the above example, if you were to set a breakpoint at FOPEN, it might not be hit, even if your program calls the FOPEN intrinsic. If you set a breakpoint at ?FOPEN, it will be hit when FOPEN is called. 9.02 DB-Relative Byte Addresses -------------------------------- Debug/iX lacks a "DdBB" (Display DB Byte relative) command, but you can display the data at a DB-relative byte address easily. If you are willing to assume that the byte address is not a DL-DB address (i.e., that it is a non-negative value), you can do: ddb u16 ( the_address ) / 2, ... For example, let's say you're at a breakpoint to entry of the CM FOPEN intrinsic. The filename parameter is at Q-%22 in the stack. You can display the filename by doing: ddb u16 ([q-%22]) / 2, 20, s If we examine the individual pieces of the above: q-%22 :: take the value of the Q register, subtract %22 (since Q holds an address, Q-%22 is also an address) [q-%22] :: use the address Q-%22 and fetch a 16-bit value from memory at that address. The result is considered to be a signed 16-bit integer. u16 ([q-%22]) :: take the value we just fetched, and treat it like an unsigned 16-bit integer. u16 ([q-%22]) / 2 :: divide the unsigned 16-bit value by 2. This converts it from a DB-relative non-negative byte address to a DB-relative non-negative halfword address (what used to be called a "word address" on the Classic HP 3000). 9.03 OCT --------- If you are in CM and define a breakpoint, DEBUG/iX will set one up at the exact location you specified. In addition, if the code has been Object Code Translated (OCT'ed), it will set one up at the closest corresponding native mode address (rounding down, if it cannot find an exact match). When you hit a breakpoint in a piece of translated code, you enter DEBUG/iX in "nm" mode, not in "cm" mode. As a result, things look strange to the macros or breakpoint commands that were expecting a CM environment. Since most of SL.PUB.SYS has been OCTed, any breakpoints you set in CM intrinsics are likely to be "hit" in native mode. When setting breakpoints in such intrinsics, you can anticipate that problem by setting the breakpoint as follows: b ?FOPEN, , , cm /* switch to cm when hit 9.04 Data Breakpoints in CM stack ---------------------------------- Data Breakpoints can be set using CM addresses, with a little trick. If, after reading the warnings about data breakpoints in the earlier sections, you still want to set a data breakpoint within your CM stack, here is how to do it. First, determine the DB-relative address you want to watch. (For this example, let's assume it is DB+%123.) Second, determine the virtual address of DB+0: tdb nn /* translates DB address nnn to virtual address /* e.g.: tdb 0 translates DB+0 to virtual Thus: tdb 0 DB+0 VIRT $788.416160b0 An alternative method of determining a virtual address for a DB relative address is to determine where DB+0 is by looking at the "CM global" information for your process: nm /* switch to NM cmg /* display your CM global information ... db_sid : 38d db_offset : 40311fb0 ... The above takes us to the third step in setting up a data breakpoint for our CM data: datab $38d.$40311fb0 + %123 * 2, 2 /* watch 2 bytes Note: there may be some reason to believe that if your process opens any files after you set a data breakpoint, the effective DB+ address of the data breakpoint may "slide" towards DL. However, recent (MPE/iX 5.5) testing fails to show this happening. You can avoid the separate "+ nnn * 2" step by specifying a non-zero value to the TDB command: tdb %123 DB+$53 VIRT $788.41616156 $page "Chapter 10: Power Debugging" Chapter 10 Power Debugging This section discusses various "power debugging" techniques. 10.01 Breakpoints in NM Intrinsics & System Procedures ------------------------------------------------------- DEBUG/iX makes it easy to set breakpoints in "intrinsics" and other system procedures. On MPE V, the word "intrinsic" was used in a loose manner, and often referred to undocumented internal procedures inside MPE as well as to those procedures that were documented in the Intrinsics Manual. On MPE/iX, the word intrinsic usually refers only to documented, supported procedures. Setting a breakpoint at the entry to an intrinsic (or any procedure) is quite easy, although it does require that you (not the program being debugged) have PM capability. Most intrinsics have uppercase names. Exceptions include the TurboIMAGE intrinsics, the V/Plus intrinsics and all C/iX library functions. Examples: = FOPEN SYS $a.e9bdbc = dbopen Invalid expression for calculator. (error #1408) /* the above failed because our process was not loaded /* using XL.PUB.SYS (where the IMAGE intrinsics are), /* so the procedure name "dbopen" is unknown to Debug. findproc dbopen xl.pub.sys /* the above dynamically loads XL.PUB.SYS to find "dbopen". = dbopen PUB $21d.240768 /* now Debug knows the symbol "dbopen". The code for the majority of all of the intrinsics is in NL.PUB.SYS. TurboIMAGE and V/Plus intrinsics are in XL.PUB.SYS, as are most of the C library routines and Pascal/iX support routines. (This is normally not a concern, and was mentioned as background information.) Intrinsics (and other procedures) that are called by code outside their library (e.g.: called by your program) are entered via a short piece of code called an "export stub". The name of an export stub for a given intrinsic (or other procedure) is formed by putting a question mark in front of the procedure name. Thus, ?FOPEN is the stub that our program would jump to in the process of calling the FOPEN intrinsic. (Note: This differs from how the question mark is used with CM procedure names.) The only calls to an intrinsic (or other procedure) that do not go through an export stub are those that are from within the same file. Thus, if FOPEN calls FWRITE, it will be direct, and not through a stub. (There are rare exceptions to this, but they will not affect our debugging.) Knowing about stubs is useful: it makes setting a breakpoint at the return of an intrinsic (or other procedure) easy. We can set a breakpoint at the return from any intrinsic (or other procedure) with: b ?name + 8 Where "name" is the name of the intrinsic/procedure. Note: This will catch any calls from outside the module that contains the intrinsic (as well as dynamic calls from inside the module which are pretty rare). It works because an export stub looks like the following code fragment: ?name BL name, 2 ?name+4 STW 2, -??(30) ?name+8 ... (the return address!) ?name+c ... ... (i.e.: an export stub starts with a branch to the actual entry point of the intrinsic/procedure.) This technique will not "catch" calls to a procedure from within the same code module. 10.01.01. Breaking NM Intrinsics - Entry ----------------------------------------- Intrinsics are the procedures that most programs use to interface with MPE/iX. It is often desirable to set a breakpoint at the start of an intrinsic in order to view (and, perhaps, change) the parameters to the intrinsic. As an example, let's consider a program that aborts after reporting an error like "Nonexistent permanent file". What file was it trying to open? Did the programmer call FOPEN with the correct foptions and aoptions?. (Or, was HPFOPEN called?) We will start debugging this program by examining calls to FOPEN as follows: Note: The following assumes we are logged in to a user with PM capability. :run prog; debug b FOPEN c Now, when the program calls FOPEN, we will pop into debug. If we want to look at the filename, foptions, and options parameters, we need to know how they are passed into FOPEN. By examining the Intrinsics Reference Manual, we find that FOPEN is defined as: I16 CA U16V U16V I16V filenum :=FOPEN (filename, foptions, aoptions, recsize, CA CA I16V I16V device, formmsg, userlabels, blockfactor, I16V I32V I16V I16V numbuffer, filesize, numextent, initialloc, I16V filecode); Or, we can use the CSEQ tool from Lund Performance Solutions: :cseq FOPEN CSEQ [2.9] - LPS Toolbox [A.06c] (c) 1995 Lund Performance Solutions Function FOPEN ( filename : anyvar record ; {R26} := nil foptions : uint16 ; {R25} := 0 aoptions : uint16 ; {R24} := 0 recsize : int16 ; {R23} := 0 device : anyvar record ; {SP-$0034} := nil formmsg : anyvar record ; {SP-$0038} := nil userlabels : int16 ; {SP-$003a} := 0 blockfactor : int16 ; {SP-$003e} := 0 numbuffers : int16 ; {SP-$0042} := 0 filesize : int32 ; {SP-$0048} := 0 numextents : int16 ; {SP-$004a} := 0 initialalloc : int16 ; {SP-$004e} := 0 filecode : int16 ) {SP-$0052} := 0 := file# : int16 {R28} { CCE: Filed opened } { CCL: File not opened...do: FCHECK (0, err) for why.} {if ok, file# is > 0 (0 indicates error) } {filename: terminate with blank or null. } {foptions: (16-bits) } { 00:02 (reserved) } { 02:03 file type: 0=standard, 1=KSAM/V } { 2=RIO, 3=KSAM/XL, 4=CIR, } { 5=NM Spool, 6=MSG, 7=KSAM64 } { 05:01 1=Disallow File Equates, 0=Allow } { 06:01 1=Labelled tape } { 07:01 1=CCTL } { 08:02 Record format: 0=fixed, 1=Var, } { 2=Undefined (useful for tapes) } { 10:03 Designator: 0=use filename, } { 1=$STDLIST, 2=$NEWPASS, 3=$OLDPASS } { 4=$STDIN (don't use), 5=$STDINX, } { 6=$NULL. } { 13:01 1=ASCII, 0=Binary } { 14:02 Domain: 0=new, 1=OldPerm, 2=OldTemp, } { 3=Old (Temp first, Perm second) } {aoptions: (16 bits) } { 00:03 (reserved) } { 03:01 1=COPY mode } { 04:01 1=NOWAIT } { 05:02 Multiaccess: 0=NOMULTI,1=MULTI,2=GMULTI } { 07:01 1=NUBUF } { 08:02 Exclusive: 0=default, 1=EXClusive, } { 2=SEMI (read-share), 3=SHR } { 10:01 1=LOCK } { 11:01 1=MR, 0=NOMR. Note: Intrinsics manual } { incorrectly calls this [NO]MULTI. } { 12:04 Access type: } { 0 = IN (read) } { 1 = OUT (write, resets EOF to 0) } { 2 = WriteSAVE (don't reset EOF to 0) } { 3 = APPEND } { 4 = INOUT (read/write) } { 5 = UPDATE } { 6 = eXecute (needs PM) } { 7 = load program (needs PM) } { 8 = no acc check (needs PM) } { 9 = dir read (needs PM) } {recsize: < 0 = bytes, > 0 = 16-bit words } {device: terminate with a blank or null. } {formmsg: up to 49 bytes, terminate with period } {userlabels: 0..254 } {blockfactor: 1..255 } {numbuffers: } { 04:07 = # spooler output priority (1..13) } { 04:07 = # spoolfile copies (0..127) } { 11:05 = # buffers (1..31, or less for } { Cir, RIO, MSG, KSAM/V, KSAM/XL) } {filesize: default = 1023 } {numextents: only 1 or >1 are meaningful } {initialalloc: only meaningful if = numextents } {filecode: if < 0, must be in PM. } uncheckable_anyvar We find that the first three parameters to FOPEN are: filename ... byte address passed in R26 foptions ... 16-bit logical, passed in R25 aoptions ... 16-bit logical, passed in R24 So, we can now write a macro, show_fopen, which will display information about the FOPEN call (when the macro is used while at the entrypoint of FOPEN): macro show_fopen { if r26 = 0 then w "" else w "File: ", [r26]:"A", [r26 + 4]:"A", [r26+8]:"A"; /* above will show first 12 bytes of file name wl ", foptions = ", r25, ", aoptions = ", r24; } Now that we have the show_fopen macro, we can setup a more useful breakpoint: b FOPEN, , , show_fopen 10.01.02 Breaking NM Intrinsics - Exit --------------------------------------- Sometimes, we want to change the result returned by an intrinsic. This result may be a 32-bit integer, a condition code, or the data put into a reference parameter. This can be done by setting a breakpoint at the exit from an intrinsic. Let's take the example program from the prior section, and try to examine the result of FOPEN. If we are only interested in calls to FOPEN from the program (and from any library it uses) and *not* any calls from within NL.PUB.SYS, then we have an easy method to set a breakpoint at the exit from FOPEN: b ?FOPEN + 8 This works because calls to FOPEN (or any other intrinsic procedure in NL.PUB.SYS) from outside NL.PUB.SYS do not jump directly into the NL. Instead, they go through an import stub ("caller stub") in the caller's code, and eventually wind up in an export stub ("callee stub") in NL.PUB.SYS. The symbolic name for the export stub for any procedure in NL.PUB.SYS is the procedure name with a question mark in front of it (e.g.: ?FOPEN). Export stubs always start with the same two instructions (BL and DEP): dc ?FOPEN, 3 SYS $a.4a5300 004a5300 ?FOPEN e8400068 BL FOPEN,2 004a5304 ?FOPEN+$4 d45f0c1e DEP 31,31,2,2 004a5308 ?FOPEN+$8 4bd53fc9 LDW -28(0,30),21 Thus, when FOPEN is called from our program, we jump to ?FOPEN, ?FOPEN+4, FOPEN, FOPEN+4, ..., and eventually FOPEN returns to ?FOPEN+8. An alternative way of deriving this information is to do a "TR" command when sitting at the entry point of FOPEN: b FOPEN added: NM [1] SYS a.004a533c FOPEN c ... Break at: NM [1] SYS a.004a533c FOPEN tr PC=a.004a533c FOPEN * 0) SP=4034bd28 RP=a.004a5308 ?FOPEN+$8 export stub: 5b5.000c61e0 keep_open_file+$220 1) SP=4034bd28 RP=5b5.000d36ec keep_command+$678 2) SP=4034bb08 RP=5b5.000a9f18 main_perform_co.execu+$318 3) SP=4034b8e8 RP=5b5.000aab48 EN_main_perform_co+$668 4) SP=4034b6c8 RP=5b5.000aaea0 main_command+$bc 5) SP=4034b4a8 RP=5b5.00100d90 EX_PROGRAM The line with the "0)" shows our return address: ?FOPEN+8 If we are interested in all calls to an intrinsic, including those from within the same library, then we have to work harder at finding the correct location for a breakpoint. For most languages, perhaps all, a procedure (or function) will have only one exit point, even if the language supports multiple apparent exits (e.g.: "return" in C, FORTRAN, or SPLash!). We can find this in a crude manner using debug's DC command as follows: env filter 'BV' dc FOPEN, 400 A typical result from the above DC is: 004a53d4 FOPEN+$98 e840c000 BV 0(2) 004a53f8 xopen_version e840c002 BV,N 0(2) 004a5418 XOPEN_08.16.89 e840c002 BV,N 0(2) We want the last "BV" opcode that shows up as being at FOPEN+### during disassembly. When we find it, we need to check if it is truly the last BV in the procedure by doing: dc FOPEN+###, 4 which might result in: SYS $a.4a53d4 004a53d4 FOPEN+$98 e840c000 BV 0(2) 004a53d8 FOPEN+$9c 37de3f31 LDO -104(30),30 004a53dc ?xopen_version e8400028 BL xopen_version,2 004a53e0 ?xopen_version+$4 d45f0c1e DEP 31,31,2,2 If the second line after the BV still reflects the name of FOPEN (or whatever procedure we are examining), then we didn't find the last one in the procedure and we need to look further. Exercise for the student: write a macro to find the last BV for an arbitrary procedure. Hint #1: the BV instruction is usually instruction $e840c000 or instruction $e840c002 Hint #2: nmproc. Hint #3: unwind descriptors. Hint #4: not all hints may work. 10.02 Breakpoints in System Code --------------------------------- In general, most procedures in NL.PUB.SYS (and XL.PUB.SYS) are safe for setting breakpoints. However, as in MPE V, there are some routines that will kill the machine if you set a breakpoint in them. These include: cb_lock (and, probably, any procedure beginning with "cb_"), and disable_interrupts. If you set a breakpoint in some procedure and then the system crashes with a "Bx00" death (either immediately or when you probably hit the breakpoint), use SAT or DAT to do a stack trace as: tr, i, d If the stack trace shows dozens of interrupt markers, then you probably found one of the procedures that you shouldn't breakpoint. 10.03 Invisible Symbolic Addresses ----------------------------------- Sometimes, the debugger doesn't seem to be able to "see" a symbolic address that you know exists. For example, you might have windows turned on and be looking at an interesting piece of code in the nmP window. If you try to set a breakpoint at the address shown (for this example, let's say it is "fred+10") and DEBUG/iX gives you an error implying that the name is unknown, then one of the following is probably happening: 1) The symbolic name (fred) is not the name of an exported procedure. If "fred" is part of a procedure written in assembler, then it may be an occurrence of a label, and not the entry to a procedure. Or, "fred" could be a procedure that was marked as being internal to the module. In either case, the following may work: b nmaddr ("fred", "any") However, when you use the "any" option with the nmaddr function, be prepared to wait up to a minute for DEBUG/iX to search for the name! 2) The symbolic name is actually a nested procedure name. You can determine if this is the case in two ways: - look at the title bar for the nmP window (if "fred" is showing there) after using PF/PB command to make "fred" be the top line of the window. If "fred" is a nested procedure, the title bar will show most of the fully qualified name. If "fred" is a nested procedure (e.g.: flintstone.fred), then you can set a breakpoint symbolically by specifying the entire "path" as follows: b nmaddr ("flintstone.fred") - take the hex address of "fred" (e.g.: a.12340) and do: = nmaddr (a.12340) Or, rather than determining the symbolic name, you could give up and set the breakpoint using the hex address: b a.12340 If the address is entered correctly, a breakpoint indicator should pop up in the nmP window. A simple and quick way to set a breakpoint at the code address that appears at the top of the nmP window is: b pw ("pw" is the DEBUG/iX variable that tracks the address of the top of the nmP window.) Another problem that sometimes occurs in trying to set a breakpoint in a procedure in NL.PUB.SYS is that you accidentally get instead a procedure with the identical name in XL.PUB.SYS. This will happen, for instance, if you wanted to set a breakpoint at the procedure U_nonlocal_escape (run- time support for non-local ESCAPE statements). The workaround is to fully qualify the name of the procedure with the name of the file it is in and use the nmaddr function: b nmaddr ("nl.pub.sys/U_nonlocal_escape") 10.04 trap_handler ------------------- If your program is aborting due to an internal trap (e.g.: invalid address alignment), or is having problems with "unexpected escapes", the single most useful internal procedure to know about is trap_handler. Whenever a hardware trap occurs (including Pascal/iX range checking code) which would abort you (or which would do an implicit "escape"), MPE/iX calls trap_handler. It is trap_handler that checks to see if your process has an xaritrap handler, or an xcodetrap handler, or a TRY/RECOVER block. Although the :SETDUMP command can help you by putting you in DEBUG/iX after your program has started to die, putting a breakpoint at trap_handler means that you are catching your program BEFORE it is aborted by MPE. The difference can be important. For traps in CM code, you need several breakpoints in SL.PUB.SYS. One breakpoint you can use is ?BOUNDSVIOLATION. 10.05 Measurement (Counting Instructions/Ticks) ------------------------------------------------ You can use DEBUG/iX to measure the performance of your code in two different methods: by counting the number of instructions used between two points, and by counting the number of clock cycles used (which is not the same as the number of instructions). For both examples, let's assume that you are measuring the cost of a procedure called "foo". 10.05.01 Counting Instructions ------------------------------- Get into DEBUG/iX as of the start of the procedure ("foo"). Example: b foo c foo?????????????????????????? Now, set a breakpoint at the return address and issue a Singlestep command as follows: b sr4.r2 s #1000000 = #1000000 - rctr, # The SingleStep command (with the parameter #1000000) tells DEBUG/iX to restart your process after setting the Recovery Counter (one of the control registers in the hardware) to #1000000 and arming the Recovery Counter Trap. After every instruction, the hardware will automatically decrement the recovery counter. When it hits 0, an interrupt occurs, throwing you back into DEBUG/iX. If your procedure takes less than #1000000 instructions, then you will come back to DEBUG/iX because you exited the procedure and hit the breakpoint. If that happened, then we can compute the number of instructions used by subtracting the current value of the recover counter ("Rctr" to DEBUG/iX) from the original value. Note: If #1000000 was not enough, then you will re-enter DEBUG/iX without a "Break" message. If this happens, do another "s #1000000" and (if that was sufficient) remember to add #1000000 to the result of the "=" later. Note: If your program gets a page fault, the cost of fetching the page into memory is *not* reflected in the "=" calculation. Nor is the time you spend waiting on any resource (e.g.: in a GETSIR call). 10.05.02 Counting Cycles ------------------------- One of the CPU's Control Registers, CR16, holds the value of a counter that is incremented once per clock tick. Unfortunately, DEBUG/iX cannot be used to check the value of this register as we did in the above section. This is due to the fact that a relatively large amount of time elapses between the interrupt that gets us into DEBUG/iX at the start of your procedure (which is when the register's value is picked up by DEBUG/iX) and the time when you finally say "C" to continue your procedure. A less accurate mechanism of timing a procedure is: /* assuming you are at the start of the procedure */ b r2 var save_proctime nmcall ("PROCTIME") c var cpu_millisecs_used = nmcall ("PROCTIME") - save_proctime Note: Using NMCALL requires MPE XL 2.2 (or later) and PM capability. Further, you should try the above at least once with just an "s" in place of the "c", so you can determine the cost of leaving/entering DEBUG/iX. If you have access to the assembler, or to AVATAR from SRN, or don't mind developing a few instructions by hand, you can fairly easily write a procedure with a couple of assembly level instructions to return the value of the clock counter Control Register. With this, you can add inexpensive timing logic to your code. 10.06 Debugging Other Processes -------------------------------- One of the more powerful features of DEBUG/iX is the ability to debug other processes. If you know the PIN (Process Identification Number) of another process, and if you have PM capability, you can work wonders. Techniques for obtaining the PIN of another process include: the SHOT program from SRN, the SHOWPROC command (MPE XL 2.2 or later, buggy), and the SHOWQ command (noting the job/session number displayed for the various PINs). Once you have the PIN for a process you want to look at, you can get a snapshot of where it is "at" by doing the following: :debug pin #123 (or whatever, remember to specify decimal, if appropriate) tr,i,d Sometimes the "PIN" command in DEBUG/iX will report a message like "unable to capture CM state for process". If this happens, and the stack trace seems incorrect, try switching to CM mode and doing a TR command again: :debug pin #123 cm tr 10.07 Capturing Runaway Processes ---------------------------------- The ability to debug another process, coupled with setting breakpoints for a specified PIN, provides a means for gaining control of a runaway process (e.g.: one that is stuck in an infinite loop). Determine the PIN for the process that is looping (e.g.: #123), and do the following from a session where you have PM capability: :debug pin #123 tr,i,d pin #123 /* important to do the PIN command before each /* TR command! tr,i,d If the process is part of a job, enter the following DEBUG/iX command: env job_debug true and, at the system console, do: :PAUSE 3600 this will "lock up" the terminal, so that no read is pending on it. This is important, since when the target process is "caught", it will enter DEBUG/iX and try to read from the hardware console (ldev 20). Once you have done a few TR (stacktrace) commands, and are familiar with the addresses in the stacktrace (e.g.: which ones might be changing), pick one of the first few addresses (preferably the top-most one) and do: b address:#123 /* where "123" is the pin c The process should hit the breakpoint you defined almost immediately and enter DEBUG/iX. Remember that most NM programs take a little time to enter DEBUG/iX the first time, so don't be surprised if it takes up to 30 seconds before the DEBUG/iX prompt appears. You can see if it is trying to enter DEBUG/iX by doing: pin #123; tr, i, d /* Note that the "pin" and "tr" commands are on the same line! If the process was running in a session other than your own, it should be prompting with the DEBUG/iX prompt on its terminal. If the process is in a job, and you prepared ldev 20 as discussed above, then it should be prompting on the hardware console. If the process is in your session, then a slight modification to the above description makes things easier: /* this will interrupt your process :showproc ;tree /* to determine your PIN :debug /* enter DEBUG/iX from the top-level CI pin #123 /* "switch" to your process tr,i,d /* the ",i" is important here. Look for an interrupt marker, above which is MPE/iX internal code (perhaps including a routine with the word "break" or "pm_interrupt_handler"), and below which is your code. Put a breakpoint at the code address shown just below the top interrupt marker. Now, issue the :RESUME command. Your process should hit the breakpoint immediately and pop up in DEBUG/iX shortly. Example: a process (PIN 123) is looping on another terminal. First, we enter Debug and look at the process: :debug pin #123; tr,i,d PC=a.0020cf98 Cleanup NM* 0) SP=41642958 RP=a.0032043c ticks_to_micro+$bc NM 1) SP=41642950 RP=a.0029ee90 get_proc_cpu_time+$2c NM 2) SP=416428d0 RP=a.00320180 proc_time+$18 NM 3) SP=41642890 RP=a.00219d80 PROCTIME+$b8 NM 4) SP=41642850 RP=a.00219cb4 ?PROCTIME+$8 export stub: a.00ea1f24 arg_regs+$28 NM 5) SP=41642790 RP=a.00e64b24 nm_switch_code+$94c NM 6) SP=41642660 RP=a.00e4fc5c SWT_RETURN (switch marker frame) CM SYS % 230.404 SWITCH'TO'NM'+%4 (MItroC CCG) SUSER1 CM * 0) SYS % 230.404 SWITCH'TO'NM'+%4 (MItroC CCG) SUSER1 CM 1) SYS % 166.2070 PROCTIME+%27 (MItroC CCG) CMSWITCH CM 2) PROG % 0.31 OB'+%31 (mITroC CCL) SEG' CM 3) SYS % 166.0 ?TERMINATE (MItroc CCG) CMSWITCH CM 4) UNKN % 0.0 (mitroc CCG) NM 7) SP=41642320 RP=a.00513ae8 outer_block+$1cc NM 8) SP=41642130 RP=a.00000000 (end of NM stack) Let's look again: pin #123;tr,i,d Dual trace allowed only from current mode of the process. DUAL ignored. PROG % 0.30 OB'+%30 (mITroC CCL) SEG' * 0) PROG % 0.31 OB'+%31 (mITroC CCL) SEG' 1) SYS % 166.0 ?TERMINATE (MItroc CCG) CMSWITCH 2) UNKN % 0.0 (mitroc CCG) and again: pin #123;tr,i,d PC=a.fff7a008 $RECOVER_END CALLX stub: a.00e64288 nm_switch_code+$b0 NM* 0) SP=41642660 RP=a.00e4fc5c SWT_RETURN (switch marker frame) CM SYS % 230.404 SWITCH'TO'NM'+%4 (MItroC CCG) SUSER1 CM * 0) SYS % 230.404 SWITCH'TO'NM'+%4 (MItroC CCG) SUSER1 CM 1) SYS % 166.2070 PROCTIME+%27 (MItroC CCE) CMSWITCH CM 2) PROG % 0.31 OB'+%31 (mITroC CCL) SEG' CM 3) SYS % 166.0 ?TERMINATE (MItroc CCG) CMSWITCH CM 4) UNKN % 0.0 (mitroc CCG) NM 1) SP=41642320 RP=a.00513ae8 outer_block+$1cc NM 2) SP=41642130 RP=a.00000000 (end of NM stack) From the above three stack traces, it looks like the loop is aroundr the CM code address "OB'+%31", where it's calling the CM PROCTIME intrinsic. We can "catch" it at the return from a PROCTIME call: pin #123; cm b 0.31 c /* exit Debug As soon as the process returns from the next PROCTIME (called from OB'+30), it will hit the breakpoint and enter Debug. If the process you're trying to "capture" is running on your terminal, and you don't have access to another terminal/window, you can try hitting and then entering Debug from the CI. Note: The technique may not work if your process is looping while it is in critical mode. If this is the case, use the original technique from another terminal. Note: The technique works well on processes that belong to other sessions too, as follows: /* on the terminal with the runaway /* process Then, on your terminal (with PM capability): :debug pin #123 tr,i,d and issue a breakpoint at the code address just below the top most interrupt marker. Now, on the original terminal issue the :RESUME command. Regardless of which of the above techniques was used, when you finally enter DEBUG/iX for the process that was looping you are now in control of it. You can take whatever steps are needed (e.g.: trying to force the loop to end by modifying a register or a value in memory; or by modifying the program counter). A nice feature of this mechanism is that the original user did not have to have the legal ability to debug the process ... you forced him/her into DEBUG/iX by setting the breakpoint externally. 10.08 Avoiding "works only when being debugged" Syndrome --------------------------------------------------------- Sometimes, the act of using DEBUG/iX to find a problem is enough of a perturbation that your problem disappears. If you have a program that exhibits a bug when run normally, and works correctly when you run it with ";DEBUG", then the bug is most likely one (or more) uninitialized variables within a procedure. Pascal/iX does not define the initial value of variables. (Despite this, outer block variables tend to be 0.) The initial value of a variable that is local to a procedure will be whatever happened to be left on the stack at an earlier time. Whenever you enter DEBUG/iX, it will eat up hundreds of words on top of your stack ... changing their values, affecting future uninitialized values. Sometimes, you can avoid this symptom by setting a breakpoint for your program from another invocation of DEBUG/iX (perhaps on another terminal). As an example, if you have a procedure "open_files" that is behaving wrong (except when you try to use DEBUG/iX to set a breakpoint at the start of the procedure), somehow delay the call to "open_files", so you can enter DEBUG/iX from somewhere else. Then, in that DEBUG/iX, set a breakpoint for your process at the procedure's address plus 8. The "plus 8" is designed to position the breakpoint after the stack-frame allocation code (typically the first instruction of a procedure). One technique to "delay" the call to your procedure is to hit immediately after the program starts up. However, this may change the stack enough to avoid the bug. ( is accomplished by having the top-level CI interrupt your process into the equivalent of the "suspend" intrinsic. This means that hundreds of words of your stack (above the current top-of-stack) will be changed. Another technique is to add a variable and the following code to your outer block (assuming you have not turned on the optimizer!): $map$ var debugger_trick : integer; ... debugger_trick := 1; while debugger_trick = 1 do ; ... This code will put you in an infinite loop, but one that did not perturb the stack values above the current top-of-stack. (If we had simply done a READX intrinsic or a PAUSE intrinsic, they could change the stack values.) In the compiler listing, find the address of the variable "debugger_trick" (the address is shown as "DP+nnn", where "nnn" is a hex byte offset). From another terminal, determine your process' PIN, enter DEBUG/iX, and do: pin #123 b procedure /* put a breakpoint in process #123 at desired location mv dp+$??? 0 /* where "???" is the offset to "debugger_trick" 10.09 System Wide Breakpoints ------------------------------ Sometimes, you're trying to find out who calls a particular procedure from *anywhere* in the system. This is where a "system wide breakpoint" can be used. (They're also called "global breakpoints".) (System-wide breakpoints can only be setup if you have PM capability.) For example, if you're debugging a problem where some process is doing an FUPDATE and corrupting data in a file ... but you don't know which process it is, and FUPDATE isn't called too frequently, you can try: :debug b FUPDATE:@ /* requires PM capability c Now, whenever any process in the system calls FUPDATE, it will hit the system-wide breakpoint and enter Debug. Note: if any of the processes are in batch, you may want to do: env job_debug true and "tie up" ldev 20 (e.g., ":pause 3600") so that they can safely enter Debug. Note: system-wide breakpoints are potentially dangerous...they can provide privileged access to Debug to ordinary users. Do not try to invoke a macro from the "cmd" parameter of a system wide breakpoint ... the macro is almost certainly not going to be defined for any process that enters DEBUG/iX! 10.10 Data Breakpoints ----------------------- DEBUG/iX allows users with PM capability to set breakpoints in data, as well as in code. This feature will cause your process to pop back into DEBUG/iX whenever it is about to store into a range of bytes protected by a data breakpoint. Data breakpoints can easily kill the system. Because of this, the DEBUG/iX manual has a large warning against using them. However, they can be used safely if some attention is given to the following considerations: - Do not put a data breakpoint in data structures belonging to the operating system. - Do not put a data breakpoint in your stack. - If you put a data breakpoint in your NM stack below sp at an address larger than sp_#4096, do *not* exit from the current procedure until you remove the breakpoint. - For data breakpoints within your CM stack, make sure that the data address is less than s-#2048. (And, don't exit from the current procedure). - For data breakpoints within your CM stack, don't open any files while the breakpoint is in effect. (Opening a file can cause your PXFILE area to be expanded, which shifts your entire DL-S area a few words higher in virtual memory. What the above suggestions boil down to is: Don't put a data breakpoint on a page where sensitive operating system code will hit it. The most common cause of system failures when data breakpoints are used is the putting of a data breakpoint in the stack. The basic syntax for setting a data breakpoint is: datab address [#bytes] [count] [LOUD | QUIET] [cmd] Data breakpoints work as follows: - DEBUG/iX marks the page containing your address (and any pages from that point up to the end of your specified address range) as "Break when Modified", a hardware supported bit for each page. - When you try to store *anywhere* within a page marked with the break bit, a trap occurs and DEBUG/iX is invoked to determine if the address in question is part of your address range. If it is not, the modification is allowed and you quietly continue. If it is part of your range, then DEBUG/iX processes the breakpoint (i.e.: the "count", LOUD/QUIET, and "cmd" options are handled as they would be for a code breakpoint). Note: The last point above implies an enormous performance degradation whenever a store is done ANYWHERE on a page with a data breakpoint. Exercise for the student: time the cost of a data breakpoint that isn't triggered. Hint: about 10 milliseconds per STORE to the page, on an HP 3000/968. 10.11 System Failures ---------------------- Sometimes, the system dies. However, you don't have to like it. In fact, knowing how the system failure mechanism works gives us a chance to postpone a system failure. In some cases, we can even back out of a system failure. In MPE V, software that wanted to kill the system called an internal routine called "SUDDENDEATH". On MPE/iX, CM code still calls that routine. NM code calls the procedure "system_abort". (Several other routines are sometimes called (e.g.: NM sudden_death and fs_system_abort), but these in turn call system_abort.) The system_abort procedure is implemented as one BREAK instruction. The following dc command shows the entire system_abort procedure: system_abort 00020005 BREAK (sys abort) When system_abort is called, the BREAK is executed. The break interrupt handler is invoked. This handler determines the type of BREAK instruction that was executed (e.g.: a DEBUG/iX breakpoint, a Tracing breakpoint, a system abort breakpoint), and takes the appropriate action (e.g.: entering DEBUG/iX, killing the system). If the "BREAK sys abort" instruction is killing the system ... all we have to do is avoid calling that instruction! The obvious way to do this is to put a system-wide breakpoint of our own at the start of system_abort. In this way, if someone calls system_abort, they should pop up into DEBUG/iX instead of killing the machine. Unfortunately, if you try to set a breakpoint at system_abort, you are told that a break instruction already exists there, and are refused. The debugger noticed the "BREAK (sys abort)" and failed to look past the word "BREAK". So, if DEBUG/iX is complaining about finding a break instruction there ... change it! The following sequence of DEBUG/iX commands will change the "BREAK (sys abort)" instruction into a NOP, and install a system-wide breakpoint at the start of system_abort: mc system_abort 08000240 /* new value: a NOP instruction /* almost any value could have been used /* here! b system_abort:@ The primary drawback to this technique is that if system_abort is called from an environment where breakpoints are not allowed (e.g.: a job (and job_debug is false), or while interrupts are disabled or while on the Interrupt Control Stack (ICS), then our breakpoint will either be ignored (and the NOP executed) or the interrupt will cause a recursive call to system_abort, which will (eventually) cause a stack overflow for our process, which will take us to the ICS and call system_abort, which will (eventually) overflow the ICS and result in a "B000 xxxx DEAD" death. I usually modify the system_abort as follows: mc system_abort, 2 08000240 /* a NOP 20005 /* a "BREAK (sys abort)" instruction b system_abort:@ In this way, if the NOP is somehow executed (because we can't pop into DEBUG/iX for some reason), then a "BREAK (sys abort)" is executed, resulting in a nice system failure. Note: The above modification changes the first word of the procedure following system_abort, which is (on MPE XL 3.0) do_shutdown_reset, which is not typically called. The above technique was successful in catching a system_abort that was being triggered by a vendor at the Boston Interex conference in 1990. If you are primarily interested in catching SUDDENDEATH calls (perhaps caused by accessing an invalid DST), you can do this by: :debug cm b ?SUDDENDEATH:@ c I have not encountered any "quiet deaths" with this technique. It was also successfully used at the Boston Interex conference. 10.12 Lost Control-Y --------------------- If your DEBUG/iX session has "lost" the ability to use control-Y, the following command (issued at the DEBUG/iX prompt) may regain it for you: :listf foo Be careful when debugging from the top-level CI, if you have lost control-Y and then use a command like the "findv" command (relatively new command), you may have to abort the session from another terminal. $page "Chapter 11: Dump Analysis" Chapter 11 Dump Analysis $page "Appendix A: Example Macros" Appendix A Example Macros /* j ... use instead of single step to execute a /* procedure call and then come back into /* to debugger. macro j {b pc+8, -1; c} /* find uses the DEBUG/iX "filter" variable to list only those /* lines of an "FT typ MAP" command that contain the string /* "field". Example usage: /* find ("system_globals_type", "pin") macro find (typ:str, field:str) { loc old_filter filter; env error 0; ignore quiet; { env filter strup (field); if error = 0 then ft typ map; }; env filter ''; if error <> 0 then wl errmsg (error); env filter old_filter; }; /* vfind uses the DEBUG/iX "filter" variable to list only /* those lines of an "FV addr typ" command that contain the /* string "field". Example usage: /* vfind ($c0000000, "system_globals_type", "pin") macro vfind (xxx:ptr, typ:str, field:str) { loc old_filter filter; env error 0; ignore quiet; { env filter strup (field); if error = 0 then fv xxx typ; }; env filter ''; if error <> 0 then wl errmsg (error); env filter old_filter; }; $page /* show_hpfopen displays the parameters to an HPFOPEN /* intrinsic call. Typical usage is when you are sitting at /* the start of HPFOPEN, so the values in registers R26..R23 /* are still valid. macro show_hpfopen { /* hpfopen (file, status, itemnum1, item1, itemnum2, /* item2, ...) option extensible; wl "HPFOPEN: # Parameters = ", r26:"#", ", File# @ ", r25, ", Status @ ", r24, loc args r26; /* contains # of arguments. loc itemnum r23; /* the first item#. loc args args - 2; /* subtract for the file# and /* status parms. loc nth 0; /* we will be counting from 1 loc spminus sp-#56+#8; /* location of first mem-based /* parm + 8. while args >= 0 do { loc nth nth + 1; if nth > 1 then loc itemnum [spminus]; /* extract item# from /* mem-parm. w "Item # ", nth:"DW2", " = ", itemnum:"#W2", ' '; if itemnum = 0 then wl else { loc itemval [spminus - 4]; w ": ", itemval; if (itemval >= $40000000) and (itemval <= $4f000000) then { loc parmval [itemval]; w ' --> ', parmval; if itemnum = #51 then w ' = ', parmval:"A", [itemval + 4]:"A" else if (parmval > #9) or (parmval < #9) then w ' (', parmval:'#', ')'; } else w " (", itemval:"#", ')'; wl; }; loc args args - 2; loc spminus spminus - #8; }; } $page "Appendix B: QUICK Reference" Appendix B QUICK Reference This is a brief summary of some of the basic commands in DEBUG/iX, the instruction-level debugger on MPE/iX. DEBUG can be used to debug Native Mode (NM) code or Compatibility Mode (CM) code. DEBUG has a very large amount of help information available. The command "HELP" describes the basic help facility. The HELP command, along with the CMDLIST, FUNCLIST, and ENVLIST commands will give you access to almost any feature you are looking for. Most of the commands that end with the letters "LIST" follow a similar pattern: the "IST" can be abbreviated (CMDL or CMDLIST), and the first parameter is a wildcard, ala the LISTF command (CMDLIST @Z@). CMDLIST cmdpattern [category] [what] The CMDList command lists all of the commands known to DEBUG. Examples: CMDLIST ... list all commands CMDLIST @Z@ ... list all commands with "Z" in name CMDLIST @R@, window ... list all window commands with "R" in name CMDLIST @Z@, ,all ... print full help for all commands with a "Z" in name FUNCLIST funcpattern [category] [what] The FUNCList command lists all of the built in functions known to DEBUG. Examples: FUNCLIST ... list all functions FUNCL @Z@ ... list all functions with "Z" in name ENVLIST envpattern [category] DEBUG has a set of about 200 predefined variables you can access. These variables are called "environmental variables." All of the variables can be "read", and most can be changed (written). Some require that the user have PM to change their values. The category parameter selects the type of environmental variable you want to have listed. The default is NOSTATE, which excludes variables that refer to hardware registers (e.g.: R21, DP, SR1). Examples: ENVLIST ... list most environmental variables ENVLIST, STATE ... list the "state" oriented variables ENVLIST, ALL ... list all environmental variables For a complete list of commands that "list" things, try: CMDLIST @LIST and ALIASLIST @LIST The ALIASLIST command is used because DEBUG supports a synonym feature that it refers to as "alias". The CMDLIST command is actually an alias for the CMDL command. Indeed, most of the apparent commands ending in "LIST" are simply predefined aliases for commands ending in "L". Displaying Data --------------- DV address [#words] [base] [recw] [recb] The Display Virtual (DV) command displays the contents of virtual memory starting at the specified address. Note that DV rounds the address down to a multiple of 4 at the start. The #words parameter is the number of 32- bit words to be displayed, the default is 1. The base parameter defaults to the current output base. Possible values are: $ or H hex output # or D decimal output % or O octal output A ASCII output (4 bytes, then a blank) B Both hex and ascii output C Code output (disassembled NM code) S String output (one long continuous string of output) Examples: DV DP+8 ... display first global Pascal/iX variable DV [DP+8], 3 ... fetch a 32 bit address from DP+8, then display 3 words of memory at that address DDB dbaddr [#halfs] [base] The DDB command displays the specified number of 16-bit words from the CM stack starting at the desired DB relative address. DQ qaddr [#halfs] [base] DS saddr [#halfs] [base] The DQ and DS commands display data from the CM stack Q- relative and S-relative, respectively. DR [register] If used by itself, Display Registers (DR) displays all of the "hardware" registers for your current execution mode. To see the registers for the other mode, combine with the CM and NM commands. Examples: DR ... displays all registers for current mode CM; DR ... displays all CM "registers" CM; DR; NM; DR ... displays all registers for both modes $page Breakpoint/SingleStep --------------------- Ss [#instructions] The singlestep command (S or SS) executes the desired number of instructions and then comes back to the debugger (assuming the program has not terminated prior to that point). You may come back to the debugger earlier, if a breakpoint is encountered. B address [count] [] [cmd] The Breakpoint instruction establishes a breakpoint at the specified address. For example, to set up a breakpoint at the second instruction after the current program counter (in NM): B pc + 8 By default, breakpoints are permanent (for the life of the process). To set up a one-shot breakpoint (called "temporary" in MPE V), do: B pc + 8, -1 The count option, which defaults to 1, means: pop up into the debugger every "count" times through this point. Thus, if a FOR loop had a breakpoint with a count of "2", then every second time through the loop you would enter the debugger. The LOUD/QUIET option (default is LOUD) tells the debugger whether it should announce the fact that you just entered debug. The cmd option (default is none) is a command to be executed when you pop up into the debugger. If the "C" command is not part of the commands executed, then you will be left in the debugger after the commands execute. Examples: b FOPEN ... break at entry to FOPEN b 5100 ... break at 5000 in my program b PROGRAM+24, -1 ... temporary breakpoint at 24 bytes beyond the "PROGRAM" start b pc + 8, , , {if [dp+8] <> 0 then c} ... break at pc + 8. Every time we hit this breakpoint, look at the contents of virtual memory at dp+8. If the 32 bit word stored there is NOT a 0, then automatically continue. Note: Because QUIET was not requested, you will be told about each time the breakpoint is hit. BD [@] Breakpoint Delete. If "@" is specified, all breakpoints are deleted. If a number is specified (BD 3), then that specific breakpoint is deleted. If no parameters are specified, DEBUG will prompt for permission to delete each breakpoint (the default answer is NO). $page Miscellaneous ------------- C (and exit) The continue command resumes executing your program. ABORT Aborts your program, immediately. Note: If you have PM and are debugging inside of system code, the debugger will not let you abort if your process is critical or holds a SIR. SET CRON Tells DEBUG that an empty line of input means: repeat the last non-empty line. One use for this is in single stepping, as shown below: SET CRON S ... single step ... single step ... single step SET CROFF Disables the SET CRON mode. WHELP Displays about 3 screens of help text about windows. WON Turns window mode on. WOFF Turns window mode off. RED Redraws the windows, useful if your program sent an escape sequence that cleared the screen. VW virtaddr [name] Opens a window looking at the specified virtual address. When windows are on (WON), this window will appear after the default windows. VK [virtualwindow#] Kills a virtual window (deletes it from window display and forgets about it). $page "Appendix C: DBUGINIT File Example" Appendix C DBUGINIT File Example /* dbuginit.source 00/02/10 /* Note that each line ends in a semicolon (;). /* This allows later grouping of commands by /* inserting a "{" in front and "}" after the end. /* next two lines may be needed if MACSTART.DAT.TELESUP /* would otherwise fail... ignore quiet; env vars $407; ignore quiet; env macros $503; /* following is optional, sometimes I comment it out... /* set cron; wl "set CRON"; /* following is useful when I want DEBUG/iX to work /* real hard in looking up symbolic names... /* env lookup_id "ALLPROC"; /* following is useful when SYMOS is open... /* Example: find ("pib_type", "pin") mac find (typ, xxx) {loc xxx strup(xxx); env filter xxx; ignore quiet; ft typ, m; env filter ''}; /* open appropriate SYMOS file... setvar symos_name "symos.os" + str (sysversion,1,1) + str (sysversion, 3, 2) + ".telesup"; env error = 0; ignore quiet; symopen !symos_name; if error = 0 then wl "Opened SYMOS: " + symos_name else wl "Failed to open SYMOS: " + symos_name; /* following makes LIST command output nicer... env list_paging false; env list_input false; mac kso (n) { return [$c0000000 + n * $8].[$c0000000 + n * $8 + 4]}; /* Load SPLash! macros... ignore quiet; use splash.macro.splish; wl; wl "Done with DBUGINIT"; wl; $page "Appendix D: Sample Programs" Appendix D Sample Programs See: http://www.allegro.com/papers/EX1.txt See: http://www.allegro.com/papers/EX2.txt See: http://www.allegro.com/papers/EX3.txt See: http://www.allegro.com/papers/EX4.txt See: http://www.allegro.com/papers/EX5.txt See: http://www.allegro.com/papers/EX6.txt