Re: [RFC PATCH v3 1/5] perf/trace-dat: Add trace.dat export infrastructure

[email protected] Mon, 03 Aug 2026 15:14:23 +0000
Newsgroups org.kernel.vger.linux-perf-users
Message-ID <[email protected]>
Thank you for your contribution! Sashiko AI review found 5 potential issue(=
s) to consider:
- [High] The `TIME_EXTEND` upper bits are calculated using an incorrect bit=
wise right shift of 5 instead of 27, resulting in corrupted extended timest=
amps.
- [High] The ftrace ring buffer event length encoding for large events inco=
rrectly writes the unaligned payload length, omitting necessary alignment p=
adding.
- [High] Using 32-bit `long` variables and `ftell()`/`fseek()` for file off=
sets truncates trace files larger than 2GB on 32-bit systems.
- [High] Trace data export file headers and ftrace metadata are written in =
host endianness instead of the target trace's endianness, breaking cross-pl=
atform analysis.
- [Medium] Lack of validation for `trace_dat_page_size` can result in a fat=
al integer division by zero exception when calculating page padding.
--

--- Patch [1]: [RFC PATCH v3 1/5] perf/trace-dat: Add trace.dat export infr=
astructure ---
commit 34a2391aa9ca8393dfbdda4a477f39710a409324
Author: Tanushree Shah <[email protected]>

perf/trace-dat: Add trace.dat export infrastructure

This commit adds new utility files to implement infrastructure for
exporting perf.data tracepoints to the trace.dat format. It provides
functions for managing per-cpu event buffers and constructing ftrace
ring buffer pages.

