-
-
Save jkufver/4a2d444b560e7e93e11c6b5f3558525f to your computer and use it in GitHub Desktop.
Convert an IPS crash report (.ips) to the legacy crash report format (.crash)
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/perl | |
## | |
## This script converts an IPS crash report (.ips) to the legacy crash report format. | |
## | |
## The .ips file (JSON) is read from STDIN and the legacy crash report is written to | |
## STDOUT. | |
## | |
use strict; | |
use warnings; | |
use JSON qw( decode_json ); | |
use Data::Dumper; | |
## | |
## Read IPS File | |
## | |
# Echo lines until we find one starting with '{', which marks the beginning of the | |
# .ips | |
my $ips_header_json; | |
while(1) { | |
$ips_header_json = <>; | |
exit unless defined $ips_header_json; | |
last if $ips_header_json =~ /^{/; | |
print $ips_header_json; | |
} | |
# First line is a JSON header on a single line. | |
my $ipsh = decode_json( $ips_header_json ); | |
die "Invalid header\n" unless defined $ipsh; | |
#print Dumper( $ipsh ); | |
# Read the rest of the file into $ips_json; it is a second JSON object containing the | |
# body of the crash report. | |
my $ips_json = do { local $/; <> }; | |
my $ips = decode_json( $ips_json ); | |
die "Invalid body\n" unless defined $ips; | |
#print Dumper( $ips ); | |
# Access the thread that crashed | |
my $ftid = $ips->{faultingThread}; | |
my $fthr = $ips->{threads}[$ftid]; | |
## | |
## Crash Report Header | |
## | |
my $build_version = $ipsh->{build_version}; | |
$build_version = '???' unless defined $build_version && $build_version ne ''; | |
my $translated = ' (native)'; | |
$translated = ' (Translated)' if exists $ips->{translated} && $ips->{translated}; | |
# New header | |
print <<"HDR"; | |
------------------------------------- | |
Translated Report (Full Report Below) | |
------------------------------------- | |
Incident Identifier: $ipsh->{incident_id} | |
CrashReporter Key: $ips->{crashReporterKey} | |
Hardware Model: $ips->{modelCode} | |
Process: $ips->{procName} [$ips->{pid}] | |
Path: $ips->{procPath} | |
Identifier: $ipsh->{bundleID} | |
Version: $ipsh->{app_version} ($build_version) | |
Code Type: $ips->{cpuType}$translated | |
Role: $ips->{procRole} | |
Parent Process: $ips->{parentProc} [$ips->{parentPid}] | |
Coalition: $ips->{coalitionName} [$ips->{coalitionID}] | |
Date/Time: $ips->{captureTime} | |
Launch Time: $ips->{procLaunch} | |
OS Version: $ipsh->{os_version} | |
Release Type: $ips->{osVersion}{releaseType} | |
Baseband Version: $ips->{basebandVersion} | |
Report Version: 104 | |
HDR | |
print <<"HDR" if exists $ips->{responsibleProc}; | |
Responsible: $ips->{responsibleProc} [$ips->{responsiblePid}] | |
HDR | |
print <<"HDR"; | |
User ID: $ips->{userID} | |
HDR | |
print <<"HDR" if exists $ips->{bridgeVersion}; | |
Bridge OS Version: $ips->{bridgeVersion}{train} ($ips->{bridgeVersion}{build}) | |
HDR | |
print <<"HDR" if exists $ips->{sleepWakeUUID}; | |
Sleep/Wake UUID: $ips->{sleepWakeUUID} | |
HDR | |
print <<"HDR"; | |
Uptime: $ips->{uptime} | |
Is locked: $ips->{isLocked} | |
HDR | |
print <<"HDR" if exists $ips->{wakeTime}; | |
Time Since Wake: $ips->{wakeTime} seconds | |
HDR | |
print <<"HDR"; | |
Exception Type: $ips->{exception}{type} ($ips->{exception}{signal}) | |
HDR | |
print <<"HDR" if exists $ips->{exception}{subtype}; | |
Exception Codes: $ips->{exception}{subtype} | |
HDR | |
print <<"HDR" if exists $ips->{exception}{codes}; | |
Exception Codes: $ips->{exception}{codes} | |
HDR | |
print <<"HDR" if $ips->{isCorpse}; | |
Exception Note: EXC_CORPSE_NOTIFY | |
HDR | |
my $code = ''; | |
$code = "Code $ips->{termination}{code} " if exists $ips->{termination}{code}; | |
print <<"HDR" if exists $ips->{termination}; | |
Termination Reason: Namespace $ips->{termination}{namespace}, $code$ips->{termination}{indicator} | |
Terminating Process: $ips->{termination}{byProc} [$ips->{termination}{byPid}] | |
HDR | |
print <<"HDR"; | |
Triggered by Thread: $ftid | |
HDR | |
print <<"HDR" if exists $ips->{legacyInfo}{threadTriggered}{queue}; | |
Dispatch queue: $ips->{legacyInfo}{threadTriggered}{queue} | |
HDR | |
# Application Specific Information is a bit trickier | |
my $asi = $ips->{asi}; | |
if( defined $asi ) { | |
# "asi" : {"AppKit":["Performing @selector(submit:) from sender NSMenuItem 0x6000008d80e0"],"libsystem_c.dylib":["abort() called"]}, | |
print <<"HDR"; | |
Application Specific Information: | |
HDR | |
foreach my $app ( sort keys %$asi ) { | |
foreach my $info ( @{$asi->{$app}}) { | |
print "$info\n"; | |
} | |
} | |
} | |
print "\n"; | |
print <<"HDR" if exists $ips->{vmregioninfo}; | |
VM Region Info: $ips->{vmregioninfo} | |
HDR | |
print <<"HDR" if exists $ips->{ktriageinfo}; | |
Kernel Triage: | |
$ips->{ktriageinfo} | |
HDR | |
## | |
## Stack Trace for each Thread | |
## | |
my $tid = 0; | |
my @threads = @{$ips->{threads}}; | |
my %a = ( "frames" => $ips->{lastExceptionBacktrace} ); | |
if( exists $ips->{lastExceptionBacktrace}) { | |
$tid = -1; | |
unshift @threads, { "frames" => $ips->{lastExceptionBacktrace} }; | |
} | |
foreach my $thr (@threads) { | |
# Format and print thread header | |
my $crashed = exists $thr->{triggered} ? ' Crashed' : ''; | |
my $name = ''; | |
$name = ": Dispatch queue: $thr->{queue}" if exists $thr->{queue}; | |
$name = ": $thr->{name}" if exists $thr->{name}; | |
my $title = "Thread $tid$crashed:$name"; | |
$title = "Last Exception Backtrace:" if $tid == -1; | |
print <<"THREAD_HDR"; | |
$title | |
THREAD_HDR | |
# Print information for each stack frame | |
my $fid = 0; | |
foreach my $fr ( @{ $thr->{frames}}) { | |
my $image = $ips->{usedImages}[$fr->{imageIndex}]; | |
my $imageName = exists $image->{name} ? $image->{name} : '???'; | |
printf "%-3d %-30s\t%#18x ", $fid, $imageName, $image->{base} + $fr->{imageOffset}; | |
if( exists $fr->{symbol}) { | |
# Symbolized | |
print $fr->{symbol}; | |
print " + $fr->{symbolLocation}" if $fr->{symbolLocation} > 0; | |
print " ($fr->{sourceFile}:$fr->{sourceLine})" if exists $fr->{sourceFile} && exists $fr->{sourceLine}; | |
print ' [inlined]' if exists $fr->{inline} && $fr->{inline}; | |
} | |
elsif( $imageName eq '???' ) { | |
print '???'; | |
} | |
else { | |
# Raw | |
printf "%#x + %d", $image->{base}, $fr->{imageOffset} | |
} | |
print "\n"; | |
++$fid; | |
} | |
++$tid; | |
} | |
## | |
## Thread State (different flavors for each architecture) | |
## | |
print "\n"; | |
my $state = $fthr->{threadState}; | |
if( $state->{flavor} eq 'x86_THREAD_STATE') { | |
print <<"THREADSTATE_X86_HDR"; | |
Thread $ftid crashed with X86 Thread State (64-bit): | |
THREADSTATE_X86_HDR | |
my @regs = qw(rax rbx rcx rdx rdi rsi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 rip rflags cr2); | |
my $reg_row = 0; | |
foreach my $reg ( @regs ) { | |
my $fmt_reg; | |
my $reg_value = $state->{$reg}{value}; | |
if( defined $reg_value ) { | |
$fmt_reg = sprintf('%0#18x', $reg_value); | |
$fmt_reg =~ s/^00/0x/; | |
} | |
else { | |
next if $reg eq 'cr2'; | |
$fmt_reg = '<unavailable>'; | |
} | |
my $preg = $reg; | |
$preg = 'rfl' if $preg eq 'rflags'; | |
printf '%5s: %-18s', $preg, $fmt_reg; | |
if( ++$reg_row >= 4 ) { | |
print "\n"; | |
$reg_row = 0; | |
} | |
} | |
print "\n" unless $reg_row == 0; | |
if( exists $state->{rosetta} ) { | |
my @rosetta_regs = qw(tmp0 tmp1 tmp2); | |
my $reg_row = 0; | |
foreach my $reg ( @rosetta_regs ) { | |
my $fmt_reg; | |
my $reg_value = $state->{rosetta}{$reg}{value}; | |
if( defined $reg_value ) { | |
$fmt_reg = sprintf('%0#18x', $reg_value); | |
} | |
else { | |
$fmt_reg = '<unavailable>'; | |
} | |
printf '%5s: %-18s', $reg, $fmt_reg; | |
if( ++$reg_row >= 4 ) { | |
print "\n"; | |
$reg_row = 0; | |
} | |
} | |
print "\n" unless $reg_row == 0; | |
} | |
if( exists $state->{err}{value} ) { | |
my $err = sprintf('%0#10x', $state->{err}{value}); | |
$err .= " $state->{trap}{description}" if exists $state->{trap}{description}; | |
print <<"THREADSTATE_X86_INFO"; | |
Logical CPU: $state->{cpu}{value} | |
Error Code: $err | |
Trap Number: $state->{trap}{value} | |
THREADSTATE_X86_INFO | |
} | |
print "\n"; | |
} | |
elsif( $state->{flavor} eq 'ARM_THREAD_STATE64') { | |
print <<"THREADSTATE_ARM64_HDR"; | |
Thread $ftid crashed with ARM Thread State (64-bit): | |
THREADSTATE_ARM64_HDR | |
my @regs = qw(fp lr sp pc cpsr far); | |
my $reg_row = 0; | |
my $rid = 0; | |
foreach my $reg ( @{$state->{x}}) { | |
print ' ' if $reg_row == 0; | |
printf '%5s: %0#18x', "x$rid", $reg->{value}; | |
if( ++$reg_row >= 4 ) { | |
print "\n"; | |
$reg_row = 0; | |
} | |
++$rid; | |
} | |
foreach my $reg ( @regs ) { | |
print ' ' if $reg_row == 0; | |
printf '%5s: %0#18x', $reg, $state->{$reg}{value}; | |
if( ++$reg_row >= 3 ) { | |
print "\n"; | |
$reg_row = 0; | |
} | |
} | |
printf " esr: %0#10x %s\n", $state->{esr}{value}, $state->{esr}{description}; | |
} | |
else { | |
print <<"THREADSTATE_UNKNOWN_FLAVOR"; | |
Thread $ftid crashed with $state->{flavor}: | |
<registers> | |
THREADSTATE_UNKNOWN_FLAVOR | |
} | |
## | |
## Thread Instruction Stream | |
## | |
if( exists $fthr->{instructionState}) { | |
print <<"THREAD_INSTRUCTION_STREAM"; | |
Thread $ftid instruction stream: | |
THREAD_INSTRUCTION_STREAM | |
my $instruction_stream = $fthr->{instructionState}{instructionStream}; | |
my $is_offset = $instruction_stream->{offset}; | |
my $isb_off = 0; | |
my $isb_row = 0; | |
my $isb_has_off = 0; | |
my $isb_off_col = 0; | |
my $is_ascii = ''; | |
foreach my $isb ( @{$instruction_stream->{bytes}}) { | |
print ' ' if $isb_row == 0; | |
my $l = ' '; | |
$l = '-' if $isb_row == 8; | |
$l = '[' if $isb_off == $is_offset; | |
$l = ']' if $isb_off == $is_offset + 1; | |
if( $isb_off == $is_offset ) { | |
$isb_has_off = 1; | |
$isb_off_col = $isb_row + 1; | |
} | |
printf '%s%02x', $l, $isb; | |
my $isb_ascii = chr($isb); | |
$isb_ascii = '.' unless $isb_ascii =~ /^[ -}]$/; | |
$is_ascii .= $isb_ascii; | |
if( ++$isb_row >= 16 ) { | |
$l = ' '; | |
$l = ']' if $isb_off == $is_offset; | |
print "$l $is_ascii\n"; | |
$isb_row = 0; | |
$is_ascii = ''; | |
if( $isb_has_off ) { | |
print ' '; | |
print ' ' x ( 3 * $isb_off_col); | |
print "<==\n"; | |
$isb_has_off = 0; | |
} | |
} | |
++$isb_off; | |
} | |
print "\n" unless $isb_row == 0; | |
print "\n"; | |
} | |
## | |
## Binary Images | |
## | |
print <<"BINARY_IMAGES_HDR"; | |
Binary Images: | |
BINARY_IMAGES_HDR | |
foreach my $img ( @{$ips->{usedImages}}) { | |
my $name = $img->{CFBundleIdentifier}; | |
$name = $img->{name} unless defined $name; | |
$name = '???' unless defined $name; | |
my $version = $img->{CFBundleShortVersionString}; | |
$version = '*' unless defined $version; | |
my $path = $img->{path}; | |
$path = '???' unless defined $path; | |
my $imgBase = sprintf('%#x', $img->{base}); | |
$imgBase = '0x0' if $imgBase eq '0'; | |
printf "%#18s - %#18x %s (%s) <%s> %s\n", | |
$imgBase, $img->{base} + $img->{size} - 1, | |
$name, $version, $img->{uuid}, $path; | |
} | |
## | |
## The rest | |
## | |
if ( exists $ips->{extMods} ) { | |
my $extMods = $ips->{extMods}; | |
print <<"EXTMODS"; | |
External Modification Summary: | |
Calls made by other processes targeting this process: | |
task_for_pid: $extMods->{targeted}{task_for_pid} | |
thread_create: $extMods->{targeted}{thread_create} | |
thread_set_state: $extMods->{targeted}{thread_set_state} | |
Calls made by this process: | |
task_for_pid: $extMods->{caller}{task_for_pid} | |
thread_create: $extMods->{caller}{thread_create} | |
thread_set_state: $extMods->{caller}{thread_set_state} | |
Calls made by all processes on this machine: | |
task_for_pid: $extMods->{system}{task_for_pid} | |
thread_create: $extMods->{system}{thread_create} | |
thread_set_state: $extMods->{system}{thread_set_state} | |
EXTMODS | |
} | |
if ( exists $ips->{vmSummary} ) { | |
print <<"VMSUMMARY"; | |
VM Region Summary: | |
$ips->{vmSummary} | |
VMSUMMARY | |
} | |
print <<"RAW"; | |
EOF | |
----------- | |
Full Report | |
----------- | |
$ips_header_json | |
$ips_json | |
RAW |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Made the output match the way Preview and System Messages presents the file, including "Last Exception Backtrace".
"Is locked" is added as well...