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:
Jinyang He 2023-01-17 11:42:16 +08:00 коммит произвёл Huacai Chen
Родитель c5ac25e0d7
Коммит dc74a9e8a8
4 изменённых файлов: 93 добавлений и 15 удалений

Просмотреть файл

@ -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)];