Debugging a Program With dbx

Chapter 9 Using Runtime Checking


Note -

Access checking is available only on SPARC systems.


Runtime checking (RTC) enables you to automatically detect runtime errors in an application during the development phase. RTC lets you detect runtime errors such as memory access errors and memory leak errors and monitor memory usage.

The following topics are covered in this chapter:

Basic Concepts

Because RTC is an integral debugging feature, all debugging functions such as setting breakpoints and examining variables can be used with RTC, except the Collector.

The following list briefly describes the capabilities of RTC:

Compiling with the -g flag provides source line number correlation in the RTC error messages. RTC can also check programs compiled with the optimization -O flag. There are some special considerations with programs not compiled with the -g option.

For more detailed information on any aspect of RTC, see the online help.

When to Use RTC

One way to avoid seeing a large number of errors at once is to use RTC earlier in the development cycle, as you are developing the individual modules that make up your program. Write a unit test to drive each module and use RTC incrementally to check one module at a time. That way, you deal with a smaller number of errors at a time. When you integrate all of the modules into the full program, you are likely to encounter few new errors. When you reduce the number of errors to zero, you need to run RTC again only when you make changes to a module.

Requirements

To use RTC, you must fulfill the following requirements.

Limitations

RTC does not handle program text areas and data areas larger than 8 megabytes.

A possible solution is to insert special files in the executable image to handle program text areas and data areas larger than 8 megabytes.

Using RTC

To use runtime checking, enable the type of checking you want to use.

To turn on the desired checking mode, start dbx with the -C option:


% dbx [-C] program_name

The -C flag forces early loading of the RTC library. When you start dbx without the -C option and then enable checking, the RTC library is loaded when you issue the next run command; that may cause reloading of the shared libraries needed by the program. Using the -C flag initially allows you to avoid reloading.


Note -

You must turn on the type of checking you want before you run the program.


To turn on memory use and memory leak checking:


(dbx) check -memuse

To turn on memory access checking only:


(dbx) check -access

To turn on memory leak, memory use, and memory access checking:


(dbx) check -all

To turn off RTC entirely:


(dbx) uncheck -all

Run the program being tested, with or without breakpoints.

The program runs normally, but slowly because each memory access is checked for validity just before it occurs. If dbx detects invalid access, it displays the type and location of the error. Control returns to you (unless the dbxenv variable rtc_auto_continue is set to on). You can then issue dbx commands, such as where to get the current stack trace or print to examine variables. If the error is not a fatal error, you can continue execution of the program with the cont command. The program continues to the next error or breakpoint, whichever is detected first.

If rtc_auto_continue is set to on, RTC continues to find errors, and keeps running automatically. It redirects errors to the value of the dbxenv variable rtc_error_log_file_name.

You can limit the reporting of RTC errors using the suppress command. The program continues to the next error or breakpoint, whichever is detected first.

You can perform any of the usual debugging activities, such as setting breakpoints and examining variables. The cont command runs the program until another error or breakpoint is encountered or until the program terminates.

Below is a simple example showing how to turn on memory access and memory use checking for a program called hello.c.


% cat -n hello.c
     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4
     5 char *hello1, *hello2;
     6
     7 void
     8 memory_use()
     9 {
    10      hello1 = (char *)malloc(32);
    11      strcpy(hello1, "hello world");
    12      hello2 = (char *)malloc(strlen(hello1)+1);
    13      strcpy(hello2, hello1);
    14 }
    15
    16 void
    17 memory_leak()
    18 {
    19      char *local;
    20      local = (char *)malloc(32);
    21      strcpy(local, "hello world");
    22 }
    23
    24 void
    25 access_error()
    26 {
    27      int i,j;
    28
    29      i = j;
    30 }
    31
    32 int
    33 main()
    34 {
    35      memory_use();
    36      access_error();
    37      memory_leak();
    38      printf("%s\n", hello2);
    39      return 0;
    40 }


% cc -g -o hello hello.c
% dbx -C hello
Reading symbolic information for hello
Reading symbolic information for rtld /usr/lib/ld.so.1
Reading symbolic information for librtc.so
Reading symbolic information for libc.so.1
Reading symbolic information for libdl.so.1
(dbx) check -access   
access checking - ON
(dbx) check -memuse
memuse checking - ON
(dbx) run         
Running: hello 
(process id 18306)
Enabling Error Checking... done
Read from uninitialized (rui):
Attempting to read 4 bytes at address 0xeffff068
    which is 96 bytes above the current stack pointer
Variable is 'j'
Current function is access_error
   29       i = j;
(dbx) cont
hello world
Checking for memory leaks...
Actual leaks report    (actual leaks:         1 total size:      32 bytes)

 Total  Num of  Leaked      Allocation call stack
 Size   Blocks  Block
                Address
======  ====== ==========  =======================================
    32       1    0x21aa8  memory_leak < main 

Possible leaks report  (possible leaks:       0  total size:      0 bytes)

Checking for memory use...
Blocks in use report   (blocks in use:        2  total size:      44 bytes)

 Total  % of Num of  Avg     Allocation call stack
 Size    All Blocks  Size
======= ==== ====== ======  =======================================
     32  72%      1     32  memory_use < main 
     12  27%      1     12  memory_use < main 

execution completed, exit code is 0

The function access_error() reads variable j before it is initialized. RTC reports this access error as a Read from uninitialized (rui).

Function memory_leak() does not free() the variable local before it returns. When memory_leak() returns, this variable goes out of scope and the block allocated at line 20 becomes a leak.

The program uses global variables hello1 and hello2, which are in scope all the time. They both point to dynamically allocated memory, which is reported as Blocks in use (biu).

Using Access Checking (SPARC only)

RTC checks whether your program accesses memory correctly by monitoring each read, write, and memory free operation.

Programs may incorrectly read or write memory in a variety of ways; these are called memory access errors. For example, the program may reference a block of memory that has been deallocated through a free()call for a heap block, or because a function returned a pointer to a local variable. Access errors may result in wild pointers in the program and can cause incorrect program behavior, including wrong outputs and segmentation violations. Some kinds of memory access errors can be very hard to track down.

RTC maintains a table that tracks the state of each block of memory being used by the program. RTC checks each memory operation against the state of the block of memory it involves and then determines whether the operation is valid. The possible memory states are:

Using RTC to find memory access errors is not unlike using a compiler to find syntax errors in your program. In both cases a list of errors is produced, with each error message giving the cause of the error and the location in the program where the error occurred. In both cases, you should fix the errors in your program starting at the top of the error list and working your way down. One error can cause other errors in a sort of chain reaction. The first error in the chain is therefore the "first cause," and fixing that error may also fix some subsequent errors. For example, a read from an uninitialized section of memory can create an incorrect pointer, which when dereferenced can cause another invalid read or write, which can in turn lead to yet another error.

Understanding the Memory Access Error Report

RTC prints the following information for memory access errors:

type 

Type of error. 

access 

Type of access attempted (read or write). 

size 

Size of attempted access. 

addr 

Address of attempted access. 

detail 

More detailed information about addr. For example, if addr is in the vicinity of the stack, then its position relative to the current stack pointer is given. If addr is in the heap, then the address, size, and relative position of the nearest heap block is given. 

stack 

Call stack at time of error (with batch mode). 

allocation 

If addr is in the heap, then the allocation trace of the nearest heap block is given.

location 

Where the error occurred. If line number information is available, this information includes line number and function. If line numbers are not available, RTC provides function and address.

The following example shows a typical access error:


Read from uninitialized (rui):
Attempting to read 4 bytes at address 0xefffee50
	which is 96 bytes above the current stack pointer
