Commit c5ac25e0 authored by Jinyang He's avatar Jinyang He Committed by Huacai Chen

LoongArch: Strip guess unwinder out from prologue unwinder

The prolugue unwinder rely on symbol info. When PC is not in kernel text
address, it cannot find relative symbol info and it will be broken. The
guess unwinder will be used in this case. And the guess unwinder code in
prolugue unwinder is redundant. Strip it out and set the unwinder type
in unwind_state. Make guess_unwinder::unwind_next_frame() as default way
when other unwinders cannot unwind in some extreme case.
Signed-off-by: default avatarJinyang He <hejinyang@loongson.cn>
Signed-off-by: default avatarHuacai Chen <chenhuacai@loongson.cn>
parent 5bb8d344
...@@ -27,6 +27,8 @@ struct unwind_state { ...@@ -27,6 +27,8 @@ struct unwind_state {
unsigned long sp, pc, ra; unsigned long sp, pc, ra;
}; };
bool default_next_frame(struct unwind_state *state);
void unwind_start(struct unwind_state *state, void unwind_start(struct unwind_state *state,
struct task_struct *task, struct pt_regs *regs); struct task_struct *task, struct pt_regs *regs);
bool unwind_next_frame(struct unwind_state *state); bool unwind_next_frame(struct unwind_state *state);
...@@ -50,4 +52,31 @@ static inline unsigned long unwind_graph_addr(struct unwind_state *state, ...@@ -50,4 +52,31 @@ static inline unsigned long unwind_graph_addr(struct unwind_state *state,
return ftrace_graph_ret_addr(state->task, &state->graph_idx, return ftrace_graph_ret_addr(state->task, &state->graph_idx,
pc, (unsigned long *)(cfa - GRAPH_FAKE_OFFSET)); pc, (unsigned long *)(cfa - GRAPH_FAKE_OFFSET));
} }
static __always_inline void __unwind_start(struct unwind_state *state,
struct task_struct *task, struct pt_regs *regs)
{
memset(state, 0, sizeof(*state));
if (regs) {
state->sp = regs->regs[3];
state->pc = regs->csr_era;
state->ra = regs->regs[1];
} else if (task && task != current) {
state->sp = thread_saved_fp(task);
state->pc = thread_saved_ra(task);
state->ra = 0;
} else {
state->sp = (unsigned long)__builtin_frame_address(0);
state->pc = (unsigned long)__builtin_return_address(0);
state->ra = 0;
}
state->task = task;
get_stack_info(state->sp, state->task, &state->stack_info);
state->pc = unwind_graph_addr(state, state->pc, state->sp);
}
static __always_inline unsigned long __unwind_get_return_address(struct unwind_state *state)
{
return unwind_done(state) ? 0 : state->pc;
}
#endif /* _ASM_UNWIND_H */ #endif /* _ASM_UNWIND_H */
...@@ -8,7 +8,7 @@ extra-y := vmlinux.lds ...@@ -8,7 +8,7 @@ extra-y := vmlinux.lds
obj-y += head.o cpu-probe.o cacheinfo.o env.o setup.o entry.o genex.o \ obj-y += head.o cpu-probe.o cacheinfo.o env.o setup.o entry.o genex.o \
traps.o irq.o idle.o process.o dma.o mem.o io.o reset.o switch.o \ traps.o irq.o idle.o process.o dma.o mem.o io.o reset.o switch.o \
elf.o syscall.o signal.o time.o topology.o inst.o ptrace.o vdso.o \ elf.o syscall.o signal.o time.o topology.o inst.o ptrace.o vdso.o \
alternative.o unaligned.o alternative.o unaligned.o unwind.o
obj-$(CONFIG_ACPI) += acpi.o obj-$(CONFIG_ACPI) += acpi.o
obj-$(CONFIG_EFI) += efi.o obj-$(CONFIG_EFI) += efi.o
......
...@@ -72,9 +72,6 @@ static void show_backtrace(struct task_struct *task, const struct pt_regs *regs, ...@@ -72,9 +72,6 @@ static void show_backtrace(struct task_struct *task, const struct pt_regs *regs,
if (!task) if (!task)
task = current; task = current;
if (user_mode(regs))
state.type = UNWINDER_GUESS;
printk("%sCall Trace:", loglvl); printk("%sCall Trace:", loglvl);
for (unwind_start(&state, task, pregs); for (unwind_start(&state, task, pregs);
!unwind_done(&state); unwind_next_frame(&state)) { !unwind_done(&state); unwind_next_frame(&state)) {
......
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022-2023 Loongson Technology Corporation Limited
*/
#include <linux/kernel.h>
#include <linux/ftrace.h>
#include <asm/unwind.h>
bool default_next_frame(struct unwind_state *state)
{
struct stack_info *info = &state->stack_info;
unsigned long addr;
if (unwind_done(state))
return false;
do {
for (state->sp += sizeof(unsigned long);
state->sp < info->end; state->sp += sizeof(unsigned long)) {
addr = *(unsigned long *)(state->sp);
state->pc = unwind_graph_addr(state, addr, state->sp + 8);
if (__kernel_text_address(state->pc))
return true;
}
state->sp = info->next_sp;
} while (!get_stack_info(state->sp, state->task, info));
return false;
}
...@@ -2,41 +2,18 @@ ...@@ -2,41 +2,18 @@
/* /*
* Copyright (C) 2022 Loongson Technology Corporation Limited * Copyright (C) 2022 Loongson Technology Corporation Limited
*/ */
#include <linux/kernel.h>
#include <linux/ftrace.h>
#include <asm/unwind.h> #include <asm/unwind.h>
unsigned long unwind_get_return_address(struct unwind_state *state) unsigned long unwind_get_return_address(struct unwind_state *state)
{ {
if (unwind_done(state)) return __unwind_get_return_address(state);
return 0;
return state->pc;
} }
EXPORT_SYMBOL_GPL(unwind_get_return_address); EXPORT_SYMBOL_GPL(unwind_get_return_address);
void unwind_start(struct unwind_state *state, struct task_struct *task, void unwind_start(struct unwind_state *state, struct task_struct *task,
struct pt_regs *regs) struct pt_regs *regs)
{ {
memset(state, 0, sizeof(*state)); __unwind_start(state, task, regs);
if (regs) {
state->sp = regs->regs[3];
state->pc = regs->csr_era;
} else if (task && task != current) {
state->sp = thread_saved_fp(task);
state->pc = thread_saved_ra(task);
} else {
state->sp = (unsigned long)__builtin_frame_address(0);
state->pc = (unsigned long)__builtin_return_address(0);
}
state->task = task;
state->first = true;
state->pc = unwind_graph_addr(state, state->pc, state->sp);
get_stack_info(state->sp, state->task, &state->stack_info);
if (!unwind_done(state) && !__kernel_text_address(state->pc)) if (!unwind_done(state) && !__kernel_text_address(state->pc))
unwind_next_frame(state); unwind_next_frame(state);
} }
...@@ -44,29 +21,6 @@ EXPORT_SYMBOL_GPL(unwind_start); ...@@ -44,29 +21,6 @@ EXPORT_SYMBOL_GPL(unwind_start);
bool unwind_next_frame(struct unwind_state *state) bool unwind_next_frame(struct unwind_state *state)
{ {
struct stack_info *info = &state->stack_info; return default_next_frame(state);
unsigned long addr;
if (unwind_done(state))
return false;
if (state->first)
state->first = false;
do {
for (state->sp += sizeof(unsigned long);
state->sp < info->end;
state->sp += sizeof(unsigned long)) {
addr = *(unsigned long *)(state->sp);
state->pc = unwind_graph_addr(state, addr, state->sp + 8);
if (__kernel_text_address(state->pc))
return true;
}
state->sp = info->next_sp;
} while (!get_stack_info(state->sp, state->task, info));
return false;
} }
EXPORT_SYMBOL_GPL(unwind_next_frame); EXPORT_SYMBOL_GPL(unwind_next_frame);
...@@ -19,32 +19,14 @@ static inline void unwind_state_fixup(struct unwind_state *state) ...@@ -19,32 +19,14 @@ static inline void unwind_state_fixup(struct unwind_state *state)
#endif #endif
} }
unsigned long unwind_get_return_address(struct unwind_state *state) /*
{ * LoongArch function prologue is like follows,
if (unwind_done(state)) * [instructions not use stack var]
return 0; * addi.d sp, sp, -imm
* st.d xx, sp, offset <- save callee saved regs and
return state->pc; * st.d yy, sp, offset save ra if function is nest.
} * [others instructions]
EXPORT_SYMBOL_GPL(unwind_get_return_address); */
static bool unwind_by_guess(struct unwind_state *state)
{
struct stack_info *info = &state->stack_info;
unsigned long addr;
for (state->sp += sizeof(unsigned long);
state->sp < info->end;
state->sp += sizeof(unsigned long)) {
addr = *(unsigned long *)(state->sp);
state->pc = unwind_graph_addr(state, addr, state->sp + 8);
if (__kernel_text_address(state->pc))
return true;
}
return false;
}
static bool unwind_by_prologue(struct unwind_state *state) static bool unwind_by_prologue(struct unwind_state *state)
{ {
long frame_ra = -1; long frame_ra = -1;
...@@ -91,6 +73,10 @@ static bool unwind_by_prologue(struct unwind_state *state) ...@@ -91,6 +73,10 @@ static bool unwind_by_prologue(struct unwind_state *state)
ip++; ip++;
} }
/*
* Can't find stack alloc action, PC may be in a leaf function. Only the
* first being true is reasonable, otherwise indicate analysis is broken.
*/
if (!frame_size) { if (!frame_size) {
if (state->first) if (state->first)
goto first; goto first;
...@@ -108,6 +94,7 @@ static bool unwind_by_prologue(struct unwind_state *state) ...@@ -108,6 +94,7 @@ static bool unwind_by_prologue(struct unwind_state *state)
ip++; ip++;
} }
/* Can't find save $ra action, PC may be in a leaf function, too. */
if (frame_ra < 0) { if (frame_ra < 0) {
if (state->first) { if (state->first) {
state->sp = state->sp + frame_size; state->sp = state->sp + frame_size;
...@@ -116,96 +103,48 @@ static bool unwind_by_prologue(struct unwind_state *state) ...@@ -116,96 +103,48 @@ static bool unwind_by_prologue(struct unwind_state *state)
return false; return false;
} }
if (state->first)
state->first = false;
state->pc = *(unsigned long *)(state->sp + frame_ra); state->pc = *(unsigned long *)(state->sp + frame_ra);
state->sp = state->sp + frame_size; state->sp = state->sp + frame_size;
goto out; goto out;
first: first:
state->first = false;
if (state->pc == state->ra)
return false;
state->pc = state->ra; state->pc = state->ra;
out: out:
state->first = false;
unwind_state_fixup(state); unwind_state_fixup(state);
return !!__kernel_text_address(state->pc); return !!__kernel_text_address(state->pc);
} }
void unwind_start(struct unwind_state *state, struct task_struct *task, static bool next_frame(struct unwind_state *state)
struct pt_regs *regs)
{
memset(state, 0, sizeof(*state));
state->type = UNWINDER_PROLOGUE;
if (regs) {
state->sp = regs->regs[3];
state->pc = regs->csr_era;
state->ra = regs->regs[1];
if (!__kernel_text_address(state->pc))
state->type = UNWINDER_GUESS;
} else if (task && task != current) {
state->sp = thread_saved_fp(task);
state->pc = thread_saved_ra(task);
state->ra = 0;
} else {
state->sp = (unsigned long)__builtin_frame_address(0);
state->pc = (unsigned long)__builtin_return_address(0);
state->ra = 0;
}
state->task = task;
state->first = true;
state->pc = unwind_graph_addr(state, state->pc, state->sp);
get_stack_info(state->sp, state->task, &state->stack_info);
if (!unwind_done(state) && !__kernel_text_address(state->pc))
unwind_next_frame(state);
}
EXPORT_SYMBOL_GPL(unwind_start);
bool unwind_next_frame(struct unwind_state *state)
{ {
struct stack_info *info = &state->stack_info;
struct pt_regs *regs;
unsigned long pc; unsigned long pc;
struct pt_regs *regs;
struct stack_info *info = &state->stack_info;
if (unwind_done(state)) if (unwind_done(state))
return false; return false;
do { do {
switch (state->type) { if (unwind_by_prologue(state)) {
case UNWINDER_GUESS: state->pc = unwind_graph_addr(state, state->pc, state->sp);
state->first = false; return true;
if (unwind_by_guess(state)) }
return true;
break;
case UNWINDER_PROLOGUE:
if (unwind_by_prologue(state)) {
state->pc = unwind_graph_addr(state, state->pc, state->sp);
return true;
}
if (info->type == STACK_TYPE_IRQ && if (info->type == STACK_TYPE_IRQ && info->end == state->sp) {
info->end == state->sp) { regs = (struct pt_regs *)info->next_sp;
regs = (struct pt_regs *)info->next_sp; pc = regs->csr_era;
pc = regs->csr_era;
if (user_mode(regs) || !__kernel_text_address(pc)) if (user_mode(regs) || !__kernel_text_address(pc))
return false; return false;
state->first = true; state->first = true;
state->ra = regs->regs[1]; state->pc = pc;
state->sp = regs->regs[3]; state->ra = regs->regs[1];
state->pc = pc; state->sp = regs->regs[3];
get_stack_info(state->sp, state->task, info); get_stack_info(state->sp, state->task, info);
return true; return true;
}
} }
state->sp = info->next_sp; state->sp = info->next_sp;
...@@ -214,4 +153,36 @@ bool unwind_next_frame(struct unwind_state *state) ...@@ -214,4 +153,36 @@ bool unwind_next_frame(struct unwind_state *state)
return false; return false;
} }
unsigned long unwind_get_return_address(struct unwind_state *state)
{
return __unwind_get_return_address(state);
}
EXPORT_SYMBOL_GPL(unwind_get_return_address);
void unwind_start(struct unwind_state *state, struct task_struct *task,
struct pt_regs *regs)
{
__unwind_start(state, task, regs);
state->type = UNWINDER_PROLOGUE;
state->first = true;
/*
* The current PC is not kernel text address, we cannot find its
* relative symbol. Thus, prologue analysis will be broken. Luckily,
* we can use the default_next_frame().
*/
if (!__kernel_text_address(state->pc)) {
state->type = UNWINDER_GUESS;
if (!unwind_done(state))
unwind_next_frame(state);
}
}
EXPORT_SYMBOL_GPL(unwind_start);
bool unwind_next_frame(struct unwind_state *state)
{
return state->type == UNWINDER_PROLOGUE ?
next_frame(state) : default_next_frame(state);
}
EXPORT_SYMBOL_GPL(unwind_next_frame); EXPORT_SYMBOL_GPL(unwind_next_frame);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment