GNAT GPL User's Guide: Memory Management Issues

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

# 23. Memory Management Issues

This chapter describes some useful memory pools provided in the GNAT library and in particular the GNAT Debug Pool facility, which can be used to detect incorrect uses of access values (including "dangling references"). It also describes the `gnatmem` tool, which can be used to track down "memory leaks".

> 23.1 Some Useful Memory Pools |  |
> 23.2 The GNAT Debug Pool Facility |  |
> 23.3 The `gnatmem` Tool |  |

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## 23.1 Some Useful Memory Pools

The `System.Pool_Global` package offers the Unbounded_No_Reclaim_Pool storage pool. Allocations use the standard system call `malloc` while deallocations use the standard system call `free`. No reclamation is performed when the pool goes out of scope. For performance reasons, the standard default Ada allocators/deallocators do not use any explicit storage pools but if they did, they could use this storage pool without any change in behavior. That is why this storage pool is used when the user manages to make the default implicit allocator explicit as in this example:

       type T1 is access Something;
        -- no Storage pool is defined for T2
       type T2 is access Something_Else;
       for T2'Storage_Pool use T1'Storage_Pool;
       -- the above is equivalent to
       for T2'Storage_Pool use System.Pool_Global.Global_Pool_Object;

The `System.Pool_Local` package offers the Unbounded_Reclaim_Pool storage pool. The allocation strategy is similar to `Pool_Local`'s except that the all storage allocated with this pool is reclaimed when the pool object goes out of scope. This pool provides a explicit mechanism similar to the implicit one provided by several Ada 83 compilers for allocations performed through a local access type and whose purpose was to reclaim memory when exiting the scope of a given local access. As an example, the following program does not leak memory even though it does not perform explicit deallocation:

    with System.Pool_Local;
    procedure Pooloc1 is
       procedure Internal is
          type A is access Integer;
          X : System.Pool_Local.Unbounded_Reclaim_Pool;
          for A'Storage_Pool use X;
          v : A;
       begin
          for I in  1 .. 50 loop
             v := new Integer;
          end loop;
       end Internal;
    begin
       for I in  1 .. 100 loop
          Internal;
       end loop;
    end Pooloc1;

The `System.Pool_Size` package implements the Stack_Bounded_Pool used when `Storage_Size` is specified for an access type. The whole storage for the pool is allocated at once, usually on the stack at the point where the access type is elaborated. It is automatically reclaimed when exiting the scope where the access type is defined. This package is not intended to be used directly by the user and it is implicitly used for each such declaration:

       type T1 is access Something;
       for T1'Storage_Size use 10_000;

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## 23.2 The GNAT Debug Pool Facility

The use of unchecked deallocation and unchecked conversion can easily lead to incorrect memory references. The problems generated by such references are usually difficult to tackle because the symptoms can be very remote from the origin of the problem. In such cases, it is very helpful to detect the problem as early as possible. This is the purpose of the Storage Pool provided by `GNAT.Debug_Pools`.

In order to use the GNAT specific debugging pool, the user must associate a debug pool object with each of the access types that may be related to suspected memory problems. See Ada Reference Manual 13.11.

    type Ptr is access Some_Type;
    Pool : GNAT.Debug_Pools.Debug_Pool;
    for Ptr'Storage_Pool use Pool;

`GNAT.Debug_Pools` is derived from a GNAT-specific kind of pool: the `Checked_Pool`. Such pools, like standard Ada storage pools, allow the user to redefine allocation and deallocation strategies. They also provide a checkpoint for each dereference, through the use of the primitive operation `Dereference` which is implicitly called at each dereference of an access value.

Once an access type has been associated with a debug pool, operations on values of the type may raise four distinct exceptions, which correspond to four potential kinds of memory corruption:

- `GNAT.Debug_Pools.Accessing_Not_Allocated_Storage`
- `GNAT.Debug_Pools.Accessing_Deallocated_Storage`
- `GNAT.Debug_Pools.Freeing_Not_Allocated_Storage`
- `GNAT.Debug_Pools.Freeing_Deallocated_Storage`

For types associated with a Debug_Pool, dynamic allocation is performed using the standard GNAT allocation routine. References to all allocated chunks of memory are kept in an internal dictionary. Several deallocation strategies are provided, whereupon the user can choose to release the memory to the system, keep it allocated for further invalid access checks, or fill it with an easily recognizable pattern for debug sessions. The memory pattern is the old IBM hexadecimal convention: `16#DEADBEEF#`.

See the documentation in the file g-debpoo.ads for more information on the various strategies.

Upon each dereference, a check is made that the access value denotes a properly allocated memory location. Here is a complete example of use of `Debug_Pools`, that includes typical instances of memory corruption:

    with Gnat.Io; use Gnat.Io;
    with Unchecked_Deallocation;
    with Unchecked_Conversion;
    with GNAT.Debug_Pools;
    with System.Storage_Elements;
    with Ada.Exceptions; use Ada.Exceptions;
    procedure Debug_Pool_Test is

       type T is access Integer;
       type U is access all T;

       P : GNAT.Debug_Pools.Debug_Pool;
       for T'Storage_Pool use P;

       procedure Free is new Unchecked_Deallocation (Integer, T);
       function UC is new Unchecked_Conversion (U, T);
       A, B : aliased T;

       procedure Info is new GNAT.Debug_Pools.Print_Info(Put_Line);

    begin
       Info (P);
       A := new Integer;
       B := new Integer;
       B := A;
       Info (P);
       Free (A);
       begin
          Put_Line (Integer'Image(B.all));
       exception
          when E : others => Put_Line ("raised: " & Exception_Name (E));
       end;
       begin
          Free (B);
       exception
          when E : others => Put_Line ("raised: " & Exception_Name (E));
       end;
       B := UC(A'Access);
       begin
          Put_Line (Integer'Image(B.all));
       exception
          when E : others => Put_Line ("raised: " & Exception_Name (E));
       end;
       begin
          Free (B);
       exception
          when E : others => Put_Line ("raised: " & Exception_Name (E));
       end;
       Info (P);
    end Debug_Pool_Test;

The debug pool mechanism provides the following precise diagnostics on the execution of this erroneous program:

    Debug Pool info:
      Total allocated bytes :  0
      Total deallocated bytes :  0
      Current Water Mark:  0
      High Water Mark:  0

    Debug Pool info:
      Total allocated bytes :  8
      Total deallocated bytes :  0
      Current Water Mark:  8
      High Water Mark:  8

    raised: GNAT.DEBUG_POOLS.ACCESSING_DEALLOCATED_STORAGE
    raised: GNAT.DEBUG_POOLS.FREEING_DEALLOCATED_STORAGE
    raised: GNAT.DEBUG_POOLS.ACCESSING_NOT_ALLOCATED_STORAGE
    raised: GNAT.DEBUG_POOLS.FREEING_NOT_ALLOCATED_STORAGE
    Debug Pool info:
      Total allocated bytes :  8
      Total deallocated bytes :  4
      Current Water Mark:  4
      High Water Mark:  8

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

## 23.3 The `gnatmem` Tool

The `gnatmem` utility monitors dynamic allocation and deallocation activity in a program, and displays information about incorrect deallocations and possible sources of memory leaks. It is designed to work in association with a static runtime library only and in this context provides three types of information:

- General information concerning memory management, such as the total number of allocations and deallocations, the amount of allocated memory and the high water mark, i.e. the largest amount of allocated memory in the course of program execution.

- Backtraces for all incorrect deallocations, that is to say deallocations which do not correspond to a valid allocation.

- Information on each allocation that is potentially the origin of a memory leak.

> 23.3.1 Running `gnatmem` |  |
> 23.3.2 Switches for `gnatmem` |  |
> 23.3.3 Example of `gnatmem` Usage |  |

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

### 23.3.1 Running `gnatmem`

`gnatmem` makes use of the output created by the special version of allocation and deallocation routines that record call information. This allows it to obtain accurate dynamic memory usage history at a minimal cost to the execution speed. Note however, that `gnatmem` is not supported on all platforms (currently, it is supported on AIX, HP-UX, GNU/Linux, Solaris and Windows NT/2000/XP (x86).

The `gnatmem` command has the form

          $ gnatmem [switches] user_program

The program must have been linked with the instrumented version of the allocation and deallocation routines. This is done by linking with the ``libgmem.a'` library. For correct symbolic backtrace information, the user program should be compiled with debugging options (see 3.2 Switches for `gcc`). For example to build ``my_program'`:

    $ gnatmake -g my_program -largs -lgmem

As library ``libgmem.a'` contains an alternate body for package `System.Memory`, ``s-memory.adb'` should not be compiled and linked when an executable is linked with library ``libgmem.a'`. It is then not recommended to use `gnatmake` with switch ``-a'`.

When ``my_program'` is executed, the file ``gmem.out'` is produced. This file contains information about all allocations and deallocations performed by the program. It is produced by the instrumented allocations and deallocations routines and will be used by `gnatmem`.

In order to produce symbolic backtrace information for allocations and deallocations performed by the GNAT run-time library, you need to use a version of that library that has been compiled with the ``-g'` switch (see 21.4 Rebuilding the GNAT Run-Time Library).

Gnatmem must be supplied with the ``gmem.out'` file and the executable to examine. If the location of ``gmem.out'` file was not explicitly supplied by ``-i'` switch, gnatmem will assume that this file can be found in the current directory. For example, after you have executed ``my_program'`, ``gmem.out'` can be analyzed by `gnatmem` using the command:

    $ gnatmem my_program

This will produce the output with the following format:

*************** debut cc

    $ gnatmem my_program

    Global information
    ------------------
       Total number of allocations        :  45
       Total number of deallocations      :   6
       Final Water Mark (non freed mem)   :  11.29 Kilobytes
       High Water Mark                    :  11.40 Kilobytes

    .
    .
    .
    Allocation Root # 2
    -------------------
     Number of non freed allocations    :  11
     Final Water Mark (non freed mem)   :   1.16 Kilobytes
     High Water Mark                    :   1.27 Kilobytes
     Backtrace                          :
       my_program.adb:23 my_program.alloc
    .
    .
    .

The first block of output gives general information. In this case, the Ada construct "`new`" was executed 45 times, and only 6 calls to an Unchecked_Deallocation routine occurred.

Subsequent paragraphs display information on all allocation roots. An allocation root is a specific point in the execution of the program that generates some dynamic allocation, such as a "`new`" construct. This root is represented by an execution backtrace (or subprogram call stack). By default the backtrace depth for allocations roots is 1, so that a root corresponds exactly to a source location. The backtrace can be made deeper, to make the root more specific.

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

### 23.3.2 Switches for `gnatmem`

`gnatmem` recognizes the following switches:

``-q'`
  Quiet. Gives the minimum output needed to identify the origin of the memory leaks. Omits statistical information.

``N'`
  N is an integer literal (usually between 1 and 10) which controls the depth of the backtraces defining allocation root. The default value for N is 1. The deeper the backtrace, the more precise the localization of the root. Note that the total number of roots can depend on this parameter. This parameter must be specified before the name of the executable to be analyzed, to avoid ambiguity.

``-b n'`
  This switch has the same effect as just depth parameter.

``-i file'`
  Do the `gnatmem` processing starting from ``file'`, rather than ``gmem.out'` in the current directory.

``-m n'`
  This switch causes `gnatmem` to mask the allocation roots that have less than n leaks. The default value is 1. Specifying the value of 0 will allow examination of even the roots that did not result in leaks.

``-s order'`
  This switch causes `gnatmem` to sort the allocation roots according to the specified order of sort criteria, each identified by a single letter. The currently supported criteria are `n, h, w` standing respectively for number of unfreed allocations, high watermark, and final watermark corresponding to a specific root. The default order is `nwh`.

``-t'`
  This switch causes memory allocated size to be always output in bytes. Default `gnatmem` behavior is to show memory sizes less then 1 kilobyte in bytes, from 1 kilobyte till 1 megabyte in kilobytes and the rest in megabytes.

[ < ] | [ > ] |  | [ << ] | [ Up ] | [ >> ] |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

### 23.3.3 Example of `gnatmem` Usage

The following example shows the use of `gnatmem` on a simple memory-leaking program. Suppose that we have the following Ada program:

    with Unchecked_Deallocation;
    procedure Test_Gm is

       type T is array (1..1000) of Integer;
       type Ptr is access T;
       procedure Free is new Unchecked_Deallocation (T, Ptr);
       A : Ptr;

       procedure My_Alloc is
       begin
          A := new T;
       end My_Alloc;

       procedure My_DeAlloc is
          B : Ptr := A;
       begin
          Free (B);
       end My_DeAlloc;

    begin
       My_Alloc;
       for I in 1 .. 5 loop
          for J in I .. 5 loop
             My_Alloc;
          end loop;
          My_Dealloc;
       end loop;
    end;

The program needs to be compiled with debugging option and linked with `gmem` library:

    $ gnatmake -g test_gm -largs -lgmem

Then we execute the program as usual:

    $ test_gm

Then `gnatmem` is invoked simply with

    $ gnatmem test_gm

which produces the following output (result may vary on different platforms):

    Global information
    ------------------
       Total number of allocations        :  18
       Total number of deallocations      :   5
       Final Water Mark (non freed mem)   :  53.00 Kilobytes
       High Water Mark                    :  56.90 Kilobytes

    Allocation Root # 1
    -------------------
     Number of non freed allocations    :  11
     Final Water Mark (non freed mem)   :  42.97 Kilobytes
     High Water Mark                    :  46.88 Kilobytes
     Backtrace                          :
       test_gm.adb:11 test_gm.my_alloc

    Allocation Root # 2
    -------------------
     Number of non freed allocations    :   1
     Final Water Mark (non freed mem)   :  10.02 Kilobytes
     High Water Mark                    :  10.02 Kilobytes
     Backtrace                          :
       s-secsta.adb:81 system.secondary_stack.ss_init

    Allocation Root # 3
    -------------------
     Number of non freed allocations    :   1
     Final Water Mark (non freed mem)   :  12 Bytes
     High Water Mark                    :  12 Bytes
     Backtrace                          :
       s-secsta.adb:181 system.secondary_stack.ss_init

Note that the GNAT run time contains itself a certain number of allocations that have no corresponding deallocation, as shown here for root #2 and root #3. This is a normal behavior when the number of non-freed allocations is one, it allocates dynamic data structures that the run time needs for the complete lifetime of the program. Note also that there is only one allocation root in the user program with a single line back trace: test_gm.adb:11 test_gm.my_alloc, whereas a careful analysis of the program shows that 'My_Alloc' is called at 2 different points in the source (line 21 and line 24). If those two allocation roots need to be distinguished, the backtrace depth parameter can be used:

    $ gnatmem 3 test_gm

which will give the following output:

    Global information
    ------------------
       Total number of allocations        :  18
       Total number of deallocations      :   5
       Final Water Mark (non freed mem)   :  53.00 Kilobytes
       High Water Mark                    :  56.90 Kilobytes

    Allocation Root # 1
    -------------------
     Number of non freed allocations    :  10
     Final Water Mark (non freed mem)   :  39.06 Kilobytes
     High Water Mark                    :  42.97 Kilobytes
     Backtrace                          :
       test_gm.adb:11 test_gm.my_alloc
       test_gm.adb:24 test_gm
       b_test_gm.c:52 main

    Allocation Root # 2
    -------------------
     Number of non freed allocations    :   1
     Final Water Mark (non freed mem)   :  10.02 Kilobytes
     High Water Mark                    :  10.02 Kilobytes
     Backtrace                          :
       s-secsta.adb:81  system.secondary_stack.ss_init
       s-secsta.adb:283 <system__secondary_stack___elabb>
       b_test_gm.c:33   adainit

    Allocation Root # 3
    -------------------
     Number of non freed allocations    :   1
     Final Water Mark (non freed mem)   :   3.91 Kilobytes
     High Water Mark                    :   3.91 Kilobytes
     Backtrace                          :
       test_gm.adb:11 test_gm.my_alloc
       test_gm.adb:21 test_gm
       b_test_gm.c:52 main

    Allocation Root # 4
    -------------------
     Number of non freed allocations    :   1
     Final Water Mark (non freed mem)   :  12 Bytes
     High Water Mark                    :  12 Bytes
     Backtrace                          :
       s-secsta.adb:181 system.secondary_stack.ss_init
       s-secsta.adb:283 <system__secondary_stack___elabb>
       b_test_gm.c:33   adainit

The allocation root #1 of the first example has been split in 2 roots #1 and #3 thanks to the more precise associated backtrace.

[ << ] | [ >> ] |  |  |  |  |  | [Top] | [Contents] | [Index] | [ ? ]

This document was generated by GNAT Mailserver on April, 17 2014 using texi2html