This series adds thread-stack and synthesized callchain support for Arm CoreSight, which comes from older series [1] but heavily rewritten.
CS ETM previously kept last-branch state in a per-trace-queue buffer. That effectively makes the state per CPU, while the call/return history belongs to a thread. This series moves branch tracking to the common thread-stack code.
The series records CoreSight branches with thread_stack__event(), uses thread_stack__br_sample() for last branch entries, flushes thread stacks after decoder resets.
A decoder reset between AUX trace buffers is treated as a global trace discontinuity, so all thread stacks are flushed, so avoids carrying stale call/return history across a trace discontinuity.
One limitation remains for instructions emulated by the kernel. In that case the exception return address may not match the return address stored in the thread stack, because after exception return can be one instruction ahead. The stack can still recover when a later return matches an upper caller. Given emulated instructions are not the common target for performance callchain analysis. Supporting this would require extending the common thread-stack path to accept both the real target address and an adjusted address for stack matching, so this series leaves that extra complexity out.
The series has been tested on Orion6 board:
perf test 136 -vvv 136: CoreSight synthesized callchain: --- start --- test child forked, pid 3539 ---- end(0) ---- 136: CoreSight synthesized callchain : Ok
perf script --itrace=g16i10il64
callchain_test 17468 [005] 1031003.229943: 10 instructions: aaaac32507c4 main+0x8 (/home/kernel/leoy/test_cs_callchain/callchain_test) ffff90bd225c __libc_start_call_main+0x7c (/usr/lib/aarch64-linux-gnu/libc.so.6) ffff90bd233c call_init+0x9c (inlined) ffff90bd233c __libc_start_main_impl+0x9c (inlined) aaaac3250670 _start+0x30 (/home/kernel/leoy/test_cs_callchain/callchain_test)
callchain_test 17468 [005] 1031003.229943: 10 instructions: aaaac3250774 do_svc+0xc (/home/kernel/leoy/test_cs_callchain/callchain_test) aaaac3250798 print+0xc (/home/kernel/leoy/test_cs_callchain/callchain_test) aaaac32507b0 foo+0xc (/home/kernel/leoy/test_cs_callchain/callchain_test) aaaac32507c8 main+0xc (/home/kernel/leoy/test_cs_callchain/callchain_test) ffff90bd225c __libc_start_call_main+0x7c (/usr/lib/aarch64-linux-gnu/libc.so.6) ffff90bd233c call_init+0x9c (inlined) ffff90bd233c __libc_start_main_impl+0x9c (inlined) aaaac3250670 _start+0x30 (/home/kernel/leoy/test_cs_callchain/callchain_test)
callchain_test 17468 [005] 1031003.229944: 10 instructions: ffff800080010c20 vectors+0x420 ([kernel.kallsyms]) aaaac3250784 do_svc+0x1c (/home/kernel/leoy/test_cs_callchain/callchain_test) aaaac3250798 print+0xc (/home/kernel/leoy/test_cs_callchain/callchain_test) aaaac32507b0 foo+0xc (/home/kernel/leoy/test_cs_callchain/callchain_test) aaaac32507c8 main+0xc (/home/kernel/leoy/test_cs_callchain/callchain_test) ffff90bd225c __libc_start_call_main+0x7c (/usr/lib/aarch64-linux-gnu/libc.so.6) ffff90bd233c call_init+0x9c (inlined) ffff90bd233c __libc_start_main_impl+0x9c (inlined) aaaac3250670 _start+0x30 (/home/kernel/leoy/test_cs_callchain/callchain_test)
Note, the test fails on Juno board which is caused by many discontinuity packets (mainly caused by NO_SYNC elem). This is likely caused by the FIFO overflow on the path.
[1] https://lore.kernel.org/linux-arm-kernel/20200220052701.7754-1-leo.yan@linar...
Signed-off-by: Leo Yan leo.yan@arm.com --- Changes in v8: - Updated test_arm_coresight_disasm.sh to pass "--itrace=b" and updated examples in arm-cs-trace-disasm.py (James). - Removed static annotation in callchain workload and renamed functions with prefix "callchain_" to reduce naming conflict (James). - For callchain test pre-condition check, removed the aarch64 check and added the root permission check (James). - Resolved the shellcheck errors (James). - Link to v7: https://lore.kernel.org/r/20260611-b4-arm_cs_callchain_support_v1-v7-0-1ba77...
Changes in v7: - Rebased on the latest perf-tools-next. - Used struct_size() for allocation callchain struct (James). - Added a helper cs_etm__packet_has_taken_branch() (James). - Minor improvements for the callchain test (used record-ctl FIFO and reworked the validation callstack push / pop). - Link to v6: https://lore.kernel.org/r/20260526-b4-arm_cs_callchain_support_v1-v6-0-f9f49...
Changes in v6: - Heavily rewrote the patches since restarted the work after 6 years. - Changed to use the common thread-stack for branch stack and callchain management. - Added a callchain test. - Link to v5: https://lore.kernel.org/linux-arm-kernel/20200220052701.7754-1-leo.yan@linar...
Changes in v5: - Addressed Mike's suggestion for performance improvement for function cs_etm__instr_addr() for quick calculation for non T32; - Removed the patch 'perf cs-etm: Synchronize instruction sample with the thread stack' (Mike); - Fixed the issue for exception is taken for branch target address accessing, for the branch sample and stack thread handling, the related patches are 01, 02, 07; - Fixed the stack thread handling for instruction emulation and single step with patches 08, 09. - Link to v4: https://lore.kernel.org/linux-arm-kernel/20200203020716.31832-1-leo.yan@lina...
Changes in v4: - Split out separate patch set for instruction samples fixing. - Rebased on latest perf/core branch. - Link to v3: https://lore.kernel.org/linux-arm-kernel/20191005091614.11635-1-leo.yan@lina...
--- Leo Yan (8): perf cs-etm: Filter synthesized branch samples perf cs-etm: Decode ETE exception packets perf cs-etm: Refactor instruction size handling perf cs-etm: Use thread-stack for last branch entries perf cs-etm: Flush thread stacks after decoder reset perf cs-etm: Support call indentation perf cs-etm: Synthesize callchains for instruction samples perf test: Add Arm CoreSight callchain test
tools/perf/Documentation/perf-test.txt | 6 +- tools/perf/scripts/python/arm-cs-trace-disasm.py | 9 +- tools/perf/tests/builtin-test.c | 1 + tools/perf/tests/shell/coresight/callchain.sh | 172 ++++++++++ .../shell/coresight/test_arm_coresight_disasm.sh | 4 +- tools/perf/tests/tests.h | 1 + tools/perf/tests/workloads/Build | 2 + tools/perf/tests/workloads/callchain.c | 33 ++ tools/perf/util/cs-etm.c | 351 ++++++++++++--------- 9 files changed, 430 insertions(+), 149 deletions(-) --- base-commit: 7336514f41e75d44782fee7e0990d4195a3d3161 change-id: 20260521-b4-arm_cs_callchain_support_v1-2c2a70719bcc
Best regards,
From: Leo Yan leo.yan@linaro.org
The itrace 'c' and 'r' options request synthesized branch events for calls and returns only. For perf script the default itrace options are "--itrace=ce", so CS ETM should emit call branches and error events by default.
CS ETM currently synthesizes a branch sample for every decoded taken branch whenever branch synthesis is enabled. This produces redundant jump and conditional branch samples.
Add a branch filter derived from the itrace calls and returns options. When neither option is set, keep the existing behavior and synthesize all branch samples. When calls or returns are requested, emit only branch samples whose flags match the selected branch type, while preserving trace begin/end markers.
Also update test_arm_coresight_disasm.sh and arm-cs-trace-disasm.py to use the --itrace=b option for generating branch samples.
Before:
perf script -F,+flags
callchain_test 6114 [005] 331519.825214: 1 branches: tr strt jmp 0 [unknown] ([unknown]) => ffff8000803a3a68 perf_report_aux_output_id+0x50 ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: call ffff8000803a3a74 perf_report_aux_output_id+0x5c ([kernel.kallsyms]) => ffff8000817f4d88 memset+0x0 ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: jmp ffff8000817f4d8c memset+0x4 ([kernel.kallsyms]) => ffff8000817f4c00 __pi_memset_generic+0x0 ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: jcc ffff8000817f4c1c __pi_memset_generic+0x1c ([kernel.kallsyms]) => ffff8000817f4c44 __pi_memset_generic+0x44 ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: jcc ffff8000817f4c4c __pi_memset_generic+0x4c ([kernel.kallsyms]) => ffff8000817f4c5c __pi_memset_generic+0x5c ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: jcc ffff8000817f4c5c __pi_memset_generic+0x5c ([kernel.kallsyms]) => ffff8000817f4cf0 __pi_memset_generic+0xf0 ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: jcc ffff8000817f4d30 __pi_memset_generic+0x130 ([kernel.kallsyms]) => ffff8000817f4d68 __pi_memset_generic+0x168 ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: jcc ffff8000817f4d78 __pi_memset_generic+0x178 ([kernel.kallsyms]) => ffff8000817f4d6c __pi_memset_generic+0x16c ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: jcc ffff8000817f4d78 __pi_memset_generic+0x178 ([kernel.kallsyms]) => ffff8000817f4d6c __pi_memset_generic+0x16c ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: jcc ffff8000817f4d78 __pi_memset_generic+0x178 ([kernel.kallsyms]) => ffff8000817f4d6c __pi_memset_generic+0x16c ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: return ffff8000817f4d84 __pi_memset_generic+0x184 ([kernel.kallsyms]) => ffff8000803a3a78 perf_report_aux_output_id+0x60 ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: jcc ffff8000803a3a98 perf_report_aux_output_id+0x80 ([kernel.kallsyms]) => ffff8000803a3b04 perf_report_aux_output_id+0xec ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: call ffff8000803a3b1c perf_report_aux_output_id+0x104 ([kernel.kallsyms]) => ffff8000803a38f8 __perf_event_header__init_id+0x0 ([kernel.kallsyms])
After:
callchain_test 6114 [005] 331519.825214: 1 branches: tr strt jmp 0 [unknown] ([unknown]) => ffff8000803a3a68 perf_report_aux_output_id+0x50 ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: call ffff8000803a3a74 perf_report_aux_output_id+0x5c ([kernel.kallsyms]) => ffff8000817f4d88 memset+0x0 ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: call ffff8000803a3b1c perf_report_aux_output_id+0x104 ([kernel.kallsyms]) => ffff8000803a38f8 __perf_event_header__init_id+0x0 ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: call ffff8000803a39c0 __perf_event_header__init_id+0xc8 ([kernel.kallsyms]) => ffff800080105258 __task_pid_nr_ns+0x0 ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: call ffff80008010528c __task_pid_nr_ns+0x34 ([kernel.kallsyms]) => ffff8000801d5610 __rcu_read_lock+0x0 ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: call ffff8000801052b0 __task_pid_nr_ns+0x58 ([kernel.kallsyms]) => ffff800080192078 lock_acquire+0x0 ([kernel.kallsyms]) callchain_test 6114 [005] 331519.825214: 1 branches: call ffff8000801923f4 lock_acquire+0x37c ([kernel.kallsyms]) => ffff8000801d6da0 rcu_is_watching+0x0 ([kernel.kallsyms])
Fixes: b12235b113cf ("perf tools: Add mechanic to synthesise CoreSight trace packets") Signed-off-by: Leo Yan leo.yan@linaro.org Reviewed-by: James Clark james.clark@linaro.org Signed-off-by: Leo Yan leo.yan@arm.com --- tools/perf/scripts/python/arm-cs-trace-disasm.py | 9 +++++---- .../tests/shell/coresight/test_arm_coresight_disasm.sh | 4 ++-- tools/perf/util/cs-etm.c | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/tools/perf/scripts/python/arm-cs-trace-disasm.py b/tools/perf/scripts/python/arm-cs-trace-disasm.py index 8f6fa4a007b42fcc98e71b74b36ba3a61d7acb2f..42579f8586842704d3800ad731d4609d2bb968da 100755 --- a/tools/perf/scripts/python/arm-cs-trace-disasm.py +++ b/tools/perf/scripts/python/arm-cs-trace-disasm.py @@ -31,18 +31,19 @@ from perf_trace_context import perf_sample_srccode, perf_config_get # # Output disassembly with objdump and auto detect vmlinux # (when running on same machine.): -# perf script -s scripts/python/arm-cs-trace-disasm.py -d +# perf script --itrace=b -s scripts/python/arm-cs-trace-disasm.py \ +# -- -d # # Output disassembly with llvm-objdump: -# perf script -s scripts/python/arm-cs-trace-disasm.py \ +# perf script --itrace=b -s scripts/python/arm-cs-trace-disasm.py \ # -- -d llvm-objdump-11 -k path/to/vmlinux # # Output accurate disassembly by passing kcore to script: -# perf script -s scripts/python/arm-cs-trace-disasm.py \ +# perf script --itrace=b -s scripts/python/arm-cs-trace-disasm.py \ # -- -d -k perf.data/kcore_dir/kcore # # Output only source line and symbols: -# perf script -s scripts/python/arm-cs-trace-disasm.py +# perf script --itrace=b -s scripts/python/arm-cs-trace-disasm.py
def default_objdump(): config = perf_config_get("annotate.objdump") diff --git a/tools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh b/tools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh index ccb90dda24758522be12cba27140abc9b60d8261..f3ebad5963783e9ae74be5b046d20c3f2e01a5a1 100755 --- a/tools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh +++ b/tools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh @@ -44,7 +44,7 @@ branch_search='[[:space:]](bl|b(.(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al) if [ "$(id -u)" == 0 ] && [ -e /proc/kcore ]; then echo "Testing kernel disassembly" perf record -o ${perfdata} -e cs_etm//k --kcore -Se -m,64K -- touch $file > /dev/null 2>&1 - perf script -i ${perfdata} -s python:${script_path} -- \ + perf script -i ${perfdata} --itrace=b -s python:${script_path} -- \ -d --stop-sample=2 -k ${perfdata}/kcore_dir/kcore 2> /dev/null > ${file} grep -q -E ${branch_search} ${file} echo "Found kernel branches" @@ -56,7 +56,7 @@ fi ## Test user ## echo "Testing userspace disassembly" perf record -o ${perfdata} -e cs_etm//u -Se -m,64K -- touch $file > /dev/null 2>&1 -perf script -i ${perfdata} -s python:${script_path} -- \ +perf script -i ${perfdata} --itrace=b -s python:${script_path} -- \ -d --stop-sample=2 2> /dev/null > ${file} grep -q -E ${branch_search} ${file} echo "Found userspace branches" diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c index 5e92359f51a7cb87a26866ae71466fcce809d551..c2b0f98ceee7671d0e98cfe5673c6f4ec19707a5 100644 --- a/tools/perf/util/cs-etm.c +++ b/tools/perf/util/cs-etm.c @@ -70,6 +70,7 @@ struct cs_etm_auxtrace { int num_cpu; u64 latest_kernel_timestamp; u32 auxtrace_type; + u32 branches_filter; u64 branches_sample_type; u64 branches_id; u64 instructions_sample_type; @@ -1681,6 +1682,10 @@ static int cs_etm__synth_branch_sample(struct cs_etm_queue *etmq, } dummy_bs; u64 ip;
+ if (etm->branches_filter && + !(etm->branches_filter & tidq->prev_packet->flags)) + return 0; + ip = cs_etm__last_executed_instr(tidq->prev_packet);
event->sample.header.type = PERF_RECORD_SAMPLE; @@ -3517,6 +3522,16 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, etm->synth_opts.callchain = false; }
+ if (etm->synth_opts.calls) + etm->branches_filter |= PERF_IP_FLAG_CALL | + PERF_IP_FLAG_TRACE_BEGIN | + PERF_IP_FLAG_TRACE_END; + + if (etm->synth_opts.returns) + etm->branches_filter |= PERF_IP_FLAG_RETURN | + PERF_IP_FLAG_TRACE_BEGIN | + PERF_IP_FLAG_TRACE_END; + etm->session = session;
etm->num_cpu = num_cpu;
ETE shares the same packet format as ETMv4, but exception decoding handled ETMv4 packets only. As a result, ETE exception packets were not classified.
Recognize the ETE magic for exception number decoding.
Reviewed-by: James Clark james.clark@linaro.org Signed-off-by: Leo Yan leo.yan@arm.com --- tools/perf/util/cs-etm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c index c2b0f98ceee7671d0e98cfe5673c6f4ec19707a5..b4d598ccabbd2551affdc8feed5c63bac4fee98d 100644 --- a/tools/perf/util/cs-etm.c +++ b/tools/perf/util/cs-etm.c @@ -2176,7 +2176,7 @@ static bool cs_etm__is_syscall(struct cs_etm_queue *etmq, * HVC cases; need to check if it's SVC instruction based on * packet address. */ - if (magic == __perf_cs_etmv4_magic) { + 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)) @@ -2199,7 +2199,7 @@ static bool cs_etm__is_async_exception(struct cs_etm_traceid_queue *tidq, packet->exception_number == CS_ETMV3_EXC_FIQ) return true;
- if (magic == __perf_cs_etmv4_magic) + if (magic == __perf_cs_etmv4_magic || magic == __perf_cs_ete_magic) if (packet->exception_number == CS_ETMV4_EXC_RESET || packet->exception_number == CS_ETMV4_EXC_DEBUG_HALT || packet->exception_number == CS_ETMV4_EXC_SYSTEM_ERROR || @@ -2229,7 +2229,7 @@ static bool cs_etm__is_sync_exception(struct cs_etm_queue *etmq, packet->exception_number == CS_ETMV3_EXC_GENERIC) return true;
- if (magic == __perf_cs_etmv4_magic) { + if (magic == __perf_cs_etmv4_magic || magic == __perf_cs_ete_magic) { if (packet->exception_number == CS_ETMV4_EXC_TRAP || packet->exception_number == CS_ETMV4_EXC_ALIGNMENT || packet->exception_number == CS_ETMV4_EXC_INST_FAULT ||
From: Leo Yan leo.yan@linaro.org
This patch introduces a new function cs_etm__instr_size() to calculate the instruction size based on ISA type and instruction address.
Given the trace data can be MB and most likely that will be A64/A32 on a lot of platforms, cs_etm__instr_addr() keeps a single ISA type check for A64/A32 and executes an optimized calculation (addr + offset * 4).
Signed-off-by: Leo Yan leo.yan@linaro.org Reviewed-by: James Clark james.clark@linaro.org Signed-off-by: Leo Yan leo.yan@arm.com --- tools/perf/util/cs-etm.c | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-)
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c index b4d598ccabbd2551affdc8feed5c63bac4fee98d..4127120459418389ca7aabb9a49dead2b50e7533 100644 --- a/tools/perf/util/cs-etm.c +++ b/tools/perf/util/cs-etm.c @@ -1366,6 +1366,18 @@ static inline int cs_etm__t32_instr_size(struct cs_etm_queue *etmq, return ((instrBytes[1] & 0xF8) >= 0xE8) ? 4 : 2; }
+static inline int cs_etm__instr_size(struct cs_etm_queue *etmq, + struct cs_etm_traceid_queue *tidq, + struct cs_etm_packet *packet, + u64 addr) +{ + 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; +} + static inline u64 cs_etm__first_executed_instr(struct cs_etm_packet *packet) { /* @@ -1394,19 +1406,17 @@ static inline u64 cs_etm__instr_addr(struct cs_etm_queue *etmq, struct cs_etm_packet *packet, u64 offset) { - if (packet->isa == CS_ETM_ISA_T32) { - u64 addr = packet->start_addr; + u64 addr = packet->start_addr;
- while (offset) { - addr += cs_etm__t32_instr_size(etmq, tidq, packet, - addr); - offset--; - } - return addr; - } + /* 4-byte instruction size for A32/A64 */ + if (packet->isa == CS_ETM_ISA_A64 || packet->isa == CS_ETM_ISA_A32) + return addr + offset * 4;
- /* Assume a 4 byte instruction size (A32/A64) */ - return packet->start_addr + offset * 4; + while (offset) { + addr += cs_etm__instr_size(etmq, tidq, packet, addr); + offset--; + } + return addr; }
static void cs_etm__update_last_branch_rb(struct cs_etm_queue *etmq, @@ -1576,16 +1586,7 @@ static void cs_etm__copy_insn(struct cs_etm_queue *etmq, return; }
- /* - * T32 instruction size might be 32-bit or 16-bit, decide by calling - * cs_etm__t32_instr_size(). - */ - if (packet->isa == CS_ETM_ISA_T32) - sample->insn_len = cs_etm__t32_instr_size(etmq, tidq, packet, - sample->ip); - /* Otherwise, A64 and A32 instruction size are always 32-bit. */ - else - sample->insn_len = 4; + sample->insn_len = cs_etm__instr_size(etmq, tidq, packet, sample->ip);
cs_etm__frontend_mem_access(etmq, tidq, packet, sample->ip, sample->insn_len, (void *)sample->insn);
CS ETM maintains its own circular array for last branch entries, with local helpers to update, copy and reset the branch stack. This duplicates logic already provided by the common code.
Record taken branches with thread_stack__event() and synthesize PERF_SAMPLE_BRANCH_STACK data with thread_stack__br_sample(). This removes the private last_branch_rb buffer and its position tracking.
This also makes the branch history state belong to the thread rather than the trace queue. That is a better fit for CoreSight traces where a trace queue can effectively be CPU scoped, while call/return history is per thread.
Keep the buffer number updated via thread_stack__set_trace_nr(), which is used when exporting samples to Python scripts. Pass callstack=false for now; synthesized callchains are added by a later patch.
The output should remain same, except that be->flags.predicted is no longer set. Since CoreSight trace does not provide branch prediction information, clearing the flag avoids confusion.
Reviewed-by: James Clark james.clark@linaro.org Signed-off-by: Leo Yan leo.yan@arm.com --- tools/perf/util/cs-etm.c | 159 ++++++++++++++--------------------------------- 1 file changed, 46 insertions(+), 113 deletions(-)
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c index 4127120459418389ca7aabb9a49dead2b50e7533..8798bf0471faf3b1813780b45c588263ff6b4416 100644 --- a/tools/perf/util/cs-etm.c +++ b/tools/perf/util/cs-etm.c @@ -84,10 +84,9 @@ struct cs_etm_auxtrace { struct cs_etm_traceid_queue { u8 trace_chan_id; u64 period_instructions; - size_t last_branch_pos; union perf_event *event_buf; + unsigned int br_stack_sz; struct branch_stack *last_branch; - struct branch_stack *last_branch_rb; struct cs_etm_packet *prev_packet; struct cs_etm_packet *packet; struct cs_etm_packet_queue packet_queue; @@ -644,9 +643,8 @@ static int cs_etm__init_traceid_queue(struct cs_etm_queue *etmq, tidq->last_branch = zalloc(sz); if (!tidq->last_branch) goto out_free; - tidq->last_branch_rb = zalloc(sz); - if (!tidq->last_branch_rb) - goto out_free; + + tidq->br_stack_sz = etm->synth_opts.last_branch_sz; }
tidq->event_buf = malloc(PERF_SAMPLE_MAX_SIZE); @@ -656,7 +654,6 @@ static int cs_etm__init_traceid_queue(struct cs_etm_queue *etmq, return 0;
out_free: - zfree(&tidq->last_branch_rb); zfree(&tidq->last_branch); zfree(&tidq->prev_packet); zfree(&tidq->packet); @@ -939,7 +936,6 @@ static void cs_etm__free_traceid_queues(struct cs_etm_queue *etmq) thread__zput(tidq->decode_thread); zfree(&tidq->event_buf); zfree(&tidq->last_branch); - zfree(&tidq->last_branch_rb); zfree(&tidq->prev_packet); zfree(&tidq->packet); zfree(&tidq); @@ -1299,57 +1295,6 @@ static int cs_etm__queue_first_cs_timestamp(struct cs_etm_auxtrace *etm, return ret; }
-static inline -void cs_etm__copy_last_branch_rb(struct cs_etm_queue *etmq, - struct cs_etm_traceid_queue *tidq) -{ - struct branch_stack *bs_src = tidq->last_branch_rb; - struct branch_stack *bs_dst = tidq->last_branch; - size_t nr = 0; - - /* - * Set the number of records before early exit: ->nr is used to - * determine how many branches to copy from ->entries. - */ - bs_dst->nr = bs_src->nr; - - /* - * Early exit when there is nothing to copy. - */ - if (!bs_src->nr) - return; - - /* - * As bs_src->entries is a circular buffer, we need to copy from it in - * two steps. First, copy the branches from the most recently inserted - * branch ->last_branch_pos until the end of bs_src->entries buffer. - */ - nr = etmq->etm->synth_opts.last_branch_sz - tidq->last_branch_pos; - memcpy(&bs_dst->entries[0], - &bs_src->entries[tidq->last_branch_pos], - sizeof(struct branch_entry) * nr); - - /* - * If we wrapped around at least once, the branches from the beginning - * of the bs_src->entries buffer and until the ->last_branch_pos element - * are older valid branches: copy them over. The total number of - * branches copied over will be equal to the number of branches asked by - * the user in last_branch_sz. - */ - if (bs_src->nr >= etmq->etm->synth_opts.last_branch_sz) { - memcpy(&bs_dst->entries[nr], - &bs_src->entries[0], - sizeof(struct branch_entry) * tidq->last_branch_pos); - } -} - -static inline -void cs_etm__reset_last_branch_rb(struct cs_etm_traceid_queue *tidq) -{ - tidq->last_branch_pos = 0; - tidq->last_branch_rb->nr = 0; -} - static inline int cs_etm__t32_instr_size(struct cs_etm_queue *etmq, struct cs_etm_traceid_queue *tidq, struct cs_etm_packet *packet, u64 addr) @@ -1419,38 +1364,6 @@ static inline u64 cs_etm__instr_addr(struct cs_etm_queue *etmq, return addr; }
-static void cs_etm__update_last_branch_rb(struct cs_etm_queue *etmq, - struct cs_etm_traceid_queue *tidq) -{ - struct branch_stack *bs = tidq->last_branch_rb; - struct branch_entry *be; - - /* - * The branches are recorded in a circular buffer in reverse - * chronological order: we start recording from the last element of the - * buffer down. After writing the first element of the stack, move the - * insert position back to the end of the buffer. - */ - if (!tidq->last_branch_pos) - tidq->last_branch_pos = etmq->etm->synth_opts.last_branch_sz; - - tidq->last_branch_pos -= 1; - - be = &bs->entries[tidq->last_branch_pos]; - be->from = cs_etm__last_executed_instr(tidq->prev_packet); - be->to = cs_etm__first_executed_instr(tidq->packet); - /* No support for mispredict */ - be->flags.mispred = 0; - be->flags.predicted = 1; - - /* - * Increment bs->nr until reaching the number of last branches asked by - * the user on the command line. - */ - if (bs->nr < etmq->etm->synth_opts.last_branch_sz) - bs->nr += 1; -} - static int cs_etm__inject_event(struct cs_etm_auxtrace *etm, union perf_event *event, struct perf_sample *sample, u64 type) { @@ -1614,6 +1527,42 @@ 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) +{ + if (packet->sample_type == CS_ETM_RANGE && + packet->last_instr_taken_branch) + return true; + + return false; +} + +static void cs_etm__add_stack_event(struct cs_etm_queue *etmq, + struct cs_etm_traceid_queue *tidq) +{ + u64 from, to; + int size; + + if (!cs_etm__packet_has_taken_branch(tidq->prev_packet)) + return; + + if (etmq->etm->synth_opts.last_branch) { + from = cs_etm__last_executed_instr(tidq->prev_packet); + to = cs_etm__first_executed_instr(tidq->packet); + + size = cs_etm__instr_size(etmq, tidq, tidq->prev_packet, from); + + /* Enable callchain so thread stack entry can be allocated */ + thread_stack__event(tidq->frontend_thread, tidq->prev_packet->cpu, + tidq->prev_packet->flags, from, to, size, + etmq->buffer->buffer_nr + 1, false, + tidq->br_stack_sz, 0); + } else { + thread_stack__set_trace_nr(tidq->frontend_thread, + tidq->prev_packet->cpu, + etmq->buffer->buffer_nr + 1); + } +} + static int cs_etm__synth_instruction_sample(struct cs_etm_queue *etmq, struct cs_etm_traceid_queue *tidq, struct cs_etm_packet *packet, @@ -1644,8 +1593,11 @@ static int cs_etm__synth_instruction_sample(struct cs_etm_queue *etmq,
cs_etm__copy_insn(etmq, tidq, packet, &sample);
- if (etm->synth_opts.last_branch) + if (etm->synth_opts.last_branch) { + thread_stack__br_sample(tidq->frontend_thread, tidq->packet->cpu, + tidq->last_branch, tidq->br_stack_sz); sample.branch_stack = tidq->last_branch; + }
if (etm->synth_opts.inject) { ret = cs_etm__inject_event(etm, event, &sample, @@ -1836,14 +1788,7 @@ static int cs_etm__sample(struct cs_etm_queue *etmq,
tidq->period_instructions += tidq->packet->instr_count;
- /* - * Record a branch when the last instruction in - * PREV_PACKET is a branch. - */ - if (etm->synth_opts.last_branch && - tidq->prev_packet->sample_type == CS_ETM_RANGE && - tidq->prev_packet->last_instr_taken_branch) - cs_etm__update_last_branch_rb(etmq, tidq); + cs_etm__add_stack_event(etmq, tidq);
if (etm->synth_opts.instructions && tidq->period_instructions >= etm->instructions_sample_period) { @@ -1902,10 +1847,6 @@ static int cs_etm__sample(struct cs_etm_queue *etmq, u64 offset = etm->instructions_sample_period - instrs_prev; u64 addr;
- /* Prepare last branches for instruction sample */ - if (etm->synth_opts.last_branch) - cs_etm__copy_last_branch_rb(etmq, tidq); - while (tidq->period_instructions >= etm->instructions_sample_period) { /* @@ -1936,8 +1877,7 @@ static int cs_etm__sample(struct cs_etm_queue *etmq, generate_sample = true;
/* Generate sample for branch taken packet */ - if (tidq->prev_packet->sample_type == CS_ETM_RANGE && - tidq->prev_packet->last_instr_taken_branch) + if (cs_etm__packet_has_taken_branch(tidq->prev_packet)) generate_sample = true;
if (generate_sample) { @@ -1985,10 +1925,6 @@ static int cs_etm__flush(struct cs_etm_queue *etmq, etmq->etm->synth_opts.instructions && tidq->prev_packet->sample_type == CS_ETM_RANGE) { u64 addr; - - /* Prepare last branches for instruction sample */ - cs_etm__copy_last_branch_rb(etmq, tidq); - /* * Generate a last branch event for the branches left in the * circular buffer at the end of the trace. @@ -2020,7 +1956,7 @@ static int cs_etm__flush(struct cs_etm_queue *etmq,
/* Reset last branches after flush the trace */ if (etm->synth_opts.last_branch) - cs_etm__reset_last_branch_rb(tidq); + thread_stack__flush(tidq->frontend_thread);
return err; } @@ -2044,9 +1980,6 @@ static int cs_etm__end_block(struct cs_etm_queue *etmq, tidq->prev_packet->sample_type == CS_ETM_RANGE) { u64 addr;
- /* Prepare last branches for instruction sample */ - cs_etm__copy_last_branch_rb(etmq, tidq); - /* * Use the address of the end of the last reported execution * range.
Perf resets the CoreSight decoder when moving to a new AUX trace buffer, this causes trace discontinunity globally.
For callchain synthesis, keeping thread-stack state after decoder reset can leave stale call/return history attached to threads that are decoded later, producing incorrect synthesized callchains.
Flush all host thread stacks after a decoder reset. When virtualization is present, flush the guest thread stacks as well.
Reviewed-by: James Clark james.clark@linaro.org Signed-off-by: Leo Yan leo.yan@arm.com --- tools/perf/util/cs-etm.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+)
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c index 8798bf0471faf3b1813780b45c588263ff6b4416..7069b4990e6107fdece3cc5451142714f1d627ef 100644 --- a/tools/perf/util/cs-etm.c +++ b/tools/perf/util/cs-etm.c @@ -1997,6 +1997,37 @@ static int cs_etm__end_block(struct cs_etm_queue *etmq,
return 0; } + +static int cs_etm__flush_stack_cb(struct thread *thread, + void *data __maybe_unused) +{ + thread_stack__flush(thread); + return 0; +} + +static void cs_etm__flush_machine_stack(struct cs_etm_queue *etmq, pid_t pid) +{ + struct machine *machine; + + machine = machines__find(&etmq->etm->session->machines, pid); + if (machine) + machine__for_each_thread(machine, cs_etm__flush_stack_cb, NULL); +} + +static void cs_etm__flush_all_stack(struct cs_etm_queue *etmq) +{ + enum cs_etm_pid_fmt pid_fmt = cs_etm__get_pid_fmt(etmq); + + if (!etmq->etm->synth_opts.last_branch) + return; + + cs_etm__flush_machine_stack(etmq, HOST_KERNEL_ID); + + /* Clear the guest stack if virtualization is supported */ + if (pid_fmt == CS_ETM_PIDFMT_CTXTID2) + cs_etm__flush_machine_stack(etmq, DEFAULT_GUEST_KERNEL_ID); +} + /* * cs_etm__get_data_block: Fetch a block from the auxtrace_buffer queue * if need be. @@ -2019,6 +2050,12 @@ static int cs_etm__get_data_block(struct cs_etm_queue *etmq) ret = cs_etm_decoder__reset(etmq->decoder); if (ret) return ret; + + /* + * Since the decoder is reset, this causes a global trace + * discontinuity. Flush all thread stacks. + */ + cs_etm__flush_all_stack(etmq); }
return etmq->buf_len;
From: Leo Yan leo.yan@linaro.org
The perf script callindent is derived from call stack in thread context, CS ETM ignores the requirement for callindent without pushing and poping call stack.
Enable thread-stack when either itrace thread-stack support or last branch entries are requested, allocate the branch stack storage accordingly, and feed taken branches to thread_stack__event() whenever thread-stack state is needed.
When callindent is requested, pass callstack=true to thread_stack__event() so the common thread-stack code maintains call depth for branch samples.
Before:
perf script -F +callindent
callchain_test 6543 [002] 1 branches: main ffff93252258 __libc_start_call_main+0x78 (/usr/lib/aarch64-linux-gnu/libc.so.6) callchain_test 6543 [002] 1 branches: foo aaaad6b607c4 main+0x8 (/home/kernel/leoy/test_cs_callchain/callchain_test) callchain_test 6543 [002] 1 branches: print aaaad6b607ac foo+0x8 (/home/kernel/leoy/test_cs_callchain/callchain_test) callchain_test 6543 [002] 1 branches: do_svc aaaad6b60794 print+0x8 (/home/kernel/leoy/test_cs_callchain/callchain_test) callchain_test 6543 [002] 1 branches: vectors aaaad6b60780 do_svc+0x18 (/home/kernel/leoy/test_cs_callchain/callchain_test) callchain_test 6543 [002] 1 branches: el0t_64_sync_handler ffff80008001159c el0t_64_sync+0x194 ([kernel.kallsyms]) callchain_test 6543 [002] 1 branches: el0_svc ffff800081829194 el0t_64_sync_handler+0x9c ([kernel.kallsyms]) callchain_test 6543 [002] 1 branches: lockdep_hardirqs_off ffff800081828794 el0_svc+0x24 ([kernel.kallsyms]) callchain_test 6543 [002] 1 branches: __this_cpu_preempt_check ffff80008182b348 lockdep_hardirqs_off+0xf0 ([kernel.kallsyms])
After:
callchain_test 6543 [002] 1 branches: main ffff93252258 __libc_start_call_main+0x78 (/usr/lib/aarch64-linux-gnu/libc.so.6) callchain_test 6543 [002] 1 branches: foo aaaad6b607c4 main+0x8 (/home/kernel/leoy/test_cs_callchain/callchain_test) callchain_test 6543 [002] 1 branches: print aaaad6b607ac foo+0x8 (/home/kernel/leoy/test_cs_callchain/callchain_test) callchain_test 6543 [002] 1 branches: do_svc aaaad6b60794 print+0x8 (/home/kernel/leoy/test_cs_callchain/callchain_test) callchain_test 6543 [002] 1 branches: vectors aaaad6b60780 do_svc+0x18 (/home/kernel/leoy/test_cs_callchain/callchain_test) callchain_test 6543 [002] 1 branches: el0t_64_sync_handler ffff80008001159c el0t_64_sync+0x194 ([kernel.kallsyms]) callchain_test 6543 [002] 1 branches: el0_svc ffff800081829194 el0t_64_sync_handler+0x9c ([kernel.kallsyms]) callchain_test 6543 [002] 1 branches: lockdep_hardirqs_off ffff800081828794 el0_svc+0x24 ([kernel.kallsyms]) callchain_test 6543 [002] 1 branches: __this_cpu_preempt_check ffff80008182b348 lockdep_hardirqs_off+0xf0 ([kernel.kallsyms])
Signed-off-by: Leo Yan leo.yan@linaro.org Reviewed-by: James Clark james.clark@linaro.org Signed-off-by: Leo Yan leo.yan@arm.com --- tools/perf/util/cs-etm.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c index 7069b4990e6107fdece3cc5451142714f1d627ef..830618763d8b1bdcc015c492d7b2354d862566ca 100644 --- a/tools/perf/util/cs-etm.c +++ b/tools/perf/util/cs-etm.c @@ -66,6 +66,8 @@ struct cs_etm_auxtrace { bool snapshot_mode; bool data_queued; bool has_virtual_ts; /* Virtual/Kernel timestamps in the trace. */ + bool use_thread_stack; + bool use_callchain;
int num_cpu; u64 latest_kernel_timestamp; @@ -635,7 +637,7 @@ static int cs_etm__init_traceid_queue(struct cs_etm_queue *etmq, if (!tidq->prev_packet) goto out_free;
- if (etm->synth_opts.last_branch) { + if (etm->use_thread_stack) { size_t sz = sizeof(struct branch_stack);
sz += etm->synth_opts.last_branch_sz * @@ -1545,7 +1547,7 @@ static void cs_etm__add_stack_event(struct cs_etm_queue *etmq, if (!cs_etm__packet_has_taken_branch(tidq->prev_packet)) return;
- if (etmq->etm->synth_opts.last_branch) { + if (etmq->etm->use_thread_stack) { from = cs_etm__last_executed_instr(tidq->prev_packet); to = cs_etm__first_executed_instr(tidq->packet);
@@ -1554,7 +1556,8 @@ static void cs_etm__add_stack_event(struct cs_etm_queue *etmq, /* Enable callchain so thread stack entry can be allocated */ thread_stack__event(tidq->frontend_thread, tidq->prev_packet->cpu, tidq->prev_packet->flags, from, to, size, - etmq->buffer->buffer_nr + 1, false, + etmq->buffer->buffer_nr + 1, + etmq->etm->use_callchain, tidq->br_stack_sz, 0); } else { thread_stack__set_trace_nr(tidq->frontend_thread, @@ -1955,7 +1958,7 @@ static int cs_etm__flush(struct cs_etm_queue *etmq, cs_etm__packet_swap(etm, tidq);
/* Reset last branches after flush the trace */ - if (etm->synth_opts.last_branch) + if (etm->use_thread_stack) thread_stack__flush(tidq->frontend_thread);
return err; @@ -2018,7 +2021,7 @@ static void cs_etm__flush_all_stack(struct cs_etm_queue *etmq) { enum cs_etm_pid_fmt pid_fmt = cs_etm__get_pid_fmt(etmq);
- if (!etmq->etm->synth_opts.last_branch) + if (!etmq->etm->use_thread_stack) return;
cs_etm__flush_machine_stack(etmq, HOST_KERNEL_ID); @@ -3491,6 +3494,7 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, itrace_synth_opts__set_default(&etm->synth_opts, session->itrace_synth_opts->default_no_sample); etm->synth_opts.callchain = false; + etm->synth_opts.thread_stack = session->itrace_synth_opts->thread_stack; }
if (etm->synth_opts.calls) @@ -3552,6 +3556,12 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, etm->tc.cap_user_time_zero = tc->cap_user_time_zero; etm->tc.cap_user_time_short = tc->cap_user_time_short; } + + etm->use_thread_stack = etm->synth_opts.thread_stack || + etm->synth_opts.last_branch; + + etm->use_callchain = etm->synth_opts.thread_stack; + err = cs_etm__synth_events(etm, session); if (err) goto err_free_queues;
From: Leo Yan leo.yan@linaro.org
CS ETM already records branches into the thread stack, but instruction samples do not carry synthesized callchains. It misses to support the callchain and no output with the itrace option 'g'.
Allocate a callchain buffer per queue and use thread_stack__sample() when synthesizing instruction samples.
Advertise PERF_SAMPLE_CALLCHAIN on the synthetic instruction event. Allocate one extra callchain entry than requested, as the first entry is reserved for storing context information.
cs_etm__context() is introduced for handling context packet and update the thread info and start kernel address for frontend decoding.
After:
perf script --itrace=g16l64i1i
callchain_test 6543 [002] 1 instructions: ffff800080010c14 vectors+0x414 ([kernel.kallsyms]) aaaad6b60784 do_svc+0x1c (/home/kernel/leoy/test_cs_callchain/callchain_test) aaaad6b60798 print+0xc (/home/kernel/leoy/test_cs_callchain/callchain_test) aaaad6b607b0 foo+0xc (/home/kernel/leoy/test_cs_callchain/callchain_test) aaaad6b607c8 main+0xc (/home/kernel/leoy/test_cs_callchain/callchain_test) ffff9325225c __libc_start_call_main+0x7c (/usr/lib/aarch64-linux-gnu/libc.so.6) ffff9325233c call_init+0x9c (inlined) ffff9325233c __libc_start_main_impl+0x9c (inlined) aaaad6b60670 _start+0x30 (/home/kernel/leoy/test_cs_callchain/callchain_test) ffff800080012290 ret_to_user+0x120 ([kernel.kallsyms])
Signed-off-by: Leo Yan leo.yan@linaro.org Reviewed-by: James Clark james.clark@linaro.org Signed-off-by: Leo Yan leo.yan@arm.com --- tools/perf/util/cs-etm.c | 83 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 5 deletions(-)
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c index 830618763d8b1bdcc015c492d7b2354d862566ca..f37aa41b3587aad063ea464bc460fe3438bd039d 100644 --- a/tools/perf/util/cs-etm.c +++ b/tools/perf/util/cs-etm.c @@ -17,6 +17,7 @@ #include <stdlib.h>
#include "auxtrace.h" +#include "callchain.h" #include "color.h" #include "cs-etm.h" #include "cs-etm-decoder/cs-etm-decoder.h" @@ -86,9 +87,11 @@ struct cs_etm_auxtrace { struct cs_etm_traceid_queue { u8 trace_chan_id; u64 period_instructions; + u64 kernel_start; union perf_event *event_buf; unsigned int br_stack_sz; struct branch_stack *last_branch; + struct ip_callchain *callchain; struct cs_etm_packet *prev_packet; struct cs_etm_packet *packet; struct cs_etm_packet_queue packet_queue; @@ -649,6 +652,15 @@ static int cs_etm__init_traceid_queue(struct cs_etm_queue *etmq, tidq->br_stack_sz = etm->synth_opts.last_branch_sz; }
+ if (etm->synth_opts.callchain) { + /* Add 1 to callchain_sz for callchain context */ + tidq->callchain = + zalloc(struct_size(tidq->callchain, ips, + etm->synth_opts.callchain_sz + 1)); + if (!tidq->callchain) + goto out_free; + } + tidq->event_buf = malloc(PERF_SAMPLE_MAX_SIZE); if (!tidq->event_buf) goto out_free; @@ -656,6 +668,7 @@ static int cs_etm__init_traceid_queue(struct cs_etm_queue *etmq, return 0;
out_free: + zfree(&tidq->callchain); zfree(&tidq->last_branch); zfree(&tidq->prev_packet); zfree(&tidq->packet); @@ -937,6 +950,7 @@ static void cs_etm__free_traceid_queues(struct cs_etm_queue *etmq) thread__zput(tidq->frontend_thread); thread__zput(tidq->decode_thread); zfree(&tidq->event_buf); + zfree(&tidq->callchain); zfree(&tidq->last_branch); zfree(&tidq->prev_packet); zfree(&tidq->packet); @@ -1602,6 +1616,26 @@ static int cs_etm__synth_instruction_sample(struct cs_etm_queue *etmq, sample.branch_stack = tidq->last_branch; }
+ if (etm->synth_opts.callchain) { + if (tidq->kernel_start) + thread_stack__sample(tidq->frontend_thread, + tidq->packet->cpu, + tidq->callchain, + etm->synth_opts.callchain_sz + 1, + sample.ip, tidq->kernel_start); + else + /* + * Clear the callchain when the kernel start address is + * not available yet. The empty callchain can then be + * consumed by cs_etm__inject_event(). + */ + memset(tidq->callchain, 0, + struct_size(tidq->callchain, ips, + etm->synth_opts.callchain_sz + 1)); + + sample.callchain = tidq->callchain; + } + if (etm->synth_opts.inject) { ret = cs_etm__inject_event(etm, event, &sample, etm->instructions_sample_type); @@ -1764,6 +1798,9 @@ static int cs_etm__synth_events(struct cs_etm_auxtrace *etm, attr.branch_sample_type |= PERF_SAMPLE_BRANCH_HW_INDEX; }
+ if (etm->synth_opts.callchain) + attr.sample_type |= PERF_SAMPLE_CALLCHAIN; + if (etm->synth_opts.instructions) { attr.config = PERF_COUNT_HW_INSTRUCTIONS; attr.sample_period = etm->synth_opts.period; @@ -1895,6 +1932,34 @@ static int cs_etm__sample(struct cs_etm_queue *etmq, return 0; }
+static int cs_etm__context(struct cs_etm_queue *etmq, + struct cs_etm_traceid_queue *tidq) +{ + ocsd_ex_level el = tidq->packet->el; + struct machine *machine; + int ret; + + machine = cs_etm__get_machine(etmq, el); + if (!machine) { + ret = -EINVAL; + goto err; + } + + tidq->kernel_start = machine__kernel_start(machine); + + ret = cs_etm__etmq_update_thread(etmq, el, tidq->packet->tid, + &tidq->frontend_thread); + if (ret) + goto err; + + return 0; + +err: + tidq->frontend_thread = NULL; + tidq->kernel_start = 0; + return ret; +} + static int cs_etm__exception(struct cs_etm_traceid_queue *tidq) { /* @@ -2487,9 +2552,7 @@ static int cs_etm__process_traceid_queue(struct cs_etm_queue *etmq, * tracing the kernel the context packet will be emitted * between two ranges. */ - ret = cs_etm__etmq_update_thread(etmq, tidq->packet->el, - tidq->packet->tid, - &tidq->frontend_thread); + ret = cs_etm__context(etmq, tidq); if (ret) goto out; break; @@ -3507,6 +3570,14 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, PERF_IP_FLAG_TRACE_BEGIN | PERF_IP_FLAG_TRACE_END;
+ if (etm->synth_opts.callchain && !symbol_conf.use_callchain) { + symbol_conf.use_callchain = true; + if (callchain_register_param(&callchain_param) < 0) { + symbol_conf.use_callchain = false; + etm->synth_opts.callchain = false; + } + } + etm->session = session;
etm->num_cpu = num_cpu; @@ -3558,9 +3629,11 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, }
etm->use_thread_stack = etm->synth_opts.thread_stack || - etm->synth_opts.last_branch; + etm->synth_opts.last_branch || + etm->synth_opts.callchain;
- etm->use_callchain = etm->synth_opts.thread_stack; + etm->use_callchain = etm->synth_opts.thread_stack || + etm->synth_opts.callchain;
err = cs_etm__synth_events(etm, session); if (err)
Add a CoreSight shell test for synthesized callchains.
The test uses the new callchain workload to generate trace and decodes it with synthesis callchain. It then verifies that the instruction samples show the expected callchain push and pop.
Use control FIFOs so tracing starts only around the workload, which keeps the trace data small. The test is limited to with the cs_etm event available and root permission.
After:
perf test 138 -vvv 138: CoreSight synthesized callchain: ---- start ---- test child forked, pid 35581 Callchain flow matched: l1=4642868 l2=4642880 l3=4642895 l4=4642919 l5=4670494 l6=4670500 l7=4670520 ---- end(0) ---- 138: CoreSight synthesized callchain : Ok
Assisted-by: Codex:GPT-5.5 Signed-off-by: Leo Yan leo.yan@arm.com --- tools/perf/Documentation/perf-test.txt | 6 +- tools/perf/tests/builtin-test.c | 1 + tools/perf/tests/shell/coresight/callchain.sh | 172 ++++++++++++++++++++++++++ tools/perf/tests/tests.h | 1 + tools/perf/tests/workloads/Build | 2 + tools/perf/tests/workloads/callchain.c | 33 +++++ 6 files changed, 213 insertions(+), 2 deletions(-)
diff --git a/tools/perf/Documentation/perf-test.txt b/tools/perf/Documentation/perf-test.txt index 81c8525f594680d814f80e6f88bcce8d867bb350..859df74e62efc4b1e80da13ae8e053356f68ae54 100644 --- a/tools/perf/Documentation/perf-test.txt +++ b/tools/perf/Documentation/perf-test.txt @@ -57,7 +57,8 @@ OPTIONS --workload=:: Run a built-in workload, to list them use '--list-workloads', current ones include: noploop, thloop, leafloop, sqrtloop, brstack, datasym, - context_switch_loop, deterministic, named_threads and landlock. + context_switch_loop, deterministic, named_threads, landlock and + callchain.
Used with the shell script regression tests.
@@ -69,7 +70,8 @@ OPTIONS 'named_threads' accepts the number of threads and the number of loops to do in each thread.
- The datasym, landlock and deterministic workloads don't accept any. + The datasym, landlock, deterministic and callchain workloads don't accept + any.
--list-workloads:: List the available workloads to use with -w/--workload. diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index afc06cec49546d29d86b94840c7021c5bf5c88e3..8994488cc206863ba77f7e7e5803e62f18e151ba 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -166,6 +166,7 @@ static struct test_workload *workloads[] = { &workload__jitdump, &workload__context_switch_loop, &workload__deterministic, + &workload__callchain,
#ifdef HAVE_RUST_SUPPORT &workload__code_with_type, diff --git a/tools/perf/tests/shell/coresight/callchain.sh b/tools/perf/tests/shell/coresight/callchain.sh new file mode 100755 index 0000000000000000000000000000000000000000..13cca7dc11184002e3ddc058c0d0ffa1c458c483 --- /dev/null +++ b/tools/perf/tests/shell/coresight/callchain.sh @@ -0,0 +1,172 @@ +#!/bin/bash +# CoreSight synthesized callchain (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +glb_err=1 + +if ! tmpdir=$(mktemp -d /tmp/perf-cs-callchain-test.XXXXXX); then + echo "mktemp failed" + exit 1 +fi + +cleanup_files() +{ + rm -rf "$tmpdir" +} + +trap cleanup_files EXIT +trap 'cleanup_files; exit $glb_err' TERM INT + +skip_if_system_is_not_ready() +{ + perf list | grep -Pzq 'cs_etm//' || { + echo "[Skip] cs_etm event is not available" >&2 + return 2 + } + + # Requires root for trace in kernel + [ "$(id -u)" = 0 ] || { + echo "[Skip] No root permission" >&2 + return 2 + } + + return 0 +} + +record_trace() +{ + local data=$1 + local script=$2 + + local cf="$tmpdir/ctl" + local af="$tmpdir/ack" + + mkfifo "$cf" "$af" + + perf record -o "$data" -e cs_etm// --per-thread -D -1 --control fifo:"$cf","$af" -- \ + perf test --record-ctl fifo:"$cf","$af" -w callchain >/dev/null 2>&1 && + + # It is safe to use 'i3i' with a three-instruction interval, since the + # workload is compiled with -O0. + perf script --itrace=g16i3il64 -i "$data" > "$script" +} + +callchain_regex_1() +{ + printf '%s' \ +'perf[[:space:]]+[0-9]+[[:space:]]+[[0-9]+][[:space:]]+([0-9.]+:[[:space:]]+)?[0-9]+ instructions:[[:space:]]*\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain_foo+0x[[:xdigit:]]+ (.*/perf)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain+0x[[:xdigit:]]+ (.*/perf)\n'\ +'([[:space:]]+[[:xdigit:]]+ .*\n)*' +} + +callchain_regex_2() +{ + printf '%s' \ +'perf[[:space:]]+[0-9]+[[:space:]]+[[0-9]+][[:space:]]+([0-9.]+:[[:space:]]+)?[0-9]+ instructions:[[:space:]]*\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain_do_syscall+0x[[:xdigit:]]+ (.*/perf)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain_foo+0x[[:xdigit:]]+ (.*/perf)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain+0x[[:xdigit:]]+ (.*/perf)\n'\ +'([[:space:]]+[[:xdigit:]]+ .*\n)*' +} + +callchain_regex_3() +{ + printf '%s' \ +'perf[[:space:]]+[0-9]+[[:space:]]+[[0-9]+][[:space:]]+([0-9.]+:[[:space:]]+)?[0-9]+ instructions:[[:space:]]*\n'\ +'[[:space:]]+[[:xdigit:]]+ syscall(@plt)?+0x[[:xdigit:]]+ (.*)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain_do_syscall+0x[[:xdigit:]]+ (.*/perf)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain_foo+0x[[:xdigit:]]+ (.*/perf)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain+0x[[:xdigit:]]+ (.*/perf)\n'\ +'([[:space:]]+[[:xdigit:]]+ .*\n)*' +} + +callchain_regex_4() +{ + printf '%s' \ +'perf[[:space:]]+[0-9]+[[:space:]]+[[0-9]+][[:space:]]+([0-9.]+:[[:space:]]+)?[0-9]+ instructions:[[:space:]]*\n'\ +'[[:space:]]+[[:xdigit:]]+ .*+0x[[:xdigit:]]+ ([kernel.kallsyms])\n'\ +'[[:space:]]+[[:xdigit:]]+ syscall(@plt)?+0x[[:xdigit:]]+ (.*)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain_do_syscall+0x[[:xdigit:]]+ (.*/perf)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain_foo+0x[[:xdigit:]]+ (.*/perf)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain+0x[[:xdigit:]]+ (.*/perf)\n'\ +'([[:space:]]+[[:xdigit:]]+ .*\n)*' +} + +find_after_line() +{ + local regex="$1" + local file="$2" + local start="$3" + local offset + local line + + # Search in byte offset + offset=$( + tail -n +"$start" "$file" | + grep -Pzob -m1 "$regex" | + tr '\0' '\n' | + sed -n 's/^([0-9][0-9]*):.*/\1/p;q' + ) + + if [ -z "$offset" ]; then + echo "Failed to match regex after line $start" >&2 + echo "Regex:" >&2 + printf '%s\n' "$regex" >&2 + echo "Context from line $start:" >&2 + sed -n "${start},$((start + 100))p" "$file" >&2 + return 1 + fi + + # Convert from offset to line + line=$( + tail -n +"$start" "$file" | + head -c "$offset" | + wc -l + ) + + echo "$((start + line))" +} + +check_callchain_flow() +{ + local file="$1" + local l1 l2 l3 l4 l5 l6 l7 + + # Callchain push + l1=$(find_after_line "$(callchain_regex_1)" "$file" 1) || return 1 + l2=$(find_after_line "$(callchain_regex_2)" "$file" "$((l1 + 1))") || return 1 + l3=$(find_after_line "$(callchain_regex_3)" "$file" "$((l2 + 1))") || return 1 + l4=$(find_after_line "$(callchain_regex_4)" "$file" "$((l3 + 1))") || return 1 + + # Callchain pop + l5=$(find_after_line "$(callchain_regex_3)" "$file" "$((l4 + 1))") || return 1 + l6=$(find_after_line "$(callchain_regex_2)" "$file" "$((l5 + 1))") || return 1 + l7=$(find_after_line "$(callchain_regex_1)" "$file" "$((l6 + 1))") || return 1 + + echo "Callchain flow matched:" + echo " l1=$l1 l2=$l2 l3=$l3 l4=$l4 l5=$l5 l6=$l6 l7=$l7" + + return 0 +} + +run_test() +{ + local data=$tmpdir/perf.data + local script=$tmpdir/perf.script + + if ! record_trace "$data" "$script"; then + echo "perf record/script failed" + return + fi + + check_callchain_flow "$script" || return + + glb_err=0 +} + +skip_if_system_is_not_ready || exit 2 + +run_test + +exit $glb_err diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index 7cedf05be544ad79a99e86d30dfa4f7b01ca0837..cee9e6b62dcc838c864bbe76efe3b638ed75b134 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -248,6 +248,7 @@ DECLARE_WORKLOAD(inlineloop); DECLARE_WORKLOAD(jitdump); DECLARE_WORKLOAD(context_switch_loop); DECLARE_WORKLOAD(deterministic); +DECLARE_WORKLOAD(callchain);
#ifdef HAVE_RUST_SUPPORT DECLARE_WORKLOAD(code_with_type); diff --git a/tools/perf/tests/workloads/Build b/tools/perf/tests/workloads/Build index 75b377934a0e62b9ac1fec245520ea0978ac957e..dfdf9a2720b22f67a3d7b53d0ed14e0654059c8f 100644 --- a/tools/perf/tests/workloads/Build +++ b/tools/perf/tests/workloads/Build @@ -13,6 +13,7 @@ perf-test-y += inlineloop.o perf-test-y += jitdump.o perf-test-y += context_switch_loop.o perf-test-y += deterministic.o +perf-test-y += callchain.o
ifeq ($(CONFIG_RUST_SUPPORT),y) perf-test-y += code_with_type.o @@ -26,3 +27,4 @@ CFLAGS_datasym.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE CFLAGS_traploop.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE CFLAGS_inlineloop.o = -g -O2 CFLAGS_deterministic.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE +CFLAGS_callchain.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE diff --git a/tools/perf/tests/workloads/callchain.c b/tools/perf/tests/workloads/callchain.c new file mode 100644 index 0000000000000000000000000000000000000000..3951423d8115e9efb49af8ba2586001fc6f02761 --- /dev/null +++ b/tools/perf/tests/workloads/callchain.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/compiler.h> +#include <sys/syscall.h> +#include <unistd.h> +#include "../tests.h" + +/* + * Mark as noinline to establish the call chain, and avoid the static + * annotation to prevent LTO from renaming the functions. + */ +noinline void callchain_do_syscall(void); +noinline void callchain_foo(void); +noinline int callchain(int argc, const char **argv); + +noinline void callchain_do_syscall(void) +{ + syscall(SYS_getpid); +} + +noinline void callchain_foo(void) +{ + callchain_do_syscall(); +} + +noinline int callchain(int argc __maybe_unused, + const char **argv __maybe_unused) +{ + callchain_foo(); + + return 0; +} + +DEFINE_WORKLOAD(callchain);
On 11/06/2026 3:50 pm, Leo Yan wrote:
Add a CoreSight shell test for synthesized callchains.
The test uses the new callchain workload to generate trace and decodes it with synthesis callchain. It then verifies that the instruction samples show the expected callchain push and pop.
Use control FIFOs so tracing starts only around the workload, which keeps the trace data small. The test is limited to with the cs_etm event available and root permission.
After:
perf test 138 -vvv 138: CoreSight synthesized callchain: ---- start ---- test child forked, pid 35581 Callchain flow matched: l1=4642868 l2=4642880 l3=4642895 l4=4642919 l5=4670494 l6=4670500 l7=4670520 ---- end(0) ---- 138: CoreSight synthesized callchain : Ok
Assisted-by: Codex:GPT-5.5 Signed-off-by: Leo Yan leo.yan@arm.com
Reviewed-by: James Clark james.clark@linaro.org
tools/perf/Documentation/perf-test.txt | 6 +- tools/perf/tests/builtin-test.c | 1 + tools/perf/tests/shell/coresight/callchain.sh | 172 ++++++++++++++++++++++++++ tools/perf/tests/tests.h | 1 + tools/perf/tests/workloads/Build | 2 + tools/perf/tests/workloads/callchain.c | 33 +++++ 6 files changed, 213 insertions(+), 2 deletions(-)
diff --git a/tools/perf/Documentation/perf-test.txt b/tools/perf/Documentation/perf-test.txt index 81c8525f594680d814f80e6f88bcce8d867bb350..859df74e62efc4b1e80da13ae8e053356f68ae54 100644 --- a/tools/perf/Documentation/perf-test.txt +++ b/tools/perf/Documentation/perf-test.txt @@ -57,7 +57,8 @@ OPTIONS --workload=:: Run a built-in workload, to list them use '--list-workloads', current ones include: noploop, thloop, leafloop, sqrtloop, brstack, datasym,
- context_switch_loop, deterministic, named_threads and landlock.
- context_switch_loop, deterministic, named_threads, landlock and
- callchain.
Used with the shell script regression tests. @@ -69,7 +70,8 @@ OPTIONS 'named_threads' accepts the number of threads and the number of loops to do in each thread.
- The datasym, landlock and deterministic workloads don't accept any.
- The datasym, landlock, deterministic and callchain workloads don't accept
- any.
--list-workloads:: List the available workloads to use with -w/--workload. diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index afc06cec49546d29d86b94840c7021c5bf5c88e3..8994488cc206863ba77f7e7e5803e62f18e151ba 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -166,6 +166,7 @@ static struct test_workload *workloads[] = { &workload__jitdump, &workload__context_switch_loop, &workload__deterministic,
- &workload__callchain,
#ifdef HAVE_RUST_SUPPORT &workload__code_with_type, diff --git a/tools/perf/tests/shell/coresight/callchain.sh b/tools/perf/tests/shell/coresight/callchain.sh new file mode 100755 index 0000000000000000000000000000000000000000..13cca7dc11184002e3ddc058c0d0ffa1c458c483 --- /dev/null +++ b/tools/perf/tests/shell/coresight/callchain.sh @@ -0,0 +1,172 @@ +#!/bin/bash +# CoreSight synthesized callchain (exclusive) +# SPDX-License-Identifier: GPL-2.0
+glb_err=1
+if ! tmpdir=$(mktemp -d /tmp/perf-cs-callchain-test.XXXXXX); then
- echo "mktemp failed"
- exit 1
+fi
+cleanup_files() +{
- rm -rf "$tmpdir"
+}
+trap cleanup_files EXIT +trap 'cleanup_files; exit $glb_err' TERM INT
+skip_if_system_is_not_ready() +{
- perf list | grep -Pzq 'cs_etm//' || {
echo "[Skip] cs_etm event is not available" >&2return 2- }
- # Requires root for trace in kernel
- [ "$(id -u)" = 0 ] || {
echo "[Skip] No root permission" >&2return 2- }
- return 0
+}
+record_trace() +{
- local data=$1
- local script=$2
- local cf="$tmpdir/ctl"
- local af="$tmpdir/ack"
- mkfifo "$cf" "$af"
- perf record -o "$data" -e cs_etm// --per-thread -D -1 --control fifo:"$cf","$af" -- \
perf test --record-ctl fifo:"$cf","$af" -w callchain >/dev/null 2>&1 &&- # It is safe to use 'i3i' with a three-instruction interval, since the
- # workload is compiled with -O0.
- perf script --itrace=g16i3il64 -i "$data" > "$script"
+}
+callchain_regex_1() +{
- printf '%s' \
+'perf[[:space:]]+[0-9]+[[:space:]]+[[0-9]+][[:space:]]+([0-9.]+:[[:space:]]+)?[0-9]+ instructions:[[:space:]]*\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain_foo+0x[[:xdigit:]]+ (.*/perf)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain+0x[[:xdigit:]]+ (.*/perf)\n'\ +'([[:space:]]+[[:xdigit:]]+ .*\n)*' +}
+callchain_regex_2() +{
- printf '%s' \
+'perf[[:space:]]+[0-9]+[[:space:]]+[[0-9]+][[:space:]]+([0-9.]+:[[:space:]]+)?[0-9]+ instructions:[[:space:]]*\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain_do_syscall+0x[[:xdigit:]]+ (.*/perf)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain_foo+0x[[:xdigit:]]+ (.*/perf)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain+0x[[:xdigit:]]+ (.*/perf)\n'\ +'([[:space:]]+[[:xdigit:]]+ .*\n)*' +}
+callchain_regex_3() +{
- printf '%s' \
+'perf[[:space:]]+[0-9]+[[:space:]]+[[0-9]+][[:space:]]+([0-9.]+:[[:space:]]+)?[0-9]+ instructions:[[:space:]]*\n'\ +'[[:space:]]+[[:xdigit:]]+ syscall(@plt)?+0x[[:xdigit:]]+ (.*)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain_do_syscall+0x[[:xdigit:]]+ (.*/perf)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain_foo+0x[[:xdigit:]]+ (.*/perf)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain+0x[[:xdigit:]]+ (.*/perf)\n'\ +'([[:space:]]+[[:xdigit:]]+ .*\n)*' +}
+callchain_regex_4() +{
- printf '%s' \
+'perf[[:space:]]+[0-9]+[[:space:]]+[[0-9]+][[:space:]]+([0-9.]+:[[:space:]]+)?[0-9]+ instructions:[[:space:]]*\n'\ +'[[:space:]]+[[:xdigit:]]+ .*+0x[[:xdigit:]]+ ([kernel.kallsyms])\n'\ +'[[:space:]]+[[:xdigit:]]+ syscall(@plt)?+0x[[:xdigit:]]+ (.*)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain_do_syscall+0x[[:xdigit:]]+ (.*/perf)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain_foo+0x[[:xdigit:]]+ (.*/perf)\n'\ +'[[:space:]]+[[:xdigit:]]+ callchain+0x[[:xdigit:]]+ (.*/perf)\n'\ +'([[:space:]]+[[:xdigit:]]+ .*\n)*' +}
+find_after_line() +{
- local regex="$1"
- local file="$2"
- local start="$3"
- local offset
- local line
- # Search in byte offset
- offset=$(
tail -n +"$start" "$file" |grep -Pzob -m1 "$regex" |tr '\0' '\n' |sed -n 's/^\([0-9][0-9]*\):.*/\1/p;q'- )
- if [ -z "$offset" ]; then
echo "Failed to match regex after line $start" >&2echo "Regex:" >&2printf '%s\n' "$regex" >&2echo "Context from line $start:" >&2sed -n "${start},$((start + 100))p" "$file" >&2return 1- fi
- # Convert from offset to line
- line=$(
tail -n +"$start" "$file" |head -c "$offset" |wc -l- )
- echo "$((start + line))"
+}
+check_callchain_flow() +{
- local file="$1"
- local l1 l2 l3 l4 l5 l6 l7
- # Callchain push
- l1=$(find_after_line "$(callchain_regex_1)" "$file" 1) || return 1
- l2=$(find_after_line "$(callchain_regex_2)" "$file" "$((l1 + 1))") || return 1
- l3=$(find_after_line "$(callchain_regex_3)" "$file" "$((l2 + 1))") || return 1
- l4=$(find_after_line "$(callchain_regex_4)" "$file" "$((l3 + 1))") || return 1
- # Callchain pop
- l5=$(find_after_line "$(callchain_regex_3)" "$file" "$((l4 + 1))") || return 1
- l6=$(find_after_line "$(callchain_regex_2)" "$file" "$((l5 + 1))") || return 1
- l7=$(find_after_line "$(callchain_regex_1)" "$file" "$((l6 + 1))") || return 1
- echo "Callchain flow matched:"
- echo " l1=$l1 l2=$l2 l3=$l3 l4=$l4 l5=$l5 l6=$l6 l7=$l7"
- return 0
+}
+run_test() +{
- local data=$tmpdir/perf.data
- local script=$tmpdir/perf.script
- if ! record_trace "$data" "$script"; then
echo "perf record/script failed"return- fi
- check_callchain_flow "$script" || return
- glb_err=0
+}
+skip_if_system_is_not_ready || exit 2
+run_test
+exit $glb_err diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index 7cedf05be544ad79a99e86d30dfa4f7b01ca0837..cee9e6b62dcc838c864bbe76efe3b638ed75b134 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -248,6 +248,7 @@ DECLARE_WORKLOAD(inlineloop); DECLARE_WORKLOAD(jitdump); DECLARE_WORKLOAD(context_switch_loop); DECLARE_WORKLOAD(deterministic); +DECLARE_WORKLOAD(callchain); #ifdef HAVE_RUST_SUPPORT DECLARE_WORKLOAD(code_with_type); diff --git a/tools/perf/tests/workloads/Build b/tools/perf/tests/workloads/Build index 75b377934a0e62b9ac1fec245520ea0978ac957e..dfdf9a2720b22f67a3d7b53d0ed14e0654059c8f 100644 --- a/tools/perf/tests/workloads/Build +++ b/tools/perf/tests/workloads/Build @@ -13,6 +13,7 @@ perf-test-y += inlineloop.o perf-test-y += jitdump.o perf-test-y += context_switch_loop.o perf-test-y += deterministic.o +perf-test-y += callchain.o ifeq ($(CONFIG_RUST_SUPPORT),y) perf-test-y += code_with_type.o @@ -26,3 +27,4 @@ CFLAGS_datasym.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE CFLAGS_traploop.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE CFLAGS_inlineloop.o = -g -O2 CFLAGS_deterministic.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE +CFLAGS_callchain.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE diff --git a/tools/perf/tests/workloads/callchain.c b/tools/perf/tests/workloads/callchain.c new file mode 100644 index 0000000000000000000000000000000000000000..3951423d8115e9efb49af8ba2586001fc6f02761 --- /dev/null +++ b/tools/perf/tests/workloads/callchain.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/compiler.h> +#include <sys/syscall.h> +#include <unistd.h> +#include "../tests.h"
+/*
- Mark as noinline to establish the call chain, and avoid the static
- annotation to prevent LTO from renaming the functions.
- */
+noinline void callchain_do_syscall(void); +noinline void callchain_foo(void); +noinline int callchain(int argc, const char **argv);
+noinline void callchain_do_syscall(void) +{
- syscall(SYS_getpid);
+}
+noinline void callchain_foo(void) +{
- callchain_do_syscall();
+}
+noinline int callchain(int argc __maybe_unused,
const char **argv __maybe_unused)+{
- callchain_foo();
- return 0;
+}
+DEFINE_WORKLOAD(callchain);