1------------------------------------------------------------------------------
2--                                                                          --
3--                         GNAT COMPILER COMPONENTS                         --
4--                                                                          --
5--                       G N A T . D E B U G _ P O O L S                    --
6--                                                                          --
7--                                 B o d y                                  --
8--                                                                          --
9--          Copyright (C) 1992-2018, Free Software Foundation, 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 3,  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.                                     --
17--                                                                          --
18-- As a special exception under Section 7 of GPL version 3, you are granted --
19-- additional permissions described in the GCC Runtime Library Exception,   --
20-- version 3.1, as published by the Free Software Foundation.               --
21--                                                                          --
22-- You should have received a copy of the GNU General Public License and    --
23-- a copy of the GCC Runtime Library Exception along with this program;     --
24-- see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see    --
25-- <http://www.gnu.org/licenses/>.                                          --
26--                                                                          --
27-- GNAT was originally developed  by the GNAT team at  New York University. --
28-- Extensive contributions were provided by Ada Core Technologies Inc.      --
29--                                                                          --
30------------------------------------------------------------------------------
31
32with GNAT.IO; use GNAT.IO;
33
34with System.CRTL;
35with System.Memory;     use System.Memory;
36with System.Soft_Links; use System.Soft_Links;
37
38with System.Traceback_Entries;
39
40with GNAT.Debug_Utilities; use GNAT.Debug_Utilities;
41with GNAT.HTable;
42with GNAT.Traceback; use GNAT.Traceback;
43
44with Ada.Finalization;
45with Ada.Unchecked_Conversion;
46
47package body GNAT.Debug_Pools is
48
49   Storage_Alignment : constant := Standard'Maximum_Alignment;
50   --  Alignment enforced for all the memory chunks returned by Allocate,
51   --  maximized to make sure that it will be compatible with all types.
52   --
53   --  The addresses returned by the underlying low-level allocator (be it
54   --  'new' or a straight 'malloc') aren't guaranteed to be that much aligned
55   --  on some targets, so we manage the needed alignment padding ourselves
56   --  systematically. Use of a common value for every allocation allows
57   --  significant simplifications in the code, nevertheless, for improved
58   --  robustness and efficiency overall.
59
60   --  We combine a few internal devices to offer the pool services:
61   --
62   --  * A management header attached to each allocated memory block, located
63   --    right ahead of it, like so:
64   --
65   --        Storage Address returned by the pool,
66   --        aligned on Storage_Alignment
67   --                       v
68   --      +------+--------+---------------------
69   --      | ~~~~ | HEADER | USER DATA ... |
70   --      +------+--------+---------------------
71   --       <---->
72   --       alignment
73   --       padding
74   --
75   --    The alignment padding is required
76   --
77   --  * A validity bitmap, which holds a validity bit for blocks managed by
78   --    the pool. Enforcing Storage_Alignment on those blocks allows efficient
79   --    validity management.
80   --
81   --  * A list of currently used blocks.
82
83   Max_Ignored_Levels : constant Natural := 10;
84   --  Maximum number of levels that will be ignored in backtraces. This is so
85   --  that we still have enough significant levels in the tracebacks returned
86   --  to the user.
87   --
88   --  The value 10 is chosen as being greater than the maximum callgraph
89   --  in this package. Its actual value is not really relevant, as long as it
90   --  is high enough to make sure we still have enough frames to return to
91   --  the user after we have hidden the frames internal to this package.
92
93   Disable : Boolean := False;
94   --  This variable is used to avoid infinite loops, where this package would
95   --  itself allocate memory and then call itself recursively, forever. Useful
96   --  when System_Memory_Debug_Pool_Enabled is True.
97
98   System_Memory_Debug_Pool_Enabled : Boolean := False;
99   --  If True, System.Memory allocation uses Debug_Pool
100
101   Allow_Unhandled_Memory : Boolean := False;
102   --  If True, protects Deallocate against releasing memory allocated before
103   --  System_Memory_Debug_Pool_Enabled was set.
104
105   Traceback_Count : Byte_Count := 0;
106   --  Total number of traceback elements
107
108   ---------------------------
109   -- Back Trace Hash Table --
110   ---------------------------
111
112   --  This package needs to store one set of tracebacks for each allocation
113   --  point (when was it allocated or deallocated). This would use too much
114   --  memory,  so the tracebacks are actually stored in a hash table, and
115   --  we reference elements in this hash table instead.
116
117   --  This hash-table will remain empty if the discriminant Stack_Trace_Depth
118   --  for the pools is set to 0.
119
120   --  This table is a global table, that can be shared among all debug pools
121   --  with no problems.
122
123   type Header is range 1 .. 1023;
124   --  Number of elements in the hash-table
125
126   type Tracebacks_Array_Access is access Tracebacks_Array;
127
128   type Traceback_Kind is (Alloc, Dealloc, Indirect_Alloc, Indirect_Dealloc);
129
130   type Traceback_Htable_Elem;
131   type Traceback_Htable_Elem_Ptr
132      is access Traceback_Htable_Elem;
133
134   type Traceback_Htable_Elem is record
135      Traceback   : Tracebacks_Array_Access;
136      Kind        : Traceback_Kind;
137      Count       : Natural;
138      --  Size of the memory allocated/freed at Traceback since last Reset call
139
140      Total       : Byte_Count;
141      --  Number of chunk of memory allocated/freed at Traceback since last
142      --  Reset call.
143
144      Frees       : Natural;
145      --  Number of chunk of memory allocated at Traceback, currently freed
146      --  since last Reset call. (only for Alloc & Indirect_Alloc elements)
147
148      Total_Frees : Byte_Count;
149      --  Size of the memory allocated at Traceback, currently freed since last
150      --  Reset call. (only for Alloc & Indirect_Alloc elements)
151
152      Next        : Traceback_Htable_Elem_Ptr;
153   end record;
154
155   --  Subprograms used for the Backtrace_Htable instantiation
156
157   procedure Set_Next
158     (E    : Traceback_Htable_Elem_Ptr;
159      Next : Traceback_Htable_Elem_Ptr);
160   pragma Inline (Set_Next);
161
162   function Next
163     (E : Traceback_Htable_Elem_Ptr) return Traceback_Htable_Elem_Ptr;
164   pragma Inline (Next);
165
166   function Get_Key
167     (E : Traceback_Htable_Elem_Ptr) return Tracebacks_Array_Access;
168   pragma Inline (Get_Key);
169
170   function Hash (T : Tracebacks_Array_Access) return Header;
171   pragma Inline (Hash);
172
173   function Equal (K1, K2 : Tracebacks_Array_Access) return Boolean;
174   --  Why is this not inlined???
175
176   --  The hash table for back traces
177
178   package Backtrace_Htable is new GNAT.HTable.Static_HTable
179     (Header_Num => Header,
180      Element    => Traceback_Htable_Elem,
181      Elmt_Ptr   => Traceback_Htable_Elem_Ptr,
182      Null_Ptr   => null,
183      Set_Next   => Set_Next,
184      Next       => Next,
185      Key        => Tracebacks_Array_Access,
186      Get_Key    => Get_Key,
187      Hash       => Hash,
188      Equal      => Equal);
189
190   -----------------------
191   -- Allocations table --
192   -----------------------
193
194   type Allocation_Header;
195   type Allocation_Header_Access is access Allocation_Header;
196
197   type Traceback_Ptr_Or_Address is new System.Address;
198   --  A type that acts as a C union, and is either a System.Address or a
199   --  Traceback_Htable_Elem_Ptr.
200
201   --  The following record stores extra information that needs to be
202   --  memorized for each block allocated with the special debug pool.
203
204   type Allocation_Header is record
205      Allocation_Address : System.Address;
206      --  Address of the block returned by malloc, possibly unaligned
207
208      Block_Size : Storage_Offset;
209      --  Needed only for advanced freeing algorithms (traverse all allocated
210      --  blocks for potential references). This value is negated when the
211      --  chunk of memory has been logically freed by the application. This
212      --  chunk has not been physically released yet.
213
214      Alloc_Traceback : Traceback_Htable_Elem_Ptr;
215      --  ??? comment required
216
217      Dealloc_Traceback : Traceback_Ptr_Or_Address;
218      --  Pointer to the traceback for the allocation (if the memory chunk is
219      --  still valid), or to the first deallocation otherwise. Make sure this
220      --  is a thin pointer to save space.
221      --
222      --  Dealloc_Traceback is also for blocks that are still allocated to
223      --  point to the previous block in the list. This saves space in this
224      --  header, and make manipulation of the lists of allocated pointers
225      --  faster.
226
227      Next : System.Address;
228      --  Point to the next block of the same type (either allocated or
229      --  logically freed) in memory. This points to the beginning of the user
230      --  data, and does not include the header of that block.
231   end record;
232
233   function Header_Of
234     (Address : System.Address) return Allocation_Header_Access;
235   pragma Inline (Header_Of);
236   --  Return the header corresponding to a previously allocated address
237
238   function To_Address is new Ada.Unchecked_Conversion
239     (Traceback_Ptr_Or_Address, System.Address);
240
241   function To_Address is new Ada.Unchecked_Conversion
242     (System.Address, Traceback_Ptr_Or_Address);
243
244   function To_Traceback is new Ada.Unchecked_Conversion
245     (Traceback_Ptr_Or_Address, Traceback_Htable_Elem_Ptr);
246
247   function To_Traceback is new Ada.Unchecked_Conversion
248     (Traceback_Htable_Elem_Ptr, Traceback_Ptr_Or_Address);
249
250   Header_Offset : constant Storage_Count :=
251     (Allocation_Header'Object_Size / System.Storage_Unit);
252   --  Offset, in bytes, from start of allocation Header to start of User
253   --  data.  The start of user data is assumed to be aligned at least as much
254   --  as what the header type requires, so applying this offset yields a
255   --  suitably aligned address as well.
256
257   Extra_Allocation : constant Storage_Count :=
258     (Storage_Alignment - 1 + Header_Offset);
259   --  Amount we need to secure in addition to the user data for a given
260   --  allocation request: room for the allocation header plus worst-case
261   --  alignment padding.
262
263   -----------------------
264   -- Local subprograms --
265   -----------------------
266
267   function Align (Addr : Integer_Address) return Integer_Address;
268   pragma Inline (Align);
269   --  Return the next address aligned on Storage_Alignment from Addr.
270
271   function Find_Or_Create_Traceback
272     (Pool                : Debug_Pool;
273      Kind                : Traceback_Kind;
274      Size                : Storage_Count;
275      Ignored_Frame_Start : System.Address;
276      Ignored_Frame_End   : System.Address) return Traceback_Htable_Elem_Ptr;
277   --  Return an element matching the current traceback (omitting the frames
278   --  that are in the current package). If this traceback already existed in
279   --  the htable, a pointer to this is returned to spare memory. Null is
280   --  returned if the pool is set not to store tracebacks. If the traceback
281   --  already existed in the table, the count is incremented so that
282   --  Dump_Tracebacks returns useful results. All addresses up to, and
283   --  including, an address between Ignored_Frame_Start .. Ignored_Frame_End
284   --  are ignored.
285
286   function Output_File (Pool : Debug_Pool) return File_Type;
287   pragma Inline (Output_File);
288   --  Returns file_type on which error messages have to be generated for Pool
289
290   procedure Put_Line
291     (File                : File_Type;
292      Depth               : Natural;
293      Traceback           : Tracebacks_Array_Access;
294      Ignored_Frame_Start : System.Address := System.Null_Address;
295      Ignored_Frame_End   : System.Address := System.Null_Address);
296   --  Print Traceback to File. If Traceback is null, print the call_chain
297   --  at the current location, up to Depth levels, ignoring all addresses
298   --  up to the first one in the range:
299   --    Ignored_Frame_Start .. Ignored_Frame_End
300
301   procedure Stdout_Put (S : String);
302   --  Wrapper for Put that ensures we always write to stdout instead of the
303   --  current output file defined in GNAT.IO.
304
305   procedure Stdout_Put_Line (S : String);
306   --  Wrapper for Put_Line that ensures we always write to stdout instead of
307   --  the current output file defined in GNAT.IO.
308
309   procedure Print_Traceback
310     (Output_File : File_Type;
311      Prefix      : String;
312      Traceback   : Traceback_Htable_Elem_Ptr);
313   --  Output Prefix & Traceback & EOL. Print nothing if Traceback is null.
314
315   procedure Print_Address (File : File_Type; Addr : Address);
316   --  Output System.Address without using secondary stack.
317   --  When System.Memory uses Debug_Pool, secondary stack cannot be used
318   --  during Allocate calls, as some Allocate calls are done to
319   --  register/initialize a secondary stack for a foreign thread.
320   --  During these calls, the secondary stack is not available yet.
321
322   package Validity is
323      function Is_Handled (Storage : System.Address) return Boolean;
324      pragma Inline (Is_Handled);
325      --  Return True if Storage is the address of a block that the debug pool
326      --  already had under its control. Used to allow System.Memory to use
327      --  Debug_Pools
328
329      function Is_Valid (Storage : System.Address) return Boolean;
330      pragma Inline (Is_Valid);
331      --  Return True if Storage is the address of a block that the debug pool
332      --  has under its control, in which case Header_Of may be used to access
333      --  the associated allocation header.
334
335      procedure Set_Valid (Storage : System.Address; Value : Boolean);
336      pragma Inline (Set_Valid);
337      --  Mark the address Storage as being under control of the memory pool
338      --  (if Value is True), or not (if Value is False).
339
340      Validity_Count : Byte_Count := 0;
341      --  Total number of validity elements
342
343   end Validity;
344
345   use Validity;
346
347   procedure Set_Dead_Beef
348     (Storage_Address          : System.Address;
349      Size_In_Storage_Elements : Storage_Count);
350   --  Set the contents of the memory block pointed to by Storage_Address to
351   --  the 16#DEADBEEF# pattern. If Size_In_Storage_Elements is not a multiple
352   --  of the length of this pattern, the last instance may be partial.
353
354   procedure Free_Physically (Pool : in out Debug_Pool);
355   --  Start to physically release some memory to the system, until the amount
356   --  of logically (but not physically) freed memory is lower than the
357   --  expected amount in Pool.
358
359   procedure Allocate_End;
360   procedure Deallocate_End;
361   procedure Dereference_End;
362   --  These procedures are used as markers when computing the stacktraces,
363   --  so that addresses in the debug pool itself are not reported to the user.
364
365   Code_Address_For_Allocate_End    : System.Address;
366   Code_Address_For_Deallocate_End  : System.Address;
367   Code_Address_For_Dereference_End : System.Address;
368   --  Taking the address of the above procedures will not work on some
369   --  architectures (HPUX for instance). Thus we do the same thing that
370   --  is done in a-except.adb, and get the address of labels instead.
371
372   procedure Skip_Levels
373     (Depth               : Natural;
374      Trace               : Tracebacks_Array;
375      Start               : out Natural;
376      Len                 : in out Natural;
377      Ignored_Frame_Start : System.Address;
378      Ignored_Frame_End   : System.Address);
379   --  Set Start .. Len to the range of values from Trace that should be output
380   --  to the user. This range of values excludes any address prior to the
381   --  first one in Ignored_Frame_Start .. Ignored_Frame_End (basically
382   --  addresses internal to this package). Depth is the number of levels that
383   --  the user is interested in.
384
385   package STBE renames System.Traceback_Entries;
386
387   function PC_For (TB_Entry : STBE.Traceback_Entry) return System.Address
388     renames STBE.PC_For;
389
390   type Scope_Lock is
391     new Ada.Finalization.Limited_Controlled with null record;
392   --  Used to handle Lock_Task/Unlock_Task calls
393
394   overriding procedure Initialize (This : in out Scope_Lock);
395   --  Lock task on initialization
396
397   overriding procedure Finalize   (This : in out Scope_Lock);
398   --  Unlock task on finalization
399
400   ----------------
401   -- Initialize --
402   ----------------
403
404   procedure Initialize (This : in out Scope_Lock) is
405      pragma Unreferenced (This);
406   begin
407      Lock_Task.all;
408   end Initialize;
409
410   --------------
411   -- Finalize --
412   --------------
413
414   procedure Finalize (This : in out Scope_Lock) is
415      pragma Unreferenced (This);
416   begin
417      Unlock_Task.all;
418   end Finalize;
419
420   -----------
421   -- Align --
422   -----------
423
424   function Align (Addr : Integer_Address) return Integer_Address is
425      Factor : constant Integer_Address := Storage_Alignment;
426   begin
427      return ((Addr + Factor - 1) / Factor) * Factor;
428   end Align;
429
430   ---------------
431   -- Header_Of --
432   ---------------
433
434   function Header_Of
435     (Address : System.Address) return Allocation_Header_Access
436   is
437      function Convert is
438        new Ada.Unchecked_Conversion
439                  (System.Address,
440                   Allocation_Header_Access);
441   begin
442      return Convert (Address - Header_Offset);
443   end Header_Of;
444
445   --------------
446   -- Set_Next --
447   --------------
448
449   procedure Set_Next
450     (E    : Traceback_Htable_Elem_Ptr;
451      Next : Traceback_Htable_Elem_Ptr)
452   is
453   begin
454      E.Next := Next;
455   end Set_Next;
456
457   ----------
458   -- Next --
459   ----------
460
461   function Next
462     (E : Traceback_Htable_Elem_Ptr) return Traceback_Htable_Elem_Ptr
463   is
464   begin
465      return E.Next;
466   end Next;
467
468   -----------
469   -- Equal --
470   -----------
471
472   function Equal (K1, K2 : Tracebacks_Array_Access) return Boolean is
473      use type Tracebacks_Array;
474   begin
475      return K1.all = K2.all;
476   end Equal;
477
478   -------------
479   -- Get_Key --
480   -------------
481
482   function Get_Key
483     (E : Traceback_Htable_Elem_Ptr) return Tracebacks_Array_Access
484   is
485   begin
486      return E.Traceback;
487   end Get_Key;
488
489   ----------
490   -- Hash --
491   ----------
492
493   function Hash (T : Tracebacks_Array_Access) return Header is
494      Result : Integer_Address := 0;
495
496   begin
497      for X in T'Range loop
498         Result := Result + To_Integer (PC_For (T (X)));
499      end loop;
500
501      return Header (1 + Result mod Integer_Address (Header'Last));
502   end Hash;
503
504   -----------------
505   -- Output_File --
506   -----------------
507
508   function Output_File (Pool : Debug_Pool) return File_Type is
509   begin
510      if Pool.Errors_To_Stdout then
511         return Standard_Output;
512      else
513         return Standard_Error;
514      end if;
515   end Output_File;
516
517   -------------------
518   -- Print_Address --
519   -------------------
520
521   procedure Print_Address (File : File_Type; Addr : Address) is
522   begin
523      --  Warning: secondary stack cannot be used here. When System.Memory
524      --  implementation uses Debug_Pool, Print_Address can be called during
525      --  secondary stack creation for foreign threads.
526
527      Put (File, Image_C (Addr));
528   end Print_Address;
529
530   --------------
531   -- Put_Line --
532   --------------
533
534   procedure Put_Line
535     (File                : File_Type;
536      Depth               : Natural;
537      Traceback           : Tracebacks_Array_Access;
538      Ignored_Frame_Start : System.Address := System.Null_Address;
539      Ignored_Frame_End   : System.Address := System.Null_Address)
540   is
541      procedure Print (Tr : Tracebacks_Array);
542      --  Print the traceback to standard_output
543
544      -----------
545      -- Print --
546      -----------
547
548      procedure Print (Tr : Tracebacks_Array) is
549      begin
550         for J in Tr'Range loop
551            Print_Address (File, PC_For (Tr (J)));
552            Put (File, ' ');
553         end loop;
554         Put (File, ASCII.LF);
555      end Print;
556
557   --  Start of processing for Put_Line
558
559   begin
560      if Traceback = null then
561         declare
562            Len   : Natural;
563            Start : Natural;
564            Trace : aliased Tracebacks_Array (1 .. Depth + Max_Ignored_Levels);
565
566         begin
567            Call_Chain (Trace, Len);
568            Skip_Levels
569              (Depth               => Depth,
570               Trace               => Trace,
571               Start               => Start,
572               Len                 => Len,
573               Ignored_Frame_Start => Ignored_Frame_Start,
574               Ignored_Frame_End   => Ignored_Frame_End);
575            Print (Trace (Start .. Len));
576         end;
577
578      else
579         Print (Traceback.all);
580      end if;
581   end Put_Line;
582
583   -----------------
584   -- Skip_Levels --
585   -----------------
586
587   procedure Skip_Levels
588     (Depth               : Natural;
589      Trace               : Tracebacks_Array;
590      Start               : out Natural;
591      Len                 : in out Natural;
592      Ignored_Frame_Start : System.Address;
593      Ignored_Frame_End   : System.Address)
594   is
595   begin
596      Start := Trace'First;
597
598      while Start <= Len
599        and then (PC_For (Trace (Start)) < Ignored_Frame_Start
600                    or else PC_For (Trace (Start)) > Ignored_Frame_End)
601      loop
602         Start := Start + 1;
603      end loop;
604
605      Start := Start + 1;
606
607      --  Just in case: make sure we have a traceback even if Ignore_Till
608      --  wasn't found.
609
610      if Start > Len then
611         Start := 1;
612      end if;
613
614      if Len - Start + 1 > Depth then
615         Len := Depth + Start - 1;
616      end if;
617   end Skip_Levels;
618
619   ------------------------------
620   -- Find_Or_Create_Traceback --
621   ------------------------------
622
623   function Find_Or_Create_Traceback
624     (Pool                : Debug_Pool;
625      Kind                : Traceback_Kind;
626      Size                : Storage_Count;
627      Ignored_Frame_Start : System.Address;
628      Ignored_Frame_End   : System.Address) return Traceback_Htable_Elem_Ptr
629   is
630   begin
631      if Pool.Stack_Trace_Depth = 0 then
632         return null;
633      end if;
634
635      declare
636         Disable_Exit_Value : constant Boolean := Disable;
637
638         Elem  : Traceback_Htable_Elem_Ptr;
639         Len   : Natural;
640         Start : Natural;
641         Trace : aliased Tracebacks_Array
642                   (1 .. Integer (Pool.Stack_Trace_Depth) +
643                      Max_Ignored_Levels);
644
645      begin
646         Disable := True;
647         Call_Chain (Trace, Len);
648         Skip_Levels
649           (Depth               => Pool.Stack_Trace_Depth,
650            Trace               => Trace,
651            Start               => Start,
652            Len                 => Len,
653            Ignored_Frame_Start => Ignored_Frame_Start,
654            Ignored_Frame_End   => Ignored_Frame_End);
655
656         --  Check if the traceback is already in the table
657
658         Elem :=
659           Backtrace_Htable.Get (Trace (Start .. Len)'Unrestricted_Access);
660
661         --  If not, insert it
662
663         if Elem = null then
664            Elem :=
665              new Traceback_Htable_Elem'
666                    (Traceback   =>
667                       new Tracebacks_Array'(Trace (Start .. Len)),
668                     Count       => 1,
669                     Kind        => Kind,
670                     Total       => Byte_Count (Size),
671                     Frees       => 0,
672                     Total_Frees => 0,
673                     Next        => null);
674            Traceback_Count := Traceback_Count + 1;
675            Backtrace_Htable.Set (Elem);
676
677         else
678            Elem.Count := Elem.Count + 1;
679            Elem.Total := Elem.Total + Byte_Count (Size);
680         end if;
681
682         Disable := Disable_Exit_Value;
683         return Elem;
684      exception
685         when others =>
686            Disable := Disable_Exit_Value;
687            raise;
688      end;
689   end Find_Or_Create_Traceback;
690
691   --------------
692   -- Validity --
693   --------------
694
695   package body Validity is
696
697      --  The validity bits of the allocated blocks are kept in a has table.
698      --  Each component of the hash table contains the validity bits for a
699      --  16 Mbyte memory chunk.
700
701      --  The reason the validity bits are kept for chunks of memory rather
702      --  than in a big array is that on some 64 bit platforms, it may happen
703      --  that two chunk of allocated data are very far from each other.
704
705      Memory_Chunk_Size : constant Integer_Address := 2 ** 24; --  16 MB
706      Validity_Divisor  : constant := Storage_Alignment * System.Storage_Unit;
707
708      Max_Validity_Byte_Index : constant :=
709                                  Memory_Chunk_Size / Validity_Divisor;
710
711      subtype Validity_Byte_Index is
712        Integer_Address range 0 .. Max_Validity_Byte_Index - 1;
713
714      type Byte is mod 2 ** System.Storage_Unit;
715
716      type Validity_Bits_Part is array (Validity_Byte_Index) of Byte;
717      type Validity_Bits_Part_Ref is access all Validity_Bits_Part;
718      No_Validity_Bits_Part : constant Validity_Bits_Part_Ref := null;
719
720      type Validity_Bits is record
721         Valid : Validity_Bits_Part_Ref := No_Validity_Bits_Part;
722         --  True if chunk of memory at this address is currently allocated
723
724         Handled : Validity_Bits_Part_Ref := No_Validity_Bits_Part;
725         --  True if chunk of memory at this address was allocated once after
726         --  Allow_Unhandled_Memory was set to True. Used to know on Deallocate
727         --  if chunk of memory should be handled a block allocated by this
728         --  package.
729
730      end record;
731
732      type Validity_Bits_Ref is access all Validity_Bits;
733      No_Validity_Bits : constant Validity_Bits_Ref := null;
734
735      Max_Header_Num : constant := 1023;
736
737      type Header_Num is range 0 .. Max_Header_Num - 1;
738
739      function Hash (F : Integer_Address) return Header_Num;
740
741      function Is_Valid_Or_Handled
742        (Storage : System.Address;
743         Valid   : Boolean) return Boolean;
744      pragma Inline (Is_Valid_Or_Handled);
745      --  Internal implementation of Is_Valid and Is_Handled.
746      --  Valid is used to select Valid or Handled arrays.
747
748      package Validy_Htable is new GNAT.HTable.Simple_HTable
749        (Header_Num => Header_Num,
750         Element    => Validity_Bits_Ref,
751         No_Element => No_Validity_Bits,
752         Key        => Integer_Address,
753         Hash       => Hash,
754         Equal      => "=");
755      --  Table to keep the validity and handled bit blocks for the allocated
756      --  data.
757
758      function To_Pointer is new Ada.Unchecked_Conversion
759        (System.Address, Validity_Bits_Part_Ref);
760
761      procedure Memset (A : Address; C : Integer; N : size_t);
762      pragma Import (C, Memset, "memset");
763
764      ----------
765      -- Hash --
766      ----------
767
768      function Hash (F : Integer_Address) return Header_Num is
769      begin
770         return Header_Num (F mod Max_Header_Num);
771      end Hash;
772
773      -------------------------
774      -- Is_Valid_Or_Handled --
775      -------------------------
776
777      function Is_Valid_Or_Handled
778        (Storage : System.Address;
779         Valid   : Boolean) return Boolean is
780         Int_Storage  : constant Integer_Address := To_Integer (Storage);
781
782      begin
783         --  The pool only returns addresses aligned on Storage_Alignment so
784         --  anything off cannot be a valid block address and we can return
785         --  early in this case. We actually have to since our data structures
786         --  map validity bits for such aligned addresses only.
787
788         if Int_Storage mod Storage_Alignment /= 0 then
789            return False;
790         end if;
791
792         declare
793            Block_Number : constant Integer_Address :=
794                             Int_Storage /  Memory_Chunk_Size;
795            Ptr          : constant Validity_Bits_Ref :=
796                             Validy_Htable.Get (Block_Number);
797            Offset       : constant Integer_Address :=
798                             (Int_Storage -
799                               (Block_Number * Memory_Chunk_Size)) /
800                                  Storage_Alignment;
801            Bit          : constant Byte :=
802                             2 ** Natural (Offset mod System.Storage_Unit);
803         begin
804            if Ptr = No_Validity_Bits then
805               return False;
806            else
807               if Valid then
808                  return (Ptr.Valid (Offset / System.Storage_Unit)
809                             and Bit) /= 0;
810               else
811                  if Ptr.Handled = No_Validity_Bits_Part then
812                     return False;
813                  else
814                     return (Ptr.Handled (Offset / System.Storage_Unit)
815                                and Bit) /= 0;
816                  end if;
817               end if;
818            end if;
819         end;
820      end Is_Valid_Or_Handled;
821
822      --------------
823      -- Is_Valid --
824      --------------
825
826      function Is_Valid (Storage : System.Address) return Boolean is
827      begin
828         return Is_Valid_Or_Handled (Storage => Storage, Valid => True);
829      end Is_Valid;
830
831      -----------------
832      -- Is_Handled --
833      -----------------
834
835      function Is_Handled (Storage : System.Address) return Boolean is
836      begin
837         return Is_Valid_Or_Handled (Storage => Storage, Valid => False);
838      end Is_Handled;
839
840      ---------------
841      -- Set_Valid --
842      ---------------
843
844      procedure Set_Valid (Storage : System.Address; Value : Boolean) is
845         Int_Storage  : constant Integer_Address := To_Integer (Storage);
846         Block_Number : constant Integer_Address :=
847                          Int_Storage /  Memory_Chunk_Size;
848         Ptr          : Validity_Bits_Ref := Validy_Htable.Get (Block_Number);
849         Offset       : constant Integer_Address :=
850                          (Int_Storage - (Block_Number * Memory_Chunk_Size)) /
851                             Storage_Alignment;
852         Bit          : constant Byte :=
853                          2 ** Natural (Offset mod System.Storage_Unit);
854
855         procedure Set_Handled;
856         pragma Inline (Set_Handled);
857         --  if Allow_Unhandled_Memory set Handled bit in table.
858
859         -----------------
860         -- Set_Handled --
861         -----------------
862
863         procedure Set_Handled is
864         begin
865            if Allow_Unhandled_Memory then
866               if Ptr.Handled = No_Validity_Bits_Part then
867                  Ptr.Handled :=
868                    To_Pointer (Alloc (size_t (Max_Validity_Byte_Index)));
869                  Memset
870                    (A => Ptr.Handled.all'Address,
871                     C => 0,
872                     N => size_t (Max_Validity_Byte_Index));
873               end if;
874
875               Ptr.Handled (Offset / System.Storage_Unit) :=
876                 Ptr.Handled (Offset / System.Storage_Unit) or Bit;
877            end if;
878         end Set_Handled;
879
880      --  Start of processing for Set_Valid
881
882      begin
883         if Ptr = No_Validity_Bits then
884
885            --  First time in this memory area: allocate a new block and put
886            --  it in the table.
887
888            if Value then
889               Ptr := new Validity_Bits;
890               Validity_Count := Validity_Count + 1;
891               Ptr.Valid :=
892                 To_Pointer (Alloc (size_t (Max_Validity_Byte_Index)));
893               Validy_Htable.Set (Block_Number, Ptr);
894               Memset
895                 (A => Ptr.Valid.all'Address,
896                  C => 0,
897                  N => size_t (Max_Validity_Byte_Index));
898               Ptr.Valid (Offset / System.Storage_Unit) := Bit;
899               Set_Handled;
900            end if;
901
902         else
903            if Value then
904               Ptr.Valid (Offset / System.Storage_Unit) :=
905                 Ptr.Valid (Offset / System.Storage_Unit) or Bit;
906               Set_Handled;
907            else
908               Ptr.Valid (Offset / System.Storage_Unit) :=
909                 Ptr.Valid (Offset / System.Storage_Unit) and (not Bit);
910            end if;
911         end if;
912      end Set_Valid;
913   end Validity;
914
915   --------------
916   -- Allocate --
917   --------------
918
919   procedure Allocate
920     (Pool                     : in out Debug_Pool;
921      Storage_Address          : out Address;
922      Size_In_Storage_Elements : Storage_Count;
923      Alignment                : Storage_Count)
924   is
925      pragma Unreferenced (Alignment);
926      --  Ignored, we always force Storage_Alignment
927
928      type Local_Storage_Array is new Storage_Array
929        (1 .. Size_In_Storage_Elements + Extra_Allocation);
930
931      type Ptr is access Local_Storage_Array;
932      --  On some systems, we might want to physically protect pages against
933      --  writing when they have been freed (of course, this is expensive in
934      --  terms of wasted memory). To do that, all we should have to do it to
935      --  set the size of this array to the page size. See mprotect().
936
937      Current : Byte_Count;
938      P       : Ptr;
939      Trace   : Traceback_Htable_Elem_Ptr;
940
941      Reset_Disable_At_Exit : Boolean := False;
942
943      Lock : Scope_Lock;
944      pragma Unreferenced (Lock);
945
946   begin
947      <<Allocate_Label>>
948
949      if Disable then
950         Storage_Address :=
951           System.CRTL.malloc (System.CRTL.size_t (Size_In_Storage_Elements));
952         return;
953      end if;
954
955      Reset_Disable_At_Exit := True;
956      Disable := True;
957
958      Pool.Alloc_Count := Pool.Alloc_Count + 1;
959
960      --  If necessary, start physically releasing memory. The reason this is
961      --  done here, although Pool.Logically_Deallocated has not changed above,
962      --  is so that we do this only after a series of deallocations (e.g loop
963      --  that deallocates a big array). If we were doing that in Deallocate,
964      --  we might be physically freeing memory several times during the loop,
965      --  which is expensive.
966
967      if Pool.Logically_Deallocated >
968           Byte_Count (Pool.Maximum_Logically_Freed_Memory)
969      then
970         Free_Physically (Pool);
971      end if;
972
973      --  Use standard (i.e. through malloc) allocations. This automatically
974      --  raises Storage_Error if needed. We also try once more to physically
975      --  release memory, so that even marked blocks, in the advanced scanning,
976      --  are freed. Note that we do not initialize the storage array since it
977      --  is not necessary to do so (however this will cause bogus valgrind
978      --  warnings, which should simply be ignored).
979
980      begin
981         P := new Local_Storage_Array;
982
983      exception
984         when Storage_Error =>
985            Free_Physically (Pool);
986            P := new Local_Storage_Array;
987      end;
988
989      --  Compute Storage_Address, aimed at receiving user data. We need room
990      --  for the allocation header just ahead of the user data space plus
991      --  alignment padding so Storage_Address is aligned on Storage_Alignment,
992      --  like so:
993      --
994      --                         Storage_Address, aligned
995      --                         on Storage_Alignment
996      --                           v
997      --          | ~~~~ | Header | User data ... |
998      --                  ^........^
999      --                  Header_Offset
1000      --
1001      --  Header_Offset is fixed so moving back and forth between user data
1002      --  and allocation header is straightforward. The value is also such
1003      --  that the header type alignment is honored when starting from
1004      --  Default_alignment.
1005
1006      --  For the purpose of computing Storage_Address, we just do as if the
1007      --  header was located first, followed by the alignment padding:
1008
1009      Storage_Address :=
1010        To_Address (Align (To_Integer (P.all'Address) +
1011                      Integer_Address (Header_Offset)));
1012      --  Computation is done in Integer_Address, not Storage_Offset, because
1013      --  the range of Storage_Offset may not be large enough.
1014
1015      pragma Assert ((Storage_Address - System.Null_Address)
1016                     mod Storage_Alignment = 0);
1017      pragma Assert (Storage_Address + Size_In_Storage_Elements
1018                     <= P.all'Address + P'Length);
1019
1020      Trace :=
1021        Find_Or_Create_Traceback
1022          (Pool                => Pool,
1023           Kind                => Alloc,
1024           Size                => Size_In_Storage_Elements,
1025           Ignored_Frame_Start => Allocate_Label'Address,
1026           Ignored_Frame_End   => Code_Address_For_Allocate_End);
1027
1028      pragma Warnings (Off);
1029      --  Turn warning on alignment for convert call off. We know that in fact
1030      --  this conversion is safe since P itself is always aligned on
1031      --  Storage_Alignment.
1032
1033      Header_Of (Storage_Address).all :=
1034        (Allocation_Address => P.all'Address,
1035         Alloc_Traceback    => Trace,
1036         Dealloc_Traceback  => To_Traceback (null),
1037         Next               => Pool.First_Used_Block,
1038         Block_Size         => Size_In_Storage_Elements);
1039
1040      pragma Warnings (On);
1041
1042      --  Link this block in the list of used blocks. This will be used to list
1043      --  memory leaks in Print_Info, and for the advanced schemes of
1044      --  Physical_Free, where we want to traverse all allocated blocks and
1045      --  search for possible references.
1046
1047      --  We insert in front, since most likely we'll be freeing the most
1048      --  recently allocated blocks first (the older one might stay allocated
1049      --  for the whole life of the application).
1050
1051      if Pool.First_Used_Block /= System.Null_Address then
1052         Header_Of (Pool.First_Used_Block).Dealloc_Traceback :=
1053           To_Address (Storage_Address);
1054      end if;
1055
1056      Pool.First_Used_Block := Storage_Address;
1057
1058      --  Mark the new address as valid
1059
1060      Set_Valid (Storage_Address, True);
1061
1062      if Pool.Low_Level_Traces then
1063         Put (Output_File (Pool),
1064              "info: Allocated"
1065              & Storage_Count'Image (Size_In_Storage_Elements)
1066              & " bytes at ");
1067         Print_Address (Output_File (Pool), Storage_Address);
1068         Put (Output_File (Pool),
1069              " (physically:"
1070              & Storage_Count'Image (Local_Storage_Array'Length)
1071              & " bytes at ");
1072         Print_Address (Output_File (Pool), P.all'Address);
1073         Put (Output_File (Pool),
1074              "), at ");
1075         Put_Line (Output_File (Pool), Pool.Stack_Trace_Depth, null,
1076                   Allocate_Label'Address,
1077                   Code_Address_For_Deallocate_End);
1078      end if;
1079
1080      --  Update internal data
1081
1082      Pool.Allocated :=
1083        Pool.Allocated + Byte_Count (Size_In_Storage_Elements);
1084
1085      Current := Pool.Current_Water_Mark;
1086
1087      if Current > Pool.High_Water then
1088         Pool.High_Water := Current;
1089      end if;
1090
1091      Disable := False;
1092
1093   exception
1094      when others =>
1095         if Reset_Disable_At_Exit then
1096            Disable := False;
1097         end if;
1098         raise;
1099   end Allocate;
1100
1101   ------------------
1102   -- Allocate_End --
1103   ------------------
1104
1105   --  DO NOT MOVE, this must be right after Allocate. This is similar to what
1106   --  is done in a-except, so that we can hide the traceback frames internal
1107   --  to this package
1108
1109   procedure Allocate_End is
1110   begin
1111      <<Allocate_End_Label>>
1112      Code_Address_For_Allocate_End := Allocate_End_Label'Address;
1113   end Allocate_End;
1114
1115   -------------------
1116   -- Set_Dead_Beef --
1117   -------------------
1118
1119   procedure Set_Dead_Beef
1120     (Storage_Address          : System.Address;
1121      Size_In_Storage_Elements : Storage_Count)
1122   is
1123      Dead_Bytes : constant := 4;
1124
1125      type Data is mod 2 ** (Dead_Bytes * 8);
1126      for Data'Size use Dead_Bytes * 8;
1127
1128      Dead : constant Data := 16#DEAD_BEEF#;
1129
1130      type Dead_Memory is array
1131        (1 .. Size_In_Storage_Elements / Dead_Bytes) of Data;
1132      type Mem_Ptr is access Dead_Memory;
1133
1134      type Byte is mod 2 ** 8;
1135      for Byte'Size use 8;
1136
1137      type Dead_Memory_Bytes is array (0 .. 2) of Byte;
1138      type Dead_Memory_Bytes_Ptr is access Dead_Memory_Bytes;
1139
1140      function From_Ptr is new Ada.Unchecked_Conversion
1141        (System.Address, Mem_Ptr);
1142
1143      function From_Ptr is new Ada.Unchecked_Conversion
1144        (System.Address, Dead_Memory_Bytes_Ptr);
1145
1146      M      : constant Mem_Ptr := From_Ptr (Storage_Address);
1147      M2     : Dead_Memory_Bytes_Ptr;
1148      Modulo : constant Storage_Count :=
1149                 Size_In_Storage_Elements mod Dead_Bytes;
1150   begin
1151      M.all := (others => Dead);
1152
1153      --  Any bytes left (up to three of them)
1154
1155      if Modulo /= 0 then
1156         M2 := From_Ptr (Storage_Address + M'Length * Dead_Bytes);
1157
1158         M2 (0) := 16#DE#;
1159         if Modulo >= 2 then
1160            M2 (1) := 16#AD#;
1161
1162            if Modulo >= 3 then
1163               M2 (2) := 16#BE#;
1164            end if;
1165         end if;
1166      end if;
1167   end Set_Dead_Beef;
1168
1169   ---------------------
1170   -- Free_Physically --
1171   ---------------------
1172
1173   procedure Free_Physically (Pool : in out Debug_Pool) is
1174      type Byte is mod 256;
1175      type Byte_Access is access Byte;
1176
1177      function To_Byte is new Ada.Unchecked_Conversion
1178        (System.Address, Byte_Access);
1179
1180      type Address_Access is access System.Address;
1181
1182      function To_Address_Access is new Ada.Unchecked_Conversion
1183        (System.Address, Address_Access);
1184
1185      In_Use_Mark : constant Byte := 16#D#;
1186      Free_Mark   : constant Byte := 16#F#;
1187
1188      Total_Freed : Storage_Count := 0;
1189
1190      procedure Reset_Marks;
1191      --  Unmark all the logically freed blocks, so that they are considered
1192      --  for physical deallocation
1193
1194      procedure Mark
1195        (H : Allocation_Header_Access; A : System.Address; In_Use : Boolean);
1196      --  Mark the user data block starting at A. For a block of size zero,
1197      --  nothing is done. For a block with a different size, the first byte
1198      --  is set to either "D" (in use) or "F" (free).
1199
1200      function Marked (A : System.Address) return Boolean;
1201      --  Return true if the user data block starting at A might be in use
1202      --  somewhere else
1203
1204      procedure Mark_Blocks;
1205      --  Traverse all allocated blocks, and search for possible references
1206      --  to logically freed blocks. Mark them appropriately
1207
1208      procedure Free_Blocks (Ignore_Marks : Boolean);
1209      --  Physically release blocks. Only the blocks that haven't been marked
1210      --  will be released, unless Ignore_Marks is true.
1211
1212      -----------------
1213      -- Free_Blocks --
1214      -----------------
1215
1216      procedure Free_Blocks (Ignore_Marks : Boolean) is
1217         Header   : Allocation_Header_Access;
1218         Tmp      : System.Address := Pool.First_Free_Block;
1219         Next     : System.Address;
1220         Previous : System.Address := System.Null_Address;
1221
1222      begin
1223         while Tmp /= System.Null_Address
1224           and then
1225             not (Total_Freed > Pool.Minimum_To_Free
1226                   and Pool.Logically_Deallocated <
1227                         Byte_Count (Pool.Maximum_Logically_Freed_Memory))
1228         loop
1229            Header := Header_Of (Tmp);
1230
1231            --  If we know, or at least assume, the block is no longer
1232            --  referenced anywhere, we can free it physically.
1233
1234            if Ignore_Marks or else not Marked (Tmp) then
1235               declare
1236                  pragma Suppress (All_Checks);
1237                  --  Suppress the checks on this section. If they are overflow
1238                  --  errors, it isn't critical, and we'd rather avoid a
1239                  --  Constraint_Error in that case.
1240
1241               begin
1242                  --  Note that block_size < zero for freed blocks
1243
1244                  Pool.Physically_Deallocated :=
1245                    Pool.Physically_Deallocated -
1246                      Byte_Count (Header.Block_Size);
1247
1248                  Pool.Logically_Deallocated :=
1249                    Pool.Logically_Deallocated +
1250                      Byte_Count (Header.Block_Size);
1251
1252                  Total_Freed := Total_Freed - Header.Block_Size;
1253               end;
1254
1255               Next := Header.Next;
1256
1257               if Pool.Low_Level_Traces then
1258                  Put
1259                    (Output_File (Pool),
1260                     "info: Freeing physical memory "
1261                     & Storage_Count'Image
1262                       ((abs Header.Block_Size) + Extra_Allocation)
1263                     & " bytes at ");
1264                  Print_Address (Output_File (Pool),
1265                                 Header.Allocation_Address);
1266                  Put_Line (Output_File (Pool), "");
1267               end if;
1268
1269               if System_Memory_Debug_Pool_Enabled then
1270                  System.CRTL.free (Header.Allocation_Address);
1271               else
1272                  System.Memory.Free (Header.Allocation_Address);
1273               end if;
1274
1275               Set_Valid (Tmp, False);
1276
1277               --  Remove this block from the list
1278
1279               if Previous = System.Null_Address then
1280                  Pool.First_Free_Block := Next;
1281               else
1282                  Header_Of (Previous).Next := Next;
1283               end if;
1284
1285               Tmp := Next;
1286
1287            else
1288               Previous := Tmp;
1289               Tmp := Header.Next;
1290            end if;
1291         end loop;
1292      end Free_Blocks;
1293
1294      ----------
1295      -- Mark --
1296      ----------
1297
1298      procedure Mark
1299        (H      : Allocation_Header_Access;
1300         A      : System.Address;
1301         In_Use : Boolean)
1302      is
1303      begin
1304         if H.Block_Size /= 0 then
1305            To_Byte (A).all := (if In_Use then In_Use_Mark else Free_Mark);
1306         end if;
1307      end Mark;
1308
1309      -----------------
1310      -- Mark_Blocks --
1311      -----------------
1312
1313      procedure Mark_Blocks is
1314         Tmp      : System.Address := Pool.First_Used_Block;
1315         Previous : System.Address;
1316         Last     : System.Address;
1317         Pointed  : System.Address;
1318         Header   : Allocation_Header_Access;
1319
1320      begin
1321         --  For each allocated block, check its contents. Things that look
1322         --  like a possible address are used to mark the blocks so that we try
1323         --  and keep them, for better detection in case of invalid access.
1324         --  This mechanism is far from being fool-proof: it doesn't check the
1325         --  stacks of the threads, doesn't check possible memory allocated not
1326         --  under control of this debug pool. But it should allow us to catch
1327         --  more cases.
1328
1329         while Tmp /= System.Null_Address loop
1330            Previous := Tmp;
1331            Last     := Tmp + Header_Of (Tmp).Block_Size;
1332            while Previous < Last loop
1333               --  ??? Should we move byte-per-byte, or consider that addresses
1334               --  are always aligned on 4-bytes boundaries ? Let's use the
1335               --  fastest for now.
1336
1337               Pointed := To_Address_Access (Previous).all;
1338               if Is_Valid (Pointed) then
1339                  Header := Header_Of (Pointed);
1340
1341                  --  Do not even attempt to mark blocks in use. That would
1342                  --  screw up the whole application, of course.
1343
1344                  if Header.Block_Size < 0 then
1345                     Mark (Header, Pointed, In_Use => True);
1346                  end if;
1347               end if;
1348
1349               Previous := Previous + System.Address'Size;
1350            end loop;
1351
1352            Tmp := Header_Of (Tmp).Next;
1353         end loop;
1354      end Mark_Blocks;
1355
1356      ------------
1357      -- Marked --
1358      ------------
1359
1360      function Marked (A : System.Address) return Boolean is
1361      begin
1362         return To_Byte (A).all = In_Use_Mark;
1363      end Marked;
1364
1365      -----------------
1366      -- Reset_Marks --
1367      -----------------
1368
1369      procedure Reset_Marks is
1370         Current : System.Address := Pool.First_Free_Block;
1371         Header  : Allocation_Header_Access;
1372
1373      begin
1374         while Current /= System.Null_Address loop
1375            Header := Header_Of (Current);
1376            Mark (Header, Current, False);
1377            Current := Header.Next;
1378         end loop;
1379      end Reset_Marks;
1380
1381      Lock : Scope_Lock;
1382      pragma Unreferenced (Lock);
1383
1384   --  Start of processing for Free_Physically
1385
1386   begin
1387      if Pool.Advanced_Scanning then
1388
1389         --  Reset the mark for each freed block
1390
1391         Reset_Marks;
1392
1393         Mark_Blocks;
1394      end if;
1395
1396      Free_Blocks (Ignore_Marks => not Pool.Advanced_Scanning);
1397
1398      --  The contract is that we need to free at least Minimum_To_Free bytes,
1399      --  even if this means freeing marked blocks in the advanced scheme.
1400
1401      if Total_Freed < Pool.Minimum_To_Free
1402        and then Pool.Advanced_Scanning
1403      then
1404         Pool.Marked_Blocks_Deallocated := True;
1405         Free_Blocks (Ignore_Marks => True);
1406      end if;
1407   end Free_Physically;
1408
1409   --------------
1410   -- Get_Size --
1411   --------------
1412
1413   procedure Get_Size
1414     (Storage_Address          : Address;
1415      Size_In_Storage_Elements : out Storage_Count;
1416      Valid                    : out Boolean)
1417   is
1418      Lock : Scope_Lock;
1419      pragma Unreferenced (Lock);
1420
1421   begin
1422      Valid := Is_Valid (Storage_Address);
1423
1424      if Is_Valid (Storage_Address) then
1425         declare
1426            Header : constant Allocation_Header_Access :=
1427                       Header_Of (Storage_Address);
1428
1429         begin
1430            if Header.Block_Size >= 0 then
1431               Valid := True;
1432               Size_In_Storage_Elements := Header.Block_Size;
1433            else
1434               Valid := False;
1435            end if;
1436         end;
1437      else
1438         Valid := False;
1439      end if;
1440   end Get_Size;
1441
1442   ---------------------
1443   -- Print_Traceback --
1444   ---------------------
1445
1446   procedure Print_Traceback
1447     (Output_File : File_Type;
1448      Prefix      : String;
1449      Traceback   : Traceback_Htable_Elem_Ptr)
1450   is
1451   begin
1452      if Traceback /= null then
1453         Put (Output_File, Prefix);
1454         Put_Line (Output_File, 0, Traceback.Traceback);
1455      end if;
1456   end Print_Traceback;
1457
1458   ----------------
1459   -- Deallocate --
1460   ----------------
1461
1462   procedure Deallocate
1463     (Pool                     : in out Debug_Pool;
1464      Storage_Address          : Address;
1465      Size_In_Storage_Elements : Storage_Count;
1466      Alignment                : Storage_Count)
1467   is
1468      pragma Unreferenced (Alignment);
1469
1470      Header   : constant Allocation_Header_Access :=
1471                   Header_Of (Storage_Address);
1472      Previous : System.Address;
1473      Valid    : Boolean;
1474
1475      Header_Block_Size_Was_Less_Than_0 : Boolean := True;
1476
1477   begin
1478      <<Deallocate_Label>>
1479
1480      declare
1481         Lock : Scope_Lock;
1482         pragma Unreferenced (Lock);
1483
1484      begin
1485         Valid := Is_Valid (Storage_Address);
1486
1487         if Valid and then not (Header.Block_Size < 0) then
1488            Header_Block_Size_Was_Less_Than_0 := False;
1489
1490            --  Some sort of codegen problem or heap corruption caused the
1491            --  Size_In_Storage_Elements to be wrongly computed. The code
1492            --  below is all based on the assumption that Header.all is not
1493            --  corrupted, such that the error is non-fatal.
1494
1495            if Header.Block_Size /= Size_In_Storage_Elements and then
1496              Size_In_Storage_Elements /= Storage_Count'Last
1497            then
1498               Put_Line (Output_File (Pool),
1499                         "error: Deallocate size "
1500                         & Storage_Count'Image (Size_In_Storage_Elements)
1501                         & " does not match allocate size "
1502                         & Storage_Count'Image (Header.Block_Size));
1503            end if;
1504
1505            if Pool.Low_Level_Traces then
1506               Put (Output_File (Pool),
1507                    "info: Deallocated"
1508                    & Storage_Count'Image (Header.Block_Size)
1509                    & " bytes at ");
1510               Print_Address (Output_File (Pool), Storage_Address);
1511               Put (Output_File (Pool),
1512                    " (physically"
1513                    & Storage_Count'Image
1514                      (Header.Block_Size + Extra_Allocation)
1515                    & " bytes at ");
1516               Print_Address (Output_File (Pool), Header.Allocation_Address);
1517               Put (Output_File (Pool), "), at ");
1518
1519               Put_Line (Output_File (Pool), Pool.Stack_Trace_Depth, null,
1520                         Deallocate_Label'Address,
1521                         Code_Address_For_Deallocate_End);
1522               Print_Traceback (Output_File (Pool),
1523                                "   Memory was allocated at ",
1524                                Header.Alloc_Traceback);
1525            end if;
1526
1527            --  Remove this block from the list of used blocks
1528
1529            Previous :=
1530              To_Address (Header.Dealloc_Traceback);
1531
1532            if Previous = System.Null_Address then
1533               Pool.First_Used_Block := Header_Of (Pool.First_Used_Block).Next;
1534
1535               if Pool.First_Used_Block /= System.Null_Address then
1536                  Header_Of (Pool.First_Used_Block).Dealloc_Traceback :=
1537                    To_Traceback (null);
1538               end if;
1539
1540            else
1541               Header_Of (Previous).Next := Header.Next;
1542
1543               if Header.Next /= System.Null_Address then
1544                  Header_Of
1545                    (Header.Next).Dealloc_Traceback := To_Address (Previous);
1546               end if;
1547            end if;
1548
1549            --  Update the Alloc_Traceback Frees/Total_Frees members
1550            --  (if present)
1551
1552            if Header.Alloc_Traceback /= null then
1553               Header.Alloc_Traceback.Frees :=
1554                 Header.Alloc_Traceback.Frees + 1;
1555               Header.Alloc_Traceback.Total_Frees :=
1556                 Header.Alloc_Traceback.Total_Frees +
1557                   Byte_Count (Header.Block_Size);
1558            end if;
1559
1560            Pool.Free_Count := Pool.Free_Count + 1;
1561
1562            --  Update the header
1563
1564            Header.all :=
1565              (Allocation_Address => Header.Allocation_Address,
1566               Alloc_Traceback    => Header.Alloc_Traceback,
1567               Dealloc_Traceback  => To_Traceback
1568                 (Find_Or_Create_Traceback
1569                      (Pool, Dealloc,
1570                       Header.Block_Size,
1571                       Deallocate_Label'Address,
1572                       Code_Address_For_Deallocate_End)),
1573               Next               => System.Null_Address,
1574               Block_Size         => -Header.Block_Size);
1575
1576            if Pool.Reset_Content_On_Free then
1577               Set_Dead_Beef (Storage_Address, -Header.Block_Size);
1578            end if;
1579
1580            Pool.Logically_Deallocated :=
1581              Pool.Logically_Deallocated + Byte_Count (-Header.Block_Size);
1582
1583            --  Link this free block with the others (at the end of the list,
1584            --  so that we can start releasing the older blocks first later on)
1585
1586            if Pool.First_Free_Block = System.Null_Address then
1587               Pool.First_Free_Block := Storage_Address;
1588               Pool.Last_Free_Block := Storage_Address;
1589
1590            else
1591               Header_Of (Pool.Last_Free_Block).Next := Storage_Address;
1592               Pool.Last_Free_Block := Storage_Address;
1593            end if;
1594
1595            --  Do not physically release the memory here, but in Alloc.
1596            --  See comment there for details.
1597         end if;
1598      end;
1599
1600      if not Valid then
1601         if Storage_Address = System.Null_Address then
1602            if Pool.Raise_Exceptions and then
1603              Size_In_Storage_Elements /= Storage_Count'Last
1604            then
1605               raise Freeing_Not_Allocated_Storage;
1606            else
1607               Put (Output_File (Pool),
1608                    "error: Freeing Null_Address, at ");
1609               Put_Line (Output_File (Pool), Pool.Stack_Trace_Depth, null,
1610                         Deallocate_Label'Address,
1611                         Code_Address_For_Deallocate_End);
1612               return;
1613            end if;
1614         end if;
1615
1616         if Allow_Unhandled_Memory
1617           and then not Is_Handled (Storage_Address)
1618         then
1619            System.CRTL.free (Storage_Address);
1620            return;
1621         end if;
1622
1623         if Pool.Raise_Exceptions
1624           and then Size_In_Storage_Elements /= Storage_Count'Last
1625         then
1626            raise Freeing_Not_Allocated_Storage;
1627         else
1628            Put (Output_File (Pool),
1629                 "error: Freeing not allocated storage, at ");
1630            Put_Line (Output_File (Pool), Pool.Stack_Trace_Depth, null,
1631                      Deallocate_Label'Address,
1632                      Code_Address_For_Deallocate_End);
1633         end if;
1634
1635      elsif Header_Block_Size_Was_Less_Than_0 then
1636         if Pool.Raise_Exceptions then
1637            raise Freeing_Deallocated_Storage;
1638         else
1639            Put (Output_File (Pool),
1640                 "error: Freeing already deallocated storage, at ");
1641            Put_Line (Output_File (Pool), Pool.Stack_Trace_Depth, null,
1642                      Deallocate_Label'Address,
1643                      Code_Address_For_Deallocate_End);
1644            Print_Traceback (Output_File (Pool),
1645                             "   Memory already deallocated at ",
1646                            To_Traceback (Header.Dealloc_Traceback));
1647            Print_Traceback (Output_File (Pool), "   Memory was allocated at ",
1648                             Header.Alloc_Traceback);
1649         end if;
1650      end if;
1651   end Deallocate;
1652
1653   --------------------
1654   -- Deallocate_End --
1655   --------------------
1656
1657   --  DO NOT MOVE, this must be right after Deallocate
1658
1659   --  See Allocate_End
1660
1661   --  This is making assumptions about code order that may be invalid ???
1662
1663   procedure Deallocate_End is
1664   begin
1665      <<Deallocate_End_Label>>
1666      Code_Address_For_Deallocate_End := Deallocate_End_Label'Address;
1667   end Deallocate_End;
1668
1669   -----------------
1670   -- Dereference --
1671   -----------------
1672
1673   procedure Dereference
1674     (Pool                     : in out Debug_Pool;
1675      Storage_Address          : Address;
1676      Size_In_Storage_Elements : Storage_Count;
1677      Alignment                : Storage_Count)
1678   is
1679      pragma Unreferenced (Alignment, Size_In_Storage_Elements);
1680
1681      Valid   : constant Boolean := Is_Valid (Storage_Address);
1682      Header  : Allocation_Header_Access;
1683
1684   begin
1685      --  Locking policy: we do not do any locking in this procedure. The
1686      --  tables are only read, not written to, and although a problem might
1687      --  appear if someone else is modifying the tables at the same time, this
1688      --  race condition is not intended to be detected by this storage_pool (a
1689      --  now invalid pointer would appear as valid). Instead, we prefer
1690      --  optimum performance for dereferences.
1691
1692      <<Dereference_Label>>
1693
1694      if not Valid then
1695         if Pool.Raise_Exceptions then
1696            raise Accessing_Not_Allocated_Storage;
1697         else
1698            Put (Output_File (Pool),
1699                 "error: Accessing not allocated storage, at ");
1700            Put_Line (Output_File (Pool), Pool.Stack_Trace_Depth, null,
1701                      Dereference_Label'Address,
1702                      Code_Address_For_Dereference_End);
1703         end if;
1704
1705      else
1706         Header := Header_Of (Storage_Address);
1707
1708         if Header.Block_Size < 0 then
1709            if Pool.Raise_Exceptions then
1710               raise Accessing_Deallocated_Storage;
1711            else
1712               Put (Output_File (Pool),
1713                    "error: Accessing deallocated storage, at ");
1714               Put_Line
1715                 (Output_File (Pool), Pool.Stack_Trace_Depth, null,
1716                  Dereference_Label'Address,
1717                  Code_Address_For_Dereference_End);
1718               Print_Traceback (Output_File (Pool), "  First deallocation at ",
1719                                To_Traceback (Header.Dealloc_Traceback));
1720               Print_Traceback (Output_File (Pool), "  Initial allocation at ",
1721                                Header.Alloc_Traceback);
1722            end if;
1723         end if;
1724      end if;
1725   end Dereference;
1726
1727   ---------------------
1728   -- Dereference_End --
1729   ---------------------
1730
1731   --  DO NOT MOVE: this must be right after Dereference
1732
1733   --  See Allocate_End
1734
1735   --  This is making assumptions about code order that may be invalid ???
1736
1737   procedure Dereference_End is
1738   begin
1739      <<Dereference_End_Label>>
1740      Code_Address_For_Dereference_End := Dereference_End_Label'Address;
1741   end Dereference_End;
1742
1743   ----------------
1744   -- Print_Info --
1745   ----------------
1746
1747   procedure Print_Info
1748     (Pool          : Debug_Pool;
1749      Cumulate      : Boolean := False;
1750      Display_Slots : Boolean := False;
1751      Display_Leaks : Boolean := False)
1752   is
1753      package Backtrace_Htable_Cumulate is new GNAT.HTable.Static_HTable
1754        (Header_Num => Header,
1755         Element    => Traceback_Htable_Elem,
1756         Elmt_Ptr   => Traceback_Htable_Elem_Ptr,
1757         Null_Ptr   => null,
1758         Set_Next   => Set_Next,
1759         Next       => Next,
1760         Key        => Tracebacks_Array_Access,
1761         Get_Key    => Get_Key,
1762         Hash       => Hash,
1763         Equal      => Equal);
1764      --  This needs a comment ??? probably some of the ones below do too???
1765
1766      Current : System.Address;
1767      Data    : Traceback_Htable_Elem_Ptr;
1768      Elem    : Traceback_Htable_Elem_Ptr;
1769      Header  : Allocation_Header_Access;
1770      K       : Traceback_Kind;
1771
1772   begin
1773      Put_Line
1774        ("Total allocated bytes : " &
1775         Byte_Count'Image (Pool.Allocated));
1776
1777      Put_Line
1778        ("Total logically deallocated bytes : " &
1779         Byte_Count'Image (Pool.Logically_Deallocated));
1780
1781      Put_Line
1782        ("Total physically deallocated bytes : " &
1783         Byte_Count'Image (Pool.Physically_Deallocated));
1784
1785      if Pool.Marked_Blocks_Deallocated then
1786         Put_Line ("Marked blocks were physically deallocated. This is");
1787         Put_Line ("potentially dangerous, and you might want to run");
1788         Put_Line ("again with a lower value of Minimum_To_Free");
1789      end if;
1790
1791      Put_Line
1792        ("Current Water Mark: " &
1793         Byte_Count'Image (Pool.Current_Water_Mark));
1794
1795      Put_Line
1796        ("High Water Mark: " &
1797          Byte_Count'Image (Pool.High_Water));
1798
1799      Put_Line ("");
1800
1801      if Display_Slots then
1802         Data := Backtrace_Htable.Get_First;
1803         while Data /= null loop
1804            if Data.Kind in Alloc .. Dealloc then
1805               Elem :=
1806                 new Traceback_Htable_Elem'
1807                       (Traceback => new Tracebacks_Array'(Data.Traceback.all),
1808                        Count       => Data.Count,
1809                        Kind        => Data.Kind,
1810                        Total       => Data.Total,
1811                        Frees       => Data.Frees,
1812                        Total_Frees => Data.Total_Frees,
1813                        Next        => null);
1814               Backtrace_Htable_Cumulate.Set (Elem);
1815
1816               if Cumulate then
1817                  K := (if Data.Kind = Alloc then Indirect_Alloc
1818                                             else Indirect_Dealloc);
1819
1820                  --  Propagate the direct call to all its parents
1821
1822                  for T in Data.Traceback'First + 1 .. Data.Traceback'Last loop
1823                     Elem := Backtrace_Htable_Cumulate.Get
1824                       (Data.Traceback
1825                          (T .. Data.Traceback'Last)'Unrestricted_Access);
1826
1827                     --  If not, insert it
1828
1829                     if Elem = null then
1830                        Elem :=
1831                          new Traceback_Htable_Elem'
1832                                (Traceback =>
1833                                   new Tracebacks_Array'
1834                                         (Data.Traceback
1835                                           (T .. Data.Traceback'Last)),
1836                                 Count       => Data.Count,
1837                                 Kind        => K,
1838                                 Total       => Data.Total,
1839                                 Frees       => Data.Frees,
1840                                 Total_Frees => Data.Total_Frees,
1841                                 Next        => null);
1842                        Backtrace_Htable_Cumulate.Set (Elem);
1843
1844                        --  Properly take into account that the subprograms
1845                        --  indirectly called might be doing either allocations
1846                        --  or deallocations. This needs to be reflected in the
1847                        --  counts.
1848
1849                     else
1850                        Elem.Count := Elem.Count + Data.Count;
1851
1852                        if K = Elem.Kind then
1853                           Elem.Total := Elem.Total + Data.Total;
1854
1855                        elsif Elem.Total > Data.Total then
1856                           Elem.Total := Elem.Total - Data.Total;
1857
1858                        else
1859                           Elem.Kind  := K;
1860                           Elem.Total := Data.Total - Elem.Total;
1861                        end if;
1862                     end if;
1863                  end loop;
1864               end if;
1865
1866               Data := Backtrace_Htable.Get_Next;
1867            end if;
1868         end loop;
1869
1870         Put_Line ("List of allocations/deallocations: ");
1871
1872         Data := Backtrace_Htable_Cumulate.Get_First;
1873         while Data /= null loop
1874            case Data.Kind is
1875               when Alloc            => Put ("alloc (count:");
1876               when Indirect_Alloc   => Put ("indirect alloc (count:");
1877               when Dealloc          => Put ("free  (count:");
1878               when Indirect_Dealloc => Put ("indirect free  (count:");
1879            end case;
1880
1881            Put (Natural'Image (Data.Count) & ", total:" &
1882                 Byte_Count'Image (Data.Total) & ") ");
1883
1884            for T in Data.Traceback'Range loop
1885               Put (Image_C (PC_For (Data.Traceback (T))) & ' ');
1886            end loop;
1887
1888            Put_Line ("");
1889
1890            Data := Backtrace_Htable_Cumulate.Get_Next;
1891         end loop;
1892
1893         Backtrace_Htable_Cumulate.Reset;
1894      end if;
1895
1896      if Display_Leaks then
1897         Put_Line ("");
1898         Put_Line ("List of not deallocated blocks:");
1899
1900         --  Do not try to group the blocks with the same stack traces
1901         --  together. This is done by the gnatmem output.
1902
1903         Current := Pool.First_Used_Block;
1904         while Current /= System.Null_Address loop
1905            Header := Header_Of (Current);
1906
1907            Put ("Size: " & Storage_Count'Image (Header.Block_Size) & " at: ");
1908
1909            if Header.Alloc_Traceback /= null then
1910               for T in Header.Alloc_Traceback.Traceback'Range loop
1911                  Put (Image_C
1912                       (PC_For (Header.Alloc_Traceback.Traceback (T))) & ' ');
1913               end loop;
1914            end if;
1915
1916            Put_Line ("");
1917            Current := Header.Next;
1918         end loop;
1919      end if;
1920   end Print_Info;
1921
1922   ----------
1923   -- Dump --
1924   ----------
1925
1926   procedure Dump
1927     (Pool   : Debug_Pool;
1928      Size   : Positive;
1929      Report : Report_Type := All_Reports)
1930   is
1931      procedure Do_Report (Sort : Report_Type);
1932      --  Do a specific type of report
1933
1934      ---------------
1935      -- Do_Report --
1936      ---------------
1937
1938      procedure Do_Report (Sort : Report_Type) is
1939         Elem        : Traceback_Htable_Elem_Ptr;
1940         Bigger      : Boolean;
1941         Grand_Total : Float;
1942
1943         Max  : array (1 .. Size) of Traceback_Htable_Elem_Ptr :=
1944           (others => null);
1945         --  Sorted array for the biggest memory users
1946
1947         Allocated_In_Pool : Byte_Count;
1948         --  safe thread Pool.Allocated
1949
1950         Elem_Safe : Traceback_Htable_Elem;
1951         --  safe thread current elem.all;
1952
1953         Max_M_Safe : Traceback_Htable_Elem;
1954         --  safe thread Max(M).all
1955
1956      begin
1957         Put_Line ("");
1958
1959         case Sort is
1960            when All_Reports
1961               | Memory_Usage
1962            =>
1963               Put_Line (Size'Img & " biggest memory users at this time:");
1964               Put_Line ("Results include bytes and chunks still allocated");
1965               Grand_Total := Float (Pool.Current_Water_Mark);
1966
1967            when Allocations_Count =>
1968               Put_Line (Size'Img & " biggest number of live allocations:");
1969               Put_Line ("Results include bytes and chunks still allocated");
1970               Grand_Total := Float (Pool.Current_Water_Mark);
1971
1972            when Sort_Total_Allocs =>
1973               Put_Line (Size'Img & " biggest number of allocations:");
1974               Put_Line ("Results include total bytes and chunks allocated,");
1975               Put_Line ("even if no longer allocated - Deallocations are"
1976                         & " ignored");
1977
1978               declare
1979                  Lock : Scope_Lock;
1980                  pragma Unreferenced (Lock);
1981               begin
1982                  Allocated_In_Pool := Pool.Allocated;
1983               end;
1984
1985               Grand_Total := Float (Allocated_In_Pool);
1986
1987            when Marked_Blocks =>
1988               Put_Line ("Special blocks marked by Mark_Traceback");
1989               Grand_Total := 0.0;
1990         end case;
1991
1992         declare
1993            Lock : Scope_Lock;
1994            pragma Unreferenced (Lock);
1995         begin
1996            Elem := Backtrace_Htable.Get_First;
1997         end;
1998
1999         while Elem /= null loop
2000            declare
2001               Lock : Scope_Lock;
2002               pragma Unreferenced (Lock);
2003            begin
2004               Elem_Safe := Elem.all;
2005            end;
2006
2007            --  Handle only alloc elememts
2008            if Elem_Safe.Kind = Alloc then
2009               --  Ignore small blocks (depending on the sorting criteria) to
2010               --  gain speed.
2011
2012               if (Sort = Memory_Usage
2013                    and then Elem_Safe.Total - Elem_Safe.Total_Frees >= 1_000)
2014                 or else (Sort = Allocations_Count
2015                           and then Elem_Safe.Count - Elem_Safe.Frees >= 1)
2016                 or else (Sort = Sort_Total_Allocs
2017                           and then Elem_Safe.Count > 1)
2018                 or else (Sort = Marked_Blocks
2019                           and then Elem_Safe.Total = 0)
2020               then
2021                  if Sort = Marked_Blocks then
2022                     Grand_Total := Grand_Total + Float (Elem_Safe.Count);
2023                  end if;
2024
2025                  for M in Max'Range loop
2026                     Bigger := Max (M) = null;
2027                     if not Bigger then
2028                        declare
2029                           Lock : Scope_Lock;
2030                           pragma Unreferenced (Lock);
2031                        begin
2032                           Max_M_Safe := Max (M).all;
2033                        end;
2034
2035                        case Sort is
2036                           when All_Reports
2037                              | Memory_Usage
2038                           =>
2039                              Bigger :=
2040                                Max_M_Safe.Total - Max_M_Safe.Total_Frees
2041                                  < Elem_Safe.Total - Elem_Safe.Total_Frees;
2042
2043                           when Allocations_Count =>
2044                              Bigger :=
2045                                Max_M_Safe.Count - Max_M_Safe.Frees
2046                                  < Elem_Safe.Count - Elem_Safe.Frees;
2047
2048                           when Marked_Blocks
2049                              | Sort_Total_Allocs
2050                           =>
2051                              Bigger := Max_M_Safe.Count < Elem_Safe.Count;
2052                        end case;
2053                     end if;
2054
2055                     if Bigger then
2056                        Max (M + 1 .. Max'Last) := Max (M .. Max'Last - 1);
2057                        Max (M) := Elem;
2058                        exit;
2059                     end if;
2060                  end loop;
2061               end if;
2062            end if;
2063
2064            declare
2065               Lock : Scope_Lock;
2066               pragma Unreferenced (Lock);
2067            begin
2068               Elem := Backtrace_Htable.Get_Next;
2069            end;
2070         end loop;
2071
2072         if Grand_Total = 0.0 then
2073            Grand_Total := 1.0;
2074         end if;
2075
2076         for M in Max'Range loop
2077            exit when Max (M) = null;
2078            declare
2079               type Percent is delta 0.1 range 0.0 .. 100.0;
2080
2081               P     : Percent;
2082               Total : Byte_Count;
2083
2084            begin
2085               declare
2086                  Lock : Scope_Lock;
2087                  pragma Unreferenced (Lock);
2088               begin
2089                  Max_M_Safe := Max (M).all;
2090               end;
2091
2092               case Sort is
2093                  when All_Reports
2094                     | Allocations_Count
2095                     | Memory_Usage
2096                  =>
2097                     Total := Max_M_Safe.Total - Max_M_Safe.Total_Frees;
2098
2099                  when Sort_Total_Allocs =>
2100                     Total := Max_M_Safe.Total;
2101
2102                  when Marked_Blocks =>
2103                     Total := Byte_Count (Max_M_Safe.Count);
2104               end case;
2105
2106               declare
2107                  Normalized_Total : constant Float := Float (Total);
2108                  --  In multi tasking configuration, memory deallocations
2109                  --  during Do_Report processing can lead to Total >
2110                  --  Grand_Total. As Percent requires Total <= Grand_Total
2111
2112               begin
2113                  if Normalized_Total > Grand_Total then
2114                     P := 100.0;
2115                  else
2116                     P := Percent (100.0 * Normalized_Total / Grand_Total);
2117                  end if;
2118               end;
2119
2120               case Sort is
2121                  when All_Reports
2122                     | Allocations_Count
2123                     | Memory_Usage
2124                  =>
2125                     declare
2126                        Count : constant Natural :=
2127                          Max_M_Safe.Count - Max_M_Safe.Frees;
2128                     begin
2129                        Put (P'Img & "%:" & Total'Img & " bytes in"
2130                             & Count'Img & " chunks at");
2131                     end;
2132
2133                  when Sort_Total_Allocs =>
2134                     Put (P'Img & "%:" & Total'Img & " bytes in"
2135                          & Max_M_Safe.Count'Img & " chunks at");
2136
2137                  when Marked_Blocks =>
2138                     Put (P'Img & "%:"
2139                          & Max_M_Safe.Count'Img & " chunks /"
2140                          & Integer (Grand_Total)'Img & " at");
2141               end case;
2142            end;
2143
2144            for J in Max (M).Traceback'Range loop
2145               Put (" " & Image_C (PC_For (Max (M).Traceback (J))));
2146            end loop;
2147
2148            Put_Line ("");
2149         end loop;
2150      end Do_Report;
2151
2152      --  Local variables
2153
2154      Total_Freed : Byte_Count;
2155      --  safe thread pool logically & physically deallocated
2156
2157      Traceback_Elements_Allocated : Byte_Count;
2158      --  safe thread Traceback_Count
2159
2160      Validity_Elements_Allocated : Byte_Count;
2161      --  safe thread Validity_Count
2162
2163      Ada_Allocs_Bytes : Byte_Count;
2164      --  safe thread pool Allocated
2165
2166      Ada_Allocs_Chunks : Byte_Count;
2167      --  safe thread pool Alloc_Count
2168
2169      Ada_Free_Chunks : Byte_Count;
2170      --  safe thread pool Free_Count
2171
2172   --  Start of processing for Dump
2173
2174   begin
2175      declare
2176         Lock : Scope_Lock;
2177         pragma Unreferenced (Lock);
2178      begin
2179         Total_Freed :=
2180           Pool.Logically_Deallocated + Pool.Physically_Deallocated;
2181         Traceback_Elements_Allocated := Traceback_Count;
2182         Validity_Elements_Allocated := Validity_Count;
2183         Ada_Allocs_Bytes := Pool.Allocated;
2184         Ada_Allocs_Chunks := Pool.Alloc_Count;
2185         Ada_Free_Chunks := Pool.Free_Count;
2186      end;
2187
2188      Put_Line
2189        ("Traceback elements allocated: " & Traceback_Elements_Allocated'Img);
2190      Put_Line
2191        ("Validity elements allocated: " & Validity_Elements_Allocated'Img);
2192      Put_Line ("");
2193
2194      Put_Line ("Ada Allocs:" & Ada_Allocs_Bytes'Img
2195                & " bytes in" & Ada_Allocs_Chunks'Img & " chunks");
2196      Put_Line ("Ada Free:" & Total_Freed'Img & " bytes in" &
2197                  Ada_Free_Chunks'Img
2198                & " chunks");
2199      Put_Line ("Ada Current watermark: "
2200                & Byte_Count'Image (Pool.Current_Water_Mark)
2201                & " in" & Byte_Count'Image (Ada_Allocs_Chunks -
2202                    Ada_Free_Chunks) & " chunks");
2203      Put_Line ("Ada High watermark: " & Pool.High_Water_Mark'Img);
2204
2205      case Report is
2206         when All_Reports =>
2207            for Sort in Report_Type loop
2208               if Sort /= All_Reports then
2209                  Do_Report (Sort);
2210               end if;
2211            end loop;
2212
2213         when others =>
2214            Do_Report (Report);
2215      end case;
2216   end Dump;
2217
2218   -----------------
2219   -- Dump_Stdout --
2220   -----------------
2221
2222   procedure Dump_Stdout
2223     (Pool   : Debug_Pool;
2224      Size   : Positive;
2225      Report : Report_Type := All_Reports)
2226   is
2227      procedure Internal is new Dump
2228        (Put_Line => Stdout_Put_Line,
2229         Put      => Stdout_Put);
2230
2231   --  Start of processing for Dump_Stdout
2232
2233   begin
2234      Internal (Pool, Size, Report);
2235   end Dump_Stdout;
2236
2237   -----------
2238   -- Reset --
2239   -----------
2240
2241   procedure Reset is
2242      Elem : Traceback_Htable_Elem_Ptr;
2243      Lock : Scope_Lock;
2244      pragma Unreferenced (Lock);
2245   begin
2246      Elem := Backtrace_Htable.Get_First;
2247      while Elem /= null loop
2248         Elem.Count := 0;
2249         Elem.Frees := 0;
2250         Elem.Total := 0;
2251         Elem.Total_Frees := 0;
2252         Elem := Backtrace_Htable.Get_Next;
2253      end loop;
2254   end Reset;
2255
2256   ------------------
2257   -- Storage_Size --
2258   ------------------
2259
2260   function Storage_Size (Pool : Debug_Pool) return Storage_Count is
2261      pragma Unreferenced (Pool);
2262   begin
2263      return Storage_Count'Last;
2264   end Storage_Size;
2265
2266   ---------------------
2267   -- High_Water_Mark --
2268   ---------------------
2269
2270   function High_Water_Mark (Pool : Debug_Pool) return Byte_Count is
2271      Lock : Scope_Lock;
2272      pragma Unreferenced (Lock);
2273   begin
2274      return Pool.High_Water;
2275   end High_Water_Mark;
2276
2277   ------------------------
2278   -- Current_Water_Mark --
2279   ------------------------
2280
2281   function Current_Water_Mark (Pool : Debug_Pool) return Byte_Count is
2282      Lock : Scope_Lock;
2283      pragma Unreferenced (Lock);
2284   begin
2285      return Pool.Allocated - Pool.Logically_Deallocated -
2286        Pool.Physically_Deallocated;
2287   end Current_Water_Mark;
2288
2289   ------------------------------
2290   -- System_Memory_Debug_Pool --
2291   ------------------------------
2292
2293   procedure System_Memory_Debug_Pool
2294     (Has_Unhandled_Memory : Boolean := True)
2295   is
2296      Lock : Scope_Lock;
2297      pragma Unreferenced (Lock);
2298   begin
2299      System_Memory_Debug_Pool_Enabled := True;
2300      Allow_Unhandled_Memory := Has_Unhandled_Memory;
2301   end System_Memory_Debug_Pool;
2302
2303   ---------------
2304   -- Configure --
2305   ---------------
2306
2307   procedure Configure
2308     (Pool                           : in out Debug_Pool;
2309      Stack_Trace_Depth              : Natural := Default_Stack_Trace_Depth;
2310      Maximum_Logically_Freed_Memory : SSC     := Default_Max_Freed;
2311      Minimum_To_Free                : SSC     := Default_Min_Freed;
2312      Reset_Content_On_Free          : Boolean := Default_Reset_Content;
2313      Raise_Exceptions               : Boolean := Default_Raise_Exceptions;
2314      Advanced_Scanning              : Boolean := Default_Advanced_Scanning;
2315      Errors_To_Stdout               : Boolean := Default_Errors_To_Stdout;
2316      Low_Level_Traces               : Boolean := Default_Low_Level_Traces)
2317   is
2318      Lock : Scope_Lock;
2319      pragma Unreferenced (Lock);
2320   begin
2321      Pool.Stack_Trace_Depth              := Stack_Trace_Depth;
2322      Pool.Maximum_Logically_Freed_Memory := Maximum_Logically_Freed_Memory;
2323      Pool.Reset_Content_On_Free          := Reset_Content_On_Free;
2324      Pool.Raise_Exceptions               := Raise_Exceptions;
2325      Pool.Minimum_To_Free                := Minimum_To_Free;
2326      Pool.Advanced_Scanning              := Advanced_Scanning;
2327      Pool.Errors_To_Stdout               := Errors_To_Stdout;
2328      Pool.Low_Level_Traces               := Low_Level_Traces;
2329   end Configure;
2330
2331   ----------------
2332   -- Print_Pool --
2333   ----------------
2334
2335   procedure Print_Pool (A : System.Address) is
2336      Storage : constant Address := A;
2337      Valid   : constant Boolean := Is_Valid (Storage);
2338      Header  : Allocation_Header_Access;
2339
2340   begin
2341      --  We might get Null_Address if the call from gdb was done incorrectly.
2342      --  For instance, doing a "print_pool(my_var)" passes 0x0, instead of
2343      --  passing the value of my_var.
2344
2345      if A = System.Null_Address then
2346         Put_Line
2347            (Standard_Output, "Memory not under control of the storage pool");
2348         return;
2349      end if;
2350
2351      if not Valid then
2352         Put_Line
2353            (Standard_Output, "Memory not under control of the storage pool");
2354
2355      else
2356         Header := Header_Of (Storage);
2357         Print_Address (Standard_Output, A);
2358         Put_Line (Standard_Output, " allocated at:");
2359         Print_Traceback (Standard_Output, "", Header.Alloc_Traceback);
2360
2361         if To_Traceback (Header.Dealloc_Traceback) /= null then
2362            Print_Address (Standard_Output, A);
2363            Put_Line (Standard_Output,
2364                      " logically freed memory, deallocated at:");
2365            Print_Traceback (Standard_Output, "",
2366                             To_Traceback (Header.Dealloc_Traceback));
2367         end if;
2368      end if;
2369   end Print_Pool;
2370
2371   -----------------------
2372   -- Print_Info_Stdout --
2373   -----------------------
2374
2375   procedure Print_Info_Stdout
2376     (Pool          : Debug_Pool;
2377      Cumulate      : Boolean := False;
2378      Display_Slots : Boolean := False;
2379      Display_Leaks : Boolean := False)
2380   is
2381      procedure Internal is new Print_Info
2382        (Put_Line => Stdout_Put_Line,
2383         Put      => Stdout_Put);
2384
2385   --  Start of processing for Print_Info_Stdout
2386
2387   begin
2388      Internal (Pool, Cumulate, Display_Slots, Display_Leaks);
2389   end Print_Info_Stdout;
2390
2391   ------------------
2392   -- Dump_Gnatmem --
2393   ------------------
2394
2395   procedure Dump_Gnatmem (Pool : Debug_Pool; File_Name : String) is
2396      type File_Ptr is new System.Address;
2397
2398      function fopen (Path : String; Mode : String) return File_Ptr;
2399      pragma Import (C, fopen);
2400
2401      procedure fwrite
2402        (Ptr    : System.Address;
2403         Size   : size_t;
2404         Nmemb  : size_t;
2405         Stream : File_Ptr);
2406
2407      procedure fwrite
2408        (Str    : String;
2409         Size   : size_t;
2410         Nmemb  : size_t;
2411         Stream : File_Ptr);
2412      pragma Import (C, fwrite);
2413
2414      procedure fputc (C : Integer; Stream : File_Ptr);
2415      pragma Import (C, fputc);
2416
2417      procedure fclose (Stream : File_Ptr);
2418      pragma Import (C, fclose);
2419
2420      Address_Size : constant size_t :=
2421                       System.Address'Max_Size_In_Storage_Elements;
2422      --  Size in bytes of a pointer
2423
2424      File        : File_Ptr;
2425      Current     : System.Address;
2426      Header      : Allocation_Header_Access;
2427      Actual_Size : size_t;
2428      Num_Calls   : Integer;
2429      Tracebk     : Tracebacks_Array_Access;
2430      Dummy_Time  : Duration := 1.0;
2431
2432   begin
2433      File := fopen (File_Name & ASCII.NUL, "wb" & ASCII.NUL);
2434      fwrite ("GMEM DUMP" & ASCII.LF, 10, 1, File);
2435
2436      fwrite
2437        (Ptr    => Dummy_Time'Address,
2438         Size   => Duration'Max_Size_In_Storage_Elements,
2439         Nmemb  => 1,
2440         Stream => File);
2441
2442      --  List of not deallocated blocks (see Print_Info)
2443
2444      Current := Pool.First_Used_Block;
2445      while Current /= System.Null_Address loop
2446         Header := Header_Of (Current);
2447
2448         Actual_Size := size_t (Header.Block_Size);
2449
2450         if Header.Alloc_Traceback /= null then
2451            Tracebk   := Header.Alloc_Traceback.Traceback;
2452            Num_Calls := Tracebk'Length;
2453
2454            --  (Code taken from memtrack.adb in GNAT's sources)
2455
2456            --  Logs allocation call using the format:
2457
2458            --  'A' <mem addr> <size chunk> <len backtrace> <addr1> ... <addrn>
2459
2460            fputc (Character'Pos ('A'), File);
2461            fwrite (Current'Address, Address_Size, 1, File);
2462
2463            fwrite
2464              (Ptr    => Actual_Size'Address,
2465               Size   => size_t'Max_Size_In_Storage_Elements,
2466               Nmemb  => 1,
2467               Stream => File);
2468
2469            fwrite
2470              (Ptr    => Dummy_Time'Address,
2471               Size   => Duration'Max_Size_In_Storage_Elements,
2472               Nmemb  => 1,
2473               Stream => File);
2474
2475            fwrite
2476              (Ptr    => Num_Calls'Address,
2477               Size   => Integer'Max_Size_In_Storage_Elements,
2478               Nmemb  => 1,
2479               Stream => File);
2480
2481            for J in Tracebk'First .. Tracebk'First + Num_Calls - 1 loop
2482               declare
2483                  Ptr : System.Address := PC_For (Tracebk (J));
2484               begin
2485                  fwrite (Ptr'Address, Address_Size, 1, File);
2486               end;
2487            end loop;
2488         end if;
2489
2490         Current := Header.Next;
2491      end loop;
2492
2493      fclose (File);
2494   end Dump_Gnatmem;
2495
2496   ----------------
2497   -- Stdout_Put --
2498   ----------------
2499
2500   procedure Stdout_Put (S : String) is
2501   begin
2502      Put (Standard_Output, S);
2503   end Stdout_Put;
2504
2505   ---------------------
2506   -- Stdout_Put_Line --
2507   ---------------------
2508
2509   procedure Stdout_Put_Line (S : String) is
2510   begin
2511      Put_Line (Standard_Output, S);
2512   end Stdout_Put_Line;
2513
2514--  Package initialization
2515
2516begin
2517   Allocate_End;
2518   Deallocate_End;
2519   Dereference_End;
2520end GNAT.Debug_Pools;
2521