From fdfada9f3569a2a2b22ca8b134152b2811b4abe0 Mon Sep 17 00:00:00 2001 From: Julian Seward Date: Thu, 31 Jul 2014 14:25:29 +0000 Subject: [PATCH] Add support for stack unwinding using the ARM32 specific EXIDX format. git-svn-id: svn://svn.valgrind.org/valgrind/trunk@14217 --- NEWS | 4 + coregrind/Makefile.am | 2 + coregrind/m_debuginfo/debuginfo.c | 1 + coregrind/m_debuginfo/priv_readexidx.h | 50 ++ coregrind/m_debuginfo/priv_storage.h | 18 +- coregrind/m_debuginfo/readelf.c | 111 ++- coregrind/m_debuginfo/readexidx.c | 1097 ++++++++++++++++++++++++ coregrind/m_debuginfo/storage.c | 4 +- 8 files changed, 1272 insertions(+), 15 deletions(-) create mode 100644 coregrind/m_debuginfo/priv_readexidx.h create mode 100644 coregrind/m_debuginfo/readexidx.c diff --git a/NEWS b/NEWS index 1231937c8c..53f33fc119 100644 --- a/NEWS +++ b/NEWS @@ -90,6 +90,10 @@ Release 3.10.0 (?? ?????? 201?) * Reduction of memory used by Valgrind to read and store the debug information. +* Valgrind can now read EXIDX unwind information on 32-bit ARM targets. + If an object contains both CFI and EXIDX unwind information, Valgrind + will prefer the CFI over the EXIDX. + * ==================== FIXED BUGS ==================== The following bugs have been fixed or resolved. Note that "n-i-bz" diff --git a/coregrind/Makefile.am b/coregrind/Makefile.am index 655a462c68..aabadcb73b 100644 --- a/coregrind/Makefile.am +++ b/coregrind/Makefile.am @@ -222,6 +222,7 @@ noinst_HEADERS = \ m_debuginfo/priv_readdwarf.h \ m_debuginfo/priv_readdwarf3.h \ m_debuginfo/priv_readelf.h \ + m_debuginfo/priv_readexidx.h \ m_debuginfo/priv_readmacho.h \ m_debuginfo/priv_image.h \ m_debuginfo/lzoconf.h \ @@ -319,6 +320,7 @@ COREGRIND_SOURCES_COMMON = \ m_debuginfo/readdwarf.c \ m_debuginfo/readdwarf3.c \ m_debuginfo/readelf.c \ + m_debuginfo/readexidx.c \ m_debuginfo/readmacho.c \ m_debuginfo/readpdb.c \ m_debuginfo/readstabs.c \ diff --git a/coregrind/m_debuginfo/debuginfo.c b/coregrind/m_debuginfo/debuginfo.c index 02a2139178..7db460866c 100644 --- a/coregrind/m_debuginfo/debuginfo.c +++ b/coregrind/m_debuginfo/debuginfo.c @@ -2378,6 +2378,7 @@ UWord evalCfiExpr ( XArray* exprs, Int ix, case Creg_ARM_R14: return eec->uregs->r14; case Creg_ARM_R13: return eec->uregs->r13; case Creg_ARM_R12: return eec->uregs->r12; + case Creg_ARM_R7: return eec->uregs->r7; # elif defined(VGA_s390x) case Creg_IA_IP: return eec->uregs->ia; case Creg_IA_SP: return eec->uregs->sp; diff --git a/coregrind/m_debuginfo/priv_readexidx.h b/coregrind/m_debuginfo/priv_readexidx.h new file mode 100644 index 0000000000..8b21ffb147 --- /dev/null +++ b/coregrind/m_debuginfo/priv_readexidx.h @@ -0,0 +1,50 @@ +/* -*- mode: C; c-basic-offset: 3; -*- */ + +/*--------------------------------------------------------------------*/ +/*--- Reading of ARM(32) EXIDX unwind information ---*/ +/*-- priv_readexidx.h ---*/ +/*--------------------------------------------------------------------*/ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2014-2014 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. +*/ + +/* Contributed by Julian Seward */ + +#ifndef __PRIV_READEXIDX_H +#define __PRIV_READEXIDX_H + +#include "pub_core_debuginfo.h" // DebugInfo + +extern +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 ); + +#endif /* ndef __PRIV_READEXIDX_H */ + +/*--------------------------------------------------------------------*/ +/*--- end priv_readexidx.h ---*/ +/*--------------------------------------------------------------------*/ diff --git a/coregrind/m_debuginfo/priv_storage.h b/coregrind/m_debuginfo/priv_storage.h index 817efaed9f..ccf6742e7e 100644 --- a/coregrind/m_debuginfo/priv_storage.h +++ b/coregrind/m_debuginfo/priv_storage.h @@ -274,6 +274,8 @@ typedef Int r12_off; Int r11_off; Int r7_off; + // If you add additional fields, don't forget to update the + // initialisation of this in readexidx.c accordingly. } DiCfSI_m; #elif defined(VGA_arm64) @@ -369,13 +371,15 @@ typedef typedef enum { - Creg_IA_SP=0x213, + Creg_INVALID=0x213, + Creg_IA_SP, Creg_IA_BP, Creg_IA_IP, Creg_ARM_R13, Creg_ARM_R12, Creg_ARM_R15, Creg_ARM_R14, + Creg_ARM_R7, Creg_ARM64_X30, Creg_S390_R14, Creg_MIPS_RA @@ -788,6 +792,18 @@ struct _DebugInfo { PtrdiffT sbss_bias; Addr sbss_debug_svma; PtrdiffT sbss_debug_bias; + /* .ARM.exidx -- sometimes present on arm32, containing unwind info. */ + Bool exidx_present; + Addr exidx_avma; + Addr exidx_svma; + SizeT exidx_size; + PtrdiffT exidx_bias; + /* .ARM.extab -- sometimes present on arm32, containing unwind info. */ + Bool extab_present; + Addr extab_avma; + Addr extab_svma; + SizeT extab_size; + PtrdiffT extab_bias; /* .plt */ Bool plt_present; Addr plt_avma; diff --git a/coregrind/m_debuginfo/readelf.c b/coregrind/m_debuginfo/readelf.c index 9c69750aab..839f56ccaf 100644 --- a/coregrind/m_debuginfo/readelf.c +++ b/coregrind/m_debuginfo/readelf.c @@ -51,6 +51,7 @@ #include "priv_readdwarf.h" /* 'cos ELF contains DWARF */ #include "priv_readdwarf3.h" #include "priv_readstabs.h" /* and stabs, if we're unlucky */ +#include "priv_readexidx.h" /* --- !!! --- EXTERNAL HEADERS start --- !!! --- */ #include @@ -2176,6 +2177,50 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) } } + /* Accept .ARM.exidx where mapped as rx (code). */ + /* FIXME: make sure the entire section is mapped in, not just + the first address. */ + if (0 == VG_(strcmp)(name, ".ARM.exidx")) { + if (inrx && !di->exidx_present) { + di->exidx_present = True; + di->exidx_svma = svma; + di->exidx_avma = svma + inrx->bias; + di->exidx_size = size; + di->exidx_bias = inrx->bias; + TRACE_SYMTAB("acquiring .exidx svma = %#lx .. %#lx\n", + di->exidx_svma, + di->exidx_svma + di->exidx_size - 1); + TRACE_SYMTAB("acquiring .exidx avma = %#lx .. %#lx\n", + di->exidx_avma, + di->exidx_avma + di->exidx_size - 1); + TRACE_SYMTAB("acquiring .exidx bias = %#lx\n", di->exidx_bias); + } else { + BAD(".ARM.exidx"); + } + } + + /* Accept .ARM.extab where mapped as rx (code). */ + /* FIXME: make sure the entire section is mapped in, not just + the first address. */ + if (0 == VG_(strcmp)(name, ".ARM.extab")) { + if (inrx && !di->extab_present) { + di->extab_present = True; + di->extab_svma = svma; + di->extab_avma = svma + inrx->bias; + di->extab_size = size; + di->extab_bias = inrx->bias; + TRACE_SYMTAB("acquiring .extab svma = %#lx .. %#lx\n", + di->extab_svma, + di->extab_svma + di->extab_size - 1); + TRACE_SYMTAB("acquiring .extab avma = %#lx .. %#lx\n", + di->extab_avma, + di->extab_avma + di->extab_size - 1); + TRACE_SYMTAB("acquiring .extab bias = %#lx\n", di->extab_bias); + } else { + BAD(".ARM.extab"); + } + } + ML_(dinfo_free)(name); # undef BAD @@ -2729,6 +2774,7 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) vg_assert((dynsym_escn.szB % sizeof(ElfXX_Sym)) == 0); vg_assert((symtab_escn.szB % sizeof(ElfXX_Sym)) == 0); + /* TOPLEVEL */ /* Read symbols */ { void (*read_elf_symtab)(struct _DebugInfo*, const HChar*, @@ -2746,7 +2792,7 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) read_elf_symtab(di, "dynamic symbol table", &dynsym_escn, &dynstr_escn, &opd_escn, False); - } /* Read symbols */ + } /* TOPLEVEL */ /* Read .eh_frame and .debug_frame (call-frame-info) if any. Do @@ -2781,13 +2827,15 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) && !defined(VGPV_arm_linux_android) \ && !defined(VGPV_x86_linux_android) \ && !defined(VGP_mips64_linux) -#if 0 - if (stab_img && stabstr_img) { - ML_(read_debuginfo_stabs) ( di, stab_img, stab_sz, - stabstr_img, stabstr_sz ); - } -#endif + // JRS 31 July 2014: stabs reading is currently broken and + // therefore deactivated. + //if (stab_img && stabstr_img) { + // ML_(read_debuginfo_stabs) ( di, stab_img, stab_sz, + // stabstr_img, stabstr_sz ); + //} # endif + + /* TOPLEVEL */ /* jrs 2006-01-01: icc-8.1 has been observed to generate binaries without debug_str sections. Don't preclude debuginfo reading for that reason, but, in @@ -2820,13 +2868,50 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) ); } } -#if 0 - if (dwarf1d_img && dwarf1l_img) { - ML_(read_debuginfo_dwarf1) ( di, dwarf1d_img, dwarf1d_sz, - dwarf1l_img, dwarf1l_sz ); - } -#endif + + /* TOPLEVEL */ + // JRS 31 July 2014: dwarf-1 reading is currently broken and + // therefore deactivated. + //if (dwarf1d_img && dwarf1l_img) { + // ML_(read_debuginfo_dwarf1) ( di, dwarf1d_img, dwarf1d_sz, + // dwarf1l_img, dwarf1l_sz ); + //} + +# if defined(VGA_arm) /* TOPLEVEL */ + /* ARM32 only: read .exidx/.extab if present. Note we are + reading these directly out of the mapped in (running) image. + Also, read these only if no CFI based unwind info was + acquired for this file. + + An .exidx section is always required, but the .extab section + can be optionally omitted, provided that .exidx does not + refer to it. If the .exidx is erroneous and does refer to + .extab even though .extab is missing, the range checks done + by GET_EX_U32 in ExtabEntryExtract in readexidx.c should + prevent any invalid memory accesses, and cause the .extab to + be rejected as invalid. + + FIXME: + * check with m_aspacemgr that the entire [exidx_avma, +exidx_size) + and [extab_avma, +extab_size) areas are readable, since we're + reading this stuff out of the running image (not from a file/socket) + and we don't want to segfault. + * DebugInfo::exidx_bias and use text_bias instead. + I think it's always the same. + * remove DebugInfo::{extab_bias, exidx_svma, extab_svma} since + they are never used. + */ + if (di->exidx_present + && di->cfsi_used == 0 + && di->text_present && di->text_size > 0) { + Addr text_last_svma = di->text_svma + di->text_size - 1; + ML_(read_exidx)( di, (UChar*)di->exidx_avma, di->exidx_size, + (UChar*)di->extab_avma, di->extab_size, + text_last_svma, + di->exidx_bias ); + } +# endif /* defined(VGA_arm) */ } /* "Find interesting sections, read the symbol table(s), read any debug information" (a local scope) */ diff --git a/coregrind/m_debuginfo/readexidx.c b/coregrind/m_debuginfo/readexidx.c new file mode 100644 index 0000000000..ab343679f8 --- /dev/null +++ b/coregrind/m_debuginfo/readexidx.c @@ -0,0 +1,1097 @@ +/* -*- 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-2014 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 */ + + +// 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 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 ((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), (void*)(_addr), 4)) \ + return ExInBufOverflow; \ + (_lval) = *(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 + 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 = (UInt*)(Prel31ToAddr(&entry->data)); + GET_EXTAB_U32(data, extbl_data); + if (!(data & ARM_EXIDX_COMPACT)) { + // This denotes a "generic model" handler. That will involve + // executing arbitary 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 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, (UInt)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", (Int)how, (Int)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, 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%lx, avma 0x%lx\n", + (UWord)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 %u" + "; using 1 instead.", (UInt)(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 ---*/ +/*--------------------------------------------------------------------*/ diff --git a/coregrind/m_debuginfo/storage.c b/coregrind/m_debuginfo/storage.c index 50522c7206..ce40ea6a99 100644 --- a/coregrind/m_debuginfo/storage.c +++ b/coregrind/m_debuginfo/storage.c @@ -696,7 +696,7 @@ void ML_(addDiCfSI) ( struct _DebugInfo* di, if (VG_(clo_verbosity) > 1) { VG_(message)( Vg_DebugMsg, - "warning: DiCfSI %#lx .. %#lx outside mapped rw segments (%s)\n", + "warning: DiCfSI %#lx .. %#lx outside mapped rx segments (%s)\n", base, base + len - 1, di->soname @@ -875,6 +875,7 @@ static void ppCfiBinop ( CfiBinop op ) static void ppCfiReg ( CfiReg reg ) { switch (reg) { + case Creg_INVALID: VG_(printf)("Creg_INVALID"); break; case Creg_IA_SP: VG_(printf)("xSP"); break; case Creg_IA_BP: VG_(printf)("xBP"); break; case Creg_IA_IP: VG_(printf)("xIP"); break; @@ -882,6 +883,7 @@ static void ppCfiReg ( CfiReg reg ) case Creg_ARM_R12: VG_(printf)("R12"); break; case Creg_ARM_R15: VG_(printf)("R15"); break; case Creg_ARM_R14: VG_(printf)("R14"); break; + case Creg_ARM_R7: VG_(printf)("R7"); break; case Creg_ARM64_X30: VG_(printf)("X30"); break; case Creg_MIPS_RA: VG_(printf)("RA"); break; case Creg_S390_R14: VG_(printf)("R14"); break; -- 2.47.2