Part VI: Debugging Techniques

 

 

Writing VMS Privileged Code

Part VI: Debugging Techniques

by

Hunter Goatley

Edward A. Heinrich

 


As is true for any kind of computer programming, one of the most important phases when writing code is debugging—locating any errors (bugs) in the program and correcting them. With systems programming, this phase is even more critical, since buggy systems-level code can often result in the corruption of internal data structures and system crashes. While the OpenVMS debugger is highly-regarded by most OpenVMS programmers, it can only be used to debug user-mode code—trying to use the OpenVMS debugger for executive- or kernel-mode code will terminate the process or crash the system. However, this does not mean that the OpenVMS systems programmer is without debugging tools. In fact, OpenVMS provides a rich assortment of debugging methods that make even systems debugging relatively easy.

XDELTA-The Systems Programming Debugger

One of the best-kept secrets about OpenVMS is that there are actually two debuggers supplied with the system: the OpenVMS symbolic debugger that most programmers are familiar with, and the Delta/XDelta debugger. The Delta/XDelta utility itself actually consists of two debuggers, DELTA and XDELTA. Both debuggers have the same set of commands, but they differ in how they actually work.


NOTE: OpenVMS AXP also includes a symbolic debugger for use with systems-level code. This new debugger is a client/server system and requires two Alpha AXP machines. This new debugger will be discussed in detail in a future article.


Like DEBUG, DELTA works by setting up an exception handler that traps breakpoints. DELTA can be used to monitor privileged code running in kernel and executive modes at IPL 0, as well as normal user-mode programs. The DELTA debugger is invoked in the same ways as the normal OpenVMS debugger—by linking programs using the /DEBUG qualifier on the LINK command line and by the DCL command DEBUG.

XDELTA runs as a “standalone” debugger that uses the system console for all input and output—when XDELTA is running, nothing else on the system can run. This is because XDELTA is invoked via an interrupt at IPL 14—there is an entry in the OpenVMS SCB (System Control Block) through which XDELTA is called. Additionally, XDELTA is an optional component of the OpenVMS operating system; it must be explicitly loaded into system memory to be used. If XDELTA has not been loaded, the interrupts that invoke it are ignored. Because XDELTA prevents other programs from running and uses the system console for all I/O, it is useful only for debugging software that runs in kernel mode. XDELTA can even be used to debug code that runs at elevated IPL.

The sections below briefly describe the DELTA and XDELTA debuggers and some of their commands. There is a manual provided with the OpenVMS documentation set that fully describes Delta/XDelta. Please see the OpenVMS Delta/XDelta Utility Manual for more information.

Invoking DELTA

As mentioned above, the DELTA debugger can be entered in much the same way as the normal OpenVMS debugger is invoked. Programs can be linked using /DEBUG on the LINK command, or the RUN/DEBUG command can be given to execute the program (assuming it hasn’t been linked /NOTRACE). In order to specify that the DELTA debugger is to be used, the logical name LIB$DEBUG must be defined to point to the DELTA executable image in SYS$SYSTEM:

  $ DEFINE LIB$DEBUG SYS$SYSTEM:DELTA.EXE

When a program is run with /DEBUG after that logical has been defined, output like the following will be displayed:

  $ define lib$debug sys$system:delta.exe
  $ run/debug test
  DELTA Version 5.0

  0000020A/MOVL    @#00000000,R0

At this point, the DELTA debugger is waiting for a command to be entered from the terminal.

Invoking XDELTA

XDELTA is implemented as a loadable module of the OpenVMS executive. The XDELTA module is not normally loaded when OpenVMS is booted, so extra steps must be taken to load it. When XDELTA is not loaded, any attempts to invoke it are essentially ignored. The SCB entry for XDELTA points to an RSB (Return from SuBroutine) instruction, which effectively dismisses any calls to XDELTA.

Loading XDELTA

