mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 00/14] perf cs-etm: Fix bogus branch samples on exceptions
@ 2026-09-23 15:21 Leo Yan
  2026-09-23 15:21 ` [PATCH v2 01/14] perf sample: Allow decoders to supply branch return addresses Leo Yan
                   ` (13 more replies)
  0 siblings, 14 replies; 17+ messages in thread
From: Leo Yan @ 2026-09-23 15:21 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, Leo Yan

CoreSight currently models exception entry by changing the preceding
instruction range into a taken branch. This can give an IRQ the source
PC of an instruction that already retired and overwrite a real branch
immediately before the exception.

For an untaken B.LS followed by an IRQ, perf script currently reports:

  hw int  4000f8 => ffff800080010c80  b.ls #0x400118
  ...
  iret    ffff800080012284 => 4000fc  eret

The hardware trace supplies 0x4000fc as the preferred return address.
For an IRQ, this is the architectural resume PC (Arm ARM, R_VBQMV).
B.LS has retired, and the saved PC identifies the boundary before the
following MOV. Using this PC as the IRQ source reflects the
architectural state at exception entry:

  hw int  4000fc => ffff800080010c80  movz x2, #0x1796
  ...
  iret    ffff800080012284 => 4000fc  eret

The IRQ sample represents the transfer from this architectural execution
position to the handler. The series synthesizes exception entries from
exception packets, preserving the preceding branch and its outcome.

The supporting changes:

- Let decoders supply sample.ret_addr so later instruction fetching cannot
  change thread-stack return addresses. Apply this to Intel PT
  asynchronous samples as well.
- Prepare packet ISA and instruction-size handling and share sample
  synthesis helpers. Mark untaken branches and break history when
  instruction memory is unavailable.
- Add a thread-stack regression test and four AArch64 CoreSight tests.
  IRQs and page faults must resume at the entry PC; SVC and emulated MRS
  must resume four bytes later.

Based on the AI search and test on my x86 machine, this matches perf's
Intel PT handling of IRQs. Intel PT records the next instruction's IP in
the FUP packet, and Perf uses that IP as the interrupt sample's source.

This series is verified on Orion6 board with "perf test coresight".

Signed-off-by: Leo Yan <leo.yan@arm.com>
---
Changes in v2:

- Rework the fix around exception packets to preserve both exception
  entries and preceding branches.
- Add explicit return addresses and the Intel PT asynchronous-branch fix.
- Split out packet/synthesis preparation, record not-taken branches and
  handle unreadable instruction memory.
- Add thread-stack regression coverage and four CoreSight tests.
- Link to v1: https://lore.kernel.org/r/20260713-perf_cs_etm_fix_non_taken-v1-0-4561607fc69f@arm.com

---
Leo Yan (14):
      perf sample: Allow decoders to supply branch return addresses
      perf intel-pt: Preserve return addresses for asynchronous branches
      perf cs-etm: Break branch history when instruction memory is unavailable
      perf cs-etm: Centralize packet ISA initialization
      perf cs-etm: Use the recorded instruction size for A32 and A64
      perf cs-etm: Mark branches that were not taken
      perf cs-etm: Factor out final instruction sample synthesis
      perf cs-etm: Centralize branch sample synthesis checks
      perf cs-etm: Classify exception calls using the exception packet
      perf cs-etm: Synthesize exception entries separately from branches
      perf tests: Check CoreSight IRQ entry and exit
      perf tests: Check CoreSight syscall entry and exit
      perf tests: Check CoreSight abort entry and exit
      perf tests: Check CoreSight emulated instruction entry and exit

 tools/perf/tests/Build                             |   1 +
 tools/perf/tests/builtin-test.c                    |   3 +
 .../perf/tests/shell/coresight/abort_entry_exit.sh |  21 ++
 tools/perf/tests/shell/coresight/irq_entry_exit.sh |  37 ++++
 .../tests/shell/coresight/syscall_entry_exit.sh    |  21 ++
 .../perf/tests/shell/coresight/trap_entry_exit.sh  |  24 +++
 tools/perf/tests/shell/lib/coresight_exception.sh  | 165 +++++++++++++++
 tools/perf/tests/tests.h                           |   3 +
 tools/perf/tests/thread-stack.c                    | 106 ++++++++++
 tools/perf/tests/workloads/Build                   |   4 +
 tools/perf/tests/workloads/branch_not_taken_loop.c |  33 +++
 tools/perf/tests/workloads/page_fault_loop.c       |  37 ++++
 tools/perf/util/cs-etm-decoder/cs-etm-decoder.c    | 106 +++++++---
 tools/perf/util/cs-etm.c                           | 222 ++++++++++-----------
 tools/perf/util/cs-etm.h                           |   2 +
 tools/perf/util/intel-pt.c                         |   7 +
 tools/perf/util/sample.c                           |   1 +
 tools/perf/util/sample.h                           |   5 +
 tools/perf/util/thread-stack.c                     |   5 +-
 19 files changed, 656 insertions(+), 147 deletions(-)
---
base-commit: edd8a9fe2eca009599e013a29c421c7a6b5ad1b9
change-id: 20260713-perf_cs_etm_fix_non_taken-5b4d7f73f41e

Best regards,
-- 
Leo Yan <leo.yan@arm.com>


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 01/14] perf sample: Allow decoders to supply branch return addresses
  2026-09-23 15:21 [PATCH v2 00/14] perf cs-etm: Fix bogus branch samples on exceptions Leo Yan
@ 2026-09-23 15:21 ` Leo Yan
  2026-09-24 13:00   ` Adrian Hunter
  2026-09-23 15:21 ` [PATCH v2 02/14] perf intel-pt: Preserve return addresses for asynchronous branches Leo Yan
                   ` (12 subsequent siblings)
  13 siblings, 1 reply; 17+ messages in thread
From: Leo Yan @ 2026-09-23 15:21 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, Leo Yan

The thread stack derives return addresses from IP + insn_len. For an
interrupt or fault, the return address can instead be the sample IP even
when instruction bytes at that address are available.

Add ret_addr to perf_sample and prefer it in thread_stack__trace_end()
and thread_stack__process() when supplied. Initialize it to zero so other
samples retain the IP + insn_len calculation.

Add a regression test for explicit exception return addresses and the
ordinary call fallback when the return address is zero.

Assisted-by: Codex:gpt-6
Signed-off-by: Leo Yan <leo.yan@arm.com>
---
 tools/perf/tests/Build          |   1 +
 tools/perf/tests/builtin-test.c |   1 +
 tools/perf/tests/tests.h        |   1 +
 tools/perf/tests/thread-stack.c | 106 ++++++++++++++++++++++++++++++++++++++++
 tools/perf/util/sample.c        |   1 +
 tools/perf/util/sample.h        |   5 ++
 tools/perf/util/thread-stack.c  |   5 +-
 7 files changed, 118 insertions(+), 2 deletions(-)

diff --git a/tools/perf/tests/Build b/tools/perf/tests/Build
index 81c311b131b72715f11b6501511a18dce2af07df..d03ae938dd0ad92f78f8de6900ce7161f39fc3bc 100644
--- a/tools/perf/tests/Build
+++ b/tools/perf/tests/Build
@@ -29,6 +29,7 @@ perf-test-y += task-exit.o
 perf-test-y += sw-clock.o
 perf-test-y += mmap-thread-lookup.o
 perf-test-y += thread-maps-share.o
+perf-test-y += thread-stack.o
 perf-test-$(CONFIG_LIBTRACEEVENT) += switch-tracking.o
 perf-test-y += keep-tracking.o
 perf-test-y += code-reading.o
diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
index d2f594921e25bda9fc662e7ba82464bfef1b752b..6259ed805c5f75799d52ac742b77cf278f98bd9d 100644
--- a/tools/perf/tests/builtin-test.c
+++ b/tools/perf/tests/builtin-test.c
@@ -112,6 +112,7 @@ static struct test_suite *generic_tests[] = {
 	&suite__hists_filter,
 	&suite__mmap_thread_lookup,
 	&suite__thread_maps_share,
+	&suite__thread_stack,
 	&suite__hists_output,
 	&suite__hists_cumulate,
 #ifdef HAVE_LIBTRACEEVENT
diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h
index 9c96f33483d1435644da6068c3802f7a914a9de0..b2520a564417b61718f1dcb23da1f8cc0f601906 100644
--- a/tools/perf/tests/tests.h
+++ b/tools/perf/tests/tests.h
@@ -138,6 +138,7 @@ DECLARE_SUITE(expr);
 DECLARE_SUITE(hists_filter);
 DECLARE_SUITE(mmap_thread_lookup);
 DECLARE_SUITE(thread_maps_share);
+DECLARE_SUITE(thread_stack);
 DECLARE_SUITE(hists_output);
 DECLARE_SUITE(hists_cumulate);
 DECLARE_SUITE(switch_tracking);
diff --git a/tools/perf/tests/thread-stack.c b/tools/perf/tests/thread-stack.c
new file mode 100644
index 0000000000000000000000000000000000000000..0239e3bee34faf8fd4ddc7b723cf479609760064
--- /dev/null
+++ b/tools/perf/tests/thread-stack.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <unistd.h>
+#include "tests.h"
+#include "util/addr_location.h"
+#include "util/event.h"
+#include "util/sample.h"
+#include "util/thread.h"
+#include "util/thread-stack.h"
+
+#define CALL_REF		1234UL
+#define RET_REF			5678UL
+
+struct return_check {
+	unsigned int matched;
+	unsigned int unmatched;
+};
+
+static int check_call_return(struct call_return *cr,
+			     u64 *parent_db_id __maybe_unused, void *data)
+{
+	struct return_check *check = data;
+
+	if (cr->call_ref == CALL_REF && cr->return_ref == RET_REF && !cr->flags)
+		check->matched++;
+	else
+		check->unmatched++;
+
+	return 0;
+}
+
+/* A zero expected_ret_addr asks the stack to use ip + insn_len. */
+static int check_return_address(u64 expected_ret_addr, u64 actual_ret_addr,
+				u32 flags)
+{
+	struct call_return_processor *crp;
+	struct return_check check = { };
+	struct thread *thread;
+	struct addr_location from = { }, to = { };
+	struct perf_sample sample = { };
+	int ret = TEST_FAIL;
+
+	thread = thread__new(getpid(), getpid());
+	if (!thread)
+		return TEST_FAIL;
+
+	crp = call_return_processor__new(check_call_return, &check);
+	if (!crp)
+		goto out;
+
+	sample.ip = 0x1000;		/* Call or exception source addr */
+	sample.addr = 0x2000;		/* Callee or exception handler addr */
+	sample.ret_addr = expected_ret_addr;
+	sample.flags = PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL | flags;
+	sample.time = 1;
+	/* Model the opcode length after an instruction fetch. */
+	sample.insn_len = 4;
+	if (thread_stack__process(thread, thread__comm(thread), &sample,
+				  &from, &to, CALL_REF, crp))
+		goto out;
+
+	sample.ip = 0x2000;		/* Return instruction addr */
+	sample.addr = actual_ret_addr;	/* Return branch target addr */
+	sample.ret_addr = 0;
+	sample.flags = PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_RETURN |
+		       (flags & (PERF_IP_FLAG_INTERRUPT | PERF_IP_FLAG_SYSCALLRET));
+	sample.time = 2;
+	if (thread_stack__process(thread, thread__comm(thread), &sample,
+				  &to, &from, RET_REF, crp))
+		goto out;
+
+	if (check.matched == 1 && !check.unmatched)
+		ret = TEST_OK;
+
+out:
+	thread__put(thread);
+	call_return_processor__free(crp);
+	return ret;
+}
+
+static int test__thread_stack(struct test_suite *test __maybe_unused,
+			      int subtest __maybe_unused)
+{
+	static const struct {
+		const char *name;
+		u64 expected_ret_addr;
+		u64 actual_ret_addr;
+		u32 flags;
+	} cases[] = {
+		{ "ordinary call", 0, 0x1004, 0 },
+		{ "interrupt", 0x1000, 0x1000, PERF_IP_FLAG_ASYNC | PERF_IP_FLAG_INTERRUPT },
+		{ "fault or trap", 0x1000, 0x1000, PERF_IP_FLAG_INTERRUPT },
+		{ "SVC", 0x1004, 0x1004, PERF_IP_FLAG_SYSCALLRET },
+	};
+
+	for (size_t i = 0; i < ARRAY_SIZE(cases); i++) {
+		if (check_return_address(cases[i].expected_ret_addr,
+					 cases[i].actual_ret_addr, cases[i].flags)) {
+			pr_debug("Incorrect return address for %s\n", cases[i].name);
+			return TEST_FAIL;
+		}
+	}
+	return TEST_OK;
+}
+
+DEFINE_SUITE("Thread stack return addresses after instruction fetching", thread_stack);
diff --git a/tools/perf/util/sample.c b/tools/perf/util/sample.c
index bccc19e2aaf25118a8ecde88473aa6cb16fa561a..4abb689132bc27d0f2318eeafca20f7e57621837 100644
--- a/tools/perf/util/sample.c
+++ b/tools/perf/util/sample.c
@@ -29,6 +29,7 @@ void perf_sample__init(struct perf_sample *sample, bool all)
 		sample->intr_regs = NULL;
 		sample->merged_callchain = false;
 		sample->callchain = NULL;
+		sample->ret_addr = 0;
 	}
 }
 
