| // 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); |