summaryrefslogtreecommitdiff
path: root/arch/arm64/include/asm/stacktrace/common.h
blob: 821a8fdd31af470091ec6cc0691afd28f7268eb3 (plain) (blame)
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
166
167
168
169
170
171
/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Common arm64 stack unwinder code.
 *
 * See: arch/arm64/kernel/stacktrace.c for the reference implementation.
 *
 * Copyright (C) 2012 ARM Ltd.
 */
#ifndef __ASM_STACKTRACE_COMMON_H
#define __ASM_STACKTRACE_COMMON_H

#include <linux/types.h>

struct stack_info {
	unsigned long low;
	unsigned long high;
};

/**
 * struct unwind_state - state used for robust unwinding.
 *
 * @fp:          The fp value in the frame record (or the real fp)
 * @pc:          The lr value in the frame record (or the real lr)
 *
 * @stack:       The stack currently being unwound.
 * @stacks:      An array of stacks which can be unwound.
 * @nr_stacks:   The number of stacks in @stacks.
 */
struct unwind_state {
	unsigned long fp;
	unsigned long pc;

	struct stack_info stack;
	struct stack_info *stacks;
	int nr_stacks;
};

static inline struct stack_info stackinfo_get_unknown(void)
{
	return (struct stack_info) {
		.low = 0,
		.high = 0,
	};
}

static inline bool stackinfo_on_stack(const struct stack_info *info,
				      unsigned long sp, unsigned long size)
{
	if (!info->low)
		return false;

	if (sp < info->low || sp + size < sp || sp + size > info->high)
		return false;

	return true;
}

static inline void unwind_init_common(struct unwind_state *state)
{
	state->stack = stackinfo_get_unknown();
}

/**
 * unwind_find_stack() - Find the accessible stack which entirely contains an
 * object.
 *
 * @state: the current unwind state.
 * @sp:    the base address of the object.
 * @size:  the size of the object.
 *
 * Return: a pointer to the relevant stack_info if found; NULL otherwise.
 */
static struct stack_info *unwind_find_stack(struct unwind_state *state,
					    unsigned long sp,
					    unsigned long size)
{
	struct stack_info *info = &state->stack;

	if (stackinfo_on_stack(info, sp, size))
		return info;

	for (int i = 0; i < state->nr_stacks; i++) {
		info = &state->stacks[i];
		if (stackinfo_on_stack(info, sp, size))
			return info;
	}

	return NULL;
}

/**
 * unwind_consume_stack() - Update stack boundaries so that future unwind steps
 * cannot consume this object again.
 *
 * @state: the current unwind state.
 * @info:  the stack_info of the stack containing the object.
 * @sp:    the base address of the object.
 * @size:  the size of the object.
 *
 * Return: 0 upon success, an error code otherwise.
 */
static inline void unwind_consume_stack(struct unwind_state *state,
					struct stack_info *info,
					unsigned long sp,
					unsigned long size)
{
	struct stack_info tmp;

	/*
	 * Stack transitions are strictly one-way, and once we've
	 * transitioned from one stack to another, it's never valid to
	 * unwind back to the old stack.
	 *
	 * Destroy the old stack info so that it cannot be found upon a
	 * subsequent transition. If the stack has not changed, we'll
	 * immediately restore the current stack info.
	 *
	 * Note that stacks can nest in several valid orders, e.g.
	 *
	 *   TASK -> IRQ -> OVERFLOW -> SDEI_NORMAL
	 *   TASK -> SDEI_NORMAL -> SDEI_CRITICAL -> OVERFLOW
	 *   HYP -> OVERFLOW
	 *
	 * ... so we do not check the specific order of stack
	 * transitions.
	 */
	tmp = *info;
	*info = stackinfo_get_unknown();
	state->stack = tmp;

	/*
	 * Future unwind steps can only consume stack above this frame record.
	 * Update the current stack to start immediately above it.
	 */
	state->stack.low = sp + size;
}

/**
 * unwind_next_frame_record() - Unwind to the next frame record.
 *
 * @state:        the current unwind state.
 *
 * Return: 0 upon success, an error code otherwise.
 */
static inline int
unwind_next_frame_record(struct unwind_state *state)
{
	struct stack_info *info;
	struct frame_record *record;
	unsigned long fp = state->fp;

	if (fp & 0x7)
		return -EINVAL;

	info = unwind_find_stack(state, fp, sizeof(*record));
	if (!info)
		return -EINVAL;

	unwind_consume_stack(state, info, fp, sizeof(*record));

	/*
	 * Record this frame record's values.
	 */
	record = (struct frame_record *)fp;
	state->fp = READ_ONCE(record->fp);
	state->pc = READ_ONCE(record->lr);

	return 0;
}

#endif	/* __ASM_STACKTRACE_COMMON_H */