From 4ba667ec65fbc8a912ff3005c2876de625a5aed1 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Mon, 30 Jan 2017 16:16:55 +0100 Subject: [PATCH] Backtrace: get function name from DWARF sections --- spec/std/callstack_spec.cr | 6 +- src/callstack.cr | 120 +++++++++++++++++++++++++++++++++---- src/debug/dwarf/info.cr | 8 +-- 3 files changed, 115 insertions(+), 19 deletions(-) diff --git a/spec/std/callstack_spec.cr b/spec/std/callstack_spec.cr index 3ec55324d3b4..dbb40e2e5eb7 100644 --- a/spec/std/callstack_spec.cr +++ b/spec/std/callstack_spec.cr @@ -17,9 +17,9 @@ describe "Backtrace" do output = `#{tempfile.path}` # resolved file line:column - output.should match(/#{sample} 3:10/) # callee1 - output.should match(/#{sample} 15:3/) # callee3 - output.should match(/#{sample} 17:1/) # ??? + output.should match(/callee1 at #{sample} 3:10/) + output.should match(/callee3 at #{sample} 15:3/) + output.should match(/__crystal_main at #{sample} 17:1/) # skipped internal details output.should_not match(/src\/callstack\.cr/) diff --git a/src/callstack.cr b/src/callstack.cr index cd20c0c372c5..4baf50332910 100644 --- a/src/callstack.cr +++ b/src/callstack.cr @@ -146,7 +146,9 @@ struct CallStack private def decode_backtrace @callstack.compact_map do |ip| - file, line, column = CallStack.decode_line_number(ip) + pc = CallStack.decode_address(ip) + + file, line, column = CallStack.decode_line_number(pc) if file == "??" file_line_column = "??" else @@ -154,7 +156,9 @@ struct CallStack file_line_column = "#{file} #{line}:#{column}" end - if frame = CallStack.decode_frame(ip) + if name = CallStack.decode_function_name(pc) + function = name + elsif frame = CallStack.decode_frame(ip) _, sname = frame function = String.new(sname) else @@ -167,10 +171,12 @@ struct CallStack {% if flag?(:darwin) || flag?(:freebsd) || flag?(:linux) || flag?(:openbsd) %} @@dwarf_line_numbers : Debug::DWARF::LineNumbers? + @@dwarf_function_names : Array(Tuple(LibC::SizeT, LibC::SizeT, String))? - protected def self.decode_line_number(ip) - if ln = dwarf_line_numbers - if row = ln.find(decode_address(ip)) + protected def self.decode_line_number(pc) + read_dwarf_sections unless @@dwarf_line_numbers + if ln = @@dwarf_line_numbers + if row = ln.find(pc) path = ln.files[row.file]? if dirname = ln.directories[row.directory]? path = "#{dirname}/#{path}" @@ -181,13 +187,72 @@ struct CallStack {"??", 0, 0} end + protected def self.decode_function_name(pc) + read_dwarf_sections unless @@dwarf_function_names + if fn = @@dwarf_function_names + fn.each do |(low_pc, high_pc, function_name)| + return function_name if low_pc <= pc <= high_pc + end + end + end + + protected def self.parse_function_names_from_dwarf(info, strings) + info.each do |code, abbrev, attributes| + next unless abbrev && abbrev.tag.subprogram? + name = low_pc = high_pc = nil + + attributes.each do |(at, form, 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) + when Debug::DWARF::AT::DW_AT_high_pc + if form.addr? + high_pc = value.as(LibC::SizeT) + 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 + {% if flag?(:darwin) %} @@image_slide : LibC::Long? - protected def self.dwarf_line_numbers - @@dwarf_line_numbers ||= locate_dsym_bundle do |mach_o| + protected def self.read_dwarf_sections + locate_dsym_bundle do |mach_o| mach_o.read_section?("__debug_line") do |sh, io| - Debug::DWARF::LineNumbers.new(io, sh.size) + @@dwarf_line_numbers = Debug::DWARF::LineNumbers.new(io, sh.size) + end + + strings = mach_o.read_section?("__debug_str") do |sh, io| + Debug::DWARF::Strings.new(io, sh.offset) + end + + mach_o.read_section?("__debug_info") do |sh, io| + names = [] of {LibC::SizeT, LibC::SizeT, String} + + while io.tell - sh.offset < sh.size + offset = io.tell - sh.offset + info = Debug::DWARF::Info.new(io, offset) + + mach_o.read_section?("__debug_abbrev") do |sh, io| + info.read_abbreviations(io) + end + + parse_function_names_from_dwarf(info, strings) do |name, low_pc, high_pc| + names << {name, low_pc, high_pc} + end + end + + @@dwarf_function_names = names end end end @@ -259,14 +324,37 @@ struct CallStack {% else %} @@base_address : UInt64|UInt32|Nil - protected def self.dwarf_line_numbers - @@dwarf_line_numbers ||= Debug::ELF.open(PROGRAM_NAME) do |elf| + protected def self.read_dwarf_sections + Debug::ELF.open(PROGRAM_NAME) do |elf| elf.read_section?(".text") do |sh, _| @@base_address = sh.addr - sh.offset end elf.read_section?(".debug_line") do |sh, io| - Debug::DWARF::LineNumbers.new(io, sh.size) + @@dwarf_line_numbers = Debug::DWARF::LineNumbers.new(io, sh.size) + end + + strings = elf.read_section?(".debug_str") do |sh, io| + Debug::DWARF::Strings.new(io, sh.offset) + end + + elf.read_section?(".debug_info") do |sh, io| + names = [] of {LibC::SizeT, LibC::SizeT, String} + + while io.tell - sh.offset < sh.size + offset = io.tell - sh.offset + info = Debug::DWARF::Info.new(io, offset) + + elf.read_section?(".debug_abbrev") do |sh, io| + info.read_abbreviations(io) + end + + parse_function_names_from_dwarf(info, strings) do |name, low_pc, high_pc| + names << {name, low_pc, high_pc} + end + end + + @@dwarf_function_names = names end end end @@ -287,9 +375,17 @@ struct CallStack end {% end %} {% else %} - def self.decode_line_number(ip) + def self.decode_address(ip) + ip + end + + def self.decode_line_number(pc) {"??", 0, 0} end + + def self.decode_function_name(pc) + nil + end {% end %} protected def self.decode_frame(ip, original_ip = ip) diff --git a/src/debug/dwarf/info.cr b/src/debug/dwarf/info.cr index 264d2ada0651..6f165a3597b4 100644 --- a/src/debug/dwarf/info.cr +++ b/src/debug/dwarf/info.cr @@ -11,11 +11,11 @@ module Debug property! abbreviations : Array(Abbrev) property dwarf64 : Bool - @offset : Int64 - @ref_offset : UInt64 + @offset : LibC::OffT + @ref_offset : LibC::OffT def initialize(@io : IO::FileDescriptor, @offset) - @ref_offset = offset.to_u64 + @ref_offset = offset @unit_length = @io.read_bytes(UInt32) if @unit_length == 0xffffffff @@ -31,7 +31,7 @@ module Debug @address_size = @io.read_byte.not_nil! end - alias Value = Bool | Int32 | Slice(UInt8) | String | UInt16 | UInt32 | UInt64 | UInt8 + alias Value = Bool | Int32 | Int64 | Slice(UInt8) | String | UInt16 | UInt32 | UInt64 | UInt8 def read_abbreviations(io) @abbreviations = Abbrev.read(io, debug_abbrev_offset)