XDELTA is normally loaded when the system is booted by placing a value in R5. The actual procedure varies from system to system; please see the OpenVMS Delta/XDelta Utility Manual for full system-specific details. In general, the contents of R5 can be specified on the BOOT command line. The contents of R5 are interpreted as a bit mask by OpenVMS as it boots; this is used to specify that XDELTA is to be loaded, or that the system should boot into SYSBOOT (also known as a “conversational” boot, because a number of SYSGEN commands can be executed in SYSBOOT before continuing). Table 1-1 lists the various values that can be stored in R5; to specify more than option, the values are simply added together. For example, to load XDELTA (2) and enter SYSBOOT (1), a 3 should be placed in R5. This is normally done using a console command like one of the following:

  >>> BOOT/3

     or

  >>> BOOT/R5=3

     or (Alpha)

  >>> BOOT -FLAGS 0,3
        Table_1-1:__XDELTA_BOOT_Command_Qualifier_Values__________________

        Value__Description________________________________________________

        0      Normal boot
        1      Stop in SYSBOOT (``conversational'' boot)
        2      Load XDELTA
        3      Load XDELTA and stop in SYSBOOT
        6      Load XDELTA and take initial breakpoint
        7      Load XDELTA, stop in SYSBOOT, and take initial breakpoint
        ------------------------------------------------------------------

When XDELTA is loaded, OpenVMS can be told to take the initial breakpoint, which means that the system will invoke XDELTA before booting. If the initial breakpoint is taken, XDELTA will display the following prompt on the console before OpenVMS boots:

  1 BRK AT 800027A8
  800027A8/NOP

At this point, any valid XDELTA command can be entered. Typically, “;P” is entered to proceed with the system boot.

Loading XDELTA After Booting

According to the OpenVMS documentation, XDELTA can only be loaded when the system is booted. This is, however, not exactly true for OpenVMS VAX, because the XDELTA image (SYS$SYSTEM:SYSTEM_DEBUG.EXE) is a loadable executive image that can actually be loaded at any time by calling the system routine LDR$LOAD_IMAGE (defined in module SYSLDR). LDR$LOAD_IMAGE is called by several of the system initialization images to load all of the modular pieces of the OpenVMS executive. For a complete description of its actions, please consult the OpenVMS Internals & Data Structures manual.

LDR$LOAD_IMAGE is called in executive mode; it can be specified as the ROUTIN parameter to the $CMEXEC system service macro. It accepts 3 parameters:

  • the address of a descriptor for loadable image file name;
  • a flags longword indicating whether or not all code and data should be stored in nonpaged memory, whether or not the image is unloadable, and whether or not the bugcheck code should overlay the read-only sections of the image;
  • the address of a three-longword block that will receive information about the image (the base address is returned in the first longword).

Figure 1 shows a simple C program that will dynamically load XDELTA on a VAX system. Because of changes to XDELTA for OpenVMS AXP, it is not possible to load it dynamically—the image can be loaded easily enough, but the system doesn’t recognize the INI$BRK breakpoint because of changes in the XDELTA initialization.

#define module_name LOAD_XDELTA
#define module_ident "V1.0"
/*
*
*  Program:    LOAD_XDELTA.C
*
*  Author:     Hunter Goatley, goathunter@WKUVX1.BITNET
*
*  Date:       July 11, 1992
*
*  Abstract:   Load XDELTA on a running system.
*
*  Modified by:
*
*      01-000          Hunter Goatley          11-JUL-1992 08:22
*
*/
#if defined(__DECC) || defined(__DECCXX)
#pragma module module_name module_ident
#else
#module module_name module_ident
#endif

#include
#include
#include #include
#include
#include

extern LDR$LOAD_IMAGE();
extern exe_std$set_page_protection(int p1, int p2, unsigned long *p3);
extern INI$BRK();
extern set_page_prot (int);

static char xdelta_image[] = "SYS$LOADABLE_IMAGES:SYSTEM_DEBUG.EXE";

int init_ini_brk (void);

