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 <count> 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
<help information is printed here>
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 <ON | OFF | CLOSE>
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> ::= <value> | <expression> <operator> <expression>
| - <expression>
| (expression)
where <value> 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.: <name>) are names picked by you, and items
in brackets (e.g.: [type]) indicate optional items:
macro <name> [ <<arguments>> ] { <<body>> }
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 "<nameless>"
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:
<hit break> /* 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 <break> and then entering Debug from the CI.
Note: The <break> 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 <break> technique works well on processes that belong
to other sessions too, as follows:
<hit break> /* 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
<break> immediately after the program starts up. However, this
may change the stack enough to avoid the bug. (<break> 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] [<LOUD | QUIET>] [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
<cr> ... single step
<cr> ... 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: EX1.txt
See: EX2.txt
See: EX3.txt
See: EX4.txt
See: EX5.txt
See: EX6.txt