Commit 56dc4a3f authored by Mao Minkai's avatar Mao Minkai Committed by guzitao
Browse files

sw64: add stacktrace support

Sunway inclusion
category: feature
bugzilla: https://gitee.com/openeuler/kernel/issues/I8Y8CY



--------------------------------

Add stacktrace support for SW64.

Signed-off-by: default avatarMao Minkai <maominkai@wxiat.com>
Reviewed-by: default avatarHe Sheng <hesheng@wxiat.com>
Signed-off-by: default avatarGu Zitao <guzitao@wxiat.com>
parent aa96030b
Loading
Loading
Loading
Loading
+72 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */

#ifndef _ASM_SW64_STACKTRACE_H
#define _ASM_SW64_STACKTRACE_H

#include <linux/percpu.h>
#include <linux/sched.h>
#include <linux/sched/task_stack.h>
#include <asm/memory.h>
#include <asm/ptrace.h>

struct stackframe {
	unsigned long pc;
	unsigned long fp;
};

enum stack_type {
	STACK_TYPE_UNKNOWN,
	STACK_TYPE_TASK,
};

struct stack_info {
	unsigned long low;
	unsigned long high;
	enum stack_type type;
};

/* The form of the top of the frame on the stack */
struct stack_frame {
	unsigned long return_address;
	struct stack_frame *next_frame;
};

extern int unwind_frame(struct task_struct *tsk, struct stackframe *frame);
extern void walk_stackframe(struct task_struct *tsk, struct pt_regs *regs,
			    int (*fn)(unsigned long, void *), void *data);

static inline bool on_task_stack(struct task_struct *tsk, unsigned long sp,
				struct stack_info *info)
{
	unsigned long low = (unsigned long)task_stack_page(tsk);
	unsigned long high = low + THREAD_SIZE;

	if (sp < low || sp >= high)
		return false;

	if (info) {
		info->low = low;
		info->high = high;
		info->type = STACK_TYPE_TASK;
	}

	return true;
}

/*
 * We can only safely access per-cpu stacks from current in a non-preemptible
 * context.
 */
static inline bool on_accessible_stack(struct task_struct *tsk,
					unsigned long sp,
					struct stack_info *info)
{
	if (on_task_stack(tsk, sp, info))
		return true;
	if (tsk != current || preemptible())
		return false;

	return false;
}

#endif /* _ASM_SW64_STACKTRACE_H */
+247 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Stack trace management functions
 *
 *  Copyright (C) 2018 snyh <xiabin@deepin.com>
 */
#include <linux/sched.h>
#include <linux/stacktrace.h>
#include <linux/sched/task_stack.h>
#include <linux/sched/debug.h>
#include <linux/ftrace.h>
#include <linux/perf_event.h>
#include <linux/kallsyms.h>

#include <asm/stacktrace.h>

/*
 * sw_64 PCS assigns the frame pointer to r15.
 *
 * A simple function prologue looks like this:
 *	ldi     sp,-xx(sp)
 *	stl     ra,0(sp)
 *	stl     fp,8(sp)
 *	mov     sp,fp
 *
 * A simple function epilogue looks like this:
 *	mov     fp,sp
 *	ldl     ra,0(sp)
 *	ldl     fp,8(sp)
 *	ldi     sp,+xx(sp)
 */

#ifdef CONFIG_FRAME_POINTER

int unwind_frame(struct task_struct *tsk, struct stackframe *frame)
{
	unsigned long fp = frame->fp;

	if (fp & 0x7)
		return -EINVAL;

	if (!tsk)
		tsk = current;

	if (!on_accessible_stack(tsk, fp, NULL))
		return -EINVAL;

	frame->pc = READ_ONCE_NOCHECK(*(unsigned long *)(fp));
	frame->fp = READ_ONCE_NOCHECK(*(unsigned long *)(fp + 8));

	/*
	 * Frames created upon entry from user have NULL FP and PC values, so
	 * don't bother reporting these. Frames created by __noreturn functions
	 * might have a valid FP even if PC is bogus, so only terminate where
	 * both are NULL.
	 */
	if (!frame->fp && !frame->pc)
		return -EINVAL;

	return 0;
}
EXPORT_SYMBOL_GPL(unwind_frame);

