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: 37+ 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-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-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-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-02 12:15 ` [PATCH v3 07/18] perf python: Expose sample instruction length James Clark
2026-11-02 12:23 ` sashiko-bot
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-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-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox