Skip to contentSkip to conversation

perf tool: Support the deferred unwinding infrastructure

PATCH Superseded Steven Rostedt 5 participants 13 messages Sep 8, 2025 – Nov 13, 2025

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

v16 cover perf tool: Support the deferred unwinding infrastructure

This is the user space side of perf changes to handle deferred unwinding. It is based on the kernel side of perf patch series here:

https://lore.kernel.org/linux-trace-kernel/[email protected]/

Changes since v15: https://lore.kernel.org/linux-trace-kernel/[email protected]/

- Separate out the kernel changes from the user space changes of perf.

- Have the matching deferred code only skip when TID does not match. Otherwise, process the sample but do not merge if the cookies do not match. (Namhyung Kim)

Namhyung Kim (4):
perf tools: Minimal CALLCHAIN_DEFERRED support perf record: Enable defer_callchain for user callchains perf script: Display PERF_RECORD_CALLCHAIN_DEFERRED perf tools: Merge deferred user callchains

----

 tools/lib/perf/include/perf/event.h       |  8 +++
 tools/perf/Documentation/perf-script.txt  |  5 ++
 tools/perf/builtin-script.c               | 92 +++++++++++++++++++++++++++++++
 tools/perf/util/callchain.c               | 24 ++++++++
 tools/perf/util/callchain.h               |  3 +
 tools/perf/util/event.c                   |  1 +
 tools/perf/util/evlist.c                  |  1 +
 tools/perf/util/evlist.h                  |  1 +
 tools/perf/util/evsel.c                   | 42 ++++++++++++++
 tools/perf/util/evsel.h                   |  1 +
 tools/perf/util/machine.c                 |  1 +
 tools/perf/util/perf_event_attr_fprintf.c |  1 +
 tools/perf/util/sample.h                  |  4 +-
 tools/perf/util/session.c                 | 81 +++++++++++++++++++++++++++
 tools/perf/util/tool.c                    |  2 +
 tools/perf/util/tool.h                    |  4 +-
 16 files changed, 269 insertions(+), 2 deletions(-)
#1 lore raw

PATCH v16 1/4 perf tools: Minimal CALLCHAIN_DEFERRED support

From: Namhyung Kim <[email protected]>

Add a new event type for deferred callchains and a new callback for the struct perf_tool. For now it doesn't actually handle the deferred callchains but it just marks the sample if it has the PERF_CONTEXT_ USER_DEFFERED in the callchain array.

At least, perf report can dump the raw data with this change. Actually this requires the next commit to enable attr.defer_callchain, but if you already have a data file, it'll show the following result.

  $ perf report -D
  ...
  [email protected] [0x40]: event: 22
  .
  . ... raw event: size 64 bytes
  .  0000:  16 00 00 00 02 00 40 00 02 00 00 00 00 00 00 00  ......@.........
  .  0010:  00 fe ff ff ff ff ff ff 4b d3 3f 25 45 7f 00 00  ........K.?%E...
  .  0020:  21 03 00 00 21 03 00 00 43 02 12 ab 05 00 00 00  !...!...C.......
  .  0030:  00 00 00 00 00 00 00 00 09 00 00 00 00 00 00 00  ................

0 24344920643 0x5fe0 [0x40]: PERF_RECORD_CALLCHAIN_DEFERRED(IP, 0x2): 801/801: 0 [300000001] ... FP chain: nr:2
..... 0: fffffffffffffe00
..... 1: 00007f45253fd34b
: unhandled!

---
 tools/lib/perf/include/perf/event.h       |  8 ++++++++
 tools/perf/util/event.c                   |  1 +
 tools/perf/util/evsel.c                   | 18 ++++++++++++++++++
 tools/perf/util/machine.c                 |  1 +
 tools/perf/util/perf_event_attr_fprintf.c |  1 +
 tools/perf/util/sample.h                  |  4 +++-
 tools/perf/util/session.c                 | 18 ++++++++++++++++++
 tools/perf/util/tool.c                    |  1 +
 tools/perf/util/tool.h                    |  3 ++-
 9 files changed, 53 insertions(+), 2 deletions(-)
tools/lib/perf/include/perf/event.h +8 -0
index 6608f1e3701b..4af8cb238e27 100644
@@ -151,6 +151,13 @@ struct perf_record_switch {
 	__u32			 next_prev_tid;
 };
 
+struct perf_record_callchain_deferred {
+	struct perf_event_header header;
+	__u64			 cookie;
+	__u64			 nr;
+	__u64			 ips[];
+};
+
 struct perf_record_header_attr {
 	struct perf_event_header header;
 	struct perf_event_attr	 attr;
@@ -522,6 +529,7 @@ union perf_event {
 	struct perf_record_read			read;
 	struct perf_record_throttle		throttle;
 	struct perf_record_sample		sample;
+	struct perf_record_callchain_deferred	callchain_deferred;
 	struct perf_record_bpf_event		bpf;
 	struct perf_record_ksymbol		ksymbol;
 	struct perf_record_text_poke_event	text_poke;
tools/perf/util/event.c +1 -0
index fcf44149feb2..4c92cc1a952c 100644
@@ -61,6 +61,7 @@ static const char *perf_event__names[] = {
 	[PERF_RECORD_CGROUP]			= "CGROUP",
 	[PERF_RECORD_TEXT_POKE]			= "TEXT_POKE",
 	[PERF_RECORD_AUX_OUTPUT_HW_ID]		= "AUX_OUTPUT_HW_ID",
+	[PERF_RECORD_CALLCHAIN_DEFERRED]	= "CALLCHAIN_DEFERRED",
 	[PERF_RECORD_HEADER_ATTR]		= "ATTR",
 	[PERF_RECORD_HEADER_EVENT_TYPE]		= "EVENT_TYPE",
 	[PERF_RECORD_HEADER_TRACING_DATA]	= "TRACING_DATA",
tools/perf/util/evsel.c +18 -0
index d264c143b592..7512b9fb877d 100644
@@ -3002,6 +3002,18 @@ int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
 	data->data_src = PERF_MEM_DATA_SRC_NONE;
 	data->vcpu = -1;
 
+	if (event->header.type == PERF_RECORD_CALLCHAIN_DEFERRED) {
+		const u64 max_callchain_nr = UINT64_MAX / sizeof(u64);
+
+		data->callchain = (struct ip_callchain *)&event->callchain_deferred.nr;
+		if (data->callchain->nr > max_callchain_nr)
+			return -EFAULT;
+
+		if (evsel->core.attr.sample_id_all)
+			perf_evsel__parse_id_sample(evsel, event, data);
+		return 0;
+	}
+
 	if (event->header.type != PERF_RECORD_SAMPLE) {
 		if (!evsel->core.attr.sample_id_all)
 			return 0;
@@ -3132,6 +3144,12 @@ int evsel__parse_sample(struct evsel *evsel, union perf_event *event,
 		if (data->callchain->nr > max_callchain_nr)
 			return -EFAULT;
 		sz = data->callchain->nr * sizeof(u64);
+		if (evsel->core.attr.defer_callchain &&
+		    data->callchain->nr >= PERF_DEFERRED_ITEMS &&
+		    data->callchain->ips[data->callchain->nr - PERF_DEFERRED_ITEMS] == PERF_CONTEXT_USER_DEFERRED) {
+			data->deferred_callchain = true;
+			data->deferred_cookie = data->callchain->ips[data->callchain->nr - 1];
+		}
 		OVERFLOW_CHECK(array, sz, max_size);
 		array = (void *)array + sz;
 	}
tools/perf/util/machine.c +1 -0
index b5dd42588c91..841b711d970e 100644
@@ -2124,6 +2124,7 @@ static int add_callchain_ip(struct thread *thread,
 				*cpumode = PERF_RECORD_MISC_KERNEL;
 				break;
 			case PERF_CONTEXT_USER:
+			case PERF_CONTEXT_USER_DEFERRED:
 				*cpumode = PERF_RECORD_MISC_USER;
 				break;
 			default:
tools/perf/util/perf_event_attr_fprintf.c +1 -0
index 66b666d9ce64..abfd9b9a718c 100644
@@ -343,6 +343,7 @@ int perf_event_attr__fprintf(FILE *fp, struct perf_event_attr *attr,
 	PRINT_ATTRf(inherit_thread, p_unsigned);
 	PRINT_ATTRf(remove_on_exec, p_unsigned);
 	PRINT_ATTRf(sigtrap, p_unsigned);
+	PRINT_ATTRf(defer_callchain, p_unsigned);
 
 	PRINT_ATTRn("{ wakeup_events, wakeup_watermark }", wakeup_events, p_unsigned, false);
 	PRINT_ATTRf(bp_type, p_unsigned);
tools/perf/util/sample.h +3 -1
index fae834144ef4..68392e6298b7 100644
@@ -106,7 +106,9 @@ struct perf_sample {
 	u16 ins_lat;
 	/** @weight3: On x86 holds retire_lat, on powerpc holds p_stage_cyc. */
 	u16 weight3;
-	bool no_hw_idx;		/* No hw_idx collected in branch_stack */
+	bool no_hw_idx;			/* No hw_idx collected in branch_stack */
+	bool deferred_callchain;	/* Has deferred user callchains */
+	u64 deferred_cookie;
 	char insn[MAX_INSN];
 	void *raw_data;
 	struct ip_callchain *callchain;
tools/perf/util/session.c +18 -0
index 26ae078278cd..a071006350f5 100644
@@ -720,6 +720,7 @@ static perf_event__swap_op perf_event__swap_ops[] = {
 	[PERF_RECORD_CGROUP]		  = perf_event__cgroup_swap,
 	[PERF_RECORD_TEXT_POKE]		  = perf_event__text_poke_swap,
 	[PERF_RECORD_AUX_OUTPUT_HW_ID]	  = perf_event__all64_swap,
+	[PERF_RECORD_CALLCHAIN_DEFERRED]  = perf_event__all64_swap,
 	[PERF_RECORD_HEADER_ATTR]	  = perf_event__hdr_attr_swap,
 	[PERF_RECORD_HEADER_EVENT_TYPE]	  = perf_event__event_type_swap,
 	[PERF_RECORD_HEADER_TRACING_DATA] = perf_event__tracing_data_swap,
@@ -1123,6 +1124,20 @@ static void dump_sample(struct evsel *evsel, union perf_event *event,
 		sample_read__printf(sample, evsel->core.attr.read_format);
 }
 
+static void dump_deferred_callchain(struct evsel *evsel, union perf_event *event,
+				    struct perf_sample *sample)
+{
+	if (!dump_trace)
+		return;
+
+	printf("(IP, 0x%x): %d/%d: %#" PRIx64 " [%llx]\n",
+	       event->header.misc, sample->pid, sample->tid, sample->ip,
+		event->callchain_deferred.cookie);
+
+	if (evsel__has_callchain(evsel))
+		callchain__printf(evsel, sample);
+}
+
 static void dump_read(struct evsel *evsel, union perf_event *event)
 {
 	struct perf_record_read *read_event = &event->read;
@@ -1353,6 +1368,9 @@ static int machines__deliver_event(struct machines *machines,
 		return tool->text_poke(tool, event, sample, machine);
 	case PERF_RECORD_AUX_OUTPUT_HW_ID:
 		return tool->aux_output_hw_id(tool, event, sample, machine);
+	case PERF_RECORD_CALLCHAIN_DEFERRED:
+		dump_deferred_callchain(evsel, event, sample);
+		return tool->callchain_deferred(tool, event, sample, evsel, machine);
 	default:
 		++evlist->stats.nr_unknown_events;
 		return -1;
tools/perf/util/tool.c +1 -0
index e83c7ababc2a..8bf86af1ca90 100644
@@ -279,6 +279,7 @@ void perf_tool__init(struct perf_tool *tool, bool ordered_events)
 	tool->read = process_event_sample_stub;
 	tool->throttle = process_event_stub;
 	tool->unthrottle = process_event_stub;
+	tool->callchain_deferred = process_event_sample_stub;
 	tool->attr = process_event_synth_attr_stub;
 	tool->event_update = process_event_synth_event_update_stub;
 	tool->tracing_data = process_event_synth_tracing_data_stub;
tools/perf/util/tool.h +2 -1
index 18b76ff0f26a..2676d84da80c 100644
@@ -42,7 +42,8 @@ enum show_feature_header {
 
 struct perf_tool {
 	event_sample	sample,
-			read;
+			read,
+			callchain_deferred;
 	event_op	mmap,
 			mmap2,
 			comm,
Signature · 1 line
2.50.1
#2 lore raw

PATCH v16 2/4 perf record: Enable defer_callchain for user callchains

In reply to Steven Rostedt

From: Namhyung Kim <[email protected]>

And add the missing feature detection logic to clear the flag on old kernels.

  $ perf record -g -vv true
  ...
  ------------------------------------------------------------
  perf_event_attr:
    type                             0 (PERF_TYPE_HARDWARE)
    size                             136
    config                           0 (PERF_COUNT_HW_CPU_CYCLES)
    { sample_period, sample_freq }   4000
    sample_type                      IP|TID|TIME|CALLCHAIN|PERIOD
    read_format                      ID|LOST
    disabled                         1
    inherit                          1
    mmap                             1
    comm                             1
    freq                             1
    enable_on_exec                   1
    task                             1
    sample_id_all                    1
    mmap2                            1
    comm_exec                        1
    ksymbol                          1
    bpf_event                        1
    defer_callchain                  1
  ------------------------------------------------------------
  sys_perf_event_open: pid 162755  cpu 0  group_fd -1  flags 0x8
  sys_perf_event_open failed, error -22
  switching off deferred callchain support
---
 tools/perf/util/evsel.c | 24 ++++++++++++++++++++++++
 tools/perf/util/evsel.h |  1 +
 2 files changed, 25 insertions(+)
tools/perf/util/evsel.c +24 -0
index 7512b9fb877d..438343dbb1c9 100644
@@ -1061,6 +1061,14 @@ static void __evsel__config_callchain(struct evsel *evsel, struct record_opts *o
 		}
 	}
 
+	if (param->record_mode == CALLCHAIN_FP && !attr->exclude_callchain_user) {
+		/*
+		 * Enable deferred callchains optimistically.  It'll be switched
+		 * off later if the kernel doesn't support it.
+		 */
+		attr->defer_callchain = 1;
+	}
+
 	if (function) {
 		pr_info("Disabling user space callchains for function trace event.\n");
 		attr->exclude_callchain_user = 1;
@@ -2121,6 +2129,8 @@ static int __evsel__prepare_open(struct evsel *evsel, struct perf_cpu_map *cpus,
 
 static void evsel__disable_missing_features(struct evsel *evsel)
 {
+	if (perf_missing_features.defer_callchain)
+		evsel->core.attr.defer_callchain = 0;
 	if (perf_missing_features.inherit_sample_read && evsel->core.attr.inherit &&
 	    (evsel->core.attr.sample_type & PERF_SAMPLE_READ))
 		evsel->core.attr.inherit = 0;
@@ -2395,6 +2405,15 @@ static bool evsel__detect_missing_features(struct evsel *evsel, struct perf_cpu
 
 	/* Please add new feature detection here. */
 
+	attr.defer_callchain = true;
+	attr.sample_type = PERF_SAMPLE_CALLCHAIN;
+	if (has_attr_feature(&attr, /*flags=*/0))
+		goto found;
+	perf_missing_features.defer_callchain = true;
+	pr_debug2("switching off deferred callchain support\n");
+	attr.defer_callchain = false;
+	attr.sample_type = 0;
+
 	attr.inherit = true;
 	attr.sample_type = PERF_SAMPLE_READ;
 	if (has_attr_feature(&attr, /*flags=*/0))
@@ -2506,6 +2525,11 @@ static bool evsel__detect_missing_features(struct evsel *evsel, struct perf_cpu
 	errno = old_errno;
 
 check:
+	if (evsel->core.attr.defer_callchain &&
+	    evsel->core.attr.sample_type & PERF_SAMPLE_CALLCHAIN &&
+	    perf_missing_features.defer_callchain)
+		return true;
+
 	if (evsel->core.attr.inherit &&
 	    (evsel->core.attr.sample_type & PERF_SAMPLE_READ) &&
 	    perf_missing_features.inherit_sample_read)
tools/perf/util/evsel.h +1 -0
index 5797a02e5d6a..d9559d29e345 100644
@@ -221,6 +221,7 @@ struct perf_missing_features {
 	bool branch_counters;
 	bool aux_action;
 	bool inherit_sample_read;
+	bool defer_callchain;
 };
 
 extern struct perf_missing_features perf_missing_features;
Signature · 1 line
2.50.1
#3 lore raw

PATCH v16 3/4 perf script: Display PERF_RECORD_CALLCHAIN_DEFERRED

In reply to Steven Rostedt

From: Namhyung Kim <[email protected]>

Handle the deferred callchains in the script output.

  $ perf script
  perf     801 [000]    18.031793:          1 cycles:P:
          ffffffff91a14c36 __intel_pmu_enable_all.isra.0+0x56 ([kernel.kallsyms])
          ffffffff91d373e9 perf_ctx_enable+0x39 ([kernel.kallsyms])
          ffffffff91d36af7 event_function+0xd7 ([kernel.kallsyms])
          ffffffff91d34222 remote_function+0x42 ([kernel.kallsyms])
          ffffffff91c1ebe1 generic_exec_single+0x61 ([kernel.kallsyms])
          ffffffff91c1edac smp_call_function_single+0xec ([kernel.kallsyms])
          ffffffff91d37a9d event_function_call+0x10d ([kernel.kallsyms])
          ffffffff91d33557 perf_event_for_each_child+0x37 ([kernel.kallsyms])
          ffffffff91d47324 _perf_ioctl+0x204 ([kernel.kallsyms])
          ffffffff91d47c43 perf_ioctl+0x33 ([kernel.kallsyms])
          ffffffff91e2f216 __x64_sys_ioctl+0x96 ([kernel.kallsyms])
          ffffffff9265f1ae do_syscall_64+0x9e ([kernel.kallsyms])
          ffffffff92800130 entry_SYSCALL_64+0xb0 ([kernel.kallsyms])

perf 801 [000] 18.031814: DEFERRED CALLCHAIN

              7fb5fc22034b __GI___ioctl+0x3b (/usr/lib/x86_64-linux-gnu/libc.so.6)

---
Changes since v15: https://lore.kernel.org/[email protected]

- Removed initialization of script.tool.merge_deferred_callchains as it is introduced in the next patch.

 tools/perf/builtin-script.c | 88 +++++++++++++++++++++++++++++++++++++
 1 file changed, 88 insertions(+)
tools/perf/builtin-script.c +88 -0
index d9fbdcf72f25..03ea7b7e5bc1 100644
@@ -2484,6 +2484,93 @@ static int process_sample_event(const struct perf_tool *tool,
 	return ret;
 }
 
+static int process_deferred_sample_event(const struct perf_tool *tool,
+					 union perf_event *event,
+					 struct perf_sample *sample,
+					 struct evsel *evsel,
+					 struct machine *machine)
+{
+	struct perf_script *scr = container_of(tool, struct perf_script, tool);
+	struct perf_event_attr *attr = &evsel->core.attr;
+	struct evsel_script *es = evsel->priv;
+	unsigned int type = output_type(attr->type);
+	struct addr_location al;
+	FILE *fp = es->fp;
+	int ret = 0;
+
+	if (output[type].fields == 0)
+		return 0;
+
+	/* Set thread to NULL to indicate addr_al and al are not initialized */
+	addr_location__init(&al);
+
+	if (perf_time__ranges_skip_sample(scr->ptime_range, scr->range_num,
+					  sample->time)) {
+		goto out_put;
+	}
+
+	if (debug_mode) {
+		if (sample->time < last_timestamp) {
+			pr_err("Samples misordered, previous: %" PRIu64
+				" this: %" PRIu64 "\n", last_timestamp,
+				sample->time);
+			nr_unordered++;
+		}
+		last_timestamp = sample->time;
+		goto out_put;
+	}
+
+	if (filter_cpu(sample))
+		goto out_put;
+
+	if (machine__resolve(machine, &al, sample) < 0) {
+		pr_err("problem processing %d event, skipping it.\n",
+		       event->header.type);
+		ret = -1;
+		goto out_put;
+	}
+
+	if (al.filtered)
+		goto out_put;
+
+	if (!show_event(sample, evsel, al.thread, &al, NULL))
+		goto out_put;
+
+	if (evswitch__discard(&scr->evswitch, evsel))
+		goto out_put;
+
+	perf_sample__fprintf_start(scr, sample, al.thread, evsel,
+				   PERF_RECORD_CALLCHAIN_DEFERRED, fp);
+	fprintf(fp, "DEFERRED CALLCHAIN");
+
+	if (PRINT_FIELD(IP)) {
+		struct callchain_cursor *cursor = NULL;
+
+		if (symbol_conf.use_callchain && sample->callchain) {
+			cursor = get_tls_callchain_cursor();
+			if (thread__resolve_callchain(al.thread, cursor, evsel,
+						      sample, NULL, NULL,
+						      scripting_max_stack)) {
+				pr_info("cannot resolve deferred callchains\n");
+				cursor = NULL;
+			}
+		}
+
+		fputc(cursor ? '\n' : ' ', fp);
+		sample__fprintf_sym(sample, &al, 0, output[type].print_ip_opts,
+				    cursor, symbol_conf.bt_stop_list, fp);
+	}
+
+	fprintf(fp, "\n");
+
+	if (verbose > 0)
+		fflush(fp);
+
+out_put:
+	addr_location__exit(&al);
+	return ret;
+}
+
 // Used when scr->per_event_dump is not set
 static struct evsel_script es_stdout;
 
@@ -4080,6 +4167,7 @@ int cmd_script(int argc, const char **argv)
 
 	perf_tool__init(&script.tool, !unsorted_dump);
 	script.tool.sample		 = process_sample_event;
+	script.tool.callchain_deferred	 = process_deferred_sample_event;
 	script.tool.mmap		 = perf_event__process_mmap;
 	script.tool.mmap2		 = perf_event__process_mmap2;
 	script.tool.comm		 = perf_event__process_comm;
Signature · 1 line
2.50.1
#4 lore raw

PATCH v16 4/4 perf tools: Merge deferred user callchains

In reply to Steven Rostedt

From: Namhyung Kim <[email protected]>

Save samples with deferred callchains in a separate list and deliver them after merging the user callchains. If users don't want to merge they can set tool->merge_deferred_callchains to false to prevent the behavior.

With previous result, now perf script will show the merged callchains.

  $ perf script
  perf     801 [000]    18.031793:          1 cycles:P:
          ffffffff91a14c36 __intel_pmu_enable_all.isra.0+0x56 ([kernel.kallsyms])
          ffffffff91d373e9 perf_ctx_enable+0x39 ([kernel.kallsyms])
          ffffffff91d36af7 event_function+0xd7 ([kernel.kallsyms])
          ffffffff91d34222 remote_function+0x42 ([kernel.kallsyms])
          ffffffff91c1ebe1 generic_exec_single+0x61 ([kernel.kallsyms])
          ffffffff91c1edac smp_call_function_single+0xec ([kernel.kallsyms])
          ffffffff91d37a9d event_function_call+0x10d ([kernel.kallsyms])
          ffffffff91d33557 perf_event_for_each_child+0x37 ([kernel.kallsyms])
          ffffffff91d47324 _perf_ioctl+0x204 ([kernel.kallsyms])
          ffffffff91d47c43 perf_ioctl+0x33 ([kernel.kallsyms])
          ffffffff91e2f216 __x64_sys_ioctl+0x96 ([kernel.kallsyms])
          ffffffff9265f1ae do_syscall_64+0x9e ([kernel.kallsyms])
          ffffffff92800130 entry_SYSCALL_64+0xb0 ([kernel.kallsyms])
              7fb5fc22034b __GI___ioctl+0x3b (/usr/lib/x86_64-linux-gnu/libc.so.6)
  ...

The old output can be get using --no-merge-callchain option. Also perf report can get the user callchain entry at the end.

  $ perf report --no-children --percent-limit=0 --stdio -q -S __intel_pmu_enable_all.isra.0
  # symbol: __intel_pmu_enable_all.isra.0
       0.00%  perf     [kernel.kallsyms]
              |
              ---__intel_pmu_enable_all.isra.0
                 perf_ctx_enable
                 event_function
                 remote_function
                 generic_exec_single
                 smp_call_function_single
                 event_function_call
                 perf_event_for_each_child
                 _perf_ioctl
                 perf_ioctl
                 __x64_sys_ioctl
                 do_syscall_64
                 entry_SYSCALL_64
                 __GI___ioctl

---
Changes since v15: https://lore.kernel.org/[email protected]

- Have the matching deferred code only skip when TID does not match. Otherwise, process the sample but do not merge if the cookies do not match. (Namhyung Kim)

 tools/perf/Documentation/perf-script.txt |  5 ++
 tools/perf/builtin-script.c              |  4 ++
 tools/perf/util/callchain.c              | 24 +++++++++
 tools/perf/util/callchain.h              |  3 ++
 tools/perf/util/evlist.c                 |  1 +
 tools/perf/util/evlist.h                 |  1 +
 tools/perf/util/session.c                | 65 +++++++++++++++++++++++-
 tools/perf/util/tool.c                   |  1 +
 tools/perf/util/tool.h                   |  1 +
 9 files changed, 104 insertions(+), 1 deletion(-)
tools/perf/Documentation/perf-script.txt +5 -0
index 28bec7e78bc8..03d112960632 100644
@@ -527,6 +527,11 @@ include::itrace.txt[]
 	The known limitations include exception handing such as
 	setjmp/longjmp will have calls/returns not match.
 
+--merge-callchains::
+	Enable merging deferred user callchains if available.  This is the
+	default behavior.  If you want to see separate CALLCHAIN_DEFERRED
+	records for some reason, use --no-merge-callchains explicitly.
+
 :GMEXAMPLECMD: script
 :GMEXAMPLESUBCMD:
 include::guest-files.txt[]
tools/perf/builtin-script.c +4 -0
index 03ea7b7e5bc1..70e7658a61fb 100644
@@ -3785,6 +3785,7 @@ int cmd_script(int argc, const char **argv)
 	bool header_only = false;
 	bool script_started = false;
 	bool unsorted_dump = false;
+	bool merge_deferred_callchains = true;
 	char *rec_script_path = NULL;
 	char *rep_script_path = NULL;
 	struct perf_session *session;
@@ -3938,6 +3939,8 @@ int cmd_script(int argc, const char **argv)
 		    "Guest code can be found in hypervisor process"),
 	OPT_BOOLEAN('\0', "stitch-lbr", &script.stitch_lbr,
 		    "Enable LBR callgraph stitching approach"),
+	OPT_BOOLEAN('\0', "merge-callchains", &merge_deferred_callchains,
+		    "Enable merge deferred user callchains"),
 	OPTS_EVSWITCH(&script.evswitch),
 	OPT_END()
 	};
@@ -4194,6 +4197,7 @@ int cmd_script(int argc, const char **argv)
 	script.tool.throttle		 = process_throttle_event;
 	script.tool.unthrottle		 = process_throttle_event;
 	script.tool.ordering_requires_timestamps = true;
+	script.tool.merge_deferred_callchains = merge_deferred_callchains;
 	session = perf_session__new(&data, &script.tool);
 	if (IS_ERR(session))
 		return PTR_ERR(session);
tools/perf/util/callchain.c +24 -0
index d7b7eef740b9..d2d672f1d6ba 100644
@@ -1828,3 +1828,27 @@ int sample__for_each_callchain_node(struct thread *thread, struct evsel *evsel,
 	}
 	return 0;
 }
+
+int sample__merge_deferred_callchain(struct perf_sample *sample_orig,
+				     struct perf_sample *sample_callchain)
+{
+	u64 nr_orig = sample_orig->callchain->nr - PERF_DEFERRED_ITEMS;
+	u64 nr_deferred = sample_callchain->callchain->nr;
+	struct ip_callchain *callchain;
+
+	callchain = calloc(1 + nr_orig + nr_deferred, sizeof(u64));
+	if (callchain == NULL) {
+		sample_orig->deferred_callchain = false;
+		return -ENOMEM;
+	}
+
+	callchain->nr = nr_orig + nr_deferred;
+	/* copy except for the last PERF_CONTEXT_USER_DEFERRED */
+	memcpy(callchain->ips, sample_orig->callchain->ips, nr_orig * sizeof(u64));
+	/* copy deferred use callchains */
+	memcpy(&callchain->ips[nr_orig], sample_callchain->callchain->ips,
+	       nr_deferred * sizeof(u64));
+
+	sample_orig->callchain = callchain;
+	return 0;
+}
tools/perf/util/callchain.h +3 -0
index 86ed9e4d04f9..89785125ed25 100644
@@ -317,4 +317,7 @@ int sample__for_each_callchain_node(struct thread *thread, struct evsel *evsel,
 				    struct perf_sample *sample, int max_stack,
 				    bool symbols, callchain_iter_fn cb, void *data);
 
+int sample__merge_deferred_callchain(struct perf_sample *sample_orig,
+				     struct perf_sample *sample_callchain);
+
 #endif	/* __PERF_CALLCHAIN_H */
tools/perf/util/evlist.c +1 -0
index 80d8387e6b97..9518b45af393 100644
@@ -85,6 +85,7 @@ void evlist__init(struct evlist *evlist, struct perf_cpu_map *cpus,
 	evlist->ctl_fd.pos = -1;
 	evlist->nr_br_cntr = -1;
 	metricgroup__rblist_init(&evlist->metric_events);
+	INIT_LIST_HEAD(&evlist->deferred_samples);
 }
 
 struct evlist *evlist__new(void)
tools/perf/util/evlist.h +1 -0
index 5e71e3dc6042..309ef8d78495 100644
@@ -92,6 +92,7 @@ struct evlist {
 	 * of struct metric_expr.
 	 */
 	struct rblist	metric_events;
+	struct list_head deferred_samples;
 };
 
 struct evsel_str_handler {
tools/perf/util/session.c +64 -1

83 lines not on this page · raw message

tools/perf/util/tool.c +1 -0
index 8bf86af1ca90..9ab9e231b5d5 100644
@@ -258,6 +258,7 @@ void perf_tool__init(struct perf_tool *tool, bool ordered_events)
 	tool->cgroup_events = false;
 	tool->no_warn = false;
 	tool->show_feat_hdr = SHOW_FEAT_NO_HEADER;
+	tool->merge_deferred_callchains = true;
 
 	tool->sample = process_event_sample_stub;
 	tool->mmap = process_event_stub;
tools/perf/util/tool.h +1 -0
index 2676d84da80c..7165a487a485 100644
@@ -88,6 +88,7 @@ struct perf_tool {
 	bool		cgroup_events;
 	bool		no_warn;
 	bool		dont_split_sample_group;
+	bool		merge_deferred_callchains;
 	enum show_feature_header show_feat_hdr;
 };
 
Signature · 1 line
2.50.1
maintainer
#5 lore raw

Re: [PATCH v16 4/4] perf tools: Merge deferred user callchains

Quoted 8 lines · On Mon, 08 Sep 2025 13:53:23 -0400 Steven Rostedt <[email protected]> wrote:
tools/perf/util/session.c +8 -0
+static int evlist__deliver_deferred_samples(struct evlist *evlist,
+					    const struct perf_tool *tool,
+					    union  perf_event *event,
+					    struct perf_sample *sample,
+					    struct machine *machine)
+{
+	struct deferred_event *de, *tmp;
+	struct evsel *evsel;

tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1308

Quoted 10 lines
tools/perf/util/session.c +10 -0
+	int ret = 0;
+
+	if (!tool->merge_deferred_callchains) {
+		evsel = evlist__id2evsel(evlist, sample->id);
+		return tool->callchain_deferred(tool, event, sample,
+						evsel, machine);
+	}
+
+	list_for_each_entry_safe(de, tmp, &evlist->deferred_samples, list) {
+		struct perf_sample orig_sample;

orig_sample is not initialized and can then contain junk.

tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1320

Quoted 12 lines
tools/perf/util/session.c +12 -0
+
+		ret = evlist__parse_sample(evlist, de->event, &orig_sample);
+		if (ret < 0) {
+			pr_err("failed to parse original sample\n");
+			break;
+		}
+
+		if (sample->tid != orig_sample.tid)
+			continue;
+
+		if (event->callchain_deferred.cookie == orig_sample.deferred_cookie)
+			sample__merge_deferred_callchain(&orig_sample, sample);

The sample__merge_deferred_callchain() initializes both orig_sample.deferred_callchain and the callchain. But now that it's not being called, it can cause the below free to happen with junk as the callchain. This needs:

		else
			orig_sample.deferred_callchain = false;
Quoted 5 lines
tools/perf/util/session.c +5 -0
+
+		evsel = evlist__id2evsel(evlist, orig_sample.id);
+		ret = evlist__deliver_sample(evlist, tool, de->event,
+					     &orig_sample, evsel,> machine); +
+		if (orig_sample.deferred_callchain)

tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1336

Quoted 10 lines
tools/perf/util/session.c +10 -0
+			free(orig_sample.callchain);
+
+		list_del(&de->list);
+		free(de);
+
+		if (ret)
+			break;
+	}
+	return ret;
+}

-- Steve

maintainer
#6 lore raw

Re: [PATCH v16 4/4] perf tools: Merge deferred user callchains

Quoted 42 lines · Steven Rostedt
Quoted 8 lines · On Mon, 08 Sep 2025 13:53:23 -0400 Steven Rostedt <[email protected]> wrote:
tools/perf/util/session.c +8 -0
+static int evlist__deliver_deferred_samples(struct evlist *evlist,
+					    const struct perf_tool *tool,
+					    union  perf_event *event,
+					    struct perf_sample *sample,
+					    struct machine *machine)
+{
+	struct deferred_event *de, *tmp;
+	struct evsel *evsel;

tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1308

Quoted 10 lines
tools/perf/util/session.c +10 -0
+	int ret = 0;
+
+	if (!tool->merge_deferred_callchains) {
+		evsel = evlist__id2evsel(evlist, sample->id);
+		return tool->callchain_deferred(tool, event, sample,
+						evsel, machine);
+	}
+
+	list_for_each_entry_safe(de, tmp, &evlist->deferred_samples, list) {
+		struct perf_sample orig_sample;

orig_sample is not initialized and can then contain junk.

tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1320

Quoted 12 lines
tools/perf/util/session.c +12 -0
+
+		ret = evlist__parse_sample(evlist, de->event, &orig_sample);
+		if (ret < 0) {
+			pr_err("failed to parse original sample\n");
+			break;
+		}
+
+		if (sample->tid != orig_sample.tid)
+			continue;
+
+		if (event->callchain_deferred.cookie == orig_sample.deferred_cookie)
+			sample__merge_deferred_callchain(&orig_sample, sample);

The sample__merge_deferred_callchain() initializes both orig_sample.deferred_callchain and the callchain. But now that it's not being called, it can cause the below free to happen with junk as the callchain. This needs:

Quoted 2 lines
		else
			orig_sample.deferred_callchain = false;

Ah, so I saw crashes from here and just deleted both free()s and got on with things ;-)

Quoted 17 lines
Quoted 5 lines
tools/perf/util/session.c +5 -0
+
+		evsel = evlist__id2evsel(evlist, orig_sample.id);
+		ret = evlist__deliver_sample(evlist, tool, de->event,
+					     &orig_sample, evsel,> machine); +
+		if (orig_sample.deferred_callchain)

tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1336

Quoted 10 lines
tools/perf/util/session.c +10 -0
+			free(orig_sample.callchain);
+
+		list_del(&de->list);
+		free(de);
+
+		if (ret)
+			break;
+	}
+	return ret;
+}

-- Steve

maintainer
#7 lore raw

Re: [PATCH v16 4/4] perf tools: Merge deferred user callchains

Quoted 10 lines · On Fri, 24 Oct 2025 15:02:03 +0200 Peter Zijlstra <[email protected]> wrote:
Quoted 7 lines

The sample__merge_deferred_callchain() initializes both orig_sample.deferred_callchain and the callchain. But now that it's not being called, it can cause the below free to happen with junk as the callchain. This needs:

		else
			orig_sample.deferred_callchain = false;  

Ah, so I saw crashes from here and just deleted both free()s and got on with things ;-)

I just downloaded your tree again and it doesn't look like it was updated.

Just didn't want you to forget about this ;)

-- Steve

maintainer
#8 lore raw

Re: [PATCH v16 4/4] perf tools: Merge deferred user callchains

Quoted 11 lines · Steven Rostedt
Quoted 8 lines · On Fri, 24 Oct 2025 15:02:03 +0200 Peter Zijlstra <[email protected]> wrote:
Quoted 7 lines

The sample__merge_deferred_callchain() initializes both orig_sample.deferred_callchain and the callchain. But now that it's not being called, it can cause the below free to happen with junk as the callchain. This needs:

		else
			orig_sample.deferred_callchain = false;  
Quoted 6 lines
Quoted 2 lines

Ah, so I saw crashes from here and just deleted both free()s and got on with things ;-)

I just downloaded your tree again and it doesn't look like it was updated.

Just didn't want you to forget about this ;)

Done, this should all be in tip/perf/core now. Thanks!

#9 lore raw

Re: [PATCH v16 4/4] perf tools: Merge deferred user callchains

In reply to Peter Zijlstra

Hello Namhyung,

could you please adapt your patches from this series to Peter's latest changes to unwind user and related perf support, especially his new version c69993ecdd4d ("perf: Support deferred user unwind") available at:

git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git perf/core

Quoted 43 lines · Peter Zijlstra
Quoted 42 lines · Steven Rostedt
Quoted 8 lines · On Mon, 08 Sep 2025 13:53:23 -0400 Steven Rostedt <[email protected]> wrote:
tools/perf/util/session.c +8 -0
+static int evlist__deliver_deferred_samples(struct evlist *evlist,
+					    const struct perf_tool *tool,
+					    union  perf_event *event,
+					    struct perf_sample *sample,
+					    struct machine *machine)
+{
+	struct deferred_event *de, *tmp;
+	struct evsel *evsel;

tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1308

Quoted 10 lines
tools/perf/util/session.c +10 -0
+	int ret = 0;
+
+	if (!tool->merge_deferred_callchains) {
+		evsel = evlist__id2evsel(evlist, sample->id);
+		return tool->callchain_deferred(tool, event, sample,
+						evsel, machine);
+	}
+
+	list_for_each_entry_safe(de, tmp, &evlist->deferred_samples, list) {
+		struct perf_sample orig_sample;

orig_sample is not initialized and can then contain junk.

tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1320

Quoted 12 lines
tools/perf/util/session.c +12 -0
+
+		ret = evlist__parse_sample(evlist, de->event, &orig_sample);
+		if (ret < 0) {
+			pr_err("failed to parse original sample\n");
+			break;
+		}
+
+		if (sample->tid != orig_sample.tid)
+			continue;
+
+		if (event->callchain_deferred.cookie == orig_sample.deferred_cookie)
+			sample__merge_deferred_callchain(&orig_sample, sample);

The sample__merge_deferred_callchain() initializes both orig_sample.deferred_callchain and the callchain. But now that it's not being called, it can cause the below free to happen with junk as the callchain. This needs:

Quoted 5 lines
Quoted 2 lines
		else
			orig_sample.deferred_callchain = false;

Ah, so I saw crashes from here and just deleted both free()s and got on with things ;-)

This needs to be properly resolved. In the meantime I am using Steven's suggestion above to continue my work on unwind user sframe (s390).

Quoted 10 lines
Quoted 10 lines
Quoted 10 lines
tools/perf/util/session.c +9 -0
+
+		evsel = evlist__id2evsel(evlist, orig_sample.id);
+		ret = evlist__deliver_sample(evlist, tool, de->event,
+					     &orig_sample, evsel,> machine); +
+		if (orig_sample.deferred_callchain)
+			free(orig_sample.callchain);
+
+		list_del(&de->list);
+		free(de);
Quoted 8 lines
Quoted 8 lines

tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1336

Quoted 6 lines
tools/perf/util/session.c +6 -0
+
+		if (ret)
+			break;
+	}
+	return ret;
+}

-- Steve

Thanks and regards,
Jens

Signature · 9 lines
Jens Remus
Linux on Z Development (D3303)
+49-7031-16-1128 Office
[email protected]

IBM

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Böblingen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
maintainer
#10 lore raw

Re: [PATCH v16 4/4] perf tools: Merge deferred user callchains

Hello,

Quoted 8 lines · Jens Remus

Hello Namhyung,

could you please adapt your patches from this series to Peter's latest changes to unwind user and related perf support, especially his new version c69993ecdd4d ("perf: Support deferred user unwind") available at:

git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git perf/core

Sure, will take a look.

Thanks,
Namhyung

Quoted 86 lines · Peter Zijlstra
Quoted 43 lines · Steven Rostedt
Quoted 42 lines
Quoted 8 lines · On Mon, 08 Sep 2025 13:53:23 -0400 Steven Rostedt <[email protected]> wrote:
tools/perf/util/session.c +8 -0
+static int evlist__deliver_deferred_samples(struct evlist *evlist,
+					    const struct perf_tool *tool,
+					    union  perf_event *event,
+					    struct perf_sample *sample,
+					    struct machine *machine)
+{
+	struct deferred_event *de, *tmp;
+	struct evsel *evsel;

tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1308

Quoted 10 lines
tools/perf/util/session.c +10 -0
+	int ret = 0;
+
+	if (!tool->merge_deferred_callchains) {
+		evsel = evlist__id2evsel(evlist, sample->id);
+		return tool->callchain_deferred(tool, event, sample,
+						evsel, machine);
+	}
+
+	list_for_each_entry_safe(de, tmp, &evlist->deferred_samples, list) {
+		struct perf_sample orig_sample;

orig_sample is not initialized and can then contain junk.

tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1320

Quoted 12 lines
tools/perf/util/session.c +12 -0
+
+		ret = evlist__parse_sample(evlist, de->event, &orig_sample);
+		if (ret < 0) {
+			pr_err("failed to parse original sample\n");
+			break;
+		}
+
+		if (sample->tid != orig_sample.tid)
+			continue;
+
+		if (event->callchain_deferred.cookie == orig_sample.deferred_cookie)
+			sample__merge_deferred_callchain(&orig_sample, sample);

The sample__merge_deferred_callchain() initializes both orig_sample.deferred_callchain and the callchain. But now that it's not being called, it can cause the below free to happen with junk as the callchain. This needs:

Quoted 5 lines
Quoted 2 lines
		else
			orig_sample.deferred_callchain = false;

Ah, so I saw crashes from here and just deleted both free()s and got on with things ;-)

This needs to be properly resolved. In the meantime I am using Steven's suggestion above to continue my work on unwind user sframe (s390).

Quoted 10 lines
Quoted 10 lines
Quoted 10 lines
tools/perf/util/session.c +9 -0
+
+		evsel = evlist__id2evsel(evlist, orig_sample.id);
+		ret = evlist__deliver_sample(evlist, tool, de->event,
+					     &orig_sample, evsel,> machine); +
+		if (orig_sample.deferred_callchain)
+			free(orig_sample.callchain);
+
+		list_del(&de->list);
+		free(de);
Quoted 8 lines
Quoted 8 lines

tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1336

Quoted 6 lines
tools/perf/util/session.c +6 -0
+
+		if (ret)
+			break;
+	}
+	return ret;
+}

-- Steve

Thanks and regards,
Jens

Signature · 9 lines
Jens Remus
Linux on Z Development (D3303)
+49-7031-16-1128 Office
[email protected]

IBM

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Böblingen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
maintainer
#11 lore raw

Re: [PATCH v16 4/4] perf tools: Merge deferred user callchains

In reply to Peter Zijlstra

Hello,

Sorry for the delay. And I'm happy that the kernel part is merge to the tip tree! :)

Quoted 16 lines · Peter Zijlstra
Quoted 15 lines · Steven Rostedt
Quoted 12 lines · On Mon, 08 Sep 2025 13:53:23 -0400 Steven Rostedt <[email protected]> wrote:
tools/perf/util/session.c +12 -0
+static int evlist__deliver_deferred_samples(struct evlist *evlist,
+					    const struct perf_tool *tool,
+					    union  perf_event *event,
+					    struct perf_sample *sample,
+					    struct machine *machine)
+{
+	struct deferred_event *de, *tmp;
+	struct evsel *evsel;
+	int ret = 0;
+
+	if (!tool->merge_deferred_callchains) {
+		evsel = evlist__id2evsel(evlist, sample->id);
Quoted 8 lines
Quoted 8 lines

tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1308

Quoted 6 lines
tools/perf/util/session.c +6 -0
+		return tool->callchain_deferred(tool, event, sample,
+						evsel, machine);
+	}
+
+	list_for_each_entry_safe(de, tmp, &evlist->deferred_samples, list) {
+		struct perf_sample orig_sample;

orig_sample is not initialized and can then contain junk.

Yep.

Quoted 3 lines
Quoted 3 lines

Review

Quoted 3 lines
(quoted diff) +2 -0
+
+		ret = evlist__parse_sample(evlist, de->event, &orig_sample);

But here you call evlist__parse_sample() and evsel__parse_sample() which should initialize the sample properly.

Quoted 16 lines
Quoted 16 lines

tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1320

Quoted 10 lines
tools/perf/util/session.c +10 -0
+		if (ret < 0) {
+			pr_err("failed to parse original sample\n");
+			break;
+		}
+
+		if (sample->tid != orig_sample.tid)
+			continue;
+
+		if (event->callchain_deferred.cookie == orig_sample.deferred_cookie)
+			sample__merge_deferred_callchain(&orig_sample, sample);

The sample__merge_deferred_callchain() initializes both orig_sample.deferred_callchain and the callchain. But now that it's not being called, it can cause the below free to happen with junk as the callchain. This needs:

Quoted 5 lines
Quoted 2 lines
		else
			orig_sample.deferred_callchain = false;

Ah, so I saw crashes from here and just deleted both free()s and got on with things ;-)

I don't understand how it can have the garbage. But having the else part would be safer.

Thanks,
Namhyung

Quoted 17 lines
Quoted 17 lines
Quoted 5 lines
tools/perf/util/session.c +5 -0
+
+		evsel = evlist__id2evsel(evlist, orig_sample.id);
+		ret = evlist__deliver_sample(evlist, tool, de->event,
+					     &orig_sample, evsel,> machine); +
+		if (orig_sample.deferred_callchain)

tools/perf/util/session.c static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool · line 1336

Quoted 10 lines
tools/perf/util/session.c +10 -0
+			free(orig_sample.callchain);
+
+		list_del(&de->list);
+		free(de);
+
+		if (ret)
+			break;
+	}
+	return ret;
+}

-- Steve

maintainer
#12 lore raw
In reply to Steven Rostedt

Per the hackery I did:

https://lkml.kernel.org/r/[email protected]

The userspace bits need something like so on top..

---
 tools/perf/util/callchain.c               |    2 +-
 tools/perf/util/evsel.c                   |   10 +++++++---
 tools/perf/util/perf_event_attr_fprintf.c |    1 +
 3 files changed, 9 insertions(+), 4 deletions(-)
tools/perf/util/callchain.c +1 -1
@@ -1832,7 +1832,7 @@ int sample__for_each_callchain_node(stru
 int sample__merge_deferred_callchain(struct perf_sample *sample_orig,
 				     struct perf_sample *sample_callchain)
 {
-	u64 nr_orig = sample_orig->callchain->nr - PERF_DEFERRED_ITEMS;
+	u64 nr_orig = sample_orig->callchain->nr - 1;
 	u64 nr_deferred = sample_callchain->callchain->nr;
 	struct ip_callchain *callchain;
 
tools/perf/util/evsel.c +7 -3
@@ -1520,6 +1520,7 @@ void evsel__config(struct evsel *evsel,
 	attr->mmap2    = track && !perf_missing_features.mmap2;
 	attr->comm     = track;
 	attr->build_id = track && opts->build_id;
+	attr->defer_output = track && !perf_missing_features.defer_callchain;
 
 	/*
 	 * ksymbol is tracked separately with text poke because it needs to be
@@ -2206,8 +2207,10 @@ static int __evsel__prepare_open(struct
 
 static void evsel__disable_missing_features(struct evsel *evsel)
 {
-	if (perf_missing_features.defer_callchain)
+	if (perf_missing_features.defer_callchain) {
 		evsel->core.attr.defer_callchain = 0;
+		evsel->core.attr.defer_output = 0;
+	}
 	if (perf_missing_features.inherit_sample_read && evsel->core.attr.inherit &&
 	    (evsel->core.attr.sample_type & PERF_SAMPLE_READ))
 		evsel->core.attr.inherit = 0;
@@ -2489,6 +2492,7 @@ static bool evsel__detect_missing_featur
 	perf_missing_features.defer_callchain = true;
 	pr_debug2("switching off deferred callchain support\n");
 	attr.defer_callchain = false;
+	attr.defer_output = false;
 	attr.sample_type = 0;
 
 	attr.inherit = true;
@@ -3255,8 +3259,8 @@ int evsel__parse_sample(struct evsel *ev
 			return -EFAULT;
 		sz = data->callchain->nr * sizeof(u64);
 		if (evsel->core.attr.defer_callchain &&
-		    data->callchain->nr >= PERF_DEFERRED_ITEMS &&
-		    data->callchain->ips[data->callchain->nr - PERF_DEFERRED_ITEMS] == PERF_CONTEXT_USER_DEFERRED) {
+		    data->callchain->nr >= 2 &&
+		    data->callchain->ips[data->callchain->nr - 2] == PERF_CONTEXT_USER_DEFERRED) {
 			data->deferred_callchain = true;
 			data->deferred_cookie = data->callchain->ips[data->callchain->nr - 1];
 		}
tools/perf/util/perf_event_attr_fprintf.c +1 -0
@@ -344,6 +344,7 @@ int perf_event_attr__fprintf(FILE *fp, s
 	PRINT_ATTRf(remove_on_exec, p_unsigned);
 	PRINT_ATTRf(sigtrap, p_unsigned);
 	PRINT_ATTRf(defer_callchain, p_unsigned);
+	PRINT_ATTRf(defer_output, p_unsigned);
 
 	PRINT_ATTRn("{ wakeup_events, wakeup_watermark }", wakeup_events, p_unsigned, false);
 	PRINT_ATTRf(bp_type, p_unsigned);