void walk_stackframe(struct task_struct *tsk, struct pt_regs *regs,
		     int (*fn)(unsigned long, void *), void *data)
{
	unsigned long pc, fp;

	struct stackframe frame;

	if (regs) {
		unsigned long offset;

		pc = regs->pc;
		fp = regs->regs[15];
		if (kallsyms_lookup_size_offset(pc, NULL, &offset)
				&& offset < 16) {
			/* call stack has not been setup
			 * store pc first then loop from ra
			 */
			if (fn(pc, data))
				return;
			pc = regs->regs[26];
		}
	} else if (tsk == current || tsk == NULL) {
		fp = (unsigned long)__builtin_frame_address(0);
		pc = (unsigned long)walk_stackframe;
	} else {
		fp = tsk->thread.s[6];
		pc = tsk->thread.ra;
	}

	if (!__kernel_text_address(pc) || fn(pc, data))
		return;

	frame.pc = pc;
	frame.fp = fp;
	while (1) {
		int ret;

		ret = unwind_frame(tsk, &frame);
		if (ret < 0)
			break;

		if (fn(frame.pc, data))
			break;
	}
}
EXPORT_SYMBOL_GPL(walk_stackframe);

#else /* !CONFIG_FRAME_POINTER */
void walk_stackframe(struct task_struct *tsk, struct pt_regs *regs,
		     int (*fn)(unsigned long, void *), void *data)
{
	unsigned long *ksp;
	unsigned long sp, pc;

	if (regs) {
		sp = (unsigned long)(regs+1);
		pc = regs->pc;
	} else if (tsk == current || tsk == NULL) {
		register unsigned long current_sp __asm__ ("$30");
		sp = current_sp;
		pc = (unsigned long)walk_stackframe;
	} else {
		sp = tsk->thread.sp;
		pc = tsk->thread.ra;
	}

	ksp = (unsigned long *)sp;

	while (!kstack_end(ksp)) {
		if (__kernel_text_address(pc) && fn(pc, data))
			break;
		pc = *ksp++;
	}
}
EXPORT_SYMBOL_GPL(walk_stackframe);

#endif/* CONFIG_FRAME_POINTER */

static int print_address_trace(unsigned long pc, void *data)
{
	print_ip_sym((const char *)data, pc);
	return 0;
}

void show_stack(struct task_struct *task, unsigned long *sp, const char *loglvl)
{
	pr_info("Trace:\n");
	walk_stackframe(task, NULL, print_address_trace, (void *)loglvl);
}

#ifdef CONFIG_STACKTRACE
/*
 * Save stack-backtrace addresses into a stack_trace buffer.
 */
struct stack_trace_data {
	struct stack_trace *trace;
	unsigned int nosched;
};

int save_trace(unsigned long pc, void *d)
{
	struct stack_trace_data *data = d;
	struct stack_trace *trace = data->trace;

	if (data->nosched && in_sched_functions(pc))
		return 0;
	if (trace->skip > 0) {
		trace->skip--;
		return 0;
	}

	trace->entries[trace->nr_entries++] = pc;
	return (trace->nr_entries >= trace->max_entries);
}

void save_stack_trace_regs(struct pt_regs *regs, struct stack_trace *trace)
{
	struct stack_trace_data data;

	data.trace = trace;
	data.nosched = 0;

	walk_stackframe(current, regs, save_trace, &data);

	if (trace->nr_entries < trace->max_entries)
		trace->entries[trace->nr_entries++] = ULONG_MAX;
}

static void __save_stack_trace(struct task_struct *tsk,
		struct stack_trace *trace, unsigned int nosched)
{
	struct stack_trace_data data;

	data.trace = trace;
	data.nosched = nosched;

	walk_stackframe(tsk, NULL, save_trace, &data);

	if (trace->nr_entries < trace->max_entries)
		trace->entries[trace->nr_entries++] = ULONG_MAX;
}

void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
{
	__save_stack_trace(tsk, trace, 1);
}
EXPORT_SYMBOL_GPL(save_stack_trace_tsk);

void save_stack_trace(struct stack_trace *trace)
{
	__save_stack_trace(current, trace, 0);
}
EXPORT_SYMBOL_GPL(save_stack_trace);
#endif

static int save_pc(unsigned long pc, void *data)
{
	unsigned long *p = data;
	*p = 0;

	if (!in_sched_functions(pc))
		*p = pc;

	return *p;
}

unsigned long __get_wchan(struct task_struct *tsk)
{
	unsigned long pc;

	if (!tsk || tsk == current || task_is_running(tsk))
		return 0;
	walk_stackframe(tsk, NULL, save_pc, &pc);

	return pc;
}

#ifdef CONFIG_HAVE_RELIABLE_STACKTRACE
int save_stack_trace_tsk_reliable(struct task_struct *tsk,
				  struct stack_trace *trace)
{
	return 0;
}
#endif