> diff --git a/tools/perf/util/trace-dat.c b/tools/perf/util/trace-dat.c
> new file mode 100644
> index 0000000000000..55a7bd982c6f8
> --- /dev/null
> +++ b/tools/perf/util/trace-dat.c
[ ... ]
> +static int trace_dat__write_page(FILE *fp, unsigned long long base_ts,
> +			char **records, int *rec_sizes, int nr_recs)
> +{
> +	unsigned long long commit =3D 0;
> +	int offset =3D TRACE_DAT_RECORD_HEADER_SIZE;
> +	int i;
> +	char *page;
> +
> +	page =3D calloc(1, trace_dat_page_size);
> +	if (!page)
> +		return -ENOMEM;
> +
> +	for (i =3D 0; i < nr_recs; i++) {
> +		memcpy(page + offset, records[i], rec_sizes[i]);
> +		offset +=3D rec_sizes[i];
> +		commit +=3D rec_sizes[i];
> +	}
> +
> +	memcpy(page, &base_ts, sizeof(base_ts));
> +	memcpy(page + sizeof(base_ts), &commit, sizeof(commit));

[Severity: High]
Does this write the ftrace page header using native host endianness?

If a trace was recorded on a different architecture, copying the integers
directly to the output stream might mix host-endian metadata with
target-endian payloads.

Should this metadata be byte-swapped to match the target architecture
to ensure cross-platform trace analysis works correctly?

> +
> +	if (!fwrite(page, 1, trace_dat_page_size, fp)) {
> +		free(page);
> +		return -EIO;
> +	}
> +	free(page);
> +
> +	return 0;
> +}
[ ... ]
> +static int trace_dat__write_cpu_dat(FILE *fp, int cpu, unsigned long lon=
g *file_offset_out)
> +{
[ ... ]
> +		/* Emit TIME_EXTEND when delta does not fit in 27 bits */
> +		if (time_delta > TRACE_DAT_RECORD_TIME_MASK) {
> +			unsigned int extend_hdr;
> +			unsigned int delta_upper;
> +
> +			extend_size =3D TRACE_DAT_RECORD_TIME_EXTEND_SIZE;
> +			extend =3D calloc(1, extend_size);
> +			if (!extend)
> +				return -ENOMEM;
> +
> +			extend_hdr =3D
> +				((time_delta & TRACE_DAT_RECORD_TIME_MASK) <<
> +				 TRACE_DAT_RECORD_TIME_SHIFT) |
> +				TRACE_DAT_RECORD_TYPE_TIME_EXTEND;
> +			delta_upper =3D time_delta >> TRACE_DAT_RECORD_TIME_SHIFT;

[Severity: High]
Does this shift the upper bits of the time delta by the correct amount?

The code uses TRACE_DAT_RECORD_TIME_SHIFT, which is 5.
This calculates the upper bits as time_delta >> 5 rather than >> 27.
Would this copy the middle 22 bits instead of the upper bits, causing
timestamp corruption for any events that follow a time extend?

> +
> +			memcpy(extend, &extend_hdr, TRACE_DAT_WORD_SIZE);
> +			memcpy(extend + TRACE_DAT_WORD_SIZE, &delta_upper,
> +				TRACE_DAT_WORD_SIZE);
> +
> +			time_delta =3D 0;
> +		}
[ ... ]
> +		hdr_word =3D (time_delta << TRACE_DAT_RECORD_TIME_SHIFT) | type_len;
> +
> +		data_rec =3D calloc(1, data_rec_size);
> +		if (!data_rec) {
> +			free(extend);
> +			ret =3D -ENOMEM;
> +			goto out_free;
> +		}
> +
> +		memcpy(data_rec, &hdr_word, TRACE_DAT_WORD_SIZE);
> +
> +		/* Large events: write actual byte length after header */
> +		if (type_len =3D=3D 0)
> +			memcpy(data_rec + TRACE_DAT_WORD_SIZE, &data_len, TRACE_DAT_WORD_SIZE=
);

[Severity: High]
For large events, is this writing the raw, unaligned data_len into the
payload length field?

The internal offset data_rec_size advances by the padded size, but the
unaligned data_len is written here. If data_len is not a multiple of 4,
a parser reading this file might compute an incorrect total length and
fail to skip the alignment padding.

Could this cause the reader to misread the next event boundary?

> +
> +		memcpy(data_rec + payload_offset, event->raw, data_len);
> +
[ ... ]
> +int trace_dat__write_flyrecord_section(void)
> +{
[ ... ]
> +	section_size_pos =3D ftell(trace_dat_fp);
> +	if (!fwrite(&section_size, sizeof(unsigned long long), 1, trace_dat_fp)=
) {
> +		ret =3D -EIO;
> +		goto cleanup;
> +	}
> +
> +	/* Align to page boundary */
> +	after_header   =3D ftell(trace_dat_fp);
> +	padding_needed =3D (trace_dat_page_size -
> +			 (after_header % trace_dat_page_size)) % trace_dat_page_size;

[Severity: Medium]
Is trace_dat_page_size guaranteed to be non-zero here?

If the caller fails to initialize this global variable or parses a
configuration that sets it to zero, this modulo operation will trigger a
fatal integer division by zero exception.

> +
> +	if (padding_needed > 0) {
> +		pad =3D calloc(1, padding_needed);
> +		if (!pad) {
> +			ret =3D -ENOMEM;
> +			goto cleanup;
> +		}
> +
> +		if (!fwrite(pad, 1, padding_needed, trace_dat_fp)) {
> +			free(pad);
> +			ret =3D -EIO;
> +			goto cleanup;
> +		}
> +		free(pad);
> +	}
> +
> +	/* write per-cpu trace data */
[ ... ]
> diff --git a/tools/perf/util/trace-dat.h b/tools/perf/util/trace-dat.h
> new file mode 100644
> index 0000000000000..9aec37b708d41
> --- /dev/null
> +++ b/tools/perf/util/trace-dat.h
> @@ -0,0 +1,83 @@
[ ... ]
> +extern FILE *trace_dat_fp;
> +extern int trace_dat_page_size;
> +extern int trace_dat_nr_cpus;
> +extern long trace_dat_options_offset;
> +extern long trace_dat_header_info_offset;
> +extern long trace_dat_events_format_offset;
> +extern long trace_dat_ftrace_format_offset;
> +extern long trace_dat_kallsyms_offset;
> +extern long trace_dat_cmdline_offset;
> +extern long trace_dat_next_options_offset;

[Severity: High]
Are these offsets safe for trace files larger than 2GB on 32-bit systems?

Using long for offsets and relying on ftell() limits the maximum supported
file size to 2GB on 32-bit architectures. Since perf.data files can easily
exceed this size, could this result in integer overflow and corrupted offse=
ts?

Should this use standard 64-bit interfaces like u64 and ftello()/fseeko()
instead?

> +
> +/* collect and manage per-cpu tracepoint event buffers */
> +#define TRACE_DAT_RECORD_TYPE_TIME_EXTEND 30
> +#define TRACE_DAT_RECORD_TIME_EXTEND_SIZE 8
> +#define TRACE_DAT_RECORD_TYPE_LEN_MAX 28
[ ... ]

--=20
Sashiko AI review =C2=B7 https://sashiko.dev/#/patchset/20260803145958.2999=
[email protected]?part=3D1