Variable is `j'
Current function is rui
   12           i = j;





Memory Access Errors

RTC detects the following memory access errors:

For a full explanation of each error and an example, see "RTC Errors".


Note -

RTC does not do array bound checking, and therefore does not report array bound violations as access errors.


Using Memory Leak Checking

A memory leak is a dynamically allocated block of memory that has no pointers pointing to it anywhere in the data space of the program. Such blocks are orphaned memory. Because there are no pointers pointing to the blocks, programs cannot even reference them, much less free them. RTC finds and reports such blocks.

Memory leaks result in increased virtual memory consumption and generally result in memory fragmentation. This may slow down the performance of your program and the whole system.

Typically, memory leaks occur because allocated memory is not freed and you lose a pointer to the allocated block. Here are some examples of memory leaks:


void
foo()
{
    char *s;
    s = (char *) malloc(32);
 
    strcpy(s, "hello world");
 
    return; /* no free of s. Once foo returns, there is no     */
            /* pointer pointing to the malloc'ed block,         */
            /* so that block is leaked.                         */
}

A leak can result from incorrect use of an API:


void
printcwd()
{

    printf("cwd = %s\n", getcwd(NULL, MAXPATHLEN));

    return; /* libc function getcwd() returns a pointer to     */
            /* malloc'ed area when the first argument is NULL, */
            /* program should remember to free this. In this   */
            /* case the block is not freed and results in leak.*/
}

Memory leaks can be avoided by following a good programming practice of always freeing memory when it is no longer needed and paying close attention to library functions that return allocated memory. If you use such functions, remember to free up the memory appropriately.

Sometimes, the term memory leak is used to refer to any block that has not been freed. This is a much less useful definition of a memory leak, because it is a common programming practice not to free memory if the program will terminate shortly anyway. RTC does not report a block as a leak if the program still retains one or more pointers to it.

Detecting Memory Leak Errors


Note -

RTC only finds leaks of malloc memory. If your program does not use malloc, RTC cannot find memory leaks.


RTC detects the following memory leak errors:

For a full explanation of each error and an example, see "RTC Errors".

Possible Leaks

There are two cases where RTC may report a "possible" leak. The first case is when no pointers were found pointing to the beginning of the block, but a pointer was found pointing to the interior of the block. This case is reported as an "Address in Block (aib)" error. If it was a stray pointer that happened to point into the block, this would be a real memory leak. However, some programs deliberately move the only pointer to an array back and forth as needed to access its entries. In this case it would not be a memory leak. Because RTC cannot distinguish these two cases, it reports them as possible leaks, allowing the user to make the determination.

The second type of possible leak occurs when no pointers to a block were found in the data space, but a pointer was found in a register. This case is reported as an "Address in Register (air)" error. If the register happens to point to the block accidentally, or if it is an old copy of a memory pointer that has since been lost, then this is a real leak. However, the compiler can optimize references and place the only pointer to a block in a register without ever writing the pointer to memory. In such cases, this would not be a real leak. Hence, if the program has been optimized and the report was the result of the showleaks command, it is likely not to be a real leak. In all other cases, it is likely to be a real leak.


Note -

RTC leak checking requires use of the standard libc malloc/free/realloc functions or allocators based on those functions. For other allocators, see "Runtime Checking Application Programming Interface".


Checking for Leaks

If memory leak checking is turned on, a scan for memory leaks is automatically performed just before the program being tested exits. Any detected leaks are reported. The program should not be killed with the kill command. Here is a typical memory leak error message:


Memory leak (mel):
Found leaked block of size 6 at address 0x21718
At time of allocation, the call stack was:
    [1] foo() at line 63 in test.c
    [2] main() at line 47 in test.c

Clicking on the call stack location hypertext link takes you to that line of the source code in the editor window.

UNIX programs have a main procedure (called MAIN in f77) that is the top-level user function for the program. Normally, a program terminates either by calling exit(3) or by simply returning from main. In the latter case, all variables local to main go out of scope after the return, and any heap blocks they pointed to are reported as leaks (unless globals point to those same blocks).

It is a common programming practice not to free heap blocks allocated to local variables in main, because the program is about to terminate, and return from main without calling (exit()). To prevent RTC from reporting such blocks as memory leaks, stop the program just before main returns by setting a breakpoint on the last executable source line in main. When the program halts there, use the RTC showleaks command to report all the true leaks, omitting the leaks that would result merely from variables in main going out of scope.

Understanding the Memory Leak Report

With leak checking turned on, you get an automatic leak report when the program exits. All possible leaks are reported--provided the program has not been killed using the kill command. By default, a non-verbose leak report is generated, which is controlled by the dbxenv variable rtc_mel_at_exit.

Reports are sorted according to the combined size of the leaks. Actual memory leaks are reported first, followed by possible leaks. The verbose report contains detailed stack trace information, including line numbers and source files whenever they are available.

Both reports include the following information for memory leak errors:

location 

Location where leaked block was allocated 

addr 

Address of leaked block 

size 

Size of leaked block 

stack 

Call stack at time of allocation, as constrained by check -frames

The non-verbose report capsulizes the error information into a table, while the verbose report gives you a separate error message for each error. They both contain a hypertext link to the location of the error in the source code.

Here is the corresponding non-verbose memory leak report:


Actual leaks report    (actual leaks:    3 total size:    2427 bytes)

 Total  Num of  Leaked      Allocation call stack
 Size   Blocks  Block
                Address
======  ====== ==========  =======================================
  1852       2      -      true_leak < true_leak 
   575       1    0x22150  true_leak < main 

Possible leaks report  (possible leaks:  1  total size:       8 bytes)

 Total  Num of  Leaked      Allocation call stack
 Size   Blocks  Block
                Address
======  ====== ==========  =======================================
     8       1    0x219b0  in_block < main 

Following is a typical verbose leak report:


Actual leaks report    (actual leaks:         3  total size:    2427 bytes)

Memory Leak (mel):
Found 2 leaked blocks with total size 1852 bytes
At time of each allocation, the call stack was:
	[1] true_leak() at line 220 in "leaks.c"
	[2] true_leak() at line 224 in "leaks.c"

Memory Leak (mel):
Found leaked block of size 575 bytes at address 0x22150
At time of allocation, the call stack was:
	[1] true_leak() at line 220 in "leaks.c"
	[2] main() at line 87 in "leaks.c"

Possible leaks report  (possible leaks:       1  total size:       8 bytes)

Possible memory leak -- address in block (aib):
Found leaked block of size 8 bytes at address 0x219b0
At time of allocation, the call stack was:
	[1] in_block() at line 177 in "leaks.c"
	[2] main() at line 100 in "leaks.c"

Generating a Leak Report

You can ask for a leak report at any time using the showleaks command, which reports new memory leaks since the last showleaks command.

Combining Leaks

Because the number of individual leaks can be very large, RTC automatically combines leaks allocated at the same place into a single combined leak report. The decision to combine leaks, or report them individually, is controlled by the number-of-frames-to-match parameter specified by the -match m option on a check -leaks or the -m option of the showleaks command. If the call stack at the time of allocation for two or more leaks matches to m frames to the exact program counter level, these leaks are reported in a single combined leak report.

Consider the following three call sequences:

Block 1 

Block 2 

Block 3 

[1] malloc

[1] malloc

[1] malloc

[2] d() at 0x20000

[2] d() at 0x20000

[2] d() at 0x20000

[3] c() at 0x30000

[3] c() at 0x30000

[3] c() at 0x31000

[4] b() at 0x40000

[4] b() at 0x41000

[4] b() at 0x40000

[5] a() at 0x50000

[5] a() at 0x50000

[5] a() at 0x50000

If all of these blocks lead to memory leaks, the value of m determines whether the leaks are reported as separate leaks or as one repeated leak. If m is 2, Blocks 1 and 2 are reported as one repeated leak because the 2 stack frames above malloc() are common to both call sequences. Block 3 will be reported as a separate leak because the trace for c() does not match the other blocks. For m greater than 2, RTC reports all leaks as separate leaks. (The malloc is not shown on the leak report.)

In general, the smaller the value of m, the fewer individual leak reports and the more combined leak reports are generated. The greater the value of m, the fewer combined leak reports and the more individual leak reports are generated.

Fixing Memory Leaks

Once you have obtained a memory leak report, there are some general guidelines for fixing the memory leaks. The most important thing is to determine where the leak is. The leak report tells you the allocation trace of the leaked block, the place where the leaked block was allocated. You can then look at the execution flow of your program and see how the block was used. If it is obvious where the pointer was lost, the job is easy; otherwise you can use showleaks to narrow your leak window. showleaks by default gives you only the new leaks created since the last showleaks command. You can run showleaks repeatedly to narrow the window where the block was leaked.

Using Memory Use Checking

RTC lets you see all the heap memory in use. You can use this information to get a sense of where memory gets allocated in your program or which program sections are using the most dynamic memory. This information can also be useful in reducing the dynamic memory consumption of your program and may help in performance tuning

RTC is useful during performance tuning or to control virtual memory use. When the program exits, a memory use report can be generated. Memory usage information can also be obtained at any time during program execution with the showmemuse command, which causes memory usage to be displayed.

Turning on memory use checking also turns on leak checking. In addition to a leak report at the program exit, you also get a blocks in use (biu) report. By default, a non-verbose blocks in use report is generated at program exit, which is controlled by the dbxenv variable rtc_biu_at_exit.

The following is a typical non-verbose memory use report:


Blocks in use report   (blocks in use: 5   total size:   40 bytes)

 Total  % of Num of  Avg     Allocation call stack
 Size    All Blocks  Size
======= ==== ====== ======  =====================================
     16  40%      2      8  nonleak < nonleak 
      8  20%      1      8  nonleak < main 
      8  20%      1      8  cyclic_leaks < main 
      8  20%      1      8  cyclic_leaks < main 


Blocks in use report   (blocks in use: 5   total size:   40 bytes)

Block in use (biu):
Found 2 blocks totaling 16 bytes (40.00% of total; avg block size 8)
At time of each allocation, the call stack was:
     [1] nonleak() at line 182 in "memuse.c"
     [2] nonleak() at line 185 in "memuse.c"

Block in use (biu):
Found block of size 8 bytes at address 0x21898 (20.00% of total)
At time of allocation, the call stack was:
     [1] nonleak() at line 182 in "memuse.c"
     [2] main() at line 74 in "memuse.c"

Block in use (biu):
Found block of size 8 bytes at address 0x21958 (20.00% of total)
At time of allocation, the call stack was:
     [1] cyclic_leaks() at line 154 in "memuse.c"
     [2] main() at line 118 in "memuse.c"

Block in use (biu):
Found block of size 8 bytes at address 0x21978 (20.00% of total)
At time of allocation, the call stack was:
     [1] cyclic_leaks() at line 155 in "memuse.c"
     [2] main() at line 118 in "memuse.c"

The following is the corresponding verbose memory use report:

You can ask for a memory use report any time with the showmemuse command.

Suppressing Errors

RTC provides a powerful error suppression facility that allows great flexibility in limiting the number and types of errors reported. If an error occurs that you have suppressed, then no report is given, and the program continues as if no error had occurred.

Error suppression is done using the suppress command. Suppression can be undone using the unsuppress command. Suppression is persistent across run commands within the same debug session, but not across debug commands.

The following kinds of suppression are available:

In the following examples, main.cc is a file name, foo and bar are functions and a.out is the name of an executable.


suppress mel in foo

Do not report memory leaks whose allocation occurs in function foo:

Suppress reporting blocks in use allocated from libc.so.1

 suppress biu in libc.so.1

:

 suppress rui in a.out

Suppress read from uninitialized in a.out:

 suppress rua in main.cc

Do not report read from unallocated in file main.cc:


suppress duf at main.cc:10

Suppress duplicate free at line 10 of main.cc:

Suppress reporting of all errors in function


suppress all in bar

bar:

Default Suppressions

To detect all errors RTC does not require the program be compiled using the -g option (symbolic). However, symbolic information is sometimes needed to guarantee the correctness of certain errors, mostly rui. For this reason certain errors, rui for a.out and rui, aib, and air for shared libraries, are suppressed by default if no symbolic information is available. This behavior can be changed by using the -d option of the suppress and unsuppress commands.

The following command causes RTC to no longer suppress read from uninitialized memory (rui) in code that does not have symbolic information (compiled without -g):


unsuppress -d rui

Using Suppression to Manage Errors

For the initial run on a large program, the number of errors may be so large as to be overwhelming. In this case, it may be better to take a phased approach. This can be done using the suppress command to reduce the reported errors to a manageable number, fixing just those errors, and repeating the cycle; suppressing fewer and fewer errors with each iteration.

For example, you could focus on a few error types at one time. The most common error types typically encountered are rui, rua, and wua, usually in that order. rui errors are less serious errors (although they can cause more serious errors to happen later), and often a program may still work correctly with these errors. rua and wua errors are more serious because they are accesses to or from invalid memory addresses, and always indicate a coding error of some sort.

You could start by suppressing rui and rua errors. After fixing all the wua errors that occur, run the program again, this time suppressing only rui errors. After fixing all the rua errors that occur, run the program again, this time with no errors suppressed. Fix all the rui errors. Lastly, run the program a final time to ensure there are no errors left.

If you want to suppress the last reported error, use suppress -last. You also can limit the number of errors reported without using the suppress command by using dbxenv variable rtc_error_limit n instead.

Using RTC on a Child Process

dbx supports Runtime Checking of a child process if RTC is enabled for the parent and the dbxenv variable follow_fork_mode is set to child. When a fork happens, dbx automatically performs RTC on the child. If the program does an exec(), the RTC settings of the program calling exec() are passed on to the program.

At any given time, just one process can be under RTC control. Following is an example:


% cat -n program1.c
     1 #include <sys/types.h>
     2 #include <unistd.h>
     3 #include <stdio.h>
     4
     5 int
     6 main()
     7 {
     8      pid_t child_pid;
     9      int parent_i, parent_j;
    10
    11      parent_i = parent_j;
    12
    13      child_pid = fork();
    14
    15      if (child_pid == -1) {
    16          printf("parent: Fork failed\n");
    17          return 1;
    18      } else if (child_pid == 0) {
    19          int child_i, child_j;
    20
    21          printf("child: In child\n");
    22          child_i = child_j;
    23          if (execl("./program2", NULL) == -1) {
    24              printf("child: exec of program2 failed\n");
    25              exit(1);
    26          }
    27      } else {
    28          printf("parent: child's pid = %d\n", child_pid);
    29      }
    30      return 0;
    31 }
%


 % cat -n program2.c
     1
     2 #include <stdio.h>
     3
     4 main()
     5 {
     6      int program2_i, program2_j;
     7
     8      printf ("program2: pid = %d\n", getpid());
     9      program2_i = program2_j;
    10
    11      malloc(8);
    12
    13      return 0;
    14 }
% 















RTC reports first error in the parent, program1
 % cc -g -o program1 program1.c
 % cc -g -o program2 program2.c
 % dbx -C program1
 Reading symbolic information for program1
 Reading symbolic information for rtld /usr/lib/ld.so.1
 Reading symbolic information for librtc.so
 Reading symbolic information for libc.so.1
 Reading symbolic information for libdl.so.1
 Reading symbolic information for libc_psr.so.1
 (dbx) check -all 
 access checking - ON
 memuse checking - ON 
 (dbx) dbxenv follow_fork_mode child
 (dbx) run  
 Running: program1 
 (process id 3885)
 Enabling Error Checking... done
 Read from uninitialized (rui):
 Attempting to read 4 bytes at address 0xeffff110
     which is 104 bytes above the current stack pointer
 Variable is 'parent_j'
 Current function is main
   11       parent_i = parent_j;


Because  follow_fork_mode is set to child, when the fork occurs error checking is switched from the parent to the child process




RTC reports an error in the child

 




When the exec of program2 occurs, the RTC settings are inherited by 
program2 so access and memory use checking are enabled for that process






RTC reports an access error in the executed program, program2 
 (dbx) cont
 dbx: warning: Fork occurred; error checking disabled in parent
 detaching from process 3885
 Attached to process 3886
 stopped in _fork at 0xef6b6040
 0xef6b6040: _fork+0x0008:	bgeu    _fork+0x30
 Current function is main
    13       child_pid = fork();
 parent: child's pid = 3886
 (dbx) cont
 child: In child
 Read from uninitialized (rui):
 Attempting to read 4 bytes at address 0xeffff108
     which is 96 bytes above the current stack pointer
 Variable is 'child_j'
 Current function is main
    22   	child_i = child_j;
 (dbx) cont
 dbx: process 3886 about to exec("./program2")
 dbx: program "./program2" just exec'ed
 dbx: to go back to the original program use "debug $oprog"
 Reading symbolic information for program2
 Skipping ld.so.1, already read
 Skipping librtc.so, already read
 Skipping libc.so.1, already read
 Skipping libdl.so.1, already read
 Skipping libc_psr.so.1, already read
 Enabling Error Checking... done
 stopped in main at line 8 in file "program2.c"
     8       printf ("program2: pid = %d\n", getpid());
(dbx) cont
 program2: pid = 3886
 Read from uninitialized (rui):
 Attempting to read 4 bytes at address 0xeffff13c
     which is 100 bytes above the current stack pointer
 Variable is 'program2_j'
 Current function is main
     9       program2_i = program2_j;
 (dbx) cont
 Checking for memory leaks...


RTC prints a memory use and  memory leak report for the process that exited while under RTC control, 
program2
 Actual leaks report   (actual leaks:      1  total size:   8   
 bytes)

  Total  Num of  Leaked      Allocation call stack
  Size   Blocks  Block
                Address
 ======  ====== ========== ====================================
      8       1    0x20c50  main 

 Possible leaks report  (possible leaks:   0  total size:   0 
 bytes)

 execution completed, exit code is 0

Using RTC on an Attached Process

RTC works with attached processes as well. However, to use RTC on an attached process, that process must be started with librtc.so preloaded. librtc.so resides in the lib directory of the product (../lib from the path of dbx; if the product is installed in /opt, it is /opt/SUNWspro/lib/librtc.so).

To preload librtc.so:


% setenv LD_PRELOAD path-to-librtc/librtc.so

It is a good idea to set to preload librtc only when needed (as with attach); do not have it on all the time. For example:


% setenv LD_PRELOAD...
% start-your-application
% unsetenv LD_PRELOAD

Once you attach to the process, you can enable RTC.

In case the program you want to attach to gets forked or executed from some other program, you need to set LD_PRELOAD for the main program (which will fork). The setting of LD_PRELOAD is inherited across fork/exec.

Using Fix and Continue With RTC

You can use RTC along with Fix and Continue to rapidly isolate and fix programming errors. Fix and Continue provides a powerful combination that can save you a lot of debugging time. Here is an example:


% cat -n bug.c
     1 #include stdio.h
     2 char *s = NULL;
     3   
     4 void
     5 problem()
     6 {
     7      *s = 'c';
     8 }
     9   
    10 main()
    11 {
    12      problem();
    13      return 0;
    14 }
% cat -n bug-fixed.c
     1 #include stdio.h
     2 char *s = NULL;
     3   
     4 void
     5 problem()
     6 {
     7   
     8      s = (char *)malloc(1);
     9      *s = 'c';
    10 }
	11
    12 main()
    13 {
    14      problem();
    15      return 0;
    16 }


yourmachine46: cc -g bug.c
yourmachine47: dbx -C a.out
Reading symbolic information for a.out
Reading symbolic information for rtld /usr/lib/ld.so.1
Reading symbolic information for librtc.so
Reading symbolic information for libc.so.1
Reading symbolic information for libintl.so.1
Reading symbolic information for libdl.so.1
Reading symbolic information for libw.so.1
(dbx) check -access
access checking - ON
(dbx) run
Running: a.out
(process id 15052)
Enabling Error Checking... done
Write to unallocated (wua):
Attempting to write 1 byte through NULL pointer
Current function is problem
    7       *s = 'c';
(dbx) pop
stopped in main at line 12 in file "bug.c"
   12       problem();
(dbx) #at this time we would edit the file; in this example just copy the correct version
(dbx) cp bug-fixed.c bug.c
(dbx) fix
fixing "bug.c" ......
pc moved to "bug.c":14
stopped in main at line 14 in file "bug.c"
   14       problem();
(dbx) cont
 
execution completed, exit code is 0
(dbx) quit
The following modules in `a.out' have been changed (fixed):
bug.c
Remember to remake program.