diff --git a/tools/perf/util/sample.h b/tools/perf/util/sample.h
index cb4b16654876e9a5c3bfd66be3e0235ac15795e0..865ed18200f1339f5949ef3f185ed57a34aace72 100644
--- a/tools/perf/util/sample.h
+++ b/tools/perf/util/sample.h
@@ -131,6 +131,11 @@ struct perf_sample {
 	u64 time;
 	/** @addr: The sample event PERF_SAMPLE_ADDR value. */
 	u64 addr;
+	/**
+	 * @ret_addr: Return address supplied by the decoder for a branch sample.
+	 *            Zero means use ip + insn_len.
+	 */
+	u64 ret_addr;
 	/** @id: The sample event PERF_SAMPLE_ID or PERF_SAMPLE_IDENTIFIER value. */
 	u64 id;
 	/** @stream_id: The sample event PERF_SAMPLE_STREAM_ID value. */
diff --git a/tools/perf/util/thread-stack.c b/tools/perf/util/thread-stack.c
index 1360f44421ef8bb80fe5cfdba59be7e6029b0240..d452d1a7eabb16f040ca3dada23e6fdec8fb1088 100644
--- a/tools/perf/util/thread-stack.c
+++ b/tools/perf/util/thread-stack.c
@@ -1030,7 +1030,7 @@ static int thread_stack__trace_end(struct thread_stack *ts,
 	cp = call_path__findnew(cpr, ts->stack[ts->cnt - 1].cp, NULL, 0,
 				ts->kernel_start);
 
-	ret_addr = sample->ip + sample->insn_len;
+	ret_addr = sample->ret_addr ? sample->ret_addr : sample->ip + sample->insn_len;
 
 	return thread_stack__push_cp(ts, ret_addr, sample->time, ref, cp,
 				     false, true);
@@ -1154,7 +1154,8 @@ int thread_stack__process(struct thread *thread, struct comm *comm,
 		if (!sample->ip || !sample->addr)
 			return 0;
 
-		ret_addr = sample->ip + sample->insn_len;
+		/* Opcode fetching must not change a decoder-supplied return address. */
+		ret_addr = sample->ret_addr ? sample->ret_addr : sample->ip + sample->insn_len;
 		if (ret_addr == sample->addr)
 			return 0; /* Zero-length calls are excluded */
 

-- 
2.34.1


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 02/14] perf intel-pt: Preserve return addresses for asynchronous branches
  2026-09-23 15:21 [PATCH v2 00/14] perf cs-etm: Fix bogus branch samples on exceptions Leo Yan
  2026-09-23 15:21 ` [PATCH v2 01/14] perf sample: Allow decoders to supply branch return addresses Leo Yan
@ 2026-09-23 15:21 ` Leo Yan
  2026-09-24 13:00   ` Adrian Hunter
  2026-09-23 15:21 ` [PATCH v2 03/14] perf cs-etm: Break branch history when instruction memory is unavailable Leo Yan
                   ` (11 subsequent siblings)
  13 siblings, 1 reply; 17+ messages in thread
From: Leo Yan @ 2026-09-23 15:21 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, Leo Yan

Intel PT uses a zero instruction length for asynchronous branches so
the thread stack resumes at the source IP. A dlfilter can fetch the
instruction before call/return processing and replace that zero with
its opcode length, advancing the saved return address.

Set sample.ret_addr to sample.ip for asynchronous branch samples so
instruction fetching cannot change the return address. This also
covers asynchronous trace-end samples and VM exits.

Assisted-by: Codex:gpt-6
Signed-off-by: Leo Yan <leo.yan@arm.com>
---
 tools/perf/util/intel-pt.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/tools/perf/util/intel-pt.c b/tools/perf/util/intel-pt.c
index 8c21c9f52d572d2dd4e15b3b5c341b253ccea1b5..e8b9c8a67b0a0635e0110eda4b6ccb2511132fc7 100644
--- a/tools/perf/util/intel-pt.c
+++ b/tools/perf/util/intel-pt.c
@@ -1803,6 +1803,13 @@ static int intel_pt_synth_branch_sample(struct intel_pt_queue *ptq)
 	perf_sample__init(&sample, /*all=*/true);
 	intel_pt_prep_b_sample(pt, ptq, event, &sample);
 
+	/*
+	 * For asynchronous branches, use the sample IP as the return address
+	 * instead of advancing it during instruction decoding.
+	 */
+	if (sample.flags & PERF_IP_FLAG_ASYNC)
+		sample.ret_addr = sample.ip;
+
 	sample.id = ptq->pt->branches_id;
 	sample.stream_id = ptq->pt->branches_id;
 

-- 
2.34.1


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 03/14] perf cs-etm: Break branch history when instruction memory is unavailable
  2026-09-23 15:21 [PATCH v2 00/14] perf cs-etm: Fix bogus branch samples on exceptions Leo Yan
  2026-09-23 15:21 ` [PATCH v2 01/14] perf sample: Allow decoders to supply branch return addresses Leo Yan
  2026-09-23 15:21 ` [PATCH v2 02/14] perf intel-pt: Preserve return addresses for asynchronous branches Leo Yan
@ 2026-09-23 15:21 ` Leo Yan
  2026-09-23 15:21 ` [PATCH v2 04/14] perf cs-etm: Centralize packet ISA initialization Leo Yan
                   ` (10 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: Leo Yan @ 2026-09-23 15:21 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, Leo Yan

The decoder currently ignores OCSD_GEN_TRC_ELEM_ADDR_NACC, allowing a
subsequent range to be paired with stale branch history from before the
unreadable code.

Treat an inaccessible instruction address as a discontinuity and flush
the pending range and branch history before processing subsequent
elements.

Assisted-by: Codex:gpt-6
Signed-off-by: Leo Yan <leo.yan@arm.com>
---
 tools/perf/util/cs-etm-decoder/cs-etm-decoder.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c
index 26940f1f1b0bf44c8671d80020597c672c361c1a..35ca1a23e12735416d0d412a949595efc5bfe50a 100644
--- a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c
+++ b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c
@@ -594,7 +594,8 @@ static ocsd_datapath_resp_t cs_etm_decoder__gen_trace_elem_printer(
 
 	if (type == OCSD_GEN_TRC_ELEM_EO_TRACE ||
 	    type == OCSD_GEN_TRC_ELEM_NO_SYNC ||
-	    type == OCSD_GEN_TRC_ELEM_TRACE_ON)
+	    type == OCSD_GEN_TRC_ELEM_TRACE_ON ||
+	    type == OCSD_GEN_TRC_ELEM_ADDR_NACC)
 		resp = cs_etm_decoder__buffer_discontinuity(etmq, packet_queue,
 							    trace_chan_id);
 	else if (type == OCSD_GEN_TRC_ELEM_INSTR_RANGE)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 04/14] perf cs-etm: Centralize packet ISA initialization
  2026-09-23 15:21 [PATCH v2 00/14] perf cs-etm: Fix bogus branch samples on exceptions Leo Yan
                   ` (2 preceding siblings ...)
  2026-09-23 15:21 ` [PATCH v2 03/14] perf cs-etm: Break branch history when instruction memory is unavailable Leo Yan
@ 2026-09-23 15:21 ` Leo Yan
  2026-09-23 15:21 ` [PATCH v2 05/14] perf cs-etm: Use the recorded instruction size for A32 and A64 Leo Yan
                   ` (9 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: Leo Yan @ 2026-09-23 15:21 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, Leo Yan

Exception packets need their own ISA so the frontend can generate samples
directly from them. Currently, ISA initialization is limited to instruction
range packets.

Move ISA initialization into cs_etm_decoder__buffer_packet() and use a
common helper to obtain it from the OpenCSD element. Populate the ISA for
instruction ranges, context updates and exceptions, leaving it unknown
for other element types.

Assisted-by: Codex:gpt-6
Signed-off-by: Leo Yan <leo.yan@arm.com>
---
 tools/perf/util/cs-etm-decoder/cs-etm-decoder.c | 69 +++++++++++++++----------
 1 file changed, 41 insertions(+), 28 deletions(-)

diff --git a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c
index 35ca1a23e12735416d0d412a949595efc5bfe50a..469aba5e596a791289c5bcece132aba7b0baf6c3 100644
--- a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c
+++ b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c
@@ -368,9 +368,38 @@ cs_etm_decoder__reset_timestamp(struct cs_etm_packet_queue *packet_queue)
 	packet_queue->instr_count = 0;
 }
 
+static enum cs_etm_isa
+cs_etm_decoder__get_isa(const ocsd_generic_trace_elem *elem)
+{
+	/*
+	 * OpenCSD preserves ISA and context between output elements, including
+	 * exceptions after a context change with no intervening range.
+	 */
+	if (elem->elem_type != OCSD_GEN_TRC_ELEM_PE_CONTEXT &&
+	    elem->elem_type != OCSD_GEN_TRC_ELEM_INSTR_RANGE &&
+	    elem->elem_type != OCSD_GEN_TRC_ELEM_EXCEPTION)
+		return CS_ETM_ISA_UNKNOWN;
+
+	switch (elem->isa) {
+	case ocsd_isa_aarch64:
+		return CS_ETM_ISA_A64;
+	case ocsd_isa_arm:
+		return CS_ETM_ISA_A32;
+	case ocsd_isa_thumb2:
+		return CS_ETM_ISA_T32;
+	case ocsd_isa_tee:
+	case ocsd_isa_jazelle:
+	case ocsd_isa_custom:
+	case ocsd_isa_unknown:
+	default:
+		return CS_ETM_ISA_UNKNOWN;
+	}
+}
+
 static ocsd_datapath_resp_t
 cs_etm_decoder__buffer_packet(struct cs_etm_queue *etmq,
 			      struct cs_etm_packet_queue *packet_queue,
+			      const ocsd_generic_trace_elem *elem,
 			      const u8 trace_chan_id,
 			      enum cs_etm_sample_type sample_type)
 {
@@ -389,7 +418,7 @@ cs_etm_decoder__buffer_packet(struct cs_etm_queue *etmq,
 	packet_queue->packet_count++;
 
 	packet_queue->packet_buffer[et].sample_type = sample_type;
-	packet_queue->packet_buffer[et].isa = CS_ETM_ISA_UNKNOWN;
+	packet_queue->packet_buffer[et].isa = cs_etm_decoder__get_isa(elem);
 	packet_queue->packet_buffer[et].cpu = cpu;
 	packet_queue->packet_buffer[et].start_addr = CS_ETM_INVAL_ADDR;
 	packet_queue->packet_buffer[et].end_addr = CS_ETM_INVAL_ADDR;
@@ -420,31 +449,13 @@ cs_etm_decoder__buffer_range(struct cs_etm_queue *etmq,
 	int ret = 0;
 	struct cs_etm_packet *packet;
 
-	ret = cs_etm_decoder__buffer_packet(etmq, packet_queue, trace_chan_id,
-					    CS_ETM_RANGE);
+	ret = cs_etm_decoder__buffer_packet(etmq, packet_queue, elem,
+					    trace_chan_id, CS_ETM_RANGE);
 	if (ret != OCSD_RESP_CONT && ret != OCSD_RESP_WAIT)
 		return ret;
 
 	packet = &packet_queue->packet_buffer[packet_queue->tail];
 
-	switch (elem->isa) {
-	case ocsd_isa_aarch64:
-		packet->isa = CS_ETM_ISA_A64;
-		break;
-	case ocsd_isa_arm:
-		packet->isa = CS_ETM_ISA_A32;
-		break;
-	case ocsd_isa_thumb2:
-		packet->isa = CS_ETM_ISA_T32;
-		break;
-	case ocsd_isa_tee:
-	case ocsd_isa_jazelle:
-	case ocsd_isa_custom:
-	case ocsd_isa_unknown:
-	default:
-		packet->isa = CS_ETM_ISA_UNKNOWN;
-	}
-
 	packet->start_addr = elem->st_addr;
 	packet->end_addr = elem->en_addr;
 	packet->instr_count = elem->num_instr_range;
@@ -483,6 +494,7 @@ cs_etm_decoder__buffer_range(struct cs_etm_queue *etmq,
 static ocsd_datapath_resp_t
 cs_etm_decoder__buffer_discontinuity(struct cs_etm_queue *etmq,
 				     struct cs_etm_packet_queue *queue,
+				     const ocsd_generic_trace_elem *elem,
 				     const uint8_t trace_chan_id)
 {
 	/*
@@ -490,7 +502,7 @@ cs_etm_decoder__buffer_discontinuity(struct cs_etm_queue *etmq,
 	 * reset time statistics.
 	 */
 	cs_etm_decoder__reset_timestamp(queue);
-	return cs_etm_decoder__buffer_packet(etmq, queue, trace_chan_id,
+	return cs_etm_decoder__buffer_packet(etmq, queue, elem, trace_chan_id,
 					     CS_ETM_DISCONTINUITY);
 }
 
@@ -502,7 +514,7 @@ cs_etm_decoder__buffer_exception(struct cs_etm_queue *etmq,
 {	int ret = 0;
 	struct cs_etm_packet *packet;
 
-	ret = cs_etm_decoder__buffer_packet(etmq, queue, trace_chan_id,
+	ret = cs_etm_decoder__buffer_packet(etmq, queue, elem, trace_chan_id,
 					    CS_ETM_EXCEPTION);
 	if (ret != OCSD_RESP_CONT && ret != OCSD_RESP_WAIT)
 		return ret;
@@ -516,9 +528,10 @@ cs_etm_decoder__buffer_exception(struct cs_etm_queue *etmq,
 static ocsd_datapath_resp_t
 cs_etm_decoder__buffer_exception_ret(struct cs_etm_queue *etmq,
 				     struct cs_etm_packet_queue *queue,
+				     const ocsd_generic_trace_elem *elem,
 				     const uint8_t trace_chan_id)
 {
-	return cs_etm_decoder__buffer_packet(etmq, queue, trace_chan_id,
+	return cs_etm_decoder__buffer_packet(etmq, queue, elem, trace_chan_id,
 					     CS_ETM_EXCEPTION_RET);
 }
 
@@ -555,8 +568,8 @@ cs_etm_decoder__set_tid(struct cs_etm_queue *etmq,
 				elem->context.exception_level, tid))
 		return OCSD_RESP_FATAL_SYS_ERR;
 
-	ret = cs_etm_decoder__buffer_packet(etmq, packet_queue, trace_chan_id,
-					    CS_ETM_CONTEXT);
+	ret = cs_etm_decoder__buffer_packet(etmq, packet_queue, elem,
+					    trace_chan_id, CS_ETM_CONTEXT);
 	if (ret != OCSD_RESP_CONT && ret != OCSD_RESP_WAIT)
 		return ret;
 
@@ -597,7 +610,7 @@ static ocsd_datapath_resp_t cs_etm_decoder__gen_trace_elem_printer(
 	    type == OCSD_GEN_TRC_ELEM_TRACE_ON ||
 	    type == OCSD_GEN_TRC_ELEM_ADDR_NACC)
 		resp = cs_etm_decoder__buffer_discontinuity(etmq, packet_queue,
-							    trace_chan_id);
+							    elem, trace_chan_id);
 	else if (type == OCSD_GEN_TRC_ELEM_INSTR_RANGE)
 		resp = cs_etm_decoder__buffer_range(etmq, packet_queue, elem,
 						    trace_chan_id);
@@ -606,7 +619,7 @@ static ocsd_datapath_resp_t cs_etm_decoder__gen_trace_elem_printer(
 							trace_chan_id);
 	else if (type == OCSD_GEN_TRC_ELEM_EXCEPTION_RET)
 		resp = cs_etm_decoder__buffer_exception_ret(etmq, packet_queue,
-							    trace_chan_id);
+							    elem, trace_chan_id);
 	else if (type == OCSD_GEN_TRC_ELEM_TIMESTAMP)
 		resp = cs_etm_decoder__do_hard_timestamp(etmq, elem,
 							 trace_chan_id,

-- 
2.34.1


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 05/14] perf cs-etm: Use the recorded instruction size for A32 and A64
  2026-09-23 15:21 [PATCH v2 00/14] perf cs-etm: Fix bogus branch samples on exceptions Leo Yan
                   ` (3 preceding siblings ...)
  2026-09-23 15:21 ` [PATCH v2 04/14] perf cs-etm: Centralize packet ISA initialization Leo Yan
@ 2026-09-23 15:21 ` Leo Yan
  2026-09-23 15:21 ` [PATCH v2 06/14] perf cs-etm: Mark branches that were not taken Leo Yan
                   ` (8 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: Leo Yan @ 2026-09-23 15:21 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, Leo Yan

Use packet->last_instr_size in cs_etm__instr_size() instead of a
hard-coded value, allowing the decoder to supply the instruction size.

Keep per-address decoding for T32. A range can mix 16-bit and 32-bit
instructions, so last_instr_size describes only the final instruction
and cannot determine the size at an arbitrary address in the range.

Assisted-by: Codex:gpt-6
Signed-off-by: Leo Yan <leo.yan@arm.com>
---
 tools/perf/util/cs-etm.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index 2d1ab34f7b6b5a6331005c16c6a418d123c815eb..0163d93ac2d537a2190536e77d24628b4d707751 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -1358,8 +1358,7 @@ static inline int cs_etm__instr_size(struct cs_etm_queue *etmq,
 	if (packet->isa == CS_ETM_ISA_T32)
 		return cs_etm__t32_instr_size(etmq, tidq, packet, addr);
 
-	/* Otherwise, 4-byte instruction size for A32/A64 */
-	return 4;
+	return packet->last_instr_size;
 }
 
 static inline u64 cs_etm__first_executed_instr(struct cs_etm_packet *packet)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 06/14] perf cs-etm: Mark branches that were not taken
  2026-09-23 15:21 [PATCH v2 00/14] perf cs-etm: Fix bogus branch samples on exceptions Leo Yan
                   ` (4 preceding siblings ...)
  2026-09-23 15:21 ` [PATCH v2 05/14] perf cs-etm: Use the recorded instruction size for A32 and A64 Leo Yan
@ 2026-09-23 15:21 ` Leo Yan
  2026-09-23 15:21 ` [PATCH v2 07/14] perf cs-etm: Factor out final instruction sample synthesis Leo Yan
                   ` (7 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: Leo Yan @ 2026-09-23 15:21 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, Leo Yan

An exception can transfer execution to the kernel after any instruction.
When this creates a trace boundary, perf may attribute the trace-end
sample to the last instruction in the range. If that instruction is an
untaken branch, reporting only its branch type can make the transfer look
like a taken branch.

Set PERF_IP_FLAG_NOT_TAKEN for branch ranges when
last_instr_taken_branch is false. This records the decoded branch
outcome independently of exception entry.

For a discontinuity without an exception element, perf script previously
reported an untaken B.LS as:

  tr end  jcc             4000f8 => 0       b.ls #0x400118
  tr strt jmp                  0 => 4000fc

With the not-taken flag set:

  tr end  jcc/not_taken/  4000f8 => 0       b.ls #0x400118
  tr strt jmp                  0 => 4000fc

Here, 4000f8 remains the last traced instruction and 4000fc is the trace
restart address. These boundary samples do not establish an IRQ entry or
an exception return; the not_taken flag records only the branch outcome.

Assisted-by: Codex:gpt-6
Signed-off-by: Leo Yan <leo.yan@arm.com>
---
 tools/perf/util/cs-etm.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index 0163d93ac2d537a2190536e77d24628b4d707751..c54218aeef53e8420447e9a2bee6e98d8225e335 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -2415,6 +2415,11 @@ static int cs_etm__set_sample_flags(struct cs_etm_queue *etmq,
 			packet->flags = PERF_IP_FLAG_BRANCH |
 					PERF_IP_FLAG_RETURN;
 
+		/* Report branch that is not taken */
+		if ((packet->flags & PERF_IP_FLAG_BRANCH) &&
+		    !packet->last_instr_taken_branch)
+			packet->flags |= PERF_IP_FLAG_NOT_TAKEN;
+
 		/*
 		 * Decoder might insert a discontinuity in the middle of
 		 * instruction packets, fixup prev_packet with flag

-- 
2.34.1


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 07/14] perf cs-etm: Factor out final instruction sample synthesis
  2026-09-23 15:21 [PATCH v2 00/14] perf cs-etm: Fix bogus branch samples on exceptions Leo Yan
                   ` (5 preceding siblings ...)
  2026-09-23 15:21 ` [PATCH v2 06/14] perf cs-etm: Mark branches that were not taken Leo Yan
@ 2026-09-23 15:21 ` Leo Yan
  2026-09-23 15:21 ` [PATCH v2 08/14] perf cs-etm: Centralize branch sample synthesis checks Leo Yan
                   ` (6 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: Leo Yan @ 2026-09-23 15:21 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, Leo Yan

cs_etm__flush() and cs_etm__end_block() duplicate the instruction sample
used to report the remaining branch history at the end of a trace.

Move this code into cs_etm__synth_last_instruction_sample(), preserving
the sample generation and instruction period reset.

Assisted-by: Codex:gpt-6
Signed-off-by: Leo Yan <leo.yan@arm.com>
---
 tools/perf/util/cs-etm.c | 71 ++++++++++++++++++------------------------------
 1 file changed, 26 insertions(+), 45 deletions(-)

diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index c54218aeef53e8420447e9a2bee6e98d8225e335..5b5ee52c0277f5ff8f03c2908e4af1d7801845ff 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -1685,6 +1685,28 @@ static int cs_etm__synth_instruction_sample(struct cs_etm_queue *etmq,
 	return ret;
 }
 
+static int cs_etm__synth_last_instruction_sample(struct cs_etm_queue *etmq,
+						 struct cs_etm_traceid_queue *tidq)
+{
+	struct cs_etm_packet *packet = tidq->prev_packet;
+	int ret;
+
+	if (!etmq->etm->synth_opts.last_branch ||
+	    !etmq->etm->synth_opts.instructions)
+		return 0;
+
+	if (packet->sample_type != CS_ETM_RANGE)
+		return 0;
+
+	ret = cs_etm__synth_instruction_sample(etmq, tidq, packet,
+					       cs_etm__last_executed_instr(packet),
+					       tidq->period_instructions);
+	if (!ret)
+		tidq->period_instructions = 0;
+
+	return ret;
+}
+
 /*
  * The cs etm packet encodes an instruction range between a branch target
  * and the next taken branch. Generate sample accordingly.
@@ -2023,28 +2045,9 @@ static int cs_etm__flush(struct cs_etm_queue *etmq,
 	if (tidq->prev_packet->sample_type == CS_ETM_EMPTY)
 		goto swap_packet;
 
-	if (etmq->etm->synth_opts.last_branch &&
-	    etmq->etm->synth_opts.instructions &&
-	    tidq->prev_packet->sample_type == CS_ETM_RANGE) {
-		u64 addr;
-		/*
-		 * Generate a last branch event for the branches left in the
-		 * circular buffer at the end of the trace.
-		 *
-		 * Use the address of the end of the last reported execution
-		 * range
-		 */
-		addr = cs_etm__last_executed_instr(tidq->prev_packet);
-
-		err = cs_etm__synth_instruction_sample(
-			etmq, tidq, tidq->prev_packet, addr,
-			tidq->period_instructions);
-		if (err)
-			return err;
-
-		tidq->period_instructions = 0;
-
-	}
+	err = cs_etm__synth_last_instruction_sample(etmq, tidq);
+	if (err)
+		return err;
 
 	if (etm->synth_opts.branches &&
 	    tidq->prev_packet->sample_type == CS_ETM_RANGE) {
@@ -2066,8 +2069,6 @@ static int cs_etm__flush(struct cs_etm_queue *etmq,
 static int cs_etm__end_block(struct cs_etm_queue *etmq,
 			     struct cs_etm_traceid_queue *tidq)
 {
-	int err;
-
 	/*
 	 * It has no new packet coming and 'etmq->packet' contains the stale
 	 * packet which was set at the previous time with packets swapping;
@@ -2077,27 +2078,7 @@ static int cs_etm__end_block(struct cs_etm_queue *etmq,
 	 * event for the branches left in the circular buffer at the end of
 	 * the trace.
 	 */
-	if (etmq->etm->synth_opts.last_branch &&
-	    etmq->etm->synth_opts.instructions &&
-	    tidq->prev_packet->sample_type == CS_ETM_RANGE) {
-		u64 addr;
-
-		/*
-		 * Use the address of the end of the last reported execution
-		 * range.
-		 */
-		addr = cs_etm__last_executed_instr(tidq->prev_packet);
-
-		err = cs_etm__synth_instruction_sample(
-			etmq, tidq, tidq->prev_packet, addr,
-			tidq->period_instructions);
-		if (err)
-			return err;
-
-		tidq->period_instructions = 0;
-	}
-
-	return 0;
+	return cs_etm__synth_last_instruction_sample(etmq, tidq);
 }
 
 static int cs_etm__flush_stack_cb(struct thread *thread,

-- 
2.34.1


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 08/14] perf cs-etm: Centralize branch sample synthesis checks
  2026-09-23 15:21 [PATCH v2 00/14] perf cs-etm: Fix bogus branch samples on exceptions Leo Yan
                   ` (6 preceding siblings ...)
  2026-09-23 15:21 ` [PATCH v2 07/14] perf cs-etm: Factor out final instruction sample synthesis Leo Yan
@ 2026-09-23 15:21 ` Leo Yan
  2026-09-23 15:21 ` [PATCH v2 09/14] perf cs-etm: Classify exception calls using the exception packet Leo Yan
                   ` (5 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: Leo Yan @ 2026-09-23 15:21 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, Leo Yan

Move the branch-synthesis option and packet checks from cs_etm__sample()
into cs_etm__synth_branch_sample(), alongside the existing branch filter.
Call the helper directly from cs_etm__sample() and cs_etm__flush().

Accept trace-start and trace-end flags, this preserves boundary samples
without a taken branch.

Assisted-by: Codex:gpt-6
Signed-off-by: Leo Yan <leo.yan@arm.com>
---
 tools/perf/util/cs-etm.c | 37 ++++++++++++++-----------------------
 1 file changed, 14 insertions(+), 23 deletions(-)

diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index 5b5ee52c0277f5ff8f03c2908e4af1d7801845ff..c8a92b5dba6001329d68766d540b9b7ead90e9e6 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -1726,6 +1726,14 @@ static int cs_etm__synth_branch_sample(struct cs_etm_queue *etmq,
 	} dummy_bs;
 	u64 ip;
 
+	if (!etm->synth_opts.branches)
+		return 0;
+
+	if (!cs_etm__packet_has_taken_branch(tidq->prev_packet) &&
+	    !(tidq->prev_packet->flags & (PERF_IP_FLAG_TRACE_BEGIN |
+					  PERF_IP_FLAG_TRACE_END)))
+		return 0;
+
 	if (etm->branches_filter &&
 		!(etm->branches_filter & tidq->prev_packet->flags))
 		return 0;
@@ -1965,23 +1973,9 @@ static int cs_etm__sample(struct cs_etm_queue *etmq,
 		}
 	}
 
-	if (etm->synth_opts.branches) {
-		bool generate_sample = false;
-
-		/* Generate sample for tracing on packet */
-		if (tidq->prev_packet->sample_type == CS_ETM_DISCONTINUITY)
-			generate_sample = true;
-
-		/* Generate sample for branch taken packet */
-		if (cs_etm__packet_has_taken_branch(tidq->prev_packet))
-			generate_sample = true;
-
-		if (generate_sample) {
-			ret = cs_etm__synth_branch_sample(etmq, tidq);
-			if (ret)
-				return ret;
-		}
-	}
+	ret = cs_etm__synth_branch_sample(etmq, tidq);
+	if (ret)
+		return ret;
 
 	cs_etm__packet_swap(etm, tidq);
 
@@ -2049,12 +2043,9 @@ static int cs_etm__flush(struct cs_etm_queue *etmq,
 	if (err)
 		return err;
 
-	if (etm->synth_opts.branches &&
-	    tidq->prev_packet->sample_type == CS_ETM_RANGE) {
-		err = cs_etm__synth_branch_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);

-- 
2.34.1


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 09/14] perf cs-etm: Classify exception calls using the exception packet
  2026-09-23 15:21 [PATCH v2 00/14] perf cs-etm: Fix bogus branch samples on exceptions Leo Yan
                   ` (7 preceding siblings ...)
  2026-09-23 15:21 ` [PATCH v2 08/14] perf cs-etm: Centralize branch sample synthesis checks Leo Yan
@ 2026-09-23 15:21 ` Leo Yan
  2026-09-23 15:21 ` [PATCH v2 10/14] perf cs-etm: Synthesize exception entries separately from branches Leo Yan
                   ` (4 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: Leo Yan @ 2026-09-23 15:21 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, Leo Yan

ETMv4 and ETE use the same exception number for SVC, HVC and SMC.
Identify SVC by inspecting the instruction before the preferred return
address in the exception packet.

Preserve the exception level and preferred return address from OpenCSD,
and use them with the packet's ISA in cs_etm__is_syscall() and
cs_etm__is_sync_exception(). This makes exception call classification
independent of the preceding instruction range.

Assisted-by: Codex:gpt-6
Signed-off-by: Leo Yan <leo.yan@arm.com>
---
 tools/perf/util/cs-etm-decoder/cs-etm-decoder.c |  7 ++++++-
 tools/perf/util/cs-etm.c                        | 10 +++-------
 tools/perf/util/cs-etm.h                        |  1 +
 3 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c
index 469aba5e596a791289c5bcece132aba7b0baf6c3..48e8c355a3e495cc30dcb4056c04a034d10ce860 100644
--- a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c
+++ b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c
@@ -511,7 +511,8 @@ cs_etm_decoder__buffer_exception(struct cs_etm_queue *etmq,
 				 struct cs_etm_packet_queue *queue,
 				 const ocsd_generic_trace_elem *elem,
 				 const uint8_t trace_chan_id)
-{	int ret = 0;
+{
+	int ret = 0;
 	struct cs_etm_packet *packet;
 
 	ret = cs_etm_decoder__buffer_packet(etmq, queue, elem, trace_chan_id,
@@ -521,6 +522,10 @@ cs_etm_decoder__buffer_exception(struct cs_etm_queue *etmq,
 
 	packet = &queue->packet_buffer[queue->tail];
 	packet->exception_number = elem->exception_number;
+	if (elem->context.el_valid)
+		packet->el = elem->context.exception_level;
+	if (elem->excep_ret_addr)
+		packet->end_addr = elem->en_addr;
 
 	return ret;
 }
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index c8a92b5dba6001329d68766d540b9b7ead90e9e6..a40b3b2fe2e6318154111d9b7ebd05bee53934f1 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -2019,7 +2019,7 @@ static int cs_etm__exception(struct cs_etm_traceid_queue *tidq)
 	 * to generate branch sample for the instruction range before the
 	 * exception is trapped to kernel or before the exception returning.
 	 *
-	 * The exception packet includes the dummy address values, so don't
+	 * The exception packet does not describe an instruction range, so don't
 	 * swap PACKET with PREV_PACKET.  This keeps PREV_PACKET to be useful
 	 * for generating instruction and branch samples.
 	 */
@@ -2225,7 +2225,6 @@ static bool cs_etm__is_syscall(struct cs_etm_queue *etmq,
 			       struct cs_etm_traceid_queue *tidq, u64 magic)
 {
 	struct cs_etm_packet *packet = tidq->packet;
-	struct cs_etm_packet *prev_packet = tidq->prev_packet;
 
 	if (magic == __perf_cs_etmv3_magic)
 		if (packet->exception_number == CS_ETMV3_EXC_SVC)
@@ -2238,8 +2237,7 @@ static bool cs_etm__is_syscall(struct cs_etm_queue *etmq,
 	 */
 	if (magic == __perf_cs_etmv4_magic || magic == __perf_cs_ete_magic) {
 		if (packet->exception_number == CS_ETMV4_EXC_CALL &&
-		    cs_etm__is_svc_instr(etmq, tidq, prev_packet,
-					 prev_packet->end_addr))
+		    cs_etm__is_svc_instr(etmq, tidq, packet, packet->end_addr))
 			return true;
 	}
 
@@ -2277,7 +2275,6 @@ static bool cs_etm__is_sync_exception(struct cs_etm_queue *etmq,
 				      u64 magic)
 {
 	struct cs_etm_packet *packet = tidq->packet;
-	struct cs_etm_packet *prev_packet = tidq->prev_packet;
 
 	if (magic == __perf_cs_etmv3_magic)
 		if (packet->exception_number == CS_ETMV3_EXC_SMC ||
@@ -2301,8 +2298,7 @@ static bool cs_etm__is_sync_exception(struct cs_etm_queue *etmq,
 		 * (SMC, HVC) are taken as sync exceptions.
 		 */
 		if (packet->exception_number == CS_ETMV4_EXC_CALL &&
-		    !cs_etm__is_svc_instr(etmq, tidq, prev_packet,
-					  prev_packet->end_addr))
+		    !cs_etm__is_svc_instr(etmq, tidq, packet, packet->end_addr))
 			return true;
 
 		/*
diff --git a/tools/perf/util/cs-etm.h b/tools/perf/util/cs-etm.h
index b81099c2b301c25f894cc9a50edfde87aceff49d..4d03f2a680b58aacd91c9f6c957fb2d38e7ac2ad 100644
--- a/tools/perf/util/cs-etm.h
+++ b/tools/perf/util/cs-etm.h
@@ -174,6 +174,7 @@ struct cs_etm_packet {
 	enum cs_etm_sample_type sample_type;
 	enum cs_etm_isa isa;
 	u64 start_addr;
+	/* For exceptions: preferred return address, or CS_ETM_INVAL_ADDR. */
 	u64 end_addr;
 	u32 instr_count;
 	u32 last_instr_type;

-- 
2.34.1


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 10/14] perf cs-etm: Synthesize exception entries separately from branches
  2026-09-23 15:21 [PATCH v2 00/14] perf cs-etm: Fix bogus branch samples on exceptions Leo Yan
                   ` (8 preceding siblings ...)
  2026-09-23 15:21 ` [PATCH v2 09/14] perf cs-etm: Classify exception calls using the exception packet Leo Yan
@ 2026-09-23 15:21 ` Leo Yan
  2026-09-23 15:21 ` [PATCH v2 11/14] perf tests: Check CoreSight IRQ entry and exit Leo Yan
                   ` (3 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: Leo Yan @ 2026-09-23 15:21 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, Leo Yan

Exception entry is currently represented by replacing the preceding
range's flags and forcing its last_instr_taken_branch bit. This gives
an interrupt the source address of the last executed instruction. When
that instruction is a taken branch, its edge and original flags are lost.

For example, consider an untaken B.LS followed by an IRQ:

  4000f4: eb02003f  cmp  x1, x2
  4000f8: 54000109  b.ls 400118
  4000fc: d282f2c2  mov  x2, #0x1796

If B.LS completes untaken and the IRQ is taken before MOV completes, the
preferred exception return address is 4000fc. With kernel tracing disabled,
perf script previously attributed the IRQ to the preceding range:

  tr end  hw int  4000f8 => 0       b.ls #0x400118
  tr strt jmp          0 => 4000fc

When an IRQ element provides that return address, use it as the source of
the synthesized exception sample:

  tr end  hw int  4000fc => 0       movz x2, #0x1796
  tr strt jmp          0 => 4000fc

The source now identifies the architectural resume PC, rather than the
last completed instruction. The IRQ signal could have arrived while B.LS
was executing. The traced preferred exception return address of 4000fc
confirms that B.LS had retired architecturally before the IRQ was taken.

OpenCSD provides an exception's preferred return address in en_addr when
excep_ret_addr is set. It does not define or initialize st_addr for an
exception element. Derive A32 and A64 exception source PCs from the
preferred return address. Exception calls (SVC/SMC/HVC) use the preceding
four-byte instruction; interrupts, faults and traps use the return
address with last_instr_size set to zero.

Keep T32 exception samples on the existing fallback path, so determining
the call size requires an instruction read. Retain end_addr for the
frontend's SVC check. RESET and missing return addresses also use the
fallback because their source PC is unknown.

Resolve the preceding branch with its original flags without adding
instructions. Then retain the exception in prev_packet until the next
range, exception or discontinuity resolves its destination. This preserves
both edges when an exception follows a taken branch and also handles
consecutive exceptions.

Supply the preferred return address in sample.ret_addr so later instruction
fetching cannot change the return PC used by call/return export. Generate
final instruction samples only from nonempty instruction ranges.

CS_ETM_EXCEPTION_RET needs no further handling in the packet processing
loop because ERET is already part of the preceding instruction range.
cs_etm__set_sample_flags() now sets both that range's return flags and
last_instr_taken_branch.

Assisted-by: Codex:gpt-6
Signed-off-by: Leo Yan <leo.yan@arm.com>
---
 tools/perf/util/cs-etm-decoder/cs-etm-decoder.c |  31 ++++++-
 tools/perf/util/cs-etm.c                        | 102 ++++++++++++++----------
 tools/perf/util/cs-etm.h                        |   1 +
 3 files changed, 91 insertions(+), 43 deletions(-)

diff --git a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c
index 48e8c355a3e495cc30dcb4056c04a034d10ce860..83a851bdd87bf0aa3fbc565305451a68f42c69e6 100644
--- a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c
+++ b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c
@@ -514,6 +514,7 @@ cs_etm_decoder__buffer_exception(struct cs_etm_queue *etmq,
 {
 	int ret = 0;
 	struct cs_etm_packet *packet;
+	u8 last_instr_size = 0;
 
 	ret = cs_etm_decoder__buffer_packet(etmq, queue, elem, trace_chan_id,
 					    CS_ETM_EXCEPTION);
@@ -524,8 +525,34 @@ cs_etm_decoder__buffer_exception(struct cs_etm_queue *etmq,
 	packet->exception_number = elem->exception_number;
 	if (elem->context.el_valid)
 		packet->el = elem->context.exception_level;
-	if (elem->excep_ret_addr)
-		packet->end_addr = elem->en_addr;
+
+	/* RESET has no defined preferred return address. */
+	if (!elem->excep_ret_addr ||
+	    packet->exception_number == CS_ETMV4_EXC_RESET)
+		return ret;
+
+	packet->end_addr = elem->en_addr;
+
+	/*
+	 * T32 calls can be two or four bytes, requiring an instruction read
+	 * before end_addr to determine their size. Keep end_addr for the
+	 * frontend's SVC check and leave start_addr unknown for its fallback.
+	 */
+	if (packet->isa != CS_ETM_ISA_A32 && packet->isa != CS_ETM_ISA_A64)
+		return ret;
+
+	/*
+	 * For A32 and A64, exception calls execute a four-byte SVC, HVC or
+	 * SMC and save the following PC. Other exceptions use the interrupted
+	 * or faulting PC, including traps on those instructions.
+	 *
+	 * With a shared branch target, no instruction has completed
+	 * at the target, so last_instr_size remains zero.
+	 */
+	if (elem->exception_number == CS_ETMV4_EXC_CALL)
+		last_instr_size = 4;
+	packet->start_addr = packet->end_addr - last_instr_size;
+	packet->last_instr_size = last_instr_size;
 
 	return ret;
 }
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index a40b3b2fe2e6318154111d9b7ebd05bee53934f1..6641e257e5f18f30a185a88db720c1404cf2bd36 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -101,10 +101,8 @@ struct cs_etm_traceid_queue {
 	ocsd_ex_level decode_el;
 
 	/*
-	 * The frontend accesses the EL from '[prev_]packet' because it needs
-	 * previous EL for branch and current EL for instruction samples. It's
-	 * not possible to change thread in a single branch sample so no need to
-	 * store or access the thread through the packet.
+	 * Samples use the EL saved in their source packet. A branch sample
+	 * cannot change thread, so the thread is kept in the frontend context.
 	 */
 	struct thread *frontend_thread;
 };
@@ -1364,11 +1362,10 @@ static inline int cs_etm__instr_size(struct cs_etm_queue *etmq,
 static inline u64 cs_etm__first_executed_instr(struct cs_etm_packet *packet)
 {
 	/*
-	 * Return 0 for packets that have no addresses so that CS_ETM_INVAL_ADDR doesn't
-	 * appear in samples.
+	 * Return 0 for discontinuities so that CS_ETM_INVAL_ADDR doesn't appear
+	 * in samples.
 	 */
-	if (packet->sample_type == CS_ETM_DISCONTINUITY ||
-	    packet->sample_type == CS_ETM_EXCEPTION)
+	if (packet->sample_type == CS_ETM_DISCONTINUITY)
 		return 0;
 
 	return packet->start_addr;
@@ -1537,6 +1534,8 @@ static void cs_etm__copy_insn(struct cs_etm_queue *etmq,
 	}
 
 	sample->insn_len = cs_etm__instr_size(etmq, tidq, packet, sample->ip);
+	if (packet->sample_type == CS_ETM_EXCEPTION && !sample->insn_len)
+		return;
 
 	cs_etm__frontend_mem_access(etmq, tidq, packet, sample->ip,
 				    sample->insn_len, (void *)sample->insn);
@@ -1564,8 +1563,11 @@ static inline u64 cs_etm__resolve_sample_time(struct cs_etm_queue *etmq,
 		return etm->latest_kernel_timestamp;
 }
 
-static bool cs_etm__packet_has_taken_branch(struct cs_etm_packet *packet)
+static bool cs_etm__packet_has_branch(struct cs_etm_packet *packet)
 {
+	if (packet->sample_type == CS_ETM_EXCEPTION)
+		return true;
+
 	if (packet->sample_type == CS_ETM_RANGE &&
 	    packet->last_instr_taken_branch)
 		return true;
@@ -1583,7 +1585,7 @@ static void cs_etm__add_stack_event(struct cs_etm_queue *etmq,
 	if (!etm->synth_opts.branches && !etm->synth_opts.instructions)
 		return;
 
-	if (!cs_etm__packet_has_taken_branch(tidq->prev_packet))
+	if (!cs_etm__packet_has_branch(tidq->prev_packet))
 		return;
 
 	if (etmq->etm->use_thread_stack) {
@@ -1695,7 +1697,8 @@ static int cs_etm__synth_last_instruction_sample(struct cs_etm_queue *etmq,
 	    !etmq->etm->synth_opts.instructions)
 		return 0;
 
-	if (packet->sample_type != CS_ETM_RANGE)
+	/* Only nonempty ranges provide a final instruction to sample. */
+	if (packet->sample_type != CS_ETM_RANGE || !packet->instr_count)
 		return 0;
 
 	ret = cs_etm__synth_instruction_sample(etmq, tidq, packet,
@@ -1729,7 +1732,7 @@ static int cs_etm__synth_branch_sample(struct cs_etm_queue *etmq,
 	if (!etm->synth_opts.branches)
 		return 0;
 
-	if (!cs_etm__packet_has_taken_branch(tidq->prev_packet) &&
+	if (!cs_etm__packet_has_branch(tidq->prev_packet) &&
 	    !(tidq->prev_packet->flags & (PERF_IP_FLAG_TRACE_BEGIN |
 					  PERF_IP_FLAG_TRACE_END)))
 		return 0;
@@ -1760,6 +1763,9 @@ static int cs_etm__synth_branch_sample(struct cs_etm_queue *etmq,
 	sample.flags = tidq->prev_packet->flags;
 	sample.cpumode = event->sample.header.misc;
 
+	if (tidq->prev_packet->sample_type == CS_ETM_EXCEPTION)
+		sample.ret_addr = tidq->prev_packet->end_addr;
+
 	cs_etm__copy_insn(etmq, tidq, tidq->prev_packet, &sample);
 
 	/*
@@ -2010,21 +2016,23 @@ static int cs_etm__context(struct cs_etm_queue *etmq,
 	return ret;
 }
 
-static int cs_etm__exception(struct cs_etm_traceid_queue *tidq)
+static int cs_etm__exception(struct cs_etm_queue *etmq,
+			     struct cs_etm_traceid_queue *tidq)
 {
+	struct cs_etm_packet *packet = tidq->packet;
+
 	/*
-	 * When the exception packet is inserted, whether the last instruction
-	 * in previous range packet is taken branch or not, we need to force
-	 * to set 'prev_packet->last_instr_taken_branch' to true.  This ensures
-	 * to generate branch sample for the instruction range before the
-	 * exception is trapped to kernel or before the exception returning.
-	 *
-	 * The exception packet does not describe an instruction range, so don't
-	 * swap PACKET with PREV_PACKET.  This keeps PREV_PACKET to be useful
-	 * for generating instruction and branch samples.
+	 * Resolve the preceding branch without adding instructions, then keep
+	 * this exception as prev_packet until its destination is known.
 	 */
-	if (tidq->prev_packet->sample_type == CS_ETM_RANGE)
+	if (packet->start_addr != CS_ETM_INVAL_ADDR)
+		return cs_etm__sample(etmq, tidq);
+
+	/* Fall back to attributing the exception to the preceding range. */
+	if (tidq->prev_packet->sample_type == CS_ETM_RANGE) {
+		tidq->prev_packet->flags = packet->flags;
 		tidq->prev_packet->last_instr_taken_branch = true;
+	}
 
 	return 0;
 }
@@ -2418,7 +2426,8 @@ static int cs_etm__set_sample_flags(struct cs_etm_queue *etmq,
 		 * instruction packet, set flag PERF_IP_FLAG_TRACE_END
 		 * for previous packet.
 		 */
-		if (prev_packet->sample_type == CS_ETM_RANGE)
+		if (prev_packet->sample_type == CS_ETM_RANGE ||
+		    prev_packet->sample_type == CS_ETM_EXCEPTION)
 			prev_packet->flags |= PERF_IP_FLAG_BRANCH |
 					      PERF_IP_FLAG_TRACE_END;
 		break;
@@ -2450,15 +2459,23 @@ static int cs_etm__set_sample_flags(struct cs_etm_queue *etmq,
 					PERF_IP_FLAG_CALL |
 					PERF_IP_FLAG_INTERRUPT;
 
-		/*
-		 * When the exception packet is inserted, since exception
-		 * packet is not used standalone for generating samples
-		 * and it's affiliation to the previous instruction range
-		 * packet; so set previous range packet flags to tell perf
-		 * it is an exception taken branch.
-		 */
-		if (prev_packet->sample_type == CS_ETM_RANGE)
-			prev_packet->flags = packet->flags;
+		if (packet->start_addr == CS_ETM_INVAL_ADDR)
+			break;
+
+		/* Resolve the preceding trace start or exception return. */
+		if (prev_packet->sample_type == CS_ETM_DISCONTINUITY)
+			prev_packet->flags |= PERF_IP_FLAG_BRANCH |
+					      PERF_IP_FLAG_TRACE_BEGIN;
+
+		if (prev_packet->flags == (PERF_IP_FLAG_BRANCH |
+					   PERF_IP_FLAG_RETURN |
+					   PERF_IP_FLAG_INTERRUPT) &&
+		    cs_etm__is_svc_instr(etmq, tidq, packet,
+					packet->start_addr)) {
+			prev_packet->flags = PERF_IP_FLAG_BRANCH |
+					     PERF_IP_FLAG_RETURN |
+					     PERF_IP_FLAG_SYSCALLRET;
+		}
 		break;
 	case CS_ETM_EXCEPTION_RET:
 		/*
@@ -2486,10 +2503,12 @@ static int cs_etm__set_sample_flags(struct cs_etm_queue *etmq,
 		 * system call instruction and then calibrate the sample flag
 		 * as needed.
 		 */
-		if (prev_packet->sample_type == CS_ETM_RANGE)
+		if (prev_packet->sample_type == CS_ETM_RANGE) {
 			prev_packet->flags = PERF_IP_FLAG_BRANCH |
 					     PERF_IP_FLAG_RETURN |
 					     PERF_IP_FLAG_INTERRUPT;
+			prev_packet->last_instr_taken_branch = true;
+		}
 		break;
 	case CS_ETM_CONTEXT:
 	case CS_ETM_EMPTY:
@@ -2565,7 +2584,9 @@ static int cs_etm__process_traceid_queue(struct cs_etm_queue *etmq,
 			 * range, generate instruction sequence
 			 * events.
 			 */
-			cs_etm__sample(etmq, tidq);
+			ret = cs_etm__sample(etmq, tidq);
+			if (ret)
+				goto out;
 			break;
 		case CS_ETM_CONTEXT:
 			/*
@@ -2579,13 +2600,12 @@ static int cs_etm__process_traceid_queue(struct cs_etm_queue *etmq,
 				goto out;
 			break;
 		case CS_ETM_EXCEPTION:
+			ret = cs_etm__exception(etmq, tidq);
+			if (ret)
+				goto out;
+			break;
 		case CS_ETM_EXCEPTION_RET:
-			/*
-			 * If the exception packet is coming,
-			 * make sure the previous instruction
-			 * range packet to be handled properly.
-			 */
-			cs_etm__exception(tidq);
+			/* The return annotates the preceding instruction range. */
 			break;
 		case CS_ETM_DISCONTINUITY:
 			/*
diff --git a/tools/perf/util/cs-etm.h b/tools/perf/util/cs-etm.h
index 4d03f2a680b58aacd91c9f6c957fb2d38e7ac2ad..27201fea5af2764adf85a766925c393ad93d40f8 100644
--- a/tools/perf/util/cs-etm.h
+++ b/tools/perf/util/cs-etm.h
@@ -173,6 +173,7 @@ struct cs_etm_queue;
 struct cs_etm_packet {
 	enum cs_etm_sample_type sample_type;
 	enum cs_etm_isa isa;
+	/* For exceptions: source PC, or CS_ETM_INVAL_ADDR. */
 	u64 start_addr;
 	/* For exceptions: preferred return address, or CS_ETM_INVAL_ADDR. */
 	u64 end_addr;

-- 
2.34.1


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 11/14] perf tests: Check CoreSight IRQ entry and exit
  2026-09-23 15:21 [PATCH v2 00/14] perf cs-etm: Fix bogus branch samples on exceptions Leo Yan
                   ` (9 preceding siblings ...)
  2026-09-23 15:21 ` [PATCH v2 10/14] perf cs-etm: Synthesize exception entries separately from branches Leo Yan
@ 2026-09-23 15:21 ` Leo Yan
  2026-09-23 15:21 ` [PATCH v2 12/14] perf tests: Check CoreSight syscall " Leo Yan
                   ` (2 subsequent siblings)
  13 siblings, 0 replies; 17+ messages in thread
From: Leo Yan @ 2026-09-23 15:21 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, Leo Yan

Add an AArch64 branch_not_taken_loop workload and a CoreSight shell test
checking IRQ entry and exit PCs. Use cpu-clock generates timer
interrupts.

Retry at different timer frequencies and skip if no complete pair is
captured.

For example, an expected hw int/iret pair is:

  hw int           5cc928 => ffff800080010c80 insn: 63 04 00 f1
  ...
  iret   ffff800080012284 =>           5cc928 insn: e0 03 9f d6

The entry PC is the architectural resume address, here SUBS X3, X3, #1
at 0x5cc928. After the IRQ is handled, ERET returns to the same PC to
continue execution.

Assisted-by: Codex:gpt-6
Signed-off-by: Leo Yan <leo.yan@arm.com>
---
 tools/perf/tests/builtin-test.c                    |   1 +
 tools/perf/tests/shell/coresight/irq_entry_exit.sh |  37 +++++
 tools/perf/tests/shell/lib/coresight_exception.sh  | 165 +++++++++++++++++++++
 tools/perf/tests/tests.h                           |   1 +
 tools/perf/tests/workloads/Build                   |   2 +
 tools/perf/tests/workloads/branch_not_taken_loop.c |  33 +++++
 6 files changed, 239 insertions(+)

diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
index 6259ed805c5f75799d52ac742b77cf278f98bd9d..8eab221f115e75a3ef90be975d4ed0558ceb2b16 100644
--- a/tools/perf/tests/builtin-test.c
+++ b/tools/perf/tests/builtin-test.c
@@ -171,6 +171,7 @@ static struct test_workload *workloads[] = {
 	&workload__datasym,
 	&workload__landlock,
 	&workload__traploop,
+	&workload__branch_not_taken_loop,
 	&workload__inlineloop,
 	&workload__jitdump,
 	&workload__context_switch_loop,
diff --git a/tools/perf/tests/shell/coresight/irq_entry_exit.sh b/tools/perf/tests/shell/coresight/irq_entry_exit.sh
new file mode 100755
index 0000000000000000000000000000000000000000..f164daa222b0550ceb106aa6a1215740a1432017
--- /dev/null
+++ b/tools/perf/tests/shell/coresight/irq_entry_exit.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# CoreSight IRQ entry and exit (exclusive)
+
+# shellcheck source=../lib/coresight_exception.sh
+. "$(dirname "$0")/../lib/coresight_exception.sh"
+
+test_irq_entry_exit()
+{
+	# IRQs must return to the interrupted PC. Retry at different timer
+	# frequencies if trace gaps leave no complete entry/exit pair.
+	for freq in 1000 4000 10000; do
+		echo "Recording timer IRQs at $freq Hz"
+		if ! record_trace_with_cpu_clock "$freq" branch_not_taken_loop; then
+			cat "$tmpdir/record.log"
+			echo "Failed to record the IRQ workload"
+			return 1
+		fi
+
+		decode_trace || return 1
+
+		check_exception_pairs "hw int" "iret" 0
+		result=$?
+		case $result in
+		0) return 0 ;;
+		1) cat "$tmpdir/script.log"; return 1 ;;
+		2) echo "No complete IRQ pair in this recording" ;;
+		*) return "$result" ;;
+		esac
+	done
+
+	echo "[Skip] No complete IRQ pair after three recordings"
+	return 2
+}
+
+setup_exception_test || exit $?
+test_irq_entry_exit
diff --git a/tools/perf/tests/shell/lib/coresight_exception.sh b/tools/perf/tests/shell/lib/coresight_exception.sh
new file mode 100644
index 0000000000000000000000000000000000000000..f0133c258625147405ba4641ff7e442437fc66b8
--- /dev/null
+++ b/tools/perf/tests/shell/lib/coresight_exception.sh
@@ -0,0 +1,165 @@
+# SPDX-License-Identifier: GPL-2.0
+# Common helpers for CoreSight exception entry and return tests.
+
+cleanup()
+{
+	case $? in
+	0|2) rm -rf "$tmpdir" ;;
+	*) echo "Test files retained in $tmpdir" ;;
+	esac
+}
+
+record_trace_with_cpu_clock()
+{
+	local clock_opts=()
+
+	record_freq=$1
+	shift
+
+	# A zero frequency omits the cpu-clock event.
+	if [ "$record_freq" -gt 0 ]; then
+		# Generate timer IRQs without delivering a signal to the workload.
+		clock_opts=(-e cpu-clock:u -F "$record_freq")
+	fi
+
+	# FIFO control bounds the trace to the workload.
+	# --kcore supplies the running kernel's instructions for decoding.
+	taskset -c "$cpu" perf record -B --no-bpf-event --per-thread --kcore \
+		-e cs_etm/timestamp=0/uk "${clock_opts[@]}" \
+		-m,4M -D -1 --control fifo:"$tmpdir/ctl","$tmpdir/ack" \
+		-o "$tmpdir/data" -- \
+		perf test --record-ctl fifo:"$tmpdir/ctl","$tmpdir/ack" -w "$@" \
+		> "$tmpdir/record.log" 2>&1
+}
+
+record_trace()
+{
+	record_trace_with_cpu_clock 0 "$@"
+}
+
+decode_trace()
+{
+	local sw_fields=()
+
+	# perf script rejects -F sw: when no software event was recorded.
+	if [ "$record_freq" -gt 0 ]; then
+		sw_fields=(-F sw:)
+	fi
+
+	if ! perf script -i "$tmpdir/data" --itrace=b \
+		-F hw:ip,addr,flags,insn "${sw_fields[@]}" \
+		> "$tmpdir/script" 2> "$tmpdir/script.log"; then
+		cat "$tmpdir/script.log"
+		echo "Failed to decode the exception trace"
+		return 1
+	fi
+}
+
+# Arguments: entry pattern, exit pattern, expected return PC - entry PC.
+# Return 2 if no complete pair survives the trace gaps.
+check_exception_pairs()
+{
+	local difference=$3 entry_pc resume_pc pairs=0
+
+	awk -v entry_pattern="$1" -v exit_pattern="$2" '
+	function address(pc) {
+		sub(/^0[xX]/, "", pc)
+		sub(/^0+/, "", pc)
+		return "0x" (pc == "" ? "0" : tolower(pc))
+	}
+	BEGIN {
+		entry_pattern = "^[[:space:]]*(" entry_pattern ")([[:space:]]|$)"
+		exit_pattern = "^[[:space:]]*(" exit_pattern ")([[:space:]]|$)"
+	}
+	NF {
+		# A trace boundary invalidates all pending entries, including nested ones.
+		if ($1 == "tr" && ($2 == "strt" || $2 == "end")) {
+			depth = 0
+			next
+		}
+
+		is_entry = ($1 == "hw" && $2 == "int") || $1 == "int" || $1 == "syscall"
+		is_exit = $1 == "iret" || $1 == "sysret"
+		if (!is_entry && !is_exit)
+			next
+
+		# Branch fields are: flags source => destination [insn: bytes].
+		arrow = 0
+		for (i = 1; i <= NF; i++) {
+			if ($i == "=>") {
+				arrow = i
+				break
+			}
+		}
+		if (arrow < 3 || arrow == NF) {
+			print "FAIL: Unexpected perf script output: " $0 > "/dev/stderr"
+			errors++
+			depth = 0
+			next
+		}
+
+		# Track every exception so an unrelated nested IRET cannot close
+		# a selected outer entry. Match the requested flags at each depth.
+		if (is_entry) {
+			entry_pc[++depth] = address($(arrow - 1))
+			selected[depth] = $0 ~ entry_pattern
+			next
+		}
+		if (!depth)
+			next
+
+		if (selected[depth] && $0 ~ exit_pattern)
+			print entry_pc[depth], address($(arrow + 1))
+		depth--
+	}
+	END {
+		if (errors)
+			exit 1
+	}' "$tmpdir/script" > "$tmpdir/exception_pairs" || return 1
+
+	# Bash uses integer arithmetic, preserving all bits of 64-bit PCs.
+	while read -r entry_pc resume_pc; do
+		if [ "$((resume_pc - entry_pc))" -ne "$difference" ]; then
+			printf "FAIL: Entry %s returns to %s, expected PC difference %d\n" \
+				"$entry_pc" "$resume_pc" "$difference"
+			return 1
+		fi
+		pairs=$((pairs + 1))
+	done < "$tmpdir/exception_pairs"
+
+	if [ "$pairs" -eq 0 ]; then
+		echo "No complete exception entry/exit pair"
+		return 2
+	fi
+	printf "Checked %d exception pairs with PC difference %d\n" "$pairs" "$difference"
+}
+
+setup_exception_test()
+{
+	[ "$(uname -m)" = aarch64 ] || return 2
+	perf check feature -q libopencsd || return 2
+	[ -d /sys/bus/event_source/devices/cs_etm ] || return 2
+
+	export LC_ALL=C
+	command -v taskset >/dev/null 2>&1 || return 2
+
+	tmpdir=$(mktemp -d /tmp/perf-cs-etm-exception.XXXXXX) || return 1
+	trap cleanup EXIT
+	trap 'exit 1' HUP INT TERM
+	mkfifo "$tmpdir/ctl" "$tmpdir/ack" || return 1
+
+	# Keep each exception entry and return in the same trace queue.
+	cpu=$(awk '/Cpus_allowed_list:/ {
+		split($2, cpus, /[-,]/)
+		print cpus[1]
+	}' /proc/self/status)
+
+	# Check permissions, FIFO control and the trace sink before testing.
+	if ! record_trace callchain; then
+		cat "$tmpdir/record.log"
+		echo "[Skip] Cannot record user/kernel CoreSight trace with --kcore"
+		return 2
+	fi
+
+	return 0
+}
diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h
index b2520a564417b61718f1dcb23da1f8cc0f601906..0a61129b31ab6997616f7d04d29af73961f4f861 100644
--- a/tools/perf/tests/tests.h
+++ b/tools/perf/tests/tests.h
@@ -246,6 +246,7 @@ DECLARE_WORKLOAD(brstack);
 DECLARE_WORKLOAD(datasym);
 DECLARE_WORKLOAD(landlock);
 DECLARE_WORKLOAD(traploop);
+DECLARE_WORKLOAD(branch_not_taken_loop);
 DECLARE_WORKLOAD(inlineloop);
 DECLARE_WORKLOAD(jitdump);
 DECLARE_WORKLOAD(context_switch_loop);
diff --git a/tools/perf/tests/workloads/Build b/tools/perf/tests/workloads/Build
index 048e371eb63e316453b6b46ebd0a02794c3d25d7..ca22a82073ed08de5d4a27d0a3b33ddcba2e3f3e 100644
--- a/tools/perf/tests/workloads/Build
+++ b/tools/perf/tests/workloads/Build
@@ -9,6 +9,7 @@ perf-test-y += brstack.o
 perf-test-y += datasym.o
 perf-test-y += landlock.o
 perf-test-y += traploop.o
+perf-test-y += branch_not_taken_loop.o
 perf-test-y += inlineloop.o
 perf-test-y += jitdump.o
 perf-test-y += context_switch_loop.o
@@ -25,6 +26,7 @@ CFLAGS_leafloop.o         = -g -O0 -fno-inline -fno-omit-frame-pointer -U_FORTIF
 CFLAGS_brstack.o          = -g -O0 -fno-inline -U_FORTIFY_SOURCE
 CFLAGS_datasym.o          = -g -O0 -fno-inline -U_FORTIFY_SOURCE
 CFLAGS_traploop.o         = -g -O0 -fno-inline -U_FORTIFY_SOURCE
+CFLAGS_branch_not_taken_loop.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE
 CFLAGS_inlineloop.o       = -g -O2
 CFLAGS_deterministic.o    = -g -O0 -fno-inline -U_FORTIFY_SOURCE
 CFLAGS_named_threads.o    = -g -O0 -fno-inline -U_FORTIFY_SOURCE
diff --git a/tools/perf/tests/workloads/branch_not_taken_loop.c b/tools/perf/tests/workloads/branch_not_taken_loop.c
new file mode 100644
index 0000000000000000000000000000000000000000..6376b1c476e2f4814a94c91a5321ae919a818826
--- /dev/null
+++ b/tools/perf/tests/workloads/branch_not_taken_loop.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/compiler.h>
+#include "../tests.h"
+
+#ifdef __aarch64__
+static void branch_not_taken(void)
+{
+	/* Keep x1 > x2 so B.LS is never taken. */
+	asm volatile(
+		"mov	x1, #2\n"
+		"mov	x2, #1\n"
+		"movz	x3, #0xffff\n"
+		"movk	x3, #0x0080, lsl #16\n"
+		"1: cmp	x1, x2\n"
+		".Ltest_branch:\n"
+		"b.ls	2f\n"
+		".Lfallthrough:\n"
+		"subs	x3, x3, #1\n"
+		"b.ne	1b\n"
+		"2:\n"
+		: : : "x1", "x2", "x3", "cc");
+}
+#else
+static void branch_not_taken(void) { }
+#endif
+
+static int branch_not_taken_loop(int argc __maybe_unused, const char **argv __maybe_unused)
+{
+	branch_not_taken();
+	return 0;
+}
+
+DEFINE_WORKLOAD(branch_not_taken_loop);

-- 
2.34.1


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 12/14] perf tests: Check CoreSight syscall entry and exit
  2026-09-23 15:21 [PATCH v2 00/14] perf cs-etm: Fix bogus branch samples on exceptions Leo Yan
                   ` (10 preceding siblings ...)
  2026-09-23 15:21 ` [PATCH v2 11/14] perf tests: Check CoreSight IRQ entry and exit Leo Yan
@ 2026-09-23 15:21 ` Leo Yan
  2026-09-23 15:21 ` [PATCH v2 13/14] perf tests: Check CoreSight abort " Leo Yan
  2026-09-23 15:21 ` [PATCH v2 14/14] perf tests: Check CoreSight emulated instruction " Leo Yan
  13 siblings, 0 replies; 17+ messages in thread
From: Leo Yan @ 2026-09-23 15:21 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, Leo Yan

Record the existing callchain workload, which issues gettid() through
SVC. The test requires syscall/sysret pairs to advance the PC by four
bytes.

For example, an expected syscall/sysret pair is:

  syscall           bb76e4 => ffff800080010c00 insn: 01 00 00 d4
  ...
  sysret  ffff800080012284 =>           bb76e8 insn: e0 03 9f d6

The entry instruction is SVC #0 at 0xbb76e4. On syscall completion,
ERET returns to 0xbb76e8, the instruction immediately after SVC.

Assisted-by: Codex:gpt-6
Signed-off-by: Leo Yan <leo.yan@arm.com>
---
 .../tests/shell/coresight/syscall_entry_exit.sh     | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/tools/perf/tests/shell/coresight/syscall_entry_exit.sh b/tools/perf/tests/shell/coresight/syscall_entry_exit.sh
new file mode 100755
index 0000000000000000000000000000000000000000..bf61b1fdf6f15e290295afc7577870c6864ddd93
--- /dev/null
+++ b/tools/perf/tests/shell/coresight/syscall_entry_exit.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# CoreSight syscall entry and exit (exclusive)
+
+# shellcheck source=../lib/coresight_exception.sh
+. "$(dirname "$0")/../lib/coresight_exception.sh"
+
+test_syscall_entry_exit()
+{
+	# callchain() invokes gettid() through SVC, which returns to PC + 4.
+	if ! record_trace callchain; then
+		cat "$tmpdir/record.log"
+		echo "Failed to record the callchain workload"
+		return 1
+	fi
+	decode_trace || return 1
+	check_exception_pairs "syscall" "sysret" 4 || return 1
+}
+
+setup_exception_test || exit $?
+test_syscall_entry_exit

-- 
2.34.1


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 13/14] perf tests: Check CoreSight abort entry and exit
  2026-09-23 15:21 [PATCH v2 00/14] perf cs-etm: Fix bogus branch samples on exceptions Leo Yan
                   ` (11 preceding siblings ...)
  2026-09-23 15:21 ` [PATCH v2 12/14] perf tests: Check CoreSight syscall " Leo Yan
@ 2026-09-23 15:21 ` Leo Yan
  2026-09-23 15:21 ` [PATCH v2 14/14] perf tests: Check CoreSight emulated instruction " Leo Yan
  13 siblings, 0 replies; 17+ messages in thread
From: Leo Yan @ 2026-09-23 15:21 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, Leo Yan

Add page_fault_loop to repeatedly discard an anonymous page with
MADV_DONTNEED and read it again. Each demand fault is resolved without
signal delivery, so the load must be retried at the same PC.

For example, an expected int/iret pair is:

  int            5ccadc => ffff800080010c00 insn: 00 00 40 39
  ...
  iret ffff800080012284 =>           5ccadc insn: e0 03 9f d6

The LDRB W0, [X0] at 0x5ccadc faults before completing. After resolving
the page fault, the kernel returns via ERET to the same PC to retry it.

Assisted-by: Codex:gpt-6
Signed-off-by: Leo Yan <leo.yan@arm.com>
---
 tools/perf/tests/builtin-test.c                    |  1 +
 .../perf/tests/shell/coresight/abort_entry_exit.sh | 21 ++++++++++++
 tools/perf/tests/tests.h                           |  1 +
 tools/perf/tests/workloads/Build                   |  2 ++
 tools/perf/tests/workloads/page_fault_loop.c       | 37 ++++++++++++++++++++++
 5 files changed, 62 insertions(+)

diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
index 8eab221f115e75a3ef90be975d4ed0558ceb2b16..1af8dfd45d1a2ff5c3206234cd75fb173e122d88 100644
--- a/tools/perf/tests/builtin-test.c
+++ b/tools/perf/tests/builtin-test.c
@@ -172,6 +172,7 @@ static struct test_workload *workloads[] = {
 	&workload__landlock,
 	&workload__traploop,
 	&workload__branch_not_taken_loop,
+	&workload__page_fault_loop,
 	&workload__inlineloop,
 	&workload__jitdump,
 	&workload__context_switch_loop,
diff --git a/tools/perf/tests/shell/coresight/abort_entry_exit.sh b/tools/perf/tests/shell/coresight/abort_entry_exit.sh
new file mode 100755
index 0000000000000000000000000000000000000000..483cf39b3966e89591d4e06d03a3fc6709fc3dc9
--- /dev/null
+++ b/tools/perf/tests/shell/coresight/abort_entry_exit.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# CoreSight abort entry and exit (exclusive)
+
+# shellcheck source=../lib/coresight_exception.sh
+. "$(dirname "$0")/../lib/coresight_exception.sh"
+
+test_abort_entry_exit()
+{
+	# A demand page fault retries the load at the same PC after resolution.
+	if ! record_trace page_fault_loop; then
+		cat "$tmpdir/record.log"
+		echo "Failed to record the page fault workload"
+		return 1
+	fi
+	decode_trace || return 1
+	check_exception_pairs "int" "iret" 0 || return 1
+}
+
+setup_exception_test || exit $?
+test_abort_entry_exit
diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h
index 0a61129b31ab6997616f7d04d29af73961f4f861..0fc02c5b060133ef27b80de5c868d798d65a7c53 100644
--- a/tools/perf/tests/tests.h
+++ b/tools/perf/tests/tests.h
@@ -247,6 +247,7 @@ DECLARE_WORKLOAD(datasym);
 DECLARE_WORKLOAD(landlock);
 DECLARE_WORKLOAD(traploop);
 DECLARE_WORKLOAD(branch_not_taken_loop);
+DECLARE_WORKLOAD(page_fault_loop);
 DECLARE_WORKLOAD(inlineloop);
 DECLARE_WORKLOAD(jitdump);
 DECLARE_WORKLOAD(context_switch_loop);
diff --git a/tools/perf/tests/workloads/Build b/tools/perf/tests/workloads/Build
index ca22a82073ed08de5d4a27d0a3b33ddcba2e3f3e..e18fdaa6fd1927ed406e703a7ecbcc19a111caf3 100644
--- a/tools/perf/tests/workloads/Build
+++ b/tools/perf/tests/workloads/Build
@@ -10,6 +10,7 @@ perf-test-y += datasym.o
 perf-test-y += landlock.o
 perf-test-y += traploop.o
 perf-test-y += branch_not_taken_loop.o
+perf-test-y += page_fault_loop.o
 perf-test-y += inlineloop.o
 perf-test-y += jitdump.o
 perf-test-y += context_switch_loop.o
@@ -27,6 +28,7 @@ CFLAGS_brstack.o          = -g -O0 -fno-inline -U_FORTIFY_SOURCE
 CFLAGS_datasym.o          = -g -O0 -fno-inline -U_FORTIFY_SOURCE
 CFLAGS_traploop.o         = -g -O0 -fno-inline -U_FORTIFY_SOURCE
 CFLAGS_branch_not_taken_loop.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE
+CFLAGS_page_fault_loop.o  = -g -O0 -fno-inline -U_FORTIFY_SOURCE
 CFLAGS_inlineloop.o       = -g -O2
 CFLAGS_deterministic.o    = -g -O0 -fno-inline -U_FORTIFY_SOURCE
 CFLAGS_named_threads.o    = -g -O0 -fno-inline -U_FORTIFY_SOURCE
diff --git a/tools/perf/tests/workloads/page_fault_loop.c b/tools/perf/tests/workloads/page_fault_loop.c
new file mode 100644
index 0000000000000000000000000000000000000000..4afbc4ab0a55400360facdc3a99ea5a5a9d52596
--- /dev/null
+++ b/tools/perf/tests/workloads/page_fault_loop.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/compiler.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include "../tests.h"
+
+static int page_fault_loop(int argc __maybe_unused, const char **argv __maybe_unused)
+{
+	size_t size = getpagesize();
+	char *page;
+	int ret = 1;
+
+	page = mmap(NULL, size, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	if (page == MAP_FAILED) {
+		perror("mmap");
+		return 1;
+	}
+
+	for (int i = 0; i < 256; i++) {
+		/* Drop the PTE so the kernel must resolve a fault on each read. */
+		if (madvise(page, size, MADV_DONTNEED)) {
+			perror("madvise");
+			goto out;
+		}
+		if (READ_ONCE(*page)) {
+			fprintf(stderr, "Anonymous page is not zero-filled\n");
+			goto out;
+		}
+	}
+	ret = 0;
+out:
+	munmap(page, size);
+	return ret;
+}
+
+DEFINE_WORKLOAD(page_fault_loop);

-- 
2.34.1


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 14/14] perf tests: Check CoreSight emulated instruction entry and exit
  2026-09-23 15:21 [PATCH v2 00/14] perf cs-etm: Fix bogus branch samples on exceptions Leo Yan
                   ` (12 preceding siblings ...)
  2026-09-23 15:21 ` [PATCH v2 13/14] perf tests: Check CoreSight abort " Leo Yan
@ 2026-09-23 15:21 ` Leo Yan
  13 siblings, 0 replies; 17+ messages in thread
From: Leo Yan @ 2026-09-23 15:21 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers,
	Adrian Hunter, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, Leo Yan

Use traploop's EL0 read of ID_AA64ISAR0_EL1 to check an emulated
instruction. The kernel emulates MRS and advances the return PC by four
bytes.

Select MRS entries by instruction encoding because page faults also
appear as int/iret pairs but return to the same PC. Reuse the common FIFO
recorder and pair checker.

For example, an expected int/iret pair is:

  int            5cc808 => ffff800080010c00 insn: 00 06 38 d5
  ...
  iret ffff800080012284 =>           5cc80c insn: e0 03 9f d6

The entry instruction is MRS X0, ID_AA64ISAR0_EL1. After emulating the
read at 0x5cc808, the kernel returns via ERET to the next instruction at
0x5cc80c.

Assisted-by: Codex:gpt-6
Signed-off-by: Leo Yan <leo.yan@arm.com>
---
 .../perf/tests/shell/coresight/trap_entry_exit.sh  | 24 ++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/tools/perf/tests/shell/coresight/trap_entry_exit.sh b/tools/perf/tests/shell/coresight/trap_entry_exit.sh
new file mode 100755
index 0000000000000000000000000000000000000000..3ebd4033b247a1a43e0139868972afd41ba905ba
--- /dev/null
+++ b/tools/perf/tests/shell/coresight/trap_entry_exit.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# CoreSight emulated instruction entry and exit (exclusive)
+
+# shellcheck source=../lib/coresight_exception.sh
+. "$(dirname "$0")/../lib/coresight_exception.sh"
+
+test_trap_entry_exit()
+{
+	# traploop reads ID_AA64ISAR0_EL1 from EL0. The kernel emulates MRS and
+	# advances the saved PC, so IRET must resume four bytes after the trap.
+	if ! record_trace traploop 256; then
+		cat "$tmpdir/record.log"
+		echo "Failed to record the traploop workload"
+		return 1
+	fi
+	decode_trace || return 1
+	# Page faults also appear as "int", but retry the same PC. Select MRS
+	# Xt, ID_AA64ISAR0_EL1 by its encoding; the low five bits select Xt.
+	check_exception_pairs "int.*insn: [01][[:xdigit:]] 06 38 d5" "iret" 4 || return 1
+}
+
+setup_exception_test || exit $?
+test_trap_entry_exit

-- 
2.34.1


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 01/14] perf sample: Allow decoders to supply branch return addresses
  2026-09-23 15:21 ` [PATCH v2 01/14] perf sample: Allow decoders to supply branch return addresses Leo Yan
@ 2026-09-24 13:00   ` Adrian Hunter
  0 siblings, 0 replies; 17+ messages in thread
From: Adrian Hunter @ 2026-09-24 13:00 UTC (permalink / raw)
  To: Leo Yan, Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa,
	Ian Rogers, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel

On 23/09/2026 18:21, Leo Yan wrote:
> The thread stack derives return addresses from IP + insn_len. For an
> interrupt or fault, the return address can instead be the sample IP even
> when instruction bytes at that address are available.
> 
> Add ret_addr to perf_sample and prefer it in thread_stack__trace_end()
> and thread_stack__process() when supplied. Initialize it to zero so other
> samples retain the IP + insn_len calculation.
> 
> Add a regression test for explicit exception return addresses and the
> ordinary call fallback when the return address is zero.
> 
> Assisted-by: Codex:gpt-6
> Signed-off-by: Leo Yan <leo.yan@arm.com>

Reviewed-by: Adrian Hunter <adrian.hunter@intel.com>

> ---
>  tools/perf/tests/Build          |   1 +
>  tools/perf/tests/builtin-test.c |   1 +
>  tools/perf/tests/tests.h        |   1 +
>  tools/perf/tests/thread-stack.c | 106 ++++++++++++++++++++++++++++++++++++++++
>  tools/perf/util/sample.c        |   1 +
>  tools/perf/util/sample.h        |   5 ++
>  tools/perf/util/thread-stack.c  |   5 +-
>  7 files changed, 118 insertions(+), 2 deletions(-)
> 
> diff --git a/tools/perf/tests/Build b/tools/perf/tests/Build
> index 81c311b131b72715f11b6501511a18dce2af07df..d03ae938dd0ad92f78f8de6900ce7161f39fc3bc 100644
> --- a/tools/perf/tests/Build
> +++ b/tools/perf/tests/Build
> @@ -29,6 +29,7 @@ perf-test-y += task-exit.o
>  perf-test-y += sw-clock.o
>  perf-test-y += mmap-thread-lookup.o
>  perf-test-y += thread-maps-share.o
> +perf-test-y += thread-stack.o
>  perf-test-$(CONFIG_LIBTRACEEVENT) += switch-tracking.o
>  perf-test-y += keep-tracking.o
>  perf-test-y += code-reading.o
> diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
> index d2f594921e25bda9fc662e7ba82464bfef1b752b..6259ed805c5f75799d52ac742b77cf278f98bd9d 100644
> --- a/tools/perf/tests/builtin-test.c
> +++ b/tools/perf/tests/builtin-test.c
> @@ -112,6 +112,7 @@ static struct test_suite *generic_tests[] = {
>  	&suite__hists_filter,
>  	&suite__mmap_thread_lookup,
>  	&suite__thread_maps_share,
> +	&suite__thread_stack,
>  	&suite__hists_output,
>  	&suite__hists_cumulate,
>  #ifdef HAVE_LIBTRACEEVENT
> diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h
> index 9c96f33483d1435644da6068c3802f7a914a9de0..b2520a564417b61718f1dcb23da1f8cc0f601906 100644
> --- a/tools/perf/tests/tests.h
> +++ b/tools/perf/tests/tests.h
> @@ -138,6 +138,7 @@ DECLARE_SUITE(expr);
>  DECLARE_SUITE(hists_filter);
>  DECLARE_SUITE(mmap_thread_lookup);
>  DECLARE_SUITE(thread_maps_share);
> +DECLARE_SUITE(thread_stack);
>  DECLARE_SUITE(hists_output);
>  DECLARE_SUITE(hists_cumulate);
>  DECLARE_SUITE(switch_tracking);
> diff --git a/tools/perf/tests/thread-stack.c b/tools/perf/tests/thread-stack.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..0239e3bee34faf8fd4ddc7b723cf479609760064
> --- /dev/null
> +++ b/tools/perf/tests/thread-stack.c
> @@ -0,0 +1,106 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <linux/kernel.h>
> +#include <unistd.h>
> +#include "tests.h"
> +#include "util/addr_location.h"
> +#include "util/event.h"
> +#include "util/sample.h"
> +#include "util/thread.h"
> +#include "util/thread-stack.h"
> +
> +#define CALL_REF		1234UL
> +#define RET_REF			5678UL
> +
> +struct return_check {
> +	unsigned int matched;
> +	unsigned int unmatched;
> +};
> +
> +static int check_call_return(struct call_return *cr,
> +			     u64 *parent_db_id __maybe_unused, void *data)
> +{
> +	struct return_check *check = data;
> +
> +	if (cr->call_ref == CALL_REF && cr->return_ref == RET_REF && !cr->flags)
> +		check->matched++;
> +	else
> +		check->unmatched++;
> +
> +	return 0;
> +}
> +
> +/* A zero expected_ret_addr asks the stack to use ip + insn_len. */
> +static int check_return_address(u64 expected_ret_addr, u64 actual_ret_addr,
> +				u32 flags)
> +{
> +	struct call_return_processor *crp;
> +	struct return_check check = { };
> +	struct thread *thread;
> +	struct addr_location from = { }, to = { };
> +	struct perf_sample sample = { };
> +	int ret = TEST_FAIL;
> +
> +	thread = thread__new(getpid(), getpid());
> +	if (!thread)
> +		return TEST_FAIL;
> +
> +	crp = call_return_processor__new(check_call_return, &check);
> +	if (!crp)
> +		goto out;
> +
> +	sample.ip = 0x1000;		/* Call or exception source addr */
> +	sample.addr = 0x2000;		/* Callee or exception handler addr */
> +	sample.ret_addr = expected_ret_addr;
> +	sample.flags = PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL | flags;
> +	sample.time = 1;
> +	/* Model the opcode length after an instruction fetch. */
> +	sample.insn_len = 4;
> +	if (thread_stack__process(thread, thread__comm(thread), &sample,
> +				  &from, &to, CALL_REF, crp))
> +		goto out;
> +
> +	sample.ip = 0x2000;		/* Return instruction addr */
> +	sample.addr = actual_ret_addr;	/* Return branch target addr */
> +	sample.ret_addr = 0;
> +	sample.flags = PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_RETURN |
> +		       (flags & (PERF_IP_FLAG_INTERRUPT | PERF_IP_FLAG_SYSCALLRET));
> +	sample.time = 2;
> +	if (thread_stack__process(thread, thread__comm(thread), &sample,
> +				  &to, &from, RET_REF, crp))
> +		goto out;
> +
> +	if (check.matched == 1 && !check.unmatched)
> +		ret = TEST_OK;
> +
> +out:
> +	thread__put(thread);
> +	call_return_processor__free(crp);
> +	return ret;
> +}
> +
> +static int test__thread_stack(struct test_suite *test __maybe_unused,
> +			      int subtest __maybe_unused)
> +{
> +	static const struct {
> +		const char *name;
> +		u64 expected_ret_addr;
> +		u64 actual_ret_addr;
> +		u32 flags;
> +	} cases[] = {
> +		{ "ordinary call", 0, 0x1004, 0 },
> +		{ "interrupt", 0x1000, 0x1000, PERF_IP_FLAG_ASYNC | PERF_IP_FLAG_INTERRUPT },
> +		{ "fault or trap", 0x1000, 0x1000, PERF_IP_FLAG_INTERRUPT },
> +		{ "SVC", 0x1004, 0x1004, PERF_IP_FLAG_SYSCALLRET },
> +	};
> +
> +	for (size_t i = 0; i < ARRAY_SIZE(cases); i++) {
> +		if (check_return_address(cases[i].expected_ret_addr,
> +					 cases[i].actual_ret_addr, cases[i].flags)) {
> +			pr_debug("Incorrect return address for %s\n", cases[i].name);
> +			return TEST_FAIL;
> +		}
> +	}
> +	return TEST_OK;
> +}
> +
> +DEFINE_SUITE("Thread stack return addresses after instruction fetching", thread_stack);
> diff --git a/tools/perf/util/sample.c b/tools/perf/util/sample.c
> index bccc19e2aaf25118a8ecde88473aa6cb16fa561a..4abb689132bc27d0f2318eeafca20f7e57621837 100644
> --- a/tools/perf/util/sample.c
> +++ b/tools/perf/util/sample.c
> @@ -29,6 +29,7 @@ void perf_sample__init(struct perf_sample *sample, bool all)
>  		sample->intr_regs = NULL;
>  		sample->merged_callchain = false;
>  		sample->callchain = NULL;
> +		sample->ret_addr = 0;
>  	}
>  }
>  
> diff --git a/tools/perf/util/sample.h b/tools/perf/util/sample.h
> index cb4b16654876e9a5c3bfd66be3e0235ac15795e0..865ed18200f1339f5949ef3f185ed57a34aace72 100644
> --- a/tools/perf/util/sample.h
> +++ b/tools/perf/util/sample.h
> @@ -131,6 +131,11 @@ struct perf_sample {
>  	u64 time;
>  	/** @addr: The sample event PERF_SAMPLE_ADDR value. */
>  	u64 addr;
> +	/**
> +	 * @ret_addr: Return address supplied by the decoder for a branch sample.
> +	 *            Zero means use ip + insn_len.
> +	 */
> +	u64 ret_addr;
>  	/** @id: The sample event PERF_SAMPLE_ID or PERF_SAMPLE_IDENTIFIER value. */
>  	u64 id;
>  	/** @stream_id: The sample event PERF_SAMPLE_STREAM_ID value. */
> diff --git a/tools/perf/util/thread-stack.c b/tools/perf/util/thread-stack.c
> index 1360f44421ef8bb80fe5cfdba59be7e6029b0240..d452d1a7eabb16f040ca3dada23e6fdec8fb1088 100644
> --- a/tools/perf/util/thread-stack.c
> +++ b/tools/perf/util/thread-stack.c
> @@ -1030,7 +1030,7 @@ static int thread_stack__trace_end(struct thread_stack *ts,
>  	cp = call_path__findnew(cpr, ts->stack[ts->cnt - 1].cp, NULL, 0,
>  				ts->kernel_start);
>  
> -	ret_addr = sample->ip + sample->insn_len;
> +	ret_addr = sample->ret_addr ? sample->ret_addr : sample->ip + sample->insn_len;
>  
>  	return thread_stack__push_cp(ts, ret_addr, sample->time, ref, cp,
>  				     false, true);
> @@ -1154,7 +1154,8 @@ int thread_stack__process(struct thread *thread, struct comm *comm,
>  		if (!sample->ip || !sample->addr)
>  			return 0;
>  
> -		ret_addr = sample->ip + sample->insn_len;
> +		/* Opcode fetching must not change a decoder-supplied return address. */
> +		ret_addr = sample->ret_addr ? sample->ret_addr : sample->ip + sample->insn_len;
>  		if (ret_addr == sample->addr)
>  			return 0; /* Zero-length calls are excluded */
>  
> 


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 02/14] perf intel-pt: Preserve return addresses for asynchronous branches
  2026-09-23 15:21 ` [PATCH v2 02/14] perf intel-pt: Preserve return addresses for asynchronous branches Leo Yan
@ 2026-09-24 13:00   ` Adrian Hunter
  0 siblings, 0 replies; 17+ messages in thread
From: Adrian Hunter @ 2026-09-24 13:00 UTC (permalink / raw)
  To: Leo Yan, Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa,
	Ian Rogers, James Clark, Mike Leach, Suzuki K Poulose,
	Suyash Mahar, Amir Ayupov
  Cc: Arnaldo Carvalho de Melo, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel

On 23/09/2026 18:21, Leo Yan wrote:
> Intel PT uses a zero instruction length for asynchronous branches so
> the thread stack resumes at the source IP. A dlfilter can fetch the
> instruction before call/return processing and replace that zero with
> its opcode length, advancing the saved return address.
> 
> Set sample.ret_addr to sample.ip for asynchronous branch samples so
> instruction fetching cannot change the return address. This also
> covers asynchronous trace-end samples and VM exits.
> 
> Assisted-by: Codex:gpt-6
> Signed-off-by: Leo Yan <leo.yan@arm.com>

Reviewed-by: Adrian Hunter <adrian.hunter@intel.com>

> ---
>  tools/perf/util/intel-pt.c | 7 +++++++
>  1 file changed, 7 insertions(+)
> 
> diff --git a/tools/perf/util/intel-pt.c b/tools/perf/util/intel-pt.c
> index 8c21c9f52d572d2dd4e15b3b5c341b253ccea1b5..e8b9c8a67b0a0635e0110eda4b6ccb2511132fc7 100644
> --- a/tools/perf/util/intel-pt.c
> +++ b/tools/perf/util/intel-pt.c
> @@ -1803,6 +1803,13 @@ static int intel_pt_synth_branch_sample(struct intel_pt_queue *ptq)
>  	perf_sample__init(&sample, /*all=*/true);
>  	intel_pt_prep_b_sample(pt, ptq, event, &sample);
>  
> +	/*
> +	 * For asynchronous branches, use the sample IP as the return address
> +	 * instead of advancing it during instruction decoding.
> +	 */
> +	if (sample.flags & PERF_IP_FLAG_ASYNC)
> +		sample.ret_addr = sample.ip;
> +
>  	sample.id = ptq->pt->branches_id;
>  	sample.stream_id = ptq->pt->branches_id;
>  
> 


^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2026-09-24 13:00 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-23 15:21 [PATCH v2 00/14] perf cs-etm: Fix bogus branch samples on exceptions Leo Yan
2026-09-23 15:21 ` [PATCH v2 01/14] perf sample: Allow decoders to supply branch return addresses Leo Yan
2026-09-24 13:00   ` Adrian Hunter
2026-09-23 15:21 ` [PATCH v2 02/14] perf intel-pt: Preserve return addresses for asynchronous branches Leo Yan
2026-09-24 13:00   ` Adrian Hunter
2026-09-23 15:21 ` [PATCH v2 03/14] perf cs-etm: Break branch history when instruction memory is unavailable Leo Yan
2026-09-23 15:21 ` [PATCH v2 04/14] perf cs-etm: Centralize packet ISA initialization Leo Yan
2026-09-23 15:21 ` [PATCH v2 05/14] perf cs-etm: Use the recorded instruction size for A32 and A64 Leo Yan
2026-09-23 15:21 ` [PATCH v2 06/14] perf cs-etm: Mark branches that were not taken Leo Yan
2026-09-23 15:21 ` [PATCH v2 07/14] perf cs-etm: Factor out final instruction sample synthesis Leo Yan
2026-09-23 15:21 ` [PATCH v2 08/14] perf cs-etm: Centralize branch sample synthesis checks Leo Yan
2026-09-23 15:21 ` [PATCH v2 09/14] perf cs-etm: Classify exception calls using the exception packet Leo Yan
2026-09-23 15:21 ` [PATCH v2 10/14] perf cs-etm: Synthesize exception entries separately from branches Leo Yan
2026-09-23 15:21 ` [PATCH v2 11/14] perf tests: Check CoreSight IRQ entry and exit Leo Yan
2026-09-23 15:21 ` [PATCH v2 12/14] perf tests: Check CoreSight syscall " Leo Yan
2026-09-23 15:21 ` [PATCH v2 13/14] perf tests: Check CoreSight abort " Leo Yan
2026-09-23 15:21 ` [PATCH v2 14/14] perf tests: Check CoreSight emulated instruction " Leo Yan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®