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