Linux Perf Users
 help / color / mirror / Atom feed
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

  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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox