mirror of
https://github.com/raspberrypi/linux.git
synced 2025-12-27 12:32:50 +00:00
On arm64 we share some unwinding code between the regular kernel unwinder and the KVM hyp unwinder. Some of this common code only matters to the regular unwinder, e.g. the `kr_cur` and `task` fields in the common struct unwind_state. We're likely to add more state which only matters for regular kernel unwinding (or only for hyp unwinding). In preparation for such changes, this patch factors out the kernel-specific state into a new struct kunwind_state, and updates the kernel unwind code accordingly. There should be no functional change as a result of this patch. Signed-off-by: Mark Rutland <mark.rutland@arm.com> Cc: Catalin Marinas <catalin.marinas@arm.com> Cc: Kalesh Singh <kaleshsingh@google.com> Cc: Madhavan T. Venkataraman <madvenka@linux.microsoft.com> Cc: Mark Brown <broonie@kernel.org> Cc: Puranjay Mohan <puranjay12@gmail.com> Cc: Will Deacon <will@kernel.org> Reviewed-by: Puranjay Mohan <puranjay12@gmail.com> Reviewed-by: Kalesh Singh <kaleshsingh@google.com> Reviewed-by: Madhavan T. Venkataraman <madvenka@linux.microsoft.com> Reviewed-by: Mark Brown <broonie@kernel.org> Link: https://lore.kernel.org/r/20231124110511.2795958-2-mark.rutland@arm.com Signed-off-by: Will Deacon <will@kernel.org>
160 lines
3.6 KiB
C
160 lines
3.6 KiB
C
/* 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 */
|