Skip to content

Instantly share code, notes, and snippets.

@tjw
Created June 3, 2015 04:08
Show Gist options
  • Save tjw/db0e153c0e1ddb86c051 to your computer and use it in GitHub Desktop.
Save tjw/db0e153c0e1ddb86c051 to your computer and use it in GitHub Desktop.
CGGStack dtrace wrapper script
#!/bin/zsh -euf
# NOTE: In order for the -c flag to work, 'main' must not be stripped from the executable.
# If it has been, you'll get the cryptic
#
# dtrace: failed to control pid 82297: process exited with status 0
#
if [ $# -ne 1 ]; then
echo "usage: $0 [executable-name|app-path|app-identifier]" 1>&2
exit 1
fi
if [ "$UID" = "0" ]; then
echo "This script should not be run with sudo; it will sudo on its own when running dtrace."
exit 1
fi
# Allow specifying a bundle identifier, using Spotlight to find the actual app to run.
app_path=""
case "$1" in
com\.*)
mdfind "kMDItemCFBundleIdentifier='$1'" | while read candidate; do
if [ -n "$app_path" ]; then
echo "Multiple applications found; please pass the path to the specific copy you want to run:"
mdfind "kMDItemCFBundleIdentifier='$1'"
exit 1
else
app_path="$candidate"
fi
done
;;
*)
app_path="$1"
;;
esac
case "$app_path" in
*\.app)
process_path="$app_path/Contents/MacOS/`/usr/libexec/PlistBuddy -c Print:CFBundleExecutable "$app_path"/Contents/Info.plist`"
;;
*)
process_path="$app_path"
;;
esac
process_name="$process_path:t"
output_file="$HOME/Desktop/$process_name-$(date "+%Y%m%d-%H%M%S").$$.txt.gz"
echo "Tracing CGContext gstate operations to $output_file ..."
# If an application is sandboxed, it won't run correctly when launched as root (which dtrace needs). Also, we shouldn't be launching random applications as root anyway.
# dtrace has a -W option to wait for a named process. The process name can only be 16 bytes long though (MAXCOMLEN).
# The -Z flag allows dtrace to launch w/ zero matches to the probes. This is necessary for the -W flag, since the process gets stopped by the kernel before probes are registered.
echo "Launching dtrace, waiting for \"$process_name\"..."
# The "ring" policy means we'll cycle around our buffer until the process exits and only then emit output. In this case, we use the _DPSNextEvent:entry as a baseline starting point where we know there should be no gstates pushed.
sudo dtrace -q \
-x bufsize=128m \
-x bufpolicy=ring \
-n 'pid$target:CoreGraphics:CGGStackCreateWithGState:return { printf("Stack create: %p\n", arg1); ustack(256); }' \
-n 'pid$target:CoreGraphics:CGGStackSave:entry { printf("Context save: %p\n", arg0); ustack(256); }' \
-n 'pid$target:CoreGraphics:CGGStackRestore:entry { printf("Context restore: %p\n", arg0); ustack(256); }' \
-n 'pid$target:CoreGraphics:CGGStackRelease:entry { printf("Context release: %p\n", arg0); ustack(256); }' \
-n 'pid$target:CoreGraphics:CGGStackReset:entry { printf("Context reset: %p\n", arg0); ustack(256); }' \
-n 'pid$target:CoreGraphics:CGGStackSaveForLayer:entry { printf("Context save/layer: %p\n", arg0); ustack(256); }' \
-n 'pid$target:CoreGraphics:CGGStackSetGState:entry { printf("Context set state: %p\n", arg0); ustack(256); }' \
-n 'pid$target:AppKit:_DPSNextEvent:entry { ustack(); }' \
-Z -W "$process_name" | gzip > "$output_file" &
dtrace_pid=$!
# Wait a bit to let dtrace get ready. Without this, it can miss the launch of the target app.
sleep 1
# Launch the application in question and wait for it to exit (though we could launch it in the background and assume dtrace would latch onto it...)
"$process_path"
# Wait for dtrace to finish
echo "Waiting for dtrace to finish..."
wait "$dtrace_pid"
# Reveal the result diagnostics in Finder for the customer to submit!
open -R "$output_file"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment