diff options
Diffstat (limited to 'arch/arm/kernel/unwind.c')
| -rw-r--r-- | arch/arm/kernel/unwind.c | 139 | 
1 files changed, 101 insertions, 38 deletions
diff --git a/arch/arm/kernel/unwind.c b/arch/arm/kernel/unwind.c index 00df012c467..e67682f02cb 100644 --- a/arch/arm/kernel/unwind.c +++ b/arch/arm/kernel/unwind.c @@ -31,7 +31,7 @@  #warning Your compiler does not have EABI support.  #warning    ARM unwind is known to compile only with EABI compilers.  #warning    Change compiler or disable ARM_UNWIND option. -#elif (__GNUC__ == 4 && __GNUC_MINOR__ <= 2) +#elif (__GNUC__ == 4 && __GNUC_MINOR__ <= 2) && !defined(__clang__)  #warning Your compiler is too buggy; it is known to not compile ARM unwind support.  #warning    Change compiler or disable ARM_UNWIND option.  #endif @@ -68,6 +68,12 @@ EXPORT_SYMBOL(__aeabi_unwind_cpp_pr2);  struct unwind_ctrl_block {  	unsigned long vrs[16];		/* virtual register set */  	const unsigned long *insn;	/* pointer to the current instructions word */ +	unsigned long sp_high;		/* highest value of sp allowed */ +	/* +	 * 1 : check for stack overflow for each register pop. +	 * 0 : save overhead if there is plenty of stack remaining. +	 */ +	int check_each_pop;  	int entries;			/* number of entries left to interpret */  	int byte;			/* current byte number in the instructions word */  }; @@ -235,12 +241,85 @@ static unsigned long unwind_get_byte(struct unwind_ctrl_block *ctrl)  	return ret;  } +/* Before poping a register check whether it is feasible or not */ +static int unwind_pop_register(struct unwind_ctrl_block *ctrl, +				unsigned long **vsp, unsigned int reg) +{ +	if (unlikely(ctrl->check_each_pop)) +		if (*vsp >= (unsigned long *)ctrl->sp_high) +			return -URC_FAILURE; + +	ctrl->vrs[reg] = *(*vsp)++; +	return URC_OK; +} + +/* Helper functions to execute the instructions */ +static int unwind_exec_pop_subset_r4_to_r13(struct unwind_ctrl_block *ctrl, +						unsigned long mask) +{ +	unsigned long *vsp = (unsigned long *)ctrl->vrs[SP]; +	int load_sp, reg = 4; + +	load_sp = mask & (1 << (13 - 4)); +	while (mask) { +		if (mask & 1) +			if (unwind_pop_register(ctrl, &vsp, reg)) +				return -URC_FAILURE; +		mask >>= 1; +		reg++; +	} +	if (!load_sp) +		ctrl->vrs[SP] = (unsigned long)vsp; + +	return URC_OK; +} + +static int unwind_exec_pop_r4_to_rN(struct unwind_ctrl_block *ctrl, +					unsigned long insn) +{ +	unsigned long *vsp = (unsigned long *)ctrl->vrs[SP]; +	int reg; + +	/* pop R4-R[4+bbb] */ +	for (reg = 4; reg <= 4 + (insn & 7); reg++) +		if (unwind_pop_register(ctrl, &vsp, reg)) +				return -URC_FAILURE; + +	if (insn & 0x8) +		if (unwind_pop_register(ctrl, &vsp, 14)) +				return -URC_FAILURE; + +	ctrl->vrs[SP] = (unsigned long)vsp; + +	return URC_OK; +} + +static int unwind_exec_pop_subset_r0_to_r3(struct unwind_ctrl_block *ctrl, +						unsigned long mask) +{ +	unsigned long *vsp = (unsigned long *)ctrl->vrs[SP]; +	int reg = 0; + +	/* pop R0-R3 according to mask */ +	while (mask) { +		if (mask & 1) +			if (unwind_pop_register(ctrl, &vsp, reg)) +				return -URC_FAILURE; +		mask >>= 1; +		reg++; +	} +	ctrl->vrs[SP] = (unsigned long)vsp; + +	return URC_OK; +} +  /*   * Execute the current unwind instruction.   */  static int unwind_exec_insn(struct unwind_ctrl_block *ctrl)  {  	unsigned long insn = unwind_get_byte(ctrl); +	int ret = URC_OK;  	pr_debug("%s: insn = %08lx\n", __func__, insn); @@ -250,8 +329,6 @@ static int unwind_exec_insn(struct unwind_ctrl_block *ctrl)  		ctrl->vrs[SP] -= ((insn & 0x3f) << 2) + 4;  	else if ((insn & 0xf0) == 0x80) {  		unsigned long mask; -		unsigned long *vsp = (unsigned long *)ctrl->vrs[SP]; -		int load_sp, reg = 4;  		insn = (insn << 8) | unwind_get_byte(ctrl);  		mask = insn & 0x0fff; @@ -261,29 +338,16 @@ static int unwind_exec_insn(struct unwind_ctrl_block *ctrl)  			return -URC_FAILURE;  		} -		/* pop R4-R15 according to mask */ -		load_sp = mask & (1 << (13 - 4)); -		while (mask) { -			if (mask & 1) -				ctrl->vrs[reg] = *vsp++; -			mask >>= 1; -			reg++; -		} -		if (!load_sp) -			ctrl->vrs[SP] = (unsigned long)vsp; +		ret = unwind_exec_pop_subset_r4_to_r13(ctrl, mask); +		if (ret) +			goto error;  	} else if ((insn & 0xf0) == 0x90 &&  		   (insn & 0x0d) != 0x0d)  		ctrl->vrs[SP] = ctrl->vrs[insn & 0x0f];  	else if ((insn & 0xf0) == 0xa0) { -		unsigned long *vsp = (unsigned long *)ctrl->vrs[SP]; -		int reg; - -		/* pop R4-R[4+bbb] */ -		for (reg = 4; reg <= 4 + (insn & 7); reg++) -			ctrl->vrs[reg] = *vsp++; -		if (insn & 0x80) -			ctrl->vrs[14] = *vsp++; -		ctrl->vrs[SP] = (unsigned long)vsp; +		ret = unwind_exec_pop_r4_to_rN(ctrl, insn); +		if (ret) +			goto error;  	} else if (insn == 0xb0) {  		if (ctrl->vrs[PC] == 0)  			ctrl->vrs[PC] = ctrl->vrs[LR]; @@ -291,8 +355,6 @@ static int unwind_exec_insn(struct unwind_ctrl_block *ctrl)  		ctrl->entries = 0;  	} else if (insn == 0xb1) {  		unsigned long mask = unwind_get_byte(ctrl); -		unsigned long *vsp = (unsigned long *)ctrl->vrs[SP]; -		int reg = 0;  		if (mask == 0 || mask & 0xf0) {  			pr_warning("unwind: Spare encoding %04lx\n", @@ -300,14 +362,9 @@ static int unwind_exec_insn(struct unwind_ctrl_block *ctrl)  			return -URC_FAILURE;  		} -		/* pop R0-R3 according to mask */ -		while (mask) { -			if (mask & 1) -				ctrl->vrs[reg] = *vsp++; -			mask >>= 1; -			reg++; -		} -		ctrl->vrs[SP] = (unsigned long)vsp; +		ret = unwind_exec_pop_subset_r0_to_r3(ctrl, mask); +		if (ret) +			goto error;  	} else if (insn == 0xb2) {  		unsigned long uleb128 = unwind_get_byte(ctrl); @@ -320,7 +377,8 @@ static int unwind_exec_insn(struct unwind_ctrl_block *ctrl)  	pr_debug("%s: fp = %08lx sp = %08lx lr = %08lx pc = %08lx\n", __func__,  		 ctrl->vrs[FP], ctrl->vrs[SP], ctrl->vrs[LR], ctrl->vrs[PC]); -	return URC_OK; +error: +	return ret;  }  /* @@ -329,13 +387,13 @@ static int unwind_exec_insn(struct unwind_ctrl_block *ctrl)   */  int unwind_frame(struct stackframe *frame)  { -	unsigned long high, low; +	unsigned long low;  	const struct unwind_idx *idx;  	struct unwind_ctrl_block ctrl; -	/* only go to a higher address on the stack */ +	/* store the highest address on the stack to avoid crossing it*/  	low = frame->sp; -	high = ALIGN(low, THREAD_SIZE); +	ctrl.sp_high = ALIGN(low, THREAD_SIZE);  	pr_debug("%s(pc = %08lx lr = %08lx sp = %08lx)\n", __func__,  		 frame->pc, frame->lr, frame->sp); @@ -382,11 +440,16 @@ int unwind_frame(struct stackframe *frame)  		return -URC_FAILURE;  	} +	ctrl.check_each_pop = 0; +  	while (ctrl.entries > 0) { -		int urc = unwind_exec_insn(&ctrl); +		int urc; +		if ((ctrl.sp_high - ctrl.vrs[SP]) < sizeof(ctrl.vrs)) +			ctrl.check_each_pop = 1; +		urc = unwind_exec_insn(&ctrl);  		if (urc < 0)  			return urc; -		if (ctrl.vrs[SP] < low || ctrl.vrs[SP] >= high) +		if (ctrl.vrs[SP] < low || ctrl.vrs[SP] >= ctrl.sp_high)  			return -URC_FAILURE;  	}  | 
