Hello everyone!
I am working on a Crystal debugger ( for macOS for now as I am macOS user and I use MacDBG as a source of inspiration and lldb/gdb is quite heavy in my opinion) and I have some question:
Is it me or line numbers information are generated incorrectly by LLVM, or may be DWARF decoder is not decoding that info incorrectly?
I modified a DWARF reader code from callstack.cr a little to create a DWARF reader so it generates log like this:
0x10fe897c0-0x10fe8986e (0xae): main
Lines:
        0x10fe897c0: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:84:0
        0x10fe897c8: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:85:5
        0x10fe897d3: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:0:5
        0x10fe897d7: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:35:5
        0x10fe897e3: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:86:7
        0x10fe897ec: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:39:9
        0x10fe897fd: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:0:9
        0x10fe89802: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:86:7
        0x10fe89804: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:0:7
        0x10fe89807: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:37:5
        0x10fe8980a: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:45:5
        0x10fe89813: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:0:5
        0x10fe89817: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:39:9
        0x10fe89825: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:45:5
        0x10fe89839: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:47:14
        0x10fe8983e: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:47:5
        0x10fe89849: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:48:5
        0x10fe8985a: /usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:49:5
which is somewhat correct aside of strange line number order, but what strange is this part:
0x10fd46160-0x10fd468bf (0x75f): __crystal_main Lines: 0x10fd46160: none:0:0 0x10fd46186: /Users/sergey/Projects/crystal/crweb/src/crweb.cr:1:1 0x10fd46192: /usr/local/Cellar/crystal/0.31.1/src/callstack.cr:30:3 0x10fd461ac: /usr/local/Cellar/crystal/0.31.1/src/callstack.cr:36:12 0x10fd461c6: /usr/local/Cellar/crystal/0.31.1/src/callstack.cr:42:3 0x10fd461d2: /usr/local/Cellar/crystal/0.31.1/src/exception.cr:3:1 0x10fd461e3: /usr/local/Cellar/crystal/0.31.1/src/iterator.cr:79:5 0x10fd46202: /usr/local/Cellar/crystal/0.31.1/src/lib_c/x86_64-darwin/c/signal.cr:42:3 0x10fd4621c: /usr/local/Cellar/crystal/0.31.1/src/lib_c/x86_64-darwin/c/signal.cr:43:3 0x10fd46236: /usr/local/Cellar/crystal/0.31.1/src/string.cr:470:3 0x10fd46250: /usr/local/Cellar/crystal/0.31.1/src/string.cr:483:3 0x10fd4626a: /usr/local/Cellar/crystal/0.31.1/src/lib_c/x86_64-darwin/c/sys/mman.cr:12:3 0x10fd46284: /usr/local/Cellar/crystal/0.31.1/src/fiber.cr:18:29 0x10fd4629e: /usr/local/Cellar/crystal/0.31.1/src/file.cr:54:3 0x10fd462b8: /usr/local/Cellar/crystal/0.31.1/src/float/printer/cached_powers.cr:51:3 0x10fd462d2: /usr/local/Cellar/crystal/0.31.1/src/float/printer/cached_powers.cr:141:3 0x10fd46317: /usr/local/Cellar/crystal/0.31.1/src/gc/boehm.cr:267:3 0x10fd4631c: /usr/local/Cellar/crystal/0.31.1/src/crystal/hasher.cr:83:40 0x10fd46355: /usr/local/Cellar/crystal/0.31.1/src/crystal/hasher.cr:83:3 0x10fd46362: /usr/local/Cellar/crystal/0.31.1/src/kernel.cr:1:1 0x10fd463c1: /usr/local/Cellar/crystal/0.31.1/src/kernel.cr:542:1 0x10fd463dc: /usr/local/Cellar/crystal/0.31.1/src/humanize.cr:64:3 0x10fd463f6: /usr/local/Cellar/crystal/0.31.1/src/process/executable_path.cr:12:3 . . . (cut for brevity)
it is all over the place and if I use hexdump to disassemble it it does not look that it is correct.
Can you give me some clues if I am doing it wrong?
Here is my class that is reading DWARF info (still ugly as I am experimenting with it):
require “./dwarf/op”
require “./dwarf/info”
module DWARF
    class Reader
        struct ProcEntry
            property low_pc       : UInt64
            property high_pc      : UInt64
            property func_name    : String
            property line_numbers : Array(Debug::DWARF::LineNumbers::Row)
            def initialize
                @low_pc = 0
                @high_pc = 0
                @func_name = ""
                @line_numbers = [] of Debug::DWARF::LineNumbers::Row
            end
            def initialize(@low_pc, @high_pc, @func_name)
                @line_numbers = [] of Debug::DWARF::LineNumbers::Row
            end
            def contains (addr)
                @low_pc <= addr && addr <= @high_pc
            end
            def addLine (line : Debug::DWARF::LineNumbers::Row)
                return if line.end_sequence
                line_numbers << line
            end
            def inspect (io : IO) : Void
                io << "0x#{@low_pc.to_s(16)}-0x#{@high_pc.to_s(16)} (0x#{(@high_pc - @low_pc).to_s(16)}): #{@func_name}\n"
                io << "\tLines:\n"
                @line_numbers.each do |line|
                    io << "\t\t0x#{(line.address).to_s(16)}: #{!line.directory.empty? ? line.directory + "/" +line.file : "none"}:#{line.line}:#{line.column}\n"
                end
            end
        end
        property dwarf_line_numbers : Array(Debug::DWARF::LineNumbers::Row)
        property strings : Debug::DWARF::Strings?
        property function_names : Array(ProcEntry)
        def self.open(filename)
            self.new(filename)
        end
        def initialize(filename)
            dwarf = File.open(filename, "r")
            @dwarf_macho = Debug::MachO.new(dwarf)
            @function_names = [] of ProcEntry
            @dwarf_line_numbers = [] of Debug::DWARF::LineNumbers::Row
        end
        def read(baseAddress)
            @dwarf_macho.read_section?("__debug_line") do |sh, io|
                line_numbers = Debug::DWARF::LineNumbers.new(io, sh.size)
                line_numbers.matrix.each do |row|
                    row.each do |subRow|
                        line = Debug::DWARF::LineNumbers::Row.new subRow.address - 0x100000000 + baseAddress,
                            subRow.op_index,
                            subRow.directory,
                            subRow.file,
                            subRow.line,
                            subRow.column,
                            subRow.end_sequence
                        dwarf_line_numbers << line
                    end
                end
            end
          
            @strings = @dwarf_macho.read_section?("__debug_str") do |sh, io|
              Debug::DWARF::Strings.new(io, sh.offset, sh.size)
            end
            
            @dwarf_macho.read_section?("__debug_info") do |sh, io|
              nameList = [] of String
          
              while (offset = io.pos - sh.offset) < sh.size
                info = DWARF::Info.new(io, offset)
          
                @dwarf_macho.read_section?("__debug_abbrev") do |sh, io|
                  info.read_abbreviations(io)
                end
          
                # pp! "===> Info: ", info
          
                parse_function_names_from_dwarf(info, baseAddress) do |low_pc, high_pc, name|
                    procEntry = ProcEntry.new low_pc, high_pc, name
                    dwarf_line_numbers.each do |line|
                        if procEntry.contains line.address
                            procEntry.addLine line
                        end
                    end
                    @function_names << procEntry
                end
              end
          
              puts "===> Function Names: #{function_names}"
            end
        end
        def parse_function_names_from_dwarf(info, baseAddress)
            info.each do |code, abbrev, attributes|
              next unless abbrev && abbrev.tag.subprogram?
              name = low_pc = high_pc = nil
        
        
              attributes.each do |(at, form, value)|
                puts "Attr: AT=#{at}, form=#{form}, value=#{form.strp? ? @strings.try(&.decode(value.as(UInt32 | UInt64))) : value}"
                case at
                when Debug::DWARF::AT::DW_AT_name
                  value = @strings.try(&.decode(value.as(UInt32 | UInt64))) if form.strp?
                  name = value.as(String)
                when Debug::DWARF::AT::DW_AT_low_pc
                  low_pc = value.as(LibC::SizeT) - 0x100000000 + baseAddress
                when Debug::DWARF::AT::DW_AT_high_pc
                  if form.addr?
                    high_pc = value.as(LibC::SizeT) - 0x100000000 + baseAddress
                  elsif value.responds_to?(:to_i)
                    high_pc = low_pc.as(LibC::SizeT) + value.to_i
                  end
                end
              end
        
              if low_pc && high_pc && name
                yield low_pc, high_pc, name
              end
            end
        end
    end
end

