Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 78c98f90 authored by Martin Schwidefsky's avatar Martin Schwidefsky
Browse files

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: default avatarMartin Schwidefsky <schwidefsky@de.ibm.com>
parent 1c705ad5
Loading
Loading
Loading
Loading
+0 −72
Original line number Diff line number Diff line
@@ -156,25 +156,6 @@ struct thread_struct {

typedef struct thread_struct thread_struct;

/*
 * 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 ARCH_MIN_TASKALIGN	8

#define INIT_THREAD {							\
@@ -206,11 +187,7 @@ struct mm_struct;
struct seq_file;
struct pt_regs;

typedef int (*dump_trace_func_t)(void *data, unsigned long address, int reliable);
void dump_trace(dump_trace_func_t func, void *data,
		struct task_struct *task, unsigned long sp);
void show_registers(struct pt_regs *regs);

void show_cacheinfo(struct seq_file *m);

/* Free all resources held by a thread. */
@@ -244,55 +221,6 @@ static __no_kasan_or_inline unsigned short stap(void)
	return cpu_address;
}

#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;								\
})

/*
 * Give up the time slice of the virtual PU.
 */
+114 −0
Original line number Diff line number Diff line
/* 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 */
+101 −0
Original line number Diff line number Diff line
/* 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 */
+2 −1
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ CFLAGS_smp.o := -Wno-nonnull
#
CFLAGS_stacktrace.o	+= -fno-optimize-sibling-calls
CFLAGS_dumpstack.o	+= -fno-optimize-sibling-calls
CFLAGS_unwind_bc.o	+= -fno-optimize-sibling-calls

#
# Pass UTS_MACHINE for user_regset definition
@@ -51,7 +52,7 @@ obj-y += debug.o irq.o ipl.o dis.o diag.o vdso.o early_nobss.o
obj-y	+= sysinfo.o lgr.o os_info.o machine_kexec.o pgm_check.o
obj-y	+= runtime_instr.o cache.o fpu.o dumpstack.o guarded_storage.o sthyi.o
obj-y	+= entry.o reipl.o relocate_kernel.o kdebugfs.o alternative.o
obj-y	+= nospec-branch.o ipl_vmparm.o machine_kexec_reloc.o
obj-y	+= nospec-branch.o ipl_vmparm.o machine_kexec_reloc.o unwind_bc.o

extra-y				+= head64.o vmlinux.lds

+1 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
#include <asm/pgtable.h>
#include <asm/gmap.h>
#include <asm/nmi.h>
#include <asm/stacktrace.h>

int main(void)
{
Loading