The MPE/iX System Debugger

By David Greer

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.


Useful Links:

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


Outline


Native-Mode or Compatibility-Mode

HP was very kind to write not only a nice debugger for native-mode programs, but include features for debugging compatibility-mode programs too. When I first attempted to debug a CM-program, I got so confused that I returned to my classic HP 3000 where at least I knew all the command names. The next section will show Debug/iX commands for our old favorite Debug/V commands.

Debug/V Versus Debug/iX

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.45
 
Anyone 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}
 

Breakpoints

In the last ten years, I have probably lost over a month of time from one horrible default in Debug/V. In Debug/V, the Break Command would only break the first time it encountered the breakpoint, unless you added ":@" to the Break Command. In Debug/iX the default is to always break. Occasionally, you only want the breakpoint to be invoked once. Use ",-1" after the break location to have the breakpoint removed after one occurrence:
      nmdebug >b ?open'input'file,-1 {break once}
 

Clearing Breakpoints

In Debug/V, the Clear Command disables breakpoints that have been set with the Break Command. In Debug/iX, use the BD (Breakpoint Delete) Command to remove breakpoints:
      nmdebug >bd       {You will be asked for which one}
      nmdebug >bd @     {Delete all breakpoints}
 

Setting a "Return" Breakpoint

One of the most useful breakpoints is the one immediately after a procedure call. Suppose that your program calls the procedure extract_ready. You want to know the result of extract_ready, so you would like a breakpoint in the calling code immediately after the call to extract_ready. You do the following:
      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}
 

Abort Command -- Getting Out of Debug

You can terminate your program with the Abort Command. Use this any time that Debug/iX is prompting for commands. The Debug/iX Abort Command is similar to the Debug/V E@ Command.

Displaying CM Values

When I first used Debug/iX, I became totally confused about how to display the usual DB-, Q-, and S-relative values. It turns out to be very simple. In Debug/V the Display Command takes the register as a parameter. In Debug/iX there are separate command names for displaying values relative to each register. Here are the Debug/V and Debug/iX Display Commands:
      Debug/V     Debug/iX
        D DB        DDB
        D S         DS
        D Q         DQ
 
The 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/String
 
Instead 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}
 

Symbolic Machine Code

Our list above doesn't show you how to display the actual run-time machine instructions (commonly called decompiling). That's because Debug/iX has many excellent features to symbolically display code. While you can use the DC (Display Code) Command to show symbolic code, we have found a better method -- windows.

Symbolic Traceback

The Debug/V Trace Command was almost useless. You had to manually work through the segment numbers and offsets to figure out the true procedure names. The Debug/iX Trace Command produces a proper symbolic traceback of procedure names.

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}
 

Compatibility-Mode Windows

The WON Command is the real power of Debug/iX. WON is short for Windows On. When you turn windows on, the top portion of the screen is reserved for a symbolic display of the currently executing code, another portion displays register and/or stack values, and the bottom of the screen is used to enter commands. This is a very powerful feature.

CM Window Example

The following is an example compatibility-mode window. We first set a breakpoint, continue to that breakpoint, and finally we turn windows on.
      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.

Single-Stepping

One other command adds a lot of power to windows: S -- single-stepping. The S Command executes the next instruction, then returns control to Debug/iX. After the execution, register and stack values are updated and any changed values are highlighted. Because the compatibility-mode window shows the top few words of the stack, you can often get an instant picture of what is going on. Here is the first window after executing one single-step:
      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.

Set CRON

This sounds like the title of a futuristic movie, but when combined with single-stepping it can be very powerful. Once you start using the S (Single-Step) Command, you'll find yourself typing it a lot, especially when debugging NM programs where you have a lot more instructions per source code statement. Fortunately, the Debug/iX designers already thought of this. When you Set CRON, hitting Return tells Debug/iX "execute the last command that I typed". This is most useful when your last command was S, but it applies to any command:
      cmdebug >set cron        {Return = last-command}
      cmdebug >s               {single-step}
      cmdebug >                {another single-step!}
      cmdebug >                {and one more}
      cmdebug >                {and so on}
 

Multiple Steps

While single-stepping is useful, it can be very slow. You can step through a program faster using multiple instructions for every step. The following example shows how to step through every seven executed instructions. Note: you must have a space after the Step Command and before the number of instructions to execute (e.g., "S7" is invalid):
      cmdebug >set cron        {Return = last-command}
      cmdebug >s 7             {execute seven instructions}
      cmdebug >                {another seven!}
      cmdebug >                {and seven more}
      cmdebug >                {and so on}
 

Native-Mode Debugging

Much of what has been discussed applies to native-mode. There are a few minor differences:
  1. You don't need to specify Fpmap (or any other magic parameter) on the :Link Command. Procedure name and location information is automatically included in all NM program files.
  2. Since the first instruction of a procedure and its entry point are the same, you never need to use a question mark. If you happen to type a question mark, Debug/iX may not print an error. In this case, you will have set a breakpoint in a stub procedure. Since you almost never want to do this, it's important to remember not to type the question mark before the procedure name.
  3. In most programming languages, any separators (e.g., apostrophes) used in procedure names will now become underbars.
Here is our previous breakpoint example in native-mode:
      nmdebug >b open_input_file    {break at procedure entry}
 

Case Sensitivity

It is easy to see that portions of MPE/iX were affected by UNIX and the C programming language. In UNIX and C, case is significant (i.e., upper-case and lower-case are not the same). When setting breakpoints in native-mode code, it is important to remember this. Most MPE/iX routine names are in upper-case. The most well-known exceptions are all of the IMAGE and VPLUS intrinsics which are in lower-case. The following example results in a Debug/iX error:
      nmdebug >b fwrite          {not found; lower-case}
 

Switching Modes

Sometimes you want to switch between CM-debug and NM-debug. For example, the NM-FWRITE intrinsic calls the CM-FWRITE intrinsic for certain types of files (e.g., circular). These commands would set breakpoints in both the CM- and NM-FWRITE intrinsics:
      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}
 

Native-Mode Windows

The WON (Windows On) Command is just as powerful in native-mode as in compatibility-mode. The display is different -- instead of the old familiar DB, S, and Q registers, there is a strange group of 32 "general-purpose" registers. The code looks a lot different too -- those famous RISC instructions instead of our old faithful Classic 3000 ones.

NM Window Example

We will show an example native-mode window, by setting a breakpoint for the FWRITE intrinsic:
      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).

Window Paging

Debug/iX windows have to display all their information in the twenty-four lines on a standard terminal screen. By default, the size of the symbolic instruction list is seven instructions. Especially when you are single-stepping through instructions, it is very useful to see the previous seven instructions or the next seven. The PB (Program Back) Command displays the previous seven instructions and the PF (Program Forward) Command shows the next seven. While seven instructions is the default, there are commands to change the size of the program window. If you have changed the size, Program Back and Program Forward adjust themselves to the new window size.
      nmdebug >pf             {program forward}
      nmdebug >pb             {program back}
 

PL Command

If you want to change the number of program instructions on the screen, use the PL Command (Program List). The PL Command assumes that the number of lines you want is in the current base. Therefore, PL 10 means 16 instructions in NM Debug and 8 instructions in CM Debug. To get around the problem, we always specify the number of instructions in decimal:
      nmdebug >pl #10     {show "ten" instructions}
 

Native-Mode Procedure Parameters

Long-time users of Debug/V know how to anticipate where procedure parameters will be located. For example, if we had a procedure with this declaration:
      integer procedure convint(buf,len);  !result = Q-6
         value   len;
         integer len;                      !len    = Q-4
         byte array buf;                   !@buf   = Q-5
 
In 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).

Native-Mode Calling Conventions

With the power to set breakpoints symbolically, by just knowing the name of a procedure, there is even more incentive to be able to guess the location of procedure parameters. NM procedures are allocated registers for the first four parameters, but they are allocated left-to-right -- the opposite of CM procedures. The first parameter is assigned to Register-26, the second to Register-25, the third to Register-24, the fourth to Register-23, and any remaining parameters are stored on the NM stack. The return value is in Register-28 (and Register-29 for 64-bit values). For native-mode, you would think of the declaration for Convint as:
      integer procedure convint(buf,len); !result = R28
         value   len;
         integer len;                     !len    = R25
         byte array buf;                  !@buf   = R26
 
If 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}
  

Debug Variables

Debug/iX contains a programming language. We won't try and cover all of the features of this language, but variables are so powerful that they are worth knowing about. In our example with the Convint procedure, suppose that the buffer you are passing to Convint is a global variable. Setting the breakpoint at Convint gives you a convenient method to find and save the address of your buffer so that you can use it at any breakpoint.
      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.

Virtual Addresses

So far, we have assumed that all addresses are 32-bits. In MPE/iX, addresses are actually 64-bits. Debug/iX shows these addresses as space.offset. If you are working with mapped files, you will find that the full 64-bit address suddenly becomes important. The following example opens a file with mapped access, saves the virtual address of the file into a variable, and then displays the actual contents of the file.
      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.

Cseq Utility (Formerly Nuggets, now Lund Toolbox)

While it is easy to predict the layout of parameters in our simple example, things can get more complicated in MPE/iX. For example, addresses can be passed as 64-bit quantities instead of the default 32-bit values. The best way I've found to determine parameter location is to use the Cseq (calling sequence} utility in the Developer's Toolbox from Lund. Here is the Cseq output for the FWRITE intrinsic:
 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_anyvar
 
Note 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}
 

Integers: 16-bit versus 32-bit

Cmdebug displays integers in octal as 16-bit quantities. Nmdebug displays integers in hex as 32-bit quantities. In our FWRITE example, it is easy to see the value of the length parameter.
      nmdebug >dv sp-32,1      {display the length}
      $ 00005f00
 
We 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  24320
 
Display Virtual always rounds down to a virtual address that is a multiple of four and then displays one or more 32 bit words.

Miscellaneous Tips

Setting an NM "Return" Breakpoint

We showed how to set a return breakpoint in compatibility-mode. You use a similar method to set a return breakpoint in native-mode code:
      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.

Breakpointing a Splash Subroutine

Splash is the native-mode SPL compiler from Allegro. SPL has both procedures and subroutines because there were instructions for subroutines within procedures on the original HP 3000. On the RISC machines, there are only procedures, so Splash makes up a procedure name for each subroutine.

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

Debugging Batch Programs

In Debug/V, there was no practical way to debug a program running in batch. In Debug/iX, you can debug a batch program on the console, although it's a bit messy to set up. You have to do these steps:
  1. Obtain the pin number of the program you want to debug. You'll need to use a program like Shot.Pub.Nuggets. You can use the Showproc Command, if you have MPE/iX version 2.1 or later versions.
  2. Go to the console and insure that there will be no output on the console. The easiest way to do this is to initiate a :Restore on the console. This assumes that your tape drive is not configured for auto-reply. Do not reply to the tape request.
             {On the console ...}
             :hello user.acct
             :restore
     
  3. On another terminal, log on with SM capability and enter debug. For example,
            :hello manager.sys
            :debug
     
  4. Once you are inside Debug, you must set an environment variable and force a breakpoint in the batch program. Our example assumes that the batch program will call the FWRITE intrinsic:
             nmdebug >env job_debug true   {set special variable}
             nmdebug >b FWRITE:pin#        {don't forget the pin#}
     
Of course, you don't actually type "b FWRITE:pin#" when setting the breakpoint. You substitute the actual pin# that you obtained in step 1 (e.g., "b FWRITE:103").

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
 

Macros

Debug/iX contains a small programming language that lets you create your own macros. Debug/iX has no command to skip over procedure calls, although almost all PC-based debuggers have this feature. When single-stepping through a program, you rarely want to single-step through external procedures (e.g., the Print intrinsic). Use the j macro to jump over the next native-mode BL instruction. Macros use braces for the body of the macro (i.e., as begin/end), so don't interpret the braces as comments. Here is how to declare the macro:
      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
 

Vfilepages Macro

When doing any performance measurements with disc files, you need to know what portion of the file is in memory. This macro takes advantage of many features of Debug/iX. The macro displays the number of pages of a file that are currently present in virtual memory.
      /*  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 sectors
 
Warning: 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.

DBUGINIT File

Once you start writing macros, you will want to have them automatically loaded when you enter Debug/iX. Debug/iX always executes a use-file called DBUGINIT. Debug/iX first looks for this file in the same group and account as the program, then it looks in the logon group and account. Rather than fill our DBUGINIT file with macros, we fill it with Use Commands for different files that contain useful macros: You can use :file commands for the DBUGINIT file, but you must use a fully qualified filename. For example:
      :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}
 

:Setdump Command

Classic MPE contains a :Setdump Command, but I believe most of us ignored it because the traceback it printed was not symbolic. If you enable :setdump in MPE/iX, you not only get an excellent symbolic traceback, but in native-mode you are placed into Debug/iX (certain exceptions apply to privileged-mode programs).

MPE Commands

You can enter almost any MPE command by preceding it with a colon. This includes UDCs and the :Run Command. Often in the middle of a debugging session, you need to examine your source code. An easier way to do this is to run your editor from within Debug/iX. One word of caution -- Debug/iX, like many HP products, fails to see if a son process has terminated or suspended.

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}
 

Running Qedit from Debug/iX

If you invoke Qedit from Debug/iX, be sure to run it with Parm=32 (this tells Qedit not to suspend on exit). The most likely reason to invoke Qedit from Debug/iX is to examine your source code. If you do not /Shut your file before running your program, you will get "Error: Busy file" when you try to open your file inside Qedit (inside Debug/iX). To get around the problem, you can either /List your source code or /Text a copy.

Conclusion

If you are going to make heavy use of Debug/iX, I strongly recommend getting the System Debug Reference Manual. While it's not helpful for learning Debug/iX, it's invaluable in looking up specific commands and their syntax. That part number again is #32650-90013.

When I first set out to write this article, I thought that it would only take me a few paragraphs to convey what I'd learned about Debug/iX. If you've got this far, you realize that I underestimated the amount of material -- not surprising given the rich feature set of Debug/iX.