| #! /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<format>" |
| 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=<serial> | -s <serial>" |
| echo " Input from 'adb -s <serial> shell su 0 cat /proc/lockdep_chains'" |
| echo " --input=<filename> | -i <filename>" |
| echo " Input lockdeps from filename, otherwise from standard in" |
| echo " --output=<filename> | -o <filename>" |
| 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} |