gnatmem.adb   [plain text]


------------------------------------------------------------------------------
--                                                                          --
--                         GNAT COMPILER COMPONENTS                         --
--                                                                          --
--                              G N A T M E M                               --
--                                                                          --
--                                 B o d y                                  --
--                                                                          --
--                                                                          --
--           Copyright (C) 1997-2002, Ada Core Technologies, Inc.           --
--                                                                          --
-- GNAT is free software;  you can  redistribute it  and/or modify it under --
-- terms of the  GNU General Public License as published  by the Free Soft- --
-- ware  Foundation;  either version 2,  or (at your option) any later ver- --
-- sion.  GNAT is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License --
-- for  more details.  You should have  received  a copy of the GNU General --
-- Public License  distributed with GNAT;  see file COPYING.  If not, write --
-- to  the Free Software Foundation,  59 Temple Place - Suite 330,  Boston, --
-- MA 02111-1307, USA.                                                      --
--                                                                          --
-- GNAT was originally developed  by the GNAT team at  New York University. --
-- Extensive contributions were provided by Ada Core Technologies Inc.      --
--                                                                          --
------------------------------------------------------------------------------

--  GNATMEM is a utility that tracks memory leaks. It is based on a simple
--  idea:
--      - run the application under gdb
--      - set a breakpoint on __gnat_malloc and __gnat_free
--      - record a reference to the allocated memory on each allocation call
--      - suppress this reference on deallocation
--      - at the end of the program, remaining references are potential leaks.
--        sort them out the best possible way in order to locate the root of
--        the leak.
--
--   GNATMEM can also be used with instrumented allocation/deallocation
--   routine (see a-raise.c with symbol GMEM defined). This is not supported
--   in all platforms, again refer to a-raise.c for further information.
--   In this case the application must be relinked with library libgmem.a:
--
--      $ gnatmake my_prog -largs -lgmem
--
--   The running my_prog will produce a file named gmem.out that will be
--   parsed by gnatmem.
--
--   In order to help finding out the real leaks,  the notion of "allocation
--   root" is defined. An allocation root is a specific point in the program
--   execution generating memory allocation where data is collected (such as
--   number of allocations, quantify of memory allocated, high water mark,
--   etc.).

with Ada.Command_Line;        use Ada.Command_Line;
with Ada.Text_IO;             use Ada.Text_IO;
with Ada.Text_IO.C_Streams;
with Ada.Float_Text_IO;
with Ada.Integer_Text_IO;
with Gnatvsn;                 use Gnatvsn;
with GNAT.Heap_Sort_G;
with GNAT.OS_Lib;
with GNAT.HTable;             use GNAT.HTable;
with Interfaces.C_Streams;    use Interfaces.C_Streams;
with System;                  use System;
with System.Storage_Elements; use System.Storage_Elements;

with Memroot; use Memroot;

procedure Gnatmem is

   ------------------------------------------------
   --  Potentially Target Dependent Subprograms. --
   ------------------------------------------------

   function Get_Current_TTY return String;
   --  Give the current tty on which the program is run. This is needed to
   --  separate the output of the debugger from the output of the program.
   --  The output of this function will be used to call the gdb command "tty"
   --  in the gdb script in order to get the program output on the current tty
   --  while the gdb output is redirected and processed by gnatmem.

   function popen  (File, Mode : System.Address) return FILEs;
   pragma Import (C, popen, "popen");
   --  Execute the program 'File'. If the mode is "r" the standard output
   --  of the program is redirected and the FILEs handler of the
   --  redirection is returned.

   procedure System_Cmd (X : System.Address);
   pragma Import (C, System_Cmd, "system");
   --  Execute the program "X".

   subtype Cstring        is String (1 .. Integer'Last);
   type    Cstring_Ptr is access all Cstring;

   function ttyname (Dec : Integer) return Cstring_Ptr;
   pragma Import (C, ttyname, "__gnat_ttyname");
   --  Return a null-terminated string containing the current tty

   Dir_Sep : constant Character := '/';

   ------------------------
   -- Other Declarations --
   ------------------------

   type Gdb_Output_Elmt is (Eof, Alloc, Deall);
   --  Eof    = End of gdb output file
   --  Alloc  = found a ALLOC mark in the gdb output
   --  Deall  = found a DEALL mark in the gdb output
   Gdb_Output_Format_Error : exception;

   function Read_Next return Gdb_Output_Elmt;
   --  Read the output of the debugger till it finds either the end of the
   --  output, or the 'ALLOC' mark or the 'DEALL' mark. In the second case,
   --  it sets the Tmp_Size and Tmp_Address global variables, in the
   --  third case it sets the Tmp_Address variable.

   procedure Create_Gdb_Script;
   --  Create the GDB script and save it in a temporary file

   function Mem_Image (X : Storage_Count) return String;
   --  X is a size in storage_element. Returns a value
   --  in Megabytes, Kiloytes or Bytes as appropriate.

   procedure Process_Arguments;
   --  Read command line arguments;

   procedure Usage;
   --  Prints out the option help

   function Gmem_Initialize (Dumpname : String) return Boolean;
   --  Opens the file represented by Dumpname and prepares it for
   --  work. Returns False if the file does not have the correct format, True
   --  otherwise.

   procedure Gmem_A2l_Initialize (Exename : String);
   --  Initialises the convert_addresses interface by supplying it with
   --  the name of the executable file Exename

   procedure Gmem_Read_Next (Buf : out String; Last : out Natural);
   --  Reads the next allocation/deallocation entry and its backtrace
   --  and prepares in the string Buf (up to the position of Last) the
   --  expression compatible with gnatmem parser:
   --  Allocation entry produces the expression "ALLOC^[size]^0x[address]^"
   --  Deallocation entry produces the expression "DEALLOC^0x[address]^"

   Argc        : constant Integer   := Argument_Count;
   Gnatmem_Tmp : aliased constant String    := "gnatmem.tmp";

   Mode_R : aliased constant String (1 .. 2) := 'r'  & ASCII.NUL;
   Mode_W : aliased constant String (1 .. 3) := "w+" & ASCII.NUL;

   -----------------------------------
   -- HTable address --> Allocation --
   -----------------------------------

   type Allocation is record
      Root : Root_Id;
      Size : Storage_Count;
   end record;

   type Address_Range is range 0 .. 4097;
   function H (A : Integer_Address) return Address_Range;
   No_Alloc : constant Allocation := (No_Root_Id, 0);

   package Address_HTable is new GNAT.HTable.Simple_HTable (
     Header_Num => Address_Range,
     Element    => Allocation,
     No_Element => No_Alloc,
     Key        => Integer_Address,
     Hash       => H,
     Equal      => "=");

   BT_Depth   : Integer := 1;
   FD         : FILEs;
   FT         : File_Type;
   File_Pos   : Integer := 0;
   Exec_Pos   : Integer := 0;
   Target_Pos : Integer := 0;
   Run_Gdb    : Boolean := True;

   Global_Alloc_Size      : Storage_Count  := 0;
   Global_High_Water_Mark : Storage_Count  := 0;
   Global_Nb_Alloc        : Integer        := 0;
   Global_Nb_Dealloc      : Integer        := 0;
   Nb_Root                : Integer        := 0;
   Nb_Wrong_Deall         : Integer        := 0;
   Target_Name            : String (1 .. 80);
   Target_Protocol        : String (1 .. 80);
   Target_Name_Len        : Integer;
   Target_Protocol_Len    : Integer;
   Cross_Case             : Boolean := False;

   Tmp_Size    : Storage_Count  := 0;
   Tmp_Address : Integer_Address;
   Tmp_Alloc   : Allocation;
   Quiet_Mode  : Boolean := False;

   --------------------------------
   -- GMEM functionality binding --
   --------------------------------

   function Gmem_Initialize (Dumpname : String) return Boolean is
      function Initialize (Dumpname : System.Address) return Boolean;
      pragma Import (C, Initialize, "__gnat_gmem_initialize");
      S : aliased String := Dumpname & ASCII.NUL;
   begin
      return Initialize (S'Address);
   end Gmem_Initialize;

   procedure Gmem_A2l_Initialize (Exename : String) is
      procedure A2l_Initialize (Exename : System.Address);
      pragma Import (C, A2l_Initialize, "__gnat_gmem_a2l_initialize");
      S : aliased String := Exename & ASCII.NUL;
   begin
      A2l_Initialize (S'Address);
   end Gmem_A2l_Initialize;

   procedure Gmem_Read_Next (Buf : out String; Last : out Natural) is
      procedure Read_Next (buf : System.Address);
      pragma Import (C, Read_Next, "__gnat_gmem_read_next");
      function Strlen (str : System.Address) return Natural;
      pragma Import (C, Strlen, "strlen");

      S : String (1 .. 1000);
   begin
      Read_Next (S'Address);
      Last := Strlen (S'Address);
      Buf (1 .. Last) := S (1 .. Last);
   end Gmem_Read_Next;

   ---------------------
   -- Get_Current_TTY --
   ---------------------

   function Get_Current_TTY return String is
      Res          :  Cstring_Ptr;
      stdout       : constant Integer := 1;
      Max_TTY_Name : constant Integer := 500;

   begin
      if isatty (stdout) /= 1 then
         return "";
      end if;

      Res := ttyname (1);
      if Res /= null then
         for J in Cstring'First .. Max_TTY_Name loop
            if Res (J) = ASCII.NUL then
               return Res (Cstring'First .. J - 1);
            end if;
         end loop;
      end if;

      --  if we fall thru the ttyname result was dubious. Just forget it.

      return "";
   end Get_Current_TTY;

   -------
   -- H --
   -------

   function H (A : Integer_Address) return Address_Range is
   begin
      return Address_Range (A mod Integer_Address (Address_Range'Last));
   end H;

   -----------------------
   -- Create_Gdb_Script --
   -----------------------

   procedure Create_Gdb_Script is
      FD : File_Type;

   begin
      begin
         Create (FD, Out_File, Gnatmem_Tmp);
      exception
         when others =>
            Put_Line ("Cannot create temporary file : " & Gnatmem_Tmp);
            GNAT.OS_Lib.OS_Exit (1);
      end;

      declare
         TTY : constant String := Get_Current_TTY;
      begin
         if TTY'Length > 0 then
            Put_Line (FD, "tty " & TTY);
         end if;
      end;

      if Cross_Case then
         Put (FD, "target ");
         Put (FD, Target_Protocol (1 .. Target_Protocol_Len));
         Put (FD, " ");
         Put (FD, Argument (Target_Pos));
         New_Line (FD);
         Put (FD, "load ");
         Put_Line (FD, Argument (Exec_Pos));

      else
         --  In the native case, run the program before setting the
         --  breakpoints so that gnatmem will also work with shared
         --  libraries.

         Put_Line (FD, "set lang c");
         Put_Line (FD, "break main");
         Put_Line (FD, "set lang auto");
         Put      (FD, "run");
         for J in Exec_Pos + 1 .. Argc loop
            Put (FD, " ");
            Put (FD, Argument (J));
         end loop;
         New_Line (FD);

         --  At this point, gdb knows about __gnat_malloc and __gnat_free
      end if;

      --  Make sure that outputing long backtraces do not pause

      Put_Line (FD, "set height 0");
      Put_Line (FD, "set width 0");

      if Quiet_Mode then
         Put_Line (FD, "break __gnat_malloc");
         Put_Line (FD, "command");
         Put_Line (FD, "   silent");
         Put_Line (FD, "   set lang c");
         Put_Line (FD, "   set print address on");
         Put_Line (FD, "   up");
         Put_Line (FD, "   set $gm_addr = $pc");
         Put_Line (FD, "   printf ""\n\n""");
         Put_Line (FD, "   printf ""ALLOC^0x%x^\n"", $gm_addr");
         Put_Line (FD, "   set print address off");
         Put_Line (FD, "   set lang auto");
      else
         Put_Line (FD, "break __gnat_malloc");
         Put_Line (FD, "command");
         Put_Line (FD, "   silent");
         Put_Line (FD, "   set lang c");
         Put_Line (FD, "   set $gm_size = size");
         Put_Line (FD, "   set print address on");
         Put_Line (FD, "   up");
         Put_Line (FD, "   set $gm_addr = $pc");
         Put_Line (FD, "   printf ""\n\n""");
         Put_Line (FD, "   printf ""ALLOC^%d^0x%x^\n"", $gm_size, $gm_addr");
         Put_Line (FD, "   set print address off");
         Put_Line (FD, "   set lang auto");
      end if;

      Put (FD, "   backtrace");

      if BT_Depth /= 0 then
         Put (FD, Integer'Image (BT_Depth + 1));
      end if;

      New_Line (FD);

      Put_Line (FD, "   printf ""\n\n""");
      Put_Line (FD, "   continue");
      Put_Line (FD, "end");
      Put_Line (FD, "#");
      Put_Line (FD, "#");
      Put_Line (FD, "break __gnat_free");
      Put_Line (FD, "command");
      Put_Line (FD, "   silent");
      Put_Line (FD, "   set print address on");
      Put_Line (FD, "   printf ""\n\n""");
      Put_Line (FD, "   printf ""DEALL^0x%x^\n"", ptr");
      Put_Line (FD, "   set print address off");
      Put_Line (FD, "   up");

      Put (FD, "   backtrace");

      if BT_Depth /= 0 then
         Put (FD, Integer'Image (BT_Depth + 1));
      end if;

      New_Line (FD);

      Put_Line (FD, "   printf ""\n\n""");
      Put_Line (FD, "   continue");
      Put_Line (FD, "end");
      Put_Line (FD, "#");
      Put_Line (FD, "#");
      Put_Line (FD, "#");

      if Cross_Case then
         Put (FD, "run ");
         Put_Line (FD, Argument (Exec_Pos));

         if Target_Protocol (1 .. Target_Protocol_Len) = "wtx" then
            Put (FD, "unload ");
            Put_Line (FD, Argument (Exec_Pos));
         end if;
      else
         Put_Line (FD, "continue");
      end if;

      Close (FD);
   end Create_Gdb_Script;

   ---------------
   -- Mem_Image --
   ---------------

   function Mem_Image (X : Storage_Count) return String is
      Ks    : constant Storage_Count := X / 1024;
      Megs  : constant Storage_Count := Ks / 1024;
      Buff  : String (1 .. 7);

   begin
      if Megs /= 0 then
         Ada.Float_Text_IO.Put (Buff, Float (X) / 1024.0 / 1024.0, 2, 0);
         return Buff & " Megabytes";

      elsif Ks /= 0 then
         Ada.Float_Text_IO.Put (Buff, Float (X) / 1024.0, 2, 0);
         return Buff & " Kilobytes";

      else
         Ada.Integer_Text_IO.Put (Buff (1 .. 4), Integer (X));
         return  Buff (1 .. 4) & " Bytes";
      end if;
   end Mem_Image;

   -----------
   -- Usage --
   -----------

   procedure Usage is
   begin
      New_Line;
      Put ("GNATMEM ");
      Put (Gnat_Version_String);
      Put_Line (" Copyright 1997-2002 Free Software Foundation, Inc.");
      New_Line;

      if Cross_Case then
         Put_Line (Command_Name
           & " [-q] [n] [-o file] target entry_point ...");
         Put_Line (Command_Name & " [-q] [n] [-i file]");

      else
         Put_Line ("GDB mode");
         Put_Line ("   " & Command_Name
                   & " [-q] [n] [-o file] program arg1 arg2 ...");
         Put_Line ("   " & Command_Name
                   & " [-q] [n] [-i file]");
         New_Line;
         Put_Line ("GMEM mode");
         Put_Line ("   " & Command_Name
                   & " [-q] [n] -i gmem.out program arg1 arg2 ...");
         New_Line;
      end if;

      Put_Line ("  -q       quiet, minimum output");
      Put_Line ("   n       number of frames for allocation root backtraces");
      Put_Line ("           default is 1.");
      Put_Line ("  -o file  save gdb output in 'file' and process data");
      Put_Line ("           post mortem. also keep the gdb script around");
      Put_Line ("  -i file  don't run gdb output. Do only post mortem");
      Put_Line ("           processing from file");
      GNAT.OS_Lib.OS_Exit (1);
   end Usage;

   -----------------------
   -- Process_Arguments --
   -----------------------

   procedure Process_Arguments is
      Arg : Integer;

      procedure Check_File (Arg_Pos : Integer; For_Creat : Boolean := False);
      --  Check that Argument (Arg_Pos) is an existing file if For_Creat is
      --  false or if it is possible to create it if For_Creat is true

      procedure Check_File (Arg_Pos : Integer; For_Creat : Boolean := False) is
         Name : aliased constant String := Argument (Arg_Pos) & ASCII.NUL;
         X    : int;

      begin
         if For_Creat then
            FD := fopen (Name'Address, Mode_W'Address);
         else
            FD := fopen (Name'Address, Mode_R'Address);
         end if;

         if FD = NULL_Stream then
            New_Line;
            if For_Creat then
               Put_Line ("Cannot create file : " & Argument (Arg_Pos));
            else
               Put_Line ("Cannot locate file : " & Argument (Arg_Pos));
            end if;
            New_Line;
            Usage;
         else
            X := fclose (FD);
         end if;
      end Check_File;

   --  Start of processing for Process_Arguments

   begin

      --  Is it a cross version?

      declare
         Std_Name : constant String  := "gnatmem";
         Name     : constant String  := Command_Name;
         End_Pref : constant Integer := Name'Last - Std_Name'Length;

      begin
         if Name'Length > Std_Name'Length + 9
           and then
             Name (End_Pref + 1 .. Name'Last) = Std_Name
           and then
             Name (End_Pref - 8 .. End_Pref) = "-vxworks-"
         then
            Cross_Case := True;

            Target_Name_Len := End_Pref - 1;
            for J in reverse Name'First .. End_Pref - 1 loop
               if Name (J) = Dir_Sep then
                  Target_Name_Len := Target_Name_Len - J;
                  exit;
               end if;
            end loop;

            Target_Name (1 .. Target_Name_Len)
              := Name (End_Pref - Target_Name_Len  .. End_Pref - 1);

            if Target_Name (1 .. 5) = "alpha" then
               Target_Protocol (1 .. 7) := "vxworks";
               Target_Protocol_Len := 7;
            else
               Target_Protocol (1 .. 3) := "wtx";
               Target_Protocol_Len := 3;
            end if;
         end if;
      end;

      Arg := 1;

      if Argc < Arg then
         Usage;
      end if;

      --  Deal with "-q"

      if Argument (Arg) = "-q" then

         Quiet_Mode := True;
         Arg := Arg + 1;

         if Argc < Arg then
            Usage;
         end if;
      end if;

      --  Deal with back trace depth

      if Argument (Arg) (1) in '0' .. '9' then
         begin
            BT_Depth := Integer'Value (Argument (Arg));
         exception
            when others =>
               Usage;
         end;

         Arg := Arg + 1;

         if Argc < Arg then
            Usage;
         end if;
      end if;

      --  Deal with "-o file" or "-i file"

      while Arg <= Argc and then Argument (Arg) (1) = '-' loop
         Arg := Arg + 1;

         if Argc < Arg then
            Usage;
         end if;

         case Argument (Arg - 1) (2) is
            when 'o' =>
               Check_File (Arg, For_Creat => True);
               File_Pos := Arg;

            when 'i' =>
               Check_File (Arg);
               File_Pos := Arg;
               Run_Gdb  := False;
               if Gmem_Initialize (Argument (Arg)) then
                  Gmem_Mode := True;
               end if;

            when others =>
               Put_Line ("Unknown option : " & Argument (Arg));
               Usage;
         end case;

         Arg := Arg + 1;

         if Argc < Arg and then Run_Gdb then
            Usage;
         end if;
      end loop;

      --  In the cross case, we first get the target

      if Cross_Case then
         Target_Pos := Arg;
         Arg := Arg + 1;

         if Argc < Arg and then Run_Gdb then
            Usage;
         end if;
      end if;

      --  Now all the following arguments are to be passed to gdb

      if Run_Gdb then
         Exec_Pos := Arg;
         Check_File (Exec_Pos);

      elsif Gmem_Mode then
         if Arg > Argc then
            Usage;
         else
            Exec_Pos := Arg;
            Check_File (Exec_Pos);
            Gmem_A2l_Initialize (Argument (Exec_Pos));
         end if;

      --  ... in other cases further arguments are disallowed

      elsif Arg <= Argc then
         Usage;
      end if;
   end Process_Arguments;

   ---------------
   -- Read_Next --
   ---------------

   function Read_Next return Gdb_Output_Elmt is
      Max_Line : constant Integer   := 100;
      Line     : String (1 .. Max_Line);
      Last     : Integer := 0;

      Curs1, Curs2 : Integer;
      Separator    : constant Character := '^';

      function Next_Separator return Integer;
      --  Return the index of the next separator after Curs1 in Line

      function Next_Separator return Integer is
         Curs : Integer := Curs1;

      begin
         loop
            if Curs > Last then
               raise Gdb_Output_Format_Error;

            elsif Line (Curs) = Separator then
               return Curs;
            end if;

            Curs := Curs + 1;
         end loop;
      end Next_Separator;

   --  Start of processing for Read_Next

   begin
      Line (1) := ' ';

      loop
         if Gmem_Mode then
            Gmem_Read_Next (Line, Last);
         else
            Get_Line (FT, Line, Last);
         end if;

         if Line (1 .. 14) = "Program exited" then
            return Eof;

         elsif Line (1 .. 5) = "ALLOC" then
            --  ALLOC ^ <size> ^0x <addr> ^

            --  Read the size

            Curs1 := 7;
            Curs2 := Next_Separator - 1;

            if not Quiet_Mode then
               Tmp_Size := Storage_Count'Value (Line (Curs1 .. Curs2));
            end if;

            --  Read the address, skip "^0x"

            Curs1 := Curs2 + 4;
            Curs2 := Next_Separator - 1;
            Tmp_Address := Integer_Address'Value (
                               "16#" & Line (Curs1 .. Curs2) & "#");
            return Alloc;

         elsif Line (1 .. 5) = "DEALL" then
            --  DEALL ^ 0x <addr> ^

            --  Read the address, skip "^0x"

            Curs1 := 9;
            Curs2 := Next_Separator - 1;
            Tmp_Address := Integer_Address'Value (
                               "16#" & Line (Curs1 .. Curs2) & "#");
            return Deall;
         end if;
      end loop;
   exception
      when End_Error =>
         New_Line;
         Put_Line ("### incorrect user program  termination detected.");
         Put_Line ("    following data may not be meaningful");
         New_Line;
         return Eof;
   end Read_Next;

--  Start of processing for Gnatmem

begin
   Process_Arguments;

   if Run_Gdb then
      Create_Gdb_Script;
   end if;

   --  Now we start the gdb session using the following syntax

   --     gdb --nx --nw -batch -x gnatmem.tmp

   --  If there is a -o option we redirect the gdb output in the specified
   --  file, otherwise we just read directly from a pipe.

   if File_Pos /= 0 then
      declare
         Name : aliased String := Argument (File_Pos) & ASCII.NUL;

      begin
         if Run_Gdb then
            if Cross_Case then
               declare
                  Cmd : aliased String := Target_Name (1 .. Target_Name_Len)
                    & "-gdb --nx --nw -batch -x " & Gnatmem_Tmp & " > "
                    & Name;
               begin
                  System_Cmd (Cmd'Address);
               end;
            else

               declare
                  Cmd : aliased String
                    := "gdb --nx --nw " & Argument (Exec_Pos)
                           & " -batch -x " & Gnatmem_Tmp & " > "
                           & Name;
               begin
                  System_Cmd (Cmd'Address);
               end;
            end if;
         end if;

         if not Gmem_Mode then
            FD := fopen (Name'Address, Mode_R'Address);
         end if;
      end;

   else
      if Cross_Case then
         declare
            Cmd : aliased String := Target_Name (1 .. Target_Name_Len)
              & "-gdb --nx --nw -batch -x " & Gnatmem_Tmp & ASCII.NUL;
         begin
            FD := popen (Cmd'Address, Mode_R'Address);
         end;
      else
         declare
            Cmd : aliased String := "gdb --nx --nw " & Argument (Exec_Pos)
              & " -batch -x " & Gnatmem_Tmp & ASCII.NUL;

         begin
            FD := popen (Cmd'Address, Mode_R'Address);
         end;
      end if;
   end if;

   --  Open the FD file as a regular Text_IO file

   if not Gmem_Mode then
      Ada.Text_IO.C_Streams.Open (FT, In_File, FD);
   end if;

   --  Main loop  analysing the data generated by the debugger
   --  for each allocation, the backtrace is kept and stored in a htable
   --  whose entry is the address. Fore ach deallocation, we look for the
   --  corresponding allocation and cancel it.

   Main : loop
      case Read_Next is
         when EOF =>
            exit Main;

         when Alloc =>

            --  Update global counters if the allocated size is meaningful

            if Quiet_Mode then
               Tmp_Alloc.Root := Read_BT (BT_Depth, FT);
               if Nb_Alloc (Tmp_Alloc.Root) = 0 then
                  Nb_Root := Nb_Root + 1;
               end if;
               Set_Nb_Alloc (Tmp_Alloc.Root, Nb_Alloc (Tmp_Alloc.Root) + 1);
               Address_HTable.Set (Tmp_Address, Tmp_Alloc);

            elsif Tmp_Size > 0 then

               Global_Alloc_Size := Global_Alloc_Size + Tmp_Size;
               Global_Nb_Alloc   := Global_Nb_Alloc + 1;

               if Global_High_Water_Mark < Global_Alloc_Size then
                  Global_High_Water_Mark := Global_Alloc_Size;
               end if;

               --  Read the corresponding back trace

               Tmp_Alloc.Root := Read_BT (BT_Depth, FT);

               --  Update the number of allocation root if this is a new one

               if Nb_Alloc (Tmp_Alloc.Root) = 0 then
                  Nb_Root := Nb_Root + 1;
               end if;

               --  Update allocation root specific counters

               Set_Alloc_Size (Tmp_Alloc.Root,
                 Alloc_Size (Tmp_Alloc.Root) + Tmp_Size);

               Set_Nb_Alloc (Tmp_Alloc.Root, Nb_Alloc (Tmp_Alloc.Root) + 1);

               if High_Water_Mark (Tmp_Alloc.Root)
                  < Alloc_Size (Tmp_Alloc.Root)
               then
                  Set_High_Water_Mark (Tmp_Alloc.Root,
                    Alloc_Size (Tmp_Alloc.Root));
               end if;

               --  Associate this allocation root to the allocated address

               Tmp_Alloc.Size := Tmp_Size;
               Address_HTable.Set (Tmp_Address, Tmp_Alloc);

            --  non meaninful output, just consumes the backtrace

            else
               Tmp_Alloc.Root := Read_BT (BT_Depth, FT);
            end if;

         when Deall =>

            --  Get the corresponding Dealloc_Size and Root

            Tmp_Alloc := Address_HTable.Get (Tmp_Address);

            if Tmp_Alloc.Root = No_Root_Id then

               --  There was no prior allocation at this address, something is
               --  very wrong. Mark this allocation root as problematic a

               Tmp_Alloc.Root := Read_BT (BT_Depth, FT);

               if Nb_Alloc (Tmp_Alloc.Root) = 0 then
                  Set_Nb_Alloc (Tmp_Alloc.Root, Nb_Alloc (Tmp_Alloc.Root) - 1);
                  Nb_Wrong_Deall := Nb_Wrong_Deall + 1;
               end if;

            else
               --  Update global counters

               if not Quiet_Mode then
                  Global_Alloc_Size := Global_Alloc_Size - Tmp_Alloc.Size;
               end if;
               Global_Nb_Dealloc   := Global_Nb_Dealloc + 1;

               --  Update allocation root specific counters

               if not Quiet_Mode then
                  Set_Alloc_Size (Tmp_Alloc.Root,
                    Alloc_Size (Tmp_Alloc.Root) - Tmp_Alloc.Size);
               end if;
               Set_Nb_Alloc (Tmp_Alloc.Root, Nb_Alloc (Tmp_Alloc.Root) - 1);

               --  update the number of allocation root if this one disappear

               if Nb_Alloc (Tmp_Alloc.Root) = 0 then
                  Nb_Root := Nb_Root - 1;
               end if;

               --  De-associate the deallocated address

               Address_HTable.Remove (Tmp_Address);
            end if;
      end case;
   end loop Main;

   --  We can get rid of the temp file now

   if Run_Gdb and then File_Pos = 0 then
      declare
         X : int;
      begin
         X := unlink (Gnatmem_Tmp'Address);
      end;
   end if;

   --  Print out general information about overall allocation

   if not Quiet_Mode then
      Put_Line ("Global information");
      Put_Line ("------------------");

      Put      ("   Total number of allocations        :");
      Ada.Integer_Text_IO.Put (Global_Nb_Alloc, 4);
      New_Line;

      Put      ("   Total number of deallocations      :");
      Ada.Integer_Text_IO.Put (Global_Nb_Dealloc, 4);
      New_Line;

      Put_Line ("   Final Water Mark (non freed mem)   :"
        & Mem_Image (Global_Alloc_Size));
      Put_Line ("   High Water Mark                    :"
        & Mem_Image (Global_High_Water_Mark));
      New_Line;
   end if;

   --  Print out the back traces corresponding to potential leaks in order
   --  greatest number of non-deallocated allocations

   Print_Back_Traces : declare
      type Root_Array is array (Natural range <>) of Root_Id;
      Leaks   : Root_Array (0 .. Nb_Root);
      Leak_Index   : Natural := 0;

      Bogus_Dealls : Root_Array (1 .. Nb_Wrong_Deall);
      Deall_Index  : Natural := 0;

      procedure Move (From : Natural; To : Natural);
      function  Lt (Op1, Op2 : Natural) return Boolean;
      package   Root_Sort is new GNAT.Heap_Sort_G (Move, Lt);

      procedure Move (From : Natural; To : Natural) is
      begin
         Leaks (To) := Leaks (From);
      end Move;

      function Lt (Op1, Op2 : Natural) return Boolean is
      begin
         if Nb_Alloc (Leaks (Op1)) > Nb_Alloc (Leaks (Op2)) then
            return True;
         elsif  Nb_Alloc (Leaks (Op1)) = Nb_Alloc (Leaks (Op2)) then
            return Alloc_Size (Leaks (Op1)) > Alloc_Size (Leaks (Op2));
         else
            return False;
         end if;
      end Lt;

   --  Start of processing for Print_Back_Traces

   begin
      --  Transfer all the relevant Roots in the Leaks and a
      --  Bogus_Deall arrays

      Tmp_Alloc.Root := Get_First;
      while Tmp_Alloc.Root /= No_Root_Id loop
         if Nb_Alloc (Tmp_Alloc.Root) = 0 then
            null;

         elsif Nb_Alloc (Tmp_Alloc.Root) < 0  then
            Deall_Index := Deall_Index + 1;
            Bogus_Dealls (Deall_Index) := Tmp_Alloc.Root;

         else
            Leak_Index := Leak_Index + 1;
            Leaks (Leak_Index) := Tmp_Alloc.Root;
         end if;

         Tmp_Alloc.Root := Get_Next;
      end loop;

      --  Print out wrong deallocations

      if Nb_Wrong_Deall > 0 then
         Put_Line    ("Releasing deallocated memory at :");
         if not Quiet_Mode then
            Put_Line ("--------------------------------");
         end if;

         for J in  1 .. Bogus_Dealls'Last loop
            Print_BT (Bogus_Dealls (J));
            New_Line;
         end loop;
      end if;

      --  Print out all allocation Leaks

      if Nb_Root > 0 then

         --  Sort the Leaks so that potentially important leaks appear first

         Root_Sort.Sort (Nb_Root);

         for J in  1 .. Leaks'Last loop
            if Quiet_Mode then
               if Nb_Alloc (Leaks (J)) = 1 then
                  Put_Line (Integer'Image (Nb_Alloc (Leaks (J)))
                    & " leak at :");
               else
                  Put_Line (Integer'Image (Nb_Alloc (Leaks (J)))
                    & " leaks at :");
               end if;
            else
               Put_Line ("Allocation Root #" & Integer'Image (J));
               Put_Line ("-------------------");

               Put      (" Number of non freed allocations    :");
               Ada.Integer_Text_IO.Put (Nb_Alloc (Leaks (J)), 4);
               New_Line;

               Put_Line (" Final Water Mark (non freed mem)   :"
                 & Mem_Image (Alloc_Size (Leaks (J))));

               Put_Line (" High Water Mark                    :"
                 & Mem_Image (High_Water_Mark (Leaks (J))));

               Put_Line (" Backtrace                          :");
            end if;
            Print_BT (Leaks (J));
            New_Line;
         end loop;
      end if;
   end Print_Back_Traces;

end Gnatmem;