#! /bin/sh progname="${0##*/}" progname="${progname%.sh}" usage() { echo "Host side filter pipeline tool to convert kernel /proc/lockdep_chains via" echo "graphviz into dependency chart for visualization. Watch out for any up-arrows" echo "as they signify a circular dependency." echo echo "Usage: ${progname} [flags...] [regex...] < input-file > output-file" echo echo "flags:" echo " --format={png|ps|svg|fig|imap|cmapx} | -T" echo " Output format, default png" echo " --debug | -d" echo " Leave intermediate files /tmp/${progname}.*" echo " --verbose | -v" echo " Do not strip address from lockname" echo " --focus | -f" echo " Show only primary references for regex matches" echo " --cluster" echo " Cluster the primary references for regex matches" echo " --serial= | -s " echo " Input from 'adb -s shell su 0 cat /proc/lockdep_chains'" echo " --input= | -i " echo " Input lockdeps from filename, otherwise from standard in" echo " --output= | -o " echo " Output formatted graph to filename, otherwise to standard out" echo echo "Chart is best viewed in portrait. ps or pdf formats tend to pixelate. png tends" echo "to hit a bug in cairo rendering at scale. Not having a set of regex matches for" echo "locknames will probably give you what you deserve ..." echo echo "Kernel Prerequisite to get /proc/lockdep_chains:" echo " CONFIG_PROVE_LOCKING=y" echo " CONFIG_LOCK_STAT=y" echo " CONFIG_DEBUG_LOCKDEP=y" } rm -f /tmp/${progname}.* # Indent rules and strip out address (may be overridden below) beautify() { sed 's/^./ &/ s/"[[][0-9a-f]*[]] /"/g' } input="cat -" output="cat -" dot_format="-Tpng" filter= debug= focus= cluster= while [ ${#} -gt 0 ]; do case ${1} in -T | --format) dot_format="-T${2}" shift ;; -T*) dot_format="${1}" ;; --format=*) dot_format="-T${1#--format=}" ;; --debug | -d) debug=1 ;; --verbose | -v) # indent, but do _not_ strip out addresses beautify() { sed 's/^./ &/' } ;; --focus | -f | --primary) # reserving --primary focus=1 ;; --secondary) # reserving --secondary focus= ;; --cluster) # reserve -c for dot (configure plugins) cluster=1 ;; --serial | -s) if [ "${input}" != "cat -" ]; then usage >&2 echo "ERROR: --input or --serial can only be specified once" >&2 exit 1 fi input="adb -s ${2} shell su 0 cat /proc/lockdep_chains" shift ;; --serial=*) input="adb -s ${1#--serial=} shell su 0 cat /proc/lockdep_chains" ;; --input | -i) if [ "${input}" != "cat -" ]; then usage >&2 echo "ERROR: --input or --serial can only be specified once" >&2 exit 1 fi input="cat ${2}" shift ;; --input=*) if [ "${input}" != "cat -" ]; then usage >&2 echo "ERROR: --input or --serial can only be specified once" >&2 exit 1 fi input="cat ${1#--input=}" ;; --output | -o) if [ "${output}" != "cat -" ]; then usage >&2 echo "ERROR: --output can only be specified once" >&2 exit 1 fi output="cat - > ${2}" # run through eval shift ;; --output=*) if [ "${output}" != "cat -" ]; then usage >&2 echo "ERROR: --output can only be specified once" >&2 exit 1 fi output="cat - > ${1#--output=}" # run through eval ;; --help | -h | -\?) usage exit ;; *) # Everything else is a filter, which will also hide bad option flags, # which is an as-designed price we pay to allow "->rwlock" for instance. if [ X"${1}" = X"${1#* }" ]; then if [ -z "${filter}" ]; then filter="${1}" else filter="${filter}|${1}" fi else if [ -z "${filter}" ]; then filter=" ${1}" else filter="${filter}| ${1}" fi fi ;; esac shift done if [ -z "${filter}" ]; then echo "WARNING: no regex specified will give you what you deserve!" >&2 fi if [ -n "${focus}" -a -z "${filter}" ]; then echo "WARNING: --focus without regex, ignored" >&2 fi if [ -n "${cluster}" -a -z "${filter}" ]; then echo "WARNING: --cluster without regex, ignored" >&2 fi if [ -n "${cluster}" -a -n "${focus}" -a -n "${filter}" ]; then echo "WARNING: orthogonal options --cluster & --focus, ignoring --cluster" >&2 cluster= fi # convert to dot digraph series ${input} | sed '/^all lock chains:$/d / [&]__lockdep_no_validate__$/d /irq_context: 0/d s/irq_context: [1-9]/irq_context/ s/..*/"&" ->/ s/^$/;/' | sed ': loop N s/ ->\n;$/ ;/ t s/ ->\n/ -> / b loop' > /tmp/${progname}.formed if [ ! -s /tmp/${progname}.formed ]; then echo "ERROR: no input" >&2 if [ -z "${debug}" ]; then rm -f /tmp/${progname}.* fi exit 2 fi if [ -n "${filter}" ]; then grep "${filter}" /tmp/${progname}.formed | sed 's/ ;// s/ -> /|/g' | tr '|' '\n' | sort -u > /tmp/${progname}.symbols fi ( echo 'digraph G {' ( echo 'remincross="true";' echo 'concentrate="true";' echo if [ -s /tmp/${progname}.symbols ]; then if [ -n "${cluster}" ]; then echo 'subgraph cluster_symbols {' ( grep "${filter}" /tmp/${progname}.symbols | sed 's/.*/& [shape=box] ;/' grep -v "${filter}" /tmp/${progname}.symbols | sed 's/.*/& [shape=diamond] ;/' ) | beautify echo '}' else grep "${filter}" /tmp/${progname}.symbols | sed 's/.*/& [shape=box] ;/' grep -v "${filter}" /tmp/${progname}.symbols | sed 's/.*/& [shape=diamond] ;/' fi echo fi ) | beautify if [ -s /tmp/${progname}.symbols ]; then if [ -z "${focus}" ]; then # Secondary relationships fgrep -f /tmp/${progname}.symbols /tmp/${progname}.formed else # Focus only on primary relationships grep "${filter}" /tmp/${progname}.formed fi else cat /tmp/${progname}.formed fi | # optimize int A -> B ; single references sed 's/\("[^"]*"\) -> \("[^"]*"\) ->/\1 -> \2 ;|\2 ->/g' | sed 's/\("[^"]*"\) -> \("[^"]*"\) ->/\1 -> \2 ;|\2 ->/g' | tr '|' '\n' | beautify | grep ' -> ' | sort -u | if [ -s /tmp/${progname}.symbols ]; then beautify < /tmp/${progname}.symbols | sed 's/^ */ /' > /tmp/${progname}.short tee /tmp/${progname}.split | fgrep -f /tmp/${progname}.short | sed 's/ ;$/ [color=red] ;/' fgrep -v -f /tmp/${progname}.short /tmp/${progname}.split rm -f /tmp/${progname}.short /tmp/${progname}.split else cat - fi echo '}' ) | tee /tmp/${progname}.input | if dot ${dot_format} && [ -z "${debug}" ]; then rm -f /tmp/${progname}.* fi | eval ${output}