]>
git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blob - arch/arm64/kernel/stacktrace.c
2 * Stack tracing support
4 * Copyright (C) 2012 ARM Ltd.
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 #include <linux/kernel.h>
19 #include <linux/export.h>
20 #include <linux/ftrace.h>
21 #include <linux/sched.h>
22 #include <linux/stacktrace.h>
25 #include <asm/stack_pointer.h>
26 #include <asm/stacktrace.h>
29 * AArch64 PCS assigns the frame pointer to x29.
31 * A simple function prologue looks like this:
36 * A simple function epilogue looks like this:
41 int notrace
unwind_frame(struct task_struct
*tsk
, struct stackframe
*frame
)
43 unsigned long high
, low
;
44 unsigned long fp
= frame
->fp
;
45 unsigned long irq_stack_ptr
;
51 * Switching between stacks is valid when tracing current and in
52 * non-preemptible context.
54 if (tsk
== current
&& !preemptible())
55 irq_stack_ptr
= IRQ_STACK_PTR(smp_processor_id());
60 /* irq stacks are not THREAD_SIZE aligned */
61 if (on_irq_stack(frame
->sp
, raw_smp_processor_id()))
64 high
= ALIGN(low
, THREAD_SIZE
) - 0x20;
66 if (fp
< low
|| fp
> high
|| fp
& 0xf)
69 frame
->sp
= fp
+ 0x10;
70 frame
->fp
= READ_ONCE_NOCHECK(*(unsigned long *)(fp
));
71 frame
->pc
= READ_ONCE_NOCHECK(*(unsigned long *)(fp
+ 8));
73 #ifdef CONFIG_FUNCTION_GRAPH_TRACER
75 (frame
->pc
== (unsigned long)return_to_handler
)) {
77 * This is a case where function graph tracer has
78 * modified a return address (LR) in a stack frame
79 * to hook a function return.
80 * So replace it to an original value.
82 frame
->pc
= tsk
->ret_stack
[frame
->graph
--].ret
;
84 #endif /* CONFIG_FUNCTION_GRAPH_TRACER */
87 * Check whether we are going to walk through from interrupt stack
89 * If we reach the end of the stack - and its an interrupt stack,
90 * unpack the dummy frame to find the original elr.
92 * Check the frame->fp we read from the bottom of the irq_stack,
93 * and the original task stack pointer are both in current->stack.
95 if (frame
->sp
== irq_stack_ptr
) {
96 struct pt_regs
*irq_args
;
97 unsigned long orig_sp
= IRQ_STACK_TO_TASK_STACK(irq_stack_ptr
);
99 if (object_is_on_stack((void *)orig_sp
) &&
100 object_is_on_stack((void *)frame
->fp
)) {
103 /* orig_sp is the saved pt_regs, find the elr */
104 irq_args
= (struct pt_regs
*)orig_sp
;
105 frame
->pc
= irq_args
->pc
;
108 * This frame has a non-standard format, and we
109 * didn't fix it, because the data looked wrong.
110 * Refuse to output this frame.
119 void notrace
walk_stackframe(struct task_struct
*tsk
, struct stackframe
*frame
,
120 int (*fn
)(struct stackframe
*, void *), void *data
)
127 ret
= unwind_frame(tsk
, frame
);
133 #ifdef CONFIG_STACKTRACE
134 struct stack_trace_data
{
135 struct stack_trace
*trace
;
136 unsigned int no_sched_functions
;
140 static int save_trace(struct stackframe
*frame
, void *d
)
142 struct stack_trace_data
*data
= d
;
143 struct stack_trace
*trace
= data
->trace
;
144 unsigned long addr
= frame
->pc
;
146 if (data
->no_sched_functions
&& in_sched_functions(addr
))
153 trace
->entries
[trace
->nr_entries
++] = addr
;
155 return trace
->nr_entries
>= trace
->max_entries
;
158 void save_stack_trace_regs(struct pt_regs
*regs
, struct stack_trace
*trace
)
160 struct stack_trace_data data
;
161 struct stackframe frame
;
164 data
.skip
= trace
->skip
;
165 data
.no_sched_functions
= 0;
167 frame
.fp
= regs
->regs
[29];
170 #ifdef CONFIG_FUNCTION_GRAPH_TRACER
171 frame
.graph
= current
->curr_ret_stack
;
174 walk_stackframe(current
, &frame
, save_trace
, &data
);
175 if (trace
->nr_entries
< trace
->max_entries
)
176 trace
->entries
[trace
->nr_entries
++] = ULONG_MAX
;
179 void save_stack_trace_tsk(struct task_struct
*tsk
, struct stack_trace
*trace
)
181 struct stack_trace_data data
;
182 struct stackframe frame
;
184 if (!try_get_task_stack(tsk
))
188 data
.skip
= trace
->skip
;
190 if (tsk
!= current
) {
191 data
.no_sched_functions
= 1;
192 frame
.fp
= thread_saved_fp(tsk
);
193 frame
.sp
= thread_saved_sp(tsk
);
194 frame
.pc
= thread_saved_pc(tsk
);
196 data
.no_sched_functions
= 0;
197 frame
.fp
= (unsigned long)__builtin_frame_address(0);
198 frame
.sp
= current_stack_pointer
;
199 frame
.pc
= (unsigned long)save_stack_trace_tsk
;
201 #ifdef CONFIG_FUNCTION_GRAPH_TRACER
202 frame
.graph
= tsk
->curr_ret_stack
;
205 walk_stackframe(tsk
, &frame
, save_trace
, &data
);
206 if (trace
->nr_entries
< trace
->max_entries
)
207 trace
->entries
[trace
->nr_entries
++] = ULONG_MAX
;
212 void save_stack_trace(struct stack_trace
*trace
)
214 save_stack_trace_tsk(current
, trace
);
216 EXPORT_SYMBOL_GPL(save_stack_trace
);