MAINDEBUG User's Guide, Chapter 6

previous   next   top   complete contents   complete index   framed top   this page unframed


6. Rigging POINTERs and STRINGs

Rigging POINTERs and STRINGs is a debugging facility to catch dangling POINTERs and STRINGs, which are the sources of especially pernicious errors. Using this facility can require invoking a combination of system PROCEDURE calls, MAINEX subcommands, and debugger commands.

6.1. Introduction to Rigging

A dangling POINTER references a dynamic object that has been disposed, or whose area has been freed or cleared. A dangling STRING references STRING text in an area's STRING space that has been freed or cleared. It is normal for a program's data structures to contain dangling POINTERs and STRINGs; dangling POINTERs and STRINGs cause a problem only if they are used in an access. However, such problems are often very difficult to track, because using a dangling POINTER or STRING does not necessarily cause an immediate error, and because slight changes in memory layout from one run of a program to another may cause the symptoms of a dangling POINTER or STRING bug to disappear.

The rigging method catches an access using a dangling POINTER or STRING when it occurs. It also makes the resulting error occur more reproducibly from one run of MAINSAIL to another. Using the method, MAINSAIL sets dangling POINTERs and the CHARADR parts of dangling STRINGs to a nonzero bit pattern that is invalid, which causes the processor or operating system to trap when the value is used for an access. This is called rigging the POINTER or STRING.

This method can be implemented on all currently available MAINSAIL platforms. It is possible that in the future, some platform will be encountered where there is no POINTER or CHARADR value that causes a trap that MAINSAIL can intercept, so that rigging POINTERs and STRINGs cannot be used.

It is not necessary to compile a MAINSAIL MODULE with any special compiler options in order to use rigging. In fact, it is not even necessary to compile the MODULE debuggable, although that is desirable in order to obtain as much information as possible about where a dangling reference occurred.

6.2. $startRigging and $stopRigging

By default, MAINSAIL does not rig POINTERs and STRINGs, since rigging may slow execution significantly. Your program can cause rigging to start by calling $startRigging:

PROCEDURE $startRigging (OPTIONAL INTEGER whatToRig);

whatToRig is interpreted as follows:

After a call to $startRigging, whenever a dynamic object is disposed or an area is cleared or disposed, all POINTERs and STRINGs that point to the reclaimed memory are rigged. However, POINTERs or STRINGs that were already dangling at the time of the call to $startRigging are not rigged.

To stop rigging, call $stopRigging:

PROCEDURE $stopRigging (OPTIONAL INTEGER whatNotToRig);

After a call to $stopRigging with no arguments, MAINSAIL no longer does anything special when dynamic objects are disposed or areas are cleared or disposed. POINTERs and STRINGs that have already been rigged remain rigged. If the argument is pointerCode, only POINTERs stop being rigged; if stringCode, only STRINGs.

You can also control rigging interactively by issuing the following MAINEX subcommands:

RIGPTRS Start rigging POINTERs
RIGSTRS Start rigging STRINGs
NORIGPTRS Stop rigging POINTERs
NORIGSTRS Stop rigging STRINGs

6.3. Rigging Is Slow

The process of rigging a POINTER whenever a dynamic object is disposed or an area disposed or cleared is a very slow one. The MAINSAIL memory manager must go through a process similar to a garbage collection in order to find each dangling POINTER or STRING. Depending on how frequently your program disposes objects or areas or clears areas, the execution time overhead can range from negligible to a factor of ten or more.

The potential for vastly increased execution time is the reason why POINTERs and STRINGs are not rigged by default (except on SUPERCHECK platforms, where rigging incurs a smaller execution time penalty; see Chapter 18 of the MAINSAIL Language Manual).