Runtime Checking Application Programming Interface

Both leak detection and access checking require that the standard heap management routines in the shared library libc.so be used. This is so that RTC can keep track of all the allocations and deallocations in the program. Many applications write their own memory management routines either on top of malloc-free or from scratch. When you use your own allocators (referred to as private allocators), RTC cannot automatically track them, thus you do not learn of leak and memory access errors resulting from their improper use.

However, RTC provides an API for the use of private allocators. This API allows the private allocators to get the same treatment as the standard heap allocators. The API itself is provided in a header file rtc_api.h and is distributed as a part of WorkShop. The man page rtc_api(3x) details the RTC API entry points.

Some minor differences may exist with RTC access error reporting when private allocators do not use the program heap. The error report will not include the allocation item.

Using RTC in Batch Mode

bcheck(1) is a convenient batch interface to the RTC feature of dbx. It runs a program under dbx and by default places the RTC error output in the default file program.errs.

bcheck can perform memory leak checking, memory access checking, memory use checking, or all three. Its default action is to perform only leak checking. Refer to the bcheck (1) man page for more details on its use.

The syntax for bcheck is:


bcheck [-access | -all | -leaks | -memuse] [-o logfile
] [-q] 
[-s script] program
 [args]

Use the -o logfile option to specify a different name for the logfile. Use the -s script option before executing the program to read in the dbx commands contained in the file script. The script file typically contains commands like suppress and dbxenv to tailor the error output of bcheck.

