]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
perf record: Add 8-byte aligned event type PERF_RECORD_COMPRESSED2
authorChun-Tse Shao <ctshao@google.com>
Mon, 3 Mar 2025 18:32:40 +0000 (10:32 -0800)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Fri, 16 May 2025 20:31:40 +0000 (17:31 -0300)
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>
tools/lib/perf/Documentation/libperf.txt
tools/lib/perf/include/perf/event.h
tools/perf/Documentation/perf.data-file-format.txt
tools/perf/builtin-record.c
tools/perf/util/event.c
tools/perf/util/session.c
tools/perf/util/tool.c

index 59aabdd3cabff19c9a4835d9d20a74c6087d9a06..4072bc9b7670d2131585b8cb54101bfc487d0807 100644 (file)
@@ -210,6 +210,7 @@ SYNOPSIS
   struct perf_record_time_conv;
   struct perf_record_header_feature;
   struct perf_record_compressed;
+  struct perf_record_compressed2;
 --
 
 DESCRIPTION
index 37bb7771d9143466e99b431f9943a3fe1e31bbf7..09b7c643ddac1f1cd6876862e1bfec659f8bc7bc 100644 (file)
@@ -457,6 +457,16 @@ struct perf_record_compressed {
        char                     data[];
 };
 
+/*
+ * `header.size` includes the padding we are going to add while writing the record.
+ * `data_size` only includes the size of `data[]` itself.
+ */
+struct perf_record_compressed2 {
+       struct perf_event_header header;
+       __u64                    data_size;
+       char                     data[];
+};
+
 enum perf_user_event_type { /* above any possible kernel type */
        PERF_RECORD_USER_TYPE_START             = 64,
        PERF_RECORD_HEADER_ATTR                 = 64,
@@ -478,6 +488,7 @@ enum perf_user_event_type { /* above any possible kernel type */
        PERF_RECORD_HEADER_FEATURE              = 80,
        PERF_RECORD_COMPRESSED                  = 81,
        PERF_RECORD_FINISHED_INIT               = 82,
+       PERF_RECORD_COMPRESSED2                 = 83,
        PERF_RECORD_HEADER_MAX
 };
 
@@ -518,6 +529,7 @@ union perf_event {
        struct perf_record_time_conv            time_conv;
        struct perf_record_header_feature       feat;
        struct perf_record_compressed           pack;
+       struct perf_record_compressed2          pack2;
 };
 
 #endif /* __LIBPERF_EVENT_H */
index 010a4edcd3849257e989edd8d7681a22f661a0ec..cd95ba09f7270717e1e3ea2234e8f67b256847c3 100644 (file)
@@ -370,7 +370,7 @@ struct {
        u32     mmap_len;
 };
 
-Indicates that trace contains records of PERF_RECORD_COMPRESSED type
+Indicates that trace contains records of PERF_RECORD_COMPRESSED2 type
 that have perf_events records in compressed form.
 
        HEADER_CPU_PMU_CAPS = 28,
@@ -602,7 +602,14 @@ struct auxtrace_error_event {
 Describes a header feature. These are records used in pipe-mode that
 contain information that otherwise would be in perf.data file's header.
 
-       PERF_RECORD_COMPRESSED                  = 81,
+       PERF_RECORD_COMPRESSED                  = 81, /* deprecated */
+
+The header is followed by compressed data frame that can be decompressed
+into array of perf trace records. The size of the entire compressed event
+record including the header is limited by the max value of header.size.
+
+It is deprecated and new files should use PERF_RECORD_COMPRESSED2 to gurantee
+8-byte alignment.
 
 struct compressed_event {
        struct perf_event_header        header;
@@ -618,10 +625,17 @@ This is used, for instance, to 'perf inject' events after init and before
 regular events, those emitted by the kernel, to support combining guest and
 host records.
 
+       PERF_RECORD_COMPRESSED2                 = 83,
 
-The header is followed by compressed data frame that can be decompressed
-into array of perf trace records. The size of the entire compressed event
-record including the header is limited by the max value of header.size.
+8-byte aligned version of `PERF_RECORD_COMPRESSED`. `header.size` indicates the
+total record size, including padding for 8-byte alignment, and `data_size`
+specifies the actual size of the compressed data.
+
+struct perf_record_compressed2 {
+       struct perf_event_header        header;
+       __u64                           data_size;
+       char                            data[];
+};
 
 Event types
 
index 94d9c97af7b79a2ccaa44e03b2e749c56875452a..136c0172799a0980ddcedc5948dbc4f35e2f4e20 100644 (file)
@@ -650,14 +650,27 @@ static int record__pushfn(struct mmap *map, void *to, void *bf, size_t size)
        struct record *rec = to;
 
        if (record__comp_enabled(rec)) {
+               struct perf_record_compressed2 *event = map->data;
+               size_t padding = 0;
+               u8 pad[8] = {0};
                ssize_t compressed = zstd_compress(rec->session, map, map->data,
                                                   mmap__mmap_len(map), bf, size);
 
                if (compressed < 0)
                        return (int)compressed;
 
-               size = compressed;
-               bf   = map->data;
+               bf = event;
+               thread->samples++;
+
+               /*
+                * The record from `zstd_compress` is not 8 bytes aligned, which would cause asan
+                * error. We make it aligned here.
+                */
+               event->data_size = compressed - sizeof(struct perf_record_compressed2);
+               event->header.size = PERF_ALIGN(compressed, sizeof(u64));
+               padding = event->header.size - compressed;
+               return record__write(rec, map, bf, compressed) ||
+                      record__write(rec, map, &pad, padding);
        }
 
        thread->samples++;
@@ -1536,7 +1549,7 @@ static void record__adjust_affinity(struct record *rec, struct mmap *map)
 
 static size_t process_comp_header(void *record, size_t increment)
 {
-       struct perf_record_compressed *event = record;
+       struct perf_record_compressed2 *event = record;
        size_t size = sizeof(*event);
 
        if (increment) {
@@ -1544,7 +1557,7 @@ static size_t process_comp_header(void *record, size_t increment)
                return increment;
        }
 
-       event->header.type = PERF_RECORD_COMPRESSED;
+       event->header.type = PERF_RECORD_COMPRESSED2;
        event->header.size = size;
 
        return size;
@@ -1554,7 +1567,7 @@ static ssize_t zstd_compress(struct perf_session *session, struct mmap *map,
                            void *dst, size_t dst_size, void *src, size_t src_size)
 {
        ssize_t compressed;
-       size_t max_record_size = PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_record_compressed) - 1;
+       size_t max_record_size = PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_record_compressed2) - 1;
        struct zstd_data *zstd_data = &session->zstd_data;
 
        if (map && map->file)
index c23b77f8f854ad21bede4063b2c4173c54cad869..80c9ea682413f6c3b796e8a3e05038f41fccad20 100644 (file)
@@ -77,6 +77,7 @@ static const char *perf_event__names[] = {
        [PERF_RECORD_HEADER_FEATURE]            = "FEATURE",
        [PERF_RECORD_COMPRESSED]                = "COMPRESSED",
        [PERF_RECORD_FINISHED_INIT]             = "FINISHED_INIT",
+       [PERF_RECORD_COMPRESSED2]               = "COMPRESSED2",
 };
 
 const char *perf_event__name(unsigned int id)
index 81cc56503a2d0f51d87a6905937098c7f9ebb28a..a320672c264e78ac221e279f57f553b04d940246 100644 (file)
@@ -1400,7 +1400,9 @@ static s64 perf_session__process_user_event(struct perf_session *session,
        int err;
 
        perf_sample__init(&sample, /*all=*/true);
-       if (event->header.type != PERF_RECORD_COMPRESSED || perf_tool__compressed_is_stub(tool))
+       if ((event->header.type != PERF_RECORD_COMPRESSED &&
+            event->header.type != PERF_RECORD_COMPRESSED2) ||
+           perf_tool__compressed_is_stub(tool))
                dump_event(session->evlist, event, file_offset, &sample, file_path);
 
        /* These events are processed right away */
@@ -1481,6 +1483,7 @@ static s64 perf_session__process_user_event(struct perf_session *session,
                err = tool->feature(session, event);
                break;
        case PERF_RECORD_COMPRESSED:
+       case PERF_RECORD_COMPRESSED2:
                err = tool->compressed(session, event, file_offset, file_path);
                if (err)
                        dump_event(session->evlist, event, file_offset, &sample, file_path);
index 3b7f390f26eb427d039219186e254b9804e67623..37bd8ac63b0117abd72a29c4ede74a4d644b02f7 100644 (file)
@@ -43,8 +43,15 @@ static int perf_session__process_compressed_event(struct perf_session *session,
                decomp->size = decomp_last_rem;
        }
 
-       src = (void *)event + sizeof(struct perf_record_compressed);
-       src_size = event->pack.header.size - sizeof(struct perf_record_compressed);
+       if (event->header.type == PERF_RECORD_COMPRESSED) {
+               src = (void *)event + sizeof(struct perf_record_compressed);
+               src_size = event->pack.header.size - sizeof(struct perf_record_compressed);
+       } else if (event->header.type == PERF_RECORD_COMPRESSED2) {
+               src = (void *)event + sizeof(struct perf_record_compressed2);
+               src_size = event->pack2.data_size;
+       } else {
+               return -1;
+       }
 
        decomp_size = zstd_decompress_stream(session->active_decomp->zstd_decomp, src, src_size,
                                &(decomp->data[decomp_last_rem]), decomp_len - decomp_last_rem);