mirror of https://gitee.com/openkylin/linux.git
186 lines
4.6 KiB
C
186 lines
4.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/types.h>
|
|
|
|
#include <asm/memory.h>
|
|
#include <asm/ptrace.h>
|
|
#include <asm/sdei.h>
|
|
|
|
enum stack_type {
|
|
STACK_TYPE_UNKNOWN,
|
|
STACK_TYPE_TASK,
|
|
STACK_TYPE_IRQ,
|
|
STACK_TYPE_OVERFLOW,
|
|
STACK_TYPE_SDEI_NORMAL,
|
|
STACK_TYPE_SDEI_CRITICAL,
|
|
__NR_STACK_TYPES
|
|
};
|
|
|
|
struct stack_info {
|
|
unsigned long low;
|
|
unsigned long high;
|
|
enum stack_type type;
|
|
};
|
|
|
|
/*
|
|
* A snapshot of a frame record or fp/lr register values, along with some
|
|
* accounting information necessary for robust unwinding.
|
|
*
|
|
* @fp: The fp value in the frame record (or the real fp)
|
|
* @pc: The fp value in the frame record (or the real lr)
|
|
*
|
|
* @stacks_done: Stacks which have been entirely unwound, for which it is no
|
|
* longer valid to unwind to.
|
|
*
|
|
* @prev_fp: The fp that pointed to this frame record, or a synthetic value
|
|
* of 0. This is used to ensure that within a stack, each
|
|
* subsequent frame record is at an increasing address.
|
|
* @prev_type: The type of stack this frame record was on, or a synthetic
|
|
* value of STACK_TYPE_UNKNOWN. This is used to detect a
|
|
* transition from one stack to another.
|
|
*
|
|
* @graph: When FUNCTION_GRAPH_TRACER is selected, holds the index of a
|
|
* replacement lr value in the ftrace graph stack.
|
|
*/
|
|
struct stackframe {
|
|
unsigned long fp;
|
|
unsigned long pc;
|
|
DECLARE_BITMAP(stacks_done, __NR_STACK_TYPES);
|
|
unsigned long prev_fp;
|
|
enum stack_type prev_type;
|
|
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
|
int graph;
|
|
#endif
|
|
};
|
|
|
|
extern int unwind_frame(struct task_struct *tsk, struct stackframe *frame);
|
|
extern void walk_stackframe(struct task_struct *tsk, struct stackframe *frame,
|
|
int (*fn)(struct stackframe *, void *), void *data);
|
|
extern void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk);
|
|
|
|
DECLARE_PER_CPU(unsigned long *, irq_stack_ptr);
|
|
|
|
static inline bool on_irq_stack(unsigned long sp,
|
|
struct stack_info *info)
|
|
{
|
|
unsigned long low = (unsigned long)raw_cpu_read(irq_stack_ptr);
|
|
unsigned long high = low + IRQ_STACK_SIZE;
|
|
|
|
if (!low)
|
|
return false;
|
|
|
|
if (sp < low || sp >= high)
|
|
return false;
|
|
|
|
if (info) {
|
|
info->low = low;
|
|
info->high = high;
|
|
info->type = STACK_TYPE_IRQ;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline bool on_task_stack(const struct task_struct *tsk,
|
|
unsigned long sp,
|
|
struct stack_info *info)
|
|
{
|
|
unsigned long low = (unsigned long)task_stack_page(tsk);
|
|
unsigned long high = low + THREAD_SIZE;
|
|
|
|
if (sp < low || sp >= high)
|
|
return false;
|
|
|
|
if (info) {
|
|
info->low = low;
|
|
info->high = high;
|
|
info->type = STACK_TYPE_TASK;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef CONFIG_VMAP_STACK
|
|
DECLARE_PER_CPU(unsigned long [OVERFLOW_STACK_SIZE/sizeof(long)], overflow_stack);
|
|
|
|
static inline bool on_overflow_stack(unsigned long sp,
|
|
struct stack_info *info)
|
|
{
|
|
unsigned long low = (unsigned long)raw_cpu_ptr(overflow_stack);
|
|
unsigned long high = low + OVERFLOW_STACK_SIZE;
|
|
|
|
if (sp < low || sp >= high)
|
|
return false;
|
|
|
|
if (info) {
|
|
info->low = low;
|
|
info->high = high;
|
|
info->type = STACK_TYPE_OVERFLOW;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#else
|
|
static inline bool on_overflow_stack(unsigned long sp,
|
|
struct stack_info *info) { return false; }
|
|
#endif
|
|
|
|
|
|
/*
|
|
* We can only safely access per-cpu stacks from current in a non-preemptible
|
|
* context.
|
|
*/
|
|
static inline bool on_accessible_stack(const struct task_struct *tsk,
|
|
unsigned long sp,
|
|
struct stack_info *info)
|
|
{
|
|
if (info)
|
|
info->type = STACK_TYPE_UNKNOWN;
|
|
|
|
if (on_task_stack(tsk, sp, info))
|
|
return true;
|
|
if (tsk != current || preemptible())
|
|
return false;
|
|
if (on_irq_stack(sp, info))
|
|
return true;
|
|
if (on_overflow_stack(sp, info))
|
|
return true;
|
|
if (on_sdei_stack(sp, info))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static inline void start_backtrace(struct stackframe *frame,
|
|
unsigned long fp, unsigned long pc)
|
|
{
|
|
frame->fp = fp;
|
|
frame->pc = pc;
|
|
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
|
frame->graph = 0;
|
|
#endif
|
|
|
|
/*
|
|
* Prime the first unwind.
|
|
*
|
|
* In unwind_frame() we'll check that the FP points to a valid stack,
|
|
* which can't be STACK_TYPE_UNKNOWN, and the first unwind will be
|
|
* treated as a transition to whichever stack that happens to be. The
|
|
* prev_fp value won't be used, but we set it to 0 such that it is
|
|
* definitely not an accessible stack address.
|
|
*/
|
|
bitmap_zero(frame->stacks_done, __NR_STACK_TYPES);
|
|
frame->prev_fp = 0;
|
|
frame->prev_type = STACK_TYPE_UNKNOWN;
|
|
}
|
|
|
|
#endif /* __ASM_STACKTRACE_H */
|