| /* 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(); | |
| } | |
| static struct stack_info *unwind_find_next_stack(const struct unwind_state *state, | |
| unsigned long sp, | |
| unsigned long size) | |
| { | |
| for (int i = 0; i < state->nr_stacks; i++) { | |
| struct stack_info *info = &state->stacks[i]; | |
| if (stackinfo_on_stack(info, sp, size)) | |
| return info; | |
| } | |
| return NULL; | |
| } | |
| /** | |
| * unwind_consume_stack() - Check if an object is on an accessible stack, | |
| * updating stack boundaries so that future unwind steps cannot consume this | |
| * object again. | |
| * | |
| * @state: the current unwind state. | |
| * @sp: the base address of the object. | |
| * @size: the size of the object. | |
| * | |
| * Return: 0 upon success, an error code otherwise. | |
| */ | |
| static inline int unwind_consume_stack(struct unwind_state *state, | |
| unsigned long sp, | |
| unsigned long size) | |
| { | |
| struct stack_info *next; | |
| if (stackinfo_on_stack(&state->stack, sp, size)) | |
| goto found; | |
| next = unwind_find_next_stack(state, sp, size); | |
| if (!next) | |
| return -EINVAL; | |
| /* | |
| * 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. | |
| * | |
| * Remove the current stack from the list of stacks so that it cannot | |
| * be found on a subsequent transition. | |
| * | |
| * 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. | |
| */ | |
| state->stack = *next; | |
| *next = stackinfo_get_unknown(); | |
| found: | |
| /* | |
| * 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; | |
| return 0; | |
| } | |
| /** | |
| * 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) | |
| { | |
| unsigned long fp = state->fp; | |
| int err; | |
| if (fp & 0x7) | |
| return -EINVAL; | |
| err = unwind_consume_stack(state, fp, 16); | |
| if (err) | |
| return err; | |
| /* | |
| * Record this frame record's values. | |
| */ | |
| state->fp = READ_ONCE(*(unsigned long *)(fp)); | |
| state->pc = READ_ONCE(*(unsigned long *)(fp + 8)); | |
| return 0; | |
| } | |
| #endif /* __ASM_STACKTRACE_COMMON_H */ |