int arc_unwind()

in kernel/unwind.c [895:1312]


int arc_unwind(struct unwind_frame_info *frame)
{
#define FRAME_REG(r, t) (((t *)frame)[reg_info[r].offs])
	const u32 *fde = NULL, *cie = NULL;
	const u8 *ptr = NULL, *end = NULL;
	unsigned long pc = UNW_PC(frame) - frame->call_frame;
	unsigned long startLoc = 0, endLoc = 0, cfa;
	unsigned int i;
	signed ptrType = -1;
	uleb128_t retAddrReg = 0;
	const struct unwind_table *table;
	struct unwind_state state;
	unsigned long *fptr;
	unsigned long addr;

	unw_debug("\n\nUNWIND FRAME:\n");
	unw_debug("PC: 0x%lx BLINK: 0x%lx, SP: 0x%lx, FP: 0x%x\n",
		  UNW_PC(frame), UNW_BLINK(frame), UNW_SP(frame),
		  UNW_FP(frame));

	if (UNW_PC(frame) == 0)
		return -EINVAL;

#ifdef UNWIND_DEBUG
	{
		unsigned long *sptr = (unsigned long *)UNW_SP(frame);
		unw_debug("\nStack Dump:\n");
		for (i = 0; i < 20; i++, sptr++)
			unw_debug("0x%p:  0x%lx\n", sptr, *sptr);
		unw_debug("\n");
	}
#endif

	table = find_table(pc);
	if (table != NULL
	    && !(table->size & (sizeof(*fde) - 1))) {
		const u8 *hdr = table->header;
		unsigned long tableSize;

		smp_rmb();
		if (hdr && hdr[0] == 1) {
			switch (hdr[3] & DW_EH_PE_FORM) {
			case DW_EH_PE_native:
				tableSize = sizeof(unsigned long);
				break;
			case DW_EH_PE_data2:
				tableSize = 2;
				break;
			case DW_EH_PE_data4:
				tableSize = 4;
				break;
			case DW_EH_PE_data8:
				tableSize = 8;
				break;
			default:
				tableSize = 0;
				break;
			}
			ptr = hdr + 4;
			end = hdr + table->hdrsz;
			if (tableSize && read_pointer(&ptr, end, hdr[1])
			    == (unsigned long)table->address
			    && (i = read_pointer(&ptr, end, hdr[2])) > 0
			    && i == (end - ptr) / (2 * tableSize)
			    && !((end - ptr) % (2 * tableSize))) {
				do {
					const u8 *cur =
					    ptr + (i / 2) * (2 * tableSize);

					startLoc = read_pointer(&cur,
								cur + tableSize,
								hdr[3]);
					if (pc < startLoc)
						i /= 2;
					else {
						ptr = cur - tableSize;
						i = (i + 1) / 2;
					}
				} while (startLoc && i > 1);
				if (i == 1
				    && (startLoc = read_pointer(&ptr,
								ptr + tableSize,
								hdr[3])) != 0
				    && pc >= startLoc)
					fde = (void *)read_pointer(&ptr,
								   ptr +
								   tableSize,
								   hdr[3]);
			}
		}

		if (fde != NULL) {
			cie = cie_for_fde(fde, table);
			ptr = (const u8 *)(fde + 2);
			if (cie != NULL
			    && cie != &bad_cie
			    && cie != &not_fde
			    && (ptrType = fde_pointer_type(cie)) >= 0
			    && read_pointer(&ptr,
					    (const u8 *)(fde + 1) + *fde,
					    ptrType) == startLoc) {
				if (!(ptrType & DW_EH_PE_indirect))
					ptrType &=
					    DW_EH_PE_FORM | DW_EH_PE_signed;
				endLoc =
				    startLoc + read_pointer(&ptr,
							    (const u8 *)(fde +
									 1) +
							    *fde, ptrType);
				if (pc >= endLoc) {
					fde = NULL;
					cie = NULL;
				}
			} else {
				fde = NULL;
				cie = NULL;
			}
		}
	}
	if (cie != NULL) {
		memset(&state, 0, sizeof(state));
		state.cieEnd = ptr;	/* keep here temporarily */
		ptr = (const u8 *)(cie + 2);
		end = (const u8 *)(cie + 1) + *cie;
		frame->call_frame = 1;
		if (*++ptr) {
			/* check if augmentation size is first (thus present) */
			if (*ptr == 'z') {
				while (++ptr < end && *ptr) {
					switch (*ptr) {
					/* chk for ignorable or already handled
					 * nul-terminated augmentation string */
					case 'L':
					case 'P':
					case 'R':
						continue;
					case 'S':
						frame->call_frame = 0;
						continue;
					default:
						break;
					}
					break;
				}
			}
			if (ptr >= end || *ptr)
				cie = NULL;
		}
		++ptr;
	}
	if (cie != NULL) {
		/* get code alignment factor */
		state.codeAlign = get_uleb128(&ptr, end);
		/* get data alignment factor */
		state.dataAlign = get_sleb128(&ptr, end);
		if (state.codeAlign == 0 || state.dataAlign == 0 || ptr >= end)
			cie = NULL;
		else {
			retAddrReg =
			    state.version <= 1 ? *ptr++ : get_uleb128(&ptr,
								      end);
			unw_debug("CIE Frame Info:\n");
			unw_debug("return Address register 0x%lx\n",
				  retAddrReg);
			unw_debug("data Align: %ld\n", state.dataAlign);
			unw_debug("code Align: %lu\n", state.codeAlign);
			/* skip augmentation */
			if (((const char *)(cie + 2))[1] == 'z') {
				uleb128_t augSize = get_uleb128(&ptr, end);

				ptr += augSize;
			}
			if (ptr > end || retAddrReg >= ARRAY_SIZE(reg_info)
			    || REG_INVALID(retAddrReg)
			    || reg_info[retAddrReg].width !=
			    sizeof(unsigned long))
				cie = NULL;
		}
	}
	if (cie != NULL) {
		state.cieStart = ptr;
		ptr = state.cieEnd;
		state.cieEnd = end;
		end = (const u8 *)(fde + 1) + *fde;
		/* skip augmentation */
		if (((const char *)(cie + 2))[1] == 'z') {
			uleb128_t augSize = get_uleb128(&ptr, end);

			if ((ptr += augSize) > end)
				fde = NULL;
		}
	}
	if (cie == NULL || fde == NULL) {
#ifdef CONFIG_FRAME_POINTER
		unsigned long top, bottom;

		top = STACK_TOP_UNW(frame->task);
		bottom = STACK_BOTTOM_UNW(frame->task);
#if FRAME_RETADDR_OFFSET < 0
		if (UNW_SP(frame) < top && UNW_FP(frame) <= UNW_SP(frame)
		    && bottom < UNW_FP(frame)
#else
		if (UNW_SP(frame) > top && UNW_FP(frame) >= UNW_SP(frame)
		    && bottom > UNW_FP(frame)
#endif
		    && !((UNW_SP(frame) | UNW_FP(frame))
			 & (sizeof(unsigned long) - 1))) {
			unsigned long link;

			if (!__get_user(link, (unsigned long *)
					(UNW_FP(frame) + FRAME_LINK_OFFSET))
#if FRAME_RETADDR_OFFSET < 0
			    && link > bottom && link < UNW_FP(frame)
#else
			    && link > UNW_FP(frame) && link < bottom
#endif
			    && !(link & (sizeof(link) - 1))
			    && !__get_user(UNW_PC(frame),
					   (unsigned long *)(UNW_FP(frame)
						+ FRAME_RETADDR_OFFSET)))
			{
				UNW_SP(frame) =
				    UNW_FP(frame) + FRAME_RETADDR_OFFSET
#if FRAME_RETADDR_OFFSET < 0
				    -
#else
				    +
#endif
				    sizeof(UNW_PC(frame));
				UNW_FP(frame) = link;
				return 0;
			}
		}
#endif
		return -ENXIO;
	}
	state.org = startLoc;
	memcpy(&state.cfa, &badCFA, sizeof(state.cfa));

	unw_debug("\nProcess instructions\n");

	/* process instructions
	 * For ARC, we optimize by having blink(retAddrReg) with
	 * the sameValue in the leaf function, so we should not check
	 * state.regs[retAddrReg].where == Nowhere
	 */
	if (!processCFI(ptr, end, pc, ptrType, &state)
	    || state.loc > endLoc
/*	   || state.regs[retAddrReg].where == Nowhere */
	    || state.cfa.reg >= ARRAY_SIZE(reg_info)
	    || reg_info[state.cfa.reg].width != sizeof(unsigned long)
	    || state.cfa.offs % sizeof(unsigned long))
		return -EIO;

#ifdef UNWIND_DEBUG
	unw_debug("\n");

	unw_debug("\nRegister State Based on the rules parsed from FDE:\n");
	for (i = 0; i < ARRAY_SIZE(state.regs); ++i) {

		if (REG_INVALID(i))
			continue;

		switch (state.regs[i].where) {
		case Nowhere:
			break;
		case Memory:
			unw_debug(" r%d: c(%lu),", i, state.regs[i].value);
			break;
		case Register:
			unw_debug(" r%d: r(%lu),", i, state.regs[i].value);
			break;
		case Value:
			unw_debug(" r%d: v(%lu),", i, state.regs[i].value);
			break;
		}
	}

	unw_debug("\n");
#endif

	/* update frame */
	if (frame->call_frame
	    && !UNW_DEFAULT_RA(state.regs[retAddrReg], state.dataAlign))
		frame->call_frame = 0;
	cfa = FRAME_REG(state.cfa.reg, unsigned long) + state.cfa.offs;
	startLoc = min_t(unsigned long, UNW_SP(frame), cfa);
	endLoc = max_t(unsigned long, UNW_SP(frame), cfa);
	if (STACK_LIMIT(startLoc) != STACK_LIMIT(endLoc)) {
		startLoc = min(STACK_LIMIT(cfa), cfa);
		endLoc = max(STACK_LIMIT(cfa), cfa);
	}

	unw_debug("\nCFA reg: 0x%lx, offset: 0x%lx =>  0x%lx\n",
		  state.cfa.reg, state.cfa.offs, cfa);

	for (i = 0; i < ARRAY_SIZE(state.regs); ++i) {
		if (REG_INVALID(i)) {
			if (state.regs[i].where == Nowhere)
				continue;
			return -EIO;
		}
		switch (state.regs[i].where) {
		default:
			break;
		case Register:
			if (state.regs[i].value >= ARRAY_SIZE(reg_info)
			    || REG_INVALID(state.regs[i].value)
			    || reg_info[i].width >
			    reg_info[state.regs[i].value].width)
				return -EIO;
			switch (reg_info[state.regs[i].value].width) {
			case sizeof(u8):
				state.regs[i].value =
				FRAME_REG(state.regs[i].value, const u8);
				break;
			case sizeof(u16):
				state.regs[i].value =
				FRAME_REG(state.regs[i].value, const u16);
				break;
			case sizeof(u32):
				state.regs[i].value =
				FRAME_REG(state.regs[i].value, const u32);
				break;
#ifdef CONFIG_64BIT
			case sizeof(u64):
				state.regs[i].value =
				FRAME_REG(state.regs[i].value, const u64);
				break;
#endif
			default:
				return -EIO;
			}
			break;
		}
	}

	unw_debug("\nRegister state after evaluation with realtime Stack:\n");
	fptr = (unsigned long *)(&frame->regs);
	for (i = 0; i < ARRAY_SIZE(state.regs); ++i, fptr++) {

		if (REG_INVALID(i))
			continue;
		switch (state.regs[i].where) {
		case Nowhere:
			if (reg_info[i].width != sizeof(UNW_SP(frame))
			    || &FRAME_REG(i, __typeof__(UNW_SP(frame)))
			    != &UNW_SP(frame))
				continue;
			UNW_SP(frame) = cfa;
			break;
		case Register:
			switch (reg_info[i].width) {
			case sizeof(u8):
				FRAME_REG(i, u8) = state.regs[i].value;
				break;
			case sizeof(u16):
				FRAME_REG(i, u16) = state.regs[i].value;
				break;
			case sizeof(u32):
				FRAME_REG(i, u32) = state.regs[i].value;
				break;
#ifdef CONFIG_64BIT
			case sizeof(u64):
				FRAME_REG(i, u64) = state.regs[i].value;
				break;
#endif
			default:
				return -EIO;
			}
			break;
		case Value:
			if (reg_info[i].width != sizeof(unsigned long))
				return -EIO;
			FRAME_REG(i, unsigned long) = cfa + state.regs[i].value
			    * state.dataAlign;
			break;
		case Memory:
			addr = cfa + state.regs[i].value * state.dataAlign;

			if ((state.regs[i].value * state.dataAlign)
			    % sizeof(unsigned long)
			    || addr < startLoc
			    || addr + sizeof(unsigned long) < addr
			    || addr + sizeof(unsigned long) > endLoc)
					return -EIO;

			switch (reg_info[i].width) {
			case sizeof(u8):
				__get_user(FRAME_REG(i, u8),
					   (u8 __user *)addr);
				break;
			case sizeof(u16):
				__get_user(FRAME_REG(i, u16),
					   (u16 __user *)addr);
				break;
			case sizeof(u32):
				__get_user(FRAME_REG(i, u32),
					   (u32 __user *)addr);
				break;
#ifdef CONFIG_64BIT
			case sizeof(u64):
				__get_user(FRAME_REG(i, u64),
					   (u64 __user *)addr);
				break;
#endif
			default:
				return -EIO;
			}

			break;
		}
		unw_debug("r%d: 0x%lx ", i, *fptr);
	}

	return 0;
#undef FRAME_REG
}