@@ -122,6 +122,13 @@ static struct unwind_table {
} core, init;
const void *address;
unsigned long size;
+ struct cie {
+ unsigned version:8, aug:8, pad:16;
+ uleb128_t codeAlign;
+ sleb128_t dataAlign;
+ int fde_pointer_type;
+ uleb128_t retAddrReg;
+ } cie;
struct eh_frame_header *header;
unsigned long hdrsz;
struct unwind_table *link;
@@ -140,20 +147,18 @@ struct unwind_item {
struct unwind_state {
uleb128_t loc, org;
- const u8 *cieStart, *cieEnd;
uleb128_t codeAlign;
sleb128_t dataAlign;
struct cfa {
uleb128_t reg, offs;
} cfa;
struct unwind_item regs[ARRAY_SIZE(reg_info)];
- unsigned stackDepth:8;
- unsigned version:8;
+ unsigned stackDepth;
const u8 *label;
const u8 *stack[MAX_STACK_DEPTH];
};
-static const struct cfa badCFA = { ARRAY_SIZE(reg_info), 1 };
+static struct cfa seed_CFA = { ARRAY_SIZE(reg_info), 1 };
static struct unwind_table *find_table(unsigned long pc)
{
@@ -214,7 +219,7 @@ void __init arc_unwind_init(void)
static const u32 bad_cie, not_fde;
static const u32 *cie_for_fde(const u32 *fde, const struct unwind_table *);
-static signed fde_pointer_type(const u32 *cie);
+static int cie_validate(const u32 *cie, struct cie *t_cie);
static int cmp_eh_frame_hdr_table_entries(const void *p1, const void *p2)
{
@@ -245,7 +250,10 @@ static void __init setup_unwind_table(struct unwind_table *table,
unsigned long tableSize = table->size, hdrSize;
unsigned n;
const u32 *fde;
+ int ptrType;
struct eh_frame_header *header;
+ int n_cie = 0, len = 0;
+ char cie_orig[64];
if (table->header)
return;
@@ -261,19 +269,26 @@ static void __init setup_unwind_table(struct unwind_table *table,
tableSize > sizeof(*fde) && tableSize - sizeof(*fde) >= *fde;
tableSize -= sizeof(*fde) + *fde, fde += 1 + *fde / sizeof(*fde)) {
const u32 *cie = cie_for_fde(fde, table);
- signed ptrType;
- if (cie == ¬_fde)
+ if (cie == ¬_fde) {
+ if (n_cie++ == 0) {
+ len = cie_validate(&fde[0], &table->cie);
+ if (!len)
+ panic("Invalid CIE\n");
+
+ memcpy(&cie_orig[0], &fde[0], len);
+ } else {
+ if (memcmp(&fde[0], &cie_orig[0], len) != 0)
+ panic("Multiple CIEs not same\n");
+ }
continue;
+ }
if (cie == NULL || cie == &bad_cie)
return;
- ptrType = fde_pointer_type(cie);
- if (ptrType < 0)
- return;
ptr = (const u8 *)(fde + 2);
if (!read_pointer(&ptr, (const u8 *)(fde + 1) + *fde,
- ptrType)) {
+ table->cie.fde_pointer_type)) {
/* FIXME_Rajesh We have 4 instances of null addresses
* instead of the initial loc addr
* return;
@@ -303,19 +318,17 @@ static void __init setup_unwind_table(struct unwind_table *table,
BUILD_BUG_ON(offsetof(typeof(*header), table)
% __alignof(typeof(*header->table)));
+
+ ptrType = table->cie.fde_pointer_type;
for (fde = table->address, tableSize = table->size, n = 0;
tableSize;
tableSize -= sizeof(*fde) + *fde, fde += 1 + *fde / sizeof(*fde)) {
- /* const u32 *cie = fde + 1 - fde[1] / sizeof(*fde); */
- const u32 *cie = (const u32 *)(fde[1]);
-
if (fde[1] == 0xffffffff)
continue; /* this is a CIE */
ptr = (const u8 *)(fde + 2);
header->table[n].start = read_pointer(&ptr,
- (const u8 *)(fde + 1) +
- *fde,
- fde_pointer_type(cie));
+ (const u8 *)(fde + 1) + *fde,
+ ptrType);
header->table[n].fde = (unsigned long)fde;
++n;
}
@@ -572,65 +585,6 @@ static unsigned long read_pointer(const u8 **pLoc, const void *end,
return value;
}
-static signed fde_pointer_type(const u32 *cie)
-{
- const u8 *ptr = (const u8 *)(cie + 2);
- unsigned version = *ptr;
-
- if (version != 1)
- return -1; /* unsupported */
-
- if (*++ptr) {
- const char *aug;
- const u8 *end = (const u8 *)(cie + 1) + *cie;
- uleb128_t len;
-
- /* check if augmentation size is first (and thus present) */
- if (*ptr != 'z')
- return -1;
-
- /* check if augmentation string is nul-terminated */
- aug = (const void *)ptr;
- ptr = memchr(aug, 0, end - ptr);
- if (ptr == NULL)
- return -1;
-
- ++ptr; /* skip terminator */
- get_uleb128(&ptr, end); /* skip code alignment */
- get_sleb128(&ptr, end); /* skip data alignment */
- /* skip return address column */
- version <= 1 ? (void) ++ptr : (void)get_uleb128(&ptr, end);
- len = get_uleb128(&ptr, end); /* augmentation length */
-
- if (ptr + len < ptr || ptr + len > end)
- return -1;
-
- end = ptr + len;
- while (*++aug) {
- if (ptr >= end)
- return -1;
- switch (*aug) {
- case 'L':
- ++ptr;
- break;
- case 'P':{
- signed ptrType = *ptr++;
-
- if (!read_pointer(&ptr, end, ptrType)
- || ptr > end)
- return -1;
- }
- break;
- case 'R':
- return *ptr;
- default:
- return -1;
- }
- }
- }
- return DW_EH_PE_native | DW_EH_PE_abs;
-}
-
static int advance_loc(unsigned long delta, struct unwind_state *state, char *str)
{
state->loc += delta * state->codeAlign;
@@ -682,14 +636,6 @@ static int processCFI(const u8 *start, const u8 *end, unsigned long targetLoc,
int result = 1;
u8 opcode;
- if (start != state->cieStart) {
- state->loc = state->org;
- result =
- processCFI(state->cieStart, state->cieEnd, 0, ptrType,
- state);
- if (targetLoc == 0 && state->label == NULL)
- return result;
- }
for (ptr.p8 = start; result && ptr.p8 < end;) {
char *str = NULL;
switch (*ptr.p8 >> 6) {
@@ -775,7 +721,7 @@ static int processCFI(const u8 *start, const u8 *end, unsigned long targetLoc,
state->label =
state->stack[state->stackDepth - 1];
- memcpy(&state->cfa, &badCFA,
+ memcpy(&state->cfa, &seed_CFA,
sizeof(state->cfa));
memset(state->regs, 0,
sizeof(state->regs));
@@ -857,12 +803,102 @@ static int processCFI(const u8 *start, const u8 *end, unsigned long targetLoc,
targetLoc < state->loc && */ state->label == NULL));
}
+/*
+ * Returns length of CIE (0 if invalid)
+ */
+static int cie_validate(const u32 *cie, struct cie *t_cie)
+{
+ const u8 *ptr = NULL, *end = NULL;
+ u8 version;
+ uleb128_t retAddrReg = 0;
+ int ptrType = DW_EH_PE_native | DW_EH_PE_abs;
+ struct unwind_state state;
+
+ if (cie == NULL)
+ return 0;
+
+ ptr = (const u8 *)(cie + 2);
+ end = (const u8 *)(cie + 1) + *cie;
+ if ((version = *ptr) != 1)
+ return 0; /* unsupported version */
+
+ t_cie->version = version;
+
+ if (*++ptr) {
+ /* check if augmentation size is first (thus present) */
+ if (*ptr == 'z') {
+ t_cie->aug = 1;
+ 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': /* signal frame */
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+ }
+ if (ptr >= end || *ptr)
+ return 0;
+ }
+ ++ptr; /* skip terminator */
+
+ t_cie->codeAlign = get_uleb128(&ptr, end);
+ t_cie->dataAlign = get_sleb128(&ptr, end);
+
+ if (t_cie->codeAlign == 0 || t_cie->dataAlign == 0 || ptr >= end)
+ return 0;
+
+ retAddrReg = version <= 1 ? *ptr++ : get_uleb128(&ptr, end);
+ t_cie->retAddrReg = retAddrReg;
+
+ /* skip augmentation data */
+ if (((const char *)(cie + 2))[1] == 'z') {
+ get_uleb128(&ptr, end); /* augSize */
+
+ if (*ptr++ == 'R') /* FDE pointer encoding type */
+ ptrType = *ptr;
+ }
+ t_cie->fde_pointer_type = ptrType;
+
+ if (ptr > end
+ || retAddrReg >= ARRAY_SIZE(reg_info)
+ || REG_INVALID(retAddrReg)
+ || reg_info[retAddrReg].width != sizeof(unsigned long))
+ return 0;
+
+ unw_debug("\nDwarf Unwinder setup: CIE Info:\n");
+ unw_debug("code Align: %lu\n", t_cie->codeAlign);
+ unw_debug("data Align: %ld\n", t_cie->dataAlign);
+ unw_debug("Return Address register r%d\n", (int)t_cie->retAddrReg);
+ unw_debug("FDE pointer type %d\n", ptrType);
+ unw_debug("CFI Instructions for CIE:\n");
+
+ memset(&state, sizeof(state), 0);
+
+ /* CIE has rules for Default CFA */
+ if (!processCFI(ptr, end, 0, ptrType, &state)
+ || state.cfa.reg >= ARRAY_SIZE(reg_info)
+ || state.cfa.offs % sizeof(unsigned long))
+ return 0;
+
+ memcpy(&seed_CFA, &state.cfa, sizeof(state.cfa));
+
+ return end - (const u8 *)cie;
+}
+
/* Unwind to previous to frame. Returns 0 if successful, negative
* number in case of an error. */
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 u32 *fde = NULL;
const u8 *ptr = NULL, *end = NULL;
unsigned long pc = UNW_PC(frame);
unsigned long startLoc = 0, endLoc = 0, cfa;
@@ -920,107 +956,41 @@ int arc_unwind(struct unwind_frame_info *frame)
else
return -EINVAL;
- 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;
- if ((state.version = *ptr) != 1)
- cie = NULL; /* unsupported version */
- else 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':
- /* signal frame not handled */
- continue;
- default:
- break;
- }
- break;
- }
- }
- if (ptr >= end || *ptr)
- cie = NULL;
- }
- ++ptr;
- }
- if (cie != NULL) {
- /* get code aligment factor */
- state.codeAlign = get_uleb128(&ptr, end);
- /* get data aligment 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;
- }
+ memset(&state, 0, sizeof(state));
+ ptr = (const u8 *)(fde + 2);
+ end = (const u8 *)(fde + 1) + *fde;
+
+ ptrType = table->cie.fde_pointer_type;
+ if (read_pointer(&ptr, end, ptrType) != startLoc)
+ return -EINVAL;
+
+ if (!(ptrType & DW_EH_PE_indirect))
+ ptrType &= DW_EH_PE_FORM | DW_EH_PE_signed;
+
+ endLoc = startLoc + read_pointer(&ptr, end, ptrType);
+
+ /* For symbols not present, this is mostly hit (not startLoc check above) */
+ if (pc >= endLoc) {
+ unw_debug("Unwindo info missing for PC %lx: {%lx,%lx}\n",
+ pc, startLoc, endLoc);
+ return -EINVAL;
}
- 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 (table->cie.aug) {
+ uleb128_t augSize = get_uleb128(&ptr, end);
+
+ if ((ptr += augSize) > end)
+ return -EINVAL;
}
- if (cie == NULL || fde == NULL)
- return -ENXIO;
- state.org = startLoc;
- memcpy(&state.cfa, &badCFA, sizeof(state.cfa));
+ state.org = state.loc = startLoc;
+ memcpy(&state.cfa, &seed_CFA, sizeof(state.cfa));
+ state.codeAlign = table->cie.codeAlign;
+ state.dataAlign = table->cie.dataAlign;
+
+ retAddrReg = table->cie.retAddrReg;
- unw_debug("\nProcess CFA\n");
+ unw_debug("\nProcess FDE:\n");
/* process instructions
* For ARC, we optimize by having blink(retAddrReg) with
There is only 1 CIE per unwind table and applicable to all FDEs, so validate it only once. No need to do validate it when prcoessing the FDE itself. Signed-off-by: Vineet Gupta <vgupta@synopsys.com> --- arch/arc/kernel/unwind.c | 336 +++++++++++++++++++++-------------------------- 1 file changed, 153 insertions(+), 183 deletions(-)