michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: michael@0: /* libunwind - a platform-independent unwind library michael@0: Copyright 2011 Linaro Limited michael@0: michael@0: This file is part of libunwind. michael@0: michael@0: Permission is hereby granted, free of charge, to any person obtaining michael@0: a copy of this software and associated documentation files (the michael@0: "Software"), to deal in the Software without restriction, including michael@0: without limitation the rights to use, copy, modify, merge, publish, michael@0: distribute, sublicense, and/or sell copies of the Software, and to michael@0: permit persons to whom the Software is furnished to do so, subject to michael@0: the following conditions: michael@0: michael@0: The above copyright notice and this permission notice shall be michael@0: included in all copies or substantial portions of the Software. michael@0: michael@0: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, michael@0: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF michael@0: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND michael@0: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE michael@0: LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION michael@0: OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION michael@0: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ michael@0: michael@0: michael@0: // Copyright (c) 2010 Google Inc. michael@0: // All rights reserved. michael@0: // michael@0: // Redistribution and use in source and binary forms, with or without michael@0: // modification, are permitted provided that the following conditions are michael@0: // met: michael@0: // michael@0: // * Redistributions of source code must retain the above copyright michael@0: // notice, this list of conditions and the following disclaimer. michael@0: // * Redistributions in binary form must reproduce the above michael@0: // copyright notice, this list of conditions and the following disclaimer michael@0: // in the documentation and/or other materials provided with the michael@0: // distribution. michael@0: // * Neither the name of Google Inc. nor the names of its michael@0: // contributors may be used to endorse or promote products derived from michael@0: // this software without specific prior written permission. michael@0: // michael@0: // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: michael@0: michael@0: // Derived from libunwind, with extensive modifications. michael@0: michael@0: // This file translates EXIDX unwind information into the same format michael@0: // that LUL uses for CFI information. Hence LUL's CFI unwinding michael@0: // abilities also become usable for EXIDX. michael@0: // michael@0: // See: "Exception Handling ABI for the ARM Architecture", ARM IHI 0038A michael@0: // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038a/IHI0038A_ehabi.pdf michael@0: michael@0: // EXIDX data is presented in two parts: michael@0: // michael@0: // * an index table. This contains two words per routine, michael@0: // the first of which identifies the routine, and the second michael@0: // of which is a reference to the unwind bytecode. If the michael@0: // bytecode is very compact -- 3 bytes or less -- it can be michael@0: // stored directly in the second word. michael@0: // michael@0: // * an area containing the unwind bytecodes. michael@0: // michael@0: // General flow is: ExceptionTableInfo::Start iterates over all michael@0: // of the index table entries (pairs). For each entry, it: michael@0: // michael@0: // * calls ExceptionTableInfo::ExtabEntryExtract to copy the bytecode michael@0: // out into an intermediate buffer. michael@0: michael@0: // * uses ExceptionTableInfo::ExtabEntryDecode to parse the intermediate michael@0: // buffer. Each bytecode instruction is bundled into a michael@0: // arm_ex_to_module::extab_data structure, and handed to .. michael@0: // michael@0: // * .. ARMExToModule::ImproveStackFrame, which in turn hands it to michael@0: // ARMExToModule::TranslateCmd, and that generates the pseudo-CFI michael@0: // records that Breakpad stores. michael@0: michael@0: // This file is derived from the following files in michael@0: // toolkit/crashreporter/google-breakpad: michael@0: // src/common/arm_ex_to_module.cc michael@0: // src/common/arm_ex_reader.cc michael@0: michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/NullPtr.h" michael@0: michael@0: #include "LulExidxExt.h" michael@0: michael@0: michael@0: #define ARM_EXBUF_START(x) (((x) >> 4) & 0x0f) michael@0: #define ARM_EXBUF_COUNT(x) ((x) & 0x0f) michael@0: #define ARM_EXBUF_END(x) (ARM_EXBUF_START(x) + ARM_EXBUF_COUNT(x)) michael@0: michael@0: namespace lul { michael@0: michael@0: // Translate command from extab_data to command for Module. michael@0: int ARMExToModule::TranslateCmd(const struct extab_data* edata, michael@0: LExpr& vsp) { michael@0: int ret = 0; michael@0: switch (edata->cmd) { michael@0: case ARM_EXIDX_CMD_FINISH: michael@0: /* Copy LR to PC if there isn't currently a rule for PC in force. */ michael@0: if (curr_rules_.mR15expr.mHow == LExpr::UNKNOWN) { michael@0: if (curr_rules_.mR14expr.mHow == LExpr::UNKNOWN) { michael@0: curr_rules_.mR15expr = LExpr(LExpr::NODEREF, DW_REG_ARM_R14, 0); michael@0: } else { michael@0: curr_rules_.mR15expr = curr_rules_.mR14expr; michael@0: } michael@0: } michael@0: break; michael@0: case ARM_EXIDX_CMD_SUB_FROM_VSP: michael@0: vsp = vsp.add_delta(- static_cast(edata->data)); michael@0: break; michael@0: case ARM_EXIDX_CMD_ADD_TO_VSP: michael@0: vsp = vsp.add_delta(static_cast(edata->data)); michael@0: break; michael@0: case ARM_EXIDX_CMD_REG_POP: michael@0: for (unsigned int i = 0; i < 16; i++) { michael@0: if (edata->data & (1 << i)) { michael@0: // See if we're summarising for int register |i|. If so, michael@0: // describe how to pull it off the stack. The cast of |i| is michael@0: // a bit of a kludge but works because DW_REG_ARM_Rn has the michael@0: // value |n|, for 0 <= |n| <= 15 -- that is, for the ARM michael@0: // general-purpose registers. michael@0: LExpr* regI_exprP = curr_rules_.ExprForRegno((DW_REG_NUMBER)i); michael@0: if (regI_exprP) { michael@0: *regI_exprP = vsp.deref(); michael@0: } michael@0: vsp = vsp.add_delta(4); michael@0: } michael@0: } michael@0: /* Set cfa in case the SP got popped. */ michael@0: if (edata->data & (1 << 13)) { michael@0: vsp = curr_rules_.mR13expr; michael@0: } michael@0: break; michael@0: case ARM_EXIDX_CMD_REG_TO_SP: { michael@0: MOZ_ASSERT (edata->data < 16); michael@0: int reg_no = edata->data; michael@0: // Same comment as above, re the casting of |reg_no|, applies. michael@0: LExpr* reg_exprP = curr_rules_.ExprForRegno((DW_REG_NUMBER)reg_no); michael@0: if (reg_exprP) { michael@0: if (reg_exprP->mHow == LExpr::UNKNOWN) { michael@0: curr_rules_.mR13expr = LExpr(LExpr::NODEREF, reg_no, 0); michael@0: } else { michael@0: curr_rules_.mR13expr = *reg_exprP; michael@0: } michael@0: vsp = curr_rules_.mR13expr; michael@0: } michael@0: break; michael@0: } michael@0: case ARM_EXIDX_CMD_VFP_POP: michael@0: /* Don't recover VFP registers, but be sure to adjust the stack michael@0: pointer. */ michael@0: for (unsigned int i = ARM_EXBUF_START(edata->data); michael@0: i <= ARM_EXBUF_END(edata->data); i++) { michael@0: vsp = vsp.add_delta(8); michael@0: } michael@0: if (!(edata->data & ARM_EXIDX_VFP_FSTMD)) { michael@0: vsp = vsp.add_delta(4); michael@0: } michael@0: break; michael@0: case ARM_EXIDX_CMD_WREG_POP: michael@0: for (unsigned int i = ARM_EXBUF_START(edata->data); michael@0: i <= ARM_EXBUF_END(edata->data); i++) { michael@0: vsp = vsp.add_delta(8); michael@0: } michael@0: break; michael@0: case ARM_EXIDX_CMD_WCGR_POP: michael@0: // Pop wCGR registers under mask {wCGR3,2,1,0}, hence "i < 4" michael@0: for (unsigned int i = 0; i < 4; i++) { michael@0: if (edata->data & (1 << i)) { michael@0: vsp = vsp.add_delta(4); michael@0: } michael@0: } michael@0: break; michael@0: case ARM_EXIDX_CMD_REFUSED: michael@0: case ARM_EXIDX_CMD_RESERVED: michael@0: ret = -1; michael@0: break; michael@0: } michael@0: return ret; michael@0: } michael@0: michael@0: void ARMExToModule::AddStackFrame(uintptr_t addr, size_t size) { michael@0: // Here we are effectively reinitialising the EXIDX summariser for a michael@0: // new code address range. smap_ stays unchanged. All other fields michael@0: // are reinitialised. michael@0: vsp_ = LExpr(LExpr::NODEREF, DW_REG_ARM_R13, 0); michael@0: (void) new (&curr_rules_) RuleSet(); michael@0: curr_rules_.mAddr = (uintptr_t)addr; michael@0: curr_rules_.mLen = (uintptr_t)size; michael@0: if (0) { michael@0: char buf[100]; michael@0: sprintf(buf, " AddStackFrame %llx .. %llx", michael@0: (uint64_t)addr, (uint64_t)(addr + size - 1)); michael@0: log_(buf); michael@0: } michael@0: } michael@0: michael@0: int ARMExToModule::ImproveStackFrame(const struct extab_data* edata) { michael@0: return TranslateCmd(edata, vsp_) ; michael@0: } michael@0: michael@0: void ARMExToModule::DeleteStackFrame() { michael@0: } michael@0: michael@0: void ARMExToModule::SubmitStackFrame() { michael@0: // JRS: I'm really not sure what this means, or if it is necessary michael@0: // return address always winds up in pc michael@0: //stack_frame_entry_->initial_rules[ustr__ZDra()] // ".ra" michael@0: // = stack_frame_entry_->initial_rules[ustr__pc()]; michael@0: // maybe don't need to do anything here? michael@0: michael@0: // the final value of vsp is the new value of sp michael@0: curr_rules_.mR13expr = vsp_; michael@0: michael@0: // Finally, add the completed RuleSet to the SecMap michael@0: if (curr_rules_.mLen > 0) { michael@0: michael@0: // Futz with the rules for r4 .. r11 in the same way as happens michael@0: // with the CFI summariser: michael@0: /* Mark callee-saved registers (r4 .. r11) as unchanged, if there is michael@0: no other information about them. FIXME: do this just once, at michael@0: the point where the ruleset is committed. */ michael@0: if (curr_rules_.mR7expr.mHow == LExpr::UNKNOWN) { michael@0: curr_rules_.mR7expr = LExpr(LExpr::NODEREF, DW_REG_ARM_R7, 0); michael@0: } michael@0: if (curr_rules_.mR11expr.mHow == LExpr::UNKNOWN) { michael@0: curr_rules_.mR11expr = LExpr(LExpr::NODEREF, DW_REG_ARM_R11, 0); michael@0: } michael@0: if (curr_rules_.mR12expr.mHow == LExpr::UNKNOWN) { michael@0: curr_rules_.mR12expr = LExpr(LExpr::NODEREF, DW_REG_ARM_R12, 0); michael@0: } michael@0: if (curr_rules_.mR14expr.mHow == LExpr::UNKNOWN) { michael@0: curr_rules_.mR14expr = LExpr(LExpr::NODEREF, DW_REG_ARM_R14, 0); michael@0: } michael@0: michael@0: // And add them michael@0: smap_->AddRuleSet(&curr_rules_); michael@0: michael@0: if (0) { michael@0: curr_rules_.Print(log_); michael@0: } michael@0: if (0) { michael@0: char buf[100]; michael@0: sprintf(buf, " SubmitStackFrame %llx .. %llx", michael@0: (uint64_t)curr_rules_.mAddr, michael@0: (uint64_t)(curr_rules_.mAddr + curr_rules_.mLen - 1)); michael@0: log_(buf); michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: #define ARM_EXIDX_CANT_UNWIND 0x00000001 michael@0: #define ARM_EXIDX_COMPACT 0x80000000 michael@0: #define ARM_EXTBL_OP_FINISH 0xb0 michael@0: #define ARM_EXIDX_TABLE_LIMIT (255*4) michael@0: michael@0: using lul::ARM_EXIDX_CMD_FINISH; michael@0: using lul::ARM_EXIDX_CMD_SUB_FROM_VSP; michael@0: using lul::ARM_EXIDX_CMD_ADD_TO_VSP; michael@0: using lul::ARM_EXIDX_CMD_REG_POP; michael@0: using lul::ARM_EXIDX_CMD_REG_TO_SP; michael@0: using lul::ARM_EXIDX_CMD_VFP_POP; michael@0: using lul::ARM_EXIDX_CMD_WREG_POP; michael@0: using lul::ARM_EXIDX_CMD_WCGR_POP; michael@0: using lul::ARM_EXIDX_CMD_RESERVED; michael@0: using lul::ARM_EXIDX_CMD_REFUSED; michael@0: using lul::exidx_entry; michael@0: using lul::ARM_EXIDX_VFP_SHIFT_16; michael@0: using lul::ARM_EXIDX_VFP_FSTMD; michael@0: using lul::MemoryRange; michael@0: michael@0: michael@0: static void* Prel31ToAddr(const void* addr) michael@0: { michael@0: uint32_t offset32 = *reinterpret_cast(addr); michael@0: // sign extend offset32[30:0] to 64 bits -- copy bit 30 to positions michael@0: // 63:31 inclusive. michael@0: uint64_t offset64 = offset32; michael@0: if (offset64 & (1ULL << 30)) michael@0: offset64 |= 0xFFFFFFFF80000000ULL; michael@0: else michael@0: offset64 &= 0x000000007FFFFFFFULL; michael@0: return ((char*)addr) + (uintptr_t)offset64; michael@0: } michael@0: michael@0: michael@0: // Extract unwind bytecode for the function denoted by |entry| into |buf|, michael@0: // and return the number of bytes of |buf| written, along with a code michael@0: // indicating the outcome. michael@0: michael@0: ExceptionTableInfo::ExExtractResult michael@0: ExceptionTableInfo::ExtabEntryExtract(const struct exidx_entry* entry, michael@0: uint8_t* buf, size_t buf_size, michael@0: /*OUT*/size_t* buf_used) michael@0: { michael@0: MemoryRange mr_out(buf, buf_size); michael@0: michael@0: *buf_used = 0; michael@0: michael@0: # define PUT_BUF_U8(_byte) \ michael@0: do { if (!mr_out.Covers(*buf_used, 1)) return ExOutBufOverflow; \ michael@0: buf[(*buf_used)++] = (_byte); } while (0) michael@0: michael@0: # define GET_EX_U32(_lval, _addr, _sec_mr) \ michael@0: do { if (!(_sec_mr).Covers(reinterpret_cast(_addr) \ michael@0: - (_sec_mr).data(), 4)) \ michael@0: return ExInBufOverflow; \ michael@0: (_lval) = *(reinterpret_cast(_addr)); } while (0) michael@0: michael@0: # define GET_EXIDX_U32(_lval, _addr) \ michael@0: GET_EX_U32(_lval, _addr, mr_exidx_) michael@0: # define GET_EXTAB_U32(_lval, _addr) \ michael@0: GET_EX_U32(_lval, _addr, mr_extab_) michael@0: michael@0: uint32_t data; michael@0: GET_EXIDX_U32(data, &entry->data); michael@0: michael@0: // A function can be marked CANT_UNWIND if (eg) it is known to be michael@0: // at the bottom of the stack. michael@0: if (data == ARM_EXIDX_CANT_UNWIND) michael@0: return ExCantUnwind; michael@0: michael@0: uint32_t pers; // personality number michael@0: uint32_t extra; // number of extra data words required michael@0: uint32_t extra_allowed; // number of extra data words allowed michael@0: uint32_t* extbl_data; // the handler entry, if not inlined michael@0: michael@0: if (data & ARM_EXIDX_COMPACT) { michael@0: // The handler table entry has been inlined into the index table entry. michael@0: // In this case it can only be an ARM-defined compact model, since michael@0: // bit 31 is 1. Only personalities 0, 1 and 2 are defined for the michael@0: // ARM compact model, but 1 and 2 are "Long format" and may require michael@0: // extra data words. Hence the allowable personalities here are: michael@0: // personality 0, in which case 'extra' has no meaning michael@0: // personality 1, with zero extra words michael@0: // personality 2, with zero extra words michael@0: extbl_data = nullptr; michael@0: pers = (data >> 24) & 0x0F; michael@0: extra = (data >> 16) & 0xFF; michael@0: extra_allowed = 0; michael@0: } michael@0: else { michael@0: // The index table entry is a pointer to the handler entry. Note michael@0: // that Prel31ToAddr will read the given address, but we already michael@0: // range-checked above. michael@0: extbl_data = reinterpret_cast(Prel31ToAddr(&entry->data)); michael@0: GET_EXTAB_U32(data, extbl_data); michael@0: if (!(data & ARM_EXIDX_COMPACT)) { michael@0: // This denotes a "generic model" handler. That will involve michael@0: // executing arbitary machine code, which is something we michael@0: // can't represent here; hence reject it. michael@0: return ExCantRepresent; michael@0: } michael@0: // So we have a compact model representation. Again, 3 possible michael@0: // personalities, but this time up to 255 allowable extra words. michael@0: pers = (data >> 24) & 0x0F; michael@0: extra = (data >> 16) & 0xFF; michael@0: extra_allowed = 255; michael@0: extbl_data++; michael@0: } michael@0: michael@0: // Now look at the the handler table entry. The first word is michael@0: // |data| and subsequent words start at |*extbl_data|. The number michael@0: // of extra words to use is |extra|, provided that the personality michael@0: // allows extra words. Even if it does, none may be available -- michael@0: // extra_allowed is the maximum number of extra words allowed. */ michael@0: if (pers == 0) { michael@0: // "Su16" in the documentation -- 3 unwinding insn bytes michael@0: // |extra| has no meaning here; instead that byte is an unwind-info byte michael@0: PUT_BUF_U8(data >> 16); michael@0: PUT_BUF_U8(data >> 8); michael@0: PUT_BUF_U8(data); michael@0: } michael@0: else if ((pers == 1 || pers == 2) && extra <= extra_allowed) { michael@0: // "Lu16" or "Lu32" respectively -- 2 unwinding insn bytes, michael@0: // and up to 255 extra words. michael@0: PUT_BUF_U8(data >> 8); michael@0: PUT_BUF_U8(data); michael@0: for (uint32_t j = 0; j < extra; j++) { michael@0: GET_EXTAB_U32(data, extbl_data); michael@0: extbl_data++; michael@0: PUT_BUF_U8(data >> 24); michael@0: PUT_BUF_U8(data >> 16); michael@0: PUT_BUF_U8(data >> 8); michael@0: PUT_BUF_U8(data >> 0); michael@0: } michael@0: } michael@0: else { michael@0: // The entry is invalid. michael@0: return ExInvalid; michael@0: } michael@0: michael@0: // Make sure the entry is terminated with "FINISH" michael@0: if (*buf_used > 0 && buf[(*buf_used) - 1] != ARM_EXTBL_OP_FINISH) michael@0: PUT_BUF_U8(ARM_EXTBL_OP_FINISH); michael@0: michael@0: return ExSuccess; michael@0: michael@0: # undef GET_EXTAB_U32 michael@0: # undef GET_EXIDX_U32 michael@0: # undef GET_U32 michael@0: # undef PUT_BUF_U8 michael@0: } michael@0: michael@0: michael@0: // Take the unwind information extracted by ExtabEntryExtract michael@0: // and parse it into frame-unwind instructions. These are as michael@0: // specified in "Table 4, ARM-defined frame-unwinding instructions" michael@0: // in the specification document detailed in comments at the top michael@0: // of this file. michael@0: // michael@0: // This reads from |buf[0, +data_size)|. It checks for overruns of michael@0: // the input buffer and returns a negative value if that happens, or michael@0: // for any other failure cases. It returns zero in case of success. michael@0: int ExceptionTableInfo::ExtabEntryDecode(const uint8_t* buf, size_t buf_size) michael@0: { michael@0: if (buf == nullptr || buf_size == 0) michael@0: return -1; michael@0: michael@0: MemoryRange mr_in(buf, buf_size); michael@0: const uint8_t* buf_initially = buf; michael@0: michael@0: # define GET_BUF_U8(_lval) \ michael@0: do { if (!mr_in.Covers(buf - buf_initially, 1)) return -1; \ michael@0: (_lval) = *(buf++); } while (0) michael@0: michael@0: const uint8_t* end = buf + buf_size; michael@0: michael@0: while (buf < end) { michael@0: struct lul::extab_data edata; michael@0: memset(&edata, 0, sizeof(edata)); michael@0: michael@0: uint8_t op; michael@0: GET_BUF_U8(op); michael@0: if ((op & 0xc0) == 0x00) { michael@0: // vsp = vsp + (xxxxxx << 2) + 4 michael@0: edata.cmd = ARM_EXIDX_CMD_ADD_TO_VSP; michael@0: edata.data = (((int)op & 0x3f) << 2) + 4; michael@0: } michael@0: else if ((op & 0xc0) == 0x40) { michael@0: // vsp = vsp - (xxxxxx << 2) - 4 michael@0: edata.cmd = ARM_EXIDX_CMD_SUB_FROM_VSP; michael@0: edata.data = (((int)op & 0x3f) << 2) + 4; michael@0: } michael@0: else if ((op & 0xf0) == 0x80) { michael@0: uint8_t op2; michael@0: GET_BUF_U8(op2); michael@0: if (op == 0x80 && op2 == 0x00) { michael@0: // Refuse to unwind michael@0: edata.cmd = ARM_EXIDX_CMD_REFUSED; michael@0: } else { michael@0: // Pop up to 12 integer registers under masks {r15-r12},{r11-r4} michael@0: edata.cmd = ARM_EXIDX_CMD_REG_POP; michael@0: edata.data = ((op & 0xf) << 8) | op2; michael@0: edata.data = edata.data << 4; michael@0: } michael@0: } michael@0: else if ((op & 0xf0) == 0x90) { michael@0: if (op == 0x9d || op == 0x9f) { michael@0: // 9d: Reserved as prefix for ARM register to register moves michael@0: // 9f: Reserved as perfix for Intel Wireless MMX reg to reg moves michael@0: edata.cmd = ARM_EXIDX_CMD_RESERVED; michael@0: } else { michael@0: // Set vsp = r[nnnn] michael@0: edata.cmd = ARM_EXIDX_CMD_REG_TO_SP; michael@0: edata.data = op & 0x0f; michael@0: } michael@0: } michael@0: else if ((op & 0xf0) == 0xa0) { michael@0: // Pop r4 to r[4+nnn], or michael@0: // Pop r4 to r[4+nnn] and r14 or michael@0: unsigned end = (op & 0x07); michael@0: edata.data = (1 << (end + 1)) - 1; michael@0: edata.data = edata.data << 4; michael@0: if (op & 0x08) edata.data |= 1 << 14; michael@0: edata.cmd = ARM_EXIDX_CMD_REG_POP; michael@0: } michael@0: else if (op == ARM_EXTBL_OP_FINISH) { michael@0: // Finish michael@0: edata.cmd = ARM_EXIDX_CMD_FINISH; michael@0: buf = end; michael@0: } michael@0: else if (op == 0xb1) { michael@0: uint8_t op2; michael@0: GET_BUF_U8(op2); michael@0: if (op2 == 0 || (op2 & 0xf0)) { michael@0: // Spare michael@0: edata.cmd = ARM_EXIDX_CMD_RESERVED; michael@0: } else { michael@0: // Pop integer registers under mask {r3,r2,r1,r0} michael@0: edata.cmd = ARM_EXIDX_CMD_REG_POP; michael@0: edata.data = op2 & 0x0f; michael@0: } michael@0: } michael@0: else if (op == 0xb2) { michael@0: // vsp = vsp + 0x204 + (uleb128 << 2) michael@0: uint64_t offset = 0; michael@0: uint8_t byte, shift = 0; michael@0: do { michael@0: GET_BUF_U8(byte); michael@0: offset |= (byte & 0x7f) << shift; michael@0: shift += 7; michael@0: } while ((byte & 0x80) && buf < end); michael@0: edata.data = offset * 4 + 0x204; michael@0: edata.cmd = ARM_EXIDX_CMD_ADD_TO_VSP; michael@0: } michael@0: else if (op == 0xb3 || op == 0xc8 || op == 0xc9) { michael@0: // b3: Pop VFP regs D[ssss] to D[ssss+cccc], FSTMFDX-ishly michael@0: // c8: Pop VFP regs D[16+ssss] to D[16+ssss+cccc], FSTMFDD-ishly michael@0: // c9: Pop VFP regs D[ssss] to D[ssss+cccc], FSTMFDD-ishly michael@0: edata.cmd = ARM_EXIDX_CMD_VFP_POP; michael@0: GET_BUF_U8(edata.data); michael@0: if (op == 0xc8) edata.data |= ARM_EXIDX_VFP_SHIFT_16; michael@0: if (op != 0xb3) edata.data |= ARM_EXIDX_VFP_FSTMD; michael@0: } michael@0: else if ((op & 0xf8) == 0xb8 || (op & 0xf8) == 0xd0) { michael@0: // b8: Pop VFP regs D[8] to D[8+nnn], FSTMFDX-ishly michael@0: // d0: Pop VFP regs D[8] to D[8+nnn], FSTMFDD-ishly michael@0: edata.cmd = ARM_EXIDX_CMD_VFP_POP; michael@0: edata.data = 0x80 | (op & 0x07); michael@0: if ((op & 0xf8) == 0xd0) edata.data |= ARM_EXIDX_VFP_FSTMD; michael@0: } michael@0: else if (op >= 0xc0 && op <= 0xc5) { michael@0: // Intel Wireless MMX pop wR[10]-wr[10+nnn], nnn != 6,7 michael@0: edata.cmd = ARM_EXIDX_CMD_WREG_POP; michael@0: edata.data = 0xa0 | (op & 0x07); michael@0: } michael@0: else if (op == 0xc6) { michael@0: // Intel Wireless MMX pop wR[ssss] to wR[ssss+cccc] michael@0: edata.cmd = ARM_EXIDX_CMD_WREG_POP; michael@0: GET_BUF_U8(edata.data); michael@0: } michael@0: else if (op == 0xc7) { michael@0: uint8_t op2; michael@0: GET_BUF_U8(op2); michael@0: if (op2 == 0 || (op2 & 0xf0)) { michael@0: // Spare michael@0: edata.cmd = ARM_EXIDX_CMD_RESERVED; michael@0: } else { michael@0: // Intel Wireless MMX pop wCGR registers under mask {wCGR3,2,1,0} michael@0: edata.cmd = ARM_EXIDX_CMD_WCGR_POP; michael@0: edata.data = op2 & 0x0f; michael@0: } michael@0: } michael@0: else { michael@0: // Spare michael@0: edata.cmd = ARM_EXIDX_CMD_RESERVED; michael@0: } michael@0: michael@0: int ret = handler_->ImproveStackFrame(&edata); michael@0: if (ret < 0) return ret; michael@0: } michael@0: return 0; michael@0: michael@0: # undef GET_BUF_U8 michael@0: } michael@0: michael@0: void ExceptionTableInfo::Start() michael@0: { michael@0: const struct exidx_entry* start michael@0: = reinterpret_cast(mr_exidx_.data()); michael@0: const struct exidx_entry* end michael@0: = reinterpret_cast(mr_exidx_.data() michael@0: + mr_exidx_.length()); michael@0: michael@0: // Iterate over each of the EXIDX entries (pairs of 32-bit words). michael@0: // These occupy the entire .exidx section. michael@0: for (const struct exidx_entry* entry = start; entry < end; ++entry) { michael@0: michael@0: // Figure out the code address range that this table entry is michael@0: // associated with. michael@0: // michael@0: // I don't claim to understand the biasing here. It appears that michael@0: // (Prel31ToAddr(&entry->addr)) michael@0: // - mapping_addr_ + loading_addr_) & 0x7fffffff michael@0: // produces a SVMA. Adding the text_bias_ gives plausible AVMAs. michael@0: uint32_t svma = (reinterpret_cast(Prel31ToAddr(&entry->addr)) michael@0: - mapping_addr_ + loading_addr_) & 0x7fffffff; michael@0: uint32_t next_svma; michael@0: if (entry < end - 1) { michael@0: next_svma = (reinterpret_cast(Prel31ToAddr(&((entry + 1)->addr))) michael@0: - mapping_addr_ + loading_addr_) & 0x7fffffff; michael@0: } else { michael@0: // This is the last EXIDX entry in the sequence, so we don't michael@0: // have an address for the start of the next function, to limit michael@0: // this one. Instead use the address of the last byte of the michael@0: // text section associated with this .exidx section, that we michael@0: // have been given. So as to avoid junking up the CFI unwind michael@0: // tables with absurdly large address ranges in the case where michael@0: // text_last_svma_ is wrong, only use the value if it is nonzero michael@0: // and within one page of |svma|. Otherwise assume a length of 1. michael@0: // michael@0: // In some cases, gcc has been observed to finish the exidx michael@0: // section with an entry of length 1 marked CANT_UNWIND, michael@0: // presumably exactly for the purpose of giving a definite michael@0: // length for the last real entry, without having to look at michael@0: // text segment boundaries. michael@0: bool plausible = false; michael@0: next_svma = svma + 1; michael@0: if (text_last_svma_ != 0) { michael@0: uint32_t maybe_next_svma = text_last_svma_ + 1; michael@0: if (maybe_next_svma > svma && maybe_next_svma - svma <= 4096) { michael@0: next_svma = maybe_next_svma; michael@0: plausible = true; michael@0: } michael@0: } michael@0: if (!plausible) { michael@0: char buf[100]; michael@0: snprintf(buf, sizeof(buf), michael@0: "ExceptionTableInfo: implausible EXIDX last entry size %d" michael@0: "; using 1 instead.", (int32_t)(text_last_svma_ - svma)); michael@0: buf[sizeof(buf)-1] = 0; michael@0: log_(buf); michael@0: } michael@0: } michael@0: michael@0: // Extract the unwind info into |buf|. This might fail for michael@0: // various reasons. It involves reading both the .exidx and michael@0: // .extab sections. All accesses to those sections are michael@0: // bounds-checked. michael@0: uint8_t buf[ARM_EXIDX_TABLE_LIMIT]; michael@0: size_t buf_used = 0; michael@0: ExExtractResult res = ExtabEntryExtract(entry, buf, sizeof(buf), &buf_used); michael@0: if (res != ExSuccess) { michael@0: // Couldn't extract the unwind info, for some reason. Move on. michael@0: switch (res) { michael@0: case ExInBufOverflow: michael@0: log_("ExtabEntryExtract: .exidx/.extab section overrun"); michael@0: break; michael@0: case ExOutBufOverflow: michael@0: log_("ExtabEntryExtract: bytecode buffer overflow"); michael@0: break; michael@0: case ExCantUnwind: michael@0: log_("ExtabEntryExtract: function is marked CANT_UNWIND"); michael@0: break; michael@0: case ExCantRepresent: michael@0: log_("ExtabEntryExtract: bytecode can't be represented"); michael@0: break; michael@0: case ExInvalid: michael@0: log_("ExtabEntryExtract: index table entry is invalid"); michael@0: break; michael@0: default: { michael@0: char buf[100]; michael@0: snprintf(buf, sizeof(buf), michael@0: "ExtabEntryExtract: unknown error: %d", (int)res); michael@0: buf[sizeof(buf)-1] = 0; michael@0: log_(buf); michael@0: break; michael@0: } michael@0: } michael@0: continue; michael@0: } michael@0: michael@0: // Finally, work through the unwind instructions in |buf| and michael@0: // create CFI entries that Breakpad can use. This can also fail. michael@0: // First, add a new stack frame entry, into which ExtabEntryDecode michael@0: // will write the CFI entries. michael@0: handler_->AddStackFrame(svma + text_bias_, next_svma - svma); michael@0: int ret = ExtabEntryDecode(buf, buf_used); michael@0: if (ret < 0) { michael@0: handler_->DeleteStackFrame(); michael@0: char buf[100]; michael@0: snprintf(buf, sizeof(buf), michael@0: "ExtabEntryDecode: failed with error code: %d", ret); michael@0: buf[sizeof(buf)-1] = 0; michael@0: log_(buf); michael@0: continue; michael@0: } michael@0: handler_->SubmitStackFrame(); michael@0: } /* iterating over .exidx */ michael@0: } michael@0: michael@0: } // namespace lul