Museum

Home

Lab Overview

Retrotechnology Articles

Online Manuals

⇒ debugger(8) — Interactive 3.2r4.1

Media Vault

Software Library

Restoration Projects

Artifacts Sought

Related Articles

crash(1M)

console(7)

display(7)

debugger(8)  —  

NAME

debugger − symbolic kernel debugger

SYNOPSIS

<CTRL> <ALT> d if the console refers to a video adapter. 
<BREAK> if the console refers to a serial terminal. 

DESCRIPTION

The RAM-resident kernel debugger is accessed by generating an interrupt on interrupt level 1.  This is frequently done by pressing a front-panel switch (press front-panel int).  In the case of PC-type systems without front-panel interrupt switches, the debugger is activated by typing CTRL ALT d on the console if the console refers to a video adapter, or by pressing the BREAK key if the console refers to a serial terminal. 

The general structure of the debugger is a stack-based language, using postfix notation similar to that of the typical Reverse Polish Notation pocket calculator.  Arguments are pushed on the stack, and then the operation is entered. 

Pushing items onto the stack:
Items are pushed onto the stack by typing them in. Errors are printed for items that are not recognized, such as symbols that do not exist in the symbol table.

Symbols and symbolic references:
The debugger has the capability of referring to objects in the kernel by their symbolic names. Since the symbol table from the COFF image of the kernel does not get loaded at boot time, these references are resolved in a different fashion.  When a kernel is built, it is processed by the utility ­unixsyms as the last step in building the kernel.  This utility takes the symbols and values from the COFF image and places them in a specially reserved area of the initialized data section of the image. 

Invoking operations:
Operations are invoked by entering the name of the operation (refer to “Pushing items onto the stack,” above). An error will be printed if there are not enough items on the stack for the selected operation or if the selected operation doesn’t exist.

Arguments may be entered on separate lines, or an entire expression may be entered on one line.  For example:

200
350
+

This would leave the value 550 on top of the stack, where it can be printed, using the p operator, or used in future calculations.  The same result can be accomplished by:

200 350 +

Arguments on the stack are referred to by their positions relative to the top of the stack.  The top of the stack always contains the last item pushed.  The bottom of the stack is always the first item pushed (assuming that the operation always begins with a clean stack).  For example, if two items are pushed onto the stack, the latest one pushed is referred to as the <tos> (top of stack).  The last item pushed is said to be on top of the stack.  The first item that was pushed is referred to as <tos−1> (top of stack −1).  This ordering scheme is important for operations that are not commutative or those that require different types of operators. 

Arithmetic and logical operators:
The set of available arithmetic and logical operators is similar to that of the C language. The following table lists the arithmetic and logical operators. Also listed are those arguments that must be on the stack and those arguments that are left on the stack.

No. of No. of
Operator Operands Results Result Description
+ 2 1 sum
− 2 1 difference (<tos−1> − <tos>)
* 2 1 product
/ 2 1 integer dividend
(<tos−1> / <tos>)
% 2 1 integer remainder
(<tos−1> % <tos>)
<< 2 1 <tos> shifted left by <tos−1>
>> 2 1 <tos> shifted right by <tos−1>
& 2 1 result of logical and
| 2 1 result of logical or
-- 1 1 decrement
++ 1 1 increment
^ 2 1 exclusive or

Relational operators:
Each of the following operators leaves a 1 on the stack if the condition is true, and leaves a 0 if it is false. 

No. of No. of
Operator Operands Results Result Description
== 2 1 1 if <tos−1> and <tos> are equal
!= 2 1 1 if <tos−1> and <tos> are not
equal
> 2 1 1 if <tos−1> is greater than <tos>
< 2 1 1 if <tos−1> is less than <tos>
|| 2 1 1 if either <tos−1> or <tos> is
non-zero
&& 2 1 1 if both <tos−1> and <tos> are
non-zero

Debugger control operators:
The following operators control the behavior of the debugger itself. Most of them are related to the user interface.

No. of No. of
Operator Operands Results Result Description
verbose 0 0 places the debugger in verbose
mode
useful for debugging the
debugger, little else
nonverbose 0 0 takes the debugger out of
verbose mode
ibase 1 0 sets the debugger input base to
<tos>
ioctal 0 0 sets the debugger input base to
8 (octal)
idecimal 0 0 sets the debugger input base to
10 (decimal)
ihex 0 sets the debugger input base to
16 (hex)
ibinary 0 0 sets the debugger input base to
2 (binary)
ooctal 0 0 sets the debugger output base to
8 (octal)
odecimal 0 0 sets the debugger output base to
10 (decimal)
ohex 0 0 sets the debugger output base to
16 (hex)
stk 0 0 displays the debugger operation
stack
clrstk X 0 clears the debugger operation
stack
sysdump 0 0 causes a memory image to be
written on swapdev

