| ------------------------------------------------------------------------------ |
| -- -- |
| -- GNAT RUN-TIME COMPONENTS -- |
| -- -- |
| -- S Y S T E M . M E M O R Y -- |
| -- -- |
| -- B o d y -- |
| -- -- |
| -- Copyright (C) 2001-2022, Free Software Foundation, Inc. -- |
| -- -- |
| -- GNAT is free software; you can redistribute it and/or modify it under -- |
| -- terms of the GNU General Public License as published by the Free Soft- -- |
| -- ware Foundation; either version 3, or (at your option) any later ver- -- |
| -- sion. GNAT is distributed in the hope that it will be useful, but WITH- -- |
| -- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -- |
| -- or FITNESS FOR A PARTICULAR PURPOSE. -- |
| -- -- |
| -- As a special exception under Section 7 of GPL version 3, you are granted -- |
| -- additional permissions described in the GCC Runtime Library Exception, -- |
| -- version 3.1, as published by the Free Software Foundation. -- |
| -- -- |
| -- You should have received a copy of the GNU General Public License and -- |
| -- a copy of the GCC Runtime Library Exception along with this program; -- |
| -- see the files COPYING3 and COPYING.RUNTIME respectively. If not, see -- |
| -- <http://www.gnu.org/licenses/>. -- |
| -- -- |
| -- GNAT was originally developed by the GNAT team at New York University. -- |
| -- Extensive contributions were provided by Ada Core Technologies Inc. -- |
| -- -- |
| ------------------------------------------------------------------------------ |
| |
| -- This version contains allocation tracking capability |
| |
| -- The object file corresponding to this instrumented version is to be found |
| -- in libgmem. |
| |
| -- When enabled, the subsystem logs all the calls to __gnat_malloc and |
| -- __gnat_free. This log can then be processed by gnatmem to detect |
| -- dynamic memory leaks. |
| |
| -- To use this functionality, you must compile your application with -g |
| -- and then link with this object file: |
| |
| -- gnatmake -g program -largs -lgmem |
| |
| -- After compilation, you may use your program as usual except that upon |
| -- completion, it will generate in the current directory the file gmem.out. |
| |
| -- You can then investigate for possible memory leaks and mismatch by calling |
| -- gnatmem with this file as an input: |
| |
| -- gnatmem -i gmem.out program |
| |
| -- See gnatmem section in the GNAT User's Guide for more details |
| |
| -- NOTE: This capability is currently supported on the following targets: |
| |
| -- Windows |
| -- AIX |
| -- GNU/Linux |
| -- HP-UX |
| -- Solaris |
| |
| -- NOTE FOR FUTURE PLATFORMS SUPPORT: It is assumed that type Duration is |
| -- 64 bit. If the need arises to support architectures where this assumption |
| -- is incorrect, it will require changing the way timestamps of allocation |
| -- events are recorded. |
| |
| pragma Source_File_Name (System.Memory, Body_File_Name => "memtrack.adb"); |
| |
| with Ada.Exceptions; |
| with GNAT.IO; |
| |
| with System.Soft_Links; |
| with System.Traceback; |
| with System.Traceback_Entries; |
| with System.CRTL; |
| with System.OS_Lib; |
| with System.OS_Primitives; |
| |
| package body System.Memory is |
| |
| use Ada.Exceptions; |
| use System.Soft_Links; |
| use System.Traceback; |
| use System.Traceback_Entries; |
| use GNAT.IO; |
| |
| function c_malloc (Size : size_t) return System.Address; |
| pragma Import (C, c_malloc, "malloc"); |
| |
| procedure c_free (Ptr : System.Address); |
| pragma Import (C, c_free, "free"); |
| |
| function c_realloc |
| (Ptr : System.Address; Size : size_t) return System.Address; |
| pragma Import (C, c_realloc, "realloc"); |
| |
| In_Child_After_Fork : Integer; |
| pragma Import (C, In_Child_After_Fork, "__gnat_in_child_after_fork"); |
| |
| subtype File_Ptr is CRTL.FILEs; |
| |
| procedure Write (Ptr : System.Address; Size : size_t); |
| |
| procedure Putc (Char : Character); |
| |
| procedure Finalize; |
| pragma Export (C, Finalize, "__gnat_finalize"); |
| -- Replace the default __gnat_finalize to properly close the log file |
| |
| Address_Size : constant := System.Address'Max_Size_In_Storage_Elements; |
| -- Size in bytes of a pointer |
| |
| Max_Call_Stack : constant := 200; |
| -- Maximum number of frames supported |
| |
| Tracebk : Tracebacks_Array (1 .. Max_Call_Stack); |
| Num_Calls : aliased Integer := 0; |
| |
| Gmemfname : constant String := "gmem.out" & ASCII.NUL; |
| -- Allocation log of a program is saved in a file gmem.out |
| -- ??? What about Ada.Command_Line.Command_Name & ".out" instead of static |
| -- gmem.out |
| |
| Gmemfile : File_Ptr; |
| -- Global C file pointer to the allocation log |
| |
| Needs_Init : Boolean := True; |
| -- Reset after first call to Gmem_Initialize |
| |
| procedure Gmem_Initialize; |
| -- Initialization routine; opens the file and writes a header string. This |
| -- header string is used as a magic-tag to know if the .out file is to be |
| -- handled by GDB or by the GMEM (instrumented malloc/free) implementation. |
| |
| First_Call : Boolean := True; |
| -- Depending on implementation, some of the traceback routines may |
| -- themselves do dynamic allocation. We use First_Call flag to avoid |
| -- infinite recursion |
| |
| function Allow_Trace return Boolean; |
| pragma Inline (Allow_Trace); |
| -- Check if the memory trace is allowed |
| |
| ----------------- |
| -- Allow_Trace -- |
| ----------------- |
| |
| function Allow_Trace return Boolean is |
| begin |
| if First_Call then |
| First_Call := False; |
| return In_Child_After_Fork = 0; |
| else |
| return False; |
| end if; |
| end Allow_Trace; |
| |
| ----------- |
| -- Alloc -- |
| ----------- |
| |
| function Alloc (Size : size_t) return System.Address is |
| Result : aliased System.Address; |
| Actual_Size : aliased size_t := Size; |
| Timestamp : aliased Duration; |
| |
| begin |
| if Size = size_t'Last then |
| Raise_Exception (Storage_Error'Identity, "object too large"); |
| end if; |
| |
| -- Change size from zero to non-zero. We still want a proper pointer |
| -- for the zero case because pointers to zero length objects have to |
| -- be distinct, but we can't just go ahead and allocate zero bytes, |
| -- since some malloc's return zero for a zero argument. |
| |
| if Size = 0 then |
| Actual_Size := 1; |
| end if; |
| |
| Lock_Task.all; |
| |
| Result := c_malloc (Actual_Size); |
| |
| if Allow_Trace then |
| |
| -- Logs allocation call |
| -- format is: |
| -- 'A' <mem addr> <size chunk> <len backtrace> <addr1> ... <addrn> |
| |
| if Needs_Init then |
| Gmem_Initialize; |
| end if; |
| |
| Timestamp := System.OS_Primitives.Clock; |
| Call_Chain |
| (Tracebk, Max_Call_Stack, Num_Calls, Skip_Frames => 2); |
| Putc ('A'); |
| Write (Result'Address, Address_Size); |
| Write (Actual_Size'Address, size_t'Max_Size_In_Storage_Elements); |
| Write (Timestamp'Address, Duration'Max_Size_In_Storage_Elements); |
| Write (Num_Calls'Address, Integer'Max_Size_In_Storage_Elements); |
| |
| for J in Tracebk'First .. Tracebk'First + Num_Calls - 1 loop |
| declare |
| Ptr : System.Address := PC_For (Tracebk (J)); |
| begin |
| Write (Ptr'Address, Address_Size); |
| end; |
| end loop; |
| |
| First_Call := True; |
| |
| end if; |
| |
| Unlock_Task.all; |
| |
| if Result = System.Null_Address then |
| Raise_Exception (Storage_Error'Identity, "heap exhausted"); |
| end if; |
| |
| return Result; |
| end Alloc; |
| |
| -------------- |
| -- Finalize -- |
| -------------- |
| |
| procedure Finalize is |
| begin |
| if not Needs_Init and then CRTL.fclose (Gmemfile) /= 0 then |
| Put_Line ("gmem close error: " & OS_Lib.Errno_Message); |
| end if; |
| end Finalize; |
| |
| ---------- |
| -- Free -- |
| ---------- |
| |
| procedure Free (Ptr : System.Address) is |
| Addr : aliased constant System.Address := Ptr; |
| Timestamp : aliased Duration; |
| |
| begin |
| Lock_Task.all; |
| |
| if Allow_Trace then |
| |
| -- Logs deallocation call |
| -- format is: |
| -- 'D' <mem addr> <len backtrace> <addr1> ... <addrn> |
| |
| if Needs_Init then |
| Gmem_Initialize; |
| end if; |
| |
| Call_Chain |
| (Tracebk, Max_Call_Stack, Num_Calls, Skip_Frames => 2); |
| Timestamp := System.OS_Primitives.Clock; |
| Putc ('D'); |
| Write (Addr'Address, Address_Size); |
| Write (Timestamp'Address, Duration'Max_Size_In_Storage_Elements); |
| Write (Num_Calls'Address, Integer'Max_Size_In_Storage_Elements); |
| |
| for J in Tracebk'First .. Tracebk'First + Num_Calls - 1 loop |
| declare |
| Ptr : System.Address := PC_For (Tracebk (J)); |
| begin |
| Write (Ptr'Address, Address_Size); |
| end; |
| end loop; |
| |
| c_free (Ptr); |
| |
| First_Call := True; |
| end if; |
| |
| Unlock_Task.all; |
| end Free; |
| |
| --------------------- |
| -- Gmem_Initialize -- |
| --------------------- |
| |
| procedure Gmem_Initialize is |
| Timestamp : aliased Duration; |
| File_Mode : constant String := "wb" & ASCII.NUL; |
| begin |
| if Needs_Init then |
| Needs_Init := False; |
| System.OS_Primitives.Initialize; |
| Timestamp := System.OS_Primitives.Clock; |
| Gmemfile := CRTL.fopen (Gmemfname'Address, File_Mode'Address); |
| |
| if Gmemfile = System.Null_Address then |
| Put_Line ("Couldn't open gnatmem log file for writing"); |
| OS_Lib.OS_Exit (255); |
| end if; |
| |
| declare |
| S : constant String := "GMEM DUMP" & ASCII.LF; |
| begin |
| Write (S'Address, S'Length); |
| Write (Timestamp'Address, Duration'Max_Size_In_Storage_Elements); |
| end; |
| end if; |
| end Gmem_Initialize; |
| |
| ---------- |
| -- Putc -- |
| ---------- |
| |
| procedure Putc (Char : Character) is |
| C : constant Integer := Character'Pos (Char); |
| |
| begin |
| if CRTL.fputc (C, Gmemfile) /= C then |
| Put_Line ("gmem fputc error: " & OS_Lib.Errno_Message); |
| end if; |
| end Putc; |
| |
| ------------- |
| -- Realloc -- |
| ------------- |
| |
| function Realloc |
| (Ptr : System.Address; |
| Size : size_t) return System.Address |
| is |
| Addr : aliased constant System.Address := Ptr; |
| Result : aliased System.Address; |
| Timestamp : aliased Duration; |
| |
| begin |
| -- For the purposes of allocations logging, we treat realloc as a free |
| -- followed by malloc. This is not exactly accurate, but is a good way |
| -- to fit it into malloc/free-centered reports. |
| |
| if Size = size_t'Last then |
| Raise_Exception (Storage_Error'Identity, "object too large"); |
| end if; |
| |
| Abort_Defer.all; |
| Lock_Task.all; |
| |
| if Allow_Trace then |
| -- We first log deallocation call |
| |
| if Needs_Init then |
| Gmem_Initialize; |
| end if; |
| Call_Chain |
| (Tracebk, Max_Call_Stack, Num_Calls, Skip_Frames => 2); |
| Timestamp := System.OS_Primitives.Clock; |
| Putc ('D'); |
| Write (Addr'Address, Address_Size); |
| Write (Timestamp'Address, Duration'Max_Size_In_Storage_Elements); |
| Write (Num_Calls'Address, Integer'Max_Size_In_Storage_Elements); |
| |
| for J in Tracebk'First .. Tracebk'First + Num_Calls - 1 loop |
| declare |
| Ptr : System.Address := PC_For (Tracebk (J)); |
| begin |
| Write (Ptr'Address, Address_Size); |
| end; |
| end loop; |
| |
| -- Now perform actual realloc |
| |
| Result := c_realloc (Ptr, Size); |
| |
| -- Log allocation call using the same backtrace |
| |
| Putc ('A'); |
| Write (Result'Address, Address_Size); |
| Write (Size'Address, size_t'Max_Size_In_Storage_Elements); |
| Write (Timestamp'Address, Duration'Max_Size_In_Storage_Elements); |
| Write (Num_Calls'Address, Integer'Max_Size_In_Storage_Elements); |
| |
| for J in Tracebk'First .. Tracebk'First + Num_Calls - 1 loop |
| declare |
| Ptr : System.Address := PC_For (Tracebk (J)); |
| begin |
| Write (Ptr'Address, Address_Size); |
| end; |
| end loop; |
| |
| First_Call := True; |
| end if; |
| |
| Unlock_Task.all; |
| Abort_Undefer.all; |
| |
| if Result = System.Null_Address then |
| Raise_Exception (Storage_Error'Identity, "heap exhausted"); |
| end if; |
| |
| return Result; |
| end Realloc; |
| |
| ----------- |
| -- Write -- |
| ----------- |
| |
| procedure Write (Ptr : System.Address; Size : size_t) is |
| function fwrite |
| (buffer : System.Address; |
| size : size_t; |
| count : size_t; |
| stream : File_Ptr) return size_t; |
| pragma Import (C, fwrite); |
| |
| begin |
| if fwrite (Ptr, Size, 1, Gmemfile) /= 1 then |
| Put_Line ("gmem fwrite error: " & OS_Lib.Errno_Message); |
| end if; |
| end Write; |
| |
| end System.Memory; |