------------------------------------------------------------------------------
--                                                                          --
--                         GNAT COMPILER COMPONENTS                         --
--                                                                          --
--                   S Y S T E M . D W A R F _ L I N E S                    --
--                                                                          --
--                                 B o d y                                  --
--                                                                          --
--          Copyright (C) 2009-2020, 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.      --
--                                                                          --
------------------------------------------------------------------------------

with Ada.Characters.Handling;
with Ada.Containers.Generic_Array_Sort;
with Ada.Unchecked_Deallocation;

with Interfaces; use Interfaces;

with System;                   use System;
with System.Address_Image;
with System.Bounded_Strings;   use System.Bounded_Strings;
with System.IO;                use System.IO;
with System.Mmap;              use System.Mmap;
with System.Object_Reader;     use System.Object_Reader;
with System.Storage_Elements;  use System.Storage_Elements;

package body System.Dwarf_Lines is

   SSU : constant := System.Storage_Unit;

   function Get_Load_Displacement (C : Dwarf_Context) return Storage_Offset;
   --  Return the displacement between the load address present in the binary
   --  and the run-time address at which it is loaded (i.e. non-zero for PIE).

   function String_Length (Str : Str_Access) return Natural;
   --  Return the length of the C string Str

   ---------------------------------
   -- DWARF Parser Implementation --
   ---------------------------------

   procedure Read_Initial_Length
     (S    : in out Mapped_Stream;
      Len  :    out Offset;
      Is64 :    out Boolean);
   --  Read initial length as specified by 7.2.2

   procedure Read_Section_Offset
     (S    : in out Mapped_Stream;
      Len  :    out Offset;
      Is64 :        Boolean);
   --  Read a section offset, as specified by 7.4

   procedure Read_Entry_Format_Array
     (S    : in out Mapped_Stream;
      A    :    out Entry_Format_Array;
      Len  :        uint8);
   --  Read an entry format array, as specified by 6.2.4.1

   procedure Read_Aranges_Entry
     (C     : in out Dwarf_Context;
      Start :    out Address;
      Len   :    out Storage_Count);
   --  Read a single .debug_aranges pair

   procedure Read_Aranges_Header
     (C           : in out Dwarf_Context;
      Info_Offset :    out Offset;
      Success     :    out Boolean);
   --  Read .debug_aranges header

   procedure Aranges_Lookup
     (C           : in out Dwarf_Context;
      Addr        :        Address;
      Info_Offset :    out Offset;
      Success     :    out Boolean);
   --  Search for Addr in .debug_aranges and return offset Info_Offset in
   --  .debug_info.

   procedure Skip_Form
     (S      : in out Mapped_Stream;
      Form   :        uint32;
      Is64   :        Boolean;
      Ptr_Sz :        uint8);
   --  Advance offset in S for Form.

   procedure Seek_Abbrev
     (C             : in out Dwarf_Context;
      Abbrev_Offset :        Offset;
      Abbrev_Num    :        uint32);
   --  Seek to abbrev Abbrev_Num (starting from Abbrev_Offset)

   procedure Debug_Info_Lookup
     (C           : in out Dwarf_Context;
      Info_Offset :        Offset;
      Line_Offset :    out Offset;
      Success     :    out Boolean);
   --  Search for stmt_list tag in Info_Offset and set Line_Offset to the
   --  offset in .debug_lines. Only look at the first DIE, which should be
   --  a compilation unit.

   procedure Initialize_Pass (C : in out Dwarf_Context);
   --  Seek to the first byte of the first header and prepare to make a pass
   --  over the line number entries.

   procedure Initialize_State_Machine (C : in out Dwarf_Context);
   --  Set all state machine registers to their specified initial values

   procedure Parse_Header (C : in out Dwarf_Context);
   --  Decode a DWARF statement program header

   procedure Read_And_Execute_Insn
     (C    : in out Dwarf_Context;
      Done :    out Boolean);
   --  Read an execute a statement program instruction

   function To_File_Name
     (C    : in out Dwarf_Context;
      File :        uint32) return String;
   --  Extract a file name from the header

   type Callback is access procedure (C : in out Dwarf_Context);
   procedure For_Each_Row (C : in out Dwarf_Context; F : Callback);
   --  Traverse each .debug_line entry with a callback

   procedure Dump_Row (C : in out Dwarf_Context);
   --  Dump a single row

   function "<" (Left, Right : Search_Entry) return Boolean;
   --  For sorting Search_Entry

   procedure Sort_Search_Array is new Ada.Containers.Generic_Array_Sort
     (Index_Type   => Natural,
      Element_Type => Search_Entry,
      Array_Type   => Search_Array);

   procedure Symbolic_Address
     (C           : in out Dwarf_Context;
      Addr        :        Address;
      Dir_Name    :    out Str_Access;
      File_Name   :    out Str_Access;
      Subprg_Name :    out String_Ptr_Len;
      Line_Num    :    out Natural);
   --  Symbolize one address

   -----------------------
   --  DWARF constants  --
   -----------------------

   --  3.1.1 Full and Partial Compilation Unit Entries

   DW_TAG_Compile_Unit : constant := 16#11#;

   DW_AT_Stmt_List : constant := 16#10#;

   --  6.2.4.1 Standard Content Descriptions (DWARF 5)

   DW_LNCT_path            : constant := 1;
   DW_LNCT_directory_index : constant := 2;
   --  DW_LNCT_timestamp   : constant := 3;
   --  DW_LNCT_size        : constant := 4;
   DW_LNCT_MD5             : constant := 5;
   DW_LNCT_lo_user         : constant := 16#2000#;
   DW_LNCT_hi_user         : constant := 16#3fff#;

   --  6.2.5.2 Standard Opcodes

   DW_LNS_extended_op        : constant := 0;
   DW_LNS_copy               : constant := 1;
   DW_LNS_advance_pc         : constant := 2;
   DW_LNS_advance_line       : constant := 3;
   DW_LNS_set_file           : constant := 4;
   DW_LNS_set_column         : constant := 5;
   DW_LNS_negate_stmt        : constant := 6;
   DW_LNS_set_basic_block    : constant := 7;
   DW_LNS_const_add_pc       : constant := 8;
   DW_LNS_fixed_advance_pc   : constant := 9;
   DW_LNS_set_prologue_end   : constant := 10;
   DW_LNS_set_epilogue_begin : constant := 11;
   DW_LNS_set_isa            : constant := 12;

   --  6.2.5.3 Extended Opcodes

   DW_LNE_end_sequence      : constant := 1;
   DW_LNE_set_address       : constant := 2;
   DW_LNE_define_file       : constant := 3;
   DW_LNE_set_discriminator : constant := 4;

   --  7.5.5 Classes and Forms

   DW_FORM_addr           : constant := 16#01#;
   DW_FORM_block2         : constant := 16#03#;
   DW_FORM_block4         : constant := 16#04#;
   DW_FORM_data2          : constant := 16#05#;
   DW_FORM_data4          : constant := 16#06#;
   DW_FORM_data8          : constant := 16#07#;
   DW_FORM_string         : constant := 16#08#;
   DW_FORM_block          : constant := 16#09#;
   DW_FORM_block1         : constant := 16#0a#;
   DW_FORM_data1          : constant := 16#0b#;
   DW_FORM_flag           : constant := 16#0c#;
   DW_FORM_sdata          : constant := 16#0d#;
   DW_FORM_strp           : constant := 16#0e#;
   DW_FORM_udata          : constant := 16#0f#;
   DW_FORM_ref_addr       : constant := 16#10#;
   DW_FORM_ref1           : constant := 16#11#;
   DW_FORM_ref2           : constant := 16#12#;
   DW_FORM_ref4           : constant := 16#13#;
   DW_FORM_ref8           : constant := 16#14#;
   DW_FORM_ref_udata      : constant := 16#15#;
   DW_FORM_indirect       : constant := 16#16#;
   DW_FORM_sec_offset     : constant := 16#17#;
   DW_FORM_exprloc        : constant := 16#18#;
   DW_FORM_flag_present   : constant := 16#19#;
   DW_FORM_strx           : constant := 16#1a#;
   DW_FORM_addrx          : constant := 16#1b#;
   DW_FORM_ref_sup4       : constant := 16#1c#;
   DW_FORM_strp_sup       : constant := 16#1d#;
   DW_FORM_data16         : constant := 16#1e#;
   DW_FORM_line_strp      : constant := 16#1f#;
   DW_FORM_ref_sig8       : constant := 16#20#;
   DW_FORM_implicit_const : constant := 16#21#;
   DW_FORM_loclistx       : constant := 16#22#;
   DW_FORM_rnglistx       : constant := 16#23#;
   DW_FORM_ref_sup8       : constant := 16#24#;
   DW_FORM_strx1          : constant := 16#25#;
   DW_FORM_strx2          : constant := 16#26#;
   DW_FORM_strx3          : constant := 16#27#;
   DW_FORM_strx4          : constant := 16#28#;
   DW_FORM_addrx1         : constant := 16#29#;
   DW_FORM_addrx2         : constant := 16#2a#;
   DW_FORM_addrx3         : constant := 16#2b#;
   DW_FORM_addrx4         : constant := 16#2c#;

   ---------
   -- "<" --
   ---------

   function "<" (Left, Right : Search_Entry) return Boolean is
   begin
      return Left.First < Right.First;
   end "<";

   -----------
   -- Close --
   -----------

   procedure Close (C : in out Dwarf_Context) is
      procedure Unchecked_Deallocation is new Ada.Unchecked_Deallocation
        (Object_File,
         Object_File_Access);
      procedure Unchecked_Deallocation is new Ada.Unchecked_Deallocation
        (Search_Array,
         Search_Array_Access);

   begin
      if C.Has_Debug then
         Close (C.Lines);
         Close (C.Abbrev);
         Close (C.Info);
         Close (C.Aranges);
      end if;

      Close (C.Obj.all);
      Unchecked_Deallocation (C.Obj);

      Unchecked_Deallocation (C.Cache);
   end Close;

   ----------
   -- Dump --
   ----------

   procedure Dump (C : in out Dwarf_Context) is
   begin
      For_Each_Row (C, Dump_Row'Access);
   end Dump;

   --------------
   -- Dump_Row --
   --------------

   procedure Dump_Row (C : in out Dwarf_Context) is
      PC  : constant Integer_Address := Integer_Address (C.Registers.Address);
      Off : Offset;

   begin
      Tell (C.Lines, Off);

      Put (System.Address_Image (To_Address (PC)));
      Put (" ");
      Put (To_File_Name (C, C.Registers.File));
      Put (":");

      declare
         Image : constant String := uint32'Image (C.Registers.Line);
      begin
         Put_Line (Image (2 .. Image'Last));
      end;

      Seek (C.Lines, Off);
   end Dump_Row;

   procedure Dump_Cache (C : Dwarf_Context) is
      Cache : constant Search_Array_Access := C.Cache;
      S     : Object_Symbol;
      Name  : String_Ptr_Len;

   begin
      if Cache = null then
         Put_Line ("No cache");
         return;
      end if;

      for I in Cache'Range loop
         declare
            E : Search_Entry renames Cache (I);
            Base_Address : constant System.Address :=
              To_Address (Integer_Address (C.Low + Storage_Count (E.First)));
         begin
            Put (System.Address_Image (Base_Address));
            Put (" - ");
            Put (System.Address_Image (Base_Address + Storage_Count (E.Size)));
            Put (" l@");
            Put (System.Address_Image (To_Address (Integer_Address (E.Line))));
            Put (": ");
            S    := Read_Symbol (C.Obj.all, Offset (E.Sym));
            Name := Object_Reader.Name (C.Obj.all, S);
            Put (String (Name.Ptr (1 .. Name.Len)));
            New_Line;
         end;
      end loop;
   end Dump_Cache;

   ------------------
   -- For_Each_Row --
   ------------------

   procedure For_Each_Row (C : in out Dwarf_Context; F : Callback) is
      Done : Boolean;

   begin
      Initialize_Pass (C);

      loop
         Read_And_Execute_Insn (C, Done);

         if C.Registers.Is_Row then
            F.all (C);
         end if;

         exit when Done;
      end loop;
   end For_Each_Row;

   ---------------------------
   -- Get_Load_Displacement --
   ---------------------------

   function Get_Load_Displacement (C : Dwarf_Context) return Storage_Offset is
   begin
      if C.Load_Address /= Null_Address then
         return C.Load_Address - Address (Get_Load_Address (C.Obj.all));
      else
         return 0;
      end if;
   end Get_Load_Displacement;

   ---------------------
   -- Initialize_Pass --
   ---------------------

   procedure Initialize_Pass (C : in out Dwarf_Context) is
   begin
      Seek (C.Lines, 0);
      C.Next_Header := 0;
      Initialize_State_Machine (C);
   end Initialize_Pass;

   ------------------------------
   -- Initialize_State_Machine --
   ------------------------------

   procedure Initialize_State_Machine (C : in out Dwarf_Context) is
   begin
      --  Table 6.4: Line number program initial state

      C.Registers :=
        (Address        => 0,
         File           => 1,
         Line           => 1,
         Column         => 0,
         Is_Stmt        => C.Header.Default_Is_Stmt /= 0,
         Basic_Block    => False,
         End_Sequence   => False,
         Is_Row         => False);
   end Initialize_State_Machine;

   ---------------
   -- Is_Inside --
   ---------------

   function Is_Inside (C : Dwarf_Context; Addr : Address) return Boolean is
      Disp : constant Storage_Offset := Get_Load_Displacement (C);

   begin
      return Addr >= C.Low + Disp and then Addr <= C.High + Disp;
   end Is_Inside;

   -----------------
   -- Low_Address --
   -----------------

   function Low_Address (C : Dwarf_Context) return Address is
   begin
      return C.Low + Get_Load_Displacement (C);
   end Low_Address;

   ----------
   -- Open --
   ----------

   procedure Open
     (File_Name :     String;
      C         : out Dwarf_Context;
      Success   : out Boolean)
   is
      Abbrev, Aranges, Lines, Info, Line_Str : Object_Section;
      Hi, Lo                                 : uint64;

   begin
      --  Not a success by default

      Success := False;

      --  Open file with In_Exception set so we can control the failure mode

      C.Obj := Open (File_Name, In_Exception => True);

      if C.Obj = null then
         if C.In_Exception then
            return;
         else
            raise Dwarf_Error with "could not open file";
         end if;
      end if;

      Success := True;

      --  Get address bounds for executable code. Note that such code
      --  might come from multiple sections.

      Get_Xcode_Bounds (C.Obj.all, Lo, Hi);
      C.Low  := Address (Lo);
      C.High := Address (Hi);

      --  Create a stream for debug sections

      if Format (C.Obj.all) = XCOFF32 then
         Abbrev   := Get_Section (C.Obj.all, ".dwabrev");
         Aranges  := Get_Section (C.Obj.all, ".dwarnge");
         Info     := Get_Section (C.Obj.all, ".dwinfo");
         Lines    := Get_Section (C.Obj.all, ".dwline");
         Line_Str := Get_Section (C.Obj.all, ".dwlistr");
      else
         Abbrev   := Get_Section (C.Obj.all, ".debug_abbrev");
         Aranges  := Get_Section (C.Obj.all, ".debug_aranges");
         Info     := Get_Section (C.Obj.all, ".debug_info");
         Lines    := Get_Section (C.Obj.all, ".debug_line");
         Line_Str := Get_Section (C.Obj.all, ".debug_line_str");
      end if;

      if Abbrev = Null_Section
        or else Aranges = Null_Section
        or else Info = Null_Section
        or else Lines = Null_Section
      then
         pragma Annotate
           (CodePeer, False_Positive,
            "test always true", "codepeer got confused");

         C.Has_Debug := False;
         return;
      end if;

      C.Abbrev  := Create_Stream (C.Obj.all, Abbrev);
      C.Aranges := Create_Stream (C.Obj.all, Aranges);
      C.Info    := Create_Stream (C.Obj.all, Info);
      C.Lines   := Create_Stream (C.Obj.all, Lines);

      --  The .debug_line_str section may be available in DWARF 5

      if Line_Str /= Null_Section then
         C.Line_Str := Create_Stream (C.Obj.all, Line_Str);
      end if;

      --  All operations are successful, context is valid

      C.Has_Debug := True;
   end Open;

   ------------------
   -- Parse_Header --
   ------------------

   procedure Parse_Header (C : in out Dwarf_Context) is
      Header : Line_Info_Header renames C.Header;

      Char : uint8;
      Prev : uint8;
      --  The most recently read character and the one preceding it

      Dummy : uint32;
      --  Destination for reads we don't care about

      Buf : Buffer;
      Off : Offset;

      First_Byte_Of_Header : Offset;
      Last_Byte_Of_Header  : Offset;

      Standard_Opcode_Lengths : Opcode_Length_Array;
      pragma Unreferenced (Standard_Opcode_Lengths);

   begin
      Tell (C.Lines, First_Byte_Of_Header);

      Read_Initial_Length (C.Lines, Header.Unit_Length, Header.Is64);

      Tell (C.Lines, Off);
      C.Next_Header := Off + Header.Unit_Length;

      Header.Version := Read (C.Lines);

      if Header.Version >= 5 then
         Header.Address_Size          := Read (C.Lines);
         Header.Segment_Selector_Size := Read (C.Lines);
      else
         Header.Address_Size          := 0;
         Header.Segment_Selector_Size := 0;
      end if;

      Header.Header_Length := Read (C.Lines);
      Tell (C.Lines, Last_Byte_Of_Header);
      Last_Byte_Of_Header :=
        Last_Byte_Of_Header + Offset (Header.Header_Length) - 1;

      Header.Minimum_Insn_Length := Read (C.Lines);

      if Header.Version >= 4 then
         Header.Maximum_Op_Per_Insn := Read (C.Lines);
      else
         Header.Maximum_Op_Per_Insn := 0;
      end if;

      Header.Default_Is_Stmt := Read (C.Lines);
      Header.Line_Base       := Read (C.Lines);
      Header.Line_Range      := Read (C.Lines);
      Header.Opcode_Base     := Read (C.Lines);

      --  Standard_Opcode_Lengths is an array of Opcode_Base bytes specifying
      --  the number of LEB128 operands for each of the standard opcodes.

      for J in 1 .. Integer (Header.Opcode_Base - 1) loop
         Standard_Opcode_Lengths (J) := Read (C.Lines);
      end loop;

      --  The Directories table follows. Up to DWARF 4, this is a list of null
      --  terminated strings terminated by a null byte. In DWARF 5, this is a
      --  sequence of Directories_Count entries which are encoded as described
      --  by the Directory_Entry_Format field. We store its offset for later.

      if Header.Version <= 4 then
         Tell (C.Lines, Header.Directories);
         Char := Read (C.Lines);

         if Char /= 0 then
            loop
               Prev := Char;
               Char := Read (C.Lines);
               exit when Char = 0 and Prev = 0;
            end loop;
         end if;

      else
         Header.Directory_Entry_Format_Count := Read (C.Lines);
         Read_Entry_Format_Array (C.Lines,
           Header.Directory_Entry_Format,
           Header.Directory_Entry_Format_Count);

         Header.Directories_Count := Read_LEB128 (C.Lines);
         Tell (C.Lines, Header.Directories);
         for J in 1 .. Header.Directories_Count loop
            for K in 1 .. Integer (Header.Directory_Entry_Format_Count) loop
               Skip_Form (C.Lines,
                 Header.Directory_Entry_Format (K).Form,
                 Header.Is64,
                 Header.Address_Size);
            end loop;
         end loop;
      end if;

      --  The File_Names table is next. Up to DWARF 4, this is a list of record
      --  containing a null terminated string for the file name, an unsigned
      --  LEB128 directory index in the Directories table, an unsigned LEB128
      --  modification time, and an unsigned LEB128 for the file length; the
      --  table is terminated by a null byte. In DWARF 5, this is a sequence
      --  of File_Names_Count entries which are encoded as described by the
      --  File_Name_Entry_Format field. We store its offset for later decoding.

      if Header.Version <= 4 then
         Tell (C.Lines, Header.File_Names);

         --  Read the file names

         loop
            Read_C_String (C.Lines, Buf);
            exit when Buf (0) = 0;
            Dummy := Read_LEB128 (C.Lines); --  Skip the directory index.
            Dummy := Read_LEB128 (C.Lines); --  Skip the modification time.
            Dummy := Read_LEB128 (C.Lines); --  Skip the file length.
         end loop;

      else
         Header.File_Name_Entry_Format_Count := Read (C.Lines);
         Read_Entry_Format_Array (C.Lines,
           Header.File_Name_Entry_Format,
           Header.File_Name_Entry_Format_Count);

         Header.File_Names_Count := Read_LEB128 (C.Lines);
         Tell (C.Lines, Header.File_Names);
         for J in 1 .. Header.File_Names_Count loop
            for K in 1 .. Integer (Header.File_Name_Entry_Format_Count) loop
               Skip_Form (C.Lines,
                 Header.File_Name_Entry_Format (K).Form,
                 Header.Is64,
                 Header.Address_Size);
            end loop;
         end loop;
      end if;

      --  Check we're where we think we are. This sanity check ensures we think
      --  the header ends where the header says it does. It we aren't, then we
      --  have probably gotten out of sync somewhere.

      Tell (C.Lines, Off);

      if Header.Unit_Length /= 0
        and then Off /= Last_Byte_Of_Header + 1
      then
         raise Dwarf_Error with "parse error reading DWARF information";
      end if;
   end Parse_Header;

   ---------------------------
   -- Read_And_Execute_Insn --
   ---------------------------

   procedure Read_And_Execute_Insn
     (C    : in out Dwarf_Context;
      Done :    out Boolean)
   is
      Opcode          : uint8;
      Extended_Opcode : uint8;
      uint32_Operand  : uint32;
      int32_Operand   : int32;
      uint16_Operand  : uint16;
      Off             : Offset;

      Extended_Length : uint32;
      pragma Unreferenced (Extended_Length);

      Obj : Object_File renames C.Obj.all;
      Registers : Line_Info_Registers renames C.Registers;
      Header : Line_Info_Header renames C.Header;

   begin
      Done             := False;
      Registers.Is_Row := False;

      if Registers.End_Sequence then
         Initialize_State_Machine (C);
      end if;

      --  If we have reached the next header, read it. Beware of possibly empty
      --  blocks.

      --  When testing for the end of section, beware of possible zero padding
      --  at the end. Bail out as soon as there's not even room for at least a
      --  DW_LNE_end_sequence, 3 bytes from Off to Off+2. This resolves to
      --  Off+2 > Last_Offset_Within_Section, that is Off+2 > Section_Length-1,
      --  or Off+3 > Section_Length.

      Tell (C.Lines, Off);
      while Off = C.Next_Header loop
         Initialize_State_Machine (C);
         Parse_Header (C);
         Tell (C.Lines, Off);
         exit when Off + 3 > Length (C.Lines);
      end loop;

      --  Test whether we're done

      Tell (C.Lines, Off);

      --  We are finished when we either reach the end of the section, or we
      --  have reached zero padding at the end of the section.

      if Header.Unit_Length = 0 or else Off + 3 > Length (C.Lines) then
         Done := True;
         return;
      end if;

      --  Read and interpret an instruction

      Opcode := Read (C.Lines);

      --  Extended opcodes

      if Opcode = DW_LNS_extended_op then
         Extended_Length := Read_LEB128 (C.Lines);
         Extended_Opcode := Read (C.Lines);

         case Extended_Opcode is
            when DW_LNE_end_sequence =>

               --  Mark the end of a sequence of source locations

               Registers.End_Sequence := True;
               Registers.Is_Row       := True;

            when DW_LNE_set_address =>

               --  Set the program counter to a word

               Registers.Address := Read_Address (Obj, C.Lines);

            when DW_LNE_define_file =>

               --  Not implemented

               raise Dwarf_Error with "DWARF operator not implemented";

            when DW_LNE_set_discriminator =>

               --  Ignored

               int32_Operand := Read_LEB128 (C.Lines);

            when others =>

               --  Fail on an unrecognized opcode

               raise Dwarf_Error with "DWARF operator not implemented";
         end case;

      --  Standard opcodes

      elsif Opcode < Header.Opcode_Base then
         case Opcode is

            --  Append a row to the line info matrix

            when DW_LNS_copy =>
               Registers.Basic_Block := False;
               Registers.Is_Row      := True;

            --  Add an unsigned word to the program counter

            when DW_LNS_advance_pc =>
               uint32_Operand    := Read_LEB128 (C.Lines);
               Registers.Address :=
                 Registers.Address +
                 uint64 (uint32_Operand * uint32 (Header.Minimum_Insn_Length));

            --  Add a signed word to the current source line

            when DW_LNS_advance_line =>
               int32_Operand  := Read_LEB128 (C.Lines);
               Registers.Line :=
                 uint32 (int32 (Registers.Line) + int32_Operand);

            --  Set the current source file

            when DW_LNS_set_file =>
               uint32_Operand := Read_LEB128 (C.Lines);
               Registers.File := uint32_Operand;

            --  Set the current source column

            when DW_LNS_set_column =>
               uint32_Operand   := Read_LEB128 (C.Lines);
               Registers.Column := uint32_Operand;

            --  Toggle the "is statement" flag. GCC doesn't seem to set this???

            when DW_LNS_negate_stmt =>
               Registers.Is_Stmt := not Registers.Is_Stmt;

            --  Mark the beginning of a basic block

            when DW_LNS_set_basic_block =>
               Registers.Basic_Block := True;

            --  Advance the program counter as by the special opcode 255

            when DW_LNS_const_add_pc =>
               Registers.Address :=
                 Registers.Address +
                 uint64
                   (((255 - Header.Opcode_Base) / Header.Line_Range) *
                    Header.Minimum_Insn_Length);

            --  Advance the program counter by a constant

            when DW_LNS_fixed_advance_pc =>
               uint16_Operand    := Read (C.Lines);
               Registers.Address :=
                 Registers.Address + uint64 (uint16_Operand);

            --  The following are not implemented and ignored

            when DW_LNS_set_prologue_end =>
               null;

            when DW_LNS_set_epilogue_begin =>
               null;

            when DW_LNS_set_isa =>
               null;

            --  Anything else is an error

            when others =>
               raise Dwarf_Error with "DWARF operator not implemented";
         end case;

      --  Decode a special opcode. This is a line and address increment encoded
      --  in a single byte 'special opcode' as described in 6.2.5.1.

      else
         declare
            Address_Increment : int32;
            Line_Increment    : int32;

         begin
            Opcode := Opcode - Header.Opcode_Base;

            --  The adjusted opcode is a uint8 encoding an address increment
            --  and a signed line increment. The upperbound is allowed to be
            --  greater than int8'last so we decode using int32 directly to
            --  prevent overflows.

            Address_Increment :=
              int32 (Opcode / Header.Line_Range) *
              int32 (Header.Minimum_Insn_Length);
            Line_Increment :=
              int32 (Header.Line_Base) +
              int32 (Opcode mod Header.Line_Range);

            Registers.Address :=
              Registers.Address + uint64 (Address_Increment);
            Registers.Line := uint32 (int32 (Registers.Line) + Line_Increment);
            Registers.Basic_Block    := False;
            Registers.Is_Row         := True;
         end;
      end if;

   exception
      when Dwarf_Error =>

         --  In case of errors during parse, just stop reading

         Registers.Is_Row := False;
         Done             := True;
   end Read_And_Execute_Insn;

   ----------------------
   -- Set_Load_Address --
   ----------------------

   procedure Set_Load_Address (C : in out Dwarf_Context; Addr : Address) is
   begin
      C.Load_Address := Addr;
   end Set_Load_Address;

   ------------------
   -- To_File_Name --
   ------------------

   function To_File_Name
     (C    : in out Dwarf_Context;
      File :        uint32) return String
   is
      Buf : Buffer;
      Off : Offset;

      Dir_Idx : uint32;
      pragma Unreferenced (Dir_Idx);

      Mod_Time : uint32;
      pragma Unreferenced (Mod_Time);

      Length : uint32;
      pragma Unreferenced (Length);

      File_Entry_Format : Entry_Format_Array
        renames C.Header.File_Name_Entry_Format;

   begin
      Seek (C.Lines, C.Header.File_Names);

      --  Find the entry. Note that, up to DWARF 4, the index is 1-based
      --  whereas, in DWARF 5, it is 0-based.

      if C.Header.Version <= 4 then
         for J in 1 .. File loop
            Read_C_String (C.Lines, Buf);

            if Buf (Buf'First) = 0 then
               return "???";
            end if;

            Dir_Idx  := Read_LEB128 (C.Lines);
            Mod_Time := Read_LEB128 (C.Lines);
            Length   := Read_LEB128 (C.Lines);
         end loop;

      --  DWARF 5

      else
         for J in 0 .. File loop
            for K in 1 .. Integer (C.Header.File_Name_Entry_Format_Count) loop
               if File_Entry_Format (K).C_Type = DW_LNCT_path then
                  case File_Entry_Format (K).Form is
                     when DW_FORM_string =>
                        Read_C_String (C.Lines, Buf);

                     when DW_FORM_line_strp =>
                        Read_Section_Offset (C.Lines, Off, C.Header.Is64);
                        if J = File then
                           Seek (C.Line_Str, Off);
                           Read_C_String (C.Line_Str, Buf);
                        end if;

                     when others =>
                        raise Dwarf_Error with "DWARF form not implemented";
                  end case;

               else
                  Skip_Form (C.Lines,
                    File_Entry_Format (K).Form,
                    C.Header.Is64,
                    C.Header.Address_Size);
               end if;
            end loop;
         end loop;
      end if;

      return To_String (Buf);
   end To_File_Name;

   -------------------------
   -- Read_Initial_Length --
   -------------------------

   procedure Read_Initial_Length
     (S    : in out Mapped_Stream;
      Len  :    out Offset;
      Is64 :    out Boolean)
   is
      Len32 : uint32;
      Len64 : uint64;

   begin
      Len32 := Read (S);
      if Len32 < 16#ffff_fff0# then
         Is64 := False;
         Len  := Offset (Len32);
      elsif Len32 < 16#ffff_ffff# then
         --  Invalid length
         raise Constraint_Error;
      else
         Is64  := True;
         Len64 := Read (S);
         Len   := Offset (Len64);
      end if;
   end Read_Initial_Length;

   -------------------------
   -- Read_Section_Offset --
   -------------------------

   procedure Read_Section_Offset
     (S    : in out Mapped_Stream;
      Len  :    out Offset;
      Is64 :        Boolean)
   is
   begin
      if Is64 then
         Len := Offset (uint64'(Read (S)));
      else
         Len := Offset (uint32'(Read (S)));
      end if;
   end Read_Section_Offset;

   -----------------------------
   -- Read_Entry_Format_Array --
   -----------------------------

   procedure Read_Entry_Format_Array
     (S    : in out Mapped_Stream;
      A    :    out Entry_Format_Array;
      Len  :        uint8)
   is
      C_Type, Form : uint32;
      N            : Integer;

   begin
      N := A'First;

      for J in 1 .. Len loop
         C_Type := Read_LEB128 (S);
         Form   := Read_LEB128 (S);

         case C_Type is
            when DW_LNCT_path .. DW_LNCT_MD5 =>
               if N not in A'Range then
                  raise Dwarf_Error with "duplicate DWARF content type";
               end if;

               A (N) := (C_Type, Form);
               N := N + 1;

            when DW_LNCT_lo_user .. DW_LNCT_hi_user =>
               null;

            when others =>
               raise Dwarf_Error with "DWARF content type not implemented";
         end case;
      end loop;
   end Read_Entry_Format_Array;

   --------------------
   -- Aranges_Lookup --
   --------------------

   procedure Aranges_Lookup
     (C           : in out Dwarf_Context;
      Addr        :        Address;
      Info_Offset :    out Offset;
      Success     :    out Boolean)
   is
   begin
      Info_Offset := 0;
      Seek (C.Aranges, 0);

      while Tell (C.Aranges) < Length (C.Aranges) loop
         Read_Aranges_Header (C, Info_Offset, Success);
         exit when not Success;

         loop
            declare
               Start : Address;
               Len   : Storage_Count;
            begin
               Read_Aranges_Entry (C, Start, Len);
               exit when Start = 0 and Len = 0;
               if Addr >= Start
                 and then Addr < Start + Len
               then
                  Success := True;
                  return;
               end if;
            end;
         end loop;
      end loop;

      Success := False;
   end Aranges_Lookup;

   ---------------
   -- Skip_Form --
   ---------------

   procedure Skip_Form
     (S      : in out Mapped_Stream;
      Form   :        uint32;
      Is64   :        Boolean;
      Ptr_Sz :        uint8)
   is
      Skip : Offset;

   begin
      --  7.5.5 Classes and Forms

      case Form is
         when DW_FORM_addr =>
            Skip := Offset (Ptr_Sz);
         when DW_FORM_block1 =>
            Skip := Offset (uint8'(Read (S)));
         when DW_FORM_block2 =>
            Skip := Offset (uint16'(Read (S)));
         when DW_FORM_block4 =>
            Skip := Offset (uint32'(Read (S)));
         when DW_FORM_block | DW_FORM_exprloc =>
            Skip := Offset (uint32'(Read_LEB128 (S)));
         when DW_FORM_addrx1
            | DW_FORM_data1
            | DW_FORM_flag
            | DW_FORM_ref1
            | DW_FORM_strx1
           =>
            Skip := 1;
         when DW_FORM_addrx2
            | DW_FORM_data2
            | DW_FORM_ref2
            | DW_FORM_strx2
           =>
            Skip := 2;
         when DW_FORM_addrx3 | DW_FORM_strx3 =>
            Skip := 3;
         when DW_FORM_addrx4
            | DW_FORM_data4
            | DW_FORM_ref4
            | DW_FORM_ref_sup4
            | DW_FORM_strx4
           =>
            Skip := 4;
         when DW_FORM_data8
            | DW_FORM_ref8
            | DW_FORM_ref_sup8
            | DW_FORM_ref_sig8
           =>
            Skip := 8;
         when DW_FORM_data16 =>
            Skip := 16;
         when DW_FORM_sdata =>
            declare
               Val : constant int32 := Read_LEB128 (S);
               pragma Unreferenced (Val);
            begin
               return;
            end;
         when DW_FORM_addrx
            | DW_FORM_loclistx
            | DW_FORM_ref_udata
            | DW_FORM_rnglistx
            | DW_FORM_strx
            | DW_FORM_udata
           =>
            declare
               Val : constant uint32 := Read_LEB128 (S);
               pragma Unreferenced (Val);
            begin
               return;
            end;
         when DW_FORM_flag_present | DW_FORM_implicit_const =>
            return;
         when DW_FORM_ref_addr
            | DW_FORM_sec_offset
            | DW_FORM_strp
            | DW_FORM_line_strp
            | DW_FORM_strp_sup
           =>
            Skip := (if Is64 then 8 else 4);
         when DW_FORM_string =>
            while uint8'(Read (S)) /= 0 loop
               null;
            end loop;
            return;
         when DW_FORM_indirect =>
            raise Dwarf_Error with "DW_FORM_indirect not implemented";
         when others =>
            raise Dwarf_Error with "DWARF form not implemented";
      end case;

      Seek (S, Tell (S) + Skip);
   end Skip_Form;

   -----------------
   -- Seek_Abbrev --
   -----------------

   procedure Seek_Abbrev
     (C             : in out Dwarf_Context;
      Abbrev_Offset :        Offset;
      Abbrev_Num    :        uint32)
   is
      Abbrev    : uint32;
      Tag       : uint32;
      Has_Child : uint8;
      pragma Unreferenced (Tag, Has_Child);

   begin
      Seek (C.Abbrev, Abbrev_Offset);

      --  7.5.3 Abbreviations Tables

      loop
         Abbrev := Read_LEB128 (C.Abbrev);

         exit when Abbrev = Abbrev_Num;

         Tag       := Read_LEB128 (C.Abbrev);
         Has_Child := Read (C.Abbrev);

         loop
            declare
               Name : constant uint32 := Read_LEB128 (C.Abbrev);
               Form : constant uint32 := Read_LEB128 (C.Abbrev);
               Cst  : int32;
               pragma Unreferenced (Cst);

            begin
               --  DW_FORM_implicit_const takes its value from the table

               if Form = DW_FORM_implicit_const then
                  Cst := Read_LEB128 (C.Abbrev);
               end if;

               exit when Name = 0 and then Form = 0;
            end;
         end loop;
      end loop;
   end Seek_Abbrev;

   -----------------------
   -- Debug_Info_Lookup --
   -----------------------

   procedure Debug_Info_Lookup
     (C           : in out Dwarf_Context;
      Info_Offset :        Offset;
      Line_Offset :    out Offset;
      Success     :    out Boolean)
   is
      Unit_Length   : Offset;
      Is64          : Boolean;
      Version       : uint16;
      Abbrev_Offset : Offset;
      Addr_Sz       : uint8;
      Abbrev        : uint32;
      Has_Child     : uint8;
      pragma Unreferenced (Has_Child);
      Unit_Type     : uint8;
      pragma Unreferenced (Unit_Type);

   begin
      Line_Offset := 0;
      Success := False;

      Seek (C.Info, Info_Offset);

      --  7.5.1.1 Compilation Unit Header

      Read_Initial_Length (C.Info, Unit_Length, Is64);

      Version := Read (C.Info);

      if Version >= 5 then
         Unit_Type := Read (C.Info);

         Addr_Sz := Read (C.Info);
         if Addr_Sz /= (Address'Size / SSU) then
            return;
         end if;

         Read_Section_Offset (C.Info, Abbrev_Offset, Is64);

      elsif Version >= 2 then
         Read_Section_Offset (C.Info, Abbrev_Offset, Is64);

         Addr_Sz := Read (C.Info);
         if Addr_Sz /= (Address'Size / SSU) then
            return;
         end if;

      else
         return;
      end if;

      --  Read DIEs

      loop
         Abbrev := Read_LEB128 (C.Info);
         exit when Abbrev /= 0;
      end loop;

      --  Read abbrev table

      Seek_Abbrev (C, Abbrev_Offset, Abbrev);

      --  Then the tag

      if Read_LEB128 (C.Abbrev) /= uint32'(DW_TAG_Compile_Unit) then
         return;
      end if;

      --  Then the has child flag

      Has_Child := Read (C.Abbrev);

      loop
         declare
            Name : constant uint32 := Read_LEB128 (C.Abbrev);
            Form : constant uint32 := Read_LEB128 (C.Abbrev);
         begin
            exit when Name = 0 and Form = 0;
            if Name = DW_AT_Stmt_List then
               case Form is
                  when DW_FORM_sec_offset =>
                     Read_Section_Offset (C.Info, Line_Offset, Is64);
                  when DW_FORM_data4 =>
                     Line_Offset := Offset (uint32'(Read (C.Info)));
                  when DW_FORM_data8 =>
                     Line_Offset := Offset (uint64'(Read (C.Info)));
                  when others =>
                     --  Unhandled form
                     return;
               end case;

               Success := True;
               return;
            else
               Skip_Form (C.Info, Form, Is64, Addr_Sz);
            end if;
         end;
      end loop;
   end Debug_Info_Lookup;

   -------------------------
   -- Read_Aranges_Header --
   -------------------------

   procedure Read_Aranges_Header
     (C           : in out Dwarf_Context;
      Info_Offset :    out Offset;
      Success     :    out Boolean)
   is
      Unit_Length : Offset;
      Is64        : Boolean;
      Version     : uint16;
      Sz          : uint8;

   begin
      Success     := False;
      Info_Offset := 0;

      Read_Initial_Length (C.Aranges, Unit_Length, Is64);

      Version := Read (C.Aranges);
      if Version /= 2 then
         return;
      end if;

      Read_Section_Offset (C.Aranges, Info_Offset, Is64);

      --  Read address_size (ubyte)

      Sz := Read (C.Aranges);
      if Sz /= (Address'Size / SSU) then
         return;
      end if;

      --  Read segment_size (ubyte)

      Sz := Read (C.Aranges);
      if Sz /= 0 then
         return;
      end if;

      --  Handle alignment on twice the address size

      declare
         Cur_Off : constant Offset := Tell (C.Aranges);
         Align   : constant Offset := 2 * Address'Size / SSU;
         Space   : constant Offset := Cur_Off mod Align;
      begin
         if Space /= 0 then
            Seek (C.Aranges, Cur_Off + Align - Space);
         end if;
      end;

      Success := True;
   end Read_Aranges_Header;

   ------------------------
   -- Read_Aranges_Entry --
   ------------------------

   procedure Read_Aranges_Entry
     (C     : in out Dwarf_Context;
      Start :    out Address;
      Len   :    out Storage_Count)
   is
   begin
      --  Read table

      if Address'Size = 32 then
         declare
            S, L : uint32;
         begin
            S     := Read (C.Aranges);
            L     := Read (C.Aranges);
            Start := Address (S);
            Len   := Storage_Count (L);
         end;

      elsif Address'Size = 64 then
         declare
            S, L : uint64;
         begin
            S     := Read (C.Aranges);
            L     := Read (C.Aranges);
            Start := Address (S);
            Len   := Storage_Count (L);
         end;

      else
         raise Constraint_Error;
      end if;
   end Read_Aranges_Entry;

   ------------------
   -- Enable_Cache --
   ------------------

   procedure Enable_Cache (C : in out Dwarf_Context) is
      Cache : Search_Array_Access;

   begin
      --  Phase 1: count number of symbols.
      --  Phase 2: fill the cache.

      declare
         S               : Object_Symbol;
         Val             : uint64;
         Xcode_Low       : constant uint64 := uint64 (C.Low);
         Xcode_High      : constant uint64 := uint64 (C.High);
         Sz              : uint32;
         Addr, Prev_Addr : uint32;
         Nbr_Symbols     : Natural;
      begin
         for Phase in 1 .. 2 loop
            Nbr_Symbols := 0;
            S           := First_Symbol (C.Obj.all);
            Prev_Addr   := uint32'Last;
            while S /= Null_Symbol loop
               --  Discard symbols of length 0 or located outside of the
               --  execution code section outer boundaries.

               Sz := uint32 (Size (S));
               Val := Value (S);

               if Sz > 0
                 and then Val >= Xcode_Low
                 and then Val <= Xcode_High
               then
                  Addr := uint32 (Val - Xcode_Low);

                  --  Try to filter symbols at the same address. This is a best
                  --  effort as they might not be consecutive.

                  if Addr /= Prev_Addr then
                     Nbr_Symbols := Nbr_Symbols + 1;
                     Prev_Addr   := Addr;

                     if Phase = 2 then
                        C.Cache (Nbr_Symbols) :=
                          (First => Addr,
                           Size  => Sz,
                           Sym   => uint32 (Off (S)),
                           Line  => 0);
                     end if;
                  end if;
               end if;

               S := Next_Symbol (C.Obj.all, S);
            end loop;

            if Phase = 1 then
               --  Allocate the cache

               Cache   := new Search_Array (1 .. Nbr_Symbols);
               C.Cache := Cache;
            end if;
         end loop;
         pragma Assert (Nbr_Symbols = C.Cache'Last);
      end;

      --  Sort the cache

      Sort_Search_Array (C.Cache.all);

      --  Set line offsets

      if not C.Has_Debug then
         return;
      end if;

      declare
         Info_Offset : Offset;
         Line_Offset : Offset;
         Success     : Boolean;
         Ar_Start    : Address;
         Ar_Len      : Storage_Count;
         Start, Len  : uint32;
         First, Last : Natural;
         Mid         : Natural;

      begin
         Seek (C.Aranges, 0);

         while Tell (C.Aranges) < Length (C.Aranges) loop
            Read_Aranges_Header (C, Info_Offset, Success);
            exit when not Success;

            Debug_Info_Lookup (C, Info_Offset, Line_Offset, Success);
            exit when not Success;

            --  Read table

            loop
               Read_Aranges_Entry (C, Ar_Start, Ar_Len);
               exit when Ar_Start = Null_Address and Ar_Len = 0;

               Len   := uint32 (Ar_Len);
               Start := uint32 (Ar_Start - C.Low);

               --  Search START in the array

               First := Cache'First;
               Last  := Cache'Last;
               Mid := First;  --  In case of array with one element
               while First < Last loop
                  Mid := First + (Last - First) / 2;
                  if Start < Cache (Mid).First then
                     Last := Mid - 1;
                  elsif Start >= Cache (Mid).First + Cache (Mid).Size then
                     First := Mid + 1;
                  else
                     exit;
                  end if;
               end loop;

               --  Fill info

               --  There can be overlapping symbols

               while Mid > Cache'First
                 and then Cache (Mid - 1).First <= Start
                 and then Cache (Mid - 1).First + Cache (Mid - 1).Size > Start
               loop
                  Mid := Mid - 1;
               end loop;
               while Mid <= Cache'Last loop
                  if Start < Cache (Mid).First + Cache (Mid).Size
                    and then Start + Len > Cache (Mid).First
                  then
                     --  MID is within the bounds

                     Cache (Mid).Line := uint32 (Line_Offset);
                  elsif Start + Len <= Cache (Mid).First then
                     --  Over

                     exit;
                  end if;
                  Mid := Mid + 1;
               end loop;
            end loop;
         end loop;
      end;
   end Enable_Cache;

   ----------------------
   -- Symbolic_Address --
   ----------------------

   procedure Symbolic_Address
     (C           : in out Dwarf_Context;
      Addr        :        Address;
      Dir_Name    :    out Str_Access;
      File_Name   :    out Str_Access;
      Subprg_Name :    out String_Ptr_Len;
      Line_Num    :    out Natural)
   is
      procedure Set_Result (Match : Line_Info_Registers);
      --  Set results using match

      procedure Set_Result (Match : Line_Info_Registers) is
         Dir_Idx : uint32;
         Off     : Offset;

         Mod_Time : uint32;
         pragma Unreferenced (Mod_Time);

         Length : uint32;
         pragma Unreferenced (Length);

         Directory_Entry_Format : Entry_Format_Array
           renames C.Header.Directory_Entry_Format;

         File_Entry_Format : Entry_Format_Array
           renames C.Header.File_Name_Entry_Format;

      begin
         Seek (C.Lines, C.Header.File_Names);
         Dir_Idx := 0;

         --  Find the entry. Note that, up to DWARF 4, the index is 1-based
         --  whereas, in DWARF 5, it is 0-based.

         if C.Header.Version <= 4 then
            for J in 1 .. Match.File loop
               File_Name := Read_C_String (C.Lines);

               if File_Name (File_Name'First) = ASCII.NUL then
                  --  End of file list, so incorrect entry
                  return;
               end if;

               Dir_Idx  := Read_LEB128 (C.Lines);
               Mod_Time := Read_LEB128 (C.Lines);
               Length   := Read_LEB128 (C.Lines);
            end loop;

            if Dir_Idx = 0 then
               --  No directory

               Dir_Name := null;

            else
               Seek (C.Lines, C.Header.Directories);

               for J in 1 .. Dir_Idx loop
                  Dir_Name := Read_C_String (C.Lines);

                  if Dir_Name (Dir_Name'First) = ASCII.NUL then
                     --  End of directory list, so ill-formed table

                     return;
                  end if;
               end loop;
            end if;

         --  DWARF 5

         else
            for J in 0 .. Match.File loop
               for K in 1 .. Integer (C.Header.File_Name_Entry_Format_Count)
               loop
                  if File_Entry_Format (K).C_Type = DW_LNCT_path then
                     case File_Entry_Format (K).Form is
                        when DW_FORM_string =>
                           File_Name := Read_C_String (C.Lines);

                        when DW_FORM_line_strp =>
                           Read_Section_Offset (C.Lines, Off, C.Header.Is64);
                           if J = Match.File then
                              Seek (C.Line_Str, Off);
                              File_Name := Read_C_String (C.Line_Str);
                           end if;

                        when others =>
                           raise Dwarf_Error with "DWARF form not implemented";
                     end case;

                  elsif File_Entry_Format (K).C_Type = DW_LNCT_directory_index
                  then
                     case File_Entry_Format (K).Form is
                        when DW_FORM_data1 =>
                           Dir_Idx := uint32 (uint8'(Read (C.Lines)));

                        when DW_FORM_data2 =>
                           Dir_Idx := uint32 (uint16'(Read (C.Lines)));

                        when DW_FORM_udata =>
                           Dir_Idx := Read_LEB128 (C.Lines);

                        when others =>
                           raise Dwarf_Error with
                             "invalid DWARF form for DW_LNCT_directory_index";
                     end case;

                  else
                     Skip_Form (C.Lines,
                       File_Entry_Format (K).Form,
                       C.Header.Is64,
                       C.Header.Address_Size);
                  end if;
               end loop;
            end loop;

            Seek (C.Lines, C.Header.Directories);

            for J in 0 .. Dir_Idx loop
               for K in 1 .. Integer (C.Header.Directory_Entry_Format_Count)
               loop
                  if Directory_Entry_Format (K).C_Type = DW_LNCT_path then
                     case Directory_Entry_Format (K).Form is
                        when DW_FORM_string =>
                           Dir_Name := Read_C_String (C.Lines);

                        when DW_FORM_line_strp =>
                           Read_Section_Offset (C.Lines, Off, C.Header.Is64);
                           if J = Dir_Idx then
                              Seek (C.Line_Str, Off);
                              Dir_Name := Read_C_String (C.Line_Str);
                           end if;

                        when others =>
                           raise Dwarf_Error with "DWARF form not implemented";
                     end case;

                  else
                     Skip_Form (C.Lines,
                       Directory_Entry_Format (K).Form,
                       C.Header.Is64,
                       C.Header.Address_Size);
                  end if;
               end loop;
            end loop;
         end if;

         Line_Num := Natural (Match.Line);
      end Set_Result;

      Addr_Int     : constant uint64 := uint64 (Addr);
      Previous_Row : Line_Info_Registers;
      Info_Offset  : Offset;
      Line_Offset  : Offset;
      Success      : Boolean;
      Done         : Boolean;
      S            : Object_Symbol;

   begin
      --  Initialize result

      Dir_Name    := null;
      File_Name   := null;
      Subprg_Name := (null, 0);
      Line_Num    := 0;

      --  Look up the symbol in the cache

      if C.Cache /= null then
         declare
            Addr_Off         : constant uint32 := uint32 (Addr - C.Low);
            First, Last, Mid : Natural;
         begin
            First := C.Cache'First;
            Last  := C.Cache'Last;
            Mid   := First;

            while First <= Last loop
               Mid := First + (Last - First) / 2;
               if Addr_Off < C.Cache (Mid).First then
                  Last := Mid - 1;
               elsif Addr_Off >= C.Cache (Mid).First + C.Cache (Mid).Size then
                  First := Mid + 1;
               else
                  exit;
               end if;
            end loop;

            if Addr_Off >= C.Cache (Mid).First
              and then Addr_Off < C.Cache (Mid).First + C.Cache (Mid).Size
            then
               Line_Offset := Offset (C.Cache (Mid).Line);
               S := Read_Symbol (C.Obj.all, Offset (C.Cache (Mid).Sym));
               Subprg_Name := Object_Reader.Name (C.Obj.all, S);
            else
               return;
            end if;
         end;

      --  Search for the symbol in the binary

      else
         S := First_Symbol (C.Obj.all);
         while S /= Null_Symbol loop
            if Spans (S, Addr_Int) then
               Subprg_Name := Object_Reader.Name (C.Obj.all, S);
               exit;
            end if;

            S := Next_Symbol (C.Obj.all, S);
         end loop;

         --  Search address in aranges table

         Aranges_Lookup (C, Addr, Info_Offset, Success);
         if not Success then
            return;
         end if;

         --  Search stmt_list in info table

         Debug_Info_Lookup (C, Info_Offset, Line_Offset, Success);
         if not Success then
            return;
         end if;
      end if;

      Seek (C.Lines, Line_Offset);
      C.Next_Header := 0;
      Initialize_State_Machine (C);
      Parse_Header (C);
      Previous_Row.Line := 0;

      --  Advance to the first entry

      loop
         Read_And_Execute_Insn (C, Done);

         if C.Registers.Is_Row then
            Previous_Row := C.Registers;
            exit;
         end if;

         exit when Done;
      end loop;

      --  Read the rest of the entries

      while Tell (C.Lines) < C.Next_Header loop
         Read_And_Execute_Insn (C, Done);

         if C.Registers.Is_Row then
            if not Previous_Row.End_Sequence
              and then Addr_Int >= Previous_Row.Address
              and then Addr_Int < C.Registers.Address
            then
               Set_Result (Previous_Row);
               return;

            elsif Addr_Int = C.Registers.Address then
               Set_Result (C.Registers);
               return;
            end if;

            Previous_Row := C.Registers;
         end if;

         exit when Done;
      end loop;
   end Symbolic_Address;

   -------------------
   -- String_Length --
   -------------------

   function String_Length (Str : Str_Access) return Natural is
   begin
      for I in Str'Range loop
         if Str (I) = ASCII.NUL then
            return I - Str'First;
         end if;
      end loop;

      return Str'Last;
   end String_Length;

   ------------------------
   -- Symbolic_Traceback --
   ------------------------

   procedure Symbolic_Traceback
     (Cin          :        Dwarf_Context;
      Traceback    :        STE.Tracebacks_Array;
      Suppress_Hex :        Boolean;
      Symbol_Found :    out Boolean;
      Res          : in out System.Bounded_Strings.Bounded_String)
   is
      use Ada.Characters.Handling;
      C : Dwarf_Context := Cin;

      Addr_In_Traceback : Address;

      Dir_Name    : Str_Access;
      File_Name   : Str_Access;
      Subprg_Name : String_Ptr_Len;
      Line_Num    : Natural;
      Off         : Natural;

   begin
      if not C.Has_Debug then
         Symbol_Found := False;
         return;
      else
         Symbol_Found := True;
      end if;

      for J in Traceback'Range loop
         --  If the buffer is full, no need to do any useless work
         exit when Is_Full (Res);

         Addr_In_Traceback := STE.PC_For (Traceback (J));

         Symbolic_Address
           (C,
            Addr_In_Traceback - Get_Load_Displacement (C),
            Dir_Name,
            File_Name,
            Subprg_Name,
            Line_Num);

         --  If we're not requested to suppress hex addresses, emit it now.

         if not Suppress_Hex then
            Append_Address (Res, Addr_In_Traceback);
            Append (Res, ' ');
         end if;

         if File_Name /= null then
            declare
               Last   : constant Natural := String_Length (File_Name);
               Is_Ada : constant Boolean :=
                 Last > 3
                 and then
                   To_Upper (String (File_Name (Last - 3 .. Last - 1))) =
                   ".AD";
               --  True if this is an Ada file. This doesn't take into account
               --  nonstandard file-naming conventions, but that's OK; this is
               --  purely cosmetic. It covers at least .ads, .adb, and .ada.

               Line_Image : constant String := Natural'Image (Line_Num);
            begin
               if Subprg_Name.Len /= 0 then
                  --  For Ada code, Symbol_Image is in all lower case; we don't
                  --  have the case from the original source code. But the best
                  --  guess is Mixed_Case, so convert to that.

                  if Is_Ada then
                     declare
                        Symbol_Image : String :=
                          Object_Reader.Decoded_Ada_Name
                            (C.Obj.all,
                             Subprg_Name);
                     begin
                        for K in Symbol_Image'Range loop
                           if K = Symbol_Image'First
                             or else not
                             (Is_Letter (Symbol_Image (K - 1))
                              or else Is_Digit (Symbol_Image (K - 1)))
                           then
                              Symbol_Image (K) := To_Upper (Symbol_Image (K));
                           end if;
                        end loop;
                        Append (Res, Symbol_Image);
                     end;
                  else
                     Off := Strip_Leading_Char (C.Obj.all, Subprg_Name);

                     Append
                       (Res,
                        String (Subprg_Name.Ptr (Off .. Subprg_Name.Len)));
                  end if;
               else
                  Append (Res, "???");
               end if;

               Append (Res, " at ");
               Append (Res, String (File_Name (1 .. Last)));
               Append (Res, ':');
               Append (Res, Line_Image (2 .. Line_Image'Last));
            end;
         else
            if Subprg_Name.Len > 0 then
               Off := Strip_Leading_Char (C.Obj.all, Subprg_Name);

               Append (Res, String (Subprg_Name.Ptr (Off .. Subprg_Name.Len)));
            else
               Append (Res, "???");
            end if;

            Append (Res, " at ???");
         end if;

         Append (Res, ASCII.LF);
      end loop;
   end Symbolic_Traceback;

end System.Dwarf_Lines;
