From: sashiko-bot@kernel.org
To: "James Clark" <james.clark@linaro.org>
Cc: linux-perf-users@vger.kernel.org
Subject: Re: [PATCH v3 04/18] perf cs-etm: Use per-CPU queues for per-thread mode
Date: Mon, 02 Nov 2026 12:34:34 +0000 [thread overview]
Message-ID: <sashiko-outbox-193423@kernel.org> (raw)
In-Reply-To: <20261102-james-cs-unformatted-per-thread-fix-v3-4-dbcb0c281b9b@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: missing NULL checks in cs_etm__get_data_block()
- [Medium] perf cs-etm: per-CPU queues break TID matching in cs_etm__process_timeless_queues()
--
commit e38f1bcfae5e4494bb005e6c5cf9aa519d0b1814
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
and re-initializes the trace ID queue's threads in cs_etm__get_data_block()
when the AUX buffer TID changes on a CPU.
> diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
> index 509ec014b2760..f19aa4f787fa3 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]
When two threads run consecutively on the same CPU in per-thread mode, can
replacing tidq->frontend_thread here in cs_etm__get_data_block() clobber the
previous thread before its trailing packets and thread stack are flushed?
At the end of an AUX buffer for thread A, the last CS_ETM_RANGE or
CS_ETM_EXCEPTION packet remains in tidq->prev_packet. When
cs_etm__get_data_block() loads the next AUX buffer on that CPU for thread B,
cs_etm_decoder__reset() queues a CS_ETM_DISCONTINUITY packet into
tidq->packet_queue, and cs_etm__flush_all_stack() is a no-op because pid_fmt
is CS_ETM_PIDFMT_NONE in per-thread mode:
tools/perf/util/cs-etm.c:cs_etm__flush_all_stack() {
...
switch (pid_fmt) {
...
case CS_ETM_PIDFMT_NONE:
default:
break;
}
}
Because cs_etm__get_data_block() immediately overwrites tidq->frontend_thread
with thread B before the frontend processes the queued CS_ETM_DISCONTINUITY
packet, the subsequent call to cs_etm__process_traceid_queue() ->
cs_etm__flush() runs with tidq->frontend_thread already pointing to thread B:
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);
...
}
Wouldn't this attribute thread A's trailing instruction and TRACE_END branch
samples to thread B, read instruction bytes from thread B's memory maps via
cs_etm__copy_insn() -> cs_etm__frontend_mem_access(), and flush thread B's
thread_stack while leaving thread A's thread_stack unflushed?
[Severity: Medium]
As reported by Sashiko on v2 [1], what happens if
cs_etm__etmq_get_traceid_queue() or machine__findnew_thread() returns NULL in
cs_etm__get_data_block()?
In timestamped per-thread mode, cs_etm__process_timestamped_queues() ->
cs_etm__queue_first_cs_timestamp() -> cs_etm__get_data_block() is the first
call site that allocates the trace ID queue for etmq. If memory allocation
fails inside cs_etm__etmq_get_traceid_queue(), tidq is NULL and passing
tidq->decode_thread to thread__tid() will dereference a NULL pointer.
Similarly, if either machine__findnew_thread() call returns NULL when
re-initializing tidq->frontend_thread and tidq->decode_thread,
cs_etm__get_data_block() still returns 1, unlike cs_etm__init_traceid_queue()
which checks both pointers:
tools/perf/util/cs-etm.c:cs_etm__init_traceid_queue() {
...
tidq->frontend_thread = machine__findnew_thread(&etm->session->machines.host, -1,
queue->tid);
tidq->decode_thread = machine__findnew_thread(&etm->session->machines.host, -1,
queue->tid);
if (!tidq->frontend_thread || !tidq->decode_thread)
goto out;
}
Should cs_etm__get_data_block() check for NULL returns from
cs_etm__etmq_get_traceid_queue() and machine__findnew_thread() and return
-ENOMEM on failure?
> +
> 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]
Now that cs_etm__queue_aux_fragment() places per-thread AUX fragments into
per-CPU queues indexed by sample->cpu, how does timeless per-thread decoding
work when PERF_RECORD_EXIT triggers cs_etm__process_timeless_queues()?
In cs_etm__process_event(), PERF_RECORD_EXIT passes the exiting thread's TID
to cs_etm__process_timeless_queues():
tools/perf/util/cs-etm.c:cs_etm__process_event() {
...
case PERF_RECORD_EXIT:
if (etm->per_thread_decoding && etm->timeless_decoding)
return cs_etm__process_timeless_queues(etm,
event->fork.tid);
break;
}
And cs_etm__process_timeless_queues() still assumes each queue belongs to a
single thread matching tidq->frontend_thread:
tools/perf/util/cs-etm.c:cs_etm__process_timeless_queues() {
...
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 once when the
first buffer is queued on a CPU (and leaves queue->tid as 0 on CPUs with no
queued buffers):
1. For every CPU queue that never received an AUX buffer, etmq is non-NULL and
queue->tid is 0, so cs_etm__etmq_get_traceid_queue() allocates a trace ID
queue and creates a bogus TID 0 thread in machines.host.
2. If thread T1 and thread T2 both ran on the same CPU (with T1 queued first),
when T1 exits, thread__tid(tidq->frontend_thread) == T1 matches and
cs_etm__run_per_thread_timeless_decoder() drains every buffer in that CPU's
queue, decoding T2's buffers prematurely before T2's subsequent MMAP or
COMM events have been processed.
3. Conversely, if T2 exits before T1, thread__tid(tidq->frontend_thread) is T1
rather than T2, so T2's buffers on that CPU are not decoded on T2's
PERF_RECORD_EXIT event.
Does timeless per-thread decoding also need to be updated to handle per-CPU
queues shared across multiple threads?
[1] https://lore.kernel.org/r/20260821-james-cs-unformatted-per-thread-fix-v2-4-00c4fd0701b4@linaro.org
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-james-cs-unformatted-per-thread-fix-v3-0-dbcb0c281b9b@linaro.org?part=4
next prev parent reply other threads:[~2026-11-02 12:34 UTC|newest]
Thread overview: 46+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-02 12:15 [PATCH v3 00/18] perf cs-etm: Per-thread mode fixes and snapshot wrap support James Clark
2026-11-02 12:15 ` [PATCH v3 01/18] perf cs-etm: Fix nVHE per-thread decoding James Clark
2026-11-02 12:36 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 02/18] perf cs-etm: Warn for invalid timestamp option James Clark
2026-11-02 12:24 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 03/18] perf cs-etm: Turn on context packet timestamps in per-thread mode James Clark
2026-11-02 12:32 ` sashiko-bot
2026-11-04 11:28 ` Leo Yan
2026-11-02 12:15 ` [PATCH v3 04/18] perf cs-etm: Use per-CPU queues for " James Clark
2026-11-02 12:34 ` sashiko-bot [this message]
2026-11-04 14:24 ` Leo Yan
2026-11-04 15:07 ` James Clark
2026-11-02 12:15 ` [PATCH v3 05/18] perf test: cs-etm: Increase window size in disassembly test James Clark
2026-11-02 12:24 ` sashiko-bot
2026-11-04 16:36 ` Leo Yan
2026-11-04 17:54 ` James Clark
2026-11-02 12:15 ` [PATCH v3 06/18] perf cs-etm: Don't drop previous target for kernel samples James Clark
2026-11-02 12:29 ` sashiko-bot
2026-11-04 16:38 ` Leo Yan
2026-11-02 12:15 ` [PATCH v3 07/18] perf python: Expose sample instruction length James Clark
2026-11-02 12:23 ` sashiko-bot
2026-11-04 16:46 ` Leo Yan
2026-11-02 12:15 ` [PATCH v3 08/18] perf cs-etm: Use instruction length in disassembly script James Clark
2026-11-02 12:24 ` sashiko-bot
2026-11-04 17:06 ` Leo Yan
2026-11-02 12:15 ` [PATCH v3 09/18] perf cs-etm: Increase default timestamp generation period James Clark
2026-11-02 12:24 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 10/18] perf auxtrace: Turn Intel BTS snapshot search into a generic one James Clark
2026-11-02 12:21 ` sashiko-bot
2026-11-04 17:16 ` Leo Yan
2026-11-02 12:15 ` [PATCH v3 11/18] perf arm-spe: Use generic snapshot search James Clark
2026-11-02 12:23 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 12/18] perf auxtrace: intel-pt: Use common snapshot pointer fixup James Clark
2026-11-02 12:25 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 13/18] perf cs-etm: Queue partial AUX records James Clark
2026-11-02 12:32 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 14/18] perf cs-etm: Don't print missing buffers in snapshot mode James Clark
2026-11-02 12:26 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 15/18] perf auxtrace: cs-etm: Capture wrapped snapshots James Clark
2026-11-02 12:28 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 16/18] perf test: Allow infinite named_thread loops James Clark
2026-11-02 12:27 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 17/18] perf test: cs-etm: Add test for per-thread mode James Clark
2026-11-02 12:32 ` sashiko-bot
2026-11-02 12:15 ` [PATCH v3 18/18] perf test: cs-etm: Test multiple per-thread threads James Clark
2026-11-02 12:40 ` 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-193423@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.