Add support for stack unwinding using the ARM32 specific EXIDX format.



git-svn-id: svn://svn.valgrind.org/valgrind/trunk@14217 a5019735-40e9-0310-863c-91ae7b9d1cf9
diff --git a/NEWS b/NEWS
index 1231937..53f33fc 100644
--- a/NEWS
+++ b/NEWS
@@ -90,6 +90,10 @@
 
 * 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 655a462..aabadcb 100644
--- a/coregrind/Makefile.am
+++ b/coregrind/Makefile.am
@@ -222,6 +222,7 @@
 	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 @@
 	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 02a2139..7db4608 100644
--- a/coregrind/m_debuginfo/debuginfo.c
+++ b/coregrind/m_debuginfo/debuginfo.c
@@ -2378,6 +2378,7 @@
             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 0000000..8b21ffb
--- /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 <jseward@acm.org> */
+
+#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 817efae..ccf6742 100644
--- a/coregrind/m_debuginfo/priv_storage.h
+++ b/coregrind/m_debuginfo/priv_storage.h
@@ -274,6 +274,8 @@
       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
    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 @@
    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 9c69750..839f56c 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 <elf.h>
@@ -2176,6 +2177,50 @@
          }
       }
 
+      /* 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 @@
       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 @@
          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 @@
          && !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 @@
             );
          }
       }
-#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 0000000..ab34367
--- /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 <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 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 50522c7..ce40ea6 100644
--- a/coregrind/m_debuginfo/storage.c
+++ b/coregrind/m_debuginfo/storage.c
@@ -696,7 +696,7 @@
          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 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 @@
       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;