summaryrefslogtreecommitdiff
path: root/tools/perf/tests/shell/test_brstack.sh
blob: eb5837f82e39026a7888d2d2f87c611e929c5c25 (plain) (blame)
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
#!/bin/bash
# Check branch stack sampling

# SPDX-License-Identifier: GPL-2.0
# German Gomez <german.gomez@arm.com>, 2022

shelldir=$(dirname "$0")
# shellcheck source=lib/perf_has_symbol.sh
. "${shelldir}"/lib/perf_has_symbol.sh

# skip the test if the hardware doesn't support branch stack sampling
# and if the architecture doesn't support filter types: any,save_type,u
if ! perf record -o- --no-buildid --branch-filter any,save_type,u -- true > /dev/null 2>&1 ; then
	echo "skip: system doesn't support filter types: any,save_type,u"
	exit 2
fi

skip_test_missing_symbol brstack_bench

err=0
TMPDIR=$(mktemp -d /tmp/__perf_test.program.XXXXX)
TESTPROG="perf test -w brstack"

cleanup() {
	rm -rf $TMPDIR
	trap - EXIT TERM INT
}

trap_cleanup() {
	set +e
	echo "Unexpected signal in ${FUNCNAME[1]}"
	cleanup
	exit 1
}
trap trap_cleanup EXIT TERM INT

is_arm64() {
	[ "$(uname -m)" = "aarch64" ];
}

has_kaslr_bug() {
	[ "$(uname -m)" != "aarch64" ];
}

check_branches() {
	if ! tr -s ' ' '\n' < "$TMPDIR/perf.script" | grep -E -m1 -q "$1"; then
		echo "ERROR: Branches missing $1"
		err=1
	fi
}

test_user_branches() {
	echo "Testing user branch stack sampling"

	start_err=$err
	err=0
	perf record -o "$TMPDIR/perf.data" --branch-filter any,save_type,u -- ${TESTPROG} > "$TMPDIR/record.txt" 2>&1
	perf script -i "$TMPDIR/perf.data" --fields brstacksym > "$TMPDIR/perf.script"

	# example of branch entries:
	# 	brstack_foo+0x14/brstack_bar+0x40/P/-/-/0/CALL

	expected=(
		"^brstack_bench\+[^ ]*/brstack_foo\+[^ ]*/IND_CALL/.*$"
		"^brstack_foo\+[^ ]*/brstack_bar\+[^ ]*/CALL/.*$"
		"^brstack_bench\+[^ ]*/brstack_foo\+[^ ]*/CALL/.*$"
		"^brstack_bench\+[^ ]*/brstack_bar\+[^ ]*/CALL/.*$"
		"^brstack_bar\+[^ ]*/brstack_foo\+[^ ]*/RET/.*$"
		"^brstack_foo\+[^ ]*/brstack_bench\+[^ ]*/RET/.*$"
		"^brstack_bench\+[^ ]*/brstack_bench\+[^ ]*/COND/.*$"
		"^brstack\+[^ ]*/brstack\+[^ ]*/UNCOND/.*$"
	)
	for x in "${expected[@]}"
	do
		check_branches "$x"
	done

	# Dump addresses only this time
	perf script -i "$TMPDIR/perf.data" --fields brstack | \
		tr ' ' '\n' > "$TMPDIR/perf.script"

	# There should be no kernel addresses in the target with the u option.
	local regex="0x[89a-f][0-9a-f]{15}"
	if has_kaslr_bug; then
		# If the system has a kaslr bug that may leak kernel addresses
		# in the source of something like an ERET/SYSRET. Make the regex
		# more specific and just check the target address is in user
		# code.
		regex="^0x[0-9a-f]{0,16}/0x[89a-f][0-9a-f]{15}/"
	fi
	if grep -q -E -m1 "$regex" $TMPDIR/perf.script; then
		echo "Testing user branch stack sampling [Failed kernel address found in user mode]"
		err=1
	fi
	# some branch types are still not being tested:
	# IND COND_CALL COND_RET SYSRET SERROR NO_TX
	if [ $err -eq 0 ]; then
		echo "Testing user branch stack sampling [Passed]"
		err=$start_err
	else
		echo "Testing user branch stack sampling [Failed]"
	fi
}

test_trap_eret_branches() {
	echo "Testing trap & eret branches"

	if ! is_arm64; then
		echo "Testing trap & eret branches [Skipped not arm64]"
		return
	fi
	start_err=$err
	err=0
	perf record -o $TMPDIR/perf.data --branch-filter any,save_type,u,k -- \
		perf test -w traploop 1000 > "$TMPDIR/record.txt" 2>&1
	perf script -i $TMPDIR/perf.data --fields brstacksym | \
		tr ' ' '\n' > $TMPDIR/perf.script

	# BRBINF<n>.TYPE == TRAP are mapped to PERF_BR_IRQ by the BRBE driver
	check_branches "^trap_bench\+[^ ]+/[^ ]/IRQ/"
	check_branches "^[^ ]+/trap_bench\+[^ ]+/ERET/"
	if [ $err -eq 0 ]; then
		echo "Testing trap & eret branches [Passed]"
		err=$start_err
	else
		echo "Testing trap & eret branches [Failed]"
	fi
}

test_kernel_branches() {
	echo "Testing kernel branch sampling"

	if ! perf record --branch-filter any,k -o- -- true > "$TMPDIR/record.txt" 2>&1; then
		echo "Testing that k option [Skipped not enough privileges]"
		return
	fi
	start_err=$err
	err=0
	perf record -o $TMPDIR/perf.data --branch-filter any,k -- \
		perf bench syscall basic --loop 1000 > "$TMPDIR/record.txt" 2>&1
	perf script -i $TMPDIR/perf.data --fields brstack | \
		tr ' ' '\n' > $TMPDIR/perf.script

	# Example of branch entries:
	#       "0xffffffff93bda241/0xffffffff93bda20f/M/-/-/..."
	# Source addresses come first in user or kernel code. Next is the target
	# address that must be in the kernel.

	# Look for source addresses with top bit set
	if ! grep -q -E -m1 "^0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then
		echo "Testing kernel branch sampling [Failed kernel branches missing]"
		err=1
	fi
	# Look for no target addresses without top bit set
	if grep -q -E -m1 "^0x[0-9a-f]{0,16}/0x[0-7][0-9a-f]{1,15}/" $TMPDIR/perf.script; then
		echo "Testing kernel branch sampling [Failed user branches found]"
		err=1
	fi
	if [ $err -eq 0 ]; then
		echo "Testing kernel branch sampling [Passed]"
		err=$start_err
	else
		echo "Testing kernel branch sampling [Failed]"
	fi
}

# first argument <arg0> is the argument passed to "--branch-stack <arg0>,save_type,u"
# second argument are the expected branch types for the given filter
test_filter() {
	test_filter_filter=$1
	test_filter_expect=$2

	echo "Testing branch stack filtering permutation ($test_filter_filter,$test_filter_expect)"
	perf record -o "$TMPDIR/perf.data" --branch-filter "$test_filter_filter,save_type,u" -- \
		${TESTPROG}  > "$TMPDIR/record.txt" 2>&1
	perf script -i "$TMPDIR/perf.data" --fields brstack > "$TMPDIR/perf.script"

	# fail if we find any branch type that doesn't match any of the expected ones
	# also consider UNKNOWN branch types (-)
	if [ ! -s "$TMPDIR/perf.script" ]
	then
		echo "Testing branch stack filtering [Failed empty script output]"
		err=1
		return
	fi
	# Look for lines not matching test_filter_expect ignoring issues caused
	# by empty output
	tr -s ' ' '\n' < "$TMPDIR/perf.script" | grep '.' | \
	  grep -E -vm1 "^[^ ]*/($test_filter_expect|-|( *))/.*$" \
	  > "$TMPDIR/perf.script-filtered" || true
	if [ -s "$TMPDIR/perf.script-filtered" ]
	then
		echo "Testing branch stack filtering [Failed unexpected branch filter]"
		cat "$TMPDIR/perf.script"
		err=1
		return
	fi
	echo "Testing branch stack filtering [Passed]"
}

test_syscall() {
	echo "Testing syscalls"
	# skip if perf doesn't have enough privileges
	if ! perf record --branch-filter any,k -o- -- true > "$TMPDIR/record.txt" 2>&1; then
		echo "Testing syscalls [Skipped: not enough privileges]"
		return
	fi
	start_err=$err
	err=0
	perf record -o $TMPDIR/perf.data --branch-filter \
		any_call,save_type,u,k -c 10007 -- \
		perf bench syscall basic --loop 8000  > "$TMPDIR/record.txt" 2>&1
	perf script -i $TMPDIR/perf.data --fields brstacksym | \
		tr ' ' '\n' > $TMPDIR/perf.script

	check_branches "getppid[^ ]*/SYSCALL/"

	if [ $err -eq 0 ]; then
		echo "Testing syscalls [Passed]"
		err=$start_err
	else
		echo "Testing syscalls [Failed]"
	fi
}
set -e

test_user_branches
test_syscall
test_kernel_branches
test_trap_eret_branches

any_call="CALL|IND_CALL|COND_CALL|SYSCALL|IRQ"

if is_arm64; then
	any_call="$any_call|FAULT_DATA|FAULT_INST"
fi

test_filter "any_call" "$any_call"
test_filter "call"	"CALL|SYSCALL"
test_filter "cond"	"COND"
test_filter "any_ret"	"RET|COND_RET|SYSRET|ERET"

test_filter "call,cond"		"CALL|SYSCALL|COND"
test_filter "any_call,cond"	    "$any_call|COND"
test_filter "any_call,cond,any_ret" "$any_call|COND|RET|COND_RET"

cleanup
exit $err