[PATCH v3] perf test: Fixes for check branch stack sampling

Ian Rogers posted 1 patch 2 months, 1 week ago
There is a newer version of this series
tools/perf/tests/shell/test_brstack.sh | 134 ++++++++++++++++---------
1 file changed, 84 insertions(+), 50 deletions(-)
[PATCH v3] perf test: Fixes for check branch stack sampling
Posted by Ian Rogers 2 months, 1 week ago
When filtering branch stack samples on user events they sample in user
land but may have come from the kernel. Avoid the target address being
a kernel address but allow the source to be the kernel.

When filtering branch stack samples on kernel events they sample in
kernel land but may have come from user land. Avoid the target being a
user address but allow the source to be in user land.

Increase the duration of the system call sampling test to make the
likelihood of sampling a system call higher (increased from 1000 to
8000 loops - a number found through experimentation on an Intel
Tigerlake laptop), also make the period of the event a prime number.

Put unneeded perf record output into a temporary file so that the test
output isn't cluttered. More clearly state which test is running and
the pass, fail or skipped result of the test.

These changes make the test on an Intel tigerlake laptop reliably pass
rather than reliably fail.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/tests/shell/test_brstack.sh | 134 ++++++++++++++++---------
 1 file changed, 84 insertions(+), 50 deletions(-)

diff --git a/tools/perf/tests/shell/test_brstack.sh b/tools/perf/tests/shell/test_brstack.sh
index 85233d435be6..025ed9d3d110 100755
--- a/tools/perf/tests/shell/test_brstack.sh
+++ b/tools/perf/tests/shell/test_brstack.sh
@@ -40,7 +40,7 @@ is_arm64() {
 
 check_branches() {
 	if ! tr -s ' ' '\n' < "$TMPDIR/perf.script" | grep -E -m1 -q "$1"; then
-		echo "Branches missing $1"
+		echo "ERROR: Branches missing $1"
 		err=1
 	fi
 }
@@ -48,6 +48,8 @@ check_branches() {
 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"
 
@@ -73,59 +75,80 @@ test_user_branches() {
 	perf script -i "$TMPDIR/perf.data" --fields brstack | \
 		tr ' ' '\n' > "$TMPDIR/perf.script"
 
-	# There should be no kernel addresses with the u option, in either
-	# source or target addresses.
-	if grep -E -m1 "0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then
-		echo "ERROR: Kernel address found in user mode"
+	# There should be no kernel addresses in the target with the u option.
+	if grep -q -E -m1 "^0x[0-9a-f]{0,16}/0x[89a-f][0-9a-f]{15}/" $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 "skip: not arm64"
+		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
-		perf record -o $TMPDIR/perf.data --branch-filter any,save_type,u,k -- \
-			perf test -w traploop 1000
-		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/"
+		echo "Testing trap & eret branches [Failed]"
 	fi
 }
 
 test_kernel_branches() {
-	echo "Testing that k option only includes kernel source addresses"
+	echo "Testing kernel branch sampling"
 
-	if ! perf record --branch-filter any,k -o- -- true > /dev/null; then
-		echo "skip: not enough privileges"
+	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]{0,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
-		perf record -o $TMPDIR/perf.data --branch-filter any,k -- \
-			perf bench syscall basic --loop 1000
-		perf script -i $TMPDIR/perf.data --fields brstack | \
-			tr ' ' '\n' > $TMPDIR/perf.script
-
-		# Example of branch entries:
-		#       "0xffffffff93bda241/0xffffffff93bda20f/M/-/-/..."
-		# Source addresses come first and target address can be either
-		# userspace or kernel even with k option, as long as the source
-		# is in kernel.
-
-		#Look for source addresses with top bit set
-		if ! grep -E -m1 "^0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then
-			echo "ERROR: Kernel branches missing"
-			err=1
-		fi
-		# Look for no source addresses without top bit set
-		if grep -E -m1 "^0x[0-7][0-9a-f]{0,15}" $TMPDIR/perf.script; then
-			echo "ERROR: User branches found with kernel filter"
-			err=1
-		fi
+		echo "Testing kernel branch sampling [Failed]"
 	fi
 }
 
@@ -136,14 +159,15 @@ test_filter() {
 	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 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 "Empty script output"
+		echo "Testing branch stack filtering [Failed empty script output]"
 		err=1
 		return
 	fi
@@ -154,26 +178,36 @@ test_filter() {
 	  > "$TMPDIR/perf.script-filtered" || true
 	if [ -s "$TMPDIR/perf.script-filtered" ]
 	then
-		echo "Unexpected branch filter in script output"
+		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 > /dev/null; then
-		echo "skip: not 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
-		perf record -o $TMPDIR/perf.data --branch-filter \
-			any_call,save_type,u,k -c 10000 -- \
-			perf bench syscall basic --loop 1000
-		perf script -i $TMPDIR/perf.data --fields brstacksym | \
-			tr ' ' '\n' > $TMPDIR/perf.script
-
-		check_branches "getppid[^ ]*/SYSCALL/"
+		echo "Testing syscalls [Failed]"
 	fi
 }
 set -e
-- 
2.53.0.1213.gd9a14994de-goog
Re: [PATCH v3] perf test: Fixes for check branch stack sampling
Posted by James Clark 2 months, 1 week ago

On 08/04/2026 7:58 am, Ian Rogers wrote:
> When filtering branch stack samples on user events they sample in user
> land but may have come from the kernel. Avoid the target address being
> a kernel address but allow the source to be the kernel.

Doesn't that leak kernel pointers then? I thought they were supposed to 
be semi secret to not break KASLR? With /u on Arm64 all kernel branch 
addresses are filtered out or zeroed. I think it might be worth adding 
an architecture condition with a different test for Intel if the 
behavior is different. Otherwise we could start leaking pointers on Arm 
and the test would not detect it.

> 
> When filtering branch stack samples on kernel events they sample in
> kernel land but may have come from user land. Avoid the target being a
> user address but allow the source to be in user land.
> 
> Increase the duration of the system call sampling test to make the
> likelihood of sampling a system call higher (increased from 1000 to
> 8000 loops - a number found through experimentation on an Intel
> Tigerlake laptop), also make the period of the event a prime number.
> 
> Put unneeded perf record output into a temporary file so that the test
> output isn't cluttered. More clearly state which test is running and
> the pass, fail or skipped result of the test.
> 
> These changes make the test on an Intel tigerlake laptop reliably pass
> rather than reliably fail.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>
> ---
>   tools/perf/tests/shell/test_brstack.sh | 134 ++++++++++++++++---------
>   1 file changed, 84 insertions(+), 50 deletions(-)
> 
> diff --git a/tools/perf/tests/shell/test_brstack.sh b/tools/perf/tests/shell/test_brstack.sh
> index 85233d435be6..025ed9d3d110 100755
> --- a/tools/perf/tests/shell/test_brstack.sh
> +++ b/tools/perf/tests/shell/test_brstack.sh
> @@ -40,7 +40,7 @@ is_arm64() {
>   
>   check_branches() {
>   	if ! tr -s ' ' '\n' < "$TMPDIR/perf.script" | grep -E -m1 -q "$1"; then
> -		echo "Branches missing $1"
> +		echo "ERROR: Branches missing $1"
>   		err=1
>   	fi
>   }
> @@ -48,6 +48,8 @@ check_branches() {
>   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"
>   
> @@ -73,59 +75,80 @@ test_user_branches() {
>   	perf script -i "$TMPDIR/perf.data" --fields brstack | \
>   		tr ' ' '\n' > "$TMPDIR/perf.script"
>   
> -	# There should be no kernel addresses with the u option, in either
> -	# source or target addresses.
> -	if grep -E -m1 "0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then
> -		echo "ERROR: Kernel address found in user mode"
> +	# There should be no kernel addresses in the target with the u option.
> +	if grep -q -E -m1 "^0x[0-9a-f]{0,16}/0x[89a-f][0-9a-f]{15}/" $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 "skip: not arm64"
> +		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
> -		perf record -o $TMPDIR/perf.data --branch-filter any,save_type,u,k -- \
> -			perf test -w traploop 1000
> -		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/"
> +		echo "Testing trap & eret branches [Failed]"
>   	fi
>   }
>   
>   test_kernel_branches() {
> -	echo "Testing that k option only includes kernel source addresses"
> +	echo "Testing kernel branch sampling"
>   
> -	if ! perf record --branch-filter any,k -o- -- true > /dev/null; then
> -		echo "skip: not enough privileges"
> +	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]{0,15}/" $TMPDIR/perf.script; then

Target can be "0x0" for jumps from the kernel back to userspace with 
only kernel branches turned on. Before we were only looking at source 
addresses but now it looks at targets the test fails on 0x0 targets on Arm.

I suppose we could special case 0x0? It starts to get a bit horrible to 
do in bash with regexes though.

The rest of the changes look good.

> +		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
> -		perf record -o $TMPDIR/perf.data --branch-filter any,k -- \
> -			perf bench syscall basic --loop 1000
> -		perf script -i $TMPDIR/perf.data --fields brstack | \
> -			tr ' ' '\n' > $TMPDIR/perf.script
> -
> -		# Example of branch entries:
> -		#       "0xffffffff93bda241/0xffffffff93bda20f/M/-/-/..."
> -		# Source addresses come first and target address can be either
> -		# userspace or kernel even with k option, as long as the source
> -		# is in kernel.
> -
> -		#Look for source addresses with top bit set
> -		if ! grep -E -m1 "^0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then
> -			echo "ERROR: Kernel branches missing"
> -			err=1
> -		fi
> -		# Look for no source addresses without top bit set
> -		if grep -E -m1 "^0x[0-7][0-9a-f]{0,15}" $TMPDIR/perf.script; then
> -			echo "ERROR: User branches found with kernel filter"
> -			err=1
> -		fi
> +		echo "Testing kernel branch sampling [Failed]"
>   	fi
>   }
>   
> @@ -136,14 +159,15 @@ test_filter() {
>   	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 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 "Empty script output"
> +		echo "Testing branch stack filtering [Failed empty script output]"
>   		err=1
>   		return
>   	fi
> @@ -154,26 +178,36 @@ test_filter() {
>   	  > "$TMPDIR/perf.script-filtered" || true
>   	if [ -s "$TMPDIR/perf.script-filtered" ]
>   	then
> -		echo "Unexpected branch filter in script output"
> +		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 > /dev/null; then
> -		echo "skip: not 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
> -		perf record -o $TMPDIR/perf.data --branch-filter \
> -			any_call,save_type,u,k -c 10000 -- \
> -			perf bench syscall basic --loop 1000
> -		perf script -i $TMPDIR/perf.data --fields brstacksym | \
> -			tr ' ' '\n' > $TMPDIR/perf.script
> -
> -		check_branches "getppid[^ ]*/SYSCALL/"
> +		echo "Testing syscalls [Failed]"
>   	fi
>   }
>   set -e
Re: [PATCH v3] perf test: Fixes for check branch stack sampling
Posted by Ian Rogers 2 months, 1 week ago
On Wed, Apr 8, 2026 at 5:37 AM James Clark <james.clark@linaro.org> wrote:
>
>
>
> On 08/04/2026 7:58 am, Ian Rogers wrote:
> > When filtering branch stack samples on user events they sample in user
> > land but may have come from the kernel. Avoid the target address being
> > a kernel address but allow the source to be the kernel.
>
> Doesn't that leak kernel pointers then? I thought they were supposed to
> be semi secret to not break KASLR? With /u on Arm64 all kernel branch
> addresses are filtered out or zeroed. I think it might be worth adding
> an architecture condition with a different test for Intel if the
> behavior is different. Otherwise we could start leaking pointers on Arm
> and the test would not detect it.

On x86, the filter applies to the LBR target, suggesting a KASLR leak
for LBR sources in the kernel. Wrt the test we could say the test
fails on x86 due to this, but this is kind of annoying at the moment.
I suppose having some kind of flag to say broken sources are allowed
and then setting it on x86 would be best.

> >
> > When filtering branch stack samples on kernel events they sample in
> > kernel land but may have come from user land. Avoid the target being a
> > user address but allow the source to be in user land.
> >
> > Increase the duration of the system call sampling test to make the
> > likelihood of sampling a system call higher (increased from 1000 to
> > 8000 loops - a number found through experimentation on an Intel
> > Tigerlake laptop), also make the period of the event a prime number.
> >
> > Put unneeded perf record output into a temporary file so that the test
> > output isn't cluttered. More clearly state which test is running and
> > the pass, fail or skipped result of the test.
> >
> > These changes make the test on an Intel tigerlake laptop reliably pass
> > rather than reliably fail.
> >
> > Signed-off-by: Ian Rogers <irogers@google.com>
> > ---
> >   tools/perf/tests/shell/test_brstack.sh | 134 ++++++++++++++++---------
> >   1 file changed, 84 insertions(+), 50 deletions(-)
> >
> > diff --git a/tools/perf/tests/shell/test_brstack.sh b/tools/perf/tests/shell/test_brstack.sh
> > index 85233d435be6..025ed9d3d110 100755
> > --- a/tools/perf/tests/shell/test_brstack.sh
> > +++ b/tools/perf/tests/shell/test_brstack.sh
> > @@ -40,7 +40,7 @@ is_arm64() {
> >
> >   check_branches() {
> >       if ! tr -s ' ' '\n' < "$TMPDIR/perf.script" | grep -E -m1 -q "$1"; then
> > -             echo "Branches missing $1"
> > +             echo "ERROR: Branches missing $1"
> >               err=1
> >       fi
> >   }
> > @@ -48,6 +48,8 @@ check_branches() {
> >   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"
> >
> > @@ -73,59 +75,80 @@ test_user_branches() {
> >       perf script -i "$TMPDIR/perf.data" --fields brstack | \
> >               tr ' ' '\n' > "$TMPDIR/perf.script"
> >
> > -     # There should be no kernel addresses with the u option, in either
> > -     # source or target addresses.
> > -     if grep -E -m1 "0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then
> > -             echo "ERROR: Kernel address found in user mode"
> > +     # There should be no kernel addresses in the target with the u option.
> > +     if grep -q -E -m1 "^0x[0-9a-f]{0,16}/0x[89a-f][0-9a-f]{15}/" $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 "skip: not arm64"
> > +             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
> > -             perf record -o $TMPDIR/perf.data --branch-filter any,save_type,u,k -- \
> > -                     perf test -w traploop 1000
> > -             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/"
> > +             echo "Testing trap & eret branches [Failed]"
> >       fi
> >   }
> >
> >   test_kernel_branches() {
> > -     echo "Testing that k option only includes kernel source addresses"
> > +     echo "Testing kernel branch sampling"
> >
> > -     if ! perf record --branch-filter any,k -o- -- true > /dev/null; then
> > -             echo "skip: not enough privileges"
> > +     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]{0,15}/" $TMPDIR/perf.script; then
>
> Target can be "0x0" for jumps from the kernel back to userspace with
> only kernel branches turned on. Before we were only looking at source
> addresses but now it looks at targets the test fails on 0x0 targets on Arm.
>
> I suppose we could special case 0x0? It starts to get a bit horrible to
> do in bash with regexes though.

I wonder, then, if the target should require two numbers, like this:
```
if grep -q -E -m1 "^0x[0-9a-f]{0,16}/0x[0-7][0-9a-f]{1,15}/"
$TMPDIR/perf.script; then
```

> The rest of the changes look good.

Thanks,
Ian

> > +             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
> > -             perf record -o $TMPDIR/perf.data --branch-filter any,k -- \
> > -                     perf bench syscall basic --loop 1000
> > -             perf script -i $TMPDIR/perf.data --fields brstack | \
> > -                     tr ' ' '\n' > $TMPDIR/perf.script
> > -
> > -             # Example of branch entries:
> > -             #       "0xffffffff93bda241/0xffffffff93bda20f/M/-/-/..."
> > -             # Source addresses come first and target address can be either
> > -             # userspace or kernel even with k option, as long as the source
> > -             # is in kernel.
> > -
> > -             #Look for source addresses with top bit set
> > -             if ! grep -E -m1 "^0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then
> > -                     echo "ERROR: Kernel branches missing"
> > -                     err=1
> > -             fi
> > -             # Look for no source addresses without top bit set
> > -             if grep -E -m1 "^0x[0-7][0-9a-f]{0,15}" $TMPDIR/perf.script; then
> > -                     echo "ERROR: User branches found with kernel filter"
> > -                     err=1
> > -             fi
> > +             echo "Testing kernel branch sampling [Failed]"
> >       fi
> >   }
> >
> > @@ -136,14 +159,15 @@ test_filter() {
> >       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 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 "Empty script output"
> > +             echo "Testing branch stack filtering [Failed empty script output]"
> >               err=1
> >               return
> >       fi
> > @@ -154,26 +178,36 @@ test_filter() {
> >         > "$TMPDIR/perf.script-filtered" || true
> >       if [ -s "$TMPDIR/perf.script-filtered" ]
> >       then
> > -             echo "Unexpected branch filter in script output"
> > +             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 > /dev/null; then
> > -             echo "skip: not 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
> > -             perf record -o $TMPDIR/perf.data --branch-filter \
> > -                     any_call,save_type,u,k -c 10000 -- \
> > -                     perf bench syscall basic --loop 1000
> > -             perf script -i $TMPDIR/perf.data --fields brstacksym | \
> > -                     tr ' ' '\n' > $TMPDIR/perf.script
> > -
> > -             check_branches "getppid[^ ]*/SYSCALL/"
> > +             echo "Testing syscalls [Failed]"
> >       fi
> >   }
> >   set -e
>
[PATCH v4] perf test: Fixes for check branch stack sampling
Posted by Ian Rogers 2 months, 1 week ago
When filtering branch stack samples on user events they sample in user
land but may have come from the kernel. Aarch64 avoids leaking the
kernel address for kaslr reasons but other platforms, for now,
don't. Be more permissive in allowing kernel addresses in the source
of user branch stacks.

When filtering branch stack samples on kernel events they sample in
kernel land but may have come from user land. Avoid the target being a
user address but allow the source to be in user land. Aarch64 may not
leak the user land addresses (making them 0) but other platforms
do. As the kernel address sampling implies privelege, just allow this.

Increase the duration of the system call sampling test to make the
likelihood of sampling a system call higher (increased from 1000 to
8000 loops - a number found through experimentation on an Intel
Tigerlake laptop), also make the period of the event a prime number.

Put unneeded perf record output into a temporary file so that the test
output isn't cluttered. More clearly state which test is running and
the pass, fail or skipped result of the test.

These changes make the test on an Intel tigerlake laptop reliably pass
rather than reliably fail.

Signed-off-by: Ian Rogers <irogers@google.com>
---
v4: Address feedback from James Clark.
v2,v3: Address Sashiko feedback.
---
 tools/perf/tests/shell/test_brstack.sh | 146 ++++++++++++++++---------
 1 file changed, 96 insertions(+), 50 deletions(-)

diff --git a/tools/perf/tests/shell/test_brstack.sh b/tools/perf/tests/shell/test_brstack.sh
index 85233d435be6..eb5837f82e39 100755
--- a/tools/perf/tests/shell/test_brstack.sh
+++ b/tools/perf/tests/shell/test_brstack.sh
@@ -38,9 +38,13 @@ 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 "Branches missing $1"
+		echo "ERROR: Branches missing $1"
 		err=1
 	fi
 }
@@ -48,6 +52,8 @@ check_branches() {
 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"
 
@@ -73,59 +79,88 @@ test_user_branches() {
 	perf script -i "$TMPDIR/perf.data" --fields brstack | \
 		tr ' ' '\n' > "$TMPDIR/perf.script"
 
-	# There should be no kernel addresses with the u option, in either
-	# source or target addresses.
-	if grep -E -m1 "0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then
-		echo "ERROR: Kernel address found in user mode"
+	# 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 "skip: not arm64"
+		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
-		perf record -o $TMPDIR/perf.data --branch-filter any,save_type,u,k -- \
-			perf test -w traploop 1000
-		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/"
+		echo "Testing trap & eret branches [Failed]"
 	fi
 }
 
 test_kernel_branches() {
-	echo "Testing that k option only includes kernel source addresses"
+	echo "Testing kernel branch sampling"
 
-	if ! perf record --branch-filter any,k -o- -- true > /dev/null; then
-		echo "skip: not enough privileges"
+	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
-		perf record -o $TMPDIR/perf.data --branch-filter any,k -- \
-			perf bench syscall basic --loop 1000
-		perf script -i $TMPDIR/perf.data --fields brstack | \
-			tr ' ' '\n' > $TMPDIR/perf.script
-
-		# Example of branch entries:
-		#       "0xffffffff93bda241/0xffffffff93bda20f/M/-/-/..."
-		# Source addresses come first and target address can be either
-		# userspace or kernel even with k option, as long as the source
-		# is in kernel.
-
-		#Look for source addresses with top bit set
-		if ! grep -E -m1 "^0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then
-			echo "ERROR: Kernel branches missing"
-			err=1
-		fi
-		# Look for no source addresses without top bit set
-		if grep -E -m1 "^0x[0-7][0-9a-f]{0,15}" $TMPDIR/perf.script; then
-			echo "ERROR: User branches found with kernel filter"
-			err=1
-		fi
+		echo "Testing kernel branch sampling [Failed]"
 	fi
 }
 
@@ -136,14 +171,15 @@ test_filter() {
 	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 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 "Empty script output"
+		echo "Testing branch stack filtering [Failed empty script output]"
 		err=1
 		return
 	fi
@@ -154,26 +190,36 @@ test_filter() {
 	  > "$TMPDIR/perf.script-filtered" || true
 	if [ -s "$TMPDIR/perf.script-filtered" ]
 	then
-		echo "Unexpected branch filter in script output"
+		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 > /dev/null; then
-		echo "skip: not 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
-		perf record -o $TMPDIR/perf.data --branch-filter \
-			any_call,save_type,u,k -c 10000 -- \
-			perf bench syscall basic --loop 1000
-		perf script -i $TMPDIR/perf.data --fields brstacksym | \
-			tr ' ' '\n' > $TMPDIR/perf.script
-
-		check_branches "getppid[^ ]*/SYSCALL/"
+		echo "Testing syscalls [Failed]"
 	fi
 }
 set -e
-- 
2.53.0.1213.gd9a14994de-goog
Re: [PATCH v4] perf test: Fixes for check branch stack sampling
Posted by Namhyung Kim 2 months ago
On Wed, 08 Apr 2026 17:02:16 -0700, Ian Rogers wrote:
> When filtering branch stack samples on user events they sample in user
> land but may have come from the kernel. Aarch64 avoids leaking the
> kernel address for kaslr reasons but other platforms, for now,
> don't. Be more permissive in allowing kernel addresses in the source
> of user branch stacks.
> 
> When filtering branch stack samples on kernel events they sample in
> kernel land but may have come from user land. Avoid the target being a
> user address but allow the source to be in user land. Aarch64 may not
> leak the user land addresses (making them 0) but other platforms
> do. As the kernel address sampling implies privelege, just allow this.
> 
> [...]
Applied to perf-tools-next, thanks!

Best regards,
Namhyung
Re: [PATCH v4] perf test: Fixes for check branch stack sampling
Posted by James Clark 2 months ago

On 09/04/2026 1:02 am, Ian Rogers wrote:
> When filtering branch stack samples on user events they sample in user
> land but may have come from the kernel. Aarch64 avoids leaking the
> kernel address for kaslr reasons but other platforms, for now,
> don't. Be more permissive in allowing kernel addresses in the source
> of user branch stacks.
> 
> When filtering branch stack samples on kernel events they sample in
> kernel land but may have come from user land. Avoid the target being a
> user address but allow the source to be in user land. Aarch64 may not
> leak the user land addresses (making them 0) but other platforms
> do. As the kernel address sampling implies privelege, just allow this.
> 
> Increase the duration of the system call sampling test to make the
> likelihood of sampling a system call higher (increased from 1000 to
> 8000 loops - a number found through experimentation on an Intel
> Tigerlake laptop), also make the period of the event a prime number.
> 
> Put unneeded perf record output into a temporary file so that the test
> output isn't cluttered. More clearly state which test is running and
> the pass, fail or skipped result of the test.
> 
> These changes make the test on an Intel tigerlake laptop reliably pass
> rather than reliably fail.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Reviewed-by: James Clark <james.clark@linaro.org>

> ---
> v4: Address feedback from James Clark.
> v2,v3: Address Sashiko feedback.
> ---
>   tools/perf/tests/shell/test_brstack.sh | 146 ++++++++++++++++---------
>   1 file changed, 96 insertions(+), 50 deletions(-)
> 
> diff --git a/tools/perf/tests/shell/test_brstack.sh b/tools/perf/tests/shell/test_brstack.sh
> index 85233d435be6..eb5837f82e39 100755
> --- a/tools/perf/tests/shell/test_brstack.sh
> +++ b/tools/perf/tests/shell/test_brstack.sh
> @@ -38,9 +38,13 @@ 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 "Branches missing $1"
> +		echo "ERROR: Branches missing $1"
>   		err=1
>   	fi
>   }
> @@ -48,6 +52,8 @@ check_branches() {
>   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"
>   
> @@ -73,59 +79,88 @@ test_user_branches() {
>   	perf script -i "$TMPDIR/perf.data" --fields brstack | \
>   		tr ' ' '\n' > "$TMPDIR/perf.script"
>   
> -	# There should be no kernel addresses with the u option, in either
> -	# source or target addresses.
> -	if grep -E -m1 "0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then
> -		echo "ERROR: Kernel address found in user mode"
> +	# 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 "skip: not arm64"
> +		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
> -		perf record -o $TMPDIR/perf.data --branch-filter any,save_type,u,k -- \
> -			perf test -w traploop 1000
> -		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/"
> +		echo "Testing trap & eret branches [Failed]"
>   	fi
>   }
>   
>   test_kernel_branches() {
> -	echo "Testing that k option only includes kernel source addresses"
> +	echo "Testing kernel branch sampling"
>   
> -	if ! perf record --branch-filter any,k -o- -- true > /dev/null; then
> -		echo "skip: not enough privileges"
> +	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
> -		perf record -o $TMPDIR/perf.data --branch-filter any,k -- \
> -			perf bench syscall basic --loop 1000
> -		perf script -i $TMPDIR/perf.data --fields brstack | \
> -			tr ' ' '\n' > $TMPDIR/perf.script
> -
> -		# Example of branch entries:
> -		#       "0xffffffff93bda241/0xffffffff93bda20f/M/-/-/..."
> -		# Source addresses come first and target address can be either
> -		# userspace or kernel even with k option, as long as the source
> -		# is in kernel.
> -
> -		#Look for source addresses with top bit set
> -		if ! grep -E -m1 "^0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then
> -			echo "ERROR: Kernel branches missing"
> -			err=1
> -		fi
> -		# Look for no source addresses without top bit set
> -		if grep -E -m1 "^0x[0-7][0-9a-f]{0,15}" $TMPDIR/perf.script; then
> -			echo "ERROR: User branches found with kernel filter"
> -			err=1
> -		fi
> +		echo "Testing kernel branch sampling [Failed]"
>   	fi
>   }
>   
> @@ -136,14 +171,15 @@ test_filter() {
>   	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 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 "Empty script output"
> +		echo "Testing branch stack filtering [Failed empty script output]"
>   		err=1
>   		return
>   	fi
> @@ -154,26 +190,36 @@ test_filter() {
>   	  > "$TMPDIR/perf.script-filtered" || true
>   	if [ -s "$TMPDIR/perf.script-filtered" ]
>   	then
> -		echo "Unexpected branch filter in script output"
> +		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 > /dev/null; then
> -		echo "skip: not 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
> -		perf record -o $TMPDIR/perf.data --branch-filter \
> -			any_call,save_type,u,k -c 10000 -- \
> -			perf bench syscall basic --loop 1000
> -		perf script -i $TMPDIR/perf.data --fields brstacksym | \
> -			tr ' ' '\n' > $TMPDIR/perf.script
> -
> -		check_branches "getppid[^ ]*/SYSCALL/"
> +		echo "Testing syscalls [Failed]"
>   	fi
>   }
>   set -e
Re: [PATCH v4] perf test: Fixes for check branch stack sampling
Posted by Ian Rogers 2 months ago
On Thu, Apr 9, 2026 at 1:23 AM James Clark <james.clark@linaro.org> wrote:
>
>
>
> On 09/04/2026 1:02 am, Ian Rogers wrote:
> > When filtering branch stack samples on user events they sample in user
> > land but may have come from the kernel. Aarch64 avoids leaking the
> > kernel address for kaslr reasons but other platforms, for now,
> > don't. Be more permissive in allowing kernel addresses in the source
> > of user branch stacks.
> >
> > When filtering branch stack samples on kernel events they sample in
> > kernel land but may have come from user land. Avoid the target being a
> > user address but allow the source to be in user land. Aarch64 may not
> > leak the user land addresses (making them 0) but other platforms
> > do. As the kernel address sampling implies privelege, just allow this.
> >
> > Increase the duration of the system call sampling test to make the
> > likelihood of sampling a system call higher (increased from 1000 to
> > 8000 loops - a number found through experimentation on an Intel
> > Tigerlake laptop), also make the period of the event a prime number.
> >
> > Put unneeded perf record output into a temporary file so that the test
> > output isn't cluttered. More clearly state which test is running and
> > the pass, fail or skipped result of the test.
> >
> > These changes make the test on an Intel tigerlake laptop reliably pass
> > rather than reliably fail.
> >
> > Signed-off-by: Ian Rogers <irogers@google.com>
>
> Reviewed-by: James Clark <james.clark@linaro.org>

Thanks James, hopefully this will be included in the merge window.

Ian

> > ---
> > v4: Address feedback from James Clark.
> > v2,v3: Address Sashiko feedback.
> > ---
> >   tools/perf/tests/shell/test_brstack.sh | 146 ++++++++++++++++---------
> >   1 file changed, 96 insertions(+), 50 deletions(-)
> >
> > diff --git a/tools/perf/tests/shell/test_brstack.sh b/tools/perf/tests/shell/test_brstack.sh
> > index 85233d435be6..eb5837f82e39 100755
> > --- a/tools/perf/tests/shell/test_brstack.sh
> > +++ b/tools/perf/tests/shell/test_brstack.sh
> > @@ -38,9 +38,13 @@ 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 "Branches missing $1"
> > +             echo "ERROR: Branches missing $1"
> >               err=1
> >       fi
> >   }
> > @@ -48,6 +52,8 @@ check_branches() {
> >   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"
> >
> > @@ -73,59 +79,88 @@ test_user_branches() {
> >       perf script -i "$TMPDIR/perf.data" --fields brstack | \
> >               tr ' ' '\n' > "$TMPDIR/perf.script"
> >
> > -     # There should be no kernel addresses with the u option, in either
> > -     # source or target addresses.
> > -     if grep -E -m1 "0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then
> > -             echo "ERROR: Kernel address found in user mode"
> > +     # 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 "skip: not arm64"
> > +             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
> > -             perf record -o $TMPDIR/perf.data --branch-filter any,save_type,u,k -- \
> > -                     perf test -w traploop 1000
> > -             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/"
> > +             echo "Testing trap & eret branches [Failed]"
> >       fi
> >   }
> >
> >   test_kernel_branches() {
> > -     echo "Testing that k option only includes kernel source addresses"
> > +     echo "Testing kernel branch sampling"
> >
> > -     if ! perf record --branch-filter any,k -o- -- true > /dev/null; then
> > -             echo "skip: not enough privileges"
> > +     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
> > -             perf record -o $TMPDIR/perf.data --branch-filter any,k -- \
> > -                     perf bench syscall basic --loop 1000
> > -             perf script -i $TMPDIR/perf.data --fields brstack | \
> > -                     tr ' ' '\n' > $TMPDIR/perf.script
> > -
> > -             # Example of branch entries:
> > -             #       "0xffffffff93bda241/0xffffffff93bda20f/M/-/-/..."
> > -             # Source addresses come first and target address can be either
> > -             # userspace or kernel even with k option, as long as the source
> > -             # is in kernel.
> > -
> > -             #Look for source addresses with top bit set
> > -             if ! grep -E -m1 "^0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then
> > -                     echo "ERROR: Kernel branches missing"
> > -                     err=1
> > -             fi
> > -             # Look for no source addresses without top bit set
> > -             if grep -E -m1 "^0x[0-7][0-9a-f]{0,15}" $TMPDIR/perf.script; then
> > -                     echo "ERROR: User branches found with kernel filter"
> > -                     err=1
> > -             fi
> > +             echo "Testing kernel branch sampling [Failed]"
> >       fi
> >   }
> >
> > @@ -136,14 +171,15 @@ test_filter() {
> >       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 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 "Empty script output"
> > +             echo "Testing branch stack filtering [Failed empty script output]"
> >               err=1
> >               return
> >       fi
> > @@ -154,26 +190,36 @@ test_filter() {
> >         > "$TMPDIR/perf.script-filtered" || true
> >       if [ -s "$TMPDIR/perf.script-filtered" ]
> >       then
> > -             echo "Unexpected branch filter in script output"
> > +             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 > /dev/null; then
> > -             echo "skip: not 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
> > -             perf record -o $TMPDIR/perf.data --branch-filter \
> > -                     any_call,save_type,u,k -c 10000 -- \
> > -                     perf bench syscall basic --loop 1000
> > -             perf script -i $TMPDIR/perf.data --fields brstacksym | \
> > -                     tr ' ' '\n' > $TMPDIR/perf.script
> > -
> > -             check_branches "getppid[^ ]*/SYSCALL/"
> > +             echo "Testing syscalls [Failed]"
> >       fi
> >   }
> >   set -e
>
>