2003-12-26 Guilhem Lavaux <guilhem@kaffe.org>
[official-gcc.git] / gcc / ada / gnatmem.adb
blob8deca2e1873f4484355dc2b68d667d112666d1a0
1 ------------------------------------------------------------------------------
2 -- --
3 -- GNAT COMPILER COMPONENTS --
4 -- --
5 -- G N A T M E M --
6 -- --
7 -- B o d y --
8 -- --
9 -- Copyright (C) 1997-2003, Ada Core Technologies, Inc. --
10 -- --
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, 59 Temple Place - Suite 330, Boston, --
20 -- MA 02111-1307, USA. --
21 -- --
22 -- GNAT was originally developed by the GNAT team at New York University. --
23 -- Extensive contributions were provided by Ada Core Technologies Inc. --
24 -- --
25 ------------------------------------------------------------------------------
27 -- GNATMEM is a utility that tracks memory leaks. It is based on a simple
28 -- idea:
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
38 -- parsed by gnatmem.
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
46 -- the leak.
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 GNAT.Command_Line; use GNAT.Command_Line;
57 with Ada.Text_IO; use Ada.Text_IO;
58 with Ada.Float_Text_IO;
59 with Ada.Integer_Text_IO;
60 with Gnatvsn; use Gnatvsn;
61 with GNAT.Heap_Sort_G;
62 with GNAT.OS_Lib; use GNAT.OS_Lib;
63 with GNAT.HTable; use GNAT.HTable;
64 with System; use System;
65 with System.Storage_Elements; use System.Storage_Elements;
67 with Memroot; use Memroot;
69 procedure Gnatmem is
71 ------------------------
72 -- Other Declarations --
73 ------------------------
75 type Storage_Elmt is record
76 Elmt : Character;
77 -- * = End of log file
78 -- A = found a ALLOC mark in the log
79 -- D = found a DEALL mark in the log
80 Address : Integer_Address;
81 Size : Storage_Count;
82 end record;
83 -- This needs a comment ???
85 Log_Name, Program_Name : String_Access;
86 -- These need comments, and should be on separate lines ???
88 function Read_Next return Storage_Elmt;
89 -- Reads next dynamic storage operation from the log file.
91 function Mem_Image (X : Storage_Count) return String;
92 -- X is a size in storage_element. Returns a value
93 -- in Megabytes, Kilobytes or Bytes as appropriate.
95 procedure Process_Arguments;
96 -- Read command line arguments
98 procedure Usage;
99 -- Prints out the option help
101 function Gmem_Initialize (Dumpname : String) return Boolean;
102 -- Opens the file represented by Dumpname and prepares it for
103 -- work. Returns False if the file does not have the correct format, True
104 -- otherwise.
106 procedure Gmem_A2l_Initialize (Exename : String);
107 -- Initialises the convert_addresses interface by supplying it with
108 -- the name of the executable file Exename
110 -----------------------------------
111 -- HTable address --> Allocation --
112 -----------------------------------
114 type Allocation is record
115 Root : Root_Id;
116 Size : Storage_Count;
117 end record;
119 type Address_Range is range 0 .. 4097;
120 function H (A : Integer_Address) return Address_Range;
121 No_Alloc : constant Allocation := (No_Root_Id, 0);
123 package Address_HTable is new GNAT.HTable.Simple_HTable (
124 Header_Num => Address_Range,
125 Element => Allocation,
126 No_Element => No_Alloc,
127 Key => Integer_Address,
128 Hash => H,
129 Equal => "=");
131 BT_Depth : Integer := 1;
133 -- The following need comments ???
135 Global_Alloc_Size : Storage_Count := 0;
136 Global_High_Water_Mark : Storage_Count := 0;
137 Global_Nb_Alloc : Integer := 0;
138 Global_Nb_Dealloc : Integer := 0;
139 Nb_Root : Integer := 0;
140 Nb_Wrong_Deall : Integer := 0;
141 Minimum_NB_Leaks : Integer := 1;
143 Tmp_Alloc : Allocation;
144 Quiet_Mode : Boolean := False;
146 -------------------------------
147 -- Allocation roots sorting --
148 -------------------------------
150 Sort_Order : String (1 .. 3) := "nwh";
151 -- This is the default order in which sorting criteria will be applied
152 -- n - Total number of unfreed allocations
153 -- w - Final watermark
154 -- h - High watermark
156 --------------------------------
157 -- GMEM functionality binding --
158 --------------------------------
160 function Gmem_Initialize (Dumpname : String) return Boolean is
161 function Initialize (Dumpname : System.Address) return Boolean;
162 pragma Import (C, Initialize, "__gnat_gmem_initialize");
164 S : aliased String := Dumpname & ASCII.NUL;
166 begin
167 return Initialize (S'Address);
168 end Gmem_Initialize;
170 procedure Gmem_A2l_Initialize (Exename : String) is
171 procedure A2l_Initialize (Exename : System.Address);
172 pragma Import (C, A2l_Initialize, "__gnat_gmem_a2l_initialize");
174 S : aliased String := Exename & ASCII.NUL;
176 begin
177 A2l_Initialize (S'Address);
178 end Gmem_A2l_Initialize;
180 function Read_Next return Storage_Elmt is
181 procedure Read_Next (buf : System.Address);
182 pragma Import (C, Read_Next, "__gnat_gmem_read_next");
184 S : Storage_Elmt;
186 begin
187 Read_Next (S'Address);
188 return S;
189 end Read_Next;
191 -------
192 -- H --
193 -------
195 function H (A : Integer_Address) return Address_Range is
196 begin
197 return Address_Range (A mod Integer_Address (Address_Range'Last));
198 end H;
200 ---------------
201 -- Mem_Image --
202 ---------------
204 function Mem_Image (X : Storage_Count) return String is
205 Ks : constant Storage_Count := X / 1024;
206 Megs : constant Storage_Count := Ks / 1024;
207 Buff : String (1 .. 7);
209 begin
210 if Megs /= 0 then
211 Ada.Float_Text_IO.Put (Buff, Float (X) / 1024.0 / 1024.0, 2, 0);
212 return Buff & " Megabytes";
214 elsif Ks /= 0 then
215 Ada.Float_Text_IO.Put (Buff, Float (X) / 1024.0, 2, 0);
216 return Buff & " Kilobytes";
218 else
219 Ada.Integer_Text_IO.Put (Buff (1 .. 4), Integer (X));
220 return Buff (1 .. 4) & " Bytes";
221 end if;
222 end Mem_Image;
224 -----------
225 -- Usage --
226 -----------
228 procedure Usage is
229 begin
230 New_Line;
231 Put ("GNATMEM ");
232 Put (Gnat_Version_String);
233 Put_Line (" Copyright 1997-2003 Free Software Foundation, Inc.");
234 New_Line;
236 Put_Line ("Usage: gnatmem switches [depth] exename");
237 New_Line;
238 Put_Line (" depth backtrace depth to take into account, default is"
239 & Integer'Image (BT_Depth));
240 Put_Line (" exename the name of the executable to be analyzed");
241 New_Line;
242 Put_Line ("Switches:");
243 Put_Line (" -b n same as depth parameter");
244 Put_Line (" -i file read the allocation log from specific file");
245 Put_Line (" default is gmem.out in the current directory");
246 Put_Line (" -m n masks roots with less than n leaks, default is 1");
247 Put_Line (" specify 0 to see even released allocation roots");
248 Put_Line (" -q quiet, minimum output");
249 Put_Line (" -s order sort allocation roots according to an order of");
250 Put_Line (" sort criteria");
251 GNAT.OS_Lib.OS_Exit (1);
252 end Usage;
254 -----------------------
255 -- Process_Arguments --
256 -----------------------
258 procedure Process_Arguments is
259 begin
260 -- Parse the options first
262 loop
263 case Getopt ("b: m: i: q s:") is
264 when ASCII.Nul => exit;
266 when 'b' =>
267 begin
268 BT_Depth := Natural'Value (Parameter);
269 exception
270 when Constraint_Error =>
271 Usage;
272 end;
274 when 'm' =>
275 begin
276 Minimum_NB_Leaks := Natural'Value (Parameter);
277 exception
278 when Constraint_Error =>
279 Usage;
280 end;
282 when 'i' =>
283 Log_Name := new String'(Parameter);
285 when 'q' =>
286 Quiet_Mode := True;
288 when 's' =>
289 declare
290 S : String (Sort_Order'Range) := Parameter;
291 begin
292 for J in Sort_Order'Range loop
293 if S (J) = 'n' or else S (J) = 'w'
294 or else S (J) = 'h' then
295 Sort_Order (J) := S (J);
296 else
297 raise Constraint_Error;
298 end if;
299 end loop;
300 exception
301 when Constraint_Error =>
302 Put_Line ("Invalid sort criteria string.");
303 GNAT.OS_Lib.OS_Exit (1);
304 end;
306 when others =>
307 null;
308 end case;
309 end loop;
311 -- Set default log file if -i hasn't been specified
313 if Log_Name = null then
314 Log_Name := new String'("gmem.out");
315 end if;
317 -- Get the optional backtrace length and program name
319 declare
320 Str1 : constant String := GNAT.Command_Line.Get_Argument;
321 Str2 : constant String := GNAT.Command_Line.Get_Argument;
323 begin
324 if Str1 = "" then
325 Usage;
326 end if;
328 if Str2 = "" then
329 Program_Name := new String'(Str1);
330 else
331 BT_Depth := Natural'Value (Str1);
332 Program_Name := new String'(Str2);
333 end if;
335 exception
336 when Constraint_Error =>
337 Usage;
338 end;
340 -- Ensure presence of executable suffix in Program_Name
342 declare
343 Suffix : String_Access := Get_Executable_Suffix;
344 Tmp : String_Access;
346 begin
347 if Suffix.all /= ""
348 and then
349 Program_Name.all
350 (Program_Name.all'Last - Suffix.all'Length + 1 ..
351 Program_Name.all'Last) /= Suffix.all
352 then
353 Tmp := new String'(Program_Name.all & Suffix.all);
354 Free (Program_Name);
355 Program_Name := Tmp;
356 end if;
358 Free (Suffix);
360 -- Search the executable on the path. If not found in the PATH, we
361 -- default to the current directory. Otherwise, libaddr2line will
362 -- fail with an error:
364 -- (null): Bad address
366 Tmp := Locate_Exec_On_Path (Program_Name.all);
368 if Tmp = null then
369 Tmp := new String'('.' & Directory_Separator & Program_Name.all);
370 end if;
372 Free (Program_Name);
373 Program_Name := Tmp;
374 end;
376 if not Is_Regular_File (Log_Name.all) then
377 Put_Line ("Couldn't find " & Log_Name.all);
378 GNAT.OS_Lib.OS_Exit (1);
379 end if;
381 if not Gmem_Initialize (Log_Name.all) then
382 Put_Line ("File " & Log_Name.all & " is not a gnatmem log file");
383 GNAT.OS_Lib.OS_Exit (1);
384 end if;
386 if not Is_Regular_File (Program_Name.all) then
387 Put_Line ("Couldn't find " & Program_Name.all);
388 end if;
390 Gmem_A2l_Initialize (Program_Name.all);
392 exception
393 when GNAT.Command_Line.Invalid_Switch =>
394 Ada.Text_IO.Put_Line ("Invalid switch : "
395 & GNAT.Command_Line.Full_Switch);
396 Usage;
397 end Process_Arguments;
399 Cur_Elmt : Storage_Elmt;
401 -- Start of processing for Gnatmem
403 begin
404 Process_Arguments;
406 -- Main loop analysing the data generated by the instrumented routines.
407 -- For each allocation, the backtrace is kept and stored in a htable
408 -- whose entry is the address. For each deallocation, we look for the
409 -- corresponding allocation and cancel it.
411 Main : loop
412 Cur_Elmt := Read_Next;
414 case Cur_Elmt.Elmt is
415 when '*' =>
416 exit Main;
418 when 'A' =>
420 -- Update global counters if the allocated size is meaningful
422 if Quiet_Mode then
423 Tmp_Alloc.Root := Read_BT (BT_Depth);
425 if Nb_Alloc (Tmp_Alloc.Root) = 0 then
426 Nb_Root := Nb_Root + 1;
427 end if;
429 Set_Nb_Alloc (Tmp_Alloc.Root, Nb_Alloc (Tmp_Alloc.Root) + 1);
430 Address_HTable.Set (Cur_Elmt.Address, Tmp_Alloc);
432 elsif Cur_Elmt.Size > 0 then
434 Global_Alloc_Size := Global_Alloc_Size + Cur_Elmt.Size;
435 Global_Nb_Alloc := Global_Nb_Alloc + 1;
437 if Global_High_Water_Mark < Global_Alloc_Size then
438 Global_High_Water_Mark := Global_Alloc_Size;
439 end if;
441 -- Read the corresponding back trace
443 Tmp_Alloc.Root := Read_BT (BT_Depth);
445 -- Update the number of allocation root if this is a new one
447 if Nb_Alloc (Tmp_Alloc.Root) = 0 then
448 Nb_Root := Nb_Root + 1;
449 end if;
451 -- Update allocation root specific counters
453 Set_Alloc_Size (Tmp_Alloc.Root,
454 Alloc_Size (Tmp_Alloc.Root) + Cur_Elmt.Size);
456 Set_Nb_Alloc (Tmp_Alloc.Root, Nb_Alloc (Tmp_Alloc.Root) + 1);
458 if High_Water_Mark (Tmp_Alloc.Root) <
459 Alloc_Size (Tmp_Alloc.Root)
460 then
461 Set_High_Water_Mark (Tmp_Alloc.Root,
462 Alloc_Size (Tmp_Alloc.Root));
463 end if;
465 -- Associate this allocation root to the allocated address
467 Tmp_Alloc.Size := Cur_Elmt.Size;
468 Address_HTable.Set (Cur_Elmt.Address, Tmp_Alloc);
470 -- non meaningful output, just consumes the backtrace
472 else
473 Tmp_Alloc.Root := Read_BT (BT_Depth);
474 end if;
476 when 'D' =>
478 -- Get the corresponding Dealloc_Size and Root
480 Tmp_Alloc := Address_HTable.Get (Cur_Elmt.Address);
482 if Tmp_Alloc.Root = No_Root_Id then
484 -- There was no prior allocation at this address, something is
485 -- very wrong. Mark this allocation root as problematic
487 Tmp_Alloc.Root := Read_BT (BT_Depth);
489 if Nb_Alloc (Tmp_Alloc.Root) = 0 then
490 Set_Nb_Alloc (Tmp_Alloc.Root, Nb_Alloc (Tmp_Alloc.Root) - 1);
491 Nb_Wrong_Deall := Nb_Wrong_Deall + 1;
492 end if;
494 else
495 -- Update global counters
497 if not Quiet_Mode then
498 Global_Alloc_Size := Global_Alloc_Size - Tmp_Alloc.Size;
499 end if;
501 Global_Nb_Dealloc := Global_Nb_Dealloc + 1;
503 -- Update allocation root specific counters
505 if not Quiet_Mode then
506 Set_Alloc_Size (Tmp_Alloc.Root,
507 Alloc_Size (Tmp_Alloc.Root) - Tmp_Alloc.Size);
508 end if;
510 Set_Nb_Alloc (Tmp_Alloc.Root, Nb_Alloc (Tmp_Alloc.Root) - 1);
512 -- update the number of allocation root if this one disappear
514 if Nb_Alloc (Tmp_Alloc.Root) = 0
515 and then Minimum_NB_Leaks > 0 then
516 Nb_Root := Nb_Root - 1;
517 end if;
519 -- De-associate the deallocated address
521 Address_HTable.Remove (Cur_Elmt.Address);
522 end if;
524 when others =>
525 raise Program_Error;
526 end case;
527 end loop Main;
529 -- Print out general information about overall allocation
531 if not Quiet_Mode then
532 Put_Line ("Global information");
533 Put_Line ("------------------");
535 Put (" Total number of allocations :");
536 Ada.Integer_Text_IO.Put (Global_Nb_Alloc, 4);
537 New_Line;
539 Put (" Total number of deallocations :");
540 Ada.Integer_Text_IO.Put (Global_Nb_Dealloc, 4);
541 New_Line;
543 Put_Line (" Final Water Mark (non freed mem) :"
544 & Mem_Image (Global_Alloc_Size));
545 Put_Line (" High Water Mark :"
546 & Mem_Image (Global_High_Water_Mark));
547 New_Line;
548 end if;
550 -- Print out the back traces corresponding to potential leaks in order
551 -- greatest number of non-deallocated allocations
553 Print_Back_Traces : declare
554 type Root_Array is array (Natural range <>) of Root_Id;
555 Leaks : Root_Array (0 .. Nb_Root);
556 Leak_Index : Natural := 0;
558 Bogus_Dealls : Root_Array (1 .. Nb_Wrong_Deall);
559 Deall_Index : Natural := 0;
560 Nb_Alloc_J : Natural := 0;
562 procedure Move (From : Natural; To : Natural);
563 function Lt (Op1, Op2 : Natural) return Boolean;
564 package Root_Sort is new GNAT.Heap_Sort_G (Move, Lt);
566 procedure Move (From : Natural; To : Natural) is
567 begin
568 Leaks (To) := Leaks (From);
569 end Move;
571 function Lt (Op1, Op2 : Natural) return Boolean is
572 function Apply_Sort_Criterion (S : Character) return Integer;
573 -- Applies a specific sort criterion; returns -1, 0 or 1 if Op1 is
574 -- smaller than, equal, or greater than Op2 according to criterion
576 function Apply_Sort_Criterion (S : Character) return Integer is
577 LOp1, LOp2 : Integer;
578 begin
579 case S is
580 when 'n' =>
581 LOp1 := Nb_Alloc (Leaks (Op1));
582 LOp2 := Nb_Alloc (Leaks (Op2));
584 when 'w' =>
585 LOp1 := Integer (Alloc_Size (Leaks (Op1)));
586 LOp2 := Integer (Alloc_Size (Leaks (Op2)));
588 when 'h' =>
589 LOp1 := Integer (High_Water_Mark (Leaks (Op1)));
590 LOp2 := Integer (High_Water_Mark (Leaks (Op2)));
592 when others =>
593 return 0; -- Can't actually happen
594 end case;
596 if LOp1 < LOp2 then
597 return -1;
598 elsif LOp1 > LOp2 then
599 return 1;
600 else
601 return 0;
602 end if;
603 exception
604 when Constraint_Error =>
605 return 0;
606 end Apply_Sort_Criterion;
608 Result : Integer;
610 begin
611 for S in Sort_Order'Range loop
612 Result := Apply_Sort_Criterion (Sort_Order (S));
613 if Result = -1 then
614 return False;
615 elsif Result = 1 then
616 return True;
617 end if;
618 end loop;
619 return False;
620 end Lt;
622 -- Start of processing for Print_Back_Traces
624 begin
625 -- Transfer all the relevant Roots in the Leaks and a
626 -- Bogus_Deall arrays
628 Tmp_Alloc.Root := Get_First;
629 while Tmp_Alloc.Root /= No_Root_Id loop
630 if Nb_Alloc (Tmp_Alloc.Root) = 0 and then Minimum_NB_Leaks > 0 then
631 null;
633 elsif Nb_Alloc (Tmp_Alloc.Root) < 0 then
634 Deall_Index := Deall_Index + 1;
635 Bogus_Dealls (Deall_Index) := Tmp_Alloc.Root;
637 else
638 Leak_Index := Leak_Index + 1;
639 Leaks (Leak_Index) := Tmp_Alloc.Root;
640 end if;
642 Tmp_Alloc.Root := Get_Next;
643 end loop;
645 -- Print out wrong deallocations
647 if Nb_Wrong_Deall > 0 then
648 Put_Line ("Releasing deallocated memory at :");
649 if not Quiet_Mode then
650 Put_Line ("--------------------------------");
651 end if;
653 for J in 1 .. Bogus_Dealls'Last loop
654 Print_BT (Bogus_Dealls (J), Short => Quiet_Mode);
655 New_Line;
656 end loop;
657 end if;
659 -- Print out all allocation Leaks
661 if Nb_Root > 0 then
663 -- Sort the Leaks so that potentially important leaks appear first
665 Root_Sort.Sort (Nb_Root);
667 for J in 1 .. Leaks'Last loop
668 Nb_Alloc_J := Nb_Alloc (Leaks (J));
669 if Nb_Alloc_J >= Minimum_NB_Leaks then
670 if Quiet_Mode then
671 if Nb_Alloc_J = 1 then
672 Put_Line (" 1 leak at :");
673 else
674 Put_Line (Integer'Image (Nb_Alloc_J) & " leaks at :");
675 end if;
677 else
678 Put_Line ("Allocation Root #" & Integer'Image (J));
679 Put_Line ("-------------------");
681 Put (" Number of non freed allocations :");
682 Ada.Integer_Text_IO.Put (Nb_Alloc_J, 4);
683 New_Line;
685 Put_Line
686 (" Final Water Mark (non freed mem) :"
687 & Mem_Image (Alloc_Size (Leaks (J))));
689 Put_Line
690 (" High Water Mark :"
691 & Mem_Image (High_Water_Mark (Leaks (J))));
693 Put_Line (" Backtrace :");
694 end if;
696 Print_BT (Leaks (J), Short => Quiet_Mode);
697 New_Line;
698 end if;
699 end loop;
700 end if;
701 end Print_Back_Traces;
702 end Gnatmem;