From: Ian Rogers <irogers@google.com>
To: acme@kernel.org, gmx@google.com, namhyung@kernel.org
Cc: adrian.hunter@intel.com, james.clark@linaro.org,
jolsa@kernel.org, linux-kernel@vger.kernel.org,
linux-perf-users@vger.kernel.org, mingo@redhat.com,
peterz@infradead.org, Ian Rogers <irogers@google.com>
Subject: [PATCH v3 4/4] perf test: Add inject ASLR test
Date: Sun, 3 May 2026 20:51:25 -0700 [thread overview]
Message-ID: <20260504035125.1851720-5-irogers@google.com> (raw)
In-Reply-To: <20260504035125.1851720-1-irogers@google.com>
Add a new shell test `inject_aslr.sh` to verify the `perf inject --aslr`
feature. The test covers:
- Basic address remapping for user space samples.
- Pipe mode coverage for `perf record` piped into `perf inject --aslr`.
- Callchain address remapping.
- Consistency of `perf report` output before and after injection.
- Pipe mode report consistency.
- Dropping of samples that leak ASLR info (physical addresses).
- Kernel address remapping (skipping gracefully if permissions restrict
recording the kernel map).
- Kernel report consistency with address normalization.
The test suite is hardened with:
- Global 'set -o pipefail' pipeline checks to catch failures in perf script.
- Safe awk processing loop closures that consume whole streams to avoid SIGPIPE
signal aborts.
- False success assertions to verify callchain data isn't dynamically dropped.
- Graceful error paths on empty sample records.
- Multi-arch support (32-bit and 64-bit address normalizations).
Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
---
v3: Harden script with pipefail, SIGPIPE awk pipeline fixes, callchain empty
data asserts, baseline sample verification, and grep report abort
protections. Ensure grep report filters have || true suffixes. Reorder
set -e/pipefail to prevent stack leaks in mktemp failures.
v2: Add sum comparison for kernel overhead and 32-bit math corrections. Add
awk with gsub for trailing dots and brackets normalizations. Trap EXIT,
prevent race conditions and avoid hardcoded perf binary.
---
tools/perf/tests/shell/inject_aslr.sh | 423 ++++++++++++++++++++++++++
1 file changed, 423 insertions(+)
create mode 100755 tools/perf/tests/shell/inject_aslr.sh
diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
new file mode 100755
index 000000000000..fa7dd3b4c411
--- /dev/null
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -0,0 +1,423 @@
+#!/bin/bash
+# perf inject --aslr test
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+set -o pipefail
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+sym="noploop"
+
+skip_test_missing_symbol ${sym}
+
+# Create global temp directory
+temp_dir=$(mktemp -d /tmp/perf-test-aslr.XXXXXXXXXX)
+data="${temp_dir}/perf.data"
+data2="${temp_dir}/perf.data2"
+
+prog="perf test -w noploop"
+[ "$(uname -m)" = "s390x" ] && prog="$prog 3"
+err=0
+
+
+
+cleanup() {
+ # Check if temp_dir is set and looks sane before removing
+ if [[ "${temp_dir}" =~ ^/tmp/perf-test-aslr\. ]]; then
+ rm -rf "${temp_dir}"
+ fi
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+
+trap cleanup EXIT
+trap trap_cleanup TERM INT
+
+get_noploop_addr() {
+ local file=$1
+ perf script -i "$file" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<=NF; i++) {
+ if ($i ~ /noploop\+/) {
+ if (!found) {
+ print $(i-1)
+ found=1
+ }
+ }
+ }
+ }'
+}
+
+test_basic_aslr() {
+ echo "Test basic ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.basic.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.basic.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -v --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Basic ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Basic ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Basic ASLR test [Success]"
+ fi
+}
+
+test_pipe_aslr() {
+ echo "Test pipe mode ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe.XXXXXX")
+
+ # Use tee to save the original pipe data for comparison
+ perf record -e task-clock:u -o - ${prog} | tee "${data}" | perf inject --aslr -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Pipe ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Pipe ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Pipe ASLR test [Success]"
+ fi
+}
+
+test_callchain_aslr() {
+ echo "Test Callchain ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.callchain.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.callchain.XXXXXX")
+
+ perf record -g -e task-clock:u -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Callchain ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Callchain ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ # Extract callchain addresses (indented lines starting with hex addresses)
+ orig_callchain=$(perf script -i "${data}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+ new_callchain=$(perf script -i "${data2}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+
+ if [ -z "$orig_callchain" ]; then
+ echo "Callchain ASLR test [Failed - no callchain samples in original file]"
+ err=1
+ elif [ -z "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain data was dropped]"
+ err=1
+ elif [ "$orig_callchain" = "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain addresses were not remapped]"
+ err=1
+ else
+ echo "Callchain ASLR test [Success]"
+ fi
+ fi
+}
+
+test_report_aslr() {
+ echo "Test perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${data}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${data}" -o "${data2}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_report_aslr() {
+ echo "Test pipe mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ # Use tee to save the original pipe data, then process it with inject -b
+ perf record -e task-clock:u -o - ${prog} | \
+ tee "${data}" | \
+ perf inject -b --aslr -o "${data2}"
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1"
+ local report2="${temp_dir}/report2"
+ local report1_clean="${temp_dir}/report1.clean"
+ local report2_clean="${temp_dir}/report2.clean"
+ local diff_file="${temp_dir}/diff"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Report ASLR test [Success]"
+ fi
+}
+
+test_dropped_samples() {
+ echo "Test dropped samples (phys-data)"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.dropped.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.dropped.XXXXXX")
+
+ # Check if --phys-data is supported by recording a short run
+ if ! perf record -e task-clock:u --phys-data -o "${data}" -- sleep 0.1 > /dev/null 2>&1; then
+ echo "Skipping dropped samples test as --phys-data is not supported"
+ return
+ fi
+
+ perf record -e task-clock:u --phys-data -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ # Verify that the original file actually contained samples!
+ orig_samples=$(perf script -i "${data}" | wc -l)
+ if [ "$orig_samples" -eq 0 ]; then
+ echo "Dropped samples test [Failed - no samples in original file]"
+ err=1
+ else
+ # Verify that samples are dropped.
+ samples_count=$(perf script -i "${data2}" | wc -l)
+
+ if [ "$samples_count" -gt 0 ]; then
+ echo "Dropped samples test [Failed - samples were not dropped]"
+ err=1
+ else
+ echo "Dropped samples test [Success]"
+ fi
+ fi
+}
+
+test_kernel_aslr() {
+ echo "Test kernel ASLR remapping"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${prog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel ASLR test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel ASLR test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ perf inject -v --aslr -i "${kdata}" -o "${kdata2}"
+
+ # Check if kernel addresses are remapped.
+ # Find the field that ends with :k: (the event name) and take the next field!
+ orig_addr=$(perf script -i "${kdata}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+ new_addr=$(perf script -i "${kdata2}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+
+ echo "Kernel ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Kernel ASLR test [Failed - no kernel samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Kernel ASLR test [Success]"
+ fi
+}
+
+test_kernel_report_aslr() {
+ echo "Test kernel perf report consistency"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel_report.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_report_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${prog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel report test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel report test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${kdata}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${kdata}" -o "${kdata2}"
+
+ local report1="${temp_dir}/report_kernel1"
+ local report2="${temp_dir}/report_kernel2"
+ local report1_clean="${temp_dir}/report_kernel1.clean"
+ local report2_clean="${temp_dir}/report_kernel2.clean"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${kdata2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' > "${report2_clean}" || true
+
+ # Normalize kernel DSOs and addresses in clean reports
+ # This allows kernel modules to be either a module or kernel.kallsyms
+ local report1_norm="${temp_dir}/report_kernel1.norm"
+ local report2_norm="${temp_dir}/report_kernel2.norm"
+
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); \
+ print}' "${report1_clean}" > "${report1_norm}"
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); \
+ print}' "${report2_clean}" > "${report2_norm}"
+
+ # Calculate sum of percentages for [kernel] samples
+ sum1=$(awk '/\[kernel\]/ {sum += $1} END {print sum}' "${report1_norm}")
+ sum2=$(awk '/\[kernel\]/ {sum += $1} END {print sum}' "${report2_norm}")
+
+ echo "Kernel report sums: sum1=$sum1, sum2=$sum2"
+
+ # Compare sums with tolerance
+ if [ -z "$sum1" ] || [ -z "$sum2" ]; then
+ echo "Kernel Report ASLR test [Failed - no kernel data]"
+ err=1
+ elif awk -v s1="$sum1" -v s2="$sum2" \
+ 'BEGIN {diff = s1 - s2; if (diff < 0) diff = -diff; if (diff < 0.05) exit 0; else exit 1}'; then
+ echo "Kernel Report ASLR test [Success]"
+ else
+ echo "Kernel Report ASLR test [Failed - sums differ too much]"
+ err=1
+ fi
+}
+
+test_basic_aslr
+test_pipe_aslr
+test_callchain_aslr
+test_report_aslr
+test_pipe_report_aslr
+test_dropped_samples
+test_kernel_aslr
+test_kernel_report_aslr
+
+cleanup
+exit $err
--
2.54.0.545.g6539524ca2-goog
next prev parent reply other threads:[~2026-05-04 3:51 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-24 22:05 [PATCH v1 1/2] perf inject/aslr: Add aslr tool to remap/obfuscate virtual addresses Ian Rogers
2026-04-24 22:05 ` [PATCH v1 2/2] perf test: Add inject ASLR test Ian Rogers
2026-04-25 2:05 ` [PATCH v2 1/2] perf inject/aslr: Add aslr tool to remap/obfuscate virtual addresses Ian Rogers
2026-04-25 2:05 ` [PATCH v2 2/2] perf test: Add inject ASLR test Ian Rogers
2026-05-04 3:51 ` [PATCH v3 0/4] perf tools: Add inject --aslr feature and prerequisite robustness fixes Ian Rogers
2026-05-04 3:51 ` [PATCH v3 1/4] perf sched: Add missing mmap2 handler in timehist Ian Rogers
2026-05-04 3:51 ` [PATCH v3 2/4] perf tool: Fix missing schedstat delegates and dont_split_sample_group in delegate_tool Ian Rogers
2026-05-04 3:51 ` [PATCH v3 3/4] perf inject/aslr: Add aslr tool to remap/obfuscate virtual addresses Ian Rogers
2026-05-04 3:51 ` Ian Rogers [this message]
2026-05-04 7:29 ` [PATCH v4 0/4] perf tools: Add inject --aslr feature and prerequisite robustness fixes Ian Rogers
2026-05-04 7:29 ` [PATCH v4 1/4] perf sched: Add missing mmap2 handler in timehist Ian Rogers
2026-05-04 7:29 ` [PATCH v4 2/4] perf tool: Fix missing schedstat delegates and dont_split_sample_group in delegate_tool Ian Rogers
2026-05-04 7:29 ` [PATCH v4 3/4] perf inject/aslr: Add aslr tool to remap/obfuscate virtual addresses Ian Rogers
2026-05-04 7:29 ` [PATCH v4 4/4] perf test: Add inject ASLR test Ian Rogers
2026-05-04 8:23 ` [PATCH v4 0/4] perf tools: Add inject --aslr feature and prerequisite robustness fixes Ian Rogers
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260504035125.1851720-5-irogers@google.com \
--to=irogers@google.com \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=gmx@google.com \
--cc=james.clark@linaro.org \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mingo@redhat.com \
--cc=namhyung@kernel.org \
--cc=peterz@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox