1 ------------------------------------------------------------------------------
3 -- GNAT COMPILER COMPONENTS --
9 -- Copyright (C) 1997-2007, AdaCore --
11 -- GNAT is free software; you can redistribute it and/or modify it under --
12 -- terms of the GNU General Public License as published by the Free Soft- --
13 -- ware Foundation; either version 2, or (at your option) any later ver- --
14 -- sion. GNAT is distributed in the hope that it will be useful, but WITH- --
15 -- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
16 -- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License --
17 -- for more details. You should have received a copy of the GNU General --
18 -- Public License distributed with GNAT; see file COPYING. If not, write --
19 -- to the Free Software Foundation, 51 Franklin Street, Fifth Floor, --
20 -- Boston, MA 02110-1301, USA. --
22 -- GNAT was originally developed by the GNAT team at New York University. --
23 -- Extensive contributions were provided by Ada Core Technologies Inc. --
25 ------------------------------------------------------------------------------
27 -- GNATMEM is a utility that tracks memory leaks. It is based on a simple
30 -- - Read the allocation log generated by the application linked using
31 -- instrumented memory allocation and dealocation (see memtrack.adb for
32 -- this circuitry). To get access to this functionality, the application
33 -- must be relinked with library libgmem.a:
35 -- $ gnatmake my_prog -largs -lgmem
37 -- The running my_prog will produce a file named gmem.out that will be
40 -- - Record a reference to the allocated memory on each allocation call
42 -- - Suppress this reference on deallocation
44 -- - At the end of the program, remaining references are potential leaks.
45 -- sort them out the best possible way in order to locate the root of
48 -- This capability is not supported on all platforms, please refer to
49 -- memtrack.adb for further information.
51 -- In order to help finding out the real leaks, the notion of "allocation
52 -- root" is defined. An allocation root is a specific point in the program
53 -- execution generating memory allocation where data is collected (such as
54 -- number of allocations, amount of memory allocated, high water mark, etc.)
56 with Ada.Float_Text_IO;
57 with Ada.Integer_Text_IO;
58 with Ada.Text_IO; use Ada.Text_IO;
60 with System; use System;
61 with System.Storage_Elements; use System.Storage_Elements;
63 with GNAT.Command_Line; use GNAT.Command_Line;
64 with GNAT.Heap_Sort_G;
65 with GNAT.OS_Lib; use GNAT.OS_Lib;
66 with GNAT.HTable; use GNAT.HTable;
68 with Gnatvsn; use Gnatvsn;
69 with Memroot; use Memroot;
73 package Int_IO renames Ada.Integer_Text_IO;
75 ------------------------
76 -- Other Declarations --
77 ------------------------
79 type Storage_Elmt is record
81 -- * = End of log file
82 -- A = found a ALLOC mark in the log
83 -- D = found a DEALL mark in the log
85 Address : Integer_Address;
89 -- This type is used to read heap operations from the log file.
90 -- Elmt contains the type of the operation, which can be either
91 -- allocation, deallocation, or a special mark indicating the
92 -- end of the log file. Address is used to store address on the
93 -- heap where a chunk was allocated/deallocated, size is only
94 -- for A event and contains size of the allocation, and Timestamp
95 -- is the clock value at the moment of allocation
97 Log_Name : String_Access;
98 -- Holds the name of the heap operations log file
100 Program_Name : String_Access;
101 -- Holds the name of the user executable
103 function Read_Next return Storage_Elmt;
104 -- Reads next dynamic storage operation from the log file
106 function Mem_Image (X : Storage_Count) return String;
107 -- X is a size in storage_element. Returns a value
108 -- in Megabytes, Kilobytes or Bytes as appropriate.
110 procedure Process_Arguments;
111 -- Read command line arguments
114 -- Prints out the option help
116 function Gmem_Initialize (Dumpname : String) return Boolean;
117 -- Opens the file represented by Dumpname and prepares it for
118 -- work. Returns False if the file does not have the correct format, True
121 procedure Gmem_A2l_Initialize (Exename : String);
122 -- Initialises the convert_addresses interface by supplying it with
123 -- the name of the executable file Exename
125 -----------------------------------
126 -- HTable address --> Allocation --
127 -----------------------------------
129 type Allocation is record
131 Size : Storage_Count;
134 type Address_Range is range 0 .. 4097;
135 function H (A : Integer_Address) return Address_Range;
136 No_Alloc : constant Allocation := (No_Root_Id, 0);
138 package Address_HTable is new GNAT.HTable.Simple_HTable (
139 Header_Num => Address_Range,
140 Element => Allocation,
141 No_Element => No_Alloc,
142 Key => Integer_Address,
146 BT_Depth : Integer := 1;
148 -- Some global statistics
150 Global_Alloc_Size : Storage_Count := 0;
151 -- Total number of bytes allocated during the lifetime of a program
153 Global_High_Water_Mark : Storage_Count := 0;
154 -- Largest amount of storage ever in use during the lifetime
156 Global_Nb_Alloc : Integer := 0;
157 -- Total number of allocations
159 Global_Nb_Dealloc : Integer := 0;
160 -- Total number of deallocations
162 Nb_Root : Integer := 0;
163 -- Total number of allocation roots
165 Nb_Wrong_Deall : Integer := 0;
166 -- Total number of wrong deallocations (i.e. without matching alloc)
168 Minimum_Nb_Leaks : Integer := 1;
169 -- How many unfreed allocs should be in a root for it to count as leak
171 T0 : Duration := 0.0;
172 -- The moment at which memory allocation routines initialized (should
173 -- be pretty close to the moment the program started since there are
174 -- always some allocations at RTL elaboration
176 Tmp_Alloc : Allocation;
177 Dump_Log_Mode : Boolean := False;
178 Quiet_Mode : Boolean := False;
180 ------------------------------
181 -- Allocation Roots Sorting --
182 ------------------------------
184 Sort_Order : String (1 .. 3) := "nwh";
185 -- This is the default order in which sorting criteria will be applied
186 -- n - Total number of unfreed allocations
187 -- w - Final watermark
188 -- h - High watermark
190 --------------------------------
191 -- GMEM functionality binding --
192 --------------------------------
194 ---------------------
195 -- Gmem_Initialize --
196 ---------------------
198 function Gmem_Initialize (Dumpname : String) return Boolean is
199 function Initialize (Dumpname : System.Address) return Duration;
200 pragma Import (C, Initialize, "__gnat_gmem_initialize");
202 S : aliased String := Dumpname & ASCII.NUL;
205 T0 := Initialize (S'Address);
209 -------------------------
210 -- Gmem_A2l_Initialize --
211 -------------------------
213 procedure Gmem_A2l_Initialize (Exename : String) is
214 procedure A2l_Initialize (Exename : System.Address);
215 pragma Import (C, A2l_Initialize, "__gnat_gmem_a2l_initialize");
217 S : aliased String := Exename & ASCII.NUL;
220 A2l_Initialize (S'Address);
221 end Gmem_A2l_Initialize;
227 function Read_Next return Storage_Elmt is
228 procedure Read_Next (buf : System.Address);
229 pragma Import (C, Read_Next, "__gnat_gmem_read_next");
234 Read_Next (S'Address);
242 function H (A : Integer_Address) return Address_Range is
244 return Address_Range (A mod Integer_Address (Address_Range'Last));
251 function Mem_Image (X : Storage_Count) return String is
252 Ks : constant Storage_Count := X / 1024;
253 Megs : constant Storage_Count := Ks / 1024;
254 Buff : String (1 .. 7);
258 Ada.Float_Text_IO.Put (Buff, Float (X) / 1024.0 / 1024.0, 2, 0);
259 return Buff & " Megabytes";
262 Ada.Float_Text_IO.Put (Buff, Float (X) / 1024.0, 2, 0);
263 return Buff & " Kilobytes";
266 Ada.Integer_Text_IO.Put (Buff (1 .. 4), Integer (X));
267 return Buff (1 .. 4) & " Bytes";
279 Put_Line (Gnat_Version_String);
280 Put_Line ("Copyright 1997-2007, Free Software Foundation, Inc.");
283 Put_Line ("Usage: gnatmem switches [depth] exename");
285 Put_Line (" depth backtrace depth to take into account, default is"
286 & Integer'Image (BT_Depth));
287 Put_Line (" exename the name of the executable to be analyzed");
289 Put_Line ("Switches:");
290 Put_Line (" -b n same as depth parameter");
291 Put_Line (" -i file read the allocation log from specific file");
292 Put_Line (" default is gmem.out in the current directory");
293 Put_Line (" -m n masks roots with less than n leaks, default is 1");
294 Put_Line (" specify 0 to see even released allocation roots");
295 Put_Line (" -q quiet, minimum output");
296 Put_Line (" -s order sort allocation roots according to an order of");
297 Put_Line (" sort criteria");
298 GNAT.OS_Lib.OS_Exit (1);
301 -----------------------
302 -- Process_Arguments --
303 -----------------------
305 procedure Process_Arguments is
307 -- Parse the options first
310 case Getopt ("b: dd m: i: q s:") is
311 when ASCII.Nul => exit;
315 BT_Depth := Natural'Value (Parameter);
317 when Constraint_Error =>
322 Dump_Log_Mode := True;
326 Minimum_Nb_Leaks := Natural'Value (Parameter);
328 when Constraint_Error =>
333 Log_Name := new String'(Parameter);
340 S : constant String (Sort_Order'Range) := Parameter;
342 for J in Sort_Order'Range loop
343 if S (J) = 'n' or else
347 Sort_Order (J) := S (J);
349 Put_Line ("Invalid sort criteria string.");
350 GNAT.OS_Lib.OS_Exit (1);
360 -- Set default log file if -i hasn't been specified
362 if Log_Name = null then
363 Log_Name := new String'("gmem.out");
366 -- Get the optional backtrace length and program name
369 Str1 : constant String := GNAT.Command_Line.Get_Argument;
370 Str2 : constant String := GNAT.Command_Line.Get_Argument;
378 Program_Name := new String'(Str1);
380 BT_Depth := Natural'Value (Str1);
381 Program_Name := new String'(Str2);
385 when Constraint_Error =>
389 -- Ensure presence of executable suffix in Program_Name
392 Suffix : String_Access := Get_Executable_Suffix;
399 (Program_Name.all'Last - Suffix.all'Length + 1 ..
400 Program_Name.all'Last) /= Suffix.all
402 Tmp := new String'(Program_Name.all & Suffix.all);
409 -- Search the executable on the path. If not found in the PATH, we
410 -- default to the current directory. Otherwise, libaddr2line will
411 -- fail with an error:
413 -- (null): Bad address
415 Tmp := Locate_Exec_On_Path (Program_Name.all);
418 Tmp := new String'('.' & Directory_Separator & Program_Name.all);
425 if not Is_Regular_File (Log_Name.all) then
426 Put_Line ("Couldn't find " & Log_Name.all);
427 GNAT.OS_Lib.OS_Exit (1);
430 if not Gmem_Initialize (Log_Name.all) then
431 Put_Line ("File " & Log_Name.all & " is not a gnatmem log file");
432 GNAT.OS_Lib.OS_Exit (1);
435 if not Is_Regular_File (Program_Name.all) then
436 Put_Line ("Couldn't find " & Program_Name.all);
439 Gmem_A2l_Initialize (Program_Name.all);
442 when GNAT.Command_Line.Invalid_Switch =>
443 Ada.Text_IO.Put_Line ("Invalid switch : "
444 & GNAT.Command_Line.Full_Switch);
446 end Process_Arguments;
450 Cur_Elmt : Storage_Elmt;
451 Buff : String (1 .. 16);
453 -- Start of processing for Gnatmem
458 if Dump_Log_Mode then
459 Put_Line ("Full dump of dynamic memory operations history");
460 Put_Line ("----------------------------------------------");
463 function CTime (Clock : Address) return Address;
464 pragma Import (C, CTime, "ctime");
466 Int_T0 : Integer := Integer (T0);
467 CTime_Addr : constant Address := CTime (Int_T0'Address);
469 Buffer : String (1 .. 30);
470 for Buffer'Address use CTime_Addr;
473 Put_Line ("Log started at T0 =" & Duration'Image (T0) & " ("
474 & Buffer (1 .. 24) & ")");
478 -- Main loop analysing the data generated by the instrumented routines.
479 -- For each allocation, the backtrace is kept and stored in a htable
480 -- whose entry is the address. For each deallocation, we look for the
481 -- corresponding allocation and cancel it.
484 Cur_Elmt := Read_Next;
486 case Cur_Elmt.Elmt is
492 -- Read the corresponding back trace
494 Tmp_Alloc.Root := Read_BT (BT_Depth);
498 if Nb_Alloc (Tmp_Alloc.Root) = 0 then
499 Nb_Root := Nb_Root + 1;
502 Set_Nb_Alloc (Tmp_Alloc.Root, Nb_Alloc (Tmp_Alloc.Root) + 1);
503 Address_HTable.Set (Cur_Elmt.Address, Tmp_Alloc);
505 elsif Cur_Elmt.Size > 0 then
507 -- Update global counters if the allocated size is meaningful
509 Global_Alloc_Size := Global_Alloc_Size + Cur_Elmt.Size;
510 Global_Nb_Alloc := Global_Nb_Alloc + 1;
512 if Global_High_Water_Mark < Global_Alloc_Size then
513 Global_High_Water_Mark := Global_Alloc_Size;
516 -- Update the number of allocation root if this is a new one
518 if Nb_Alloc (Tmp_Alloc.Root) = 0 then
519 Nb_Root := Nb_Root + 1;
522 -- Update allocation root specific counters
524 Set_Alloc_Size (Tmp_Alloc.Root,
525 Alloc_Size (Tmp_Alloc.Root) + Cur_Elmt.Size);
527 Set_Nb_Alloc (Tmp_Alloc.Root, Nb_Alloc (Tmp_Alloc.Root) + 1);
529 if High_Water_Mark (Tmp_Alloc.Root) <
530 Alloc_Size (Tmp_Alloc.Root)
532 Set_High_Water_Mark (Tmp_Alloc.Root,
533 Alloc_Size (Tmp_Alloc.Root));
536 -- Associate this allocation root to the allocated address
538 Tmp_Alloc.Size := Cur_Elmt.Size;
539 Address_HTable.Set (Cur_Elmt.Address, Tmp_Alloc);
545 -- Get the corresponding Dealloc_Size and Root
547 Tmp_Alloc := Address_HTable.Get (Cur_Elmt.Address);
549 if Tmp_Alloc.Root = No_Root_Id then
551 -- There was no prior allocation at this address, something is
552 -- very wrong. Mark this allocation root as problematic.
554 Tmp_Alloc.Root := Read_BT (BT_Depth);
556 if Nb_Alloc (Tmp_Alloc.Root) = 0 then
557 Set_Nb_Alloc (Tmp_Alloc.Root, Nb_Alloc (Tmp_Alloc.Root) - 1);
558 Nb_Wrong_Deall := Nb_Wrong_Deall + 1;
562 -- Update global counters
564 if not Quiet_Mode then
565 Global_Alloc_Size := Global_Alloc_Size - Tmp_Alloc.Size;
568 Global_Nb_Dealloc := Global_Nb_Dealloc + 1;
570 -- Update allocation root specific counters
572 if not Quiet_Mode then
573 Set_Alloc_Size (Tmp_Alloc.Root,
574 Alloc_Size (Tmp_Alloc.Root) - Tmp_Alloc.Size);
577 Set_Nb_Alloc (Tmp_Alloc.Root, Nb_Alloc (Tmp_Alloc.Root) - 1);
579 -- Update the number of allocation root if this one disappears
581 if Nb_Alloc (Tmp_Alloc.Root) = 0
582 and then Minimum_Nb_Leaks > 0 then
583 Nb_Root := Nb_Root - 1;
586 -- Deassociate the deallocated address
588 Address_HTable.Remove (Cur_Elmt.Address);
595 if Dump_Log_Mode then
596 case Cur_Elmt.Elmt is
599 Int_IO.Put (Buff (1 .. 16), Integer (Cur_Elmt.Address), 16);
601 Int_IO.Put (Buff (1 .. 8), Integer (Cur_Elmt.Size));
602 Put (Buff (1 .. 8) & " bytes at moment T0 +");
603 Put_Line (Duration'Image (Cur_Elmt.Timestamp - T0));
607 Int_IO.Put (Buff (1 .. 16), Integer (Cur_Elmt.Address), 16);
609 Put_Line (" at moment T0 +"
610 & Duration'Image (Cur_Elmt.Timestamp - T0));
615 Print_BT (Tmp_Alloc.Root);
620 -- Print out general information about overall allocation
622 if not Quiet_Mode then
623 Put_Line ("Global information");
624 Put_Line ("------------------");
626 Put (" Total number of allocations :");
627 Ada.Integer_Text_IO.Put (Global_Nb_Alloc, 4);
630 Put (" Total number of deallocations :");
631 Ada.Integer_Text_IO.Put (Global_Nb_Dealloc, 4);
634 Put_Line (" Final Water Mark (non freed mem) :"
635 & Mem_Image (Global_Alloc_Size));
636 Put_Line (" High Water Mark :"
637 & Mem_Image (Global_High_Water_Mark));
641 -- Print out the back traces corresponding to potential leaks in order
642 -- greatest number of non-deallocated allocations.
644 Print_Back_Traces : declare
645 type Root_Array is array (Natural range <>) of Root_Id;
646 type Access_Root_Array is access Root_Array;
648 Leaks : constant Access_Root_Array :=
649 new Root_Array (0 .. Nb_Root);
650 Leak_Index : Natural := 0;
652 Bogus_Dealls : constant Access_Root_Array :=
653 new Root_Array (1 .. Nb_Wrong_Deall);
654 Deall_Index : Natural := 0;
655 Nb_Alloc_J : Natural := 0;
657 procedure Move (From : Natural; To : Natural);
658 function Lt (Op1, Op2 : Natural) return Boolean;
659 package Root_Sort is new GNAT.Heap_Sort_G (Move, Lt);
665 procedure Move (From : Natural; To : Natural) is
667 Leaks (To) := Leaks (From);
674 function Lt (Op1, Op2 : Natural) return Boolean is
676 function Apply_Sort_Criterion (S : Character) return Integer;
677 -- Applies a specific sort criterion; returns -1, 0 or 1 if Op1 is
678 -- smaller than, equal, or greater than Op2 according to criterion.
680 --------------------------
681 -- Apply_Sort_Criterion --
682 --------------------------
684 function Apply_Sort_Criterion (S : Character) return Integer is
685 LOp1, LOp2 : Integer;
690 LOp1 := Nb_Alloc (Leaks (Op1));
691 LOp2 := Nb_Alloc (Leaks (Op2));
694 LOp1 := Integer (Alloc_Size (Leaks (Op1)));
695 LOp2 := Integer (Alloc_Size (Leaks (Op2)));
698 LOp1 := Integer (High_Water_Mark (Leaks (Op1)));
699 LOp2 := Integer (High_Water_Mark (Leaks (Op2)));
702 return 0; -- Can't actually happen
707 elsif LOp1 > LOp2 then
714 when Constraint_Error =>
716 end Apply_Sort_Criterion;
722 -- Start of processing for Lt
725 for S in Sort_Order'Range loop
726 Result := Apply_Sort_Criterion (Sort_Order (S));
729 elsif Result = 1 then
736 -- Start of processing for Print_Back_Traces
739 -- Transfer all the relevant Roots in the Leaks and a Bogus_Deall arrays
741 Tmp_Alloc.Root := Get_First;
742 while Tmp_Alloc.Root /= No_Root_Id loop
743 if Nb_Alloc (Tmp_Alloc.Root) = 0 and then Minimum_Nb_Leaks > 0 then
746 elsif Nb_Alloc (Tmp_Alloc.Root) < 0 then
747 Deall_Index := Deall_Index + 1;
748 Bogus_Dealls (Deall_Index) := Tmp_Alloc.Root;
751 Leak_Index := Leak_Index + 1;
752 Leaks (Leak_Index) := Tmp_Alloc.Root;
755 Tmp_Alloc.Root := Get_Next;
758 -- Print out wrong deallocations
760 if Nb_Wrong_Deall > 0 then
761 Put_Line ("Releasing deallocated memory at :");
762 if not Quiet_Mode then
763 Put_Line ("--------------------------------");
766 for J in 1 .. Bogus_Dealls'Last loop
767 Print_BT (Bogus_Dealls (J), Short => Quiet_Mode);
772 -- Print out all allocation Leaks
774 if Leak_Index > 0 then
776 -- Sort the Leaks so that potentially important leaks appear first
778 Root_Sort.Sort (Leak_Index);
780 for J in 1 .. Leak_Index loop
781 Nb_Alloc_J := Nb_Alloc (Leaks (J));
783 if Nb_Alloc_J >= Minimum_Nb_Leaks then
785 if Nb_Alloc_J = 1 then
786 Put_Line (" 1 leak at :");
788 Put_Line (Integer'Image (Nb_Alloc_J) & " leaks at :");
792 Put_Line ("Allocation Root #" & Integer'Image (J));
793 Put_Line ("-------------------");
795 Put (" Number of non freed allocations :");
796 Ada.Integer_Text_IO.Put (Nb_Alloc_J, 4);
800 (" Final Water Mark (non freed mem) :"
801 & Mem_Image (Alloc_Size (Leaks (J))));
804 (" High Water Mark :"
805 & Mem_Image (High_Water_Mark (Leaks (J))));
807 Put_Line (" Backtrace :");
810 Print_BT (Leaks (J), Short => Quiet_Mode);
815 end Print_Back_Traces;