Index: linux/arch/x86_64/lib/unwind.c =================================================================== --- /dev/null +++ linux/arch/x86_64/lib/unwind.c @@ -0,0 +1,671 @@ +/***************************************************************************** + * + * File Name: cdeunwind.c + * Created by: Jan Beulich + * Date created: Fri Jun 28 03:39:45 2002 + * + * %version: 13 % + * %derived_by: jbeulich % + * %date_modified: Wed Jan 19 03:52:04 2005 % + * hacked by AK for mainline inclusion. + * + *****************************************************************************/ +/***************************************************************************** + * * + * Copyright (c) 2002-2005 Novell, Inc. All Rights Reserved. * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of version 2 of the GNU General Public License * + * as published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, contact Novell, Inc. * + * * + * To contact Novell about this file by physical or electronic mail, * + * you may find current contact information at www.novell.com. * + * * + *****************************************************************************/ +/***************************************************************************** + * + * File Description: + * + *****************************************************************************/ + +#include +#include + +#define FRAME_OFFS(f) offsetof(struct pt_regs, f) + +#if defined(__i386__) + +# define SP esp +# define FP ebp +# define FRAME_RETADDR_OFFSET 4 +# define FRAME_LINK_OFFSET 0 +# define STACK_BOTTOM(tsk) (((tsk)->thread.esp0 - 1) & ~(THREAD_SIZE - 1)) +# define STACK_TOP(tsk) ((tsk)->thread.esp0) + +static const size_t regOffsets[] = { + FRAME_OFFS(eax), + FRAME_OFFS(ecx), + FRAME_OFFS(edx), + FRAME_OFFS(ebx), + FRAME_OFFS(esp), + FRAME_OFFS(ebp), + FRAME_OFFS(esi), + FRAME_OFFS(edi), + FRAME_OFFS(eip) +}; + +#elif defined(__x86_64__) + +# define SP rsp +# define FP rbp +# define FRAME_RETADDR_OFFSET 8 +# define FRAME_LINK_OFFSET 0 +# define STACK_BOTTOM(tsk) (((tsk)->thread.rsp0 - 1) & ~(THREAD_SIZE - 1)) +# define STACK_TOP(tsk) ((tsk)->thread.rsp0) + +static const size_t regOffsets[] = { + FRAME_OFFS(rax), + FRAME_OFFS(rdx), + FRAME_OFFS(rcx), + FRAME_OFFS(rbx), + FRAME_OFFS(rsi), + FRAME_OFFS(rdi), + FRAME_OFFS(rbp), + FRAME_OFFS(rsp), + FRAME_OFFS(r8), + FRAME_OFFS(r9), + FRAME_OFFS(r10), + FRAME_OFFS(r11), + FRAME_OFFS(r12), + FRAME_OFFS(r13), + FRAME_OFFS(r14), + FRAME_OFFS(r15), + FRAME_OFFS(rip) +}; + +#else +# error Unsupported target platform. +#endif + +#define MAX_STACK_DEPTH 8 + +#define DW_CFA_nop 0x00 +#define DW_CFA_set_loc 0x01 +#define DW_CFA_advance_loc1 0x02 +#define DW_CFA_advance_loc2 0x03 +#define DW_CFA_advance_loc4 0x04 +#define DW_CFA_offset_extended 0x05 +#define DW_CFA_restore_extended 0x06 +#define DW_CFA_undefined 0x07 +#define DW_CFA_same_value 0x08 +#define DW_CFA_register 0x09 +#define DW_CFA_remember_state 0x0a +#define DW_CFA_restore_state 0x0b +#define DW_CFA_def_cfa 0x0c +#define DW_CFA_def_cfa_register 0x0d +#define DW_CFA_def_cfa_offset 0x0e +#define DW_CFA_def_cfa_expression 0x0f +#define DW_CFA_expression 0x10 +#define DW_CFA_offset_extended_sf 0x11 +#define DW_CFA_def_cfa_sf 0x12 +#define DW_CFA_def_cfa_offset_sf 0x13 +#define DW_CFA_lo_user 0x1c +#define DW_CFA_GNU_window_save 0x2d +#define DW_CFA_GNU_args_size 0x2e +#define DW_CFA_GNU_negative_offset_extended 0x2f +#define DW_CFA_hi_user 0x3f + +#define DW_EH_PE_FORM 0x07 +#define DW_EH_PE_native 0x00 +#define DW_EH_PE_leb128 0x01 +#define DW_EH_PE_data2 0x02 +#define DW_EH_PE_data4 0x03 +#define DW_EH_PE_data8 0x04 +#define DW_EH_PE_signed 0x08 +#define DW_EH_PE_ADJUST 0x70 +#define DW_EH_PE_abs 0x00 +#define DW_EH_PE_pcrel 0x10 +#define DW_EH_PE_textrel 0x20 +#define DW_EH_PE_datarel 0x30 +#define DW_EH_PE_funcrel 0x40 +#define DW_EH_PE_aligned 0x50 +#define DW_EH_PE_indirect 0x80 +#define DW_EH_PE_omit 0xff + +typedef uintptr_t uleb128_t; +typedef intptr_t sleb128_t; + +struct UnwindTableInfo { + uintptr_t address; + uintptr_t size; +}; + +struct UnwindItem { + enum ItemLocation { + nowhere, + memory, + registr, + } where; + uleb128_t value; +}; + +struct UnwindState { + uleb128_t loc, org; + const u8*cieStart, *cieEnd; + uleb128_t codeAlign; + sleb128_t dataAlign; + struct cfa { + uleb128_t reg, offs; + } cfa; + struct UnwindItem regs[ARRAY_SIZE(regOffsets)]; + nuint_t version:8; + nuint_t stackDepth:8 * (sizeof(nuint_t) - 1); + const u8*label; + const u8*stack[MAX_STACK_DEPTH]; +}; + +static const struct cfa badCFA = {ARRAY_SIZE(regOffsets), 1}; + +static uleb128_t getUleb128(const u8**pcur, const u8*end) +{ + const u8*cur = *pcur; + uleb128_t value; + unsigned shift; + + for (shift = 0, value = 0; cur < end; shift += 7) { + if (shift + 7 > 8 * sizeof(value) && (*cur & 0x7fU) >= (1U << (8 * sizeof(value) - shift))) { + cur = end + 1; + break; + } + value |= (uleb128_t)(*cur & 0x7f) << shift; + if (!(*cur++ & 0x80)) + break; + } + *pcur = cur; + return value; +} + +static sleb128_t getSleb128(const u8**pcur, const u8*end) { + const u8*cur = *pcur; + sleb128_t value; + unsigned shift; + + for (shift = 0, value = 0; cur < end; shift += 7) { + if (shift + 7 > 8 * sizeof(value) && (*cur & 0x7fU) >= (1U << (8 * sizeof(value) - shift))) { + cur = end + 1; + break; + } + value |= (sleb128_t)(*cur & 0x7f) << shift; + if (!(*cur & 0x80)) { + value |= -(*cur++ & 0x40) << shift; + break; + } + } + *pcur = cur; + return value; +} + +static uintptr_t readPointer(const u8**pLoc, const u8*end, nint_t ptrType) +{ + uintptr_t value = 0; + const u8*ptr; + + if (ptrType < 0 || ptrType == DW_EH_PE_omit) + return 0; + ptr = *pLoc; + switch(ptrType & DW_EH_PE_FORM) { + case DW_EH_PE_data2: + if (end < ptr + sizeof(uint16_t)) + return 0; + BUILD_BUG_ON(sizeof(uint16_t) <= sizeof(uintptr_t)); + memcpy(&value, ptr, sizeof(uint16_t)); + ptr += sizeof(uint16_t); + break; + case DW_EH_PE_data4: +#ifdef CONFIG_64BIT + if (end < ptr + sizeof(u32)) + return 0; + BUILD_BUG_ON(sizeof(u32) <= sizeof(uintptr_t)); + memcpy(&value, ptr, sizeof(u32)); + ptr += sizeof(u32); + break; + case DW_EH_PE_data8: + BUILD_BUG_ON(sizeof(uint64_t) == sizeof(uintptr_t)); +#else + BUILD_BUG_ON(sizeof(u32) == sizeof(uintptr_t)); +#endif + case DW_EH_PE_native: + if (end < ptr + sizeof(uintptr_t)) + return 0; + memcpy(&value, ptr, sizeof(uintptr_t)); + ptr += sizeof(uintptr_t); + break; + case DW_EH_PE_leb128: + BUILD_BUG_ON(sizeof(uleb128_t) <= sizeof(uintptr_t)); + value = ptrType & DW_EH_PE_signed ? + getSleb128(&ptr, end) : getUleb128(&ptr, end); + if (ptr > end) + return 0; + break; + default: + return 0; + } + if (ptrType & DW_EH_PE_signed) { + switch(ptrType & DW_EH_PE_FORM) { + case DW_EH_PE_data4: +#ifdef CONFIG_64BIT + value = (intptr_t)(int32_t)value; + //nobreak +#endif + case DW_EH_PE_native: + case DW_EH_PE_data8: + case DW_EH_PE_leb128: + break; + case DW_EH_PE_data2: + value = (intptr_t)(int16_t)value; + break; + default: + return 0; + } + } + switch(ptrType & DW_EH_PE_ADJUST) { + case DW_EH_PE_abs: + break; + case DW_EH_PE_pcrel: + value += (uintptr_t)*pLoc; + break; + default: + return 0; + } + if ((ptrType & DW_EH_PE_indirect) && + copy_from_user(&value, value, sizeof(value))) + return 0; + *pLoc = ptr; + return value; +} + +static nint_t fdePointerType(const u32*cie) +{ + const u8 *ptr = (const u8 *)(cie + 2); + nuint_t 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 + if ((ptr = dbgFindByte(aug = (const void*)ptr, 0, end - ptr)) == NULL) + return -1; + ++ptr; // skip terminator + getUleb128(&ptr, end); // skip code alignment + getSleb128(&ptr, end); // skip data alignment + if (version <= 1) + ptr++; + else + getUleb128(&ptr, end); // skip return address column + len = getUleb128(&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': { + nint_t ptrType = *ptr++; + + readPointer(&ptr, end, ptrType); + if (ptr > end) + return -1; + } + break; + case 'R': + return *ptr; + default: + return -1; + } + } + } + return DW_EH_PE_native|DW_EH_PE_abs; +} + +static int advanceLoc(uintptr_t delta, struct UnwindState*state) +{ + state->loc += delta * state->codeAlign; + return delta > 0; +} + +static int setRule(uleb128_t reg, enum ItemLocation where, uleb128_t value, struct UnwindState*state) +{ + if (reg >= ARRAY_SIZE(state->regs)) + return 0; + state->regs[reg].where = where; + state->regs[reg].value = value; + return 1; +} + +static int processCFI(const u8*start, const u8*end, uintptr_t targetLoc, nint_t ptrType, + struct UnwindState*state) +{ + union { + const u8*p8; + const uint16_t*p16; + const u32*p32; + } ptr; + int result = 1; + + 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; + } + else + result = 1; + for (ptr.p8 = start; result && ptr.p8 < end; ) { + switch(*ptr.p8 >> 6) { + uleb128_t value; + + case 0: + switch(*ptr.p8++) { + case DW_CFA_nop: + break; + case DW_CFA_set_loc: + if ((state->loc = readPointer(&ptr.p8, end, ptrType)) == 0) + result = 0; + break; + case DW_CFA_advance_loc1: + result = ptr.p8 < end && advanceLoc(*ptr.p8++, state); + break; + case DW_CFA_advance_loc2: + result = ptr.p8 <= end + 2 && advanceLoc(*ptr.p16++, state); + break; + case DW_CFA_advance_loc4: + result = ptr.p8 <= end + 4 && advanceLoc(*ptr.p32++, state); + break; + case DW_CFA_offset_extended: + value = getUleb128(&ptr.p8, end); + result = setRule(value, memory, getUleb128(&ptr.p8, end), state); + break; + case DW_CFA_restore_extended: + case DW_CFA_undefined: + case DW_CFA_same_value: + result = setRule(getUleb128(&ptr.p8, end), nowhere, 0, state); + break; + case DW_CFA_register: + value = getUleb128(&ptr.p8, end); + result = setRule(value, registr, getUleb128(&ptr.p8, end), state); + break; + case DW_CFA_remember_state: + if (ptr.p8 == state->label) { + state->label = NULL; + return 1; + } + if (state->stackDepth >= MAX_STACK_DEPTH) + return 0; + state->stack[state->stackDepth++] = ptr.p8; + break; + case DW_CFA_restore_state: + if (state->stackDepth == 0) + return 0; + else { + const uleb128_t loc = state->loc; + const u8*label = state->label; + + state->label = state->stack[state->stackDepth - 1]; + memcpy(&state->cfa, &badCFA, sizeof(state->cfa)); + memset(state->regs, 0, sizeof(state->regs)); + state->stackDepth = 0; + result = processCFI(start, end, 0, ptrType, state); + state->loc = loc; + state->label = label; + } + break; + case DW_CFA_def_cfa: + state->cfa.reg = getUleb128(&ptr.p8, end); + state->cfa.offs = getUleb128(&ptr.p8, end); + break; + case DW_CFA_def_cfa_register: + state->cfa.reg = getUleb128(&ptr.p8, end); + break; + case DW_CFA_def_cfa_offset: + state->cfa.offs = getUleb128(&ptr.p8, end); + break; +//todo case DW_CFA_def_cfa_expression: +//todo case DW_CFA_expression: +//todo case DW_CFA_offset_extended_sf: +//todo case DW_CFA_def_cfa_sf: +//todo case DW_CFA_def_cfa_offset_sf: + case DW_CFA_GNU_args_size: + getUleb128(&ptr.p8, end); + break; + case DW_CFA_GNU_negative_offset_extended: + value = getUleb128(&ptr.p8, end); + result = setRule(value, memory, (uintptr_t)0 - getUleb128(&ptr.p8, end), state); + break; + case DW_CFA_GNU_window_save: + default: + result = 0; + break; + } + break; + case 1: + result = advanceLoc(*ptr.p8++ & 0x3f, state); + break; + case 2: + value = *ptr.p8++ & 0x3f; + result = setRule(value, memory, getUleb128(&ptr.p8, end), state); + break; + case 3: + result = setRule(*ptr.p8++ & 0x3f, nowhere, 0, state); + break; + } + if (ptr.p8 > end) + result = 0; + if (result && targetLoc != 0 && targetLoc < state->loc) + return 1; + } + return result + && ptr.p8 == end + && (targetLoc == 0 + || (/*todo While in theory this should apply, gcc in practice omits everything + past the function prolog, and hence the location never reaches the + end of the function. + targetLoc < state->loc &&*/ state->label == NULL)); +} + +int cdeUnwind(struct pt_regs *frame) +{ + const u32*fde = NULL, *cie = NULL; + const u8*ptr = NULL, *end = NULL; + uintptr_t startLoc = 0, endLoc = 0, cfa; + nuint_t i; + nint_t ptrType = -1; + uleb128_t retAddrReg = 0; + struct UnwindTableInfo tableInfo; + struct UnwindState state; + + if (instruction_pointer(frame) == 0) + return 0; + if (findModule(instruction_pointer(frame), &tableInfo) && !(tableInfo.size & (sizeof(*fde) - 1))) + for (fde = (u32*)tableInfo.address; + tableInfo.size > sizeof(*fde) && tableInfo.size >= sizeof(*fde) + *fde; + tableInfo.size -= sizeof(*fde) + *fde, fde += 1 + *fde / sizeof(*fde)) { + if (!*fde || (*fde & (sizeof(*fde) - 1)) || *fde > tableInfo.size - sizeof(*fde)) + break; + if (!fde[1]) + continue; // this is a CIE + if ((fde[1] & (sizeof(*fde) - 1)) || fde[1] > (uintptr_t)(fde + 1) - tableInfo.address) + continue; // this is not a valid FDE + cie = fde + 1 - fde[1] / sizeof(*fde); + if (*cie <= sizeof(*cie) + 4 + || *cie >= fde[1] - sizeof(*fde) + || (*cie & (sizeof(*cie) - 1)) + || cie[1] + || (ptrType = fdePointerType(cie)) < 0) { + cie = NULL; // this is not a (valid) CIE + continue; + } + ptr = (const u8*)(fde + 2); + if (instruction_pointer(frame) >= (startLoc = readPointer(&ptr, (const u8*)(fde + 1) + *fde, ptrType)) + && instruction_pointer(frame) < (endLoc = startLoc + readPointer(&ptr, + (const u8*)(fde + 1) + *fde, + ptrType & DW_EH_PE_indirect + ? ptrType + : ptrType & (DW_EH_PE_FORM|DW_EH_PE_signed)))) + break; + 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 (and thus present) + if (*ptr == 'z') { + // check for ignorable (or already handled) nul-terminated augmentation string + while(++ptr < end && *ptr) + if (dbgFindChar("LPR", *ptr) == NULL) + break; + } + if (ptr >= end || *ptr) + cie = NULL; + } + ++ptr; + } + if (cie != NULL) { + // get code aligment factor + state.codeAlign = getUleb128(&ptr, end); + // get data aligment factor + state.dataAlign = getSleb128(&ptr, end); + if (state.codeAlign == 0 || state.dataAlign == 0 || ptr >= end) + cie = NULL; + else { + retAddrReg = state.version <= 1 ? *ptr++ : getUleb128(&ptr, end); + // skip augmentation + if (((const char*)(cie + 2))[1] == 'z') + ptr += getUleb128(&ptr, end); + if (ptr > end + || retAddrReg >= ARRAY_SIZE(regOffsets) + || regOffsets[retAddrReg] == 0) + 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 = getUleb128(&ptr, end); + + if ((ptr += augSize) > end) + fde = NULL; + } + } + if (cie == NULL || fde == NULL) { + /* RED-PEN needs to handle exception stacks */ + +#ifdef FP + struct task_struct*task = current; + uintptr_t top = STACK_TOP(task); + uintptr_t bottom = STACK_BOTTOM(task); + + if (frame->SP > top && frame->FP >= frame->SP && bottom > frame->FP + && !((frame->SP | frame->FP) & (sizeof(uintptr_t) - 1))) { + uintptr_t link; + + if (copy_from_user(&link, frame->FP + FRAME_LINK_OFFSET, sizeof(link)) > 0 + && link > frame->FP && link < bottom + && !(link & (sizeof(link) - 1)) + && cdeReadMemoryLin(frame->FP + FRAME_RETADDR_OFFSET, sizeof(instruction_pointer(frame)), &frame->PC, NULL)) { + frame->SP = frame->FP + FRAME_RETADDR_OFFSET + + sizeof(instruction_pointer(frame)); + frame->FP = link; + return 1; + } + } +#endif + return 0; + } + state.org = startLoc; + memcpy(&state.cfa, &badCFA, sizeof(state.cfa)); + // process instructions + if (!processCFI(ptr, end, instruction_pointer(frame), ptrType, &state) + || state.loc > endLoc + || state.regs[retAddrReg].where == nowhere + || state.cfa.reg >= ARRAY_SIZE(state.regs) + || state.cfa.offs % sizeof(uintptr_t)) + return 0; + // update frame + cfa = ((uintptr_t*)frame)[regOffsets[state.cfa.reg]] + state.cfa.offs; + startLoc = min((uintptr_t)frame->SP, cfa); + endLoc = max((uintptr_t)frame->SP, cfa); + for (i = 0; i < ARRAY_SIZE(state.regs); ++i) { + switch(state.regs[i].where) { + default: + break; + case registr: + if (regOffsets[i] == 0) + break; + if (state.regs[i].value >= ARRAY_SIZE(regOffsets) + || regOffsets[state.regs[i].value] == 0 + || state.regs[state.regs[i].value].where != nowhere) + return 0; + state.regs[i].value = ((const uintptr_t*)frame)[regOffsets[state.regs[i].value]]; + break; + } + } + for (i = 0; i < ARRAY_SIZE(state.regs); ++i) { + if (regOffsets[i] == 0) + continue; + switch(state.regs[i].where) { + uintptr_t addr; + + case nowhere: + if ((uintptr_t*)frame + regOffsets[i] != &frame->SP) + continue; + frame->SP = cfa; + break; + case registr: + ((uintptr_t*)frame)[regOffsets[i]] = state.regs[i].value; + break; + case memory: + addr = cfa + state.regs[i].value * state.dataAlign; + if ((state.regs[i].value * state.dataAlign) % sizeof(uintptr_t) + || addr < startLoc + || addr + sizeof(uintptr_t) < addr + || addr + sizeof(uintptr_t) > endLoc) + return 0; + ((uintptr_t*)frame)[regOffsets[i]] = *(uintptr_t*)addr; + break; + } + } + return 1; +} + +void __init cdeUnwindInitDone(void) +{ + //todo remove all __init-only FDEs +}