Skip to content

Instantly share code, notes, and snippets.

@dceddia
Created June 26, 2021 18:58
Show Gist options
  • Save dceddia/4c2cd2b32a1c9a6029fbc996a05f4a97 to your computer and use it in GitHub Desktop.
Save dceddia/4c2cd2b32a1c9a6029fbc996a05f4a97 to your computer and use it in GitHub Desktop.
Symbolicate a macOS crash report from Sentry
#!/usr/bin/env ruby
# colorization without needing a gem
class String
def colorize(color_code)
"\e[#{color_code}m#{self}\e[0m"
end
def red
colorize(31)
end
def green
colorize(32)
end
def yellow
colorize(33)
end
def blue
colorize(34)
end
def pink
colorize(35)
end
def light_blue
colorize(36)
end
end
if ARGV.length < 3
puts "Usage: symbolicate -g -b -t [crash_log] [arch] [app_bundle] [dsym]"
puts
puts "The crash log, app, and dSYM should all be in the current working directory."
puts
puts " In Xcode: Window > Organizer"
puts " right-click the release, Show in Finder"
puts " right-click the xcarchive, Show Package Contents"
puts " copy files from `dSYMs` and `Products/Applications` into a new empty folder"
puts " copy the crash log to the same folder"
puts
puts "-g Colorize the output to highlight this app's lines"
puts "-b Show the 'Binary Images' section (by default this is omitted for brevity)"
puts "-t Show all threads, including ones that have no calls to your app"
puts "crash_log text file from Sentry"
puts "arch x86_64 or arm64 (get this from Sentry)"
puts "app_bundle TheApp.app (in current directory)"
puts "dsym TheApp.app.dSYM (in current directory)"
exit
end
# Pass -g to colorize output
colorize = false
skip_binary_images = true
hide_irrelevant_threads = true
while ["-g", "-b", "-t"].include?(ARGV.first)
if ARGV.first == "-g"
ARGV.shift
colorize = true
end
if ARGV.first == "-b"
ARGV.shift
skip_binary_images = false
end
if ARGV.first == "-t"
ARGV.shift
hide_irrelevant_threads = false
end
end
crash_log, arch, app_bundle, dsym = ARGV
# Remove trailing slash
app_bundle = app_bundle.dup
app_bundle.gsub!(/\/$/, '')
# Find the binary
app_name = app_bundle.gsub(/\.app$/, '')
app_binary = "#{app_bundle}/Contents/MacOS/#{app_name}"
unless File.exist?(app_binary)
puts "Could not find app binary at #{app_binary}"
exit
end
# Read the crash log
log = File.readlines(crash_log)
class ThreadBlock
def initialize(header, app_name)
@header = header
@lines = []
@app_name = app_name
@has_app_lines = false
end
def has_app_lines?
@has_app_lines
end
def print
@lines.each { |line| printf line }
end
def add_line(line)
@lines << line
if line.include?(@app_name)
@has_app_lines = true
end
end
end
# Look for lines with the app name and symbolicate them
class Parser
def initialize(log, app_name, app_binary, arch, colorize, skip_binary_images, hide_irrelevant_threads)
@log = log
@app_name = app_name
@app_binary = app_binary
@arch = arch
@thread = nil
@colorize = colorize
@in_binary_images = false
@skip_binary_images = skip_binary_images
@hide_irrelevant_threads = hide_irrelevant_threads
end
def parse
@log.each do |line|
# Does this line start a new thread? Finalize/print the old one
if line =~ /^Thread/
begin_thread(line)
end
# Skip these lines if -b is passed
if line =~ /Binary Images:/
begin_binary_images
end
next if @in_binary_images && @skip_binary_images
segments = line.split(' ')
# Pass through lines that we don't care about
unless segments.length >= 4
add(line)
next
end
# Pass through lines that aren't related to the app
frame, name, addr, load_addr = segments
unless name == @app_name
add(line)
next
end
# Symbolicate it
code_location = `atos -o #{@app_binary} -arch #{@arch} -l #{load_addr} #{addr}`
add("%-4.4s%-32.32s%-20.20s%s" % [frame, name, addr, code_location], related_to_app: true)
end
end
def print_thread
return unless @thread
return if @hide_irrelevant_threads && !@thread.has_app_lines?
@thread.print
end
def begin_thread(line)
print_thread
# Reset/Init thread vars
@thread = ThreadBlock.new(line, @app_name)
end
def begin_binary_images
@in_binary_images = true
# This ends the previous thread
print_thread
@thread = nil
end
def add(line, opts = {})
printable = ""
if @colorize && opts[:related_to_app]
# Colorized lines include a newline somehow
printable = line.blue
else
printable = line
end
# Print or add to thread
if @thread
@thread.add_line(printable)
else
printf printable
end
end
end
Parser.new(log, app_name, app_binary, arch, colorize, skip_binary_images, hide_irrelevant_threads).parse
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment