Changeset View
Standalone View
usr.sbin/crashinfo/crashinfo.sh
Show All 25 Lines | |||||
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | ||||
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | ||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | ||||
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | ||||
# SUCH DAMAGE. | # SUCH DAMAGE. | ||||
# | # | ||||
# $FreeBSD$ | # $FreeBSD$ | ||||
usage() | CRASHDIR_DEFAULT="/var/crash" | ||||
GDB_DEFAULT="/usr/local/bin/gdb" | |||||
GDB_PATHS="${GDB_DEFAULT} /usr/libexec/gdb /usr/bin/gdb" | |||||
CRASHDIR="" | |||||
GDB="" | |||||
INFO="" | |||||
KERNEL="" | |||||
VMCORE="" | |||||
die() | |||||
{ | { | ||||
echo "usage: crashinfo [-b] [-d crashdir] [-n dumpnr]" \ | echo "$@" >&2 | ||||
markj: Perhaps this should go to stderr? | |||||
Not Done Inline ActionsI didn't want to change the current behaviour. However since you also proposed this I've decided to apply this change. def: I didn't want to change the current behaviour. However since you also proposed this I've… | |||||
Not Done Inline ActionsIt could be done in a separate change; I didn't realize when making that comment that you were avoiding functional changes. Up to you. markj: It could be done in a separate change; I didn't realize when making that comment that you were… | |||||
"[-k kernel] [core]" | |||||
exit 1 | exit 1 | ||||
} | } | ||||
# Remove an uncompressed copy of a dump | # Remove an uncompressed copy of a dump. | ||||
cleanup() | cleanup() | ||||
{ | { | ||||
if [ -e "${VMCORE}" ]; then | |||||
rm -f "${VMCORE}" | |||||
Done Inline ActionsThe variable name VMORE is misspelled. markj: The variable name VMORE is misspelled. | |||||
Not Done Inline ActionsNice catch. def: Nice catch. | |||||
fi | |||||
} | |||||
[ -e $VMCORE ] && rm -f $VMCORE | usage() | ||||
{ | |||||
die "usage: crashinfo [-b] [-d crashdir] [-n dumpnr] [-k kernel] [core]" | |||||
} | } | ||||
last_dumpnr() | |||||
{ | |||||
local next | |||||
if [ ! -r "${CRASHDIR}/bounds" ]; then | |||||
return 1 | |||||
fi | |||||
next=$(cat "${CRASHDIR}/bounds") | |||||
if [ -z "${next}" ] || [ "${next}" -eq 0 ]; then | |||||
return 1 | |||||
fi | |||||
echo $((next - 1)) | |||||
} | |||||
# Find a gdb binary to use and save the value in GDB. | # Find a gdb binary to use and save the value in GDB. | ||||
markjUnsubmitted Not Done Inline ActionsThis comment no longer holds. markj: This comment no longer holds. | |||||
find_gdb() | gdb_find() | ||||
{ | { | ||||
local binary | local binary path | ||||
for binary in /usr/local/bin/gdb /usr/libexec/gdb /usr/bin/gdb; do | path="" | ||||
if [ -x ${binary} ]; then | |||||
GDB=${binary} | for binary in ${GDB_PATHS}; do | ||||
return | if [ -x "${binary}" ]; then | ||||
jhbUnsubmitted Not Done Inline ActionsThe quotes here (and around GDB elsewhere) don't seem really useful since the expansion of GDB_PATHS above ensures that any paths in GDB_PATHS that would contain a space wouldn't work, so it seems to give the user a false sense of something that would work that won't work in practice. jhb: The quotes here (and around GDB elsewhere) don't seem really useful since the expansion of… | |||||
path="${binary}" | |||||
break | |||||
fi | fi | ||||
done | done | ||||
echo "${path}" | |||||
} | } | ||||
# Run a single gdb command against a kernel file in batch mode. | # Run a single gdb command against a kernel file in batch mode. | ||||
# The kernel file is specified as the first argument and the command | # The kernel file is specified as the first argument and the command | ||||
# is given in the remaining arguments. | # is given in the remaining arguments. | ||||
gdb_command() | gdb_command() | ||||
{ | { | ||||
local k | |||||
k=$1 ; shift | if [ "${GDB}" = "${GDB_DEFAULT}" ]; then | ||||
"${GDB}" -batch -ex "$@" "${KERNEL}" | |||||
if [ ${GDB} = /usr/local/bin/gdb ]; then | |||||
${GDB} -batch -ex "$@" $k | |||||
else | else | ||||
echo -e "$@" | ${GDB} -x /dev/stdin -batch $k | echo -e "$@" | "${GDB}" -batch -x /dev/stdin "${KERNEL}" | ||||
fi | fi | ||||
} | } | ||||
find_kernel() | kernel_find() | ||||
{ | { | ||||
local ivers k kvers | local iversion kernel kversion path | ||||
ivers=$(awk ' | path="" | ||||
iversion=$(awk ' | |||||
/Version String/ { | /Version String/ { | ||||
nextline=1 | nextline=1 | ||||
next | next | ||||
} | } | ||||
nextline==1 { | nextline==1 { | ||||
if ($0 ~ "^ [A-Za-z ]+: ") { | if ($0 ~ "^ [A-Za-z ]+: ") { | ||||
nextline=0 | nextline=0 | ||||
} else { | } else { | ||||
} | } | ||||
}' $INFO) | }' "${INFO}") | ||||
# Look for a matching kernel version, handling possible truncation | # Look for a matching kernel version, handling possible truncation | ||||
# of the version string recovered from the dump. | # of the version string recovered from the dump. | ||||
for k in `sysctl -n kern.bootfile` $(ls -t /boot/*/kernel); do | for kernel in $(sysctl -n kern.bootfile) $(ls -t /boot/*/kernel); do | ||||
kvers=$(gdb_command $k 'printf " Version String: %s", version' | \ | kversion=$(gdb_command 'printf " Version String: %s", version' | | ||||
awk "{line=line\$0\"\n\"} END{print substr(line,1,${#ivers})}" \ | awk "{line=line\$0\"\n\"} END{print substr(line,1,${#iversion})}" \ | ||||
2>/dev/null) | 2>/dev/null) | ||||
if [ "$ivers" = "$kvers" ]; then | if [ "${iversion}" = "${kversion}" ]; then | ||||
KERNEL=$k | path="${kernel}" | ||||
break | break | ||||
fi | fi | ||||
done | done | ||||
echo "${path}" | |||||
} | } | ||||
BATCH=false | core_crashdir() | ||||
CRASHDIR=/var/crash | { | ||||
DUMPNR= | local crashdir path | ||||
KERNEL= | |||||
while getopts "bd:n:k:" opt; do | path="${1}" | ||||
case "$opt" in | |||||
b) | |||||
BATCH=true | |||||
;; | |||||
d) | |||||
CRASHDIR=$OPTARG | |||||
;; | |||||
n) | |||||
DUMPNR=$OPTARG | |||||
;; | |||||
k) | |||||
KERNEL=$OPTARG | |||||
;; | |||||
\?) | |||||
usage | |||||
;; | |||||
esac | |||||
done | |||||
shift $((OPTIND - 1)) | crashdir=$(dirname "${path}") | ||||
Done Inline ActionsYou could write this entire routine as dirname "${1}" Ditto for core_dumpnr(). If you prefer to keep the variable names, I'd suggest something other than "name". Maybe "path"? markj: You could write this entire routine as
```
dirname "${1}"
```
Ditto for core_dumpnr(). If you… | |||||
Not Done Inline ActionsI prefer to keep the variables. I agree that path sounds better. def: I prefer to keep the variables. I agree that `path` sounds better. | |||||
echo "${crashdir}" | |||||
} | |||||
if [ $# -eq 1 ]; then | core_dumpnr() | ||||
if [ -n "$DUMPNR" ]; then | { | ||||
echo "-n and an explicit vmcore are mutually exclusive" | local dumpnr path | ||||
usage | |||||
fi | |||||
# Figure out the crash directory and number from the vmcore name. | path="${1}" | ||||
CRASHDIR=`dirname $1` | |||||
DUMPNR=$(expr $(basename $1) : 'vmcore\.\([0-9]*\)') | |||||
if [ -z "$DUMPNR" ]; then | |||||
echo "Unable to determine dump number from vmcore file $1." | |||||
exit 1 | |||||
fi | |||||
elif [ $# -gt 1 ]; then | |||||
usage | |||||
else | |||||
# If we don't have an explicit dump number, operate on the most | |||||
# recent dump. | |||||
if [ -z "$DUMPNR" ]; then | |||||
if ! [ -r $CRASHDIR/bounds ]; then | |||||
echo "No crash dumps in $CRASHDIR." | |||||
exit 1 | |||||
fi | |||||
next=`cat $CRASHDIR/bounds` | |||||
if [ -z "$next" ] || [ "$next" -eq 0 ]; then | |||||
echo "No crash dumps in $CRASHDIR." | |||||
exit 1 | |||||
fi | |||||
DUMPNR=$(($next - 1)) | |||||
fi | |||||
fi | |||||
VMCORE=$CRASHDIR/vmcore.$DUMPNR | dumpnr=$(expr $(basename "${path}") : 'vmcore\.\([0-9]*\)') | ||||
INFO=$CRASHDIR/info.$DUMPNR | echo "${dumpnr}" | ||||
FILE=$CRASHDIR/core.txt.$DUMPNR | } | ||||
HOSTNAME=`hostname` | |||||
if $BATCH; then | core_command() | ||||
echo "Writing crash summary to $FILE." | { | ||||
exec > $FILE 2>&1 | local cmd kernel title vmcore | ||||
fi | |||||
find_gdb | if [ $# -eq 1 ]; then | ||||
if [ -z "$GDB" ]; then | title="${1}" | ||||
echo "Unable to find a kernel debugger." | |||||
exit 1 | |||||
fi | |||||
if [ ! -e $VMCORE ]; then | cmd="${title} -M ${VMCORE} -N ${KERNEL}" | ||||
if [ -e $VMCORE.gz ]; then | |||||
trap cleanup EXIT HUP INT QUIT TERM | |||||
gzcat $VMCORE.gz > $VMCORE | |||||
elif [ -e $VMCORE.zst ]; then | |||||
trap cleanup EXIT HUP INT QUIT TERM | |||||
zstdcat $VMCORE.zst > $VMCORE | |||||
else | else | ||||
echo "$VMCORE not found" | title="${1}" | ||||
exit 1 | cmd="${2}" | ||||
fi | |||||
fi | |||||
if [ ! -e $INFO ]; then | # Parse a command format string and replace: | ||||
echo "$INFO not found" | # - %% with %; | ||||
exit 1 | # - %c with a vmcore path; | ||||
fi | # - %k with a kernel path. | ||||
# If the user didn't specify a kernel, then try to find one. | # Characters '&', '/' and '\' in vmcore and kernel paths must be | ||||
if [ -z "$KERNEL" ]; then | # first escaped for sed(1). | ||||
find_kernel | vmcore=$(echo "${VMCORE}" | sed -E 's/([&\/\\])/\\\1/g') | ||||
if [ -z "$KERNEL" ]; then | kernel=$(echo "${KERNEL}" | sed -E 's/([&\/\\])/\\\1/g') | ||||
echo "Unable to find matching kernel for $VMCORE" | |||||
exit 1 | cmd=$(echo "${cmd}" | sed -E "s/((^|[^%])(%%)*)%c/\1${vmcore}/g;\ | ||||
s/((^|[^%])(%%)*)%k/\1${kernel}/g; s/%%/%/g") | |||||
fi | fi | ||||
elif [ ! -e $KERNEL ]; then | |||||
echo "$KERNEL not found" | |||||
exit 1 | |||||
fi | |||||
umask 077 | cat << EOF | ||||
# Simulate uname | ------------------------------------------------------------------------ | ||||
ostype=$(gdb_command $KERNEL 'printf "%s", ostype') | ${title} | ||||
osrelease=$(gdb_command $KERNEL 'printf "%s", osrelease') | |||||
version=$(gdb_command $KERNEL 'printf "%s", version' | tr '\t\n' ' ') | |||||
machine=$(gdb_command $KERNEL 'printf "%s", machine') | |||||
if ! $BATCH; then | $(${cmd}) | ||||
echo "Writing crash summary to $FILE." | EOF | ||||
exec > $FILE 2>&1 | } | ||||
fi | |||||
echo "$HOSTNAME dumped core - see $VMCORE" | core_kgdb() | ||||
echo | { | ||||
date | local cmd file | ||||
echo | |||||
echo "$ostype $HOSTNAME $osrelease $version $machine" | |||||
echo | |||||
sed -ne '/^ Panic String: /{s//panic: /;p;}' $INFO | |||||
echo | |||||
cmd="${1}" | |||||
# XXX: /bin/sh on 7.0+ is broken so we can't simply pipe the commands to | # XXX: /bin/sh on 7.0+ is broken so we can't simply pipe the commands to | ||||
# kgdb via stdin and have to use a temporary file instead. | # kgdb via stdin and have to use a temporary file instead. | ||||
file=`mktemp /tmp/crashinfo.XXXXXX` | file=$(mktemp /tmp/crashinfo.XXXXXX) | ||||
if [ $? -eq 0 ]; then | if [ $? -eq 0 ]; then | ||||
echo "bt" >> $file | cat << EOF >"${file}" | ||||
echo "quit" >> $file | $(echo "${cmd}" | sed -E $'s/;/\\\n/g') | ||||
${GDB%gdb}kgdb $KERNEL $VMCORE < $file | quit | ||||
rm -f $file | EOF | ||||
"${GDB%gdb}kgdb" "${KERNEL}" "${VMCORE}" <"${file}" | |||||
echo | echo | ||||
rm -f "${file}" | |||||
Done Inline ActionsWhat's the purpose of the bare echo? markj: What's the purpose of the bare echo? | |||||
Not Done Inline Actionskgdb output doesn't end with a new line. I moved this line before rm so that it's easier to understand. def: kgdb output doesn't end with a new line. I moved this line before rm so that it's easier to… | |||||
Not Done Inline ActionsI'd argue it's even worth a comment, but I'll leave that up to you. markj: I'd argue it's even worth a comment, but I'll leave that up to you. | |||||
fi | fi | ||||
echo | } | ||||
echo "------------------------------------------------------------------------" | core_generate() | ||||
echo "ps -axlww" | { | ||||
echo | local core hostname machine osrelease ostype version | ||||
ps -M $VMCORE -N $KERNEL -axlww | |||||
echo | |||||
echo "------------------------------------------------------------------------" | core="${1}" | ||||
echo "vmstat -s" | |||||
echo | |||||
vmstat -M $VMCORE -N $KERNEL -s | |||||
echo | |||||
echo "------------------------------------------------------------------------" | umask 077 | ||||
echo "vmstat -m" | |||||
echo | |||||
vmstat -M $VMCORE -N $KERNEL -m | |||||
echo | |||||
echo "------------------------------------------------------------------------" | if [ -n "${core}" ]; then | ||||
echo "vmstat -z" | echo "Writing crash summary to ${core}." | ||||
echo | exec >"${core}" 2>&1 | ||||
vmstat -M $VMCORE -N $KERNEL -z | fi | ||||
echo | |||||
echo "------------------------------------------------------------------------" | # Simulate uname. | ||||
echo "vmstat -i" | ostype=$(gdb_command 'printf "%s", ostype') | ||||
echo | osrelease=$(gdb_command 'printf "%s", osrelease') | ||||
vmstat -M $VMCORE -N $KERNEL -i | version=$(gdb_command 'printf "%s", version' | tr '\t\n' ' ') | ||||
echo | machine=$(gdb_command 'printf "%s", machine') | ||||
echo "------------------------------------------------------------------------" | hostname=$(hostname) | ||||
echo "pstat -T" | cat << EOF | ||||
echo | ${hostname} dumped core - see ${VMCORE} | ||||
pstat -M $VMCORE -N $KERNEL -T | |||||
echo | |||||
echo "------------------------------------------------------------------------" | $(date) | ||||
echo "pstat -s" | |||||
echo | |||||
pstat -M $VMCORE -N $KERNEL -s | |||||
echo | |||||
echo "------------------------------------------------------------------------" | ${ostype} ${hostname} ${osrelease} ${version} ${machine} | ||||
echo "iostat" | |||||
echo | |||||
iostat -M $VMCORE -N $KERNEL | |||||
echo | |||||
echo "------------------------------------------------------------------------" | $(sed -ne '/^ Panic String: /{s//panic: /;p;}' "${INFO}") | ||||
echo "ipcs -a" | |||||
echo | |||||
ipcs -C $VMCORE -N $KERNEL -a | |||||
echo | |||||
echo "------------------------------------------------------------------------" | EOF | ||||
echo "ipcs -T" | |||||
echo | |||||
ipcs -C $VMCORE -N $KERNEL -T | |||||
echo | |||||
# XXX: This doesn't actually work in 5.x+ | core_kgdb "bt" | ||||
core_command "ps -axlww" | |||||
core_command "vmstat -s" | |||||
core_command "vmstat -m" | |||||
core_command "vmstat -z" | |||||
core_command "vmstat -i" | |||||
core_command "pstat -T" | |||||
core_command "pstat -s" | |||||
core_command "iostat" | |||||
core_command "ipcs -a" "ipcs -C %c -N %k -a" | |||||
core_command "ipcs -T" "ipcs -C %c -N %k -T" | |||||
# XXX: This doesn't actually work in 5.x+. | |||||
if false; then | if false; then | ||||
echo "------------------------------------------------------------------------" | core_command "w -dn" | ||||
echo "w -dn" | |||||
echo | |||||
w -M $VMCORE -N $KERNEL -dn | |||||
echo | |||||
fi | fi | ||||
core_command "nfsstat" | |||||
core_command "netstat -s" | |||||
core_command "netstat -m" | |||||
core_command "netstat -anA" | |||||
core_command "netstat -aL" | |||||
core_command "fstat" | |||||
core_command "dmesg -a" | |||||
core_command "kernel config" "config -x %k" | |||||
core_command "ddb capture buffer" "ddb capture -M %c -N %k print" | |||||
} | |||||
echo "------------------------------------------------------------------------" | main() | ||||
echo "nfsstat" | { | ||||
echo | local batch crashdir dumpnr opt vmcore | ||||
nfsstat -M $VMCORE -N $KERNEL | |||||
echo | |||||
echo "------------------------------------------------------------------------" | batch=false | ||||
echo "netstat -s" | crashdir="" | ||||
echo | dumpnr="" | ||||
netstat -M $VMCORE -N $KERNEL -s | |||||
echo | |||||
echo "------------------------------------------------------------------------" | while getopts "bd:k:n:" opt; do | ||||
echo "netstat -m" | case "${opt}" in | ||||
echo | b) | ||||
netstat -M $VMCORE -N $KERNEL -m | batch=true | ||||
echo | ;; | ||||
d) | |||||
crashdir="${OPTARG}" | |||||
[ -n "${crashdir}" ] || usage | |||||
;; | |||||
k) | |||||
KERNEL="${OPTARG}" | |||||
[ -n "${KERNEL}" ] || usage | |||||
;; | |||||
n) | |||||
dumpnr="${OPTARG}" | |||||
[ -n "${dumpnr}" ] || usage | |||||
;; | |||||
*) | |||||
usage | |||||
;; | |||||
esac | |||||
done | |||||
shift $((OPTIND - 1)) | |||||
echo "------------------------------------------------------------------------" | if [ $# -eq 1 ]; then | ||||
echo "netstat -anA" | if [ -n "${crashdir}" ]; then | ||||
echo | die "Flag -d and an explicit vmcore are mutually exclusive." | ||||
netstat -M $VMCORE -N $KERNEL -anA | elif [ -n "${dumpnr}" ]; then | ||||
echo | die "Flag -n and an explicit vmcore are mutually exclusive." | ||||
fi | |||||
elif [ $# -gt 1 ]; then | |||||
usage | |||||
fi | |||||
echo "------------------------------------------------------------------------" | vmcore="${1}" | ||||
echo "netstat -aL" | |||||
echo | |||||
netstat -M $VMCORE -N $KERNEL -aL | |||||
echo | |||||
echo "------------------------------------------------------------------------" | if [ $# -eq 1 ]; then | ||||
echo "fstat" | # Figure out a crash directory from the vmcore name. | ||||
echo | CRASHDIR=$(core_crashdir "${vmcore}") | ||||
fstat -M $VMCORE -N $KERNEL | elif [ -n "${crashdir}" ]; then | ||||
echo | CRASHDIR="${crashdir}" | ||||
else | |||||
CRASHDIR="${CRASHDIR_DEFAULT}" | |||||
fi | |||||
echo "------------------------------------------------------------------------" | if [ $# -eq 1 ]; then | ||||
echo "dmesg" | # Figure out a dump number from the vmcore name. | ||||
echo | dumpnr=$(core_dumpnr "${vmcore}") | ||||
dmesg -a -M $VMCORE -N $KERNEL | if [ -z "${dumpnr}" ]; then | ||||
echo | die "Unable to determine dump number from vmcore file ${vmcore}." | ||||
fi | |||||
elif [ -z "${dumpnr}" ]; then | |||||
# If we don't have an explicit dump number, operate on the most | |||||
# recent dump. | |||||
dumpnr=$(last_dumpnr) | |||||
if [ -z "${dumpnr}" ]; then | |||||
die "No crash dumps in ${crashdir}." | |||||
fi | |||||
fi | |||||
echo "------------------------------------------------------------------------" | INFO="${CRASHDIR}/info.${dumpnr}" | ||||
echo "kernel config" | VMCORE="${CRASHDIR}/vmcore.${dumpnr}" | ||||
echo | |||||
config -x $KERNEL | |||||
echo | GDB=$(gdb_find) | ||||
echo "------------------------------------------------------------------------" | if [ -z "${GDB}" ]; then | ||||
echo "ddb capture buffer" | die "Unable to find a kernel debugger." | ||||
echo | fi | ||||
ddb capture -M $VMCORE -N $KERNEL print | if [ -z "${KERNEL}" ]; then | ||||
# If the user didn't specify a kernel, then try to find one. | |||||
KERNEL=$(kernel_find) | |||||
if [ -z "${KERNEL}" ]; then | |||||
die "Unable to find a matching kernel for ${VMCORE}." | |||||
fi | |||||
elif [ ! -e "${KERNEL}" ]; then | |||||
die "Unable to find a kernel ${KERNEL}." | |||||
fi | |||||
if [ ! -e "${INFO}" ]; then | |||||
die "Unable to find an info file ${INFO}." | |||||
fi | |||||
if [ ! -e "${VMCORE}" ]; then | |||||
if [ -e "${VMCORE}.gz" ]; then | |||||
trap cleanup EXIT HUP INT QUIT TERM | |||||
gzcat "${VMCORE}.gz" >"${VMCORE}" | |||||
elif [ -e "${VMCORE}.zst" ]; then | |||||
trap cleanup EXIT HUP INT QUIT TERM | |||||
zstdcat "${VMCORE}.zst" >"${VMCORE}" | |||||
else | |||||
die "Unable to find a core dump ${VMCORE}." | |||||
fi | |||||
fi | |||||
if "${batch}"; then | |||||
core_generate "${CRASHDIR}/core.txt.${dumpnr}" | |||||
else | |||||
core_generate | |||||
fi | |||||
} | |||||
main "$@" |
Perhaps this should go to stderr?