-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
LoongArch: Add prologue unwinder support
It unwind the stack frame based on prologue code analyze. CONFIG_KALLSYMS is needed, at least the address and length of each function. Three stages when we do unwind, 1) unwind_start(), the prapare of unwinding, fill unwind_state. 2) unwind_done(), judge whether the unwind process is finished or not. 3) unwind_next_frame(), unwind the next frame. Dividing unwinder helps to add new unwinders in the future, e.g.: unwinder_frame, unwinder_orc, .etc. Signed-off-by: Qing Zhang <zhangqing@loongson.cn> Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
- Loading branch information
Qing Zhang
authored and
Huacai Chen
committed
Aug 12, 2022
1 parent
4923277
commit 49aef11
Showing
6 changed files
with
259 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
/* | ||
* Copyright (C) 2022 Loongson Technology Corporation Limited | ||
*/ | ||
#include <linux/kallsyms.h> | ||
|
||
#include <asm/inst.h> | ||
#include <asm/ptrace.h> | ||
#include <asm/unwind.h> | ||
|
||
unsigned long unwind_get_return_address(struct unwind_state *state) | ||
{ | ||
|
||
if (unwind_done(state)) | ||
return 0; | ||
else if (state->type) | ||
return state->pc; | ||
else if (state->first) | ||
return state->pc; | ||
|
||
return *(unsigned long *)(state->sp); | ||
|
||
} | ||
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); | ||
if (__kernel_text_address(addr)) | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
static bool unwind_by_prologue(struct unwind_state *state) | ||
{ | ||
struct stack_info *info = &state->stack_info; | ||
union loongarch_instruction *ip, *ip_end; | ||
unsigned long frame_size = 0, frame_ra = -1; | ||
unsigned long size, offset, pc = state->pc; | ||
|
||
if (state->sp >= info->end || state->sp < info->begin) | ||
return false; | ||
|
||
if (!kallsyms_lookup_size_offset(pc, &size, &offset)) | ||
return false; | ||
|
||
ip = (union loongarch_instruction *)(pc - offset); | ||
ip_end = (union loongarch_instruction *)pc; | ||
|
||
while (ip < ip_end) { | ||
if (is_stack_alloc_ins(ip)) { | ||
frame_size = (1 << 12) - ip->reg2i12_format.immediate; | ||
ip++; | ||
break; | ||
} | ||
ip++; | ||
} | ||
|
||
if (!frame_size) { | ||
if (state->first) | ||
goto first; | ||
|
||
return false; | ||
} | ||
|
||
while (ip < ip_end) { | ||
if (is_ra_save_ins(ip)) { | ||
frame_ra = ip->reg2i12_format.immediate; | ||
break; | ||
} | ||
if (is_branch_ins(ip)) | ||
break; | ||
ip++; | ||
} | ||
|
||
if (frame_ra < 0) { | ||
if (state->first) { | ||
state->sp = state->sp + frame_size; | ||
goto first; | ||
} | ||
return false; | ||
} | ||
|
||
if (state->first) | ||
state->first = false; | ||
|
||
state->pc = *(unsigned long *)(state->sp + frame_ra); | ||
state->sp = state->sp + frame_size; | ||
return !!__kernel_text_address(state->pc); | ||
|
||
first: | ||
state->first = false; | ||
if (state->pc == state->ra) | ||
return false; | ||
|
||
state->pc = state->ra; | ||
|
||
return !!__kernel_text_address(state->ra); | ||
} | ||
|
||
void unwind_start(struct unwind_state *state, struct task_struct *task, | ||
struct pt_regs *regs) | ||
{ | ||
memset(state, 0, sizeof(*state)); | ||
|
||
if (regs && __kernel_text_address(regs->csr_era)) { | ||
state->pc = regs->csr_era; | ||
state->sp = regs->regs[3]; | ||
state->ra = regs->regs[1]; | ||
state->type = UNWINDER_PROLOGUE; | ||
} | ||
|
||
state->task = task; | ||
state->first = true; | ||
|
||
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; | ||
|
||
if (unwind_done(state)) | ||
return false; | ||
|
||
do { | ||
switch (state->type) { | ||
case UNWINDER_GUESS: | ||
state->first = false; | ||
if (unwind_by_guess(state)) | ||
return true; | ||
break; | ||
|
||
case UNWINDER_PROLOGUE: | ||
if (unwind_by_prologue(state)) | ||
return true; | ||
|
||
if (info->type == STACK_TYPE_IRQ && | ||
info->end == state->sp) { | ||
regs = (struct pt_regs *)info->next_sp; | ||
pc = regs->csr_era; | ||
|
||
if (user_mode(regs) || !__kernel_text_address(pc)) | ||
return false; | ||
|
||
state->pc = pc; | ||
state->sp = regs->regs[3]; | ||
state->ra = regs->regs[1]; | ||
state->first = true; | ||
get_stack_info(state->sp, state->task, info); | ||
|
||
return true; | ||
} | ||
} | ||
|
||
state->sp = info->next_sp; | ||
|
||
} while (!get_stack_info(state->sp, state->task, info)); | ||
|
||
return false; | ||
} | ||
EXPORT_SYMBOL_GPL(unwind_next_frame); |