Commit dc74a9e8 authored by Jinyang He's avatar Jinyang He Committed by Huacai Chen
Browse files

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: default avatarQing Zhang <zhangqing@loongson.cn>
Signed-off-by: default avatarJinyang He <hejinyang@loongson.cn>
Signed-off-by: default avatarHuacai Chen <chenhuacai@loongson.cn>
parent c5ac25e0
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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;
};
+3 −0
Original line number Diff line number Diff line
@@ -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
+88 −13
Original line number Diff line number Diff line
@@ -2,21 +2,101 @@
/*
 * 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)
{
#ifdef CONFIG_DYNAMIC_FTRACE
	static unsigned long ftrace = (unsigned long)ftrace_call + 4;
	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;
	}
}

	if (state->pc == ftrace)
		state->is_ftrace = true;
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
	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;
}

/*
@@ -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 @@ static bool unwind_by_prologue(struct unwind_state *state)

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)
+1 −1
Original line number Diff line number Diff line
@@ -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)];