Reading and writing memory, I/O ports, and registers:
In order to read the contents of a memory location, I/O, or register, first push the address of the memory location or I/O port on the stack.  In order to read a register, it is necessary to push a non-printable token, which refers to the register in question, onto the stack.  Memory and I/O address can be entered either as numbers in the current input base or as symbolics.  Registers are referred to by a percent sign (%), followed by the register name (e.g., % eax). The operators to read memory locations and registers are r1, r2, and  r4.  These operators read 1, 2, or 4 bytes respectively.  The value read is left on the top of the stack.  In order to write a register or memory location, the value to be written must be pushed first, then the address or register name to which it is to be written.  The operators for writing are w1, w2, and w4.  The same syntax applies to reading and writing I/O ports.  The operators for reading and writing I/O ports are rio1, rio2, rio4, wio1, wio2, and wio4.  The following table summarizes the read and write operators. 

No. of No. of
Operator Operands Results Result Description
r1 1 1 the byte read from address <tos>
r2 1 1 the word read from address <tos>
r4 1 1 the long word read from address <tos>
w1 2 0 the byte value <tos−1> is written
to address <tos>
w2 2 0 the word value <tos−1> is written
to address <tos>
w4 2 0 the long word value <tos−1> is
written to address <tos>
rio1 1 1 the byte read from port <tos>
rio2 1 1 the word read from port <tos>
rio4 1 1 the long word read from port <tos>
wio1 2 1 the byte value <tos−1> is written
to I/O port <tos>
wio2 2 1 the word value <tos−1> is written
to I/O port <tos>
wio4 2 1 the long word value <tos−1> is
written to I/O port <tos>

Special commands:
Among the special commands are commands to print inode structures, process table entries, and disassembled 80386 assembly code. 

To display disassembled code, the starting address from which to disassemble must be pushed, followed by the number of lines of assembly code to print (remember this is in the input base).  The operator is dis. 

Process table entries can be displayed by invoking the operator ps.  This operator takes no arguments and leaves no results on the stack. 

The sleeping operator displays the process IDs and WCHAN values of all processes that are sleeping.  Like ps, sleeping requires no arguments on the stack and leaves no results there. 

The stack operator displays a symbolic stack backtrace from the current point of execution.  It needs no arguments and leaves no result on the stack. 

The findsym operator takes one argument on the stack, an address, and displays the symbol with the highest address which is equal to or below the argument address.  This allows the user to find the name of the routine or data structure into which a given address is pointing. 

The dump operator displays a specified number of words of memory, starting at a specified location.  The starting location is pushed on the stack first, followed by the count of words to display.  Words are displayed grouped as bytes, and are displayed from top to bottom, left to right, higher address locations first.  In this way, byte and word ­ordering is always correct. 

On the right-hand side of the display are the ASCII equivalents of the bytes displayed on the left-hand side, which are displayed in hex.  The output of the dump operator is unaffected by the current output base of the debugger; it is always displayed in hex. 

The stackdump operator displays the entire kernel stack and user structure.  This dump is printed in the same format as that of the dump command (see above).  The pinode operator prints information contained in an inode structure.  It takes one argument on the stack, which must be the address of an inode structure.  Information printed includes the address of the inode (relative to the kernel symbol inode), the reference count, the device number, the inode number, the number of links to this inode, the owner and group IDs of this inode, and the size of the file referred to by this inode. 

The procaddr operator takes a source linear address and a process slot number.  This operator returns a linear address on the stack that can be used to access the source linear address in the process given.  This allows you to examine and modify other processes besides the one currently running.  The page represented by the source linear address must be in memory.  Note that it is likely that each 4K byte page of a process will lie in noncontiguous physical pages of memory, therefore it is necessary to be wary of page boundaries and use the procaddr operator to find the linear address of subsequent pages. 

The procstack operator takes a process slot number argument.  It prints out a kernel stack trace of the process desired in much the same way as the stack operator does. 

The procptr operator takes a process slot number and returns the address of that process’s process table entry. 

The gdevcblk command takes one argument, an address, and prints in English all structure items of the gdev_ctl_block structure with items indicated where the use of other commands is appropriate. 

