| ------------------------------------------------------------------------------ |
| -- -- |
| -- GNAT RUN-TIME COMPONENTS -- |
| -- -- |
| -- S Y S T E M . T R A C E B A C K . S Y M B O L I C -- |
| -- -- |
| -- B o d y -- |
| -- -- |
| -- Copyright (C) 1999-2022, AdaCore -- |
| -- -- |
| -- 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. -- |
| -- -- |
| ------------------------------------------------------------------------------ |
| |
| -- Run-time symbolic traceback support for targets using DWARF debug data |
| |
| with Ada.Unchecked_Deallocation; |
| |
| with Ada.Exceptions.Traceback; use Ada.Exceptions.Traceback; |
| with Ada.Containers.Generic_Array_Sort; |
| |
| with System.Address_To_Access_Conversions; |
| with System.Soft_Links; |
| with System.CRTL; |
| with System.Dwarf_Lines; |
| with System.Exception_Traces; |
| with System.Standard_Library; |
| with System.Traceback_Entries; |
| with System.Strings; |
| with System.Bounded_Strings; |
| |
| package body System.Traceback.Symbolic is |
| |
| use System.Bounded_Strings; |
| use System.Dwarf_Lines; |
| |
| subtype Big_String is String (Positive); |
| -- To deal with C strings |
| |
| package Big_String_Conv is new System.Address_To_Access_Conversions |
| (Big_String); |
| |
| type Module_Cache; |
| type Module_Cache_Acc is access all Module_Cache; |
| |
| type Module_Cache is record |
| Name : Strings.String_Access; |
| -- Name of the module |
| |
| C : Dwarf_Context (In_Exception => True); |
| -- Context to symbolize an address within this module |
| |
| Chain : Module_Cache_Acc; |
| end record; |
| |
| procedure Free is new Ada.Unchecked_Deallocation |
| (Module_Cache, |
| Module_Cache_Acc); |
| |
| Cache_Chain : Module_Cache_Acc; |
| -- Simply linked list of modules |
| |
| type Module_Array is array (Natural range <>) of Module_Cache_Acc; |
| type Module_Array_Acc is access Module_Array; |
| |
| Modules_Cache : Module_Array_Acc; |
| -- Sorted array of cached modules (if not null) |
| |
| Exec_Module : aliased Module_Cache; |
| -- Context for the executable |
| |
| type Init_State is (Uninitialized, Initialized, Failed); |
| Exec_Module_State : Init_State := Uninitialized; |
| -- How Exec_Module is initialized |
| |
| procedure Init_Exec_Module; |
| -- Initialize Exec_Module if not already initialized |
| |
| function Symbolic_Traceback |
| (Traceback : System.Traceback_Entries.Tracebacks_Array; |
| Suppress_Hex : Boolean) return String; |
| function Symbolic_Traceback |
| (E : Ada.Exceptions.Exception_Occurrence; |
| Suppress_Hex : Boolean) return String; |
| -- Suppress_Hex means do not print any hexadecimal addresses, even if the |
| -- symbol is not available. |
| |
| function Lt (Left, Right : Module_Cache_Acc) return Boolean; |
| -- Sort function for Module_Cache |
| |
| procedure Init_Module |
| (Module : out Module_Cache; |
| Success : out Boolean; |
| Module_Name : String; |
| Load_Address : Address := Null_Address); |
| -- Initialize Module |
| |
| procedure Close_Module (Module : in out Module_Cache); |
| -- Finalize Module |
| |
| function Value (Item : System.Address) return String; |
| -- Return the String contained in Item, up until the first NUL character |
| |
| pragma Warnings (Off, "*Add_Module_To_Cache*"); |
| procedure Add_Module_To_Cache (Module_Name : String; |
| Load_Address : System.Address); |
| -- To be called by Build_Cache_For_All_Modules to add a new module to the |
| -- list. May not be referenced. |
| |
| package Module_Name is |
| |
| procedure Build_Cache_For_All_Modules; |
| -- Create the cache for all current modules |
| |
| function Get (Addr : System.Address; |
| Load_Addr : access System.Address) return String; |
| -- Returns the module name for the given address Addr, or an empty |
| -- string for the main executable. Load_Addr is set to the shared |
| -- library load address if this information is available, or to |
| -- System.Null_Address otherwise. |
| |
| function Is_Supported return Boolean; |
| pragma Inline (Is_Supported); |
| -- Returns True if Module_Name is supported, so if the traceback is |
| -- supported for shared libraries. |
| |
| end Module_Name; |
| |
| package body Module_Name is separate; |
| |
| function Executable_Name return String; |
| -- Returns the executable name as reported by argv[0]. If gnat_argv not |
| -- initialized, return an empty string. If the argv[0] executable is not |
| -- found in the PATH, return it unresolved. |
| |
| function Get_Executable_Load_Address return System.Address; |
| pragma Import |
| (C, |
| Get_Executable_Load_Address, |
| "__gnat_get_executable_load_address"); |
| -- Get the load address of the executable, or Null_Address if not known |
| |
| procedure Hexa_Traceback |
| (Traceback : Tracebacks_Array; |
| Suppress_Hex : Boolean; |
| Res : in out Bounded_String); |
| -- Non-symbolic traceback (simply write addresses in hexa) |
| |
| procedure Symbolic_Traceback_No_Lock |
| (Traceback : Tracebacks_Array; |
| Suppress_Hex : Boolean; |
| Res : in out Bounded_String); |
| -- Like the public Symbolic_Traceback_No_Lock except there is no provision |
| -- against concurrent accesses. |
| |
| procedure Module_Symbolic_Traceback |
| (Traceback : Tracebacks_Array; |
| Module : Module_Cache; |
| Suppress_Hex : Boolean; |
| Res : in out Bounded_String); |
| -- Returns the Traceback for a given module |
| |
| procedure Multi_Module_Symbolic_Traceback |
| (Traceback : Tracebacks_Array; |
| Suppress_Hex : Boolean; |
| Res : in out Bounded_String); |
| -- Build string containing symbolic traceback for the given call chain |
| |
| procedure Multi_Module_Symbolic_Traceback |
| (Traceback : Tracebacks_Array; |
| Module : Module_Cache; |
| Suppress_Hex : Boolean; |
| Res : in out Bounded_String); |
| -- Likewise but using Module |
| |
| Max_String_Length : constant := 4096; |
| -- Arbitrary limit on Bounded_Str length |
| |
| ----------- |
| -- Value -- |
| ----------- |
| |
| function Value (Item : System.Address) return String is |
| begin |
| if Item /= Null_Address then |
| for J in Big_String'Range loop |
| if Big_String_Conv.To_Pointer (Item) (J) = ASCII.NUL then |
| return Big_String_Conv.To_Pointer (Item) (1 .. J - 1); |
| end if; |
| end loop; |
| end if; |
| |
| return ""; |
| end Value; |
| |
| ------------------------- |
| -- Add_Module_To_Cache -- |
| ------------------------- |
| |
| procedure Add_Module_To_Cache (Module_Name : String; |
| Load_Address : System.Address) |
| is |
| Module : Module_Cache_Acc; |
| Success : Boolean; |
| begin |
| Module := new Module_Cache; |
| Init_Module (Module.all, Success, Module_Name, Load_Address); |
| if not Success then |
| Free (Module); |
| return; |
| end if; |
| Module.Chain := Cache_Chain; |
| Cache_Chain := Module; |
| end Add_Module_To_Cache; |
| |
| ---------------------- |
| -- Init_Exec_Module -- |
| ---------------------- |
| |
| procedure Init_Exec_Module is |
| begin |
| if Exec_Module_State = Uninitialized then |
| declare |
| Exec_Path : constant String := Executable_Name; |
| Exec_Load : constant Address := Get_Executable_Load_Address; |
| Success : Boolean; |
| begin |
| Init_Module (Exec_Module, Success, Exec_Path, Exec_Load); |
| |
| if Success then |
| Exec_Module_State := Initialized; |
| else |
| Exec_Module_State := Failed; |
| end if; |
| end; |
| end if; |
| end Init_Exec_Module; |
| |
| -------- |
| -- Lt -- |
| -------- |
| |
| function Lt (Left, Right : Module_Cache_Acc) return Boolean is |
| begin |
| return Low_Address (Left.C) < Low_Address (Right.C); |
| end Lt; |
| |
| ----------------------------- |
| -- Module_Cache_Array_Sort -- |
| ----------------------------- |
| |
| procedure Module_Cache_Array_Sort is new Ada.Containers.Generic_Array_Sort |
| (Natural, |
| Module_Cache_Acc, |
| Module_Array, |
| Lt); |
| |
| ------------------ |
| -- Enable_Cache -- |
| ------------------ |
| |
| procedure Enable_Cache (Include_Modules : Boolean := False) is |
| begin |
| -- Can be called at most once |
| if Cache_Chain /= null then |
| return; |
| end if; |
| |
| -- Add all modules |
| Init_Exec_Module; |
| |
| if Exec_Module_State = Failed then |
| raise Program_Error with |
| "cannot enable cache, executable state initialization failed."; |
| end if; |
| |
| Cache_Chain := Exec_Module'Access; |
| |
| if Include_Modules then |
| Module_Name.Build_Cache_For_All_Modules; |
| end if; |
| |
| -- Build and fill the array of modules |
| declare |
| Count : Natural; |
| Module : Module_Cache_Acc; |
| begin |
| for Phase in 1 .. 2 loop |
| Count := 0; |
| Module := Cache_Chain; |
| while Module /= null loop |
| Count := Count + 1; |
| |
| if Phase = 1 then |
| Enable_Cache (Module.C); |
| else |
| Modules_Cache (Count) := Module; |
| end if; |
| Module := Module.Chain; |
| end loop; |
| |
| if Phase = 1 then |
| Modules_Cache := new Module_Array (1 .. Count); |
| end if; |
| end loop; |
| end; |
| |
| -- Sort the array |
| Module_Cache_Array_Sort (Modules_Cache.all); |
| end Enable_Cache; |
| |
| --------------------- |
| -- Executable_Name -- |
| --------------------- |
| |
| function Executable_Name return String is |
| -- We have to import gnat_argv as an Address to match the type of |
| -- gnat_argv in the binder generated file. Otherwise, we get spurious |
| -- warnings about type mismatch when LTO is turned on. |
| |
| Gnat_Argv : System.Address; |
| pragma Import (C, Gnat_Argv, "gnat_argv"); |
| |
| type Argv_Array is array (0 .. 0) of System.Address; |
| package Conv is new System.Address_To_Access_Conversions (Argv_Array); |
| |
| function locate_exec_on_path (A : System.Address) return System.Address; |
| pragma Import (C, locate_exec_on_path, "__gnat_locate_exec_on_path"); |
| |
| begin |
| if Gnat_Argv = Null_Address then |
| return ""; |
| end if; |
| |
| -- See if we can resolve argv[0] to a full path (to a file that we will |
| -- be able to open). If the resolution fails, we were probably spawned |
| -- by an imprecise exec call, typically passing a mere file name as |
| -- argv[0] for a program in the current directory with '.' not on PATH. |
| -- Best we can do is fallback to argv[0] unchanged in this case. If we |
| -- fail opening that downstream, we'll just bail out. |
| |
| declare |
| Argv0 : constant System.Address |
| := Conv.To_Pointer (Gnat_Argv) (0); |
| |
| Resolved_Argv0 : constant System.Address |
| := locate_exec_on_path (Argv0); |
| |
| Exe_Argv : constant System.Address |
| := (if Resolved_Argv0 /= System.Null_Address |
| then Resolved_Argv0 |
| else Argv0); |
| |
| Result : constant String := Value (Exe_Argv); |
| |
| begin |
| -- The buffer returned by locate_exec_on_path was allocated using |
| -- malloc and we should release this memory. |
| |
| if Resolved_Argv0 /= Null_Address then |
| System.CRTL.free (Resolved_Argv0); |
| end if; |
| |
| return Result; |
| end; |
| end Executable_Name; |
| |
| ------------------ |
| -- Close_Module -- |
| ------------------ |
| |
| procedure Close_Module (Module : in out Module_Cache) is |
| begin |
| Close (Module.C); |
| Strings.Free (Module.Name); |
| end Close_Module; |
| |
| ----------------- |
| -- Init_Module -- |
| ----------------- |
| |
| procedure Init_Module |
| (Module : out Module_Cache; |
| Success : out Boolean; |
| Module_Name : String; |
| Load_Address : Address := Null_Address) |
| is |
| begin |
| -- Early return if the module is not known |
| |
| if Module_Name = "" then |
| Success := False; |
| return; |
| end if; |
| |
| Open (Module_Name, Module.C, Success); |
| |
| -- If a module can't be opened just return now, we just cannot give more |
| -- information in this case. |
| |
| if not Success then |
| return; |
| end if; |
| |
| Set_Load_Address (Module.C, Load_Address); |
| |
| Module.Name := new String'(Module_Name); |
| end Init_Module; |
| |
| ------------------------------- |
| -- Module_Symbolic_Traceback -- |
| ------------------------------- |
| |
| procedure Module_Symbolic_Traceback |
| (Traceback : Tracebacks_Array; |
| Module : Module_Cache; |
| Suppress_Hex : Boolean; |
| Res : in out Bounded_String) |
| is |
| Success : Boolean; |
| begin |
| if Symbolic.Module_Name.Is_Supported then |
| Append (Res, '['); |
| Append (Res, Module.Name.all); |
| Append (Res, ']' & ASCII.LF); |
| end if; |
| |
| Dwarf_Lines.Symbolic_Traceback |
| (Module.C, |
| Traceback, |
| Suppress_Hex, |
| Success, |
| Res); |
| |
| if not Success then |
| Hexa_Traceback (Traceback, Suppress_Hex, Res); |
| end if; |
| |
| -- We must not allow an unhandled exception here, since this function |
| -- may be installed as a decorator for all automatic exceptions. |
| |
| exception |
| when others => |
| return; |
| end Module_Symbolic_Traceback; |
| |
| ------------------------------------- |
| -- Multi_Module_Symbolic_Traceback -- |
| ------------------------------------- |
| |
| procedure Multi_Module_Symbolic_Traceback |
| (Traceback : Tracebacks_Array; |
| Suppress_Hex : Boolean; |
| Res : in out Bounded_String) |
| is |
| F : constant Natural := Traceback'First; |
| begin |
| if Traceback'Length = 0 or else Is_Full (Res) then |
| return; |
| end if; |
| |
| if Modules_Cache /= null then |
| -- Search in the cache |
| |
| declare |
| Addr : constant Address := Traceback (F); |
| Hi, Lo, Mid : Natural; |
| begin |
| Lo := Modules_Cache'First; |
| Hi := Modules_Cache'Last; |
| while Lo <= Hi loop |
| Mid := (Lo + Hi) / 2; |
| if Addr < Low_Address (Modules_Cache (Mid).C) then |
| Hi := Mid - 1; |
| elsif Is_Inside (Modules_Cache (Mid).C, Addr) then |
| Multi_Module_Symbolic_Traceback |
| (Traceback, |
| Modules_Cache (Mid).all, |
| Suppress_Hex, |
| Res); |
| return; |
| else |
| Lo := Mid + 1; |
| end if; |
| end loop; |
| |
| -- Not found |
| Hexa_Traceback (Traceback (F .. F), Suppress_Hex, Res); |
| Multi_Module_Symbolic_Traceback |
| (Traceback (F + 1 .. Traceback'Last), |
| Suppress_Hex, |
| Res); |
| end; |
| else |
| |
| -- First try the executable |
| if Is_Inside (Exec_Module.C, Traceback (F)) then |
| Multi_Module_Symbolic_Traceback |
| (Traceback, |
| Exec_Module, |
| Suppress_Hex, |
| Res); |
| return; |
| end if; |
| |
| -- Otherwise, try a shared library |
| declare |
| Load_Addr : aliased System.Address; |
| M_Name : constant String := |
| Module_Name.Get (Addr => Traceback (F), |
| Load_Addr => Load_Addr'Access); |
| Module : Module_Cache; |
| Success : Boolean; |
| begin |
| Init_Module (Module, Success, M_Name, Load_Addr); |
| if Success then |
| Multi_Module_Symbolic_Traceback |
| (Traceback, |
| Module, |
| Suppress_Hex, |
| Res); |
| Close_Module (Module); |
| else |
| -- Module not found |
| Hexa_Traceback (Traceback (F .. F), Suppress_Hex, Res); |
| Multi_Module_Symbolic_Traceback |
| (Traceback (F + 1 .. Traceback'Last), |
| Suppress_Hex, |
| Res); |
| end if; |
| end; |
| end if; |
| end Multi_Module_Symbolic_Traceback; |
| |
| procedure Multi_Module_Symbolic_Traceback |
| (Traceback : Tracebacks_Array; |
| Module : Module_Cache; |
| Suppress_Hex : Boolean; |
| Res : in out Bounded_String) |
| is |
| Pos : Positive; |
| begin |
| -- Will symbolize the first address... |
| |
| Pos := Traceback'First + 1; |
| |
| -- ... and all addresses in the same module |
| |
| Same_Module : |
| loop |
| exit Same_Module when Pos > Traceback'Last; |
| |
| -- Get address to check for corresponding module name |
| |
| exit Same_Module when not Is_Inside (Module.C, Traceback (Pos)); |
| |
| Pos := Pos + 1; |
| end loop Same_Module; |
| |
| Module_Symbolic_Traceback |
| (Traceback (Traceback'First .. Pos - 1), |
| Module, |
| Suppress_Hex, |
| Res); |
| Multi_Module_Symbolic_Traceback |
| (Traceback (Pos .. Traceback'Last), |
| Suppress_Hex, |
| Res); |
| end Multi_Module_Symbolic_Traceback; |
| |
| -------------------- |
| -- Hexa_Traceback -- |
| -------------------- |
| |
| procedure Hexa_Traceback |
| (Traceback : Tracebacks_Array; |
| Suppress_Hex : Boolean; |
| Res : in out Bounded_String) |
| is |
| use System.Traceback_Entries; |
| begin |
| if Suppress_Hex then |
| Append (Res, "..."); |
| Append (Res, ASCII.LF); |
| else |
| for J in Traceback'Range loop |
| Append_Address (Res, PC_For (Traceback (J))); |
| Append (Res, ASCII.LF); |
| end loop; |
| end if; |
| end Hexa_Traceback; |
| |
| -------------------------------- |
| -- Symbolic_Traceback_No_Lock -- |
| -------------------------------- |
| |
| procedure Symbolic_Traceback_No_Lock |
| (Traceback : Tracebacks_Array; |
| Suppress_Hex : Boolean; |
| Res : in out Bounded_String) |
| is |
| begin |
| if Symbolic.Module_Name.Is_Supported then |
| Multi_Module_Symbolic_Traceback (Traceback, Suppress_Hex, Res); |
| else |
| if Exec_Module_State = Failed then |
| Append (Res, "Call stack traceback locations:" & ASCII.LF); |
| Hexa_Traceback (Traceback, Suppress_Hex, Res); |
| else |
| Module_Symbolic_Traceback |
| (Traceback, |
| Exec_Module, |
| Suppress_Hex, |
| Res); |
| end if; |
| end if; |
| end Symbolic_Traceback_No_Lock; |
| |
| ------------------------ |
| -- Symbolic_Traceback -- |
| ------------------------ |
| |
| function Symbolic_Traceback |
| (Traceback : Tracebacks_Array; |
| Suppress_Hex : Boolean) return String |
| is |
| Res : Bounded_String (Max_Length => Max_String_Length); |
| begin |
| System.Soft_Links.Lock_Task.all; |
| Init_Exec_Module; |
| Symbolic_Traceback_No_Lock (Traceback, Suppress_Hex, Res); |
| System.Soft_Links.Unlock_Task.all; |
| |
| return To_String (Res); |
| |
| exception |
| when others => |
| System.Soft_Links.Unlock_Task.all; |
| raise; |
| end Symbolic_Traceback; |
| |
| function Symbolic_Traceback |
| (Traceback : System.Traceback_Entries.Tracebacks_Array) return String is |
| begin |
| return Symbolic_Traceback (Traceback, Suppress_Hex => False); |
| end Symbolic_Traceback; |
| |
| function Symbolic_Traceback_No_Hex |
| (Traceback : System.Traceback_Entries.Tracebacks_Array) return String is |
| begin |
| return Symbolic_Traceback (Traceback, Suppress_Hex => True); |
| end Symbolic_Traceback_No_Hex; |
| |
| function Symbolic_Traceback |
| (E : Ada.Exceptions.Exception_Occurrence; |
| Suppress_Hex : Boolean) return String |
| is |
| begin |
| return Symbolic_Traceback |
| (Ada.Exceptions.Traceback.Tracebacks (E), |
| Suppress_Hex); |
| end Symbolic_Traceback; |
| |
| function Symbolic_Traceback |
| (E : Ada.Exceptions.Exception_Occurrence) return String |
| is |
| begin |
| return Symbolic_Traceback (E, Suppress_Hex => False); |
| end Symbolic_Traceback; |
| |
| function Symbolic_Traceback_No_Hex |
| (E : Ada.Exceptions.Exception_Occurrence) return String is |
| begin |
| return Symbolic_Traceback (E, Suppress_Hex => True); |
| end Symbolic_Traceback_No_Hex; |
| |
| Exception_Tracebacks_Symbolic : constant Integer; |
| pragma Import |
| (C, |
| Exception_Tracebacks_Symbolic, |
| "__gl_exception_tracebacks_symbolic"); |
| -- Boolean indicating whether symbolic tracebacks should be generated. |
| |
| use Standard_Library; |
| begin |
| -- If this version of this package is available, and the binder switch -Es |
| -- was given, then we want to use this as the decorator by default, and we |
| -- want to turn on tracing for Unhandled_Raise_In_Main. Note that the user |
| -- cannot have already set Exception_Trace, because the runtime library is |
| -- elaborated before user-defined code. |
| |
| if Exception_Tracebacks_Symbolic /= 0 then |
| Exception_Traces.Set_Trace_Decorator (Symbolic_Traceback'Access); |
| pragma Assert (Exception_Trace = RM_Convention); |
| Exception_Trace := Unhandled_Raise_In_Main; |
| end if; |
| end System.Traceback.Symbolic; |