The -q option makes bcheck completely quiet, returning with the same status as the program. This is useful when you want to use bcheck in scripts or makefiles.

Perform only leak checking on hello:


bcheck hello

Perform only access checking on mach with the argument 5:


bcheck -access mach 5

Perform memory use checking on cc quietly and exit with normal exit status:


bcheck -memuse -q cc -c prog.c

The program does not stop when runtime errors are detected in batch mode. All error output is redirected to your error log file logfile. But the program stops when breakpoints are encountered or if the program is interrupted.

In batch mode, the complete stack backtrace is generated and redirected to the error log file. The number of stack frames can be controlled using the dbxenv variable stack_max_size.

If the file logfile already exists, bcheck erases the contents of that file before it redirects the batch output to it.

You can also enable a batch-like mode directly from dbx by setting the following dbxenv variables:


(dbx) dbxenv rtc_auto_continue on
(dbx) dbxenv rtc_error_log_file_name logfile

With these settings, the program does not stop when runtime errors are detected, and all error output is redirected to your error log file.

Troubleshooting Tips

After error checking has been enabled for a program and the program is run, one of the following errors may be detected:


librtc.so and dbx version mismatch; Error checking disabled

This may occur if you are using RTC on an attached process and have set LD_PRELOAD to a version of librtc.so other than the one shipped with your Sun WorkShop dbx image. To fix this, change the setting of LD_PRELOAD.


