| /* 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_task_pt_regs(struct unwind_state *state, |
| struct pt_regs *regs) |
| { |
| return task_pt_regs(state->task) == regs; |
| } |
| |
| 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_task_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); |