Because dangling POINTER and STRING bugs in large programs are very difficult to track, it can sometimes be worth the extra execution time to enable rigging when you suspect that a bug in your program is caused by a dangling POINTER or STRING. If you suspect such a bug, either modify your program to call $startRigging when it begins executing, or issue the MAINEX subcommands RIGPTRS and/or RIGSTRS, or, from the debugger, issue the command XS $startRigging. Then run your program from the debugger. Your program's runtime may be much longer than normal, but the amount of time you spend may be small compared to the time it would take to track down the bug without rigging.

To reduce the penalty in runtime that you pay for rigging, you may want to start rigging at some point later than the very beginning of your program's execution. If you are sure that no POINTER or STRING that will later be used becomes dangling before a certain point in the program, set a breakpoint there, and enable rigging only when the breakpoint is reached.

6.4. What Happens When a Rigged POINTER or STRING Is Used

Currently, if a rigged POINTER or STRING is accessed, you get an error from the operating system. On UNIX, this error is typically a segmentation violation or a bus error. When MAINSAIL intercepts this error, you should enter the MAINSAIL debugger (or use the CALLS utility MODULE if your program is not compiled debuggable) to see where the error occurred.

Unfortunately, except for a few selected parts of the MAINSAIL runtime system (such as the Structure Blaster), it is difficult to get MAINSAIL to issue a distinctive error message for the use of a rigged POINTER or STRING. The rigged POINTERs and STRINGs do have distinctive bit patterns, but some operating systems do not provide a way for MAINSAIL to figure out the bit pattern of the address whose access caused the error. However, you can deduce this yourself with a little sleuthing around in the debugger.

The bit patterns MAINSAIL uses by default are (currently the same on all platforms, although this may change):

Identifier Default Value Purpose
$riggedAdrBits 'HFFFFBEEF for rigged POINTERs
$riggedCharadrBits 'HFFFFBEEE for CHARADRs of rigged STRINGs

You can change the values of the bit patterns used at runtime; see below. The values currently used are available through the system variables $riggedAdrBits and $riggedCharadrBits.

Thus, for example, if rigging POINTERs is enabled, and you get a bus error and enter the debugger and find yourself sitting on a statement like:

p.f := q.f

you can issue the debugger command:

v p,q

If the output is something like:

p = 'H16B408:P
q = 'HFFFFBEEF:P

then you can assume that q is a dangling POINTER.

If you suspect a dangling STRING instead of a POINTER, you can use the debugger to inspect the CHARADR part of a STRING s's STRING descriptor with:

v cvc(s)

If you are lucky enough to get an error message that lists the address of the MAINSAIL chunk containing a dangling POINTER or STRING, you can do something like:

.v cvp('H14ABC20L)

(assuming 'H14ABC20L is the chunk address of a dangling POINTER as reported in the error message); one of the fields or elements of the chunk should have the rigged bit pattern as its value, and this is the dangling POINTER or STRING.

6.5. Changing the Default Bit Patterns for Rigged POINTERs and STRINGs

The rigging bit patterns 'HFFFFBEEFL and 'HFFFFBEEEL have been chosen because they are unlikely ever to be valid addresses in actual programs. However, on some platforms, there is a slight possibility they could be valid in some cases, and therefore fail to trigger an error. If you know that these addresses are valid on your system, and wish to use different addresses (e.g., 'HBBBBBEEFL and 'HBBBBEEEEL), you can use the MAINEX subcommands RIGGINGPTRBADADDR and RIGGINGSTRBADCHAR:

riggingPtrBadAddr 'HBBBBBEEF<eol>
riggingStrBadChar 'HBBBBEEEE<eol>

Both of these subcommands take a LONG BITS argument (from which the trailing L may be omitted), which is the bit pattern to use for subsequently rigged POINTERs and STRINGs. These subcommands set the system variables $riggedAdrBits and $riggedCharadrBits, respectively. The subcommands may be set in the site.cmd file on your MAINSAIL directory if you wish to specify a default more appropriate to your site than the one MAINSAIL is shipped with.

With no argument, each of these subcommands shows the current value of the corresponding bit pattern.

If you need to figure out which addresses are valid and invalid on your system, you can try running a program like the one in Figure 6–1, which works on machines with 32-bit addresses. It checks every address on a 64-Kbyte boundary to see whether or not loading from it causes an exception. It prints out a message whenever the validity of the address it just tried is different from that of the previous address it tried. Of course, it determines only which addresses are valid in the current process's address space; other executions of MAINSAIL on the same machine may produce different results.

Figure 6–1. Program to Test for Valid Memory Addresses
BEGIN "valMem"

INITIAL PROCEDURE;
BEGIN
BOOLEAN wasAbleToLoadFromLastAddress;
LONG INTEGER ii;

DEFINE inc = cvli('H00010000L);

ii := 0L; wasAbleToLoadFromLastAddress := FALSE;

Positive addresses first
DOB $HANDLEB
        
lbLoad(cva(ii));
        
IF NOT wasAbleToLoadFromLastAddress THENB
            
write(logFile,cvs(cvlb(ii),hex),
                ": 
there" & eol);
            
wasAbleToLoadFromLastAddress := TRUE END END
    
$WITH
        
IF wasAbleToLoadFromLastAddress THENB
            
write(logFile,cvs(cvlb(ii),hex),
                ": 
NOT there" & eol);
            
wasAbleToLoadFromLastAddress := FALSE END;
    
IF $maxLongInteger - inc < ii THEN DONE;
    
ii .+ inc END;

ii := $minLongInteger;
Now negative addresses
DOB $HANDLEB
        
lbLoad(cva(ii));
        
IF NOT wasAbleToLoadFromLastAddress THENB
            
write(logFile,cvs(cvlb(ii),hex),
                ": 
there" & eol);
            
wasAbleToLoadFromLastAddress := TRUE END END
    
$WITH
        
IF wasAbleToLoadFromLastAddress THENB
            
write(logFile,cvs(cvlb(ii),hex),
                ": 
NOT there" & eol);
            
wasAbleToLoadFromLastAddress := FALSE END;
    
IF ii GEQ 0L THEN DONE;
    
ii .+ inc END;

END;

END "valMem"

6.6. Figuring Out Where the Rigged POINTER or STRING Was Disposed

Sometimes, as soon as you see the statement that used a dangling POINTER or STRING, you understand where the problem originated and are able to fix it. Other times, you need to know exactly where the POINTER or STRING became dangling.

To figure this out, first figure out how to run your program with rigging set such that the error is reproducible. Then you may be able to use one of the debugger commands:

+W:N cvlb(cva(myPtr)) = $riggedAdrBits

or:

+W:N cvlb(cvc(myStr)) = $riggedCharadrBits

These commands allow you to watch an expression and break when that expression's value changes. The debugger will break on the first debuggable PROCEDURE call after the POINTER or STRING changes to the rigged value. Once you reach this point, where the object or STRING text has just been disposed, examine and record the count value. Then run the program again, setting the count break to one less than the recorded count value. You can then single step, checking for the rigged value at each statement to determine which statement is incorrectly freeing memory.

6.6.1. Debugger's +W:C Command and POINTER Rigging

When rigging is turned on, the debugger command +W:C p does not break when p becomes dangling, i.e., when p is set to the rigging pattern. This is because the debugger remembers the old value of p in order to see when p changes, but the debugger's saved value changes to the rigging pattern at the same time p's value does, when the dynamic object both point to is disposed. When it comes time to determine whether the watch breakpoint has been reached, the debugger compares the two bit patterns, and they are the same, so the debugger thinks that p has not changed.

Use +W:N cvlb(cva(p)) = $riggedAdrBits, as shown above, to break when p becomes dangling.

Similar considerations apply to STRINGs that become dangling.


previous   next   top   complete contents   complete index   framed top   this page unframed

MAINDEBUG User's Guide, Chapter 6