long int
main (void)
{
  struct dsc$descriptor_s xdelta;

  struct image_info_array {
    char *image_address;
    long int long1;
    long int long2;
  } image_info;

  struct {
    long int argno;
    struct dsc$descriptor_s *filename;
    long int flags;
    struct image_info_array *image_info_ptr;
  } arglist;

  register unsigned long int status;

  xdelta.dsc$w_length = sizeof(xdelta_image)-1;
  xdelta.dsc$b_dtype = DSC$K_DTYPE_T;
  xdelta.dsc$b_class = DSC$K_CLASS_S;
  xdelta.dsc$a_pointer = xdelta_image;

  arglist.argno = 0;
printf("Calling CMKRNL...");
  status = sys$cmkrnl (init_ini_brk, &arglist);

  arglist.argno = 3;
  arglist.filename = &xdelta;
  arglist.flags = 1;
  arglist.image_info_ptr = &image_info;

  status = sys$cmexec (LDR$LOAD_IMAGE, &arglist);

  return(status);
}

int init_ini_brk (void)
{
  unsigned long *ptr;

  struct ini_brk_prc_desc {
    unsigned long x;
    unsigned long y;
    unsigned long *ptr;
  } *ini_brk_desc;

  unsigned long status;

  ini_brk_desc = (struct ini_brk_prc_desc *) INI$BRK;
  ptr = (unsigned long *) ini_brk_desc->ptr;

  status = exe_std$set_page_protection (HWPRT$C_URKW, 1, ptr);
  if (!(status & 1)) return (status);

  (*ptr) = 0x00000080; /* This is a BPT instruction */

  __PAL_IMB(); /* Instruction Memory Barrier */

  status = exe_std$set_page_protection (HWPRT$C_UR, 1, ptr);
  if (!(status & 1)) return (status);

  return(status);
}

Invoking XDELTA

XDELTA can be invoked on a running system in one of two ways: an application can call it directly, and a software interrupt can be generated by an application or by console commands.

Calling XDELTA

A MACRO-32 program can call XDELTA directly using the JSB (Jump SuBroutine) instruction. The JSB target is the global symbol INI$BRK:

          JSB     G^INI$BRK  ;Invoke XDELTA

When XDELTA is loaded and this instruction is executed, XDELTA prompts for input on the console:

  1 BRK AT 800027A8
  800027A8/NOP

If XDELTA is not loaded, INI$BRK points to an RSB instruction; any JSBs to INI$BRK immediately return. This is true on both VAX and AXP.


NOTE: Invoking XDELTA via a JSB to INI$BRK will work from any access mode, not just kernel or executive modes.


Programs can also invoke XDELTA by generating a software interrupt 14. This is usually done by using the SOFTINT macro from LIB.MLB. SOFTINT takes as a parameter the interrupt IPL to generate. The following VAX MACRO code fragment shows how XDELTA can be invoked:

          SOFTINT #14           ;Generate IPL 14 interrupt (XDELTA)

On both VAX and AXP, SOFTINT produces a MTPR (Move To Processor Register) instruction (a PALCODE call on AXP) that copies the specified IPL to the Software Interrupt Request register (PR$_SIRR, which is register 20 on a VAX processor). This instruction is a privileged instruction that can only be executed from kernel mode. The following line shows the actual MACRO-32 code that is created when the SOFTINT macro is expanded:

          MTPR    #14,S^#PR$_SIRR

Invoking XDELTA from a VAX console

Once loaded on a VAX, XDELTA can also be invoked at any time by requesting a software interrupt from the console. This method is useful when debugging a program that does not contain any calls to INI$BRK or is in an infinite loop. The principle is the same as that performed by the SOFTINT macro: a software interrupt is requested by depositing the IPL (14 for XDELTA) into the Internal Processor Register 14 (hex) on a VAX. This is usually accomplished using console commands like the following:

  $  or 

  ?02 EXT HLT
      PC = 8083D6D3
  >>> D/I 14 E
  >>> C
  1 BRK AT 800027A8
  800027A8/NOP

The actual key pressed to enter console mode varies from system to system; the OpenVMS Delta/XDelta Utility Manual contains system-specific information. Note that the values of the XDELTA IPL must be given as a hexadecimal value (E (hex), which is 14 decimal).

Note that this works only on a VAX and not on an Alpha AXP system.

Using DELTA/XDELTA

Unlike the normal OpenVMS debugger, DELTA/XDELTA is a very primitive utility. It only has one error message (EH?), and most of the commands consist of single characters or key presses. However, with only a little practice, it becomes a very simple utility to use.

Stepping Through Code

The “S” command lets the programmer single-step through a sequence of instructions. When XDELTA is first invoked through a JSB to INI$BRK on a VAX, a NOP instruction is displayed (it’s a BPT instruction on an AXP). Pressing “S” at that point will “step” through the NOP (the NOP is executed) and an RSB will be displayed. Stepping past the RSB will return to the instruction following the JSB to INI$BRK, etc.

The “S” command can also be used to single-step through routine calls (invoked via a JSB or CALLx on a VAX). If there is no need to watch the target routine execute, the “O” command can be used to step “over” the current instruction. In the case of a CALLS or CALLG on a VAX, the call to the target routine is made and XDELTA assumes control again at the instruction following the original CALLS or CALLG. This feature is especially useful when dealing with code that makes a lot of calls to run-time library routines or to system services. Trying to single-step through one of those routines can be an exercise is frustration—but it’s also valuable for seeing just how many instructions are executed to perform even the simplest of services.

When all data has been examined and all code stepped through, the command “;P” is used to “proceed” to the next breakpoint, if there is one, or to return control back to OpenVMS.

Examining Memory

DELTA/XDELTA can display memory locations in a variety of formats. A memory location is examined by first “opening” the location and then displaying it. Typically, an address is opened by simply specifying the address at the XDELTA prompt. The character entered after the address determines the format of the display: a slash (/) displays a hexadecimal longword, by default, a double-quote (“) displays it as ASCII text, and an exclamation point (!) displays it as a sequence of instructions.

          Example 1-1:  DELTA/XDELTA Command Example
          _____________________________________________________________________

          $ run x
          DELTA Version 5.0

          00000210/CLRL    R1         
          00000212/MOVL    R1,R2      
          00000215/MOVL    #00000001,R0       
          0000021C/RET        s
          00000212/MOVL    R1,R2      s
          00000215/MOVL    #00000001,R0       r0/7FF0EF60

          200"Hunt 
          00000204/er G 
          00000208/oatl 
          0000020C/ey 210!CLRL    R1       ;p

           EXIT 00000001
          804432B2/POPR    #03        exit

          $

          _____________________________________________________________________

Subsequent memory locations can be displayed in the current format by simply pressing repeatedly. Example 1-1 demonstrates the DELTA output from a simple program.

Setting Breakpoints

Up to 8 arbitrary breakpoints can be set from the DELTA/XDELTA prompt. Each breakpoint is assigned to a particular breakpoint number, from 1 to 8. On a VAX, breakpoint 1 is the primary XDELTA entry point at INI$BRK; when the 1 breakpoint is cleared on a VAX, XDELTA is effectively disabled. This does not work on AXP because of the changes to the XDELTA initialization.

Breakpoints are set, cleared, and shown using the “;B” DELTA/XDELTA command. A breakpoint is set by specifying the address of the instruction at which XDELTA is to be invoked, followed by a comma (,) and the breakpoint number for that address. The “;B” is then added to the end of the command:

  800017A2 NOP      80002323,2;B

This command sets a breakpoint at the instruction at address 80002323. The breakpoint is assigned as breakpoint 2 (remember that 1 is the INI$BRK address). The next time control is transferred to 80002323, XDELTA will be invoked and XDELTA debugging commands can be entered on the system console.

A breakpoint can be cleared by storing a 0 in the breakpoint slot. For example, the following command will cancel the breakpoint set above by storing 0 as breakpoint 2:

  80002323 RET      0,2;B

As mentioned earlier, executing the following command will disable XDELTA on a VAX system:

       0,1;B

Executing the command “;B” alone will list all current breakpoint settings.


NOTE: When setting breakpoints, keep in mind that DELTA and XDELTA use addresses literally. It is the programmer’s job to remember to account for entry masks when setting breakpoints on CALLable routines on a VAX. For example, if routine CALL_ME is called via a CALLS or CALLG on a VAX and it is loaded at address 62416, the breakpoint must be set as 62616 (624 plus 2 bytes for the entry mask).


