| /* 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 */ |