Last active
September 6, 2024 09:28
-
-
Save toonetown/618507f37da728bbfe3f3e3490c8a550 to your computer and use it in GitHub Desktop.
A script which will help decode macOS dumps, logs, and panics
This file contains 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
#!/bin/bash | |
########### | |
# A script which will help to decode crashes, spindumps, and samples | |
########### | |
# Helper functions | |
function do_jq { | |
_J="$(which jq 2>/dev/null)" || { | |
echo "You must install jq (brew install jq) to use the script" >&2; return 1; | |
}; "${_J}" "$@" | |
} | |
function do_ips2crash { | |
_I="$(which ips2crash 2>/dev/null)" || { | |
echo "You must install ips2crash (brew install toonetown/extras/ips2crash --head) to use the script" >&2; return 1; | |
}; "${_I}" "$@" | |
} | |
function make_absolute { cd "$(dirname "${1}")"; echo "$(pwd)/$(basename "${1}")"; } | |
function strip_trailing_slash { echo "${1}" | sed -e 's/\/$//g'; } | |
function exit_usage { cat << EOF | |
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
!! ${1} | |
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
Usage: ${0} <TARGET_FILE> <PATH_TO_BINARY|--extract-only> [PATH_TO_KDK] | |
Note: You must place the dSYM file in the same directory as the binary file is located | |
If you pass \`--extract-only\` to a kernel panic, it will decode the panic data and output it instead. | |
When doing extraction-only, the path is not needed. | |
The path to the KDK is only needed for decoding kernel panics. KDKs are downloaded from | |
https://developer.apple.com/downloads/ and when installed are placed locally in /Library/Developer/KDKs. | |
If you do not specify a path to the KDK, the script will attempt to locate the KDK corresponding to the | |
macOS version in the target file. | |
Environment Variables: | |
XCODE Set to the path to Xcode for use in decoding kernel panics. Defaults to /Applications/Xcode-10.3.app | |
ATOS Set to the path to atos for use in decoding userspace panics. Defaults to the version in your PATH | |
EOF | |
exit 1 | |
} | |
# Check our target file | |
TARGET_FILE="$(make_absolute "${1}")" | |
[ -f "${TARGET_FILE}" ] || { exit_usage "Missing target file ${1}"; } | |
# Check the binary file | |
if [ "${2}" == "--extract-only" ]; then | |
EXTRACT_ONLY="yes" | |
else | |
BINARY_PATH="$(strip_trailing_slash "$(make_absolute "${2}")")" | |
BINARY_PLIST="${BINARY_PATH}/Contents/Info.plist" | |
if [ -f "${BINARY_PLIST}" ]; then | |
BINARY_ID="$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${BINARY_PLIST}" 2>/dev/null)" | |
BINARY_VER="$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "${BINARY_PLIST}" 2>/dev/null)" | |
BINARY_EXE="$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "${BINARY_PLIST}" 2>/dev/null)" | |
BINARY_TYPE="$(/usr/libexec/PlistBuddy -c "Print :CFBundlePackageType" "${BINARY_PLIST}" 2>/dev/null)" | |
BINARY_BIN="${BINARY_PATH}/Contents/MacOS/${BINARY_EXE}" | |
else | |
BINARY_ID="$(basename "${BINARY_PATH}")" | |
BINARY_VER="0" | |
BINARY_EXE="${BINARY_ID}" | |
BINARY_TYPE="exe" | |
BINARY_BIN="${BINARY_PATH}" | |
fi | |
[ -f "${BINARY_BIN}" ] || { exit_usage "Invalid or missing binary path ${2}"; } | |
# Check the dSYM | |
DSYM_PATH="${BINARY_PATH}.dSYM" | |
[ -d "${DSYM_PATH}" ] || { exit_usage "Missing dSYM at ${DSYM_PATH}"; } | |
EXTRACT_ONLY="no" | |
fi | |
# Check if we have data to extract | |
CRASH_DATA="$(cat "${TARGET_FILE}")" | |
echo "${CRASH_DATA}" | grep -q macOSPanicString && { | |
CRASH_DATA="$(echo "${CRASH_DATA}" | grep macOSPanicString | do_jq -r .macOSPanicString)" | |
BINARY_TYPE="KEXT" | |
OS_VER="$(echo "${CRASH_DATA}" | grep -a1 'Mac OS version:' 2>/dev/null | tail -n1)" | |
[ -n "${OS_VER}" ] || { exit_usage "Invalid panic file ${1}"; } | |
} | |
echo "${CRASH_DATA}" | do_jq -r '.incident | select(.)' 2>/dev/null | grep -q '.' && { | |
CRASH_DATA="$(do_ips2crash "${TARGET_FILE}")" | |
} | |
if [ "${EXTRACT_ONLY}" == "yes" ]; then | |
echo "${CRASH_DATA}" | |
exit 0 | |
fi | |
# Check if we are a KEXT or a regular bundle | |
if [ "${BINARY_TYPE}" == "KEXT" ]; then | |
OS_VER="$(echo "${CRASH_DATA}" | grep -a1 'Mac OS version:' 2>/dev/null | tail -n1)" | |
[ -n "${OS_VER}" ] || { exit_usage "Invalid panic file ${1}"; } | |
if [ "${EXTRACT_ONLY}" == "yes" ]; then | |
echo "${CRASH_DATA}" | |
exit 0 | |
fi | |
# Point to the right xcode | |
: ${XCODE:="/Applications/Xcode-10.3.app"} | |
LLDB="${XCODE}/Contents/Developer/usr/bin/lldb" | |
[ -x "${LLDB}" ] || { exit_usage "Missing Xcode version 10.3 - set XCODE environment variable"; } | |
# Check the provided KDK (or find one) | |
if [ -d "${3}" ]; then | |
KDK_PATH="$(strip_trailing_slash "$(make_absolute "${3}")")" | |
else | |
KDK_PATH="$(ls -d "/Library/Developer/KDKs/"*"_${OS_VER}.kdk" 2>/dev/null)" | |
[ -d "${KDK_PATH}" ] || { exit_usage "Could not find KDK for OS Version ${OS_VER}"; } | |
fi | |
KDK_KERN="${KDK_PATH}/System/Library/Kernels/kernel" | |
KDK_SCRIPT="${KDK_KERN}.dSYM/Contents/Resources/Python/kernel.py" | |
[ -f "${KDK_KERN}" -a -f "${KDK_SCRIPT}" ] || { exit_usage "Invalid or missing KDK path ${3}"; } | |
LOAD_ADDR="$(echo "${CRASH_DATA}" | sed -nE "s/^ +${BINARY_ID}\(${BINARY_VER}\).*@(0x[a-f0-9]+)->0x[a-f0-9]+$/\1/p")" | |
[ -n "${LOAD_ADDR}" ] || { exit_usage "Could not find ${BINARY_ID}(${BINARY_VER}) load address in ${TARGET_FILE}"; } | |
echo "=========================" | |
echo "Symbolicate: ${TARGET_FILE}" | |
echo " with: ${KDK_PATH}" | |
echo " image: ${BINARY_PATH}" | |
echo " symbols: ${DSYM_PATH}" | |
echo "-------------------------" | |
COMMANDS=() | |
COMMANDS+=("command script import \"${KDK_SCRIPT}\"") | |
COMMANDS+=("settings set target.load-script-from-symbol-file true") | |
COMMANDS+=("addkext -F \"${BINARY_BIN}\" ${LOAD_ADDR}") | |
for i in $(echo "${CRASH_DATA}" | sed -nE 's/^0x[a-f0-9]+ : (0x[a-f0-9]+).*$/\1/p'); do | |
COMMANDS+=("image lookup -a ${i}") | |
done | |
for i in "${COMMANDS[@]}"; do echo "${i}"; done | PATH="/usr/bin:${PATH}" "${LLDB}" "${KDK_KERN}" | |
else | |
# This is a userspace app we want to symbolicate | |
LOAD_ADDR="$(echo "${CRASH_DATA}" | \ | |
sed -nE "s/^ +(0x[0-9a-f]+) - +0x[0-9a-f]+ +\+?(${BINARY_ID}|${BINARY_EXE}).*[ \(](${BINARY_VER}|0)\).*$/\1/p")" | |
[ -n "${LOAD_ADDR}" ] || { exit_usage "Could not find ${BINARY_ID}(${BINARY_VER}) load address in ${TARGET_FILE}"; } | |
: ${ATOS:="$(which atos)"} | |
[ -x "${ATOS}" ] || { exit_usage "Missing atos - set ATOS environment variable"; } | |
# Parse crash dump first | |
ADDRS=(); MATCH="" | |
for i in $(echo "${CRASH_DATA}" | \ | |
sed -nE "s/^[0-9]+[[:space:]]+(${BINARY_ID}|${BINARY_EXE})[[:space:]]+(0x[0-9a-f]+) ${LOAD_ADDR} \+ .*$/\2/p" | \ | |
sort -u); do | |
ADDRS+=("${i}"); MATCH="([0-9]+[[:space:]]+(${BINARY_ID}|${BINARY_EXE})[[:space:]]+)" | |
done | |
# Parse sample | |
[ -n "${MATCH}" ] || { | |
for i in $(echo "${CRASH_DATA}" | \ | |
sed -nE "s/^.*\?\?\? +\(in ${BINARY_ID}\) +load address ${LOAD_ADDR} \+ .*\[(0x[a-f0-9]+)\].*$/\1/p" | \ | |
sort -u); do | |
ADDRS+=("${i}"); MATCH="(.*)\?\?\? +\(in ${BINARY_ID}\) +load address ${LOAD_ADDR} \+ .*\[" | |
done | |
} | |
# Parse spindump | |
[ -n "${MATCH}" ] || { | |
for i in $(echo "${CRASH_DATA}" | \ | |
sed -nE "s/^.*\?\?\? +\(${BINARY_ID} \+.*\[(0x[a-f0-9]+)\].*$/\1/p" | \ | |
sort -u); do | |
ADDRS+=("${i}"); MATCH="(.*)\?\?\? +\(${BINARY_ID} \+.*\[" | |
done | |
} | |
[ -n "${MATCH}" ] || { exit_usage "Invalid target file ${TARGET_FILE}"; } | |
# Symbolicate | |
echo "=========================" | |
echo "Symbolicate: ${TARGET_FILE}" | |
echo " image: ${BINARY_PATH}" | |
echo " symbols: ${DSYM_PATH}" | |
echo "-------------------------" | |
TMPFILE="${TMPDIR:-/tmp}/sym.$$" | |
trap 'rm -f "${TMPFILE}"' EXIT | |
while read -r LINE; do | |
A="${ADDRS[$(($(echo "${LINE}" | cut -d':' -f1)-1))]}" | |
L="$(echo "${LINE}" | cut -d':' -f2-)" | |
echo "s/^${MATCH}${A}.*$/\1${L}/g" | sed -e 's/&/\\\&/g' >> ${TMPFILE} | |
done << EOF | |
$(${ATOS} -o "${BINARY_BIN}" -l ${LOAD_ADDR} ${ADDRS[@]} | grep -n '.') | |
EOF | |
echo "${CRASH_DATA}" | sed -f "${TMPFILE}" -E | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment