@@ -22,6 +22,7 @@ outfile="${temp_dir}/test-out.txt"
2222errfile=" ${temp_dir} /test-err.txt"
2323workload=" ${temp_dir} /workload"
2424awkscript=" ${temp_dir} /awkscript"
25+ jitdump_workload=" ${temp_dir} /jitdump_workload"
2526
2627cleanup ()
2728{
@@ -50,6 +51,13 @@ perf_record_no_decode()
5051 perf record -B -N --no-bpf-event " $@ "
5152}
5253
54+ # perf record for testing should not need BPF events
55+ perf_record_no_bpf ()
56+ {
57+ # Options for no BPF events
58+ perf record --no-bpf-event " $@ "
59+ }
60+
5361have_workload=false
5462cat << _end_of_file_ | /usr/bin/cc -o "${workload} " -xc - -pthread && have_workload=true
5563#include <time.h>
@@ -269,6 +277,159 @@ test_per_thread()
269277 return 0
270278}
271279
280+ test_jitdump ()
281+ {
282+ echo " --- Test tracing self-modifying code that uses jitdump ---"
283+
284+ script_path=$( realpath " $0 " )
285+ script_dir=$( dirname " $script_path " )
286+ jitdump_incl_dir=" ${script_dir} /../../util"
287+ jitdump_h=" ${jitdump_incl_dir} /jitdump.h"
288+
289+ if [ ! -e " ${jitdump_h} " ] ; then
290+ echo " SKIP: Include file jitdump.h not found"
291+ return 2
292+ fi
293+
294+ if [ -z " ${have_jitdump_workload} " ] ; then
295+ have_jitdump_workload=false
296+ # Create a workload that uses self-modifying code and generates its own jitdump file
297+ cat << - "_end_of_file_ " | /usr/bin/cc -o "${jitdump_workload}" -I "${jitdump_incl_dir}" -xc - -pthread && have_jitdump_workload=true
298+ #define _GNU_SOURCE
299+ #include <sys/mman.h>
300+ #include <sys/types.h>
301+ #include <stddef.h>
302+ #include <stdio.h>
303+ #include <stdint.h>
304+ #include <unistd.h>
305+ #include <string.h>
306+
307+ #include "jitdump.h"
308+
309+ #define CHK_BYTE 0x5a
310+
311+ static inline uint64_t rdtsc(void)
312+ {
313+ unsigned int low, high;
314+
315+ asm volatile("rdtsc" : "=a" (low), "=d" (high));
316+
317+ return low | ((uint64_t)high) << 32;
318+ }
319+
320+ static FILE *open_jitdump(void)
321+ {
322+ struct jitheader header = {
323+ .magic = JITHEADER_MAGIC,
324+ .version = JITHEADER_VERSION,
325+ .total_size = sizeof(header),
326+ .pid = getpid(),
327+ .timestamp = rdtsc(),
328+ .flags = JITDUMP_FLAGS_ARCH_TIMESTAMP,
329+ };
330+ char filename[256];
331+ FILE *f;
332+ void *m;
333+
334+ snprintf(filename, sizeof(filename), "jit-%d.dump", getpid());
335+ f = fopen(filename, "w+");
336+ if (!f)
337+ goto err;
338+ /* Create an MMAP event for the jitdump file. That is how perf tool finds it. */
339+ m = mmap(0, 4096, PROT_READ | PROT_EXEC, MAP_PRIVATE, fileno(f), 0);
340+ if (m == MAP_FAILED)
341+ goto err_close;
342+ munmap(m, 4096);
343+ if (fwrite(&header,sizeof(header),1,f) != 1)
344+ goto err_close;
345+ return f;
346+
347+ err_close:
348+ fclose(f);
349+ err:
350+ return NULL;
351+ }
352+
353+ static int write_jitdump(FILE *f, void *addr, const uint8_t *dat, size_t sz, uint64_t *idx)
354+ {
355+ struct jr_code_load rec = {
356+ .p.id = JIT_CODE_LOAD,
357+ .p.total_size = sizeof(rec) + sz,
358+ .p.timestamp = rdtsc(),
359+ .pid = getpid(),
360+ .tid = gettid(),
361+ .vma = (unsigned long)addr,
362+ .code_addr = (unsigned long)addr,
363+ .code_size = sz,
364+ .code_index = ++*idx,
365+ };
366+
367+ if (fwrite(&rec,sizeof(rec),1,f) != 1 ||
368+ fwrite(dat, sz, 1, f) != 1)
369+ return -1;
370+ return 0;
371+ }
372+
373+ static void close_jitdump(FILE *f)
374+ {
375+ fclose(f);
376+ }
377+
378+ int main()
379+ {
380+ /* Get a memory page to store executable code */
381+ void *addr = mmap(0, 4096, PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
382+ /* Code to execute: mov CHK_BYTE, %eax ; ret */
383+ uint8_t dat[] = {0xb8, CHK_BYTE, 0x00, 0x00, 0x00, 0xc3};
384+ FILE *f = open_jitdump();
385+ uint64_t idx = 0;
386+ int ret = 1;
387+
388+ if (!f)
389+ return 1;
390+ /* Copy executable code to executable memory page */
391+ memcpy(addr, dat, sizeof(dat));
392+ /* Record it in the jitdump file */
393+ if (write_jitdump(f, addr, dat, sizeof(dat), &idx))
394+ goto out_close;
395+ /* Call it */
396+ ret = ((int (*)(void))addr)() - CHK_BYTE;
397+ out_close:
398+ close_jitdump(f);
399+ return ret;
400+ }
401+ _end_of_file_
402+ fi
403+
404+ if ! $have_jitdump_workload ; then
405+ echo " SKIP: No jitdump workload"
406+ return 2
407+ fi
408+
409+ # Change to temp_dir so jitdump collateral files go there
410+ cd " ${temp_dir} "
411+ perf_record_no_bpf -o " ${tmpfile} " -e intel_pt//u " ${jitdump_workload} "
412+ perf inject -i " ${tmpfile} " -o " ${perfdatafile} " --jit
413+ decode_br_cnt=$( perf script -i " ${perfdatafile} " --itrace=b | wc -l)
414+ # Note that overflow and lost errors are suppressed for the error count
415+ decode_err_cnt=$( perf script -i " ${perfdatafile} " --itrace=e-o-l | grep -ci error)
416+ cd -
417+ # Should be thousands of branches
418+ if [ " ${decode_br_cnt} " -lt 1000 ] ; then
419+ echo " Decode failed, only ${decode_br_cnt} branches"
420+ return 1
421+ fi
422+ # Should be no errors
423+ if [ " ${decode_err_cnt} " -ne 0 ] ; then
424+ echo " Decode failed, ${decode_err_cnt} errors"
425+ perf script -i " ${perfdatafile} " --itrace=e-o-l
426+ return 1
427+ fi
428+
429+ echo OK
430+ return 0
431+ }
432+
272433count_result ()
273434{
274435 if [ " $1 " -eq 2 ] ; then
@@ -286,6 +447,7 @@ ret=0
286447test_system_wide_side_band || ret=$? ; count_result $ret ; ret=0
287448test_per_thread " " " " || ret=$? ; count_result $ret ; ret=0
288449test_per_thread " k" " (incl. kernel) " || ret=$? ; count_result $ret ; ret=0
450+ test_jitdump || ret=$? ; count_result $ret ; ret=0
289451
290452cleanup
291453
0 commit comments