Skip to content

Commit

Permalink
Backtrace: get function name from DWARF sections
Browse files Browse the repository at this point in the history
  • Loading branch information
ysbaddaden committed Feb 1, 2017
1 parent d3d2af5 commit 4ba667e
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 19 deletions.
6 changes: 3 additions & 3 deletions spec/std/callstack_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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/)
Expand Down
120 changes: 108 additions & 12 deletions src/callstack.cr
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,19 @@ 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
next if @@skip.includes?(file)
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
Expand All @@ -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}"
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions src/debug/dwarf/info.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down

0 comments on commit 4ba667e

Please sign in to comment.