LoongArch: Add generic ex-handler unwind in prologue unwinder
When exception is triggered, code flow go handle_\exception in some cases. One of stackframe in this case as follows, high -> +-------+ | REGS | <- a pt_regs | | | | <- ex trigger | REGS | <- ex pt_regs <-+ | | | | | | low -> +-------+ ->unwind-+ When unwinder unwinds to handler_\exception it cannot go on prologue analysis. Because it is an asynchronous code flow, we should get the next frame PC from regs->csr_era rather than regs->regs[1]. At init time we copy the handlers to eentry and also copy them to NUMA-affine memory named pcpu_handlers if NUMA is enabled. Thus, unwinder cannot unwind normally. To solve this, we try to give some hints in handler_\exception and fixup unwinders in unwind_next_frame(). Reported-by: Qing Zhang <zhangqing@loongson.cn> Signed-off-by: Jinyang He <hejinyang@loongson.cn> Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
This commit is contained in:
Родитель
c5ac25e0d7
Коммит
dc74a9e8a8
|
@ -22,7 +22,7 @@ struct unwind_state {
|
|||
char type; /* UNWINDER_XXX */
|
||||
struct stack_info stack_info;
|
||||
struct task_struct *task;
|
||||
bool first, error, is_ftrace;
|
||||
bool first, error, reset;
|
||||
int graph_idx;
|
||||
unsigned long sp, pc, ra;
|
||||
};
|
||||
|
|
|
@ -67,14 +67,17 @@ SYM_FUNC_END(except_vec_cex)
|
|||
.macro BUILD_HANDLER exception handler prep
|
||||
.align 5
|
||||
SYM_FUNC_START(handle_\exception)
|
||||
666:
|
||||
BACKUP_T0T1
|
||||
SAVE_ALL
|
||||
build_prep_\prep
|
||||
move a0, sp
|
||||
la.abs t0, do_\handler
|
||||
jirl ra, t0, 0
|
||||
668:
|
||||
RESTORE_ALL_AND_RET
|
||||
SYM_FUNC_END(handle_\exception)
|
||||
SYM_DATA(unwind_hint_\exception, .word 668b - 666b)
|
||||
.endm
|
||||
|
||||
BUILD_HANDLER ade ade badv
|
||||
|
|
|
@ -2,23 +2,103 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Loongson Technology Corporation Limited
|
||||
*/
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/ftrace.h>
|
||||
#include <linux/kallsyms.h>
|
||||
|
||||
#include <asm/inst.h>
|
||||
#include <asm/loongson.h>
|
||||
#include <asm/ptrace.h>
|
||||
#include <asm/setup.h>
|
||||
#include <asm/unwind.h>
|
||||
|
||||
static inline void unwind_state_fixup(struct unwind_state *state)
|
||||
extern const int unwind_hint_ade;
|
||||
extern const int unwind_hint_ale;
|
||||
extern const int unwind_hint_bp;
|
||||
extern const int unwind_hint_fpe;
|
||||
extern const int unwind_hint_fpu;
|
||||
extern const int unwind_hint_lsx;
|
||||
extern const int unwind_hint_lasx;
|
||||
extern const int unwind_hint_lbt;
|
||||
extern const int unwind_hint_ri;
|
||||
extern const int unwind_hint_watch;
|
||||
extern unsigned long eentry;
|
||||
#ifdef CONFIG_NUMA
|
||||
extern unsigned long pcpu_handlers[NR_CPUS];
|
||||
#endif
|
||||
|
||||
static inline bool scan_handlers(unsigned long entry_offset)
|
||||
{
|
||||
int idx, offset;
|
||||
|
||||
if (entry_offset >= EXCCODE_INT_START * VECSIZE)
|
||||
return false;
|
||||
|
||||
idx = entry_offset / VECSIZE;
|
||||
offset = entry_offset % VECSIZE;
|
||||
switch (idx) {
|
||||
case EXCCODE_ADE:
|
||||
return offset == unwind_hint_ade;
|
||||
case EXCCODE_ALE:
|
||||
return offset == unwind_hint_ale;
|
||||
case EXCCODE_BP:
|
||||
return offset == unwind_hint_bp;
|
||||
case EXCCODE_FPE:
|
||||
return offset == unwind_hint_fpe;
|
||||
case EXCCODE_FPDIS:
|
||||
return offset == unwind_hint_fpu;
|
||||
case EXCCODE_LSXDIS:
|
||||
return offset == unwind_hint_lsx;
|
||||
case EXCCODE_LASXDIS:
|
||||
return offset == unwind_hint_lasx;
|
||||
case EXCCODE_BTDIS:
|
||||
return offset == unwind_hint_lbt;
|
||||
case EXCCODE_INE:
|
||||
return offset == unwind_hint_ri;
|
||||
case EXCCODE_WATCH:
|
||||
return offset == unwind_hint_watch;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool fix_exception(unsigned long pc)
|
||||
{
|
||||
#ifdef CONFIG_NUMA
|
||||
int cpu;
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
if (!pcpu_handlers[cpu])
|
||||
continue;
|
||||
if (scan_handlers(pc - pcpu_handlers[cpu]))
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return scan_handlers(pc - eentry);
|
||||
}
|
||||
|
||||
/*
|
||||
* As we meet ftrace_regs_entry, reset first flag like first doing
|
||||
* tracing. Prologue analysis will stop soon because PC is at entry.
|
||||
*/
|
||||
static inline bool fix_ftrace(unsigned long pc)
|
||||
{
|
||||
#ifdef CONFIG_DYNAMIC_FTRACE
|
||||
static unsigned long ftrace = (unsigned long)ftrace_call + 4;
|
||||
|
||||
if (state->pc == ftrace)
|
||||
state->is_ftrace = true;
|
||||
return pc == (unsigned long)ftrace_call + LOONGARCH_INSN_SIZE;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool unwind_state_fixup(struct unwind_state *state)
|
||||
{
|
||||
if (!fix_exception(state->pc) && !fix_ftrace(state->pc))
|
||||
return false;
|
||||
|
||||
state->reset = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* LoongArch function prologue is like follows,
|
||||
* [instructions not use stack var]
|
||||
|
@ -39,14 +119,10 @@ static bool unwind_by_prologue(struct unwind_state *state)
|
|||
if (state->sp >= info->end || state->sp < info->begin)
|
||||
return false;
|
||||
|
||||
if (state->is_ftrace) {
|
||||
/*
|
||||
* As we meet ftrace_regs_entry, reset first flag like first doing
|
||||
* tracing. Prologue analysis will stop soon because PC is at entry.
|
||||
*/
|
||||
if (state->reset) {
|
||||
regs = (struct pt_regs *)state->sp;
|
||||
state->first = true;
|
||||
state->is_ftrace = false;
|
||||
state->reset = false;
|
||||
state->pc = regs->csr_era;
|
||||
state->ra = regs->regs[1];
|
||||
state->sp = regs->regs[3];
|
||||
|
@ -112,8 +188,7 @@ first:
|
|||
|
||||
out:
|
||||
state->first = false;
|
||||
unwind_state_fixup(state);
|
||||
return !!__kernel_text_address(state->pc);
|
||||
return unwind_state_fixup(state) || __kernel_text_address(state->pc);
|
||||
}
|
||||
|
||||
static bool next_frame(struct unwind_state *state)
|
||||
|
|
|
@ -251,7 +251,7 @@ static void output_pgtable_bits_defines(void)
|
|||
}
|
||||
|
||||
#ifdef CONFIG_NUMA
|
||||
static unsigned long pcpu_handlers[NR_CPUS];
|
||||
unsigned long pcpu_handlers[NR_CPUS];
|
||||
#endif
|
||||
extern long exception_handlers[VECSIZE * 128 / sizeof(long)];
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче