Currently we call an on_accessible_stack() callback for each step of the unwinder, requiring redundant work to be performed in the core of the unwind loop (e.g. disabling preemption around accesses to per-cpu variables containing stack boundaries). To prevent unwind loops which go through a stack multiple times, we have to track the set of unwound stacks, requiring a stack_type enum which needs to cater for all the stacks of all possible callees. To prevent loops within a stack, we must track the prior FP values. This patch reworks the unwinder to minimize the work in the core of the unwinder, and to remove the need for the stack_type enum. The set of accessible stacks (and their boundaries) are determined at the start of the unwind, and the current stack is tracked during the unwind, with completed stacks removed from the set of accessible stacks. This makes the boundary checks more accurate (e.g. detecting overlapped frame records), and removes the need for separate tracking of the prior FP and visited stacks. Signed-off-by: Mark Rutland <mark.rutland@arm.com> Reviewed-by: Kalesh Singh <kaleshsingh@google.com> Reviewed-by: Madhavan T. Venkataraman <madvenka@linux.microsoft.com> Reviewed-by: Mark Brown <broonie@kernel.org> Cc: Fuad Tabba <tabba@google.com> Cc: Marc Zyngier <maz@kernel.org> Cc: Will Deacon <will@kernel.org> Link: https://lore.kernel.org/r/20220901130646.1316937-9-mark.rutland@arm.com Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
108 lines
2.6 KiB
C
108 lines
2.6 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
/*
|
|
* Copyright (C) 2012 ARM Ltd.
|
|
*/
|
|
#ifndef __ASM_STACKTRACE_H
|
|
#define __ASM_STACKTRACE_H
|
|
|
|
#include <linux/percpu.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/sched/task_stack.h>
|
|
#include <linux/llist.h>
|
|
|
|
#include <asm/memory.h>
|
|
#include <asm/pointer_auth.h>
|
|
#include <asm/ptrace.h>
|
|
#include <asm/sdei.h>
|
|
|
|
#include <asm/stacktrace/common.h>
|
|
|
|
extern void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk,
|
|
const char *loglvl);
|
|
|
|
DECLARE_PER_CPU(unsigned long *, irq_stack_ptr);
|
|
|
|
static inline struct stack_info stackinfo_get_irq(void)
|
|
{
|
|
unsigned long low = (unsigned long)raw_cpu_read(irq_stack_ptr);
|
|
unsigned long high = low + IRQ_STACK_SIZE;
|
|
|
|
return (struct stack_info) {
|
|
.low = low,
|
|
.high = high,
|
|
};
|
|
}
|
|
|
|
static inline bool on_irq_stack(unsigned long sp, unsigned long size)
|
|
{
|
|
struct stack_info info = stackinfo_get_irq();
|
|
return stackinfo_on_stack(&info, sp, size);
|
|
}
|
|
|
|
static inline struct stack_info stackinfo_get_task(const struct task_struct *tsk)
|
|
{
|
|
unsigned long low = (unsigned long)task_stack_page(tsk);
|
|
unsigned long high = low + THREAD_SIZE;
|
|
|
|
return (struct stack_info) {
|
|
.low = low,
|
|
.high = high,
|
|
};
|
|
}
|
|
|
|
static inline bool on_task_stack(const struct task_struct *tsk,
|
|
unsigned long sp, unsigned long size)
|
|
{
|
|
struct stack_info info = stackinfo_get_task(tsk);
|
|
return stackinfo_on_stack(&info, sp, size);
|
|
}
|
|
|
|
#ifdef CONFIG_VMAP_STACK
|
|
DECLARE_PER_CPU(unsigned long [OVERFLOW_STACK_SIZE/sizeof(long)], overflow_stack);
|
|
|
|
static inline struct stack_info stackinfo_get_overflow(void)
|
|
{
|
|
unsigned long low = (unsigned long)raw_cpu_ptr(overflow_stack);
|
|
unsigned long high = low + OVERFLOW_STACK_SIZE;
|
|
|
|
return (struct stack_info) {
|
|
.low = low,
|
|
.high = high,
|
|
};
|
|
}
|
|
#else
|
|
#define stackinfo_get_overflow() stackinfo_get_unknown()
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARM_SDE_INTERFACE) && defined(CONFIG_VMAP_STACK)
|
|
DECLARE_PER_CPU(unsigned long *, sdei_stack_normal_ptr);
|
|
DECLARE_PER_CPU(unsigned long *, sdei_stack_critical_ptr);
|
|
|
|
static inline struct stack_info stackinfo_get_sdei_normal(void)
|
|
{
|
|
unsigned long low = (unsigned long)raw_cpu_read(sdei_stack_normal_ptr);
|
|
unsigned long high = low + SDEI_STACK_SIZE;
|
|
|
|
return (struct stack_info) {
|
|
.low = low,
|
|
.high = high,
|
|
};
|
|
}
|
|
|
|
static inline struct stack_info stackinfo_get_sdei_critical(void)
|
|
{
|
|
unsigned long low = (unsigned long)raw_cpu_read(sdei_stack_critical_ptr);
|
|
unsigned long high = low + SDEI_STACK_SIZE;
|
|
|
|
return (struct stack_info) {
|
|
.low = low,
|
|
.high = high,
|
|
};
|
|
}
|
|
#else
|
|
#define stackinfo_get_sdei_normal() stackinfo_get_unknown()
|
|
#define stackinfo_get_sdei_critical() stackinfo_get_unknown()
|
|
#endif
|
|
|
|
#endif /* __ASM_STACKTRACE_H */
|