Other Features

DELTA and XDELTA provide a number of other useful features for the systems programmer, including the following:

  • the ability to examine memory locations and deposit new values;
  • the ability to examine and deposit in other processes’ memory (DELTA only);
  • the ability to include DELTA/XDELTA commands in a program and execute them from within the debugger;
  • and the ability to list the names and locations of all loaded executive images.

For more information about the Delta/XDelta Utility, please see the OpenVMS Delta/XDelta Utility Manual.

Conditional Breakpoints and Debug Data

In addition to setting breakpoints by issuing XDELTA commands, XDELTA can be invoked directly by a program as described above. When a program issues a JSB to INI$BRK, XDELTA prompts for input on the console.

This ability to invoke XDELTA on-the-fly can be important to systems programmers, because it lets them easily invoke the debugger only for those routines that must be debugged. For example, it is not necessary to first invoke XDELTA, determine the addresses for which breakpoints should be set, and then set the breakpoints. No matter where the routine is located in memory, XDELTA can be called. This ability is useful for both stepping through a routine and also for checking to see whether or not, and under what conditions, a routine is called.

While extremely useful when testing software, any calls to XDELTA should be removed from production software. As long as XDELTA is not loaded, any JSBs to INI$BRK will just be dismissed by the system. However, if XDELTA is ever loaded, the next call will generate the breakpoint—which is probably not a good thing to do on a production system, since it effectively halts the system.

There are a number of techniques for removing debugging calls to XDELTA. For example, all JSB G^INI$BRK instructions could be deleted from the source program. Another option would be to use macros to simply not generate the instructions unless a compile-time flag is set. Both of these options work well for producing code for execution on a production system. But sometimes it’s desirable during testing to be able to control dynamically whether or not XDELTA should be invoked. Disabling the initial XDELTA breakpoint (as described in Section 1.1.4.3) is not a very useful option because XDELTA cannot be invoked again without rebooting the system and reloading XDELTA.

A better approach would be to determine at run-time whether or not XDELTA is to be invoked. There are a couple of ways this can be accomplished: by checking the value stored in some longword and by checking the existence of a system logical.

Using System Logicals to Enable Debugging

System logicals can be used to provide a very flexible method for determining the level of debugging that is to be provided during a program run. The basic idea is simple: check to see if a certain logical is defined and, if it is, execute the extra debugging code. For example, the program may check to see if a logical KERNEL_DEBUG is defined by calling the $TRNLNM system service. If the logical is defined, the program can set some internal flag that would be checked before doing such things as calling XDELTA.

For example, Example 1-2 demonstrates a simple use of this mechanism.

Example 1-2:  Sample Debug Logical Check
_____________________________________________________________________
DEBUG_ENABLED:  .LONG   0  ; Start out debugging disabled
LOGICAL_TABLE:  .ASCID  /LNM$SYSTEM_TABLE/
KERNEL_DEBUG:   .ASCID  /KERNEL_DEBUG/
        .
        .
        .
        $TRNLNM_S -
                TABNAM = LOGICAL_TABLE,-
                LOGNAM = KERNEL_DEBUG
        BLBC    R0,10$          ; Branch if not defined
        MOVL    #1,DEBUG_ENABLED   ; Set the DEBUG_ENABLED flag
10$:    .
        .
        .
        BLBC    DEBUG_ENABLED,20$  ; Branch if not debugging
        JSB     G^INI$BRK          ; Jump to invoke XDELTA
        .
        .
        .
_____________________________________________________________________

The system logical would be defined with a command like the following:

  $ DEFINE/SYSTEM/EXEC KERNEL_DEBUG TRUE

Of course, the use of a logical name can provide a lot more flexibility than just enabling and disabling debugging. For example, the program could treat the logical’s equivalence string as a number that indicates the level to which debugging steps should be performed. For example, the value could be treated as a bitmask, where each bit represents something different—bit 0 set means call XDELTA, bit 1 set means write an entry to the error logger (discussed below), etc.

