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 local ret=1 114 for loops in 1000 10000 100000; do 115 err=0 116 perf record -o $TMPDIR/perf.data --branch-filter any,save_type,u,k -- \ 117 perf test -w traploop $loops > "$TMPDIR/record.txt" 2>&1 118 perf script -i $TMPDIR/perf.data --fields brstacksym | \ 119 tr ' ' '\n' > $TMPDIR/perf.script 120 121 # BRBINF<n>.TYPE == TRAP are mapped to PERF_BR_IRQ by the BRBE driver 122 check_branches "^trap_bench\+[^ ]+/[^ ]/IRQ/" 123 check_branches "^[^ ]+/trap_bench\+[^ ]+/ERET/" 124 if [ $err -eq 0 ]; then 125 ret=0 126 break 127 fi 128 done 129 130 if [ $ret -eq 0 ]; then 131 echo "Testing trap & eret branches [Passed]" 132 err=$start_err 133 else 134 echo "Testing trap & eret branches [Failed]" 135 err=1 136 fi 137} 138 139test_kernel_branches() { 140 echo "Testing kernel branch sampling" 141 142 if ! perf record --branch-filter any,k -o- -- true > "$TMPDIR/record.txt" 2>&1; then 143 echo "Testing that k option [Skipped not enough privileges]" 144 return 145 fi 146 start_err=$err 147 local ret=1 148 for loops in 1000 10000 100000; do 149 err=0 150 perf record -o $TMPDIR/perf.data --branch-filter any,k -- \ 151 perf bench syscall basic --loop $loops > "$TMPDIR/record.txt" 2>&1 152 perf script -i $TMPDIR/perf.data --fields brstack | \ 153 tr ' ' '\n' > $TMPDIR/perf.script 154 155 # Example of branch entries: 156 # "0xffffffff93bda241/0xffffffff93bda20f/M/-/-/..." 157 # Source addresses come first in user or kernel code. Next is the target 158 # address that must be in the kernel. 159 160 # Look for source addresses with top bit set 161 if ! grep -q -E -m1 "^0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then 162 err=1 163 fi 164 # Look for no target addresses without top bit set 165 if grep -q -E -m1 "^0x[0-9a-f]{0,16}/0x[0-7][0-9a-f]{1,15}/" \ 166 $TMPDIR/perf.script; then 167 err=1 168 fi 169 if [ $err -eq 0 ]; then 170 ret=0 171 break 172 fi 173 done 174 175 if [ $ret -eq 0 ]; then 176 echo "Testing kernel branch sampling [Passed]" 177 err=$start_err 178 else 179 echo "Testing kernel branch sampling [Failed]" 180 err=1 181 fi 182} 183 184# first argument <arg0> is the argument passed to "--branch-stack <arg0>,save_type,u" 185# second argument are the expected branch types for the given filter 186test_filter() { 187 test_filter_filter=$1 188 test_filter_expect=$2 189 190 echo "Testing branch stack filtering permutation ($test_filter_filter,$test_filter_expect)" 191 perf record -o "$TMPDIR/perf.data" --branch-filter "$test_filter_filter,save_type,u" -- \ 192 ${TESTPROG} > "$TMPDIR/record.txt" 2>&1 193 perf script -i "$TMPDIR/perf.data" --fields brstack > "$TMPDIR/perf.script" 194 195 # fail if we find any branch type that doesn't match any of the expected ones 196 # also consider UNKNOWN branch types (-) 197 if [ ! -s "$TMPDIR/perf.script" ] 198 then 199 echo "Testing branch stack filtering [Failed empty script output]" 200 err=1 201 return 202 fi 203 # Look for lines not matching test_filter_expect ignoring issues caused 204 # by empty output 205 tr -s ' ' '\n' < "$TMPDIR/perf.script" | grep '.' | \ 206 grep -E -vm1 "^[^ ]*/($test_filter_expect|-|( *))/.*$" \ 207 > "$TMPDIR/perf.script-filtered" || true 208 if [ -s "$TMPDIR/perf.script-filtered" ] 209 then 210 echo "Testing branch stack filtering [Failed unexpected branch filter]" 211 cat "$TMPDIR/perf.script" 212 err=1 213 return 214 fi 215 echo "Testing branch stack filtering [Passed]" 216} 217 218test_syscall() { 219 echo "Testing syscalls" 220 # skip if perf doesn't have enough privileges 221 if ! perf record --branch-filter any,k -o- -- true > "$TMPDIR/record.txt" 2>&1; then 222 echo "Testing syscalls [Skipped: not enough privileges]" 223 return 224 fi 225 start_err=$err 226 local ret=1 227 for loops in 8000 30000 100000; do 228 err=0 229 perf record -o $TMPDIR/perf.data --branch-filter \ 230 any_call,save_type,u,k -c 10007 -- \ 231 perf bench syscall basic --loop $loops > "$TMPDIR/record.txt" 2>&1 232 perf script -i $TMPDIR/perf.data --fields brstacksym | \ 233 tr ' ' '\n' > $TMPDIR/perf.script 234 235 check_branches "getppid[^ ]*/SYSCALL/" 236 if [ $err -eq 0 ]; then 237 ret=0 238 break 239 fi 240 done 241 242 if [ $ret -eq 0 ]; then 243 echo "Testing syscalls [Passed]" 244 err=$start_err 245 else 246 echo "Testing syscalls [Failed]" 247 err=1 248 fi 249} 250set -e 251 252test_user_branches 253test_syscall 254test_kernel_branches 255test_trap_eret_branches 256 257any_call="CALL|IND_CALL|COND_CALL|SYSCALL|IRQ" 258 259if is_arm64; then 260 any_call="$any_call|FAULT_DATA|FAULT_INST" 261fi 262 263test_filter "any_call" "$any_call" 264test_filter "call" "CALL|SYSCALL" 265test_filter "cond" "COND" 266test_filter "any_ret" "RET|COND_RET|SYSRET|ERET" 267 268test_filter "call,cond" "CALL|SYSCALL|COND" 269test_filter "any_call,cond" "$any_call|COND" 270test_filter "any_call,cond,any_ret" "$any_call|COND|RET|COND_RET" 271 272cleanup 273exit $err 274