From: sashiko-bot@kernel.org
To: "James Clark" <james.clark@linaro.org>
Cc: linux-perf-users@vger.kernel.org
Subject: Re: [PATCH v4 04/19] perf cs-etm: Use per-CPU queues for per-thread mode
Date: Thu, 05 Nov 2026 12:33:48 +0000 [thread overview]
Message-ID: <sashiko-outbox-199039@kernel.org> (raw)
In-Reply-To: <20261105-james-cs-unformatted-per-thread-fix-v4-4-7759e0ce2449@linaro.org>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] perf cs-etm: cs_etm__get_data_block() clobbers frontend_thread before flush
- [Medium] perf cs-etm: per-CPU queues break TID matching in cs_etm__process_timeless_queues()
- [Medium] perf cs-etm: missing NULL checks in cs_etm__get_data_block()
--
commit 0f3b8ea285f68a8f46b7c531d58a7ba0f323c483
Author: James Clark <james.clark@linaro.org>
perf cs-etm: Use per-CPU queues for per-thread mode
This commit switches per-thread CoreSight ETM decoding to use per-CPU queues
indexed by the sample CPU instead of a single shared queue, and updates the
trace ID queue threads in cs_etm__get_data_block() when the AUX buffer TID
changes.
> diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
> index 5abdd726c6d22..966261429da61 100644
> --- a/tools/perf/util/cs-etm.c
> +++ b/tools/perf/util/cs-etm.c
[ ... ]
> @@ -2215,6 +2209,29 @@ static int cs_etm__get_data_block(struct cs_etm_queue *etmq)
> */
> cs_etm__flush_all_stack(etmq);
>
> + /*
> + * Per-thread mode still uses a queue for each CPU, but that CPU can run
> + * different threads. When the TID from the AUX record on a CPU changes,
> + * re-initialize the thread using the AUX record/buffer fragment TID so
> + * we can start decoding even if the context ID packet was cropped or
> + * they're disabled.
> + */
> + if (etm->per_thread_decoding) {
> + struct cs_etm_traceid_queue *tidq = cs_etm__etmq_get_traceid_queue(etmq,
> + CS_ETM_PER_THREAD_TRACEID);
> +
> + if (thread__tid(tidq->decode_thread) != etmq->buffer->tid) {
> + thread__zput(tidq->frontend_thread);
> + thread__zput(tidq->decode_thread);
> + tidq->frontend_thread = machine__findnew_thread(
> + &etm->session->machines.host, -1,
> + etmq->buffer->tid);
> + tidq->decode_thread = machine__findnew_thread(
> + &etm->session->machines.host, -1,
> + etmq->buffer->tid);
> + }
> + }
[Severity: Medium]
As reported by Sashiko on v3 [1], does replacing tidq->frontend_thread here in
cs_etm__get_data_block() clobber the previous thread before its pending trace
data is flushed?
When two threads run consecutively on the same CPU in per-thread mode, the
final CS_ETM_RANGE or CS_ETM_EXCEPTION packet from the first thread remains in
tidq->prev_packet at the end of its buffer, and its branches remain on its
thread_stack.
When cs_etm__get_data_block() fetches the next buffer for the second thread,
cs_etm_decoder__reset() queues a CS_ETM_DISCONTINUITY packet into
tidq->packet_queue, and cs_etm__flush_all_stack() does nothing because pid_fmt
is CS_ETM_PIDFMT_NONE:
tools/perf/util/cs-etm.c:cs_etm__flush_all_stack() {
...
switch (pid_fmt) {
case CS_ETM_PIDFMT_CTXTID2:
...
case CS_ETM_PIDFMT_CTXTID:
cs_etm__flush_machine_stack(etmq, HOST_KERNEL_ID);
break;
case CS_ETM_PIDFMT_NONE:
default:
break;
}
}
Overwriting tidq->frontend_thread immediately in cs_etm__get_data_block()
means that when cs_etm__process_traceid_queue() later processes the queued
CS_ETM_DISCONTINUITY packet and calls cs_etm__flush(), it runs against the new
thread instead of the previous one:
tools/perf/util/cs-etm.c:cs_etm__flush() {
...
err = cs_etm__synth_last_instruction_sample(etmq, tidq);
if (err)
return err;
err = cs_etm__synth_branch_sample(etmq, tidq);
if (err)
return err;
swap_packet:
cs_etm__packet_swap(etm, tidq);
/* Reset last branches after flush the trace */
if (etm->use_thread_stack)
thread_stack__flush(tidq->frontend_thread);
...
}
Won't this attribute the previous thread's trailing instruction and trace-end
branch samples to the new thread, read instruction bytes from the new thread's
address space, and flush the new thread's thread_stack while leaving the old
thread's thread_stack unflushed?
[Severity: Medium]
As reported by Sashiko on v3 [2], can cs_etm__get_data_block() dereference a
NULL pointer if cs_etm__etmq_get_traceid_queue() or machine__findnew_thread()
fails?
In timestamped per-thread mode, cs_etm__queue_first_cs_timestamp() calls
cs_etm__get_data_block() before the trace ID queue has been created, so
cs_etm__etmq_get_traceid_queue() allocates tidq here and can return NULL on
allocation failure. Passing tidq->decode_thread to thread__tid() without a
NULL check on tidq will crash.
Similarly, cs_etm__init_traceid_queue() checks that both
tidq->frontend_thread and tidq->decode_thread are non-NULL after calling
machine__findnew_thread(), whereas cs_etm__get_data_block() leaves them NULL
and returns 1 on failure. Should both tidq and the new thread pointers be
checked for NULL here?
> +
> return 1;
> }
>
> @@ -3288,7 +3305,9 @@ static int cs_etm__queue_aux_fragment(struct perf_session *session, off_t file_o
>
> if (aux_offset >= auxtrace_event->offset &&
> aux_offset + aux_size <= auxtrace_event->offset + auxtrace_event->size) {
> - struct cs_etm_queue *etmq = cs_etm__get_queue(etm, auxtrace_event->cpu);
> + /* Old per-thread files have no CPU (before v6.4) */
> + int queue_idx = sample->cpu == (u32)-1 ? 0 : sample->cpu;
> + struct cs_etm_queue *etmq = cs_etm__get_queue(etm, queue_idx);
>
> if (!etmq)
> return -EINVAL;
> @@ -3301,6 +3320,7 @@ static int cs_etm__queue_aux_fragment(struct perf_session *session, off_t file_o
> auxtrace_fragment.auxtrace.size = aux_size;
> auxtrace_fragment.auxtrace.offset = aux_offset;
> auxtrace_fragment.auxtrace.idx = etmq->queue_nr;
> + auxtrace_fragment.auxtrace.cpu = etmq->queue_nr;
[Severity: Medium]
As reported by Sashiko on v3 [3], how does queuing per-thread AUX fragments
into per-CPU queues interact with timeless decoding in
cs_etm__process_timeless_queues()?
When PERF_RECORD_EXIT is processed in timeless per-thread mode,
cs_etm__process_event() calls cs_etm__process_timeless_queues() with the
exiting thread's TID:
tools/perf/util/cs-etm.c:cs_etm__process_timeless_queues() {
...
for (i = 0; i < etm->queues.nr_queues; i++) {
...
if (etm->per_thread_decoding) {
tidq = cs_etm__etmq_get_traceid_queue(
etmq, CS_ETM_PER_THREAD_TRACEID);
if (!tidq)
continue;
if (tid == -1 || thread__tid(tidq->frontend_thread) == tid)
cs_etm__run_per_thread_timeless_decoder(etmq);
} else
cs_etm__run_per_cpu_timeless_decoder(etmq);
}
...
}
Because auxtrace_queues__queue_buffer() only sets queue->tid on the first
buffer queued to a given CPU queue, and cs_etm__run_per_thread_timeless_decoder()
drains all buffers in that CPU queue in a loop:
1. If thread A and thread B both ran on the same CPU (with thread A first),
when thread A exits, thread__tid(tidq->frontend_thread) == tid matches and
cs_etm__run_per_thread_timeless_decoder() will drain and decode thread B's
buffers on that CPU before thread B exits and before its subsequent MMAP or
COMM events have been processed.
2. Conversely, if thread B exits before thread A, tidq->frontend_thread still
holds thread A's TID, so thread B's buffers on that CPU will not be decoded
when thread B exits.
3. For any CPU queue that received no AUX buffers, queue->tid remains 0, so
cs_etm__etmq_get_traceid_queue() will allocate a traceid_queue and create a
spurious TID 0 thread in machines.host.
Does timeless per-thread decoding need to avoid draining buffers belonging to
other threads when a single thread exits?
[1] https://lore.kernel.org/r/20261102-james-cs-unformatted-per-thread-fix-v3-4-dbcb0c281b9b@linaro.org
[2] https://lore.kernel.org/r/20261102-james-cs-unformatted-per-thread-fix-v3-4-dbcb0c281b9b@linaro.org
[3] https://lore.kernel.org/r/20261102-james-cs-unformatted-per-thread-fix-v3-4-dbcb0c281b9b@linaro.org
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105-james-cs-unformatted-per-thread-fix-v4-0-7759e0ce2449@linaro.org?part=4
next prev parent reply other threads:[~2026-11-05 12:33 UTC|newest]
Thread overview: 40+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-05 11:44 [PATCH v4 00/19] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
2026-11-05 11:44 ` [PATCH v4 01/19] perf cs-etm: Fix nVHE per-thread decoding James Clark
2026-11-05 12:00 ` sashiko-bot
2026-11-05 11:44 ` [PATCH v4 02/19] perf cs-etm: Warn for invalid timestamp option James Clark
2026-11-05 12:05 ` sashiko-bot
2026-11-05 11:44 ` [PATCH v4 03/19] perf cs-etm: Turn on context packet timestamps in per-thread mode James Clark
2026-11-05 12:17 ` sashiko-bot
2026-11-05 11:44 ` [PATCH v4 04/19] perf cs-etm: Use per-CPU queues for " James Clark
2026-11-05 12:33 ` sashiko-bot [this message]
2026-11-05 11:44 ` [PATCH v4 05/19] perf test: cs-etm: Increase window size in disassembly test James Clark
2026-11-05 12:35 ` sashiko-bot
2026-11-05 11:44 ` [PATCH v4 06/19] perf cs-etm: Don't drop previous target for kernel samples James Clark
2026-11-05 12:51 ` sashiko-bot
2026-11-05 11:44 ` [PATCH v4 07/19] perf python: Expose sample instruction length James Clark
2026-11-05 12:54 ` sashiko-bot
2026-11-05 11:44 ` [PATCH v4 08/19] perf cs-etm: Use instruction length in disassembly script James Clark
2026-11-05 12:58 ` sashiko-bot
2026-11-05 11:44 ` [PATCH v4 09/19] perf cs-etm: Increase default timestamp generation period James Clark
2026-11-05 13:02 ` sashiko-bot
2026-11-05 11:44 ` [PATCH v4 10/19] perf auxtrace: Turn Intel BTS snapshot search into a generic one James Clark
2026-11-05 13:05 ` sashiko-bot
2026-11-05 11:44 ` [PATCH v4 11/19] perf arm-spe: Use generic snapshot search James Clark
2026-11-05 13:09 ` sashiko-bot
2026-11-05 11:45 ` [PATCH v4 12/19] perf auxtrace: intel-pt: Use common snapshot pointer fixup James Clark
2026-11-05 13:12 ` sashiko-bot
2026-11-05 11:45 ` [PATCH v4 13/19] perf cs-etm: Queue partial AUX records James Clark
2026-11-05 13:23 ` sashiko-bot
2026-11-05 11:45 ` [PATCH v4 14/19] perf cs-etm: Don't print missing buffers in snapshot mode James Clark
2026-11-05 13:25 ` sashiko-bot
2026-11-05 11:45 ` [PATCH v4 15/19] perf auxtrace: cs-etm: Capture wrapped snapshots James Clark
2026-11-05 13:30 ` sashiko-bot
2026-11-05 11:45 ` [PATCH v4 16/19] perf test: Allow infinite named_thread loops James Clark
2026-11-05 13:31 ` sashiko-bot
2026-11-05 11:45 ` [PATCH v4 17/19] perf test: cs-etm: Add test for per-thread mode James Clark
2026-11-05 13:44 ` sashiko-bot
2026-11-05 11:45 ` [PATCH v4 18/19] perf test: cs-etm: Test multiple per-thread threads James Clark
2026-11-05 14:00 ` sashiko-bot
2026-11-05 11:45 ` [PATCH v4 19/19] perf test: cs-etm: Increase snapshot size James Clark
2026-11-05 11:56 ` Leo Yan
2026-11-05 14:02 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-199039@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=james.clark@linaro.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.