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