Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions arch/riscv/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ config RISCV
select HAVE_KRETPROBES
# https://github.com/ClangBuiltLinux/linux/issues/1881
select HAVE_LD_DEAD_CODE_DATA_ELIMINATION if !LD_IS_LLD
select HAVE_LIVEPATCH if FRAME_POINTER && 64BIT
select HAVE_MOVE_PMD
select HAVE_MOVE_PUD
select HAVE_PAGE_SIZE_4KB
Expand All @@ -195,6 +196,7 @@ config RISCV
select HAVE_POSIX_CPU_TIMERS_TASK_WORK
select HAVE_PREEMPT_DYNAMIC_KEY
select HAVE_REGS_AND_STACK_ACCESS_API
select HAVE_RELIABLE_STACKTRACE if FRAME_POINTER && 64BIT
select HAVE_RETHOOK
select HAVE_RSEQ
select HAVE_RUST if RUSTC_SUPPORTS_RISCV && CC_IS_CLANG
Expand Down Expand Up @@ -1394,3 +1396,5 @@ endmenu # "CPU Power Management"
source "arch/riscv/kvm/Kconfig"

source "drivers/acpi/Kconfig"

source "kernel/livepatch/Kconfig"
9 changes: 9 additions & 0 deletions arch/riscv/include/asm/ptrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <uapi/asm/ptrace.h>
#include <asm/csr.h>
#include <asm/stacktrace/frame.h>
#include <linux/compiler.h>

#ifndef __ASSEMBLER__
Expand Down Expand Up @@ -53,6 +54,14 @@ struct pt_regs {
unsigned long cause;
/* a0 value before the syscall */
unsigned long orig_a0;

/*
* This frame record is entirely zeroed on exception entry, allowing the
* unwinder to identify exception boundaries. The type field encodes
* whether the exception was taken from user (FINAL) or kernel (PT_REGS)
* mode.
*/
struct frame_record_meta stackframe;
};

#define PTRACE_SYSEMU 0x1f
Expand Down
65 changes: 63 additions & 2 deletions arch/riscv/include/asm/stacktrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@
#ifndef _ASM_RISCV_STACKTRACE_H
#define _ASM_RISCV_STACKTRACE_H

#include <linux/percpu.h>
#include <linux/sched.h>
#include <linux/sched/task_stack.h>

#include <asm/irq_stack.h>
#include <asm/ptrace.h>
#include <asm/stacktrace/common.h>

