Skip to content

Instantly share code, notes, and snippets.

@jkufver
Forked from hecht1962/ipstocrash.pl
Last active May 16, 2024 15:35
Show Gist options
  • Save jkufver/4a2d444b560e7e93e11c6b5f3558525f to your computer and use it in GitHub Desktop.
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)
#!/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
@jkufver
Copy link
Author

jkufver commented Nov 3, 2023

Made the output match the way Preview and System Messages presents the file, including "Last Exception Backtrace".
"Is locked" is added as well...

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