Skip to content

Instantly share code, notes, and snippets.

@hecht1962
Last active May 10, 2024 17:40
Show Gist options
  • Save hecht1962/34e35e4917da2d10fb66e2c88d299b51 to your computer and use it in GitHub Desktop.
Save hecht1962/34e35e4917da2d10fb66e2c88d299b51 to your computer and use it in GitHub Desktop.
Convert an IPS crash report (.ips) to the legacy crash report format (.crash)
#!/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 = '';
$translated = ' (Translated)' if exists $ips->{translated} && $ips->{translated};
# Skip elements that are not present
print <<"HDR";
Process: $ips->{procName} [$ips->{pid}]
Path: $ips->{procPath}
Identifier: $ipsh->{bundleID}
Version: $ipsh->{app_version} ($build_version)
Code Type: $ips->{cpuType}$translated
Parent Process: $ips->{parentProc} [$ips->{parentPid}]
HDR
print <<"HDR" if exists $ips->{responsibleProc};
Responsible: $ips->{responsibleProc} [$ips->{responsiblePid}]
HDR
print <<"HDR";
User ID: $ips->{userID}
Date/Time: $ips->{captureTime}
OS Version: $ipsh->{os_version}
Report Version: 12
HDR
print <<"HDR" if exists $ips->{bridgeVersion};
Bridge OS Version: $ips->{bridgeVersion}{train} ($ips->{bridgeVersion}{build})
HDR
print <<"HDR";
Anonymous UUID: $ips->{crashReporterKey}
HDR
print <<"HDR" if exists $ips->{sleepWakeUUID};
Sleep/Wake UUID: $ips->{sleepWakeUUID}
HDR
print <<"HDR";
Time Awake Since Boot: $ips->{uptime} seconds
HDR
print <<"HDR" if exists $ips->{wakeTime};
Time Since Wake: $ips->{wakeTime} seconds
HDR
print <<"HDR";
System Integrity Protection: $ips->{sip}
Crashed Thread: $ftid Dispatch queue: $ips->{legacyInfo}{threadTriggered}{queue}
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
# 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;
foreach my $thr ( @{ $ips->{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};
print <<"THREAD_HDR";
Thread $tid$crashed:$name
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
##
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
print <<"VMSUMMARY";
VM Region Summary:
$ips->{vmSummary}
VMSUMMARY
@scritturamista
Copy link

Brought here by a search... This is easier to use and integrate than any other solution found online. Would love to be able to use this script in my software to symbolicate crash reports before they are sent over the wire. Is there any license that covers this? Would love to contribute especially if it meant some occasional updates when Apple shifts things around... thx!

@hecht1962
Copy link
Author

No license, it's Public Domain. Use and modify it at will.

@scritturamista
Copy link

Thank you! Please feel free to let us know of any updates, or any way to donate. It would only feel appropriate for all the time this script can save us :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment