The C part of the GNU compiler now supports full fine-grained pointer checking at runtime. This work was originally done by Richard W.M. Jones `rjones@orchestream.com', and has been extended by the work of many other kind contributors.1
The runtime checking library, test kit and various tools can be found in
the bounds/
subdirectory.
The brief manual here is a distillation of the original paper that appeared
at the same time as the original patches to GCC. The paper contains more
details about the inner workings of bounds checking GCC. The paper can be
found in PostScript format in bounds/report/bcrep2.ps.gz
.
GCC_BOUNDS_OPTS
.
To compile all or part of your program with bounds checking, simply add the
-fbounds-checking
flag when compiling and linking. In the simplest
instance, you might do:
gcc -fbounds-checking program.c -o program
Or, linking several checked files together:
gcc -fbounds-checking -c file1.c -o file1.o gcc -fbounds-checking -c file2.c -o file2.o gcc -fbounds-checking -c file3.c -o file3.o gcc -fbounds-checking file1.o file2.o file3.o -o program
If your program uses a Makefile, you will probably only need to add the
-fbounds-checking
flag to CFLAGS
, and remake the program
from scratch.
Bounds checking is unfortunately incompatible with setjmp/longjmp. This is regrettable, but the problem is quite fundamental and it is unlikely that these functions will ever be permissable.
If possible, move signal handlers to a separate source file and set checking
off in that file. If this is not possible, then you will need to edit
bounds/lib/mutex.h
which provides mutual exclusion to vital
internal structures in the checking library. Normally this mutual exclusion
is turned off, for reasons of efficiency.
You may use threads with bounds checking. If more than one thread could
ever run with bounds checking, you will need to provide mutual exclusion
as with signal handlers above. Edit the file bounds/lib/mutex.h
and
add whatever code is necessary to give mutual exclusion.
Every so often, someone mails me to ask why their C++ program isn't checked
when they do g++ -fbounds-checking
. At the moment, the bounds checking
changes are specific to the C front end, so you can't use them with the other
GCC front ends (eg. C++, FORTRAN, Modula-2). There is no reason why bounds
checking couldn't be added to the C++ front end. It would take perhaps one or
two months to do. It is highly unlikely that I will ever do this, but if a
keen beta-tester wants it enough, I may be willing to help them. In the
meantime, it is possible to translate C++ programs to C and check them, but
line number and other debugging information may get scrambled in the process.
You can normally freely mix unchecked and checked code. This is why you don't
need to make any changes to your C or X11 libraries when you install GCC
with bounds checking. The checking library will detect code compiled with
and without checking automagically, and let the two run together. You
can mix unchecked object files with checked ones for the same reason. Always
pass the -fbounds-checking
flag to the link stage.
gcc -fbounds-checking -c file1.c -o file1.o gcc -c unchecked.c -o unchecked.o gcc -fbounds-checking file1.o unchecked.o -o program
The checking library will usually only know about variables that are
declared in checked code, and about memory allocated with malloc
. So
if a variable is declared in unchecked.c
above, then references
to it will not be checked, even when these references occur in
checked code.
Say that file unchecked.c
contains the following code:
int a[10]; int *get_ptr_to_a () { return a; }
and file file1.c
contains:
extern int *get_ptr_to_a (); main () { int *ptr_to_a = get_ptr_to_a (); int i; for (i = 0; i < 20; ++i) ptr_to_a[i] = 0; }
The references to ptr_to_a
will not be checked. You can resolve
this by adding a
, either by hand, or semi-automatically.
See Unchecked objects.
If you place extern int a[10];
anywhere in file1.c
, bounds
checking GCC will also be able to find and check the array references
properly.
If you include bounds/run-includes/unchecked.h
, you get facilities
to turn bounds checking on and off over short stretches of code and
within single expressions and statements. Even when bounds checking is
switched off, you may still use these features. The macros are silently
ignored if bounds checking is off, or if the compiler is not GCC.
BOUNDS_CHECKING_OFF
... BOUNDS_CHECKING_ON
Turn off bounds checking over a section of code. For instance:
/* This code is checked ... */ BOUNDS_CHECKING_OFF; /* This code is unchecked ... */ BOUNDS_CHECKING_ON; /* This code is checked again ... */The unchecked code should not try to return from a function, or jump over the
BOUNDS_CHECKING_ON
statement with goto
, else checking
will be switched off for the rest of the program!
BOUNDS_CHECKING_OFF_DURING
Switch off checking in a single statement. For instance:
BOUNDS_CHECKING_OFF_DURING (p += 5);The statement should not (obviously) be goto, return, ...
BOUNDS_CHECKING_OFF_IN_EXPR
Switch off checking while a single expression is being evaluated. For instance:
p = BOUNDS_CHECKING_OFF_IN_EXPR (a + 5);
If you have GDB (or another debugger) on your system, you will be able to
debug bounds checked programs easily and efficiently. To help you catch
bounds errors before the program aborts (which sometimes causes the
program's stack to disappear), place a breakpoint at
__bounds_breakpoint
. The checking library always calls this
breakpoint before aborting. If the -never-fatal
flag has been supplied
See Environment at runtime, you will need to place this
breakpoint, since the program does not abort when it hits a bounds error.
You can customize the way a bounds-checked program runs by passing options
to it in the environment variable `GCC_BOUNDS_OPTS'. For instance, suppose
you don't want the banner message that appears when bounds checked programs
start up. With sh or ksh, you might type:
% GCC_BOUNDS_OPTS='-no-message' program
With csh:
% setenv GCC_BOUNDS_OPTS '-no-message'; program
You can put any combination of the following flags in GCC_BOUNDS_OPTS. Place spaces or tabs between each flag.
-no-message
-no-statistics
-?, -help
-reuse-heap (*)
-reuse-age=<age>
-no-reuse-heap
-warn-unchecked-statics
-no-warn-unchecked-statics (*)
-warn-unchecked-stack
-no-warn-unchecked-stack (*)
-warn-free-null (*)
-no-warn-free-null
-warn-misc-strings (*)
-no-warn-misc-strings
memcpy
with size = 0.
-warn-illegal
-no-warn-illegal (*)
-warn-unaligned (*)
-no-warn-unaligned
-warn-all
-array-index-check (*)
-no-array-index-check
-never-fatal
-print-calls
-no-print-calls (*)
-print-heap
-print-heap-long
-no-print-heap (*)
-print-heap
shows the total leaked memory for each malloc
call,
while -print-heap-long
shows each leaked allocation in detail.
Items marked with a `(*)' are the default.
The bounds checking library includes a customized version of the GNU
malloc
library. Calls to malloc
, free
, realloc
,
calloc
, cfree
, valloc
and memalign
are
checked. You will get a bounds error if you try to:
realloc
.
There are several strategies for tracking stale memory pointers. Ideally,
we would like to never reuse VM after the programmer has freed it, so that
we will always be able to detect a stale pointer, no matter how long the
program runs before using it. If you wish this behaviour, then pass
the -no-reuse-heap
option in `GCC_BOUNDS_OPTS'
See Environment at runtime.2
In practice, we found this technique to be wasteful, so the default is to
reuse heap memory immediately. However, in order to provide some
protection against stale pointers, you may pass the -reuse-age=<age>
option to the library. This will add freed blocks to a queue of pending
blocks. You must call free
<age>
times before the block
is actually reused.
Notice that the most common error is:
free_list (list *p) { for (; p != NULL; p = p->next) free (p); }
The default flags, -reuse-heap -reuse-age=0
, will catch this error.
Variables declared in files that are not compiled with -fbounds-checking
are not normally known about by the checking library. Pointers that
point to these variables are not checked, even where the operations
on these pointers happen within checked code. To be sure that your
program is running without any errors, you should turn on warnings about
unchecked operations by giving the -warn-unchecked-statics
and/or
-warn-unchecked-stack
flags at runtime. See Environment at runtime.
To avoid these warnings, and check all operations, you should take steps to add these objects to the tree used by the checking library. There are three approaches:
extern
somewhere in checked code. Make sure that
the size of the object appears in the expression, ie. extern int a[10];
,
not extern int a[];
.
__bounds_note_constructed_object
.
This function is declared:
void __bounds_note_constructed_object (ptr, size, align, filename, line, name); void *ptr; /* Pointer to the object. */ size_t size; /* Size of the object (bytes). */ size_t align; /* Pass 1 here. */ char *filename; /* Filename where declared (for debugging). */ int line; /* Line number. */ char *name; /* Name of the object. */
grab-statics
tool in bounds/tools
. There is a README
file in that directory that will tell you more.
When GCC compiles a file with the -fbounds-checking
flag, it
defines __BOUNDS_CHECKING_ON
in the preprocessor. In addition, the
variable __bounds_checking_on
is set to 1 when bounds checking is
on in the program as a whole, and set to 0 when it is not. The variable
is actually located in libgcc.a
, so it is always present (unless
you aren't using GCC).
Notice the subtle difference between these two methods. Say a program
consists of source files file1.c
and file2.c
. If the
program is compiled with
gcc -fbounds-checking -c file1.c gcc -c file2.c gcc -fbounds-checking file1.o file2.o -o program
then file1.c
will be compiled with __BOUNDS_CHECKING_ON
defined. In file2.c
this will not be defined. Both files will
be able to declare extern int __bounds_checking_on;
and the
variable will be read as 1
by both.
If the same files are compiled without bounds checking, then
__BOUNDS_CHECKING_ON
will not be defined. Both files will be
able to declare extern int __bounds_checking_on;
and will read
the variable as 0
.
If the same files are compiled with another C compiler, then variable
__bounds_checking_on
will not exist. So all references to this
variable should be defended by #ifdef __GNUC__
...
#endif
.
2D arrays (and, indeed, n-D arrays with n >= 2) are checked as you might
expect. We consider such arrays to be flattened before checking. For instance
a mathematical 3x3-matrix A
might be defined as:
double A[3][3];
When -no-array-index-check
is active we consider such a array as
flattened.
Bounds checking will then consider this to be a flat array with 9 elements.
So, it is perfectly sound to write A[1][4]
, since 1*3+4
==
7
, and 0 <= 7 < 9. Similarly, A[0][8]
and A[2][-1]
will not generate bounds errors. (Interestingly, though, errors in the
first index will be caught -- this is to do with a subtlety in the
way bounds checking works).
When -array-index-check
is present every dimension of the array is
checked separatly. This is the default.
This flag is only used for arrays with size > 1. For arrays with size of 1
or 0 the flattened model is used. This allows the following code to work
correctly.
struct _string_t { int len; char str[1]; };
A lot of people tell me that they have Purify, and bounds checking GCC seems unnecessary, since it seems to duplicate Purify but more slowly. Well, there are important reasons why bounds checking GCC is better than Purify, and if you rely on Purify alone, you will certainly miss bugs in your program.
This is what bounds checking GCC will find, which Purify won't:
Try compiling:
main () { int a[10], b[100], i; for (i = 0; i < 100; ++i) a[i] = 0; }Purify will only detect these sorts of errors reliably if
a
is allocated
with malloc
.
malloc
Bugs such as the following one will not be found reliably by Purify, since
it only puts a certain amount of blank padding between malloc
'd
memory.
struct large_type { int data[5000]; }; main () { char *m1; struct large_type *m2; int i; m1 = (char *) malloc (20000); m2 = (struct large_type *) malloc (sizeof (struct large_type) * 5); for (i = 4; i >= -2; --i) /* note: error when i == -1 */ m2[i].data[0] = 0; }
This is what Purify will find, which bounds checking GCC won't:
Bounds checking GCC can't currently check this, but it may well be added
in a future version. GCC itself will pick up simple instances of this
if you pass the -Winitialized
flag (without -fbounds-checking
),
but cannot check use of malloc
'd memory.
There is a freeware program which emulates Purify available from Tristan Gingold <gingold@amoco.saclay.cea.fr>. It only runs under Linux. Purify only works on Sun SPARCstations and HP-PA machines, and, of course, costs lots of cash.
This page is under construction.
The very latest list of bugs can be found in bounds/BUGS
. This is a
list of some of the most stubborn bugs, some of which have been around since
the first version. Please send bug reports and (even better) bug fixes to
`rjones@orchestream.com' or `Haj.Ten.Brugge@net.HCC.nl'.
Bounds checking GCC usually inserts bytes of padding between adjacent stack objects. This dead area between objects helps the checking library to detect the difference between a pointer to the last byte + 1 of one object and a pointer to the first byte of the next object. For some reason, this padding is omitted occasionally when a 32-bit object (eg. int, pointer) follows an aggregate (eg. array). But not always.
The bug used to happen under Linux, but at some point in the past it seems to have fixed itself. The bug still appears under Solaris. You can demonstrate the bug by compiling Tcl/Tk on Solaris. The checking library will report at run time that a reference has been made to the byte following the end of the first object. When you look at the code, you will see that it is in fact referring to the next object (ie. the 32-bit integer).
Update (16/10/95): I fixed some stuff in assign_stack_local and assign_outer_stack_local (thanks to Don Lewis <gdonl@gv.ssi1.com>) but I haven't been able to verify that this bug has gone for sure.
Bounds checking patches break the current G77 patches. You can get round this
very easily. Copy cp/bounds.c
into the f/
subdirectory. Alter
f/Makefile.in
so that it compiles bounds.c
along with
the other G77 object files.
Notice that this doesn't add bounds checking to FORTRAN (:-<). Just lets you compile it.
See the file bounds/CONTRIBUTORS
for a full
list of the people who gave their effort for free to make all this possible.
In a future version of bounds checking GCC, we will be able to unmap this memory. Thus the operating system will be able to reuse physical memory, whilest virtual memory addresses remain unused.