Note that the use of system logicals may not be possible in programs that do not have process context available. Such programs could include device drivers and interrupt handlers. However, the same principles can be applied using memory that is accessible to these programs.

Using EXE$GL_SITESPEC and CTL$GL_SITESPEC

OpenVMS provides a system-wide longword that is reserved for use by a site. This longword, EXE$GL_SITESPEC, can be used to set conditional breakpoints for drivers and other privileged code, assuming it’s not already being used for anything else. Much like setting the internal flag described in the previous section, the value stored in EXE$GL_SITESPEC can be treated as either a simple on/off flag or as a complex bitmask.

The use of EXE$GL_SITESPEC is especially useful for tracking bugs that only happen periodically. The following code fragment shows how the value could be checked from a VAX MACRO program:

        BLBS    G^EXE$GL_SITESPEC, 0$   ; Branch if not in debug mode
        JSB     G^INI$BRK               ; Else call XDELTA breakpoint

In addition to the system-wide longword, each process has a process-specific site longword that can be used for debugging kernel-mode code that executes in process context. This longword is the global symbol CTL$GL_SITESPEC.

Of course, the site-specific longwords can be used for any purpose. For example, the following code allocates some memory from nonpaged pool and stores the address of the allocated memory in CTL$GL_SITESPEC; if the system were to crash, the allocated memory could be easily located by simply checking the SITESPEC longword for that process.

        JSB     G^EXE$ALONONPAGED       ; Ask for some more nonpaged pool
        BLBC    R0, 1200$               ; Continue if there wasn't any left
        MOVL    R2, G^CTL$GL_SITESPEC   ; Make finding it in a dump easier

Example 1-3 is a C program that toggles the value in EXE$GL_SITESPEC to allow code to include conditional breakpoints by checking the low bit.

__________________________________________________________________
Example 1-3:  SETBREAK.C
__________________________________________________________________
#define module_name SETBREAK
#define module_ident "V1.0"
/*
 *
 *  Program:	SETBREAK.C
 *
 *  Author:	Hunter Goatley, goathunter@WKUVX1.BITNET
 *
 *  Date:	July 11, 1992
 *
 *  Abstract:	Toggle value of EXE$GL_SITESPEC on a running system.
 *
 *  Modified by:
 *
 *	01-000		Hunter Goatley		11-JUL-1992 14:00
 *
 */
#if defined(__DECC) || defined(__DECCXX)
#pragma module module_name module_ident
#else
#module module_name module_ident
#endif

#include 
#include 
#include 

#if defined(__DECC) || defined(__DECCXX)
#pragma extern_model strict_refdef		/* This is how you do */
extern EXE$GL_SITESPEC;				/* globalref in DEC C */
#else
globalref EXE$GL_SITESPEC;
#endif

int kernel_toggle (void);

long int main (void)
{
  return (sys$cmkrnl (kernel_toggle, 0));	/* Goto kernel mode to toggle */
}

int kernel_toggle (void)
{
    EXE$GL_SITESPEC = (EXE$GL_SITESPEC == 0 ? 1 : 0);	/* Toggle it! */
    return (SS$_NORMAL);
}
__________________________________________________________________

Crash Spots and Data Cells

Even though code running on a production system should not invoke XDELTA, the systems programmer is not without a means of obtaining additional information in the event of a system crash. Through the use of debug data cells, a history of code execution can be maintained, along with additional useful data.

For example, a programmer writing a device driver might store a unique value in a debug location at the beginning and end of each routine in the driver to indicate which routine was active or had completed at the time of a crash. Other information that might be useful could include the address of the current UCB, the contents of one or more registers, the address of the IRP that was being processed, etc.

When a system that has a system dump file crashes, the contents of memory at the time of the crash are written to the dump file for later analysis using the System Dump Analyzer (SDA). Most of the time, it’s easy to determine from the dump which process caused the crash and which code thread was executing at the time. However, there are times when it’s not easy; in cases like this, debug data cells are especially useful.

Debug Data In-Line with Code

