Skip to content

Commit 71753c6

Browse files
jpoimboerostedt
authored andcommitted
unwind_user: Add user space unwinding API with frame pointer support
Introduce a generic API for unwinding user stacks. In order to expand user space unwinding to be able to handle more complex scenarios, such as deferred unwinding and reading user space information, create a generic interface that all architectures can use that support the various unwinding methods. This is an alternative method for handling user space stack traces from the simple stack_trace_save_user() API. This does not replace that interface, but this interface will be used to expand the functionality of user space stack walking. None of the structures introduced will be exposed to user space tooling. Support for frame pointer unwinding is added. For an architecture to support frame pointer unwinding it needs to enable CONFIG_HAVE_UNWIND_USER_FP and define ARCH_INIT_USER_FP_FRAME. By encoding the frame offsets in struct unwind_user_frame, much of this code can also be reused for future unwinder implementations like sframe. Cc: Masami Hiramatsu <[email protected]> Cc: Peter Zijlstra <[email protected]> Cc: Ingo Molnar <[email protected]> Cc: Jiri Olsa <[email protected]> Cc: Arnaldo Carvalho de Melo <[email protected]> Cc: Namhyung Kim <[email protected]> Cc: Thomas Gleixner <[email protected]> Cc: Andrii Nakryiko <[email protected]> Cc: Indu Bhagat <[email protected]> Cc: "Jose E. Marchesi" <[email protected]> Cc: Beau Belgrave <[email protected]> Cc: Linus Torvalds <[email protected]> Cc: Andrew Morton <[email protected]> Cc: Jens Axboe <[email protected]> Cc: Florian Weimer <[email protected]> Cc: Sam James <[email protected]> Link: https://lore.kernel.org/[email protected] Reviewed-by: Jens Remus <[email protected]> Signed-off-by: Josh Poimboeuf <[email protected]> Co-developed-by: Mathieu Desnoyers <[email protected]> Link: https://lore.kernel.org/all/[email protected]/ Signed-off-by: Mathieu Desnoyers <[email protected]> Co-developed-by: Steven Rostedt (Google) <[email protected]> Signed-off-by: Steven Rostedt (Google) <[email protected]>
1 parent d7b8f8e commit 71753c6

File tree

9 files changed

+209
-0
lines changed

9 files changed

+209
-0
lines changed

MAINTAINERS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25928,6 +25928,14 @@ F: Documentation/driver-api/uio-howto.rst
2592825928
F: drivers/uio/
2592925929
F: include/linux/uio_driver.h
2593025930

25931+
USERSPACE STACK UNWINDING
25932+
M: Josh Poimboeuf <[email protected]>
25933+
M: Steven Rostedt <[email protected]>
25934+
S: Maintained
25935+
F: include/linux/unwind*.h
25936+
F: kernel/unwind/
25937+
25938+
2593125939
UTIL-LINUX PACKAGE
2593225940
M: Karel Zak <[email protected]>
2593325941

arch/Kconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,13 @@ config HAVE_HARDLOCKUP_DETECTOR_ARCH
435435
It uses the same command line parameters, and sysctl interface,
436436
as the generic hardlockup detectors.
437437

438+
config UNWIND_USER
439+
bool
440+
441+
config HAVE_UNWIND_USER_FP
442+
bool
443+
select UNWIND_USER
444+
438445
config HAVE_PERF_REGS
439446
bool
440447
help

include/asm-generic/Kbuild

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ mandatory-y += tlbflush.h
5959
mandatory-y += topology.h
6060
mandatory-y += trace_clock.h
6161
mandatory-y += uaccess.h
62+
mandatory-y += unwind_user.h
6263
mandatory-y += vermagic.h
6364
mandatory-y += vga.h
6465
mandatory-y += video.h

include/asm-generic/unwind_user.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
#ifndef _ASM_GENERIC_UNWIND_USER_H
3+
#define _ASM_GENERIC_UNWIND_USER_H
4+
5+
#endif /* _ASM_GENERIC_UNWIND_USER_H */

include/linux/unwind_user.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
#ifndef _LINUX_UNWIND_USER_H
3+
#define _LINUX_UNWIND_USER_H
4+
5+
#include <linux/unwind_user_types.h>
6+
#include <asm/unwind_user.h>
7+
8+
#ifndef ARCH_INIT_USER_FP_FRAME
9+
#define ARCH_INIT_USER_FP_FRAME
10+
#endif
11+
12+
int unwind_user(struct unwind_stacktrace *trace, unsigned int max_entries);
13+
14+
#endif /* _LINUX_UNWIND_USER_H */

include/linux/unwind_user_types.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
#ifndef _LINUX_UNWIND_USER_TYPES_H
3+
#define _LINUX_UNWIND_USER_TYPES_H
4+
5+
#include <linux/types.h>
6+
7+
/*
8+
* Unwind types, listed in priority order: lower numbers are attempted first if
9+
* available.
10+
*/
11+
enum unwind_user_type_bits {
12+
UNWIND_USER_TYPE_FP_BIT = 0,
13+
14+
NR_UNWIND_USER_TYPE_BITS,
15+
};
16+
17+
enum unwind_user_type {
18+
/* Type "none" for the start of stack walk iteration. */
19+
UNWIND_USER_TYPE_NONE = 0,
20+
UNWIND_USER_TYPE_FP = BIT(UNWIND_USER_TYPE_FP_BIT),
21+
};
22+
23+
struct unwind_stacktrace {
24+
unsigned int nr;
25+
unsigned long *entries;
26+
};
27+
28+
struct unwind_user_frame {
29+
s32 cfa_off;
30+
s32 ra_off;
31+
s32 fp_off;
32+
bool use_fp;
33+
};
34+
35+
struct unwind_user_state {
36+
unsigned long ip;
37+
unsigned long sp;
38+
unsigned long fp;
39+
enum unwind_user_type current_type;
40+
unsigned int available_types;
41+
bool done;
42+
};
43+
44+
#endif /* _LINUX_UNWIND_USER_TYPES_H */

kernel/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ obj-y += rcu/
5555
obj-y += livepatch/
5656
obj-y += dma/
5757
obj-y += entry/
58+
obj-y += unwind/
5859
obj-$(CONFIG_MODULES) += module/
5960

6061
obj-$(CONFIG_KCMP) += kcmp.o

kernel/unwind/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
obj-$(CONFIG_UNWIND_USER) += user.o

kernel/unwind/user.c

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Generic interfaces for unwinding user space
4+
*/
5+
#include <linux/kernel.h>
6+
#include <linux/sched.h>
7+
#include <linux/sched/task_stack.h>
8+
#include <linux/unwind_user.h>
9+
#include <linux/uaccess.h>
10+
11+
static const struct unwind_user_frame fp_frame = {
12+
ARCH_INIT_USER_FP_FRAME
13+
};
14+
15+
#define for_each_user_frame(state) \
16+
for (unwind_user_start(state); !(state)->done; unwind_user_next(state))
17+
18+
static int unwind_user_next_fp(struct unwind_user_state *state)
19+
{
20+
const struct unwind_user_frame *frame = &fp_frame;
21+
unsigned long cfa, fp, ra;
22+
unsigned int shift;
23+
24+
if (frame->use_fp) {
25+
if (state->fp < state->sp)
26+
return -EINVAL;
27+
cfa = state->fp;
28+
} else {
29+
cfa = state->sp;
30+
}
31+
32+
/* Get the Canonical Frame Address (CFA) */
33+
cfa += frame->cfa_off;
34+
35+
/* stack going in wrong direction? */
36+
if (cfa <= state->sp)
37+
return -EINVAL;
38+
39+
/* Make sure that the address is word aligned */
40+
shift = sizeof(long) == 4 ? 2 : 3;
41+
if (cfa & ((1 << shift) - 1))
42+
return -EINVAL;
43+
44+
/* Find the Return Address (RA) */
45+
if (get_user(ra, (unsigned long *)(cfa + frame->ra_off)))
46+
return -EINVAL;
47+
48+
if (frame->fp_off && get_user(fp, (unsigned long __user *)(cfa + frame->fp_off)))
49+
return -EINVAL;
50+
51+
state->ip = ra;
52+
state->sp = cfa;
53+
if (frame->fp_off)
54+
state->fp = fp;
55+
return 0;
56+
}
57+
58+
static int unwind_user_next(struct unwind_user_state *state)
59+
{
60+
unsigned long iter_mask = state->available_types;
61+
unsigned int bit;
62+
63+
if (state->done)
64+
return -EINVAL;
65+
66+
for_each_set_bit(bit, &iter_mask, NR_UNWIND_USER_TYPE_BITS) {
67+
enum unwind_user_type type = BIT(bit);
68+
69+
state->current_type = type;
70+
switch (type) {
71+
case UNWIND_USER_TYPE_FP:
72+
if (!unwind_user_next_fp(state))
73+
return 0;
74+
continue;
75+
default:
76+
WARN_ONCE(1, "Undefined unwind bit %d", bit);
77+
break;
78+
}
79+
break;
80+
}
81+
82+
/* No successful unwind method. */
83+
state->current_type = UNWIND_USER_TYPE_NONE;
84+
state->done = true;
85+
return -EINVAL;
86+
}
87+
88+
static int unwind_user_start(struct unwind_user_state *state)
89+
{
90+
struct pt_regs *regs = task_pt_regs(current);
91+
92+
memset(state, 0, sizeof(*state));
93+
94+
if ((current->flags & PF_KTHREAD) || !user_mode(regs)) {
95+
state->done = true;
96+
return -EINVAL;
97+
}
98+
99+
if (IS_ENABLED(CONFIG_HAVE_UNWIND_USER_FP))
100+
state->available_types |= UNWIND_USER_TYPE_FP;
101+
102+
state->ip = instruction_pointer(regs);
103+
state->sp = user_stack_pointer(regs);
104+
state->fp = frame_pointer(regs);
105+
106+
return 0;
107+
}
108+
109+
int unwind_user(struct unwind_stacktrace *trace, unsigned int max_entries)
110+
{
111+
struct unwind_user_state state;
112+
113+
trace->nr = 0;
114+
115+
if (!max_entries)
116+
return -EINVAL;
117+
118+
if (current->flags & PF_KTHREAD)
119+
return 0;
120+
121+
for_each_user_frame(&state) {
122+
trace->entries[trace->nr++] = state.ip;
123+
if (trace->nr >= max_entries)
124+
break;
125+
}
126+
127+
return 0;
128+
}

0 commit comments

Comments
 (0)