1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
|
#!/bin/bash
##===- bolt/utils/bughunter.sh - Help locate BOLT bugs -------*- Script -*-===##
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
# details.
#
##===----------------------------------------------------------------------===##
#
# This script attempts to narrow down llvm-bolt bug to a single function in the
# input binary.
#
# If such a function is found, llvm-bolt could be run just on this function
# to mitigate debugging process.
#
# The following envvars are used by this script:
#
# BOLT - path to llvm-bolt
#
# BOLT_OPTIONS - options to be used by llvm-bolt
#
# INPUT_BINARY - input for llvm-bolt
#
# PRE_COMMAND - command to execute prior to running optimized binary
#
# POST_COMMAND - command to filter results of running optimized binary
#
# TIMEOUT_OR_CMD - optional timeout or command on optimized binary command
# if the value is a number with an optional trailing letter
# [smhd] it is considered a paramter to "timeout",
# otherwise it's a shell command that wraps the optimized
# binary command.
#
# COMMAND_LINE - command line options to run optimized binary with
#
# IGNORE_ERROR - ignore error codes returned from optimized binary
#
# GOLD_FILE - file containing expected output from optimized binary
#
# FUNC_NAMES - if set, path to an initial list of function names to
# search. Otherwise, nm is used on the original binary.
#
# OFFLINE - if set, bughunter will produce the binaries but will not
# run them, and will depend on you telling whether it
# succeeded or not.
#
# MAX_FUNCS - if set, use -max-funcs to narrow down the offending
# function. if non-zero, start -max-funcs at $MAX_FUNCS
# otherwise, count the number of symbols in the binary.
#
# MAX_FUNCS_FLAG - BOLT command line option to use for MAX_FUNCS search.
# Default is -max-funcs. Can also be used for relocation
# debugging, e.g. -max-data-relocations.
#
# VERBOSE - if non-empty, set the script to echo mode.
#
##===----------------------------------------------------------------------===##
BOLT=${BOLT:=llvm-bolt}
ulimit -c 0
set -o pipefail
if [[ -n "$VERBOSE" ]]; then
set -x
fi
if [[ ! -x $INPUT_BINARY ]] ; then
echo "INPUT_BINARY must be set to an executable file"
exit 1
fi
if [[ -z "$PRE_COMMAND" ]] ; then
PRE_COMMAND=':'
fi
if [[ -z "$POST_COMMAND" ]] ; then
POST_COMMAND='cat'
fi
if [[ -n "$TIMEOUT_OR_CMD" && $TIMEOUT_OR_CMD =~ ^[0-9]+[smhd]?$ ]] ; then
TIMEOUT_OR_CMD="timeout -s KILL $TIMEOUT_OR_CMD"
fi
if [[ -z "$MAX_FUNCS_FLAG" ]] ; then
MAX_FUNCS_FLAG="-max-funcs"
fi
OPTIMIZED_BINARY=$(mktemp -t -u --suffix=.bolt $(basename ${INPUT_BINARY}).XXX)
OUTPUT_FILE="${OPTIMIZED_BINARY}.out"
BOLT_LOG=$(mktemp -t -u --suffix=.log boltXXX)
if [[ -z $OFFLINE ]]; then
echo "Verify input binary passes"
echo " INPUT_BINARY: $PRE_COMMAND && $TIMEOUT_OR_CMD $INPUT_BINARY $COMMAND_LINE |& $POST_COMMAND >& $OUTPUT_FILE"
($PRE_COMMAND && $TIMEOUT_OR_CMD $INPUT_BINARY $COMMAND_LINE |& $POST_COMMAND >& $OUTPUT_FILE)
STATUS=$?
if [[ "$IGNORE_ERROR" == "1" ]]; then
FAIL=0
else
FAIL=$STATUS
fi
if [[ -e "$GOLD_FILE" ]] ; then
cmp -s "$OUTPUT_FILE" "$GOLD_FILE"
FAIL=$?
fi
if [[ $FAIL -ne "0" ]] ; then
echo " Warning: input binary failed"
else
echo " Input binary passes."
fi
fi
echo "Verify optimized binary fails"
($BOLT $BOLT_OPTIONS $INPUT_BINARY -o $OPTIMIZED_BINARY >& $BOLT_LOG)
FAIL=$?
if [[ $FAIL -eq "0" ]]; then
if [[ -z $OFFLINE ]]; then
echo " OPTIMIZED_BINARY: $PRE_COMMAND && $TIMEOUT_OR_CMD $OPTIMIZED_BINARY $COMMAND_LINE |& $POST_COMMAND >& $OUTPUT_FILE"
($PRE_COMMAND && $TIMEOUT_OR_CMD $OPTIMIZED_BINARY $COMMAND_LINE |& $POST_COMMAND >& $OUTPUT_FILE)
STATUS=$?
if [[ "$IGNORE_ERROR" == "1" ]]; then
FAIL=0
else
FAIL=$STATUS
fi
if [[ -e "$GOLD_FILE" ]] ; then
cmp -s "$OUTPUT_FILE" "$GOLD_FILE"
FAIL=$?
fi
else
echo "Did it pass? Type the return code [0 = pass, 1 = fail]"
read -n1 PASS
fi
if [[ $FAIL -eq "0" ]] ; then
echo " Warning: optimized binary passes."
else
echo " Optimized binary fails as expected."
fi
else
echo " Bolt crashes while generating optimized binary."
fi
# Collect function names
FF=$(mktemp -t -u --suffix=.txt func-names.XXX)
nm --defined-only -p $INPUT_BINARY | grep " [TtWw] " | cut -d ' ' -f 3 | egrep -v "\._" | egrep -v '^$' | sort -u > $FF
# Use function names or numbers
if [[ -z "$MAX_FUNCS" ]] ; then
# Do binary search on function names
if [[ -n "$FUNC_NAMES" ]]; then
FF=$FUNC_NAMES
fi
NUM_FUNCS=$(wc -l $FF | cut -d ' ' -f 1)
HALF=$(expr \( $NUM_FUNCS + 1 \) / 2)
PREFIX=$(mktemp -t -u --suffix=.txt func-names.XXX)
FF0=$PREFIX\aa
FF1=$PREFIX\ab
split -a 2 -l $HALF $FF $PREFIX
FF=$FF0
NUM_FUNCS=$(wc -l $FF | cut -d ' ' -f 1)
CONTINUE=$(expr $NUM_FUNCS \> 0)
else
P=0
if [[ "$MAX_FUNCS" -eq "0" ]]; then
Q=$(wc -l $FF | cut -d ' ' -f 1)
else
Q=$MAX_FUNCS
fi
I=$Q
CONTINUE=$(expr \( $Q - $P \) \> 1)
fi
ITER=0
while [[ "$CONTINUE" -ne "0" ]] ; do
rm -f $OPTIMIZED_BINARY
if [[ -z "$MAX_FUNCS" ]] ; then
echo "Iteration $ITER, trying $FF / $HALF functions"
SEARCH_OPT="-funcs-file-no-regex=$FF"
else
I=$(expr \( $Q + $P \) / 2)
echo "Iteration $ITER, P=$P, Q=$Q, I=$I"
SEARCH_OPT="$MAX_FUNCS_FLAG=$I"
fi
echo " BOLT: $BOLT $BOLT_OPTIONS $INPUT_BINARY $SEARCH_OPT -o $OPTIMIZED_BINARY >& $BOLT_LOG"
($BOLT $BOLT_OPTIONS $INPUT_BINARY $SEARCH_OPT -o $OPTIMIZED_BINARY >& $BOLT_LOG)
FAIL=$?
echo " BOLT failure=$FAIL"
rm -f $OUTPUT_FILE
if [[ $FAIL -eq "0" ]] ; then
if [[ -z $OFFLINE ]]; then
echo " OPTIMIZED_BINARY: $PRE_COMMAND && $TIMEOUT_OR_CMD $OPTIMIZED_BINARY $COMMAND_LINE |& $POST_COMMAND >& $OUTPUT_FILE"
($PRE_COMMAND && $TIMEOUT_OR_CMD $OPTIMIZED_BINARY $COMMAND_LINE |& $POST_COMMAND >& $OUTPUT_FILE)
STATUS=$?
if [[ "$IGNORE_ERROR" == "1" ]]; then
FAIL=0
else
FAIL=$STATUS
fi
if [[ -e "$GOLD_FILE" ]] ; then
cmp -s "$OUTPUT_FILE" "$GOLD_FILE"
FAIL=$?
fi
echo " OPTIMIZED_BINARY failure=$FAIL"
else
echo "Did it pass? Type the return code [0 = pass, 1 = fail]"
read -n1 PASS
fi
else
FAIL=1
fi
if [[ -z "$MAX_FUNCS" ]] ; then
if [[ $FAIL -eq "0" ]] ; then
if [[ "$FF" == "$FF1" ]]; then
NUM_FUNCS=0
break;
fi
FF=$FF1
NUM_FUNCS=$(wc -l $FF | cut -d ' ' -f 1)
else
HALF=$(expr \( $NUM_FUNCS + 1 \) / 2)
PREFIX=$(mktemp -t -u --suffix=.txt func-names.XXX)
split -a 2 -l $HALF $FF $PREFIX
FF0=$PREFIX\aa
FF1=$PREFIX\ab
FF=$FF0
NUM_FUNCS=$(wc -l $FF | cut -d ' ' -f 1)
if [[ $NUM_FUNCS -eq "1" && ! -e $FF1 ]]; then
break;
fi
fi
CONTINUE=$(expr $NUM_FUNCS \> 0)
else
if [[ $FAIL -eq "0" ]] ; then
P=$I
else
Q=$I
fi
FF=$I
HALF=$I
CONTINUE=$(expr \( $Q - $P \) \> 1)
fi
ITER=$(expr $ITER + 1)
done
if [[ -z "$MAX_FUNCS" ]] ; then
if [[ "$NUM_FUNCS" -ne "0" ]] ; then
FAILED="The function(s) that failed are in $FF"
fi
else
if [[ $P -ne $Q ]] ; then
FF=$(grep "processing ending" $BOLT_LOG | sed -e "s/BOLT-INFO: processing ending on \(.*\)/\1/g" | tail -1)
FAILED="The item that failed is $FF @ $Q"
fi
fi
if [[ -n "$FAILED" ]] ; then
echo "$FAILED"
echo "To reproduce, run: $BOLT $BOLT_OPTIONS $INPUT_BINARY $SEARCH_OPT -o $OPTIMIZED_BINARY"
else
echo "Unable to reproduce bug."
fi
rm $OPTIMIZED_BINARY $OUTPUT_FILE $BOLT_LOG
|