-
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.
s390/unwind: introduce stack unwind API
Rework the dump_trace() stack unwinder interface to support different unwinding algorithms. The new interface looks like this: struct unwind_state state; unwind_for_each_frame(&state, task, regs, start_stack) do_something(state.sp, state.ip, state.reliable); The unwind_bc.c file contains the implementation for the classic back-chain unwinder. One positive side effect of the new code is it now handles ftraced functions gracefully. It prints the real name of the return function instead of 'return_to_handler'. Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
- Loading branch information
Martin Schwidefsky
committed
May 2, 2019
1 parent
1c705ad
commit 78c98f9
Showing
16 changed files
with
521 additions
and
205 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/* SPDX-License-Identifier: GPL-2.0 */ | ||
#ifndef _ASM_S390_STACKTRACE_H | ||
#define _ASM_S390_STACKTRACE_H | ||
|
||
#include <linux/uaccess.h> | ||
#include <linux/ptrace.h> | ||
#include <asm/switch_to.h> | ||
|
||
enum stack_type { | ||
STACK_TYPE_UNKNOWN, | ||
STACK_TYPE_TASK, | ||
STACK_TYPE_IRQ, | ||
STACK_TYPE_NODAT, | ||
STACK_TYPE_RESTART, | ||
}; | ||
|
||
struct stack_info { | ||
enum stack_type type; | ||
unsigned long begin, end; | ||
}; | ||
|
||
const char *stack_type_name(enum stack_type type); | ||
int get_stack_info(unsigned long sp, struct task_struct *task, | ||
struct stack_info *info, unsigned long *visit_mask); | ||
|
||
static inline bool on_stack(struct stack_info *info, | ||
unsigned long addr, size_t len) | ||
{ | ||
if (info->type == STACK_TYPE_UNKNOWN) | ||
return false; | ||
if (addr + len < addr) | ||
return false; | ||
return addr >= info->begin && addr + len < info->end; | ||
} | ||
|
||
static inline unsigned long get_stack_pointer(struct task_struct *task, | ||
struct pt_regs *regs) | ||
{ | ||
if (regs) | ||
return (unsigned long) kernel_stack_pointer(regs); | ||
if (task == current) | ||
return current_stack_pointer(); | ||
return (unsigned long) task->thread.ksp; | ||
} | ||
|
||
/* | ||
* Stack layout of a C stack frame. | ||
*/ | ||
#ifndef __PACK_STACK | ||
struct stack_frame { | ||
unsigned long back_chain; | ||
unsigned long empty1[5]; | ||
unsigned long gprs[10]; | ||
unsigned int empty2[8]; | ||
}; | ||
#else | ||
struct stack_frame { | ||
unsigned long empty1[5]; | ||
unsigned int empty2[8]; | ||
unsigned long gprs[10]; | ||
unsigned long back_chain; | ||
}; | ||
#endif | ||
|
||
#define CALL_ARGS_0() \ | ||
register unsigned long r2 asm("2") | ||
#define CALL_ARGS_1(arg1) \ | ||
register unsigned long r2 asm("2") = (unsigned long)(arg1) | ||
#define CALL_ARGS_2(arg1, arg2) \ | ||
CALL_ARGS_1(arg1); \ | ||
register unsigned long r3 asm("3") = (unsigned long)(arg2) | ||
#define CALL_ARGS_3(arg1, arg2, arg3) \ | ||
CALL_ARGS_2(arg1, arg2); \ | ||
register unsigned long r4 asm("4") = (unsigned long)(arg3) | ||
#define CALL_ARGS_4(arg1, arg2, arg3, arg4) \ | ||
CALL_ARGS_3(arg1, arg2, arg3); \ | ||
register unsigned long r4 asm("5") = (unsigned long)(arg4) | ||
#define CALL_ARGS_5(arg1, arg2, arg3, arg4, arg5) \ | ||
CALL_ARGS_4(arg1, arg2, arg3, arg4); \ | ||
register unsigned long r4 asm("6") = (unsigned long)(arg5) | ||
|
||
#define CALL_FMT_0 "=&d" (r2) : | ||
#define CALL_FMT_1 "+&d" (r2) : | ||
#define CALL_FMT_2 CALL_FMT_1 "d" (r3), | ||
#define CALL_FMT_3 CALL_FMT_2 "d" (r4), | ||
#define CALL_FMT_4 CALL_FMT_3 "d" (r5), | ||
#define CALL_FMT_5 CALL_FMT_4 "d" (r6), | ||
|
||
#define CALL_CLOBBER_5 "0", "1", "14", "cc", "memory" | ||
#define CALL_CLOBBER_4 CALL_CLOBBER_5 | ||
#define CALL_CLOBBER_3 CALL_CLOBBER_4, "5" | ||
#define CALL_CLOBBER_2 CALL_CLOBBER_3, "4" | ||
#define CALL_CLOBBER_1 CALL_CLOBBER_2, "3" | ||
#define CALL_CLOBBER_0 CALL_CLOBBER_1 | ||
|
||
#define CALL_ON_STACK(fn, stack, nr, args...) \ | ||
({ \ | ||
CALL_ARGS_##nr(args); \ | ||
unsigned long prev; \ | ||
\ | ||
asm volatile( \ | ||
" la %[_prev],0(15)\n" \ | ||
" la 15,0(%[_stack])\n" \ | ||
" stg %[_prev],%[_bc](15)\n" \ | ||
" brasl 14,%[_fn]\n" \ | ||
" la 15,0(%[_prev])\n" \ | ||
: [_prev] "=&a" (prev), CALL_FMT_##nr \ | ||
[_stack] "a" (stack), \ | ||
[_bc] "i" (offsetof(struct stack_frame, back_chain)), \ | ||
[_fn] "X" (fn) : CALL_CLOBBER_##nr); \ | ||
r2; \ | ||
}) | ||
|
||
#endif /* _ASM_S390_STACKTRACE_H */ |
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,101 @@ | ||
/* SPDX-License-Identifier: GPL-2.0 */ | ||
#ifndef _ASM_S390_UNWIND_H | ||
#define _ASM_S390_UNWIND_H | ||
|
||
#include <linux/sched.h> | ||
#include <linux/ftrace.h> | ||
#include <asm/ptrace.h> | ||
#include <asm/stacktrace.h> | ||
|
||
/* | ||
* To use the stack unwinder it has to be initialized with unwind_start. | ||
* There four combinations for task and regs: | ||
* 1) task==NULL, regs==NULL: the unwind starts for the task that is currently | ||
* running, sp/ip picked up from the CPU registers | ||
* 2) task==NULL, regs!=NULL: the unwind starts from the sp/ip found in | ||
* the struct pt_regs of an interrupt frame for the current task | ||
* 3) task!=NULL, regs==NULL: the unwind starts for an inactive task with | ||
* the sp picked up from task->thread.ksp and the ip picked up from the | ||
* return address stored by __switch_to | ||
* 4) task!=NULL, regs!=NULL: the sp/ip are picked up from the interrupt | ||
* frame 'regs' of a inactive task | ||
* If 'first_frame' is not zero unwind_start skips unwind frames until it | ||
* reaches the specified stack pointer. | ||
* The end of the unwinding is indicated with unwind_done, this can be true | ||
* right after unwind_start, e.g. with first_frame!=0 that can not be found. | ||
* unwind_next_frame skips to the next frame. | ||
* Once the unwind is completed unwind_error() can be used to check if there | ||
* has been a situation where the unwinder could not correctly understand | ||
* the tasks call chain. | ||
*/ | ||
|
||
struct unwind_state { | ||
struct stack_info stack_info; | ||
unsigned long stack_mask; | ||
struct task_struct *task; | ||
struct pt_regs *regs; | ||
unsigned long sp, ip; | ||
int graph_idx; | ||
bool reliable; | ||
bool error; | ||
}; | ||
|
||
void __unwind_start(struct unwind_state *state, struct task_struct *task, | ||
struct pt_regs *regs, unsigned long first_frame); | ||
bool unwind_next_frame(struct unwind_state *state); | ||
unsigned long unwind_get_return_address(struct unwind_state *state); | ||
|
||
static inline bool unwind_done(struct unwind_state *state) | ||
{ | ||
return state->stack_info.type == STACK_TYPE_UNKNOWN; | ||
} | ||
|
||
static inline bool unwind_error(struct unwind_state *state) | ||
{ | ||
return state->error; | ||
} | ||
|
||
static inline void unwind_start(struct unwind_state *state, | ||
struct task_struct *task, | ||
struct pt_regs *regs, | ||
unsigned long sp) | ||
{ | ||
sp = sp ? : get_stack_pointer(task, regs); | ||
__unwind_start(state, task, regs, sp); | ||
} | ||
|
||
static inline struct pt_regs *unwind_get_entry_regs(struct unwind_state *state) | ||
{ | ||
return unwind_done(state) ? NULL : state->regs; | ||
} | ||
|
||
#define unwind_for_each_frame(state, task, regs, first_frame) \ | ||
for (unwind_start(state, task, regs, first_frame); \ | ||
!unwind_done(state); \ | ||
unwind_next_frame(state)) | ||
|
||
static inline void unwind_init(void) {} | ||
static inline void unwind_module_init(struct module *mod, void *orc_ip, | ||
size_t orc_ip_size, void *orc, | ||
size_t orc_size) {} | ||
|
||
#ifdef CONFIG_KASAN | ||
/* | ||
* This disables KASAN checking when reading a value from another task's stack, | ||
* since the other task could be running on another CPU and could have poisoned | ||
* the stack in the meantime. | ||
*/ | ||
#define READ_ONCE_TASK_STACK(task, x) \ | ||
({ \ | ||
unsigned long val; \ | ||
if (task == current) \ | ||
val = READ_ONCE(x); \ | ||
else \ | ||
val = READ_ONCE_NOCHECK(x); \ | ||
val; \ | ||
}) | ||
#else | ||
#define READ_ONCE_TASK_STACK(task, x) READ_ONCE(x) | ||
#endif | ||
|
||
#endif /* _ASM_S390_UNWIND_H */ |
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
Oops, something went wrong.