-
-
Save alinradut/90458876cbcdd9bf90b4a8b646742c39 to your computer and use it in GitHub Desktop.
Symbolicate a macOS crash report from Sentry
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 && [email protected]_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