1#!/bin/bash 2# Check branch stack sampling 3 4# SPDX-License-Identifier: GPL-2.0 5# German Gomez <german.gomez@arm.com>, 2022 6 7shelldir=$(dirname "$0") 8# shellcheck source=lib/perf_has_symbol.sh 9. "${shelldir}"/lib/perf_has_symbol.sh 10 11# skip the test if the hardware doesn't support branch stack sampling 12# and if the architecture doesn't support filter types: any,save_type,u 13if ! perf record -o- --no-buildid --branch-filter any,save_type,u -- true > /dev/null 2>&1 ; then 14 echo "skip: system doesn't support filter types: any,save_type,u" 15 exit 2 16fi 17 18skip_test_missing_symbol brstack_bench 19 20err=0 21TMPDIR=$(mktemp -d /tmp/__perf_test.program.XXXXX) 22TESTPROG="perf test -w brstack" 23 24cleanup() { 25 rm -rf $TMPDIR 26 trap - EXIT TERM INT 27} 28 29trap_cleanup() { 30 set +e 31 echo "Unexpected signal in ${FUNCNAME[1]}" 32 cleanup 33 exit 1 34} 35trap trap_cleanup EXIT TERM INT 36 37is_arm64() { 38 [ "$(uname -m)" = "aarch64" ]; 39} 40 41has_kaslr_bug() { 42 [ "$(uname -m)" != "aarch64" ]; 43} 44 45check_branches() { 46 if ! tr -s ' ' '\n' < "$TMPDIR/perf.script" | grep -E -m1 -q "$1"; then 47 echo "ERROR: Branches missing $1" 48 err=1 49 fi 50} 51 52test_user_branches() { 53 echo "Testing user branch stack sampling" 54 55 start_err=$err 56 err=0 57 perf record -o "$TMPDIR/perf.data" --branch-filter any,save_type,u -- ${TESTPROG} > "$TMPDIR/record.txt" 2>&1 58 perf script -i "$TMPDIR/perf.data" --fields brstacksym > "$TMPDIR/perf.script" 59 60 # example of branch entries: 61 # brstack_foo+0x14/brstack_bar+0x40/P/-/-/0/CALL 62 63 expected=( 64 "^brstack_bench\+[^ ]*/brstack_foo\+[^ ]*/IND_CALL/.*$" 65 "^brstack_foo\+[^ ]*/brstack_bar\+[^ ]*/CALL/.*$" 66 "^brstack_bench\+[^ ]*/brstack_foo\+[^ ]*/CALL/.*$" 67 "^brstack_bench\+[^ ]*/brstack_bar\+[^ ]*/CALL/.*$" 68 "^brstack_bar\+[^ ]*/brstack_foo\+[^ ]*/RET/.*$" 69 "^brstack_foo\+[^ ]*/brstack_bench\+[^ ]*/RET/.*$" 70 "^brstack_bench\+[^ ]*/brstack_bench\+[^ ]*/COND/.*$" 71 "^brstack\+[^ ]*/brstack\+[^ ]*/UNCOND/.*$" 72 ) 73 for x in "${expected[@]}" 74 do 75 check_branches "$x" 76 done 77 78 # Dump addresses only this time 79 perf script -i "$TMPDIR/perf.data" --fields brstack | \ 80 tr ' ' '\n' > "$TMPDIR/perf.script" 81 82 # There should be no kernel addresses in the target with the u option. 83 local regex="0x[89a-f][0-9a-f]{15}" 84 if has_kaslr_bug; then 85 # If the system has a kaslr bug that may leak kernel addresses 86 # in the source of something like an ERET/SYSRET. Make the regex 87 # more specific and just check the target address is in user 88 # code. 89 regex="^0x[0-9a-f]{0,16}/0x[89a-f][0-9a-f]{15}/" 90 fi 91 if grep -q -E -m1 "$regex" $TMPDIR/perf.script; then 92 echo "Testing user branch stack sampling [Failed kernel address found in user mode]" 93 err=1 94 fi 95 # some branch types are still not being tested: 96 # IND COND_CALL COND_RET SYSRET SERROR NO_TX 97 if [ $err -eq 0 ]; then 98 echo "Testing user branch stack sampling [Passed]" 99 err=$start_err 100 else 101 echo "Testing user branch stack sampling [Failed]" 102 fi 103} 104 105test_trap_eret_branches() { 106 echo "Testing trap & eret branches" 107 108 if ! is_arm64; then 109 echo "Testing trap & eret branches [Skipped not arm64]" 110 return 111 fi 112 start_err=$err 113 err=0 114 perf record -o $TMPDIR/perf.data --branch-filter any,save_type,u,k -- \ 115 perf test -w traploop 1000 > "$TMPDIR/record.txt" 2>&1 116 perf script -i $TMPDIR/perf.data --fields brstacksym | \ 117 tr ' ' '\n' > $TMPDIR/perf.script 118 119 # BRBINF<n>.TYPE == TRAP are mapped to PERF_BR_IRQ by the BRBE driver 120 check_branches "^trap_bench\+[^ ]+/[^ ]/IRQ/" 121 check_branches "^[^ ]+/trap_bench\+[^ ]+/ERET/" 122 if [ $err -eq 0 ]; then 123 echo "Testing trap & eret branches [Passed]" 124 err=$start_err 125 else 126 echo "Testing trap & eret branches [Failed]" 127 fi 128} 129 130test_kernel_branches() { 131 echo "Testing kernel branch sampling" 132 133 if ! perf record --branch-filter any,k -o- -- true > "$TMPDIR/record.txt" 2>&1; then 134 echo "Testing that k option [Skipped not enough privileges]" 135 return 136 fi 137 start_err=$err 138 err=0 139 perf record -o $TMPDIR/perf.data --branch-filter any,k -- \ 140 perf bench syscall basic --loop 1000 > "$TMPDIR/record.txt" 2>&1 141 perf script -i $TMPDIR/perf.data --fields brstack | \ 142 tr ' ' '\n' > $TMPDIR/perf.script 143 144 # Example of branch entries: 145 # "0xffffffff93bda241/0xffffffff93bda20f/M/-/-/..." 146 # Source addresses come first in user or kernel code. Next is the target 147 # address that must be in the kernel. 148 149 # Look for source addresses with top bit set 150 if ! grep -q -E -m1 "^0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then 151 echo "Testing kernel branch sampling [Failed kernel branches missing]" 152 err=1 153 fi 154 # Look for no target addresses without top bit set 155 if grep -q -E -m1 "^0x[0-9a-f]{0,16}/0x[0-7][0-9a-f]{1,15}/" $TMPDIR/perf.script; then 156 echo "Testing kernel branch sampling [Failed user branches found]" 157 err=1 158 fi 159 if [ $err -eq 0 ]; then 160 echo "Testing kernel branch sampling [Passed]" 161 err=$start_err 162 else 163 echo "Testing kernel branch sampling [Failed]" 164 fi 165} 166 167# first argument <arg0> is the argument passed to "--branch-stack <arg0>,save_type,u" 168# second argument are the expected branch types for the given filter 169test_filter() { 170 test_filter_filter=$1 171 test_filter_expect=$2 172 173 echo "Testing branch stack filtering permutation ($test_filter_filter,$test_filter_expect)" 174 perf record -o "$TMPDIR/perf.data" --branch-filter "$test_filter_filter,save_type,u" -- \ 175 ${TESTPROG} > "$TMPDIR/record.txt" 2>&1 176 perf script -i "$TMPDIR/perf.data" --fields brstack > "$TMPDIR/perf.script" 177 178 # fail if we find any branch type that doesn't match any of the expected ones 179 # also consider UNKNOWN branch types (-) 180 if [ ! -s "$TMPDIR/perf.script" ] 181 then 182 echo "Testing branch stack filtering [Failed empty script output]" 183 err=1 184 return 185 fi 186 # Look for lines not matching test_filter_expect ignoring issues caused 187 # by empty output 188 tr -s ' ' '\n' < "$TMPDIR/perf.script" | grep '.' | \ 189 grep -E -vm1 "^[^ ]*/($test_filter_expect|-|( *))/.*$" \ 190 > "$TMPDIR/perf.script-filtered" || true 191 if [ -s "$TMPDIR/perf.script-filtered" ] 192 then 193 echo "Testing branch stack filtering [Failed unexpected branch filter]" 194 cat "$TMPDIR/perf.script" 195 err=1 196 return 197 fi 198 echo "Testing branch stack filtering [Passed]" 199} 200 201test_syscall() { 202 echo "Testing syscalls" 203 # skip if perf doesn't have enough privileges 204 if ! perf record --branch-filter any,k -o- -- true > "$TMPDIR/record.txt" 2>&1; then 205 echo "Testing syscalls [Skipped: not enough privileges]" 206 return 207 fi 208 start_err=$err 209 err=0 210 perf record -o $TMPDIR/perf.data --branch-filter \ 211 any_call,save_type,u,k -c 10007 -- \ 212 perf bench syscall basic --loop 8000 > "$TMPDIR/record.txt" 2>&1 213 perf script -i $TMPDIR/perf.data --fields brstacksym | \ 214 tr ' ' '\n' > $TMPDIR/perf.script 215 216 check_branches "getppid[^ ]*/SYSCALL/" 217 218 if [ $err -eq 0 ]; then 219 echo "Testing syscalls [Passed]" 220 err=$start_err 221 else 222 echo "Testing syscalls [Failed]" 223 fi 224} 225set -e 226 227test_user_branches 228test_syscall 229test_kernel_branches 230test_trap_eret_branches 231 232any_call="CALL|IND_CALL|COND_CALL|SYSCALL|IRQ" 233 234if is_arm64; then 235 any_call="$any_call|FAULT_DATA|FAULT_INST" 236fi 237 238test_filter "any_call" "$any_call" 239test_filter "call" "CALL|SYSCALL" 240test_filter "cond" "COND" 241test_filter "any_ret" "RET|COND_RET|SYSRET|ERET" 242 243test_filter "call,cond" "CALL|SYSCALL|COND" 244test_filter "any_call,cond" "$any_call|COND" 245test_filter "any_call,cond,any_ret" "$any_call|COND|RET|COND_RET" 246 247cleanup 248exit $err 249