patch area too far (8mb limitation); Access checking disabled

RTC was unable to find patch space close enough to a load object for access checking to be enabled. See "rtc_patch_area".

RTC's Eight Megabyte Limit

When access checking, dbx replaces each load and store instruction with a branch instruction that branches to a patch area. This branch instruction has an eight megabyte range. This means that if the debugged program has used up all the address space within eight megabytes of the particular load/store instruction being replaced, there is no place to put the patch area.

If RTC can't intercept all loads and stores to memory, it cannot provide accurate information and so disables access checking completely. Leak checking is unaffected.

dbx internally applies some strategies when it runs into this limitation and continues if it can rectify this problem. In some cases dbx cannot proceed; when this happens it turns off access checking after printing an error message.

Working Around the Eight Megabyte Limit


Note -

On V9, you can work around the eight megabyte limit by using the setenv command to set the USE_FASTTRAPS environment variable to 1. This workaround makes dbx run more slowly and use more memory.


dbx provides some possible workarounds to users who have run into this limit. These workarounds require the use of a utility called rtc_patch_area which is included with Sun WorkShop.

This utility creates object files or shared object files that can be linked into your program to create patch areas for RTC to use.

There are two situations that can prevent dbx from finding patch areas within 8 megabytes of all the loads and stores in an executable image:

Case 1: The statically linked a.out file is too large.

Case 2: One or more dynamically linked shared libraries is too large.

When dbx runs into this limitation, it prints a message telling how much patch space it needs and directs you to the appropriate case (Case 1 or Case 2).

Case 1

In Case 1, you can use rtc_patch_area to make one or more object files to serve as patch areas and link them into the a.out.After you have seen a message like the following:


Enabling Error Checking... dbx: warning: rtc: cannot find patch space within 8Mb (need 6490432 bytes for ./a.out)
dbx: patch area too far (8Mb limitation); Access checking disabled
         (See `help rtc8M', case 1)

  1. Create an object file patch.o for a patch area with a size less than or equal to 8 megabytes:


    rtc_patch_area -o patch.o -size 6490432
    

    The -size flag is optional; the default value is 8000000.

  2. If the size request from the error message is satisfied, continue to the next step. Otherwise, repeat step 1 and create more .o files as needed.

  3. Relink the a.out, adding the patch.o files to the link line.

  4. Try RTC again with the new binary.

    If RTC still fails, you may try to reposition the patch.o files on the link line.

An alternate workaround is to divide the a.out into a smaller a.out and shared libraries.

Case 2

If you are running RTC on an attached process, see case 2a. Otherwise, the only workaround is to rebuild the shared library with extra patch space:

After you have seen a message like the following::


Enabling Error Checking... dbx: warning: rtc: cannot find patch space within 8Mb (need 563332 bytes for ./sh1.so)
dbx: patch area too far (8Mb limitation); Access checking disabled
         (See `help rtc8M', case 2)

  1. Create an object file patch.o for a patch area with a size less than or equal to 8 megabytes:


    rtc_patch_area -o patch.o -size 563332
    

    The -size flag is optional; the default value is 8000000.

  2. If the size request from the error message is satisfied, continue to the next step. Otherwise, repeat step 1 and create more .o files as needed.

  3. Relink sh1.so, adding the patch.o files to the link line.

  4. Try RTC again with the new binary; if dbx requests patch space for another shared library, repeat steps 1-3 for that library.

Case 2a

In Case 2a, if the shared library patch space requested by dbx is eight megabytes or less, you can use rtc_patch_area to make a shared library to serve as a patch area and link it into the a.out.

After you have seen a message like the following:


Enabling Error Checking... dbx: warning: rtc: cannot find patch space within 8Mb (need 563332 bytes for ./sh1.so)
dbx: patch area too far (8Mb limitation); Access checking disabled
         (See `help rtc8M', case 2)

  1. Create a shared object patch1.so for a patch area:


    rtc_patch_area -so patch1.so -size 563332
    

    The -size flag is optional; the default value is 8000000.

  2. 2) Relink the program with patch1.so placed adjacent to sh1.so in the link line. If dbx still requests patch space for the same sh1.so, then try placing patch1.so immediately before sh1.so.

    It may be necessary to use full pathnames instead of the ld -l option to get the desired shared library ordering.

  3. Try RTC again with the new binary; if dbx requests patch space for another shared library, repeat steps 1-2 for that library.

    If the patch space requested by dbx is more than eight megabytes for a given shared library, follow the steps in case 2 above.

rtc_patch_area

rtc_patch_area is a shell script that creates object files or shared library files that can be linked into the user's program to add patch area space to programs with large text, data, or bss images.

The object file (or shared library) created contains one RTC patch area of the specified size or 8000000 if size is not supplied.

The name of the resulting object file (or shared library) is written to the standard output. Either the -o or -so option must be used.

Specify the name of the shared library to be created. This name is then written to the standard output:


-so sharedlibname

Specify the name of the object file to be created. This name is then written to the standard output:


-o objectname

Create a patch area of size bytes (default and reasonable maximum is 8000000):


-size size

Use compiler instead of cc to build the object file:


-cc compiler

Examples

Generate a standard eight megabyte patch area object file:


rtc_patch_area -o patch.o

Generate an object file containing a 100,000 byte patch:


rtc_patch_area -size 100000 -o patch.o

Generate a one megabyte patch area shared library:


rtc_patch_area -so rtc1M.so -size 1000000

Command Reference

check|uncheck

All forms of the check and uncheck commands are described below.

The check command prints the current status of RTC


check

:

To turn on access checking:


check -access

To turn on leak checking:


check -leaks [-frames n
] [-match m]

-frames n

Up to n distinct stack frames are displayed when showing the allocation trace of the leaked block.

-match m

Used for combining leaks; if the call stack at the time of allocation for two or more leaks matches m frames, then these leaks are reported in a single combined leak report.

The default value of n is 8 or the value of m (whichever is larger), with a maximum value of 16. The default value of m is 2.

The command check -memuse implies check -leaks. In addition to a leak report at the program exit, you also get a memory use report. At any time during program execution, you can see where all the memory in the program has been allocated.


check -memuse [-frames n
] [-match m]

To turn on memory use checking:

-frames n

Up to n distinct stack frames are listed when showing the allocation trace of the block in use.

-match m

There may be many blocks in use in the program, so RTC automatically combines the blocks allocated from the same execution trace into one report. The decision to combine the report is controlled by value of m. If the call stack at the allocation of two or more blocks matches m frames, then these blocks are reported in a combined block in use report. The way that blocks are combined is similar to the method used in a leak report.

The default value of n is 8 or the value of m (whichever is larger), with a maximum value of 16. The default value of m is 2.

These two commands are equivalent:


check -all [-frames n
] [-match m]
check -access ; check -memuse [-frames n
] [-match m]

To turn off access checking:


uncheck -access

To turn off leak checking:


uncheck -leaks

To turn off memory use checking (leak checking is also turned off):


uncheck -memuse

The following two commands are equivalent:


uncheck -all
uncheck -access; uncheck -memuse

These two commands are equivalent:


uncheck [funcs]
[files] [loadobjects
]
suppress all in funcs
 files loadobjects

This command allows you to turn on checking in specific functions, modules, and load objects while leaving it turned off for the rest of the program:


check function* file* loadobject*

This command is equivalent to:


suppress all
unsuppress all in function
* file* loadobject
*

The command operates cumulatively. For example, the first three commands are equivalent to the last four commands:


check main
check foo
check f.c

suppress all
unsuppress all in main
unsuppress all in foo
unsuppress all in f.c

Notice that the suppress all command is only applied once, leaving checking turned on for main, foo, and f.c.

These commands are also equivalent:


uncheck function*
 file* loadobject
suppress all in function* file* loadobject*

showblock


showblock -a addr

When memory use checking or memory leak checking is turned on, showblock shows the details about the heap block at address addr. The details include the location of the block's allocation and its size.

showleaks

To report new memory leaks since the last showleaks command:


showleaks [-a] [-m m
] [-n num]
[-v]

In the default non-verbose case, a one line report per leak record is printed. Actual leaks are reported followed by the possible leaks. Reports are sorted according to the combined size of the leaks.

-a

Shows all leaks generated so far (not just the leaks since the last showleaks command).

-m m

Used for combining leaks; if the call stack at the time of allocation for two or more leaks matches m frames, then the leaks are reported in a single combined leak report. The -m option overrides the global value of m specified with the check command. The default value of m is 2 or the global value last given with check.

-n num

Shows up to num records in the report. Default is to show all records.

-v

Generates verbose output. Default is to show non-verbose output. 

showmemuse

This command generates a report showing all blocks of memory in use:


showmemuse [-a] [-m m
] [-n num]
[-v]

Report the top num blocks-in-use records sorted by size. Only blocks that are new since the last showmemuse command are shown.

-a

Shows all blocks in use so far. 

-m m

Used for combining the blocks-in-use reports; if the call stack at the time of allocation for two or more blocks matches m frames, then the blocks are reported in a single combined report. The -m option overrides the global value of m specified with the check command. The default value of m is 2 or the global value last given with check.

-n num

Shows up to num records in the report. Default is to show 20.

-v

Generates verbose output. Default is to show non-verbose output. 

When memory use checking is on, at program exit an implicit showmemuse -a -n 20 is performed. You can get a verbose output at the program exit time by using the rtc_biu_at_exit dbxenv variable.

suppress|unsuppress

Some or all files in a load object may not be compiled with the -g switch. This implies that there is no debugging information available for functions that belong in these files. RTC uses some default suppression in these cases.

To get a list of these defaults:


{suppress | unsuppress} -d

To change the defaults for one load object:


{suppress | unsuppress} -d [error type
] [in loadobject]

To change the defaults for all load objects:


{suppress | unsuppress} -d [error type
]

To reset these defaults to the original settings:


suppress -reset

Use the following command to suppress or unsuppress the most recent error. The command applies only to access errors and not to leak errors:


{suppress | unsuppress} -last

To display the history of the suppress commands not including the -d and -reset commands:


{suppress | unsuppress}

Turn error reports on or off for the specified error types for the specified location:


{suppress | unsuppress} [error type
... [location_specifier
]]

To remove the suppress or unsuppress events as given by the id(s):


suppress -r id...

To remove all the suppress and unsuppress events as given by suppress:


suppress -r [0 | all | -all]

Error Type Location Specifier

The following are the error type location specifiers.

in loadobject

All functions in the designated program or library 

in file

All functions in file 

in function

Named function 

at line specifier

At source line 

addr address

At hex address 

To see a list of the load objects, type loadobjects at the dbx prompt. If the line specifier is blank, the command applies globally to the program. Only one line specifier may be given per command.

RTC Errors

Address in Block (aib)


Problem: A possible memory leak. There is no reference to the start of an allocated block, but there is at least one reference to an address within the block.
Possible causes: The only pointer to the start of the block is incremented.
char *ptr;
main()
{
		ptr = (char *)malloc(4);
		ptr++;					/* Address in Block */
}

Address in Register (air)


Problem: A possible memory leak. An allocated block has not been freed, and no reference to the block exists anywhere in program memory.
Possible causes: All references to an allocated block are contained in registers. This can occur legitimately if the compiler keeps a program variable only in a register instead of in memory. The compiler often does this for local variables and function parameters when optimization is turned on. If this error occurs when optimization has not been turned on, it is likely to be an actual memory leak. This can occur if the only pointer to an allocated block goes out of scope before the block is freed.
		if (i == 0) {
				char *ptr = (char *)malloc(4);
				/* ptr is going out of scope */
		}
	 /* Memory Leak or Address in Register */

Bad Free (baf)


Problem: Attempt to free memory that has never been allocated.
Possible causes: Passing a non-heap data pointer to 
free() or realloc().
		char a[4];
		char *b = &a[0];

		free(b);					/* Bad free (baf) */

Duplicate Free (duf)


Problem: Attempt to free a heap block that has already been freed.
Possible causes: Calling free()
 more than once with the same pointer. In C++, using the 
delete operator more than once on the
same pointer.
		char *a = (char *)malloc(1);
		free(a);
		free(a);					/* Duplicate free (duf) */

Misaligned Free (maf)


Problem: Attempt to free a misaligned heap block.
Possible causes: Passing an improperly aligned pointer
to free() or realloc()
; changing the pointer returned by malloc
.
		char *ptr = (char *)malloc(4);
		ptr++;
		free(ptr);					/* Misaligned free */

Misaligned Read (mar)


Problem: Attempt to read data from an address without proper alignment.
Possible causes: Reading 2, 4, or 8 bytes from an address that is not half-word-aligned, word-aligned, or double-word-aligned, respectively.
		char *s = "hello world";
		int *i = (int *)&s[1];
		int j;

		j = *i;					/* Misaligned read (mar) */

Misaligned Write (maw)


Problem: Attempt to write data to an address without proper alignment.
Possible causes: Writing 2, 4, or 8 bytes to an address that is not half-word-aligned, word-aligned, or double-word-aligned, respectively.
		char *s = "hello world";
		int *i = (int *)&s[1];

		*i = 0;					/* Misaligned write (maw) */

Memory Leak (mel)


Problem: An allocated block has not been freed, and no reference to the block exists anywhere in the program.
Possible causes: Program failed to free a block no longer used.
char *ptr;
    ptr = (char *)malloc(1);
    ptr = 0;
/* Memory leak (mel) */

Out of Memory (oom)


Problem: Attempt to allocate memory beyond physical memory available.
Cause: Program cannot obtain more memory from the system.
Useful in tracking down problems that occur when the return value
from malloc() is not checked
for NULL, which is a common
programming mistake.
		char *ptr = (char *)malloc(0x7fffffff);
		/* Out of Memory (oom), ptr == NULL */

Read from Unallocated Memory (rua)


Problem: Attempt to read from nonexistent, unallocated, or unmapped memory.
Possible causes: A stray pointer, overflowing the bounds of a heap block or accessing a heap block that has already been freed.
		char c, *a = (char *)malloc(1);
		c = a[1];					/* Read from unallocated memory (rua) */

Read from Uninitialized Memory (rui)


Problem: Attempt to read from uninitialized memory.
Possible causes: Reading local or heap data that has not been initialized.
		foo()
		{ 	int i, j;
			j = i;				/* Read from uninitialized memory (rui) */
		}

Write to Read-Only Memory (wro)


Problem: Attempt to write to read-only memory.
Possible causes: Writing to a text address, writing to
a read-only data section (.rodata
), or writing to a page that has been mmap
'ed as read-only.
		foo()
		{ 	int *foop = (int *) foo;
			*foop = 0;				/* Write to read-only memory (wro) */
		}

Write to Unallocated Memory (wua)


Problem: Attempt to write to nonexistent, unallocated, or unmapped memory.
Possible causes: A stray pointer, overflowing the bounds of a heap block, or accessing a heap block that has already been freed.
		char *a = (char *)malloc(1);
		a[1] = `\0';					/* Write to unallocated memory (wua) */

dbxenv Variables

The following dbxenv variables control the operation of RTC. If you want to permanently change any of these variables from their default values, place the dbxenv commands in the $HOME/.dbxrc file.Then, your preferred values are used whenever you use RTC.

dbxenv rtc_auto_continue {on | off}

rtc_auto_continue on causes RTC not to stop upon finding an error, but to continue running. It also causes all errors to be redirected to the rtc_error_log_file_name.The default is off.

dbxenv rtc_auto_suppress {on | off}

rtc_auto_suppress on causes a particular access error at a particular location to be reported only the first time it is encountered and suppressed thereafter. This is useful, for example, for preventing multiple copies of the same error report when an error occurs in a loop that is executed many times. The default is on.

dbxenv rtc_biu_at_exit {on | off | verbose}

This variable is used when memory use checking is on. If the value of the variable is on, a non-verbose memory use (blocks in use) report is produced at program exit. The default is on.

If the value is verbose, a verbose memory use report is produced at program exit. The value off causes no output. This variable has no effect on the showmemuse command.

dbxenv rtc_error_log_file_name filename

rtc_error_log_file_name redirects RTC error messages to the designated file instead of to the standard output of dbx. The default is /tmp/dbx.errlog.uniqueid.

The program does not automatically stop when run time errors are detected in batch mode. All error output is directed to your rtc_error_log_file_name file. The program stops when breakpoints are encountered or if the program is interrupted.

In batch mode, the complete stack backtrace is generated and redirected to the rtc_error_log_file_name file. To redirect all errors to the terminal, set the rtc_error_log_file_name to /dev/tty.

dbxenv rtc_error_limit n

n is the maximum number of errors that RTC reports. The error limit is used separately for access errors and leak errors. For example, if the error limit is set to 5, then a maximum of five access errors and five memory leaks are shown in both the leak report at the end of the run and for each showleaks command you issue. The default is 1000.

dbxenv rtc_mel_at_exit {on | off | verbose}

This variable is used when leak checking is on. If the value of the variable is on, a non-verbose memory leak report is produced at program exit. If the value is verbose, a verbose memory leak report is produced at program exit. The value off causes no output. This variable has no effect on the showleaks command. The default is on.