lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <ef9a03ee-6fdf-3968-de04-057cafc7c7c7@loongson.cn>
Date:   Fri, 29 Jul 2022 10:03:03 +0800
From:   Youling Tang <tangyouling@...ngson.cn>
To:     Qing Zhang <zhangqing@...ngson.cn>,
        Huacai Chen <chenhuacai@...nel.org>
Cc:     WANG Xuerui <kernel@...0n.name>, loongarch@...ts.linux.dev,
        linux-kernel@...r.kernel.org,
        Jiaxun Yang <jiaxun.yang@...goat.com>, hejinyang@...ngson.cn
Subject: Re: [PATCH 2/3] LoongArch: Add prologue unwinder support



On 07/28/2022 10:05 PM, Qing Zhang wrote:
> It unwind the stack frame based on prologue code analyze.
> CONFIG_KALLSYMS is needed, at least the address and length
> of each function.
>
> Three stages when we do unwind,
>   (1)unwind_start(), the prapare of unwinding, fill unwind_state.
>   (2)unwind_done(), judge whether the unwind process is finished or not.
>   (3)unwind_next_frame(), unwind the next frame.
>
> Dividing unwinder helps to add new unwinders in the future, eg:
> unwind_frame, unwind_orc .etc
>
> Signed-off-by: Qing Zhang <zhangqing@...ngson.cn>
>
> diff --git a/arch/loongarch/Kconfig.debug b/arch/loongarch/Kconfig.debug
> index 68634d4fa27b..57cdbe0cfd98 100644
> --- a/arch/loongarch/Kconfig.debug
> +++ b/arch/loongarch/Kconfig.debug
> @@ -1,3 +1,11 @@
> +choice
> +	prompt "Choose kernel unwinder"
> +	default UNWINDER_PROLOGUE if KALLSYMS
> +	help
> +	  This determines which method will be used for unwinding kernel stack
> +	  traces for panics, oopses, bugs, warnings, perf, /proc/<pid>/stack,
> +	  lockdep, and more.
> +
>  config UNWINDER_GUESS
>  	bool "Guess unwinder"
>  	help
> @@ -7,3 +15,14 @@ config UNWINDER_GUESS
>
>  	  While this option often produces false positives, it can still be
>  	  useful in many cases.
> +
> +config UNWINDER_PROLOGUE
> +	bool "Prologue unwinder"
> +	depends on KALLSYMS
> +	help
> +	  This option enables the "prologue" unwinder for unwinding kernel stack
> +	  traces.  It unwind the stack frame based on prologue code analyze.  Symbol
> +	  information is needed, at least the address and length of each function.
> +	  Some of the addresses it reports may be incorrect.
> +
> +endchoice
> diff --git a/arch/loongarch/include/asm/inst.h b/arch/loongarch/include/asm/inst.h
> index 575d1bb66ffb..bd684cff4008 100644
> --- a/arch/loongarch/include/asm/inst.h
> +++ b/arch/loongarch/include/asm/inst.h
> @@ -23,12 +23,33 @@ enum reg1i20_op {
>  	lu32id_op	= 0x0b,
>  };
>
> +enum reg1i21_op {
> +	beqz_op		= 0x10,
> +	bnez_op		= 0x11,
> +};
> +
>  enum reg2i12_op {
> +	addiw_op	= 0x0a,
> +	addid_op	= 0x0b,
>  	lu52id_op	= 0x0c,
> +	ldb_op		= 0xa0,
> +	ldh_op		= 0xa1,
> +	ldw_op		= 0xa2,
> +	ldd_op		= 0xa3,
> +	stb_op		= 0xa4,
> +	sth_op		= 0xa5,
> +	stw_op		= 0xa6,
> +	std_op		= 0xa7,
>  };
>
>  enum reg2i16_op {
>  	jirl_op		= 0x13,
> +	beq_op		= 0x16,
> +	bne_op		= 0x17,
> +	blt_op		= 0x18,
> +	bge_op		= 0x19,
> +	bltu_op		= 0x1a,
> +	bgeu_op		= 0x1b,
>  };
>
>  struct reg0i26_format {
> @@ -110,6 +131,29 @@ enum loongarch_gpr {
>  	LOONGARCH_GPR_MAX
>  };
>
> +static inline bool is_stack_alloc_ins(union loongarch_instruction *ip)
> +{
> +	/* addi.d $sp, $sp, -imm */
> +	return ip->reg2i12_format.opcode == addid_op &&
> +		ip->reg2i12_format.rj == LOONGARCH_GPR_SP &&
> +		ip->reg2i12_format.rd == LOONGARCH_GPR_SP &&
> +		ip->reg2i12_format.immediate & (1 << 11);
> +}
> +
> +static inline bool is_ra_save_ins(union loongarch_instruction *ip)
> +{
> +	/* st.d $ra, $sp, offset */
> +	return ip->reg2i12_format.opcode == std_op &&
> +		ip->reg2i12_format.rj == LOONGARCH_GPR_SP &&
> +		ip->reg2i12_format.rd == LOONGARCH_GPR_RA;
> +}
> +
> +static inline bool is_branch_insn(union loongarch_instruction insn)
> +{
> +	return insn.reg1i21_format.opcode >= beqz_op &&
> +			insn.reg1i21_format.opcode <= bgeu_op;
> +}
> +
>  u32 larch_insn_gen_lu32id(enum loongarch_gpr rd, int imm);
>  u32 larch_insn_gen_lu52id(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm);
>  u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, unsigned long pc, unsigned long dest);
> diff --git a/arch/loongarch/include/asm/unwind.h b/arch/loongarch/include/asm/unwind.h
> index 243330b39d0d..09394e536ea9 100644
> --- a/arch/loongarch/include/asm/unwind.h
> +++ b/arch/loongarch/include/asm/unwind.h
> @@ -14,6 +14,10 @@
>  struct unwind_state {
>  	struct stack_info stack_info;
>  	struct task_struct *task;
> +#if defined(CONFIG_UNWINDER_PROLOGUE)
> +	unsigned long ra;
> +	bool enable;
> +#endif
>  	unsigned long sp, pc;
>  	bool first;
>  	bool error;
> diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile
> index c5fa4adb23b6..918600e7b30f 100644
> --- a/arch/loongarch/kernel/Makefile
> +++ b/arch/loongarch/kernel/Makefile
> @@ -23,5 +23,6 @@ obj-$(CONFIG_SMP)		+= smp.o
>  obj-$(CONFIG_NUMA)		+= numa.o
>
>  obj-$(CONFIG_UNWINDER_GUESS)	+= unwind_guess.o
> +obj-$(CONFIG_UNWINDER_PROLOGUE) += unwind_prologue.o
>
>  CPPFLAGS_vmlinux.lds		:= $(KBUILD_CFLAGS)
> diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c
> index ef2c3aeb1dab..3e904fa12d48 100644
> --- a/arch/loongarch/kernel/traps.c
> +++ b/arch/loongarch/kernel/traps.c
> @@ -73,6 +73,11 @@ static void show_backtrace(struct task_struct *task, const struct pt_regs *regs,
>
>  	unwind_start(&state, task, pregs);
>
> +#ifdef CONFIG_UNWINDER_PROLOGUE
> +	if (user_mode(regs))
> +		state.enable = false;
> +#endif
> +
>  	printk("%sCall Trace:", loglvl);
>  	for (; !unwind_done(&state); unwind_next_frame(&state)) {
>  		addr = unwind_get_return_address(&state);
> diff --git a/arch/loongarch/kernel/unwind_prologue.c b/arch/loongarch/kernel/unwind_prologue.c
> new file mode 100644
> index 000000000000..6539c9e98364
> --- /dev/null
> +++ b/arch/loongarch/kernel/unwind_prologue.c
> @@ -0,0 +1,174 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2022 Loongson Technology Corporation Limited
> + */
> +#include <linux/kallsyms.h>
> +
> +#include <asm/inst.h>
> +#include <asm/ptrace.h>
> +#include <asm/unwind.h>
> +
> +unsigned long unwind_get_return_address(struct unwind_state *state)
> +{
> +
> +	if (unwind_done(state))
> +		return 0;
This judgment can be removed, because unwind_done() has been judged
before entering this function, and unwind_get_return_address will not
be called if it is true.

> +
> +	if (state->enable)
> +		return state->pc;
> +	else if (state->first)
> +		return state->pc;
> +
> +	return *(unsigned long *)(state->sp);
> +
> +}
> +EXPORT_SYMBOL_GPL(unwind_get_return_address);
> +
> +static bool unwind_by_prologue(struct unwind_state *state)
> +{
> +	struct stack_info *info = &state->stack_info;
> +	union loongarch_instruction *ip, *ip_end;
> +	unsigned long frame_size = 0, frame_ra = -1;
> +	unsigned long size, offset, pc = state->pc;
> +
> +	if (state->sp >= info->end || state->sp < info->begin)
> +		return false;
> +
> +	if (!kallsyms_lookup_size_offset(pc, &size, &offset))
> +		return false;
> +
> +	ip = (union loongarch_instruction *)(pc - offset);
> +	ip_end = (union loongarch_instruction *)pc;
> +
> +	while (ip < ip_end) {
> +		if (is_stack_alloc_ins(ip)) {
> +			frame_size = (1 << 12) - ip->reg2i12_format.immediate;
> +			ip++;
> +			break;
> +		}
> +		ip++;
> +	}
> +
> +	if (!frame_size) {
> +		if (state->first)
> +			goto first;
> +
> +		return false;
> +	}
> +
> +	while (ip < ip_end) {
> +		if (is_ra_save_ins(ip)) {
> +			frame_ra = ip->reg2i12_format.immediate;
Because the immediate member in struct reg2i12_format is defined as an
unsigned type, the value obtained by frame_ra here can only be a
positive number.

> +			break;
> +		}
> +		if (is_branch_insn(*ip))
> +			break;
> +		ip++;
> +	}
> +
> +	if (frame_ra < 0) {
In addition to judging whether the initial value of frame_ra is
negative, we also want to judge whether the previously assigned
frame_ra is negative.

Save the ra value to the stack in the prologue, offset must be a
positive number, so we can add another judgment to is_ra_save_ins, the
code is as follows:
+static inline bool is_ra_save_ins(union loongarch_instruction *ip)
+{
+	/* st.d $ra, $sp, offset */
+	return ip->reg2i12_format.opcode == std_op &&
+		ip->reg2i12_format.rj == LOONGARCH_GPR_SP &&
+		ip->reg2i12_format.rd == LOONGARCH_GPR_RA &&
+		!(ip->reg2i12_format.immediate & (1 << 11));
+}
> +		if (state->first) {
> +			state->sp = state->sp + frame_size;
> +			goto first;
> +		}
> +		return false;
> +	}

> +
> +	if (state->first)
> +		state->first = false;
> +
> +	state->pc = *(unsigned long *)(state->sp + frame_ra);
> +	state->sp = state->sp + frame_size;
> +	return !!__kernel_text_address(state->pc);
> +
> +first:
> +	state->first = false;
> +	if (state->pc == state->ra)
> +		return false;
> +
> +	state->pc = state->ra;
> +
> +	return !!__kernel_text_address(state->ra);
> +}
> +
> +static bool unwind_by_guess(struct unwind_state *state)
> +{
> +	struct stack_info *info = &state->stack_info;
> +	unsigned long addr;
> +
> +	for (state->sp += sizeof(unsigned long);
> +	     state->sp < info->end;
> +	     state->sp += sizeof(unsigned long)) {
> +		addr = *(unsigned long *)(state->sp);
> +		if (__kernel_text_address(addr))
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +bool unwind_next_frame(struct unwind_state *state)
> +{
> +	struct stack_info *info = &state->stack_info;
> +	struct pt_regs *regs;
> +	unsigned long pc;
> +
> +	if (unwind_done(state))
> +		return false;
Can be removed as above.

Thanks,
Youling
> +
> +	do {
> +		if (state->enable) {
> +			if (unwind_by_prologue(state))
> +				return true;
> +
> +			if (info->type == STACK_TYPE_IRQ &&
> +				info->end == state->sp) {
> +				regs = (struct pt_regs *)info->next_sp;
> +				pc = regs->csr_era;
> +				if (user_mode(regs) || !__kernel_text_address(pc))
> +					return false;
> +
> +				state->pc = pc;
> +				state->sp = regs->regs[3];
> +				state->ra = regs->regs[1];
> +				state->first = true;
> +				get_stack_info(state->sp, state->task, info);
> +
> +				return true;
> +			}
> +		} else {
> +			if (state->first)
> +				state->first = false;
> +
> +			if (unwind_by_guess(state))
> +				return true;
> +		}
> +
> +		state->sp = info->next_sp;
> +
> +	} while (!get_stack_info(state->sp, state->task, info));
> +
> +	return false;
> +}
> +EXPORT_SYMBOL_GPL(unwind_next_frame);
> +
> +void unwind_start(struct unwind_state *state, struct task_struct *task,
> +		    struct pt_regs *regs)
> +{
> +	memset(state, 0, sizeof(*state));
> +
> +	if (__kernel_text_address(regs->csr_era))
> +		state->enable = true;
> +
> +	state->task = task;
> +	state->pc = regs->csr_era;
> +	state->sp = regs->regs[3];
> +	state->ra = regs->regs[1];
> +	state->first = true;
> +
> +	get_stack_info(state->sp, state->task, &state->stack_info);
> +
> +	if (!unwind_done(state) && !__kernel_text_address(state->pc))
> +		unwind_next_frame(state);
> +}
> +EXPORT_SYMBOL_GPL(unwind_start);
>

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