struct stackframe {
unsigned long fp;
Expand All @@ -16,14 +21,70 @@ extern void notrace walk_stackframe(struct task_struct *task, struct pt_regs *re
extern void dump_backtrace(struct pt_regs *regs, struct task_struct *task,
const char *loglvl);

static inline bool on_thread_stack(void)
/*
* IRQ stack accessors
*/
static inline struct stack_info stackinfo_get_irq(void)
{
unsigned long low = (unsigned long)raw_cpu_read(irq_stack_ptr);
unsigned long high = low + IRQ_STACK_SIZE;

return (struct stack_info) {
.low = low,
.high = high,
};
}

static inline bool on_irq_stack(unsigned long sp, unsigned long size)
{
struct stack_info info = stackinfo_get_irq();

return stackinfo_on_stack(&info, sp, size);
}

/*
* Task stack accessors
*/
static inline struct stack_info stackinfo_get_task(const struct task_struct *tsk)
{
return !(((unsigned long)(current->stack) ^ current_stack_pointer) & ~(THREAD_SIZE - 1));
unsigned long low = (unsigned long)task_stack_page(tsk);
unsigned long high = low + THREAD_SIZE;

return (struct stack_info) {
.low = low,
.high = high,
};
}

static inline bool on_task_stack(const struct task_struct *tsk,
unsigned long sp, unsigned long size)
{
struct stack_info info = stackinfo_get_task(tsk);

return stackinfo_on_stack(&info, sp, size);
}

/*
* Cast is necessary since current->stack is an opaque ptr.
*/
#define on_thread_stack() (on_task_stack(current, current_stack_pointer, 1))

/*
* Overflow stack accessors
*/
#ifdef CONFIG_VMAP_STACK
DECLARE_PER_CPU(unsigned long [OVERFLOW_STACK_SIZE/sizeof(long)], overflow_stack);

static inline struct stack_info stackinfo_get_overflow(void)
{
unsigned long low = (unsigned long)raw_cpu_ptr(overflow_stack);
unsigned long high = low + OVERFLOW_STACK_SIZE;

return (struct stack_info) {
.low = low,
.high = high,
};
}
#endif /* CONFIG_VMAP_STACK */

#endif /* _ASM_RISCV_STACKTRACE_H */
159 changes: 159 additions & 0 deletions arch/riscv/include/asm/stacktrace/common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* RISC-V common stack unwinder types and helpers.
*
* See: arch/arm64/include/asm/stacktrace/common.h for the reference
* implementation.
*
* Copyright (C) 2024
*/
#ifndef __ASM_RISCV_STACKTRACE_COMMON_H
#define __ASM_RISCV_STACKTRACE_COMMON_H

#include <linux/compiler.h>
#include <linux/errno.h>
#include <linux/types.h>

#include <asm/stacktrace/frame.h>

/**
* struct stack_info - describes the bounds of a stack.
*
* @low: The lowest valid address on the stack.
* @high: The highest valid address on the stack.
*/
struct stack_info {
unsigned long low;
unsigned long high;
};

/**
* struct unwind_state - state used for robust unwinding.
*
* @fp: The fp value in the frame record (or the real fp).
* @pc: The ra value in the frame record (or the real ra).
*
* @stack: The stack currently being unwound.
* @stacks: An array of stacks which can be unwound.
* @nr_stacks: The number of stacks in @stacks.
*/
struct unwind_state {
unsigned long fp;
unsigned long pc;

struct stack_info stack;
struct stack_info *stacks;
int nr_stacks;
};

/**
* stackinfo_get_unknown() - Get an unknown stack_info.
*
* Return: a stack_info with low and high set to 0.
*/
static inline struct stack_info stackinfo_get_unknown(void)
{
return (struct stack_info) {
.low = 0,
.high = 0,
};
}

/**
* stackinfo_on_stack() - Check whether an object is fully within a stack.
*
* @info: The stack to check against.
* @sp: The base address of the object.
* @size: The size of the object.
*
* Return: true if the object is fully contained within the stack.
*/
static inline bool stackinfo_on_stack(const struct stack_info *info,
unsigned long sp, unsigned long size)
{
if (!info->low)
return false;

if (sp < info->low || sp + size < sp || sp + size > info->high)
return false;

return true;
}

/**
* unwind_init_common() - Initialize the common parts of the unwind state.
*
* @state: the unwind state to initialize.
*/
static inline void unwind_init_common(struct unwind_state *state)
{
state->stack = stackinfo_get_unknown();
}

/**
* unwind_find_stack() - Find the accessible stack which entirely contains an
* object.
*
* @state: the current unwind state.
* @sp: the base address of the object.
* @size: the size of the object.
*
* Return: a pointer to the relevant stack_info if found; NULL otherwise.
*/
static inline struct stack_info *unwind_find_stack(struct unwind_state *state,
unsigned long sp,
unsigned long size)
{
struct stack_info *info = &state->stack;

if (stackinfo_on_stack(info, sp, size))
return info;

for (int i = 0; i < state->nr_stacks; i++) {
info = &state->stacks[i];
if (stackinfo_on_stack(info, sp, size))
return info;
}

return NULL;
}

/**
* unwind_consume_stack() - Update stack boundaries so that future unwind steps
* cannot consume this object again.
*
* @state: the current unwind state.
* @info: the stack_info of the stack containing the object.
* @sp: the base address of the object.
* @size: the size of the object.
*
* Stack transitions are strictly one-way, and once we've
* transitioned from one stack to another, it's never valid to
* unwind back to the old stack.
*
* Note that stacks can nest in several valid orders, e.g.
*
* TASK -> IRQ -> OVERFLOW
*
* ... so we do not check the specific order of stack
* transitions.
*/
static inline void unwind_consume_stack(struct unwind_state *state,
struct stack_info *info,
unsigned long sp,
unsigned long size)
{
struct stack_info tmp;

tmp = *info;
*info = stackinfo_get_unknown();
state->stack = tmp;

/*
* Future unwind steps can only consume stack above this frame record.
* Update the current stack to start immediately above it.
*/
state->stack.low = sp + size;
}

#endif /* __ASM_RISCV_STACKTRACE_COMMON_H */
53 changes: 53 additions & 0 deletions arch/riscv/include/asm/stacktrace/frame.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef __ASM_RISCV_STACKTRACE_FRAME_H
#define __ASM_RISCV_STACKTRACE_FRAME_H

/*
* See: arch/arm64/include/asm/stacktrace/frame.h for the reference
* implementation.
*/

/*
* - FRAME_META_TYPE_NONE
*
* This value is reserved.
*
* - FRAME_META_TYPE_FINAL
*
* The record is the last entry on the stack.
* Unwinding should terminate successfully.
*
* - FRAME_META_TYPE_PT_REGS
*
* The record is embedded within a struct pt_regs, recording the registers at
* an arbitrary point in time.
* Unwinding should consume pt_regs::epc, followed by pt_regs::ra.
*
* Note: all other values are reserved and should result in unwinding
* terminating with an error.
*/
#define FRAME_META_TYPE_NONE 0
#define FRAME_META_TYPE_FINAL 1
#define FRAME_META_TYPE_PT_REGS 2

#ifndef __ASSEMBLER__
/*
* A standard RISC-V frame record.
*/
struct frame_record {
unsigned long fp;
unsigned long ra;
};

/*
* A metadata frame record indicating a special unwind.
* The record::{fp,ra} fields must be zero to indicate the presence of
* metadata.
*/
struct frame_record_meta {
struct frame_record record;
unsigned long type;
};
#endif /* __ASSEMBLER__ */

#endif /* __ASM_RISCV_STACKTRACE_FRAME_H */
5 changes: 5 additions & 0 deletions arch/riscv/kernel/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ CFLAGS_REMOVE_return_address.o = $(CC_FLAGS_FTRACE)
CFLAGS_REMOVE_sbi_ecall.o = $(CC_FLAGS_FTRACE)
endif

# When KASAN is enabled, a stack trace is recorded for every alloc/free, which
# can significantly impact performance. Avoid instrumenting the stack trace
# collection code to minimize this impact.
KASAN_SANITIZE_stacktrace.o := n

always-$(KBUILD_BUILTIN) += vmlinux.lds

obj-y += head.o
Expand Down
4 changes: 4 additions & 0 deletions arch/riscv/kernel/asm-offsets.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ void asm_offsets(void)
OFFSET(PT_BADADDR, pt_regs, badaddr);
OFFSET(PT_CAUSE, pt_regs, cause);

DEFINE(S_STACKFRAME, offsetof(struct pt_regs, stackframe));
DEFINE(S_STACKFRAME_TYPE, offsetof(struct pt_regs, stackframe.type));

OFFSET(SUSPEND_CONTEXT_REGS, suspend_context, regs);

OFFSET(HIBERN_PBE_ADDR, pbe, address);
Expand Down Expand Up @@ -501,6 +504,7 @@ void asm_offsets(void)
OFFSET(SBI_HART_BOOT_STACK_PTR_OFFSET, sbi_hart_boot_data, stack_ptr);

DEFINE(STACKFRAME_SIZE_ON_STACK, ALIGN(sizeof(struct stackframe), STACK_ALIGN));
DEFINE(STACKFRAME_RECORD_SIZE, sizeof(struct stackframe));
OFFSET(STACKFRAME_FP, stackframe, fp);
OFFSET(STACKFRAME_RA, stackframe, ra);
#ifdef CONFIG_FUNCTION_TRACER
Expand Down
Loading
Loading