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