220 lines
7.3 KiB
Bash
Executable File
220 lines
7.3 KiB
Bash
Executable File
#! /bin/sh
|
|
#
|
|
# Wrapper script which runs a command under valgrind control,
|
|
# redirects valgrind output into a log file and then
|
|
# checks after the run whether valgrind errors or warnings
|
|
# were logged.
|
|
#
|
|
# The environment is set so that GNOME libs and libstdc++ are
|
|
# valgrind-friendly (for example, explicit alloc/free instead of
|
|
# pooling).
|
|
#
|
|
# Additional valgrind parameters can be passed in the VALGRIND_ARGS
|
|
# environment variable. The log file can be chosen via VALGRIND_LOG,
|
|
# with valgrind.<pid of shell>.<current process>.out as default.
|
|
|
|
|
|
if [ "$VALGRIND_LOG" ]; then
|
|
LOGPARAM=$VALGRIND_LOG
|
|
LOGFILES=$VALGRIND_LOG
|
|
CUSTOM_LOGPARAM=yes
|
|
else
|
|
LOGPARAM=$(pwd)/valgrind.p$$.c%p.out
|
|
# More than one file might get written, use wildcard.
|
|
LOGFILES=$(pwd)/valgrind.p$$.*.out
|
|
CUSTOM_LOGPARAM=no
|
|
fi
|
|
|
|
trap "cat $LOGFILES >&2 2>/dev/null; rm -f $LOGFILES" EXIT
|
|
|
|
# Finds all valgrind processes created for this run of
|
|
# valgrindcheck.sh, detected based on the log files created for
|
|
# them. This is necessary to catch processes which might have forked
|
|
# off from the $VALGRIND_PID that we know about. We cannot simply
|
|
# kill all valgrind processes, because there might be other, longer
|
|
# running processes that need to continue.
|
|
valgrindProcesses () {
|
|
if [ $CUSTOM_LOGPARAM = "no" ]; then
|
|
for file in $LOGFILES; do
|
|
pid=`echo $file | perl -p -e 's/.*valgrind\.p\d+\.c(\d+).*/$1/'`
|
|
echo $pid
|
|
done
|
|
fi
|
|
}
|
|
|
|
# Any child valgrind processes still running?
|
|
valgrindRunning () {
|
|
for pid in `valgrindProcesses`; do
|
|
if ps $pid >/dev/null; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Kill child valgrind process with given list of signals.
|
|
killvalgrind () {
|
|
signals=$1
|
|
for pid in `valgrindProcesses`; do
|
|
if ps --no-headers ww $pid; then
|
|
for signal in $signals; do
|
|
echo "valgrind.sh: killing forked process $pid with signal $signal"
|
|
LC_ALL=C kill -$signal $pid 2>&1 | grep -v 'No such process'
|
|
done
|
|
fi
|
|
done
|
|
}
|
|
|
|
( set +x; echo "*** starting $1 under valgrind, output to ${VALGRIND_CMD_LOG:-stdout}" )
|
|
( set -x; if [ "$VALGRIND_CMD_LOG" ]; then exec >>"$VALGRIND_CMD_LOG" 2>&1; fi; exec env GLIBCXX_FORCE_NEW=1 G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind $VALGRIND_ARGS --gen-suppressions=all --log-file=$LOGPARAM "$@" $OUTPUT 2>&1 ) &
|
|
VALGRIND_PID=$!
|
|
|
|
intvalgrind () {
|
|
kill -INT $VALGRIND_PID
|
|
}
|
|
termvalgrind () {
|
|
kill -TERM $VALGRIND_PID
|
|
}
|
|
|
|
trap "kill -TERM $VALGRIND_PID" TERM
|
|
trap "kill -INT $VALGRIND_PID" INT
|
|
|
|
wait $VALGRIND_PID
|
|
RET=$?
|
|
echo "valgrindcheck ($$): '$@' ($VALGRIND_PID): returned $RET" >&2
|
|
|
|
# give other valgrind instances some time to settle down, then kill them
|
|
sleep 1
|
|
killvalgrind INT TERM
|
|
# let valgrind chew on leak checking for up to 30 seconds before killing it
|
|
# for good
|
|
i=0
|
|
while valgrindRunning && [ $i -lt 30 ]; do
|
|
sleep 1
|
|
i=`expr $i + 1`
|
|
done
|
|
killvalgrind KILL
|
|
# Filter out leaks in forked processes if VALGRIND_LEAK_CHECK_ONLY_FIRST is set,
|
|
# detect if unfiltered errors were reported by valgrind. Unfiltered errors
|
|
# are detected because valgrind will produce a suppression for us, which can
|
|
# found easily by looking for "insert_a_suppression_name_here".
|
|
#
|
|
# Here is some example output:
|
|
#
|
|
# ==13044== Memcheck, a memory error detector
|
|
# ==13044== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
|
|
# ==13044== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
|
|
# ==13044== Command: /tmp/test-fork
|
|
# ==13044== Parent PID: 13043
|
|
# ==13044==
|
|
# ==13044== Conditional jump or move depends on uninitialised value(s)
|
|
# ==13044== at 0x400555: main (test-fork.c:9)
|
|
# ==13044==
|
|
# {
|
|
# <insert_a_suppression_name_here>
|
|
# Memcheck:Cond
|
|
# fun:main
|
|
# }
|
|
# ==13044==
|
|
# ==13044== HEAP SUMMARY:
|
|
# ==13044== in use at exit: 0 bytes in 0 blocks
|
|
# ==13044== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
|
|
# ==13044==
|
|
# ==13044== All heap blocks were freed -- no leaks are possible
|
|
# ==13044==
|
|
# ==13044== For counts of detected and suppressed errors, rerun with: -v
|
|
# ==13044== Use --track-origins=yes to see where uninitialised values come from
|
|
# ==13044== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
|
|
# ==13047==
|
|
# ==13047== HEAP SUMMARY:
|
|
# ==13047== in use at exit: 100 bytes in 1 blocks
|
|
# ==13047== total heap usage: 1 allocs, 0 frees, 100 bytes allocated
|
|
# ==13047==
|
|
# ==13047== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
|
|
# ==13047== at 0x4C244E8: malloc (vg_replace_malloc.c:236)
|
|
# ==13047== by 0x40056E: main (test-fork.c:15)
|
|
# ==13047==
|
|
# {
|
|
# <insert_a_suppression_name_here>
|
|
# Memcheck:Leak
|
|
# fun:malloc
|
|
# fun:main
|
|
# }
|
|
# ==13047== LEAK SUMMARY:
|
|
# ==13047== definitely lost: 100 bytes in 1 blocks
|
|
# ==13047== indirectly lost: 0 bytes in 0 blocks
|
|
# ==13047== possibly lost: 0 bytes in 0 blocks
|
|
# ==13047== still reachable: 0 bytes in 0 blocks
|
|
# ==13047== suppressed: 0 bytes in 0 blocks
|
|
# ==13047==
|
|
# ==13047== For counts of detected and suppressed errors, rerun with: -v
|
|
# ==13047== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
|
|
#
|
|
# This output is meant to be printed completely when VALGRIND_LEAK_CHECK_ONLY_FIRST
|
|
# is not set. Otherwise the "definitely lost" error including its suppression
|
|
# shall be filtered out.
|
|
#
|
|
# 100 is returned by Perl either way because of the Cond error in the main process.
|
|
#
|
|
|
|
SUBRET=0
|
|
for i in $LOGFILES; do
|
|
perl \
|
|
-e '$onlyfirst = $ENV{VALGRIND_LEAK_CHECK_ONLY_FIRST};' \
|
|
-e '@leakskip = split(/,/, $ENV{VALGRIND_LEAK_CHECK_SKIP});' \
|
|
-e '%skippids = {};' \
|
|
-e '$ret = 0;' \
|
|
-e '$pid = 0;' \
|
|
-e '$skipping = 0;' \
|
|
-e 'while (<>) {' \
|
|
-e ' if (/^==(\d*)== Command: (.*)/) {' \
|
|
-e ' $newpid = $1; $command = $2;' \
|
|
-e ' foreach $pattern (@leakskip) {' \
|
|
-e ' if ($command =~ /$pattern/) {' \
|
|
-e ' $skippids{$newpid} = 1;' \
|
|
-e ' last;' \
|
|
-e ' }' \
|
|
-e ' }' \
|
|
-e ' } elsif (/^==(\d*)==/) {'\
|
|
-e ' $newpid = $1;' \
|
|
-e ' } else {' \
|
|
-e ' $newpid = 0;' \
|
|
-e ' }' \
|
|
-e ' if ($newpid && ($skipping == 1 && $pid == $newpid || $skipping == 2 && !$skippids{$newpid})) {' \
|
|
-e ' $skipping = 0;' \
|
|
-e ' }' \
|
|
-e ' if (!$skipping) {' \
|
|
-e ' $pid = $newpid unless $pid;' \
|
|
-e ' $ispotential = /(possibly|indirectly) lost in loss record/;' \
|
|
-e ' if ($newpid && $skippids{$newpid} && $ispotential) {' \
|
|
-e ' $skipping = 2;' \
|
|
-e ' } elsif ($newpid && $onlyfirst && $pid != $newpid && $ispotential) {' \
|
|
-e ' $skipping = 1;' \
|
|
-e ' } else {' \
|
|
-e ' print;' \
|
|
-e ' if (/insert_a_suppression_name_here/) {' \
|
|
-e ' $ret = 100;' \
|
|
-e ' }' \
|
|
-e ' }' \
|
|
-e ' }' \
|
|
-e '}' \
|
|
-e 'exit $ret;' \
|
|
$i
|
|
SUBSUBRET=$?
|
|
if [ $SUBRET -eq 0 ]; then
|
|
SUBRET=$SUBSUBRET
|
|
fi
|
|
done
|
|
|
|
# bad valgrind log result always overrides normal completion status:
|
|
# that way the valgrind errors also show up in the nightly test summary
|
|
# in the case where some other test already failed
|
|
if [ $SUBRET -ne 0 ]; then
|
|
RET=$SUBRET
|
|
echo valgrindcheck: "$@": log analysis overrides return code $RET with $SUBRET >&2
|
|
fi
|
|
rm $LOGFILES
|
|
|
|
echo valgrindcheck: "$@": final result $RET >&2
|
|
exit $RET
|