1#!/bin/bash 2# Miscellaneous Intel PT testing (exclusive) 3# SPDX-License-Identifier: GPL-2.0 4 5set -e 6 7# Skip if no Intel PT 8perf list pmu | grep -q 'intel_pt//' || exit 2 9 10shelldir=$(dirname "$0") 11# shellcheck source=lib/waiting.sh 12. "${shelldir}"/lib/waiting.sh 13 14skip_cnt=0 15ok_cnt=0 16err_cnt=0 17 18temp_dir=$(mktemp -d /tmp/perf-test-intel-pt-sh.XXXXXXXXXX) 19 20tmpfile="${temp_dir}/tmp-perf.data" 21perfdatafile="${temp_dir}/test-perf.data" 22outfile="${temp_dir}/test-out.txt" 23errfile="${temp_dir}/test-err.txt" 24workload="${temp_dir}/workload" 25awkscript="${temp_dir}/awkscript" 26jitdump_workload="${temp_dir}/jitdump_workload" 27maxbrstack="${temp_dir}/maxbrstack.py" 28 29cleanup() 30{ 31 trap - EXIT TERM INT 32 sane=$(echo "${temp_dir}" | cut -b 1-26) 33 if [ "${sane}" = "/tmp/perf-test-intel-pt-sh" ] ; then 34 echo "--- Cleaning up ---" 35 rm -f "${temp_dir}/"* 36 rmdir "${temp_dir}" 37 fi 38} 39 40trap_cleanup() 41{ 42 cleanup 43 exit 1 44} 45 46trap trap_cleanup EXIT TERM INT 47 48# perf record for testing without decoding 49perf_record_no_decode() 50{ 51 # Options to speed up recording: no post-processing, no build-id cache update, 52 # and no BPF events. 53 perf record -B -N --no-bpf-event "$@" 54} 55 56# perf record for testing should not need BPF events 57perf_record_no_bpf() 58{ 59 # Options for no BPF events 60 perf record --no-bpf-event "$@" 61} 62 63have_workload=false 64cat << _end_of_file_ | /usr/bin/cc -o "${workload}" -xc - -pthread && have_workload=true 65#include <time.h> 66#include <pthread.h> 67 68void work(void) { 69 struct timespec tm = { 70 .tv_nsec = 1000000, 71 }; 72 int i; 73 74 /* Run for about 30 seconds */ 75 for (i = 0; i < 30000; i++) 76 nanosleep(&tm, NULL); 77} 78 79void *threadfunc(void *arg) { 80 work(); 81 return NULL; 82} 83 84int main(void) { 85 pthread_t th; 86 87 pthread_create(&th, NULL, threadfunc, NULL); 88 work(); 89 pthread_join(th, NULL); 90 return 0; 91} 92_end_of_file_ 93 94can_cpu_wide() 95{ 96 echo "Checking for CPU-wide recording on CPU $1" 97 if ! perf_record_no_decode -o "${tmpfile}" -e dummy:u -C "$1" true >/dev/null 2>&1 ; then 98 echo "No so skipping" 99 return 2 100 fi 101 echo OK 102 return 0 103} 104 105test_system_wide_side_band() 106{ 107 echo "--- Test system-wide sideband ---" 108 109 # Need CPU 0 and CPU 1 110 can_cpu_wide 0 || return $? 111 can_cpu_wide 1 || return $? 112 113 # Record on CPU 0 a task running on CPU 1 114 perf_record_no_decode -o "${perfdatafile}" -e intel_pt//u -C 0 -- taskset --cpu-list 1 uname 115 116 # Should get MMAP events from CPU 1 because they can be needed to decode 117 mmap_cnt=$(perf script -i "${perfdatafile}" --no-itrace --show-mmap-events -C 1 2>/dev/null | grep -c MMAP) 118 119 if [ "${mmap_cnt}" -gt 0 ] ; then 120 echo OK 121 return 0 122 fi 123 124 echo "Failed to record MMAP events on CPU 1 when tracing CPU 0" 125 return 1 126} 127 128can_kernel() 129{ 130 if [ -z "${can_kernel_trace}" ] ; then 131 can_kernel_trace=0 132 perf_record_no_decode -o "${tmpfile}" -e dummy:k true >/dev/null 2>&1 && can_kernel_trace=1 133 fi 134 if [ ${can_kernel_trace} -eq 0 ] ; then 135 echo "SKIP: no kernel tracing" 136 return 2 137 fi 138 return 0 139} 140 141test_per_thread() 142{ 143 k="$1" 144 desc="$2" 145 146 echo "--- Test per-thread ${desc}recording ---" 147 148 if ! $have_workload ; then 149 echo "No workload, so skipping" 150 return 2 151 fi 152 153 if [ "${k}" = "k" ] ; then 154 can_kernel || return 2 155 fi 156 157 cat <<- "_end_of_file_" > "${awkscript}" 158 BEGIN { 159 s = "[ ]*" 160 u = s"[0-9]+"s 161 d = s"[0-9-]+"s 162 x = s"[0-9a-fA-FxX]+"s 163 mmapping = "idx"u": mmapping fd"u 164 set_output = "idx"u": set output fd"u"->"u 165 perf_event_open = "sys_perf_event_open: pid"d"cpu"d"group_fd"d"flags"x"="u 166 } 167 168 /perf record opening and mmapping events/ { 169 if (!done) 170 active = 1 171 } 172 173 /perf record done opening and mmapping events/ { 174 active = 0 175 done = 1 176 } 177 178 $0 ~ perf_event_open && active { 179 match($0, perf_event_open) 180 $0 = substr($0, RSTART, RLENGTH) 181 pid = $3 182 cpu = $5 183 fd = $11 184 print "pid " pid " cpu " cpu " fd " fd " : " $0 185 fd_array[fd] = fd 186 pid_array[fd] = pid 187 cpu_array[fd] = cpu 188 } 189 190 $0 ~ mmapping && active { 191 match($0, mmapping) 192 $0 = substr($0, RSTART, RLENGTH) 193 fd = $5 194 print "fd " fd " : " $0 195 if (fd in fd_array) { 196 mmap_array[fd] = 1 197 } else { 198 print "Unknown fd " fd 199 exit 1 200 } 201 } 202 203 $0 ~ set_output && active { 204 match($0, set_output) 205 $0 = substr($0, RSTART, RLENGTH) 206 fd = $6 207 fd_to = $8 208 print "fd " fd " fd_to " fd_to " : " $0 209 if (fd in fd_array) { 210 if (fd_to in fd_array) { 211 set_output_array[fd] = fd_to 212 } else { 213 print "Unknown fd " fd_to 214 exit 1 215 } 216 } else { 217 print "Unknown fd " fd 218 exit 1 219 } 220 } 221 222 END { 223 print "Checking " length(fd_array) " fds" 224 for (fd in fd_array) { 225 if (fd in mmap_array) { 226 pid = pid_array[fd] 227 if (pid != -1) { 228 if (pid in pids) { 229 print "More than 1 mmap for PID " pid 230 exit 1 231 } 232 pids[pid] = 1 233 } 234 cpu = cpu_array[fd] 235 if (cpu != -1) { 236 if (cpu in cpus) { 237 print "More than 1 mmap for CPU " cpu 238 exit 1 239 } 240 cpus[cpu] = 1 241 } 242 } else if (!(fd in set_output_array)) { 243 print "No mmap for fd " fd 244 exit 1 245 } 246 } 247 n = length(pids) 248 if (n != thread_cnt) { 249 print "Expected " thread_cnt " per-thread mmaps - found " n 250 exit 1 251 } 252 } 253 _end_of_file_ 254 255 $workload & 256 w1=$! 257 $workload & 258 w2=$! 259 echo "Workload PIDs are $w1 and $w2" 260 wait_for_threads ${w1} 2 261 wait_for_threads ${w2} 2 262 263 perf_record_no_decode -o "${perfdatafile}" -e intel_pt//u"${k}" -vvv --per-thread -p "${w1},${w2}" 2>"${errfile}" >"${outfile}" & 264 ppid=$! 265 echo "perf PID is $ppid" 266 wait_for_perf_to_start ${ppid} "${errfile}" || return 1 267 268 kill ${w1} 269 wait_for_process_to_exit ${w1} || return 1 270 is_running ${ppid} || return 1 271 272 kill ${w2} 273 wait_for_process_to_exit ${w2} || return 1 274 wait_for_process_to_exit ${ppid} || return 1 275 276 awk -v thread_cnt=4 -f "${awkscript}" "${errfile}" || return 1 277 278 echo OK 279 return 0 280} 281 282test_jitdump() 283{ 284 echo "--- Test tracing self-modifying code that uses jitdump ---" 285 286 script_path=$(realpath "$0") 287 script_dir=$(dirname "$script_path") 288 jitdump_incl_dir="${script_dir}/../../util" 289 jitdump_h="${jitdump_incl_dir}/jitdump.h" 290 291 if ! perf check feature -q libelf ; then 292 echo "SKIP: libelf is needed for jitdump" 293 return 2 294 fi 295 296 if [ ! -e "${jitdump_h}" ] ; then 297 echo "SKIP: Include file jitdump.h not found" 298 return 2 299 fi 300 301 if [ -z "${have_jitdump_workload}" ] ; then 302 have_jitdump_workload=false 303 # Create a workload that uses self-modifying code and generates its own jitdump file 304 cat <<- "_end_of_file_" | /usr/bin/cc -o "${jitdump_workload}" -I "${jitdump_incl_dir}" -xc - -pthread && have_jitdump_workload=true 305 #define _GNU_SOURCE 306 #include <sys/mman.h> 307 #include <sys/types.h> 308 #include <stddef.h> 309 #include <stdio.h> 310 #include <stdint.h> 311 #include <unistd.h> 312 #include <string.h> 313 314 #include "jitdump.h" 315 316 #define CHK_BYTE 0x5a 317 318 static inline uint64_t rdtsc(void) 319 { 320 unsigned int low, high; 321 322 asm volatile("rdtsc" : "=a" (low), "=d" (high)); 323 324 return low | ((uint64_t)high) << 32; 325 } 326 327 static FILE *open_jitdump(void) 328 { 329 struct jitheader header = { 330 .magic = JITHEADER_MAGIC, 331 .version = JITHEADER_VERSION, 332 .total_size = sizeof(header), 333 .pid = getpid(), 334 .timestamp = rdtsc(), 335 .flags = JITDUMP_FLAGS_ARCH_TIMESTAMP, 336 }; 337 char filename[256]; 338 FILE *f; 339 void *m; 340 341 snprintf(filename, sizeof(filename), "jit-%d.dump", getpid()); 342 f = fopen(filename, "w+"); 343 if (!f) 344 goto err; 345 /* Create an MMAP event for the jitdump file. That is how perf tool finds it. */ 346 m = mmap(0, 4096, PROT_READ | PROT_EXEC, MAP_PRIVATE, fileno(f), 0); 347 if (m == MAP_FAILED) 348 goto err_close; 349 munmap(m, 4096); 350 if (fwrite(&header,sizeof(header),1,f) != 1) 351 goto err_close; 352 return f; 353 354 err_close: 355 fclose(f); 356 err: 357 return NULL; 358 } 359 360 static int write_jitdump(FILE *f, void *addr, const uint8_t *dat, size_t sz, uint64_t *idx) 361 { 362 struct jr_code_load rec = { 363 .p.id = JIT_CODE_LOAD, 364 .p.total_size = sizeof(rec) + sz, 365 .p.timestamp = rdtsc(), 366 .pid = getpid(), 367 .tid = gettid(), 368 .vma = (unsigned long)addr, 369 .code_addr = (unsigned long)addr, 370 .code_size = sz, 371 .code_index = ++*idx, 372 }; 373 374 if (fwrite(&rec,sizeof(rec),1,f) != 1 || 375 fwrite(dat, sz, 1, f) != 1) 376 return -1; 377 return 0; 378 } 379 380 static void close_jitdump(FILE *f) 381 { 382 fclose(f); 383 } 384 385 int main() 386 { 387 /* Get a memory page to store executable code */ 388 void *addr = mmap(0, 4096, PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 389 /* Code to execute: mov CHK_BYTE, %eax ; ret */ 390 uint8_t dat[] = {0xb8, CHK_BYTE, 0x00, 0x00, 0x00, 0xc3}; 391 FILE *f = open_jitdump(); 392 uint64_t idx = 0; 393 int ret = 1; 394 395 if (!f) 396 return 1; 397 /* Copy executable code to executable memory page */ 398 memcpy(addr, dat, sizeof(dat)); 399 /* Record it in the jitdump file */ 400 if (write_jitdump(f, addr, dat, sizeof(dat), &idx)) 401 goto out_close; 402 /* Call it */ 403 ret = ((int (*)(void))addr)() - CHK_BYTE; 404 out_close: 405 close_jitdump(f); 406 return ret; 407 } 408 _end_of_file_ 409 fi 410 411 if ! $have_jitdump_workload ; then 412 echo "SKIP: No jitdump workload" 413 return 2 414 fi 415 416 # Change to temp_dir so jitdump collateral files go there 417 cd "${temp_dir}" 418 perf_record_no_bpf -o "${tmpfile}" -e intel_pt//u "${jitdump_workload}" 419 perf inject -i "${tmpfile}" -o "${perfdatafile}" --jit 420 decode_br_cnt=$(perf script -i "${perfdatafile}" --itrace=b | wc -l) 421 # Note that overflow and lost errors are suppressed for the error count 422 decode_err_cnt=$(perf script -i "${perfdatafile}" --itrace=e-o-l | grep -ci error) 423 cd - 424 # Should be thousands of branches 425 if [ "${decode_br_cnt}" -lt 1000 ] ; then 426 echo "Decode failed, only ${decode_br_cnt} branches" 427 return 1 428 fi 429 # Should be no errors 430 if [ "${decode_err_cnt}" -ne 0 ] ; then 431 echo "Decode failed, ${decode_err_cnt} errors" 432 perf script -i "${perfdatafile}" --itrace=e-o-l --show-mmap-events | cat 433 return 1 434 fi 435 436 echo OK 437 return 0 438} 439 440test_packet_filter() 441{ 442 echo "--- Test with MTC and TSC disabled ---" 443 # Disable MTC and TSC 444 perf_record_no_decode -o "${perfdatafile}" -e intel_pt/mtc=0,tsc=0/u uname 445 # Should not get MTC packet 446 mtc_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "MTC 0x") 447 if [ "${mtc_cnt}" -ne 0 ] ; then 448 echo "Failed to filter with mtc=0" 449 return 1 450 fi 451 # Should not get TSC package 452 tsc_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "TSC 0x") 453 if [ "${tsc_cnt}" -ne 0 ] ; then 454 echo "Failed to filter with tsc=0" 455 return 1 456 fi 457 echo OK 458 return 0 459} 460 461test_disable_branch() 462{ 463 echo "--- Test with branches disabled ---" 464 # Disable branch 465 perf_record_no_decode -o "${perfdatafile}" -e intel_pt/branch=0/u uname 466 # Should not get branch related packets 467 tnt_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "TNT 0x") 468 tip_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "TIP 0x") 469 fup_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "FUP 0x") 470 if [ "${tnt_cnt}" -ne 0 ] || [ "${tip_cnt}" -ne 0 ] || [ "${fup_cnt}" -ne 0 ] ; then 471 echo "Failed to disable branches" 472 return 1 473 fi 474 echo OK 475 return 0 476} 477 478test_time_cyc() 479{ 480 echo "--- Test with/without CYC ---" 481 # Check if CYC is supported 482 cyc=$(cat /sys/bus/event_source/devices/intel_pt/caps/psb_cyc) 483 if [ "${cyc}" != "1" ] ; then 484 echo "SKIP: CYC is not supported" 485 return 2 486 fi 487 # Enable CYC 488 perf_record_no_decode -o "${perfdatafile}" -e intel_pt/cyc/u uname 489 # should get CYC packets 490 cyc_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "CYC 0x") 491 if [ "${cyc_cnt}" = "0" ] ; then 492 echo "Failed to get CYC packet" 493 return 1 494 fi 495 # Without CYC 496 perf_record_no_decode -o "${perfdatafile}" -e intel_pt//u uname 497 # Should not get CYC packets 498 cyc_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "CYC 0x") 499 if [ "${cyc_cnt}" -gt 0 ] ; then 500 echo "Still get CYC packet without cyc" 501 return 1 502 fi 503 echo OK 504 return 0 505} 506 507test_sample() 508{ 509 echo "--- Test recording with sample mode ---" 510 # Check if recording with sample mode is working 511 if ! perf_record_no_decode -o "${perfdatafile}" --aux-sample=8192 -e '{intel_pt//u,branch-misses:u}' uname ; then 512 echo "perf record failed with --aux-sample" 513 return 1 514 fi 515 # Check with event with PMU name 516 if perf_record_no_decode -o "${perfdatafile}" -e br_misp_retired.all_branches:u uname ; then 517 if ! perf_record_no_decode -o "${perfdatafile}" -e '{intel_pt//,br_misp_retired.all_branches/aux-sample-size=8192/}:u' uname ; then 518 echo "perf record failed with --aux-sample-size" 519 return 1 520 fi 521 fi 522 echo OK 523 return 0 524} 525 526test_kernel_trace() 527{ 528 echo "--- Test with kernel trace ---" 529 # Check if recording with kernel trace is working 530 can_kernel || return 2 531 if ! perf_record_no_decode -o "${perfdatafile}" -e intel_pt//k -m1,128 uname ; then 532 echo "perf record failed with intel_pt//k" 533 return 1 534 fi 535 echo OK 536 return 0 537} 538 539test_virtual_lbr() 540{ 541 echo "--- Test virtual LBR ---" 542 # Check if python script is supported 543 libpython=$(perf version --build-options | grep python | grep -cv OFF) 544 if [ "${libpython}" != "1" ] ; then 545 echo "SKIP: python scripting is not supported" 546 return 2 547 fi 548 549 # Python script to determine the maximum size of branch stacks 550 cat << "_end_of_file_" > "${maxbrstack}" 551from __future__ import print_function 552 553bmax = 0 554 555def process_event(param_dict): 556 if "brstack" in param_dict: 557 brstack = param_dict["brstack"] 558 n = len(brstack) 559 global bmax 560 if n > bmax: 561 bmax = n 562 563def trace_end(): 564 print("max brstack", bmax) 565_end_of_file_ 566 567 # Check if virtual lbr is working 568 perf_record_no_bpf -o "${perfdatafile}" --aux-sample -e '{intel_pt//,cycles}:u' uname 569 times_val=$(perf script -i "${perfdatafile}" --itrace=L -s "${maxbrstack}" 2>/dev/null | grep "max brstack " | cut -d " " -f 3) 570 case "${times_val}" in 571 [0-9]*) ;; 572 *) times_val=0;; 573 esac 574 if [ "${times_val}" -lt 2 ] ; then 575 echo "Failed with virtual lbr" 576 return 1 577 fi 578 echo OK 579 return 0 580} 581 582test_power_event() 583{ 584 echo "--- Test power events ---" 585 # Check if power events are supported 586 power_event=$(cat /sys/bus/event_source/devices/intel_pt/caps/power_event_trace) 587 if [ "${power_event}" != "1" ] ; then 588 echo "SKIP: power_event_trace is not supported" 589 return 2 590 fi 591 if ! perf_record_no_decode -o "${perfdatafile}" -a -e intel_pt/pwr_evt/u uname ; then 592 echo "perf record failed with pwr_evt" 593 return 1 594 fi 595 echo OK 596 return 0 597} 598 599test_no_tnt() 600{ 601 echo "--- Test with TNT packets disabled ---" 602 # Check if TNT disable is supported 603 notnt=$(cat /sys/bus/event_source/devices/intel_pt/caps/tnt_disable) 604 if [ "${notnt}" != "1" ] ; then 605 echo "SKIP: tnt_disable is not supported" 606 return 2 607 fi 608 perf_record_no_decode -o "${perfdatafile}" -e intel_pt/notnt/u uname 609 # Should be no TNT packets 610 tnt_cnt=$(perf script -i "${perfdatafile}" -D | grep -c TNT) 611 if [ "${tnt_cnt}" -ne 0 ] ; then 612 echo "TNT packets still there after notnt" 613 return 1 614 fi 615 echo OK 616 return 0 617} 618 619test_event_trace() 620{ 621 echo "--- Test with event_trace ---" 622 # Check if event_trace is supported 623 event_trace=$(cat /sys/bus/event_source/devices/intel_pt/caps/event_trace) 624 if [ "${event_trace}" != 1 ] ; then 625 echo "SKIP: event_trace is not supported" 626 return 2 627 fi 628 if ! perf_record_no_decode -o "${perfdatafile}" -e intel_pt/event/u uname ; then 629 echo "perf record failed with event trace" 630 return 1 631 fi 632 echo OK 633 return 0 634} 635 636test_pipe() 637{ 638 echo "--- Test with pipe mode ---" 639 # Check if it works with pipe 640 if ! perf_record_no_bpf -o- -e intel_pt//u uname | perf report -q -i- --itrace=i10000 ; then 641 echo "perf record + report failed with pipe mode" 642 return 1 643 fi 644 if ! perf_record_no_bpf -o- -e intel_pt//u uname | perf inject -b > /dev/null ; then 645 echo "perf record + inject failed with pipe mode" 646 return 1 647 fi 648 echo OK 649 return 0 650} 651 652test_pause_resume() 653{ 654 echo "--- Test with pause / resume ---" 655 if ! perf_record_no_decode -o "${perfdatafile}" -e intel_pt/aux-action=start-paused/u uname ; then 656 echo "SKIP: pause / resume is not supported" 657 return 2 658 fi 659 if ! perf_record_no_bpf -o "${perfdatafile}" \ 660 -e intel_pt/aux-action=start-paused/u \ 661 -e instructions/period=50000,aux-action=resume,name=Resume/u \ 662 -e instructions/period=100000,aux-action=pause,name=Pause/u uname ; then 663 echo "perf record with pause / resume failed" 664 return 1 665 fi 666 if ! perf script -i "${perfdatafile}" --itrace=b -Fperiod,event | \ 667 awk 'BEGIN {paused=1;branches=0} 668 /Resume/ {paused=0} 669 /branches/ {if (paused) exit 1;branches=1} 670 /Pause/ {paused=1} 671 END {if (!branches) exit 1}' ; then 672 echo "perf record with pause / resume failed" 673 return 1 674 fi 675 echo OK 676 return 0 677} 678 679count_result() 680{ 681 if [ "$1" -eq 2 ] ; then 682 skip_cnt=$((skip_cnt + 1)) 683 return 684 fi 685 if [ "$1" -eq 0 ] ; then 686 ok_cnt=$((ok_cnt + 1)) 687 return 688 fi 689 err_cnt=$((err_cnt + 1)) 690} 691 692ret=0 693test_system_wide_side_band || ret=$? ; count_result $ret ; ret=0 694test_per_thread "" "" || ret=$? ; count_result $ret ; ret=0 695test_per_thread "k" "(incl. kernel) " || ret=$? ; count_result $ret ; ret=0 696test_jitdump || ret=$? ; count_result $ret ; ret=0 697test_packet_filter || ret=$? ; count_result $ret ; ret=0 698test_disable_branch || ret=$? ; count_result $ret ; ret=0 699test_time_cyc || ret=$? ; count_result $ret ; ret=0 700test_sample || ret=$? ; count_result $ret ; ret=0 701test_kernel_trace || ret=$? ; count_result $ret ; ret=0 702test_virtual_lbr || ret=$? ; count_result $ret ; ret=0 703test_power_event || ret=$? ; count_result $ret ; ret=0 704test_no_tnt || ret=$? ; count_result $ret ; ret=0 705test_event_trace || ret=$? ; count_result $ret ; ret=0 706test_pipe || ret=$? ; count_result $ret ; ret=0 707test_pause_resume || ret=$? ; count_result $ret ; ret=0 708 709cleanup 710 711echo "--- Done ---" 712 713if [ ${err_cnt} -gt 0 ] ; then 714 exit 1 715fi 716 717if [ ${ok_cnt} -gt 0 ] ; then 718 exit 0 719fi 720 721exit 2 722