Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

perf record: Add 8-byte aligned event type PERF_RECORD_COMPRESSED2

The original PERF_RECORD_COMPRESS is not 8-byte aligned, which can cause
asan runtime error:

# Build with asan
$ make -C tools/perf O=/tmp/perf DEBUG=1 EXTRA_CFLAGS="-O0 -g -fno-omit-frame-pointer -fsanitize=undefined"
# Test success with many asan runtime errors:
$ /tmp/perf/perf test "Zstd perf.data compression/decompression" -vv
83: Zstd perf.data compression/decompression:
...
util/session.c:1959:13: runtime error: member access within misaligned address 0x7f69e3f99653 for type 'union perf_event', which requires 13 byte alignment
0x7f69e3f99653: note: pointer points here
d0 3a 50 69 44 00 00 00 00 00 08 00 bb 07 00 00 00 00 00 00 44 00 00 00 00 00 00 00 ff 07 00 00
^
util/session.c:2163:22: runtime error: member access within misaligned address 0x7f69e3f99653 for type 'union perf_event', which requires 8 byte alignment
0x7f69e3f99653: note: pointer points here
d0 3a 50 69 44 00 00 00 00 00 08 00 bb 07 00 00 00 00 00 00 44 00 00 00 00 00 00 00 ff 07 00 00
^
...

Since there is no way to align compressed data in zstd compression, this
patch add a new event type `PERF_RECORD_COMPRESSED2`, which adds a field
`data_size` to specify the actual compressed data size.

The `header.size` contains the total record size, including the padding
at the end to make it 8-byte aligned.

Tested with `Zstd perf.data compression/decompression`

Signed-off-by: Chun-Tse Shao <ctshao@google.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Ben Gainey <ben.gainey@arm.com>
Cc: Christophe Leroy <christophe.leroy@csgroup.eu>
Cc: Ian Rogers <irogers@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Kan Liang <kan.liang@linux.intel.com>
Cc: Leo Yan <leo.yan@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Nick Terrell <terrelln@fb.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://lore.kernel.org/r/20250303183646.327510-1-ctshao@google.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

authored by

Chun-Tse Shao and committed by
Arnaldo Carvalho de Melo
208c0e16 bcfab08d

+64 -13
+1
tools/lib/perf/Documentation/libperf.txt
··· 210 210 struct perf_record_time_conv; 211 211 struct perf_record_header_feature; 212 212 struct perf_record_compressed; 213 + struct perf_record_compressed2; 213 214 -- 214 215 215 216 DESCRIPTION
+12
tools/lib/perf/include/perf/event.h
··· 457 457 char data[]; 458 458 }; 459 459 460 + /* 461 + * `header.size` includes the padding we are going to add while writing the record. 462 + * `data_size` only includes the size of `data[]` itself. 463 + */ 464 + struct perf_record_compressed2 { 465 + struct perf_event_header header; 466 + __u64 data_size; 467 + char data[]; 468 + }; 469 + 460 470 enum perf_user_event_type { /* above any possible kernel type */ 461 471 PERF_RECORD_USER_TYPE_START = 64, 462 472 PERF_RECORD_HEADER_ATTR = 64, ··· 488 478 PERF_RECORD_HEADER_FEATURE = 80, 489 479 PERF_RECORD_COMPRESSED = 81, 490 480 PERF_RECORD_FINISHED_INIT = 82, 481 + PERF_RECORD_COMPRESSED2 = 83, 491 482 PERF_RECORD_HEADER_MAX 492 483 }; 493 484 ··· 529 518 struct perf_record_time_conv time_conv; 530 519 struct perf_record_header_feature feat; 531 520 struct perf_record_compressed pack; 521 + struct perf_record_compressed2 pack2; 532 522 }; 533 523 534 524 #endif /* __LIBPERF_EVENT_H */
+19 -5
tools/perf/Documentation/perf.data-file-format.txt
··· 370 370 u32 mmap_len; 371 371 }; 372 372 373 - Indicates that trace contains records of PERF_RECORD_COMPRESSED type 373 + Indicates that trace contains records of PERF_RECORD_COMPRESSED2 type 374 374 that have perf_events records in compressed form. 375 375 376 376 HEADER_CPU_PMU_CAPS = 28, ··· 602 602 Describes a header feature. These are records used in pipe-mode that 603 603 contain information that otherwise would be in perf.data file's header. 604 604 605 - PERF_RECORD_COMPRESSED = 81, 605 + PERF_RECORD_COMPRESSED = 81, /* deprecated */ 606 + 607 + The header is followed by compressed data frame that can be decompressed 608 + into array of perf trace records. The size of the entire compressed event 609 + record including the header is limited by the max value of header.size. 610 + 611 + It is deprecated and new files should use PERF_RECORD_COMPRESSED2 to gurantee 612 + 8-byte alignment. 606 613 607 614 struct compressed_event { 608 615 struct perf_event_header header; ··· 625 618 regular events, those emitted by the kernel, to support combining guest and 626 619 host records. 627 620 621 + PERF_RECORD_COMPRESSED2 = 83, 628 622 629 - The header is followed by compressed data frame that can be decompressed 630 - into array of perf trace records. The size of the entire compressed event 631 - record including the header is limited by the max value of header.size. 623 + 8-byte aligned version of `PERF_RECORD_COMPRESSED`. `header.size` indicates the 624 + total record size, including padding for 8-byte alignment, and `data_size` 625 + specifies the actual size of the compressed data. 626 + 627 + struct perf_record_compressed2 { 628 + struct perf_event_header header; 629 + __u64 data_size; 630 + char data[]; 631 + }; 632 632 633 633 Event types 634 634
+18 -5
tools/perf/builtin-record.c
··· 650 650 struct record *rec = to; 651 651 652 652 if (record__comp_enabled(rec)) { 653 + struct perf_record_compressed2 *event = map->data; 654 + size_t padding = 0; 655 + u8 pad[8] = {0}; 653 656 ssize_t compressed = zstd_compress(rec->session, map, map->data, 654 657 mmap__mmap_len(map), bf, size); 655 658 656 659 if (compressed < 0) 657 660 return (int)compressed; 658 661 659 - size = compressed; 660 - bf = map->data; 662 + bf = event; 663 + thread->samples++; 664 + 665 + /* 666 + * The record from `zstd_compress` is not 8 bytes aligned, which would cause asan 667 + * error. We make it aligned here. 668 + */ 669 + event->data_size = compressed - sizeof(struct perf_record_compressed2); 670 + event->header.size = PERF_ALIGN(compressed, sizeof(u64)); 671 + padding = event->header.size - compressed; 672 + return record__write(rec, map, bf, compressed) || 673 + record__write(rec, map, &pad, padding); 661 674 } 662 675 663 676 thread->samples++; ··· 1549 1536 1550 1537 static size_t process_comp_header(void *record, size_t increment) 1551 1538 { 1552 - struct perf_record_compressed *event = record; 1539 + struct perf_record_compressed2 *event = record; 1553 1540 size_t size = sizeof(*event); 1554 1541 1555 1542 if (increment) { ··· 1557 1544 return increment; 1558 1545 } 1559 1546 1560 - event->header.type = PERF_RECORD_COMPRESSED; 1547 + event->header.type = PERF_RECORD_COMPRESSED2; 1561 1548 event->header.size = size; 1562 1549 1563 1550 return size; ··· 1567 1554 void *dst, size_t dst_size, void *src, size_t src_size) 1568 1555 { 1569 1556 ssize_t compressed; 1570 - size_t max_record_size = PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_record_compressed) - 1; 1557 + size_t max_record_size = PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_record_compressed2) - 1; 1571 1558 struct zstd_data *zstd_data = &session->zstd_data; 1572 1559 1573 1560 if (map && map->file)
+1
tools/perf/util/event.c
··· 77 77 [PERF_RECORD_HEADER_FEATURE] = "FEATURE", 78 78 [PERF_RECORD_COMPRESSED] = "COMPRESSED", 79 79 [PERF_RECORD_FINISHED_INIT] = "FINISHED_INIT", 80 + [PERF_RECORD_COMPRESSED2] = "COMPRESSED2", 80 81 }; 81 82 82 83 const char *perf_event__name(unsigned int id)
+4 -1
tools/perf/util/session.c
··· 1400 1400 int err; 1401 1401 1402 1402 perf_sample__init(&sample, /*all=*/true); 1403 - if (event->header.type != PERF_RECORD_COMPRESSED || perf_tool__compressed_is_stub(tool)) 1403 + if ((event->header.type != PERF_RECORD_COMPRESSED && 1404 + event->header.type != PERF_RECORD_COMPRESSED2) || 1405 + perf_tool__compressed_is_stub(tool)) 1404 1406 dump_event(session->evlist, event, file_offset, &sample, file_path); 1405 1407 1406 1408 /* These events are processed right away */ ··· 1483 1481 err = tool->feature(session, event); 1484 1482 break; 1485 1483 case PERF_RECORD_COMPRESSED: 1484 + case PERF_RECORD_COMPRESSED2: 1486 1485 err = tool->compressed(session, event, file_offset, file_path); 1487 1486 if (err) 1488 1487 dump_event(session->evlist, event, file_offset, &sample, file_path);
+9 -2
tools/perf/util/tool.c
··· 43 43 decomp->size = decomp_last_rem; 44 44 } 45 45 46 - src = (void *)event + sizeof(struct perf_record_compressed); 47 - src_size = event->pack.header.size - sizeof(struct perf_record_compressed); 46 + if (event->header.type == PERF_RECORD_COMPRESSED) { 47 + src = (void *)event + sizeof(struct perf_record_compressed); 48 + src_size = event->pack.header.size - sizeof(struct perf_record_compressed); 49 + } else if (event->header.type == PERF_RECORD_COMPRESSED2) { 50 + src = (void *)event + sizeof(struct perf_record_compressed2); 51 + src_size = event->pack2.data_size; 52 + } else { 53 + return -1; 54 + } 48 55 49 56 decomp_size = zstd_decompress_stream(session->active_decomp->zstd_decomp, src, src_size, 50 57 &(decomp->data[decomp_last_rem]), decomp_len - decomp_last_rem);