Skip to content

Commit 4a365eb

Browse files
anakryikomhiramat
authored andcommitted
perf,uprobes: fix user stack traces in the presence of pending uretprobes
When kernel has pending uretprobes installed, it hijacks original user function return address on the stack with a uretprobe trampoline address. There could be multiple such pending uretprobes (either on different user functions or on the same recursive one) at any given time within the same task. This approach interferes with the user stack trace capture logic, which would report suprising addresses (like 0x7fffffffe000) that correspond to a special "[uprobes]" section that kernel installs in the target process address space for uretprobe trampoline code, while logically it should be an address somewhere within the calling function of another traced user function. This is easy to correct for, though. Uprobes subsystem keeps track of pending uretprobes and records original return addresses. This patch is using this to do a post-processing step and restore each trampoline address entries with correct original return address. This is done only if there are pending uretprobes for current task. This is a similar approach to what fprobe/kretprobe infrastructure is doing when capturing kernel stack traces in the presence of pending return probes. Link: https://lore.kernel.org/all/[email protected]/ Reported-by: Riham Selim <[email protected]> Signed-off-by: Andrii Nakryiko <[email protected]> Signed-off-by: Masami Hiramatsu (Google) <[email protected]>
1 parent 3eddb03 commit 4a365eb

File tree

2 files changed

+51
-1
lines changed

2 files changed

+51
-1
lines changed

kernel/events/callchain.c

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <linux/perf_event.h>
1212
#include <linux/slab.h>
1313
#include <linux/sched/task_stack.h>
14+
#include <linux/uprobes.h>
1415

1516
#include "internal.h"
1617

@@ -176,13 +177,51 @@ put_callchain_entry(int rctx)
176177
put_recursion_context(this_cpu_ptr(callchain_recursion), rctx);
177178
}
178179

180+
static void fixup_uretprobe_trampoline_entries(struct perf_callchain_entry *entry,
181+
int start_entry_idx)
182+
{
183+
#ifdef CONFIG_UPROBES
184+
struct uprobe_task *utask = current->utask;
185+
struct return_instance *ri;
186+
__u64 *cur_ip, *last_ip, tramp_addr;
187+
188+
if (likely(!utask || !utask->return_instances))
189+
return;
190+
191+
cur_ip = &entry->ip[start_entry_idx];
192+
last_ip = &entry->ip[entry->nr - 1];
193+
ri = utask->return_instances;
194+
tramp_addr = uprobe_get_trampoline_vaddr();
195+
196+
/*
197+
* If there are pending uretprobes for the current thread, they are
198+
* recorded in a list inside utask->return_instances; each such
199+
* pending uretprobe replaces traced user function's return address on
200+
* the stack, so when stack trace is captured, instead of seeing
201+
* actual function's return address, we'll have one or many uretprobe
202+
* trampoline addresses in the stack trace, which are not helpful and
203+
* misleading to users.
204+
* So here we go over the pending list of uretprobes, and each
205+
* encountered trampoline address is replaced with actual return
206+
* address.
207+
*/
208+
while (ri && cur_ip <= last_ip) {
209+
if (*cur_ip == tramp_addr) {
210+
*cur_ip = ri->orig_ret_vaddr;
211+
ri = ri->next;
212+
}
213+
cur_ip++;
214+
}
215+
#endif
216+
}
217+
179218
struct perf_callchain_entry *
180219
get_perf_callchain(struct pt_regs *regs, u32 init_nr, bool kernel, bool user,
181220
u32 max_stack, bool crosstask, bool add_mark)
182221
{
183222
struct perf_callchain_entry *entry;
184223
struct perf_callchain_entry_ctx ctx;
185-
int rctx;
224+
int rctx, start_entry_idx;
186225

187226
entry = get_callchain_entry(&rctx);
188227
if (!entry)
@@ -215,7 +254,9 @@ get_perf_callchain(struct pt_regs *regs, u32 init_nr, bool kernel, bool user,
215254
if (add_mark)
216255
perf_callchain_store_context(&ctx, PERF_CONTEXT_USER);
217256

257+
start_entry_idx = entry->nr;
218258
perf_callchain_user(&ctx, regs);
259+
fixup_uretprobe_trampoline_entries(entry, start_entry_idx);
219260
}
220261
}
221262

kernel/events/uprobes.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2159,6 +2159,15 @@ void uprobe_handle_trampoline(struct pt_regs *regs)
21592159

21602160
instruction_pointer_set(regs, ri->orig_ret_vaddr);
21612161
do {
2162+
/* pop current instance from the stack of pending return instances,
2163+
* as it's not pending anymore: we just fixed up original
2164+
* instruction pointer in regs and are about to call handlers;
2165+
* this allows fixup_uretprobe_trampoline_entries() to properly fix up
2166+
* captured stack traces from uretprobe handlers, in which pending
2167+
* trampoline addresses on the stack are replaced with correct
2168+
* original return addresses
2169+
*/
2170+
utask->return_instances = ri->next;
21622171
if (valid)
21632172
handle_uretprobe_chain(ri, regs);
21642173
ri = free_ret_instance(ri);

0 commit comments

Comments
 (0)