mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
4.7 KiB
182 lines
4.7 KiB
/* SPDX-License-Identifier: GPL-2.0 */ |
|
#include <linux/sched.h> |
|
#include <linux/sched/task.h> |
|
#include <linux/sched/task_stack.h> |
|
#include <linux/interrupt.h> |
|
#include <asm/sections.h> |
|
#include <asm/ptrace.h> |
|
#include <asm/bitops.h> |
|
#include <asm/stacktrace.h> |
|
#include <asm/unwind.h> |
|
|
|
unsigned long unwind_get_return_address(struct unwind_state *state) |
|
{ |
|
if (unwind_done(state)) |
|
return 0; |
|
return __kernel_text_address(state->ip) ? state->ip : 0; |
|
} |
|
EXPORT_SYMBOL_GPL(unwind_get_return_address); |
|
|
|
static bool outside_of_stack(struct unwind_state *state, unsigned long sp) |
|
{ |
|
return (sp <= state->sp) || |
|
(sp > state->stack_info.end - sizeof(struct stack_frame)); |
|
} |
|
|
|
static bool update_stack_info(struct unwind_state *state, unsigned long sp) |
|
{ |
|
struct stack_info *info = &state->stack_info; |
|
unsigned long *mask = &state->stack_mask; |
|
|
|
/* New stack pointer leaves the current stack */ |
|
if (get_stack_info(sp, state->task, info, mask) != 0 || |
|
!on_stack(info, sp, sizeof(struct stack_frame))) |
|
/* 'sp' does not point to a valid stack */ |
|
return false; |
|
return true; |
|
} |
|
|
|
static inline bool is_final_pt_regs(struct unwind_state *state, |
|
struct pt_regs *regs) |
|
{ |
|
/* user mode or kernel thread pt_regs at the bottom of task stack */ |
|
if (task_pt_regs(state->task) == regs) |
|
return true; |
|
|
|
/* user mode pt_regs at the bottom of irq stack */ |
|
return state->stack_info.type == STACK_TYPE_IRQ && |
|
state->stack_info.end - sizeof(struct pt_regs) == (unsigned long)regs && |
|
READ_ONCE_NOCHECK(regs->psw.mask) & PSW_MASK_PSTATE; |
|
} |
|
|
|
bool unwind_next_frame(struct unwind_state *state) |
|
{ |
|
struct stack_info *info = &state->stack_info; |
|
struct stack_frame *sf; |
|
struct pt_regs *regs; |
|
unsigned long sp, ip; |
|
bool reliable; |
|
|
|
regs = state->regs; |
|
if (unlikely(regs)) { |
|
sp = state->sp; |
|
sf = (struct stack_frame *) sp; |
|
ip = READ_ONCE_NOCHECK(sf->gprs[8]); |
|
reliable = false; |
|
regs = NULL; |
|
if (!__kernel_text_address(ip)) { |
|
/* skip bogus %r14 */ |
|
state->regs = NULL; |
|
return unwind_next_frame(state); |
|
} |
|
} else { |
|
sf = (struct stack_frame *) state->sp; |
|
sp = READ_ONCE_NOCHECK(sf->back_chain); |
|
if (likely(sp)) { |
|
/* Non-zero back-chain points to the previous frame */ |
|
if (unlikely(outside_of_stack(state, sp))) { |
|
if (!update_stack_info(state, sp)) |
|
goto out_err; |
|
} |
|
sf = (struct stack_frame *) sp; |
|
ip = READ_ONCE_NOCHECK(sf->gprs[8]); |
|
reliable = true; |
|
} else { |
|
/* No back-chain, look for a pt_regs structure */ |
|
sp = state->sp + STACK_FRAME_OVERHEAD; |
|
if (!on_stack(info, sp, sizeof(struct pt_regs))) |
|
goto out_err; |
|
regs = (struct pt_regs *) sp; |
|
if (is_final_pt_regs(state, regs)) |
|
goto out_stop; |
|
ip = READ_ONCE_NOCHECK(regs->psw.addr); |
|
sp = READ_ONCE_NOCHECK(regs->gprs[15]); |
|
if (unlikely(outside_of_stack(state, sp))) { |
|
if (!update_stack_info(state, sp)) |
|
goto out_err; |
|
} |
|
reliable = true; |
|
} |
|
} |
|
|
|
/* Sanity check: ABI requires SP to be aligned 8 bytes. */ |
|
if (sp & 0x7) |
|
goto out_err; |
|
|
|
ip = ftrace_graph_ret_addr(state->task, &state->graph_idx, ip, (void *) sp); |
|
|
|
/* Update unwind state */ |
|
state->sp = sp; |
|
state->ip = ip; |
|
state->regs = regs; |
|
state->reliable = reliable; |
|
return true; |
|
|
|
out_err: |
|
state->error = true; |
|
out_stop: |
|
state->stack_info.type = STACK_TYPE_UNKNOWN; |
|
return false; |
|
} |
|
EXPORT_SYMBOL_GPL(unwind_next_frame); |
|
|
|
void __unwind_start(struct unwind_state *state, struct task_struct *task, |
|
struct pt_regs *regs, unsigned long first_frame) |
|
{ |
|
struct stack_info *info = &state->stack_info; |
|
struct stack_frame *sf; |
|
unsigned long ip, sp; |
|
|
|
memset(state, 0, sizeof(*state)); |
|
state->task = task; |
|
state->regs = regs; |
|
|
|
/* Don't even attempt to start from user mode regs: */ |
|
if (regs && user_mode(regs)) { |
|
info->type = STACK_TYPE_UNKNOWN; |
|
return; |
|
} |
|
|
|
/* Get the instruction pointer from pt_regs or the stack frame */ |
|
if (regs) { |
|
ip = regs->psw.addr; |
|
sp = regs->gprs[15]; |
|
} else if (task == current) { |
|
sp = current_frame_address(); |
|
} else { |
|
sp = task->thread.ksp; |
|
} |
|
|
|
/* Get current stack pointer and initialize stack info */ |
|
if (!update_stack_info(state, sp)) { |
|
/* Something is wrong with the stack pointer */ |
|
info->type = STACK_TYPE_UNKNOWN; |
|
state->error = true; |
|
return; |
|
} |
|
|
|
if (!regs) { |
|
/* Stack frame is within valid stack */ |
|
sf = (struct stack_frame *)sp; |
|
ip = READ_ONCE_NOCHECK(sf->gprs[8]); |
|
} |
|
|
|
ip = ftrace_graph_ret_addr(state->task, &state->graph_idx, ip, NULL); |
|
|
|
/* Update unwind state */ |
|
state->sp = sp; |
|
state->ip = ip; |
|
state->reliable = true; |
|
|
|
if (!first_frame) |
|
return; |
|
/* Skip through the call chain to the specified starting frame */ |
|
while (!unwind_done(state)) { |
|
if (on_stack(&state->stack_info, first_frame, sizeof(struct stack_frame))) { |
|
if (state->sp >= first_frame) |
|
break; |
|
} |
|
unwind_next_frame(state); |
|
} |
|
} |
|
EXPORT_SYMBOL_GPL(__unwind_start);
|
|
|