The gdevpblk command takes one argument, an address, and prints in English all structure items of the gdev_parm_block structure with items indicated where the use of other commands is appropriate. 

The gdevpart command takes one argument, an address, and prints information on the partition table of the gdev_parm_block structure. 

The gdevdrv command takes one argument, an address, and prints information on the gdev_driver of the gdev_parm_block structure. 

The print_chain command takes one argument, an address, and prints the drq chain for a gdev_parm_block item. 

The print_proc command takes one argument, an address, and prints the proc structure information in English. 

Breakpoints:
The debug registers on the chip are available directly to the debugger. In addition, there are some commands for setting breakpoints and single-stepping conveniently. The debug registers are read and written with the following operators:

No. of No. of
Operator Operands Results Result Description
db0 0 1 leaves the contents of debug register
0 on stack
db1 0 1 leaves the contents of debug register
1 on stack
db2 0 1 leaves the contents of debug register
2 on stack
db3 0 1 leaves the contents of debug register
3 on stack
db6 0 1 leaves the contents of debug register
6 on stack
db7 0 1 leaves the contents of debug register
7 on stack
wdb0 1 0 sets db0 to <tos−1>
wdb1 1 0 sets db1 to <tos−1>
wdb2 1 0 sets db2 to <tos−1>
wdb3 1 0 sets db3 to <tos−1>
wdb6 1 0 sets db6 to <tos−1>
wdb7 1 0 sets db7 to <tos−1>

The functions of each of the debug registers can be found in the Intel 80386 Programmer’s Reference Manual (Order No. 230985−001). 

There are also explicit operators to set breakpoints.  These operators are brk0, brk1, brk2, brk3 ... These operators all have the same syntax; the only difference is that each one uses a different debug address register. These operators need two arguments on the stack when executed. The first of these arguments is the address or symbol at which to set a breakpoint. The second argument is a token specifying which type of breakpoint to set. The possible breakpoint type tokens are:

Token Breakpoint Type
.i break on instruction execution
.a break on byte access (read/write)
.aw break on word access (read/write)
.al break on long word access (read/write)
.m break on byte modify (write)
.mw break on word modify (write)
.ml break on long word modify (write)
.clr clear the breakpoint

The db?  operator displays the current status of the debug registers. 

For example, to set a breakpoint at the routine namei in the kernel, type:

namei .i brk0

This sets an instruction execution breakpoint at the first instruction in the routine namei. As another example, assume you would also like to break into the debugger whenever a write takes place to the data location swapdev. Further, suppose swapdev is a word (16-bit) object.  To set this breakpoint, enter:

swapdev .mw brk1

Notice the use of break register 1 this time; this is to avoid using the break register used by the instruction breakpoint above. 

To clear a breakpoint, for example, the breakpoint set at namei above, enter:

0 .clr brk0

In addition to allowing the setting of breakpoints using the on-chip debug facilities, the debugger also has the capability to single step instructions.  This is done by typing s.  Subsequent single steps may be performed by using RETURN. 

NOTES

Since debugger messages are always directed to the console, the debugger always attempts to switch to the console when invoked from a virtual terminal, if the console refers to a video adapter.  However, if the process running from the virtual terminal has the display driver in VT_PROCESS mode (as is the case for VP/ix or an X server, for example), then the switch to the console is not carried out.  For more information on VT_PROCESS mode, see display(7). Note that if the debugger is entered because of a system panic, the switch to the console will always occur. Moreover, once the debugger is entered, further virtual terminal switching is disabled until you exit the debugger.

In the case of a serial console, pressing the BREAK key on the console’s keyboard activates the debugger.  If you prefer to use a key other than BREAK, you can choose another key simply by changing the DEBSYM definition in the /etc/conf/pack.d/asy file.  In order for the new key to become active, a new kernel must be built and installed. 

SEE ALSO

crash(1M), console(7), display(7). 

BUGS

These are the currently known bugs.  They are being investigated. 

The sysdump command doesn’t always work. 

Due to variable initialization problems, after entering the debugger, always execute the stack command first. 

The debugger uses tab characters to separate columns of output, so make sure tabs are set on the terminal before entering the debugger. 

After breaking into the debugger at an instruction breakpoint, register access operators access the register set from the user context, not the kernel. 

The dis function sometimes enters an endless loop when given too large a count.  If this occurs, use CTRL ALT d to re-enter the debugger. 

ADDED VALUE

This entry, supplied by SunSoft, Inc., is an extension of UNIX System V. 

\*U  —  Version 1.0

Typewritten Software • bear@typewritten.org • Edmonds, WA 98026