For device drivers, an easy way to implement the debug data storage is to simply allocate space for the data somewhere in the code for the driver. For example, the following section could be positioned before the initialization routine for the driver:

        .
        .
        .
        DBG_DATA::      .Long   ^X0999  ; Way to find base of crash data
        DBG_LOCATION::  .Long        0  ; Where we crashed
        DBG_UCB::       .Long        0  ; Address of current UCB
        DBG_VERSION::   .Long ^X051034  ; Version V5.1-034
        .
        .
        .

Each of the global symbols (DBG_DATA, DBG_LOCATION, etc.) would be accessible from all modules that comprise the driver. The following MACRO-32 code shows one way that the locations could be used:

  ROUTINE::     ; Beginning of subroutine
          MOVL    #^X1000, DBG_LOCATION	; Mark where we are
          MOVL    R5, DBG_UCB		; Save current UCB address
          .
          .
          .
          MOVL    #^X1099, DBG_LOCATION	; Flag we exited from here safely
          RSB				; And go back to calling routine

If each routine begins and ends by storing a unique value in DBG_ LOCATION, the current or most-recently-executed routine can be quickly and easily identified by simply examining the contents of DBG_LOCATION. The value stored at DBG_VERSION represents the current version of the software, which can help ensure that the latest version of the software was actually running. The value stored in DBG_UCB identifies the UCB that the driver was currently servicing.

Once the data has been created, the programmer must be able to quickly locate it, either on a running system or in a dump file. The value stored in DBG_DATA (^X0999) is present only to help locate the debug data using the SDA command SEARCH. The SEARCH command simply steps through memory looking for a specified value. To search for the beginning of the debug data, a command like the following would be used:

          SDA> SEARCH/STEP=BYTE xxDRIVER;1000 999

where xxDRIVER is the name of the device driver. SEARCH will start at the beginning of the driver and search through 100016 bytes for the value 99916. SDA will display the address of each match that’s made:

          SDA> search/step=byte ghdriver;1000 999
          Searching from 807D96E0 to 807DA6E0 in BYTE steps for 00000999...
          Match at 807D9800
          SDA>

Once the debug data area has been located, the EXAMINE command can be used to look at the data:

          SDA> exam 807D9800;10
          00051034 807AB240 00001099 00000999  ................     807D9800
          SDA>

NOTE: To use the method above on OpenVMS AXP systems, the data must be in a different PSECT from the code.

Debug Data in Extended Structures

The debug data can also be included in data structures allocated from nonpaged pool, for example, in AST Control Blocks and UCBs. The only problem with this method is in locating the address of the structure. One trick that can be used to solve this problem is storing a unique hexadecimal string in the data structures, which can then be located via the SDA SEARCH command. For example, the following instruction copies “BAD ED” to a debug offset in the data structure pointed to by R2:

          MOVL    #^X000BADED, DBG_DATA(R2) ; Store "BAD ED" in there

If the program already uses existing OpenVMS data structures (UCBs, for example), the structure can be extended to include storage space for debug data. The following MACRO-32 fragment shows how this is typically done.

        $DEFINI UCB,GLOBAL ; Start of extended UCB definitions
        .=UCB$C_LENGTH     ; Position at end of standard UCB
$DEF    UCB_L_DBG_LOCATION ; Last place we were at
        .Blkl 1
$DEF    UCB_L_DBG_R3       ; R3 at time of exit
        .Blkl 1
        UCB_C_LENGTH == .  ; Length of our UCB
        $DEFEND UCB        ; End of extended UCB definitions

When these statements are processed by the MACRO-32 assembler/compiler, the current position (.) is set to the length of a normal UCB, then the extra longwords are added. For example, the value of the symbol UCB_L_DBG_LOCATION will be equal to the value of UCB$C_LENGTH and UCB_L_DBG_R3 will be equal to UCB_L_DBG_LOCATION+4. For a device driver, extending the UCB provides the ability to save debug information for every device manipulated by the driver, whereas the general in-line debug data only provides information about the driver itself and the last UCB processed.


Hunter Goatley, Western Kentucky University, Bowling Green, KY.
Edward A. Heinrich, Vice-President, LOKI Group, Inc., Sandy, UT.

 Posted by at 10:31 pm