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 != ¬_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
}