| /* -*- mode: C; c-basic-offset: 3; -*- */ |
| |
| /*--------------------------------------------------------------------*/ |
| /*--- Reading of ARM(32) EXIDX unwind information readexidx.c ---*/ |
| /*--------------------------------------------------------------------*/ |
| |
| /* |
| This file is part of Valgrind, a dynamic binary instrumentation |
| framework. |
| |
| Copyright (C) 2014-2015 Mozilla Foundation |
| |
| This program is free software; you can redistribute it and/or |
| modify it under the terms of the GNU General Public License as |
| published by the Free Software Foundation; either version 2 of the |
| License, or (at your option) any later version. |
| |
| 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, write to the Free Software |
| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
| 02111-1307, USA. |
| |
| The GNU General Public License is contained in the file COPYING. |
| */ |
| |
| /* libunwind - a platform-independent unwind library |
| Copyright 2011 Linaro Limited |
| |
| This file is part of libunwind. |
| |
| Permission is hereby granted, free of charge, to any person obtaining |
| a copy of this software and associated documentation files (the |
| "Software"), to deal in the Software without restriction, including |
| without limitation the rights to use, copy, modify, merge, publish, |
| distribute, sublicense, and/or sell copies of the Software, and to |
| permit persons to whom the Software is furnished to do so, subject to |
| the following conditions: |
| |
| The above copyright notice and this permission notice shall be |
| included in all copies or substantial portions of the Software. |
| |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| |
| |
| // Copyright (c) 2010 Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| |
| // Derived originally from libunwind, with very extensive modifications. |
| /* Contributed by Julian Seward <jseward@acm.org> */ |
| |
| |
| // This file translates EXIDX unwind information into the same format |
| // that Valgrind uses for CFI information. Hence Valgrind's CFI |
| // unwinding abilities also become usable for EXIDX. |
| // |
| // See: "Exception Handling ABI for the ARM Architecture", ARM IHI 0038A |
| // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038a/IHI0038A_ehabi.pdf |
| |
| // EXIDX data is presented in two parts: |
| // |
| // * an index table. This contains two words per routine, |
| // the first of which identifies the routine, and the second |
| // of which is a reference to the unwind bytecode. If the |
| // bytecode is very compact -- 3 bytes or less -- it can be |
| // stored directly in the second word. |
| // |
| // * an area containing the unwind bytecodes. |
| // |
| // General flow is: ML_(read_exidx) iterates over all |
| // of the index table entries (pairs). For each entry, it: |
| // |
| // * calls ExtabEntryExtract to copy the bytecode out into |
| // an intermediate buffer. |
| |
| // * uses ExtabEntryDecode to parse the intermediate |
| // buffer. Each bytecode instruction is bundled into a |
| // arm_ex_to_module::extab_data structure, and handed to .. |
| // |
| // * .. TranslateCmd, which generates the pseudo-CFI |
| // records that Valgrind stores. |
| |
| // This file is derived from the following files in the Mozilla tree |
| // toolkit/crashreporter/google-breakpad: |
| // src/common/arm_ex_to_module.cc |
| // src/common/arm_ex_reader.cc |
| |
| |
| #if defined(VGA_arm) |
| |
| #include "pub_core_basics.h" |
| #include "pub_core_libcbase.h" |
| #include "pub_core_libcprint.h" |
| #include "pub_core_libcassert.h" |
| #include "pub_core_options.h" |
| |
| #include "priv_storage.h" |
| #include "priv_readexidx.h" |
| |
| |
| static void complain ( const HChar* str ) |
| { |
| if (!VG_(clo_xml) && VG_(clo_verbosity) > 1) |
| VG_(message)(Vg_UserMsg, |
| " Warning: whilst reading EXIDX: %s\n", str); |
| } |
| |
| |
| /*------------------------------------------------------------*/ |
| /*--- MemoryRange ---*/ |
| /*------------------------------------------------------------*/ |
| |
| typedef struct { Addr start; SizeT len; } MemoryRange; |
| |
| /* Initialise |mr| for [start .. start+len). Zero ranges are allowed, |
| but wraparounds are not. Returns True on success. */ |
| static Bool MemoryRange__init ( /*OUT*/MemoryRange* mr, |
| const void* startV, SizeT len ) |
| { |
| VG_(memset)(mr, 0, sizeof(*mr)); |
| /* This relies on Addr being unsigned. */ |
| Addr start = (Addr)startV; |
| if (len > 0 && start + len - 1 < start) { |
| return False; |
| } |
| mr->start = start; |
| mr->len = len; |
| return True; |
| } |
| |
| static Bool MemoryRange__covers ( MemoryRange* mr, |
| const void* startV, SizeT len ) |
| { |
| vg_assert(len > 0); |
| if (mr->len == 0) { |
| return False; |
| } |
| Addr start = (Addr)startV; |
| return start >= mr->start && start + len - 1 <= mr->start + mr->len - 1; |
| } |
| |
| |
| /*------------------------------------------------------------*/ |
| /*--- (Pass 1 of 3) The EXIDX extractor ---*/ |
| /*------------------------------------------------------------*/ |
| |
| #define ARM_EXIDX_CANT_UNWIND 0x00000001 |
| #define ARM_EXIDX_COMPACT 0x80000000 |
| #define ARM_EXTBL_OP_FINISH 0xb0 |
| #define ARM_EXIDX_TABLE_LIMIT (255*4) |
| |
| /* These are in the ARM-defined format, so their layout is important. */ |
| typedef |
| struct { UInt addr; UInt data; } |
| ExidxEntry; |
| |
| |
| typedef |
| enum { |
| ExSuccess=1, // success |
| ExInBufOverflow, // out-of-range while reading .exidx |
| ExOutBufOverflow, // output buffer is too small |
| ExCantUnwind, // this function is marked CANT_UNWIND |
| ExCantRepresent, // entry valid, but we can't represent it |
| ExInvalid // entry is invalid |
| } |
| ExExtractResult; |
| |
| |
| /* Helper function for fishing bits out of the EXIDX representation. */ |
| static const void* Prel31ToAddr(const void* addr) |
| { |
| UInt offset32 = *(const UInt*)addr; |
| // sign extend offset32[30:0] to 64 bits -- copy bit 30 to positions |
| // 63:31 inclusive. |
| ULong offset64 = offset32; |
| if (offset64 & (1ULL << 30)) |
| offset64 |= 0xFFFFFFFF80000000ULL; |
| else |
| offset64 &= 0x000000007FFFFFFFULL; |
| return ((const UChar*)addr) + (UWord)offset64; |
| } |
| |
| |
| // Extract unwind bytecode for the function denoted by |entry| into |buf|, |
| // and return the number of bytes of |buf| written, along with a code |
| // indicating the outcome. |
| static |
| ExExtractResult ExtabEntryExtract ( MemoryRange* mr_exidx, |
| MemoryRange* mr_extab, |
| const ExidxEntry* entry, |
| UChar* buf, SizeT buf_size, |
| /*OUT*/SizeT* buf_used) |
| { |
| Bool ok; |
| MemoryRange mr_out; |
| ok = MemoryRange__init(&mr_out, buf, buf_size); |
| if (!ok) return ExOutBufOverflow; |
| |
| *buf_used = 0; |
| |
| # define PUT_BUF_U8(_byte) \ |
| do { if (!MemoryRange__covers(&mr_out, &buf[*buf_used], 1)) \ |
| return ExOutBufOverflow; \ |
| buf[(*buf_used)++] = (_byte); } while (0) |
| |
| # define GET_EX_U32(_lval, _addr, _mr) \ |
| do { if (!MemoryRange__covers((_mr), (const void*)(_addr), 4)) \ |
| return ExInBufOverflow; \ |
| (_lval) = *(const UInt*)(_addr); } while (0) |
| |
| # define GET_EXIDX_U32(_lval, _addr) \ |
| GET_EX_U32(_lval, _addr, mr_exidx) |
| |
| # define GET_EXTAB_U32(_lval, _addr) \ |
| GET_EX_U32(_lval, _addr, mr_extab) |
| |
| UInt data; |
| GET_EXIDX_U32(data, &entry->data); |
| |
| // A function can be marked CANT_UNWIND if (eg) it is known to be |
| // at the bottom of the stack. |
| if (data == ARM_EXIDX_CANT_UNWIND) |
| return ExCantUnwind; |
| |
| UInt pers; // personality number |
| UInt extra; // number of extra data words required |
| UInt extra_allowed; // number of extra data words allowed |
| const UInt* extbl_data; // the handler entry, if not inlined |
| |
| if (data & ARM_EXIDX_COMPACT) { |
| // The handler table entry has been inlined into the index table entry. |
| // In this case it can only be an ARM-defined compact model, since |
| // bit 31 is 1. Only personalities 0, 1 and 2 are defined for the |
| // ARM compact model, but 1 and 2 are "Long format" and may require |
| // extra data words. Hence the allowable personalities here are: |
| // personality 0, in which case 'extra' has no meaning |
| // personality 1, with zero extra words |
| // personality 2, with zero extra words |
| extbl_data = NULL; |
| pers = (data >> 24) & 0x0F; |
| extra = (data >> 16) & 0xFF; |
| extra_allowed = 0; |
| } |
| else { |
| // The index table entry is a pointer to the handler entry. Note |
| // that Prel31ToAddr will read the given address, but we already |
| // range-checked above. |
| extbl_data = Prel31ToAddr(&entry->data); |
| GET_EXTAB_U32(data, extbl_data); |
| if (!(data & ARM_EXIDX_COMPACT)) { |
| // This denotes a "generic model" handler. That will involve |
| // executing arbitrary machine code, which is something we |
| // can't represent here; hence reject it. |
| return ExCantRepresent; |
| } |
| // So we have a compact model representation. Again, 3 possible |
| // personalities, but this time up to 255 allowable extra words. |
| pers = (data >> 24) & 0x0F; |
| extra = (data >> 16) & 0xFF; |
| extra_allowed = 255; |
| extbl_data++; |
| } |
| |
| // Now look at the handler table entry. The first word is |data| |
| // and subsequent words start at |*extbl_data|. The number of |
| // extra words to use is |extra|, provided that the personality |
| // allows extra words. Even if it does, none may be available -- |
| // extra_allowed is the maximum number of extra words allowed. */ |
| if (pers == 0) { |
| // "Su16" in the documentation -- 3 unwinding insn bytes |
| // |extra| has no meaning here; instead that byte is an unwind-info byte |
| PUT_BUF_U8(data >> 16); |
| PUT_BUF_U8(data >> 8); |
| PUT_BUF_U8(data); |
| } |
| else if ((pers == 1 || pers == 2) && extra <= extra_allowed) { |
| // "Lu16" or "Lu32" respectively -- 2 unwinding insn bytes, |
| // and up to 255 extra words. |
| PUT_BUF_U8(data >> 8); |
| PUT_BUF_U8(data); |
| UInt j; |
| for (j = 0; j < extra; j++) { |
| GET_EXTAB_U32(data, extbl_data); |
| extbl_data++; |
| PUT_BUF_U8(data >> 24); |
| PUT_BUF_U8(data >> 16); |
| PUT_BUF_U8(data >> 8); |
| PUT_BUF_U8(data >> 0); |
| } |
| } |
| else { |
| // The entry is invalid. |
| return ExInvalid; |
| } |
| |
| // Make sure the entry is terminated with "FINISH" |
| if (*buf_used > 0 && buf[(*buf_used) - 1] != ARM_EXTBL_OP_FINISH) |
| PUT_BUF_U8(ARM_EXTBL_OP_FINISH); |
| |
| return ExSuccess; |
| |
| # undef GET_EXTAB_U32 |
| # undef GET_EXIDX_U32 |
| # undef GET_U32 |
| # undef PUT_BUF_U8 |
| } |
| |
| |
| /*------------------------------------------------------------*/ |
| /*--- (Pass 2 of 3) The EXIDX decoder ---*/ |
| /*------------------------------------------------------------*/ |
| |
| /* This (ExtabData) is an intermediate structure, used to carry |
| information from the decoder (pass 2) to the summariser (pass 3). |
| I don't think its layout is important. */ |
| typedef |
| enum { |
| ARM_EXIDX_CMD_FINISH=0x100, |
| ARM_EXIDX_CMD_SUB_FROM_VSP, |
| ARM_EXIDX_CMD_ADD_TO_VSP, |
| ARM_EXIDX_CMD_REG_POP, |
| ARM_EXIDX_CMD_REG_TO_SP, |
| ARM_EXIDX_CMD_VFP_POP, |
| ARM_EXIDX_CMD_WREG_POP, |
| ARM_EXIDX_CMD_WCGR_POP, |
| ARM_EXIDX_CMD_RESERVED, |
| ARM_EXIDX_CMD_REFUSED |
| } |
| ExtabCmd; |
| |
| static const HChar* showExtabCmd ( ExtabCmd cmd ) { |
| switch (cmd) { |
| case ARM_EXIDX_CMD_FINISH: return "FINISH"; |
| case ARM_EXIDX_CMD_SUB_FROM_VSP: return "SUB_FROM_VSP"; |
| case ARM_EXIDX_CMD_ADD_TO_VSP: return "ADD_TO_VSP"; |
| case ARM_EXIDX_CMD_REG_POP: return "REG_POP"; |
| case ARM_EXIDX_CMD_REG_TO_SP: return "REG_TO_SP"; |
| case ARM_EXIDX_CMD_VFP_POP: return "VFP_POP"; |
| case ARM_EXIDX_CMD_WREG_POP: return "WREG_POP"; |
| case ARM_EXIDX_CMD_WCGR_POP: return "WCGR_POP"; |
| case ARM_EXIDX_CMD_RESERVED: return "RESERVED"; |
| case ARM_EXIDX_CMD_REFUSED: return "REFUSED"; |
| default: return "???"; |
| } |
| } |
| |
| |
| typedef |
| struct { ExtabCmd cmd; UInt data; } |
| ExtabData; |
| |
| static void ppExtabData ( const ExtabData* etd ) { |
| VG_(printf)("ExtabData{%-12s 0x%08x}", showExtabCmd(etd->cmd), etd->data); |
| } |
| |
| |
| enum extab_cmd_flags { |
| ARM_EXIDX_VFP_SHIFT_16 = 1 << 16, |
| ARM_EXIDX_VFP_FSTMD = 1 << 17, // distinguishes FSTMxxD from FSTMxxX |
| }; |
| |
| |
| /* Forwards */ |
| typedef struct _SummState SummState; |
| static Int TranslateCmd(/*MOD*/SummState* state, const ExtabData* edata); |
| |
| |
| // Take the unwind information extracted by ExtabEntryExtract |
| // and parse it into frame-unwind instructions. These are as |
| // specified in "Table 4, ARM-defined frame-unwinding instructions" |
| // in the specification document detailed in comments at the top |
| // of this file. |
| // |
| // This reads from |buf[0, +data_size)|. It checks for overruns of |
| // the input buffer and returns a negative value if that happens, or |
| // for any other failure cases. It returns zero in case of success. |
| // Whilst reading the input, it dumps the result in |*state|. |
| static |
| Int ExtabEntryDecode(/*OUT*/SummState* state, const UChar* buf, SizeT buf_size) |
| { |
| if (buf == NULL || buf_size == 0) |
| return -3; |
| |
| MemoryRange mr_in; |
| Bool ok = MemoryRange__init(&mr_in, buf, buf_size); |
| if (!ok) |
| return -2; |
| |
| # define GET_BUF_U8(_lval) \ |
| do { if (!MemoryRange__covers(&mr_in, buf, 1)) \ |
| return -4; \ |
| (_lval) = *(buf++); } while (0) |
| |
| const UChar* end = buf + buf_size; |
| |
| while (buf < end) { |
| ExtabData edata; |
| VG_(bzero_inline)(&edata, sizeof(edata)); |
| |
| UChar op; |
| GET_BUF_U8(op); |
| if ((op & 0xc0) == 0x00) { |
| // vsp = vsp + (xxxxxx << 2) + 4 |
| edata.cmd = ARM_EXIDX_CMD_ADD_TO_VSP; |
| edata.data = (((Int)op & 0x3f) << 2) + 4; |
| } |
| else if ((op & 0xc0) == 0x40) { |
| // vsp = vsp - (xxxxxx << 2) - 4 |
| edata.cmd = ARM_EXIDX_CMD_SUB_FROM_VSP; |
| edata.data = (((Int)op & 0x3f) << 2) + 4; |
| } |
| else if ((op & 0xf0) == 0x80) { |
| UChar op2; |
| GET_BUF_U8(op2); |
| if (op == 0x80 && op2 == 0x00) { |
| // Refuse to unwind |
| edata.cmd = ARM_EXIDX_CMD_REFUSED; |
| } else { |
| // Pop up to 12 integer registers under masks {r15-r12},{r11-r4} |
| edata.cmd = ARM_EXIDX_CMD_REG_POP; |
| edata.data = ((op & 0xf) << 8) | op2; |
| edata.data = edata.data << 4; |
| } |
| } |
| else if ((op & 0xf0) == 0x90) { |
| if (op == 0x9d || op == 0x9f) { |
| // 9d: Reserved as prefix for ARM register to register moves |
| // 9f: Reserved as prefix for Intel Wireless MMX reg to reg moves |
| edata.cmd = ARM_EXIDX_CMD_RESERVED; |
| } else { |
| // Set vsp = r[nnnn] |
| edata.cmd = ARM_EXIDX_CMD_REG_TO_SP; |
| edata.data = op & 0x0f; |
| } |
| } |
| else if ((op & 0xf0) == 0xa0) { |
| // Pop r4 to r[4+nnn], or |
| // Pop r4 to r[4+nnn] and r14 |
| Int nnn = (op & 0x07); |
| edata.data = (1 << (nnn + 1)) - 1; |
| edata.data = edata.data << 4; |
| if (op & 0x08) edata.data |= 1 << 14; |
| edata.cmd = ARM_EXIDX_CMD_REG_POP; |
| } |
| else if (op == ARM_EXTBL_OP_FINISH) { |
| // Finish |
| edata.cmd = ARM_EXIDX_CMD_FINISH; |
| buf = end; |
| } |
| else if (op == 0xb1) { |
| UChar op2; |
| GET_BUF_U8(op2); |
| if (op2 == 0 || (op2 & 0xf0)) { |
| // Spare |
| edata.cmd = ARM_EXIDX_CMD_RESERVED; |
| } else { |
| // Pop integer registers under mask {r3,r2,r1,r0} |
| edata.cmd = ARM_EXIDX_CMD_REG_POP; |
| edata.data = op2 & 0x0f; |
| } |
| } |
| else if (op == 0xb2) { |
| // vsp = vsp + 0x204 + (uleb128 << 2) |
| ULong offset = 0; |
| UChar byte, shift = 0; |
| do { |
| GET_BUF_U8(byte); |
| offset |= (byte & 0x7f) << shift; |
| shift += 7; |
| } while ((byte & 0x80) && buf < end); |
| edata.data = offset * 4 + 0x204; |
| edata.cmd = ARM_EXIDX_CMD_ADD_TO_VSP; |
| } |
| else if (op == 0xb3 || op == 0xc8 || op == 0xc9) { |
| // b3: Pop VFP regs D[ssss] to D[ssss+cccc], FSTMFDX-ishly |
| // c8: Pop VFP regs D[16+ssss] to D[16+ssss+cccc], FSTMFDD-ishly |
| // c9: Pop VFP regs D[ssss] to D[ssss+cccc], FSTMFDD-ishly |
| edata.cmd = ARM_EXIDX_CMD_VFP_POP; |
| GET_BUF_U8(edata.data); |
| if (op == 0xc8) edata.data |= ARM_EXIDX_VFP_SHIFT_16; |
| if (op != 0xb3) edata.data |= ARM_EXIDX_VFP_FSTMD; |
| } |
| else if ((op & 0xf8) == 0xb8 || (op & 0xf8) == 0xd0) { |
| // b8: Pop VFP regs D[8] to D[8+nnn], FSTMFDX-ishly |
| // d0: Pop VFP regs D[8] to D[8+nnn], FSTMFDD-ishly |
| edata.cmd = ARM_EXIDX_CMD_VFP_POP; |
| edata.data = 0x80 | (op & 0x07); |
| if ((op & 0xf8) == 0xd0) edata.data |= ARM_EXIDX_VFP_FSTMD; |
| } |
| else if (op >= 0xc0 && op <= 0xc5) { |
| // Intel Wireless MMX pop wR[10]-wr[10+nnn], nnn != 6,7 |
| edata.cmd = ARM_EXIDX_CMD_WREG_POP; |
| edata.data = 0xa0 | (op & 0x07); |
| } |
| else if (op == 0xc6) { |
| // Intel Wireless MMX pop wR[ssss] to wR[ssss+cccc] |
| edata.cmd = ARM_EXIDX_CMD_WREG_POP; |
| GET_BUF_U8(edata.data); |
| } |
| else if (op == 0xc7) { |
| UChar op2; |
| GET_BUF_U8(op2); |
| if (op2 == 0 || (op2 & 0xf0)) { |
| // Spare |
| edata.cmd = ARM_EXIDX_CMD_RESERVED; |
| } else { |
| // Intel Wireless MMX pop wCGR registers under mask {wCGR3,2,1,0} |
| edata.cmd = ARM_EXIDX_CMD_WCGR_POP; |
| edata.data = op2 & 0x0f; |
| } |
| } |
| else { |
| // Spare |
| edata.cmd = ARM_EXIDX_CMD_RESERVED; |
| } |
| |
| if (0) |
| VG_(printf)(" edata: cmd %08x data %08x\n", |
| (UInt)edata.cmd, edata.data); |
| |
| Int ret = TranslateCmd ( state, &edata ); |
| if (ret < 0) return ret; |
| } |
| return 0; |
| |
| # undef GET_BUF_U8 |
| } |
| |
| |
| /*------------------------------------------------------------*/ |
| /*--- (Pass 3 of 3) The EXIDX summariser ---*/ |
| /*------------------------------------------------------------*/ |
| |
| /* In this translation into DiCfSI_m, we're going to have the CFA play |
| the role of the VSP. That means that the VSP can be exactly any of |
| the CFA expressions, viz: {r7,r11,r12,r13) +/- offset. |
| |
| All of this would be a lot simpler if the DiCfSI_m representation |
| was just a bit more expressive and orthogonal. But it isn't. |
| |
| The central difficulty is that, although we can track changes |
| to the offset of VSP (via vsp_off), we can't deal with assignments |
| of an entirely new expression to it, because the existing |
| rules in |cfi| will almost certainly refer to the CFA, and so |
| changing it will make them invalid. Hence, below: |
| |
| * for the case ARM_EXIDX_CMD_REG_TO_SP we simply disallow |
| assignment, and hence give up, if any rule refers to CFA |
| |
| * for the case ARM_EXIDX_CMD_REG_POP, the SP (hence, VSP) is |
| updated by the pop, give up. |
| |
| This is an ugly hack to work around not having a better (LUL-like) |
| expression representation. That said, these restrictions don't |
| appear to be a big problem in practice. |
| */ |
| |
| struct _SummState { |
| // The DiCfSI_m under construction |
| DiCfSI_m cfi; |
| Int vsp_off; |
| // For generating CFI register expressions, if needed. |
| DebugInfo* di; |
| }; |
| |
| |
| /* Generate a trivial CfiExpr, for the ARM(32) integer register |
| numbered |gprNo|. First ensure this DebugInfo has a cfsi_expr |
| array in which to park it. Returns -1 if |gprNo| cannot be |
| represented, otherwise returns a value >= 0. */ |
| static |
| Int gen_CfiExpr_CfiReg_ARM_GPR ( /*MB_MOD*/DebugInfo* di, UInt gprNo ) |
| { |
| CfiReg creg = Creg_INVALID; |
| switch (gprNo) { |
| case 13: creg = Creg_ARM_R13; break; |
| case 12: creg = Creg_ARM_R12; break; |
| case 15: creg = Creg_ARM_R15; break; |
| case 14: creg = Creg_ARM_R14; break; |
| case 7: creg = Creg_ARM_R7; break; |
| default: break; |
| } |
| if (creg == Creg_INVALID) { |
| return -1; |
| } |
| if (!di->cfsi_exprs) { |
| di->cfsi_exprs = VG_(newXA)( ML_(dinfo_zalloc), "di.gCCAG", |
| ML_(dinfo_free), sizeof(CfiExpr) ); |
| } |
| Int res = ML_(CfiExpr_CfiReg)( di->cfsi_exprs, creg ); |
| vg_assert(res >= 0); |
| return res; |
| } |
| |
| |
| /* Given a DiCfSI_m, find the _how/_off pair for the given ARM(32) GPR |
| number inside |cfsi_m|, or return NULL for both if that register |
| number is not represented. */ |
| static |
| void maybeFindExprForRegno( /*OUT*/UChar** howPP, /*OUT*/Int** offPP, |
| DiCfSI_m* cfsi_m, Int regNo ) |
| { |
| switch (regNo) { |
| case 15: *howPP = &cfsi_m->ra_how; *offPP = &cfsi_m->ra_off; return; |
| case 14: *howPP = &cfsi_m->r14_how; *offPP = &cfsi_m->r14_off; return; |
| case 13: *howPP = &cfsi_m->r13_how; *offPP = &cfsi_m->r13_off; return; |
| case 12: *howPP = &cfsi_m->r12_how; *offPP = &cfsi_m->r12_off; return; |
| case 11: *howPP = &cfsi_m->r11_how; *offPP = &cfsi_m->r11_off; return; |
| case 7: *howPP = &cfsi_m->r7_how; *offPP = &cfsi_m->r7_off; return; |
| default: break; |
| } |
| *howPP = NULL; *offPP = NULL; |
| } |
| |
| |
| /* Set cfi.cfa_{how,off} so as to be a copy of the expression denoted |
| by (how,off), if it is possible to do so. Returns True on |
| success. */ |
| static |
| Bool setCFAfromCFIR( /*MOD*/DiCfSI_m* cfi, XArray*/*CfiExpr*/ cfsi_exprs, |
| UChar how, Int off ) |
| { |
| switch (how) { |
| case CFIR_EXPR: |
| if (!cfsi_exprs) return False; |
| CfiExpr* e = (CfiExpr*)VG_(indexXA)(cfsi_exprs, off); |
| if (e->tag != Cex_CfiReg) return False; |
| if (e->Cex.CfiReg.reg == Creg_ARM_R7) { |
| cfi->cfa_how = CFIC_ARM_R7REL; |
| cfi->cfa_off = 0; |
| return True; |
| } |
| ML_(ppCfiExpr)(cfsi_exprs, off); |
| vg_assert(0); |
| default: |
| break; |
| } |
| VG_(printf)("setCFAfromCFIR: FAIL: how %d off %d\n", how, off); |
| vg_assert(0); |
| return False; |
| } |
| |
| |
| #define ARM_EXBUF_START(x) (((x) >> 4) & 0x0f) |
| #define ARM_EXBUF_COUNT(x) ((x) & 0x0f) |
| #define ARM_EXBUF_END(x) (ARM_EXBUF_START(x) + ARM_EXBUF_COUNT(x)) |
| |
| |
| static Bool mentionsCFA ( DiCfSI_m* cfi ) |
| { |
| # define MENTIONS_CFA(_how) ((_how) == CFIR_CFAREL || (_how) == CFIR_MEMCFAREL) |
| if (MENTIONS_CFA(cfi->ra_how)) return True; |
| if (MENTIONS_CFA(cfi->r14_how)) return True; |
| if (MENTIONS_CFA(cfi->r13_how)) return True; |
| if (MENTIONS_CFA(cfi->r12_how)) return True; |
| if (MENTIONS_CFA(cfi->r11_how)) return True; |
| if (MENTIONS_CFA(cfi->r7_how)) return True; |
| return False; |
| # undef MENTIONS_CFA |
| } |
| |
| |
| // Translate command from extab_data to command for Module. |
| static |
| Int TranslateCmd(/*MOD*/SummState* state, const ExtabData* edata) |
| { |
| /* Stay sane: check that the CFA has the expected form. */ |
| vg_assert(state); |
| switch (state->cfi.cfa_how) { |
| case CFIC_ARM_R13REL: case CFIC_ARM_R12REL: |
| case CFIC_ARM_R11REL: case CFIC_ARM_R7REL: break; |
| default: vg_assert(0); |
| } |
| |
| if (0) { |
| VG_(printf)(" TranslateCmd: "); |
| ppExtabData(edata); |
| VG_(printf)("\n"); |
| } |
| |
| Int ret = 0; |
| switch (edata->cmd) { |
| case ARM_EXIDX_CMD_FINISH: |
| /* Copy LR to PC if there isn't currently a rule for PC in force. */ |
| if (state->cfi.ra_how == CFIR_UNKNOWN) { |
| if (state->cfi.r14_how == CFIR_UNKNOWN) { |
| state->cfi.ra_how = CFIR_EXPR; |
| state->cfi.ra_off = gen_CfiExpr_CfiReg_ARM_GPR(state->di, 14); |
| vg_assert(state->cfi.ra_off >= 0); |
| } else { |
| state->cfi.ra_how = state->cfi.r14_how; |
| state->cfi.ra_off = state->cfi.r14_off; |
| } |
| } |
| break; |
| case ARM_EXIDX_CMD_SUB_FROM_VSP: |
| state->vsp_off -= (Int)(edata->data); |
| break; |
| case ARM_EXIDX_CMD_ADD_TO_VSP: |
| state->vsp_off += (Int)(edata->data); |
| break; |
| case ARM_EXIDX_CMD_REG_POP: { |
| UInt i; |
| for (i = 0; i < 16; i++) { |
| if (edata->data & (1 << i)) { |
| // See if we're summarising for int register |i|. If so, |
| // describe how to pull it off the stack. The cast of |i| is |
| // a bit of a kludge but works because DW_REG_ARM_Rn has the |
| // value |n|, for 0 <= |n| <= 15 -- that is, for the ARM |
| // general-purpose registers. |
| UChar* rX_howP = NULL; |
| Int* rX_offP = NULL; |
| maybeFindExprForRegno(&rX_howP, &rX_offP, &state->cfi, i); |
| if (rX_howP) { |
| vg_assert(rX_offP); |
| /* rX_howP and rX_offP point at one of the rX fields |
| in |state->cfi|. Hence the following assignments |
| are really updating |state->cfi|. */ |
| *rX_howP = CFIR_MEMCFAREL; |
| *rX_offP = state->vsp_off; |
| } else { |
| /* We're not tracking this register, so ignore it. */ |
| vg_assert(!rX_offP); |
| } |
| state->vsp_off += 4; |
| } |
| } |
| /* Set cfa in case the SP got popped. */ |
| if (edata->data & (1 << 13)) { |
| // vsp = curr_rules_.mR13expr; |
| //state->cfi.cfa_how = |
| //state->cfi.cfa_off = |
| //state->vsp_off = 0; |
| // If this happens, it would make the existing CFA references |
| // in the summary invalid. So give up instead. |
| goto cant_summarise; |
| } |
| break; |
| } |
| case ARM_EXIDX_CMD_REG_TO_SP: { |
| /* We're generating a new value for the CFA/VSP here. Hence, |
| if the summary already refers to the CFA at all, we can't |
| go any further, and have to abandon summarisation. */ |
| if (mentionsCFA(&state->cfi)) |
| goto cant_summarise; |
| vg_assert(edata->data < 16); |
| Int reg_no = edata->data; |
| // Same comment as above, re the casting of |reg_no|, applies. |
| UChar* rX_howP = NULL; |
| Int* rX_offP = NULL; |
| maybeFindExprForRegno(&rX_howP, &rX_offP, &state->cfi, reg_no); |
| if (rX_howP) { |
| vg_assert(rX_offP); |
| if (*rX_howP == CFIR_UNKNOWN) { |
| //curr_rules_.mR13expr = LExpr(LExpr::NODEREF, reg_no, 0); |
| Int expr_ix = gen_CfiExpr_CfiReg_ARM_GPR(state->di, reg_no); |
| if (expr_ix >= 0) { |
| state->cfi.r13_how = CFIR_EXPR; |
| state->cfi.r13_off = expr_ix; |
| } else { |
| goto cant_summarise; |
| } |
| } else { |
| //curr_rules_.mR13expr = *reg_exprP; |
| state->cfi.r13_how = *rX_howP; |
| state->cfi.r13_off = *rX_offP; |
| } |
| //vsp = curr_rules_.mR13expr; |
| Bool ok = setCFAfromCFIR( &state->cfi, state->di->cfsi_exprs, |
| state->cfi.r13_how, state->cfi.r13_off ); |
| if (!ok) goto cant_summarise; |
| state->vsp_off = 0; |
| } else { |
| vg_assert(!rX_offP); |
| } |
| break; |
| } |
| case ARM_EXIDX_CMD_VFP_POP: { |
| /* Don't recover VFP registers, but be sure to adjust the stack |
| pointer. */ |
| UInt i; |
| for (i = ARM_EXBUF_START(edata->data); |
| i <= ARM_EXBUF_END(edata->data); i++) { |
| state->vsp_off += 8; |
| } |
| if (!(edata->data & ARM_EXIDX_VFP_FSTMD)) { |
| state->vsp_off += 4; |
| } |
| break; |
| } |
| case ARM_EXIDX_CMD_WREG_POP: { |
| UInt i; |
| for (i = ARM_EXBUF_START(edata->data); |
| i <= ARM_EXBUF_END(edata->data); i++) { |
| state->vsp_off += 8; |
| } |
| break; |
| } |
| case ARM_EXIDX_CMD_WCGR_POP: { |
| UInt i; |
| // Pop wCGR registers under mask {wCGR3,2,1,0}, hence "i < 4" |
| for (i = 0; i < 4; i++) { |
| if (edata->data & (1 << i)) { |
| state->vsp_off += 4; |
| } |
| } |
| break; |
| } |
| case ARM_EXIDX_CMD_REFUSED: |
| case ARM_EXIDX_CMD_RESERVED: |
| ret = -1; |
| break; |
| } |
| return ret; |
| |
| cant_summarise: |
| return -10; |
| } |
| |
| |
| /* Initialise the EXIDX summariser, by writing initial values in |state|. */ |
| static |
| void AddStackFrame ( /*OUT*/SummState* state, |
| DebugInfo* di ) |
| { |
| VG_(bzero_inline)(state, sizeof(*state)); |
| state->vsp_off = 0; |
| state->di = di; |
| /* Initialise the DiCfSI_m that we are building. */ |
| state->cfi.cfa_how = CFIC_ARM_R13REL; |
| state->cfi.cfa_off = 0; |
| state->cfi.ra_how = CFIR_UNKNOWN; |
| state->cfi.r14_how = CFIR_UNKNOWN; |
| state->cfi.r13_how = CFIR_UNKNOWN; |
| state->cfi.r12_how = CFIR_UNKNOWN; |
| state->cfi.r11_how = CFIR_UNKNOWN; |
| state->cfi.r7_how = CFIR_UNKNOWN; |
| } |
| |
| static |
| void SubmitStackFrame( /*MOD*/DebugInfo* di, |
| SummState* state, Addr avma, SizeT len ) |
| { |
| // JRS: I'm really not sure what this means, or if it is necessary |
| // return address always winds up in pc |
| //stack_frame_entry_->initial_rules[ustr__ZDra()] // ".ra" |
| // = stack_frame_entry_->initial_rules[ustr__pc()]; |
| // maybe don't need to do anything here? |
| |
| // the final value of vsp is the new value of sp. |
| switch (state->cfi.cfa_how) { |
| case CFIC_ARM_R13REL: case CFIC_ARM_R12REL: |
| case CFIC_ARM_R11REL: case CFIC_ARM_R7REL: break; |
| default: vg_assert(0); |
| } |
| state->cfi.r13_how = CFIR_CFAREL; |
| state->cfi.r13_off = state->vsp_off; |
| |
| // Finally, add the completed RuleSet to the SecMap |
| if (len > 0) { |
| |
| // Futz with the rules for r4 .. r11 in the same way as happens |
| // with the CFI summariser: |
| /* Mark callee-saved registers (r4 .. r11) as unchanged, if there is |
| no other information about them. FIXME: do this just once, at |
| the point where the ruleset is committed. */ |
| if (state->cfi.r7_how == CFIR_UNKNOWN) { |
| state->cfi.r7_how = CFIR_SAME; |
| state->cfi.r7_off = 0; |
| } |
| if (state->cfi.r11_how == CFIR_UNKNOWN) { |
| state->cfi.r11_how = CFIR_SAME; |
| state->cfi.r11_off = 0; |
| } |
| if (state->cfi.r12_how == CFIR_UNKNOWN) { |
| state->cfi.r12_how = CFIR_SAME; |
| state->cfi.r12_off = 0; |
| } |
| if (state->cfi.r14_how == CFIR_UNKNOWN) { |
| state->cfi.r14_how = CFIR_SAME; |
| state->cfi.r14_off = 0; |
| } |
| |
| // And add them |
| ML_(addDiCfSI)(di, avma, len, &state->cfi); |
| if (di->trace_cfi) |
| ML_(ppDiCfSI)(di->cfsi_exprs, avma, len, &state->cfi); |
| } |
| } |
| |
| |
| /*------------------------------------------------------------*/ |
| /*--- Top level ---*/ |
| /*------------------------------------------------------------*/ |
| |
| void ML_(read_exidx) ( /*MOD*/DebugInfo* di, |
| UChar* exidx_img, SizeT exidx_size, |
| UChar* extab_img, SizeT extab_size, |
| Addr text_last_svma, |
| PtrdiffT text_bias ) |
| { |
| if (di->trace_cfi) |
| VG_(printf)("BEGIN ML_(read_exidx) exidx_img=[%p, +%lu) " |
| "extab_img=[%p, +%lu) text_last_svma=%lx text_bias=%lx\n", |
| exidx_img, exidx_size, extab_img, extab_size, |
| text_last_svma, (UWord)text_bias); |
| Bool ok; |
| MemoryRange mr_exidx, mr_extab; |
| ok = MemoryRange__init(&mr_exidx, exidx_img, exidx_size); |
| ok = ok && MemoryRange__init(&mr_extab, extab_img, extab_size); |
| if (!ok) { |
| complain(".exidx or .extab image area wraparound"); |
| return; |
| } |
| |
| const ExidxEntry* start_img = (const ExidxEntry*)exidx_img; |
| const ExidxEntry* end_img = (const ExidxEntry*)(exidx_img + exidx_size); |
| |
| if (VG_(clo_verbosity) > 1) |
| VG_(message)(Vg_DebugMsg, " Reading EXIDX entries: %lu available\n", |
| exidx_size / sizeof(ExidxEntry) ); |
| |
| // Iterate over each of the EXIDX entries (pairs of 32-bit words). |
| // These occupy the entire .exidx section. |
| UWord n_attempted = 0, n_successful = 0; |
| |
| const ExidxEntry* entry_img; |
| for (entry_img = start_img; entry_img < end_img; ++entry_img) { |
| |
| n_attempted++; |
| // Figure out the code address range that this table entry_img is |
| // associated with. |
| Addr avma = (Addr)Prel31ToAddr(&entry_img->addr); |
| if (di->trace_cfi) |
| VG_(printf)("XXX1 entry: entry->addr 0x%x, avma 0x%lx\n", |
| entry_img->addr, avma); |
| |
| Addr next_avma; |
| if (entry_img < end_img - 1) { |
| next_avma = (Addr)Prel31ToAddr(&(entry_img+1)->addr); |
| } else { |
| // This is the last EXIDX entry in the sequence, so we don't |
| // have an address for the start of the next function, to limit |
| // this one. Instead use the address of the last byte of the |
| // text section associated with this .exidx section, that we |
| // have been given. So as to avoid junking up the CFI unwind |
| // tables with absurdly large address ranges in the case where |
| // text_last_svma_ is wrong, only use the value if it is nonzero |
| // and within one page of |svma|. Otherwise assume a length of 1. |
| // |
| // In some cases, gcc has been observed to finish the exidx |
| // section with an entry of length 1 marked CANT_UNWIND, |
| // presumably exactly for the purpose of giving a definite |
| // length for the last real entry, without having to look at |
| // text segment boundaries. |
| Addr text_last_avma = text_last_svma + text_bias; |
| |
| Bool plausible; |
| Addr maybe_next_avma = text_last_avma + 1; |
| if (maybe_next_avma > avma && maybe_next_avma - avma <= 4096) { |
| next_avma = maybe_next_avma; |
| plausible = True; |
| } else { |
| next_avma = avma + 1; |
| plausible = False; |
| } |
| |
| if (!plausible && avma != text_last_avma + 1) { |
| HChar buf[100]; |
| VG_(snprintf)(buf, sizeof(buf), |
| "Implausible EXIDX last entry size %lu" |
| "; using 1 instead.", text_last_avma - avma); |
| buf[sizeof(buf)-1] = 0; |
| complain(buf); |
| } |
| } |
| |
| // Extract the unwind info into |buf|. This might fail for |
| // various reasons. It involves reading both the .exidx and |
| // .extab sections. All accesses to those sections are |
| // bounds-checked. |
| if (di->trace_cfi) |
| VG_(printf)("XXX1 entry is for AVMA 0x%lx 0x%lx\n", |
| avma, next_avma-1); |
| UChar buf[ARM_EXIDX_TABLE_LIMIT]; |
| SizeT buf_used = 0; |
| ExExtractResult res |
| = ExtabEntryExtract(&mr_exidx, &mr_extab, |
| entry_img, buf, sizeof(buf), &buf_used); |
| if (res != ExSuccess) { |
| // Couldn't extract the unwind info, for some reason. Move on. |
| switch (res) { |
| case ExInBufOverflow: |
| complain("ExtabEntryExtract: .exidx/.extab section overrun"); |
| break; |
| case ExOutBufOverflow: |
| complain("ExtabEntryExtract: bytecode buffer overflow"); |
| break; |
| case ExCantUnwind: |
| // Some functions are marked CantUnwind by the compiler. |
| // Don't record these as attempted, since that's just |
| // confusing, and failure to summarise them is not the fault |
| // of this code. |
| n_attempted--; |
| if (0) |
| complain("ExtabEntryExtract: function is marked CANT_UNWIND"); |
| break; |
| case ExCantRepresent: |
| complain("ExtabEntryExtract: bytecode can't be represented"); |
| break; |
| case ExInvalid: |
| complain("ExtabEntryExtract: index table entry is invalid"); |
| break; |
| default: { |
| HChar mbuf[100]; |
| VG_(snprintf)(mbuf, sizeof(mbuf), |
| "ExtabEntryExtract: unknown error: %d", (Int)res); |
| buf[sizeof(mbuf)-1] = 0; |
| complain(mbuf); |
| break; |
| } |
| } |
| continue; |
| } |
| |
| // Finally, work through the unwind instructions in |buf| and |
| // create CFI entries that Valgrind can use. This can also fail. |
| // First, initialise the summariser's running state, into which |
| // ExtabEntryDecode will write the CFI entries. |
| |
| SummState state; |
| AddStackFrame( &state, di ); |
| Int ret = ExtabEntryDecode( &state, buf, buf_used ); |
| if (ret < 0) { |
| /* Failed summarisation. Ignore and move on. */ |
| HChar mbuf[100]; |
| VG_(snprintf)(mbuf, sizeof(mbuf), |
| "ExtabEntryDecode: failed with error code: %d", ret); |
| mbuf[sizeof(mbuf)-1] = 0; |
| complain(mbuf); |
| } else { |
| /* Successful summarisation. Add it to the collection. */ |
| SubmitStackFrame( di, &state, avma, next_avma - avma ); |
| n_successful++; |
| } |
| |
| } /* iterating over .exidx */ |
| |
| if (VG_(clo_verbosity) > 1) |
| VG_(message)(Vg_DebugMsg, |
| " Reading EXIDX entries: %lu attempted, %lu successful\n", |
| n_attempted, n_successful); |
| } |
| |
| #endif /* defined(VGA_arm) */ |
| |
| /*--------------------------------------------------------------------*/ |
| /*--- end readexidx.c ---*/ |
| /*--------------------------------------------------------------------*/ |