previous next top complete contents complete index framed top this page unframed
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.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.
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 |
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.
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.
> 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" |
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.
MAINDEBUG User's Guide, Chapter 6