xref: /linux/tools/perf/tests/shell/inject_aslr.sh (revision 3d5e48944e824bddc20d7b874e784f7b279636fe)
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3# perf inject --aslr test
4
5set -e
6set -o pipefail
7
8shelldir=$(dirname "$0")
9# shellcheck source=lib/perf_has_symbol.sh
10. "${shelldir}"/lib/perf_has_symbol.sh
11
12sym="noploop"
13
14skip_test_missing_symbol ${sym}
15
16# Create global temp directory
17temp_dir=$(mktemp -d /tmp/perf-test-aslr.XXXXXXXXXX)
18
19prog="perf test -w noploop"
20[ "$(uname -m)" = "s390x" ] && prog="$prog 3"
21err=0
22kprog="dd if=/dev/urandom of=/dev/null bs=1M count=50"
23
24cleanup() {
25  local exit_code=${1:-$?}
26  trap - EXIT TERM INT
27  if [ "${exit_code}" -ne 0 ] || [ "${err}" -ne 0 ]; then
28    echo "Test failed! Preserving temp directory: ${temp_dir}"
29    return
30  fi
31  # Check if temp_dir is set and looks sane before removing
32  if [[ "${temp_dir}" =~ ^/tmp/perf-test-aslr\. ]]; then
33    rm -rf "${temp_dir}"
34  fi
35}
36
37trap_cleanup() {
38  local exit_code=$?
39  echo "Unexpected signal in ${FUNCNAME[1]}"
40  cleanup ${exit_code}
41  exit ${exit_code}
42}
43trap trap_cleanup EXIT TERM INT
44
45get_noploop_addr() {
46  local file=$1
47  perf script -i "$file" | awk '
48    BEGIN { found=0 }
49    {
50      for (i=1; i<=NF; i++) {
51        if ($i ~ /noploop\+/) {
52          if (!found) {
53            print $(i-1)
54            found=1
55          }
56        }
57      }
58    }'
59}
60
61test_basic_aslr() {
62  echo "Test basic ASLR remapping"
63  local data
64  data=$(mktemp "${temp_dir}/perf.data.basic.XXXXXX")
65  local data2
66  data2=$(mktemp "${temp_dir}/perf.data2.basic.XXXXXX")
67
68  perf record -e task-clock:u -o "${data}" ${prog}
69  perf inject -v --aslr -i "${data}" -o "${data2}"
70
71  orig_addr=$(get_noploop_addr "${data}")
72  new_addr=$(get_noploop_addr "${data2}")
73
74  echo "Basic ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
75
76  if [ -z "$orig_addr" ]; then
77    echo "Basic ASLR test [Failed - no noploop samples in original file]"
78    err=1
79  elif [ -z "$new_addr" ]; then
80    echo "Basic ASLR test [Failed - could not find remapped address]"
81    err=1
82  elif [ "$orig_addr" = "$new_addr" ]; then
83    echo "Basic ASLR test [Failed - addresses are not remapped]"
84    err=1
85  else
86    echo "Basic ASLR test [Success]"
87  fi
88}
89
90test_pipe_aslr() {
91  echo "Test pipe mode ASLR remapping"
92  local data
93  data=$(mktemp "${temp_dir}/perf.data.pipe.XXXXXX")
94  local data2
95  data2=$(mktemp "${temp_dir}/perf.data2.pipe.XXXXXX")
96
97  # Use tee to save the original pipe data for comparison
98  perf record -e task-clock:u -o - ${prog} | tee "${data}" | perf inject --aslr -o "${data2}"
99
100  orig_addr=$(get_noploop_addr "${data}")
101  new_addr=$(get_noploop_addr "${data2}")
102
103  echo "Pipe ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
104
105  if [ -z "$orig_addr" ]; then
106    echo "Pipe ASLR test [Failed - no noploop samples in original file]"
107    err=1
108  elif [ -z "$new_addr" ]; then
109    echo "Pipe ASLR test [Failed - could not find remapped address]"
110    err=1
111  elif [ "$orig_addr" = "$new_addr" ]; then
112    echo "Pipe ASLR test [Failed - addresses are not remapped]"
113    err=1
114  else
115    echo "Pipe ASLR test [Success]"
116  fi
117}
118
119test_callchain_aslr() {
120  echo "Test Callchain ASLR remapping"
121  local data
122  data=$(mktemp "${temp_dir}/perf.data.callchain.XXXXXX")
123  local data2
124  data2=$(mktemp "${temp_dir}/perf.data2.callchain.XXXXXX")
125
126  perf record -g -e task-clock:u -o "${data}" ${prog}
127  perf inject --aslr -i "${data}" -o "${data2}"
128
129  orig_addr=$(get_noploop_addr "${data}")
130  new_addr=$(get_noploop_addr "${data2}")
131
132  echo "Callchain ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
133
134  if [ -z "$orig_addr" ]; then
135    echo "Callchain ASLR test [Failed - no noploop samples in original file]"
136    err=1
137  elif [ -z "$new_addr" ]; then
138    echo "Callchain ASLR test [Failed - could not find remapped address]"
139    err=1
140  elif [ "$orig_addr" = "$new_addr" ]; then
141    echo "Callchain ASLR test [Failed - addresses are not remapped]"
142    err=1
143  else
144    # Extract callchain addresses (indented lines starting with hex addresses)
145    orig_callchain=$(perf script -i "${data}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
146    new_callchain=$(perf script -i "${data2}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
147
148    if [ -z "$orig_callchain" ]; then
149      echo "Callchain ASLR test [Failed - no callchain samples in original file]"
150      err=1
151    elif [ -z "$new_callchain" ]; then
152      echo "Callchain ASLR test [Failed - callchain data was dropped]"
153      err=1
154    elif [ "$orig_callchain" = "$new_callchain" ]; then
155      echo "Callchain ASLR test [Failed - callchain addresses were not remapped]"
156      err=1
157    else
158      echo "Callchain ASLR test [Success]"
159    fi
160  fi
161}
162
163test_report_aslr() {
164  echo "Test perf report consistency"
165  local data
166  data=$(mktemp "${temp_dir}/perf.data.report.XXXXXX")
167  local data2
168  data2=$(mktemp "${temp_dir}/perf.data2.report.XXXXXX")
169  local data_clean
170  data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
171
172  perf record -e task-clock:u -o "${data}" ${prog}
173  # Use -b to inject build-ids and force ordered events processing in both
174  perf inject -b -i "${data}" -o "${data_clean}"
175  perf inject -v -b --aslr -i "${data}" -o "${data2}"
176
177  local report1="${temp_dir}/report1_basic"
178  local report2="${temp_dir}/report2_basic"
179  local report1_clean="${temp_dir}/report1_basic.clean"
180  local report2_clean="${temp_dir}/report2_basic.clean"
181  local diff_file="${temp_dir}/diff_basic"
182
183  perf report -i "${data_clean}" --stdio > "${report1}"
184  perf report -i "${data2}" --stdio > "${report2}"
185
186  # Strip headers and compare lines with percentages
187  grep '%' "${report1}" | grep -v '^#' | \
188    grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report1_clean}" || true
189  grep '%' "${report2}" | grep -v '^#' | \
190    grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report2_clean}" || true
191
192  diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
193
194  if [ ! -s "${report1_clean}" ]; then
195    echo "Report ASLR test [Failed - no samples captured]"
196    err=1
197  elif [ -s "${diff_file}" ]; then
198    echo "Report ASLR test [Failed - reports differ]"
199    echo "Showing first 20 lines of diff:"
200    head -n 20 "${diff_file}"
201    err=1
202  else
203    echo "Report ASLR test [Success]"
204  fi
205}
206
207test_pipe_report_aslr() {
208  echo "Test pipe mode perf report consistency"
209  local data
210  data=$(mktemp "${temp_dir}/perf.data.pipe_report.XXXXXX")
211  local data2
212  data2=$(mktemp "${temp_dir}/perf.data2.pipe_report.XXXXXX")
213  local data_clean
214  data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
215
216  # Use tee to save the original pipe data, then process it with inject -b
217  perf record -e task-clock:u -o - ${prog} | \
218    tee "${data}" | \
219    perf inject -b --aslr -o "${data2}"
220  perf inject -b -i "${data}" -o "${data_clean}"
221
222  local report1="${temp_dir}/report1_pipe"
223  local report2="${temp_dir}/report2_pipe"
224  local report1_clean="${temp_dir}/report1_pipe.clean"
225  local report2_clean="${temp_dir}/report2_pipe.clean"
226  local diff_file="${temp_dir}/diff_pipe"
227
228  perf report -i "${data_clean}" --stdio > "${report1}"
229  perf report -i "${data2}" --stdio > "${report2}"
230
231  # Strip headers and compare lines with percentages
232  grep '%' "${report1}" | grep -v '^#' | \
233    grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report1_clean}" || true
234  grep '%' "${report2}" | grep -v '^#' | \
235    grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report2_clean}" || true
236
237  diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
238
239  if [ ! -s "${report1_clean}" ]; then
240    echo "Pipe Report ASLR test [Failed - no samples captured]"
241    err=1
242  elif [ -s "${diff_file}" ]; then
243    echo "Pipe Report ASLR test [Failed - reports differ]"
244    echo "Showing first 20 lines of diff:"
245    head -n 20 "${diff_file}"
246    err=1
247  else
248    echo "Pipe Report ASLR test [Success]"
249  fi
250}
251
252test_pipe_out_report_aslr() {
253  echo "Test pipe output mode perf report consistency"
254  local data
255  data=$(mktemp "${temp_dir}/perf.data.pipe_out_report.XXXXXX")
256  local data_clean
257  data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
258
259  perf record -e task-clock:u -o "${data}" ${prog}
260  perf inject -b -i "${data}" -o "${data_clean}"
261
262  local report1="${temp_dir}/report1_pipe_out"
263  local report2="${temp_dir}/report2_pipe_out"
264  local report1_clean="${temp_dir}/report1_pipe_out.clean"
265  local report2_clean="${temp_dir}/report2_pipe_out.clean"
266  local diff_file="${temp_dir}/diff_pipe_out"
267
268  perf report -i "${data_clean}" --stdio > "${report1}"
269  perf inject -b --aslr -i "${data}" -o - | perf report -i - --stdio > "${report2}"
270
271  # Strip headers and compare lines with percentages
272  grep '%' "${report1}" | grep -v '^#' | \
273    grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report1_clean}" || true
274  grep '%' "${report2}" | grep -v '^#' | \
275    grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report2_clean}" || true
276
277  diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
278
279  if [ ! -s "${report1_clean}" ]; then
280    echo "Pipe Output Report ASLR test [Failed - no samples captured]"
281    err=1
282  elif [ -s "${diff_file}" ]; then
283    echo "Pipe Output Report ASLR test [Failed - reports differ]"
284    echo "Showing first 20 lines of diff:"
285    head -n 20 "${diff_file}"
286    err=1
287  else
288    echo "Pipe Output Report ASLR test [Success]"
289  fi
290}
291
292test_dropped_samples() {
293  echo "Test dropped samples (phys-data)"
294  local data
295  data=$(mktemp "${temp_dir}/perf.data.dropped.XXXXXX")
296  local data2
297  data2=$(mktemp "${temp_dir}/perf.data2.dropped.XXXXXX")
298
299  # Check if --phys-data is supported by recording a short run
300  if ! perf record -e task-clock:u --phys-data -o "${data}" -- sleep 0.1 > /dev/null 2>&1; then
301    echo "Skipping dropped samples test as --phys-data is not supported"
302    return
303  fi
304
305  perf record -e task-clock:u --phys-data -o "${data}" ${prog}
306  perf inject --aslr -i "${data}" -o "${data2}"
307
308  # Verify that the original file actually contained samples!
309  orig_samples=$(perf script -i "${data}" | wc -l)
310  if [ "$orig_samples" -eq 0 ]; then
311    echo "Dropped samples test [Failed - no samples in original file]"
312    err=1
313  else
314    # Verify that samples are dropped.
315    samples_count=$(perf script -i "${data2}" | wc -l)
316
317    if [ "$samples_count" -gt 0 ]; then
318      echo "Dropped samples test [Failed - samples were not dropped]"
319      err=1
320    else
321      echo "Dropped samples test [Success]"
322    fi
323  fi
324}
325
326test_kernel_aslr() {
327  echo "Test kernel ASLR remapping"
328  local kdata
329  kdata=$(mktemp "${temp_dir}/perf.data.kernel.XXXXXX")
330  local kdata2
331  kdata2=$(mktemp "${temp_dir}/perf.data2.kernel.XXXXXX")
332  local log_file
333  log_file=$(mktemp "${temp_dir}/kernel_record.log.XXXXXX")
334
335  # Try to record kernel samples
336  if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
337    echo "Skipping kernel ASLR test as recording failed (maybe no permissions)"
338    return
339  fi
340
341  # Check for warning about kernel map restriction
342  if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
343    echo "Skipping kernel ASLR test as kernel map could not be recorded (permissions restricted)"
344    return
345  fi
346
347  perf inject -v --aslr -i "${kdata}" -o "${kdata2}"
348
349  # Check if kernel addresses are remapped.
350  # Find the field that ends with :k: (the event name) and take the next field!
351  orig_addr=$(perf script -i "${kdata}" | awk '
352    BEGIN { found=0 }
353    {
354      for (i=1; i<NF; i++) {
355        if ($i ~ /:[k]+:?$/) {
356          if (!found) {
357            print $(i+1)
358            found=1
359          }
360        }
361      }
362    }')
363  new_addr=$(perf script -i "${kdata2}" | awk '
364    BEGIN { found=0 }
365    {
366      for (i=1; i<NF; i++) {
367        if ($i ~ /:[k]+:?$/) {
368          if (!found) {
369            print $(i+1)
370            found=1
371          }
372        }
373      }
374    }')
375
376  echo "Kernel ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
377
378  if [ -z "$orig_addr" ]; then
379    echo "Kernel ASLR test [Failed - no kernel samples in original file]"
380    err=1
381  elif [ -z "$new_addr" ]; then
382    echo "Kernel ASLR test [Failed - could not find remapped address]"
383    err=1
384  elif [ "$orig_addr" = "$new_addr" ]; then
385    echo "Kernel ASLR test [Failed - addresses are not remapped]"
386    err=1
387  else
388    echo "Kernel ASLR test [Success]"
389  fi
390}
391
392test_kernel_report_aslr() {
393  echo "Test kernel perf report consistency"
394  local kdata
395  kdata=$(mktemp "${temp_dir}/perf.data.kernel_report.XXXXXX")
396  local kdata2
397  kdata2=$(mktemp "${temp_dir}/perf.data2.kernel_report.XXXXXX")
398  local data_clean
399  data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
400  local log_file
401  log_file=$(mktemp "${temp_dir}/kernel_report_record.log.XXXXXX")
402
403  # Try to record kernel samples
404  if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
405    echo "Skipping kernel report test as recording failed (maybe no permissions)"
406    return
407  fi
408
409  # Check for warning about kernel map restriction
410  if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
411    echo "Skipping kernel report test as kernel map could not be recorded (permissions restricted)"
412    return
413  fi
414
415  # Use -b to inject build-ids and force ordered events processing in both
416  perf inject -b -i "${kdata}" -o "${data_clean}"
417  perf inject -v -b --aslr -i "${kdata}" -o "${kdata2}"
418
419  local report1="${temp_dir}/report_kernel1"
420  local report2="${temp_dir}/report_kernel2"
421  local report1_clean="${temp_dir}/report_kernel1.clean"
422  local report2_clean="${temp_dir}/report_kernel2.clean"
423
424  perf report -i "${data_clean}" --stdio > "${report1}"
425  perf report -i "${kdata2}" --stdio > "${report2}"
426
427  # Strip headers and compare lines with percentages
428  grep '%' "${report1}" | grep -v '^#' > "${report1_clean}" || true
429  grep '%' "${report2}" | grep -v '^#' > "${report2_clean}" || true
430
431  # Normalize kernel DSOs and addresses in clean reports
432  # This allows kernel modules to be either a module or kernel.kallsyms
433  local report1_norm="${temp_dir}/report_kernel1.norm"
434  local report2_norm="${temp_dir}/report_kernel2.norm"
435  local diff_file="${temp_dir}/diff_kernel"
436
437  grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report1_clean}" | \
438    awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
439    sort > "${report1_norm}" || true
440  grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report2_clean}" | \
441    awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
442    sort > "${report2_norm}" || true
443
444  diff -u -w "${report1_norm}" "${report2_norm}" > "${diff_file}" || true
445
446  if [ ! -s "${report1_norm}" ]; then
447    echo "Kernel Report ASLR test [Failed - no samples captured]"
448    err=1
449  elif [ -s "${diff_file}" ]; then
450    echo "Kernel Report ASLR test [Failed - reports differ]"
451    echo "Showing first 20 lines of diff:"
452    head -n 20 "${diff_file}"
453    err=1
454  else
455    echo "Kernel Report ASLR test [Success]"
456  fi
457}
458
459test_regs_stripping() {
460  echo "Test user register stripping"
461  local rdata="${temp_dir}/perf.data.regs"
462  local rdata2="${temp_dir}/perf.data.regs.injected"
463  local rdata_clean="${temp_dir}/perf.data.regs.clean"
464
465  if ! perf record -e cycles:u --user-regs -o "${rdata}" ${prog} > /dev/null 2>&1; then
466    echo "Skipping user registers test as recording failed (unsupported flag/platform)"
467    return
468  fi
469
470  perf inject -b -i "${rdata}" -o "${rdata_clean}"
471  perf inject -v -b --aslr -i "${rdata}" -o "${rdata2}"
472
473  local report1="${temp_dir}/report_regs1"
474  local report2="${temp_dir}/report_regs2"
475  local report1_clean="${temp_dir}/report_regs1.clean"
476  local report2_clean="${temp_dir}/report_regs2.clean"
477  local diff_file="${temp_dir}/diff_regs"
478
479  perf report -i "${rdata_clean}" --stdio > "${report1}" 2>/dev/null || true
480  perf report -i "${rdata2}" --stdio > "${report2}" 2>/dev/null || true
481
482  grep '%' "${report1}" | grep -v '^#' | \
483    grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
484    sort > "${report1_clean}" || true
485  grep '%' "${report2}" | grep -v '^#' | \
486    grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
487    sort > "${report2_clean}" || true
488
489  diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
490
491  if [ ! -s "${report1_clean}" ]; then
492    echo "User registers stripping test [Failed - profile trace starved/empty]"
493    err=1
494    return
495  elif [ -s "${diff_file}" ]; then
496    echo "User registers stripping test [Failed - report parsing differs]"
497    echo "Showing first 20 lines of diff:"
498    head -n 20 "${diff_file}"
499    err=1
500    return
501  fi
502
503  local script_dump="${temp_dir}/script_regs_dump"
504  perf script -D -i "${rdata2}" > "${script_dump}" 2>/dev/null || true
505  if grep -q "user regs:" "${script_dump}"; then
506    echo "User registers stripping test [Failed - register dumps still present]"
507    err=1
508  else
509    echo "User registers stripping test [Success]"
510  fi
511}
512
513test_basic_aslr
514test_pipe_aslr
515test_callchain_aslr
516test_report_aslr
517test_pipe_report_aslr
518test_pipe_out_report_aslr
519test_dropped_samples
520case "$(uname -m)" in
521  aarch64*|arm*)
522    echo "Skipping kernel ASLR tests on ARM"
523    ;;
524  *)
525    test_kernel_aslr
526    test_kernel_report_aslr
527    ;;
528esac
529
530test_regs_stripping
531
532cleanup ${err}
533exit $err
534