Copyright Robelle Solutions Technology Inc. 1991,1994
MPE/iX comes with a powerful debugger, Debug/iX. But, like all new software, there is a learning curve in understanding the new MPE/iX debugger. Attempting to find the dozen or so most useful features in the three-inch stack of paper called the System Debugger Reference Manual is impossible, unless you have three spare months. In this article, I intend to summarize the features I've found most useful.
MPE/iX now also comes with a Symbolic Debugger in addition to Debug/iX - this allows you to examine program variables by name and other useful features that are not included in Debug/iX. This paper does not cover the Symbolic Debugger, but the HP manual is on-line.
I am indebted to Stan Sieler of Allegro Consultants who taught me much of what is presented in this article. To obtain the maximum benefit from this article, you should try all of the examples that are presented. Having both a CM- and an NM-program available that calls the FWRITE intrinsic will make following the examples that much easier. If you are going to be a big user of Debug/iX, then you really have to have the reference manual which is available on-line.
Note: this paper was written in 1991 and revised in 1994, so is contains more references to CM code that you would find in a paper written today. However, the CM architecture is still basic to many things in MPE/iX.
Macros to go with this paper, loaded into Debug with the use macros.group.acct command, they show the parameters of hpfopen, etc.
System Debug Reference Manual for 6.5, online
Introduction to Debug/iX by Stan Sieler
Just like Debug/V, you can invoke Debug/iX by including the keyword "Debug" on the :Run Command for your program. Debug/iX responds with its "CM" prompt:
:run testprog;debug CM DEBUG Intrinsic: PROG %6.3542 cmdebug >If you have a Pmap (or Robelle's Qmap), you can set a breakpoint just as you would in Debug/V -- using segment.offset:
cmdebug >B 0.45Anyone who has struggled with Pmaps knows how convenient it would be if the system debugger took advantage of the FPMAP information stored in program files. With this information it should be possible to set a breakpoint by procedure name. Debug/iX lets you either set a symbolic breakpoint at the first logical instruction in your procedure or at the more useful entry-point:
cmdebug >B open'input'file {first instruction} cmdebug >B ?open'input'file {? implies entry point} cmdebug >B open'input'file+255 {octal offset from first} cmdebug >c {Continue = Resume}
nmdebug >b ?open'input'file,-1 {break once}
nmdebug >bd {You will be asked for which one} nmdebug >bd @ {Delete all breakpoints}
cmdebug >b ?extract_ready cmdebug >c . . . {break at extract_ready} cmdebug >lev 1;b p,-1 {-1 means only break once} cmdebug >c {continue execution}The "lev 1" goes back to the previous logical level in the calling sequence (use "tr,d" to see a complete traceback). The "b p" sets a breakpoint at the compatibility-mode program counter. The "lev 1" places the program counter at the instruction after the one that called the current procedure. The ",-1" tells Debug/iX to execute the breakpoint once and then throw it away. Note that it's safe to use this breakpoint anywhere in the extract_ready procedure -- not just at the beginning.
What if we are in native-mode code (e.g., FWRITE)? Our return breakpoint won't work, since we called FWRITE from compatibility- mode. To set a return breakpoint in this case, first switch into cmdebug:
cmdebug >b ?FWRITE cmdebug >c . . . {break at NM FWRITE} nmdebug >cm {switch into CM} cmdebug >lev 1;b p,-1 {set return breakpoint} cmdebug >c {continue execution}
Debug/V Debug/iX D DB DDB D S DS D Q DQThe Debug/iX Display Command has a count as its second parameter (just like Debug/V), but the display attribute is different. Here is the comparison:
Debug/V Debug/iX Description ,I ,# or D Decimal ,O ,% or O Octal {default in CM} ,H ,$ or H Hexadecimal {default in NM} ,A ,S Ascii/StringInstead of S, you can also use A for displaying string values. The A-option is closer to the A-option of Debug/V, but we find the S-option more useful.
By default, the S-option displays all characters you request and only displays the virtual address of the string once. If you want to see as many characters per line as possible, with each new line starting with the virtual address of the characters displayed, use this command:
cmdebug >dq 104,200,s,e {"e" shows addresses}
You can also use the traceback to observe switches from native-mode to compatibility-mode. For example, if you have SM capability you can set a breakpoint in any system SL or system XL routine. KSAM files come in two flavors: CM and NM. If you access a CM KSAM file from a NM program, MPE/iX calls the CM FWRITE intrinsic. You can easily prove this to yourself by setting a breakpoint in the CM FWRITE intrinsic:
:run testprog;debug {NM program to read CM KSAM file} nmdebug >cmdebug {switch to CM} cmdebug >b ?FWRITE {question-mark for entry point} cmdebug >c {continue execution} . . . {note the ",d" on the TR Command} cmdebug >tr,d {traceback showing switches}
cmdebug >b ?input'command {break at the entry point} cmdebug >c {continue execution} . . . cmdebug >won {turn windows on}The top three lines of the display show the register information:
R % Regs DB=001200 DBDST=001632 X=000002 STATUS=(mITroC CCG 007) PIN=051 SDST=001627 DL=177450 Q=023620 S=023620 CMPC=PROG 000006.006711 CIR=035004 MAPFLAG=0 MAPDST=000000 FcmP % PROG 6.6711 (?) SETUP CSTX 7 Level 0 006707: INPUT'COMMAND+%437 031031 2. PCAL ?ERRX 006710: INPUT'COMMAND+%440 032000 4. SXIT 0 006711: [1]> ?INPUT'COMMAND 035004 :. ADDS 4 006712: INPUT'COMMAND+%442 171700 .. LRA S-0 006713: INPUT'COMMAND+%443 051401 S. STOR Q+1 006714: INPUT'COMMAND+%444 035023 :. ADDS %23 006715: INPUT'COMMAND+%445 041401 C. LOAD Q+1 Q % (DB mode) QDST=001627 Level 0 023610: 000000 047420 061006 000006 177600 D000002 D00364 023620:Q>D000014 <S 023630: S % (DB mode) SDST=001627 Level 0 023610: 000000 047420 061006 000006 177600 D000002 D00364 023620:Q>D000014 <S Commands %47 (%103) cmdebug >For most of us, only the DL=, Q=, S=, and X= values are interesting. If the DBDST and the SDST (the DB- and S- data segments) are different, you are in split-stack mode. Line four shows that we are currently at location 6.6711 in the program. The PROG would change if the breakpoint was inside an SL. Next we see seven instructions. The "[1]" means breakpoint number 1. The ">" symbol next to "?INPUT'COMMAND" shows the next instruction to be executed. The bottom of the display shows the values around the Q- and S- registers. In our example, the Q and S registers are the same so the Q- and S-displays are identical. Finally, you are prompted for more Debug/iX commands.
cmdebug >s {single-step} R % Regs DB=001200 DBDST=001632 X=000002 STATUS=(mITroC CCG 007) PIN=1 SDST=001632 DL=177450 Q=023620 S=023624 CMPC=PROG 000006 CIR=171700 MAPFLAG=0 MAPDST=000000 cmP % PROG 6.6712 (?) SETUP CSTX 7 Level 0 006707: INPUT'COMMAND+%437 031031 2. PCAL ?ERRX 006710: INPUT'COMMAND+%440 032000 4. SXIT 0 006711: [1] ?INPUT'COMMAND 035004 :. ADDS 4 006712: > INPUT'COMMAND+%442 171700 .. LRA S-0 006713: INPUT'COMMAND+%443 051401 S. STOR Q+1 006714: INPUT'COMMAND+%444 035023 :. ADDS %23 006715: INPUT'COMMAND+%445 041401 C. LOAD Q+1 Q % (DB mode) QDST=001632 Level 0 023610: 000000 047420 061006 000006 177600 000002 00364 023620:Q>000014 000002 006712 062007 .d 023630: S % (DB mode) SDST=001632 Level 0 023610: 000000 047420 061006 000006 177600 000002 00364 023620:Q>000014 000002 006712 062007<S Commands %47 (%103) cmdebug >The ">" symbol has moved forward by one instruction. The register values have been updated and the top of stack has changed because we added four to the S-register.
cmdebug >set cron {Return = last-command} cmdebug >s {single-step} cmdebug > {another single-step!} cmdebug > {and one more} cmdebug > {and so on}
cmdebug >set cron {Return = last-command} cmdebug >s 7 {execute seven instructions} cmdebug > {another seven!} cmdebug > {and seven more} cmdebug > {and so on}
nmdebug >b open_input_file {break at procedure entry}
nmdebug >b fwrite {not found; lower-case}
nmdebug >b FWRITE {NM-FWRITE breakpoint} nmdebug >cm {switch into cmdebug} cmdebug >b ?FWRITE {CM-FWRITE breakpoint} cmdebug >nm {switch back into nmdebug} nmdebug >c {continue execution}
nmdebug >b FWRITE {requires SM capability} nmdebug >c {continue execution}} . . . nmdebug >won {turn windows on} GR$ ipsw=0006fe0f=jthlnxbCVmrQPDI priv=0 pc=0000000a.004a5fc0 pin=0000007a r0 00000000 40100e20 004aee30 00000001 r4 c0000000 0000ffff 4033292a 00000000 r8 00000001 00000009 00000004 4034a880 r12 00000000 00000000 00000000 00000000 r16 00000000 00000000 00000000 c0000000 r20 c0000000 00000001 85240000 00000314 r24 40332604 000000d0 00000001 c0202008 r28 00000001 ffffffff 4034afd8 004aee30 nmP$ SYS a.4a5fb8 NL.PUB.SYS/FSPACE+$5a4 Level 0,0 004a5fb8: FSPACE+$5a4 e840c000 BV 0(2) 004a5fbc: FSPACE+$5a8 4fc33d31 LDWM -360(0,30),3 004a5fc0: [1]> FWRITE 6bc23fd9 STW 2,-20(0,30) 004a5fc4: FWRITE+$4 6fc30340 STWM 3,416(0,30) 004a5fc8: FWRITE+$8 6bc43cc9 STW 4,-412(0,30) 004a5fcc: FWRITE+$c 6bc53cd1 STW 5,-408(0,30) 004a5fd0: FWRITE+$10 6bc63cd9 STW 6,-404(0,30) Commands $7 ($1d) nmdebug >The first line contains general information about the process (e.g., the pin number). The pc= is the program counter (notice it's a full 64-bit address in space.offset format). Lines two through four of the display show all 32 general-purpose registers. The fifth line shows where the first instruction in the window is located (in NL.Pub.Sys @ FSPACE+$5a4). The native-mode instructions are shown, along with the breakpoint number "[1]" and the next instruction to be executed is marked with the ">".
There are two commands that can be a big benefit in examining the code "around" a breakpoint: PB (Program Back) and PF (Program Forward).
nmdebug >pf {program forward} nmdebug >pb {program back}
nmdebug >pl #10 {show "ten" instructions}
integer procedure convint(buf,len); !result = Q-6 value len; integer len; !len = Q-4 byte array buf; !@buf = Q-5In CM-debug, we would look at the parameters as follows:
cmdebug >dq -6 {result of Convint procedure} cmdebug >dq -4 {length of buffer} cmdebug >dq -5 {address of buffer} Q-%5 % 000104 {must use this value below} cmdebug >ddb 104/2,10,s {print actual buffer contents}Notice that we had to divide the value at Q-5 (i.e., %104) by two, since the buffer was passed as a byte address. In native-mode, this irritation disappears (except for those using SPLash! to emulate Classic byte addressing).
integer procedure convint(buf,len); !result = R28 value len; integer len; !len = R25 byte array buf; !@buf = R26If you have windows on, the 32 general-purpose registers are always displayed. The only problem area is the buffer parameter:
nmdebug >b convint {note lower-case} nmdebug >won {windows on} nmdebug >c {continue execution} . . . {debug breaks @ convint} nmdebug >=r25 {display the length} nmdebug >dv r26,10,s {display virtual uses the contents} {of register 26 as an address}
nmdebug >var buf_var=r26 {save address of buffer} nmdebug >dv buf_var,10,s {display buffer contents} nmdebug >c {continue execution} . . . {sometime much later ...} nmdebug >dv buf_var,10,s {display the buffer contents}The final Display Virtual Command displays the contents of the buffer using the address that we saved. When the breakpoint takes place, we may have no convenient way of finding the program variable that has the address of our buffer. Because we have saved the address in the Debug/iX variable "buf_var", we display the buffer contents without knowing where the address is stored.
nmdebug >map "file1.suprtest" {open an mpe file mapped} nmdebug >var fileaddr = mapva("file1.suprtest") nmdebug >=fileaddr {display the virtual address}Debug/iX has a built-in calculator that accepts any Debug/iX expression. You invoke the calculator with an equal sign "=". Debug/iX evaluates the calculator expression and prints the result. The calculator will display the full 64-bit address of "file1.suprtest" as space.offset.
You can display the actual contents of the file:
nmdebug >dv fileaddr,20,s {first 20-bytes of file}Warning: Due to a very serious bug in MPE/iX, never, never, never do this on the file Catalog.Pub.Sys. If you open Catalog.Pub.Sys with mapped access, you will cause a system failure.
The map command displays the virtual address of a file in space.offset format. You can use the DV (Display Virtual) Command to display the file contents or you can use our method. We prefer using a variable and mapva function, since typing in a full 64-bit address correctly is quite difficult.
Procedure FWRITE ( Parm 1: int16 ; {R26, bits = 16} Parm 2: anyvar record ; {(skip 25) R23, R24} {bits = 65536} {Address type = LongAddr} Parm 3: int16 ; {SP-$0032, bits = 16} Parm 4: UInt16 ) {SP-$0036, bits = 16} uncheckable_anyvarNote that the buffer parameter is a "LongAddr" that is passed in both R23 and R24 (the first is the space and the second is the offset). Fortunately, it is still easy to see the contents of the buffer. If we were at a breakpoint at the start of FWRITE, we would display the buffer with:
nmdebug >dv r23.r24,20,s {display buffer contents}
nmdebug >dv sp-32,1 {display the length} $ 00005f00We used the DV (Display Virtual) Command to display the stack contents. The ",1" is not necessary - it's the default, but we have shown it to make the following examples a little clearer. The "dv sp-32" displays the value at sp-32 as a 32-bit quantity, but we know that the actual value of FWRITE's length parameter is a 16-bit quantity. You can display two 16-bit integers using the following:
nmdebug >dv sp-32,1,,,2 {display two 16-bit integers} $ 0000 5f00 nmdebug >dv sp-32,1,#,,2 {display two integers in decimal} # 0 24320Display Virtual always rounds down to a virtual address that is a multiple of four and then displays one or more 32 bit words.
nmdebug >b extract_ready nmdebug >c . . . {break at extract_ready} nmdebug >lev 1;b pc,-1 {-1 means only break once} nmdebug >c {continue execution}The only difference between a CM return breakpoint, and an NM one, is the name of the program counter. In native-mode it's called "pc". This sets a return breakpoint immediately after the code that called extract_ready. Note that it's safe to use this breakpoint anywhere in the extract_ready procedure -- not just at the beginning.
To set a breakpoint in a subroutine, combine the procedure name with "$2$" and the subroutine name, replacing apostrophes (') with underscores(_). For example, to set a breakpoint in the upd'local'tag subroutine of the set'x procedure, you would do:
nmdebug >b set_x$2$upd_local_tag nmdebug >c
{On the console ...} :hello user.acct :restore
:hello manager.sys :debug
nmdebug >env job_debug true {set special variable} nmdebug >b FWRITE:pin# {don't forget the pin#}
When the batch program encounters the breakpoint, Debug/iX is invoked and all Debug/iX input/output is done via the console. On the console you can type any of the usual Debug/iX commands. When you finish your debugging session, you'll need to remember to abort the :Restore that you initiated. You must also return to the Manager.Sys session and disable job debugging:
cmdebug >env job_debug false
nmdebug >mac j {b pc+$8,-1; c}Macros are declared with the Mac Command. The first parameter to the Mac Command is the macro name (in this case it's j). The body of the macro follows and is surrounded with braces. Macros can take several lines. The j macro sets a breakpoint at the next native-mode instruction after a branch-and-link "pc+$8". The breakpoint is only executed once ",-1". Multiple commands are separated by semi-colons ";". The last step of the macro is to execute the Continue Command "c". Note that the j macro is only useful around branch-and-link instructions which is why we jump eight bytes ahead of the program counter instead of four. You execute the macro as if it were a built-in Debug/iX command:
nmdebug >j
/* Macro: Vfilepages /* /* Purpose: Display the number of pages (and corresponding /* sectors) of a file that are actually in memory. /* /* Warning: Never use this macro on catalog.pub.sys. /* mac vfilepages (filename:str) { map !filename; w !filename " contains "; w vainfo(mapva(!filename),"pages_in_mem"):"D"; w " pages in memory = "; w vainfo(mapva(!filename),"pages_in_mem")*#16:"D"; w " sectors"; wl; unmap(mapindex(!filename)); }Lines starting with "/*" are treated as comments. The "filename" is a parameter to the macro and it's of string type. To understand the rest of the macro requires looking up the description of the Map, Mapva, W, WL, and Unmap Commands and an understanding of the Vainfo and Mapindex Function. We'll leave that up to you. To invoke this macro, you would do the following (note the quotes around the filename):
vfilepages "file50.suprtest" file50.suprtest contains 8 pages in memory = 128 sectorsWarning: Because this macro uses the Debug/iX Map Command, do not use it on the file catalog.pub.sys. If you do, you will cause a system failure.
:hello david.dev,david :print dbuginit.macro.dev use splash.macro.splash use macros.macro.dev :file dbuginit.david.dev=debuginit.macro.dev :run testprog;debug {Debug/iX will use debuginit.macro}
We also find it useful to invoke Cseq.Pub.Nuggets when we are debugging a program. This lets us determine the location of the parameters for any MPE intrinsic:
nmdebug >:cseq.pub.nuggets {obtain parameter addresses}