base/regdump.[ch], etc.: Fancy register dumping infrastructure.
[catacomb] / base / regdump.h
diff --git a/base/regdump.h b/base/regdump.h
new file mode 100644 (file)
index 0000000..66933fc
--- /dev/null
@@ -0,0 +1,941 @@
+/* -*-c-*-
+ *
+ * Register dump and debugging support
+ *
+ * (c) 2019 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Catacomb.
+ *
+ * Catacomb is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Library General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Catacomb 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with Catacomb.  If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#ifndef CATACOMB_REGDUMP_H
+#define CATACOMB_REGDUMP_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#ifndef ENABLE_ASM_DEBUG
+#  error "Assembler-level debug disabled by `configure' script."
+#endif
+
+#if __ASSEMBLER__
+#  include "asm-common.h"
+#else
+#  include <float.h>
+#  include <mLib/bits.h>
+#endif
+
+/*----- Random utilities --------------------------------------------------*/
+
+#define DO8(_)                                                         \
+   _(0)  _(1)  _(2)  _(3)  _(4)  _(5)  _(6)  _(7)
+#define DOHI8(_)                                                       \
+   _(8)  _(9) _(10) _(11) _(12) _(13) _(14) _(15)
+
+#define DO16(_) DO8(_) DOHI8(_)
+
+#define DO32(_)                                                                \
+  DO16(_)                                                              \
+  _(16) _(17) _(18) _(19) _(20) _(21) _(22) _(23)                      \
+  _(24) _(25) _(26) _(27) _(28) _(29) _(30) _(31)
+
+/*----- Common data structures --------------------------------------------*/
+
+#if !__ASSEMBLER__
+
+/* The following are good on our assembler targets. */
+typedef signed char int8;
+typedef short int16;
+typedef int int32;
+#if LONG_MAX >> 31 > 0x7fffffff
+  typedef long int64;
+#else
+  typedef long long int64;
+#endif
+typedef float float32;
+typedef double float64;
+typedef long double float80;
+
+#if CPUFAM_X86 || CPUFAM_ARMEL
+#  define PTR32 void *p;
+#  define PTR64
+#endif
+#if CPUFAM_AMD64 || CPUFAM_ARM64
+#  define PTR32
+#  define PTR64 void *p;
+#endif
+
+#define SIMD_COMMON(wd)                                                        \
+  uint8 u8[wd/8];                                                      \
+  int8 i8[wd/8];                                                       \
+  uint16 u16[wd/16];                                                   \
+  int16 i16[wd/16];                                                    \
+  uint32 u32[wd/32];                                                   \
+  int32 i32[wd/32];                                                    \
+  uint64 u64[wd/64];                                                   \
+  int64 i64[wd/64];                                                    \
+  float32 f32[wd/32];                                                  \
+  float64 f64[wd/64]
+
+union gp32 { uint32 u32; int32 i32; PTR32 };
+union gp64 { uint64 u64; int64 i64; PTR64 };
+
+#endif
+
+/*----- Format word layout ------------------------------------------------*/
+
+#define REGF_IXMASK    0x000000ff
+#define REGF_IXSHIFT            0
+/* The index into the vector indicated by `REGF_SRCMASK', if applicable. */
+
+#define REGF_FMTMASK   0x0000ff00
+#define REGF_FMTSHIFT           8
+#define REGF_HEX       0x00000100
+#define REGF_CHR       0x00000200
+#define REGF_FLT       0x00000400
+#define REGF_UNSGN     0x00000800
+#define REGF_SGN       0x00001000
+/* How to format the value(s) found. */
+
+#define REGF_TYMASK    0x00ff0000
+#define REGF_TYSHIFT           16
+#define REGF_80                0x00010000
+#define REGF_64                0x00020000
+#define REGF_32                0x00040000
+#define REGF_16                0x00080000
+#define REGF_8         0x00100000
+/* Size of the value(s) to dump. */
+
+#define REGF_SRCMASK   0x0f000000
+#define   REGSRC_ABS   0x01000000      /* absolute address */
+#define   REGSRC_GP    0x02000000      /* general-purpose register */
+#define   REGSRC_FP    0x03000000      /* floating-point register */
+#define   REGSRC_SIMD  0x04000000      /* SIMD vector register */
+#define   REGSRC_STMMX 0x05000000      /* x86-specific: x87/MMX register */
+#define   REGSRC_SEG   0x06000000      /* x86-specific: segment register */
+/* Where to find the values. */
+
+#define REGF_WDMASK    0xf0000000
+#define REGF_WDSHIFT           28
+/* If we're to print a scalar, this is zero; otherwise, log_2 of the vector
+ * register width, in bits.
+ */
+
+/*----- x86 and AMD64 -----------------------------------------------------*/
+
+#if CPUFAM_X86 || CPUFAM_AMD64
+
+#define   REGIX_FLAGS   0
+#define          REGIX_IP       1
+#define   REGIX_ADDR    2
+#define   REGIX_AX      3
+#define   REGIX_BX      4
+#define   REGIX_CX      5
+#define   REGIX_DX      6
+#define   REGIX_SI      7
+#define   REGIX_DI      8
+#define   REGIX_BP      9
+#define   REGIX_SP     10
+#if CPUFAM_X86
+#  define REGIX_GPLIM  11
+#endif
+#if CPUFAM_AMD64
+#  define REGIX_R8     11
+#  define REGIX_R9     12
+#  define REGIX_R10    13
+#  define REGIX_R11    14
+#  define REGIX_R12    15
+#  define REGIX_R13    16
+#  define REGIX_R14    17
+#  define REGIX_R15    18
+#  define REGIX_GPLIM  19
+#endif
+
+#define REGIX_CS        0
+#define REGIX_DS        1
+#define REGIX_SS        2
+#define REGIX_ES        3
+#define REGIX_FS        4
+#define REGIX_GS        5
+#define REGIX_SEGLIM    6
+
+#define REGIX_FPFLAGS  255
+
+#if !__ASSEMBLER__
+
+#if CPUFAM_X86
+typedef union gp32 gpreg;
+#endif
+#if CPUFAM_AMD64
+typedef union gp64 gpreg;
+#endif
+
+struct gpsave {
+  gpreg gp[REGIX_GPLIM];
+  uint16 seg[REGIX_SEGLIM];
+};
+
+union stmmx {
+  SIMD_COMMON(64);
+#if FLT_RADIX == 2 && LDBL_MANT_DIG == 64
+  long double f80;
+#endif
+unsigned char _pad[16];
+};
+
+union xmm { SIMD_COMMON(128); };
+union ymm { SIMD_COMMON(256); };
+union vreg { union xmm v128[2]; union ymm v256; };
+
+struct fxsave {
+  unsigned short fcw;
+  unsigned short fsw;
+  unsigned char ftw;
+  unsigned char _res0;
+  unsigned short fop;
+#if CPUFAM_X86
+  unsigned int fpu_ip;
+  unsigned short fpu_cs;
+  unsigned short _res1;
+  unsigned int fpu_dp;
+  unsigned short fpu_ds;
+  unsigned short _res2;
+#endif
+#if CPUFAM_AMD64
+  unsigned long long fpu_ip;
+  unsigned long long fpu_dp;
+#endif
+  unsigned int mxcsr;
+  unsigned int mxcsr_mask;
+
+  union stmmx stmmx[8];
+
+#if CPUFAM_X86
+  union xmm xmm[8];
+  unsigned char _pad0[8*16];
+#endif
+#if CPUFAM_AMD64
+  union xmm xmm[16];
+#endif
+
+  unsigned char _pad1[96];
+};
+
+struct xsave_avx {
+#if CPUFAM_X86
+  union xmm ymmh[8];
+  unsigned char _pad0[8*16];
+#endif
+#if CPUFAM_AMD64
+  union xmm ymmh[16];
+#endif
+};
+
+struct regmap {
+  struct gpsave *gp;
+  struct fxsave *fx;
+  struct xsave_avx *avx;
+};
+
+#else
+
+       .extern regdump_gpsave
+       .extern regdump_xtsave
+       .extern regdump_xtrstr
+       .extern regdump_gprstr
+
+       regmap_gp = 0*WORDSZ
+       regmap_fx = 1*WORDSZ
+       regmap_avx = 2*WORDSZ
+       regmap_size = 3*WORDSZ
+
+#define REGDEF_GPX86_COMMON(rn, RN)                                    \
+       regsrc.e##rn = REGSRC_GP | REGIX_##RN;                          \
+       regty.e##rn = REGF_32;                                          \
+       regfmt.e##rn = REGF_HEX;                                        \
+       regsrc.r##rn = REGSRC_GP | REGIX_##RN;                          \
+       regty.r##rn = REGF_64;                                          \
+       regfmt.r##rn = REGF_HEX
+
+#define REGDEF_GPX86_ABCD(rn, RN)                                      \
+       regsrc.rn##hl = (4 << REGF_WDSHIFT) | REGSRC_GP | REGIX_##RN##X; \
+       regty.rn##hl = REGF_8;                                          \
+       regfmt.rn##hl = REGF_HEX;                                       \
+       regsrc.rn##l = REGSRC_GP | REGIX_##RN##X;                       \
+       regty.rn##l = REGF_8;                                           \
+       regfmt.rn##l = REGF_HEX;                                        \
+       regsrc.rn##x = REGSRC_GP | REGIX_##RN##X;                       \
+       regty.rn##x = REGF_16;                                          \
+       regfmt.rn##x = REGF_HEX;                                        \
+       REGDEF_GPX86_COMMON(rn##x, RN##X)
+REGDEF_GPX86_ABCD(a, A)
+REGDEF_GPX86_ABCD(b, B)
+REGDEF_GPX86_ABCD(c, C)
+REGDEF_GPX86_ABCD(d, D)
+
+       regsrc.eflags = REGSRC_GP | REGIX_FLAGS
+       regty.eflags = REGF_32
+       regty.eflags = 0
+
+#if CPUFAM_AMD64
+       regsrc.rflags = REGSRC_GP | REGIX_FLAGS
+       regty.rflags = REGF_64
+       regty.rflags = 0
+#endif
+
+#define REGDEF_GPX86_XP(rn, RN)                                                \
+       regsrc.rn##l = REGSRC_GP | REGIX_##RN;                          \
+       regty.rn##l = REGF_8;                                           \
+       regfmt.rn##l = REGF_HEX;                                        \
+       regsrc.rn = REGSRC_GP | REGIX_##RN;                             \
+       regty.rn = REGF_16;                                             \
+       regfmt.rn = REGF_HEX;                                           \
+       REGDEF_GPX86_COMMON(rn, RN)
+REGDEF_GPX86_XP(ip, IP)
+REGDEF_GPX86_XP(si, SI)
+REGDEF_GPX86_XP(di, DI)
+REGDEF_GPX86_XP(bp, BP)
+REGDEF_GPX86_XP(sp, SP)
+
+#if CPUFAM_AMD64
+#  define REGDEF_GPAMD64(i)                                            \
+       regsrc.r##i##b = REGSRC_GP | REGIX_R##i;                        \
+       regty.r##i##b = REGF_8;                                         \
+       regfmt.r##i##b = REGF_HEX;                                      \
+       regsrc.r##i##w = REGSRC_GP | REGIX_R##i;                        \
+       regty.r##i##w = REGF_16;                                        \
+       regfmt.r##i##w = REGF_HEX;                                      \
+       regsrc.r##i##d = REGSRC_GP | REGIX_R##i;                        \
+       regty.r##i##d = REGF_32;                                        \
+       regfmt.r##i##d = REGF_HEX;                                      \
+       regsrc.r##i = REGSRC_GP | REGIX_R##i;                           \
+       regty.r##i = REGF_64;                                           \
+       regfmt.r##i = REGF_HEX;
+  DOHI8(REGDEF_GPAMD64)
+#endif
+
+#define REGDEF_SEG(rn, RN)                                             \
+       regsrc.rn = REGSRC_SEG | REGIX_##RN;                            \
+       regty.rn = REGF_16;                                             \
+       regfmt.rn = REGF_HEX
+REGDEF_SEG(ss, SS)
+REGDEF_SEG(cs, CS)
+REGDEF_SEG(ds, DS)
+REGDEF_SEG(es, ES)
+REGDEF_SEG(fs, FS)
+REGDEF_SEG(gs, GS)
+
+#define REGDEF_STMMX(i)                                                        \
+       regsrc.st##i = REGSRC_STMMX | i;                                \
+       regty.st##i = REGF_80;                                          \
+       regfmt.st##i = REGF_FLT;                                        \
+       regsrc.mm##i = (6 << REGF_WDSHIFT) | REGSRC_STMMX | i;          \
+       regty.mm##i = REGF_16;                                          \
+       regfmt.mm##i = REGF_HEX;
+DO8(REGDEF_STMMX)
+
+#define REGDEF_SIMD(i)                                                 \
+       regsrc.xmm##i = (7 << REGF_WDSHIFT) | REGSRC_SIMD | i;          \
+       regty.xmm##i = REGF_32;                                         \
+       regfmt.xmm##i = REGF_HEX;                                       \
+       regsrc.ymm##i = (8 << REGF_WDSHIFT) | REGSRC_SIMD | i;          \
+       regty.ymm##i = REGF_32;                                         \
+       regfmt.ymm##i = REGF_HEX;
+DO8(REGDEF_SIMD)
+#if CPUFAM_AMD64
+  DOHI8(REGDEF_SIMD)
+#endif
+
+       REGDUMP_GPSIZE = REGIX_GPLIM*WORDSZ + REGIX_SEGLIM*2
+
+#  if CPUFAM_AMD64 && ABI_SYSV
+       REGDUMP_SPADJ = REGDUMP_GPSIZE + WORDSZ + 128
+#  else
+       REGDUMP_SPADJ = REGDUMP_GPSIZE + WORDSZ
+#  endif
+
+.macro _saveregs addr=nil
+       // Save the registers, leaving r/ebp pointing to the register map.
+
+       // Stash r/eax.  This is bletcherous: hope we don't get a signal in
+       // the next few instructions.
+       mov     [R_sp(r) - REGDUMP_SPADJ + (REGIX_AX - 1)*WORDSZ], R_a(r)
+
+  .ifnes "\addr", "nil"
+       // Collect the effective address for the following dump, leaving it
+       // in the `addr' slot of the dump.
+       lea     R_a(r), \addr
+       mov     [R_sp(r) - REGDUMP_SPADJ + (REGIX_ADDR - 1)*WORDSZ], R_a(r)
+  .endif
+
+       // Make space for the register save area.  On AMD64 with System/V
+       // ABI, also skip the red zone.  Use `lea' here to preserve the
+       // flags.
+       lea     R_sp(r), [R_sp(r) - REGDUMP_SPADJ]
+
+       // Save flags and general-purpose registers.  On 32-bit x86, we save
+       // ebx here and establish a GOT pointer here for the benefit of the
+       // PLT-indirect calls made later on.
+       pushf
+#  if CPUFAM_X86
+       mov     [esp + 4*REGIX_BX], ebx
+       ldgot
+#  endif
+       callext regdump_gpsave
+
+       // Make space for the extended registers.
+       sub     R_sp(r), R_c(r)
+       callext regdump_xtsave
+
+       // Prepare for calling back into C.  On 32-bit x86, leave space for
+       // the arguments and set up the GOT pointer; on AMD64 Windows, leave
+       // the `shadow space' for the called-function's arguments.  Also,
+       // forcibly align the stack pointer to a 16-byte boundary.
+#  if CPUFAM_X86
+       sub     esp, 16
+#  elif ABI_WIN
+       sub     rsp, 32
+#  endif
+       and     R_sp(r), ~15
+.endm
+
+.macro _rstrregs
+       // Restore registers.
+
+       // We assume r/ebp still points to the register map.
+       callext regdump_xtrstr
+       mov     R_sp(r), R_bp(r)
+       callext regdump_gprstr
+       popf
+       lea     R_sp(r), [R_sp(r) + REGDUMP_SPADJ]
+.endm
+
+.macro _regbase
+#  if CPUFAM_X86
+       mov     [esp + 0], ebp
+#  elif ABI_SYSV
+       mov     rdi, rbp
+#  elif ABI_WIN
+       mov     rcx, rbp
+#  endif
+.endm
+
+.macro _membase
+       mov     R_a(r), [R_bp(r) + regmap_gp]
+#  if CPUFAM_X86
+       mov     eax, [eax + REGIX_ADDR*WORDSZ]
+       mov     [esp + 0], eax
+#  elif ABI_SYSV
+       mov     rdi, [rax + REGIX_ADDR*WORDSZ]
+#  elif ABI_WIN
+       mov     rcx, [rax + REGIX_ADDR*WORDSZ]
+#  endif
+.endm
+
+.macro _reglbl msg
+  .ifeqs "\msg", ""
+#  if CPUFAM_X86
+       mov     dword ptr [esp + 4], 0
+#  elif ABI_SYSV
+       xor     esi, esi
+#  elif ABI_WIN
+       xor     edx, edx
+#  endif
+  .else
+#  if CPUFAM_X86
+       lea     eax, [INTADDR(.L$_reglbl$\@)]
+       mov     [esp + 4], eax
+#  elif ABI_SYSV
+       lea     rsi, [INTADDR(.L$_reglbl$\@)]
+#  elif ABI_WIN
+       lea     rdx, [INTADDR(.L$_reglbl$\@)]
+#  endif
+    _LIT
+.L$_reglbl$\@:
+       .asciz  "\msg"
+    _ENDLIT
+  .endif
+.endm
+
+.macro _regfmt arg
+#  if CPUFAM_X86
+       mov     dword ptr [esp + 8], \arg
+#  elif ABI_SYSV
+       mov     edx, \arg
+#  elif ABI_WIN
+       mov     r8d, \arg
+#  endif
+.endm
+
+#endif
+
+#endif
+
+/*----- ARM32 -------------------------------------------------------------*/
+
+#if CPUFAM_ARMEL
+
+#if !__ASSEMBLER__
+extern unsigned regdump__flags;
+#endif
+#define REGF_VFP 1u
+#define REGF_D32 2u
+
+#define REGIX_CPSR 16
+#define REGIX_ADDR 17
+#define REGIX_GPLIM 18
+
+#define REGIX_FPSCR 255
+
+#if !__ASSEMBLER__
+
+union neon64 { SIMD_COMMON(64); };
+union neon128 { SIMD_COMMON(128); };
+
+struct gpsave { union gp32 r[REGIX_GPLIM]; };
+
+struct fpsave {
+  unsigned fpscr;
+  unsigned _pad0;
+  union {
+    float32 s[32];
+    union neon64 d[32];
+    union neon128 q[16];
+  } u;
+};
+
+struct regmap {
+  struct gpsave *gp;
+  struct fpsave *fp;
+};
+
+#else
+
+       .extern regdump_gpsave
+       .extern regdump_xtsave
+       .extern regdump_xtrstr
+       .extern regdump_gprstr
+
+       regmap_gp = 0
+       regmap_fp = 4
+       regmap_size = 8
+
+#define REGDEF_GP(i)                                                   \
+       regsrc.r##i = REGSRC_GP | i;                                    \
+       regty.r##i = REGF_32;                                           \
+       regfmt.r##i = REGF_HEX;
+DO16(REGDEF_GP)
+
+       regsrc.cpsr = REGSRC_GP | REGIX_CPSR
+       regty.cpsr = REGF_32
+       regfmt.cpsr = 0
+
+#define REGDEF_NEONS(i)                                                        \
+       regsrc.s##i = REGSRC_FP | i;                                    \
+       regty.s##i = REGF_32;                                           \
+       regfmt.s##i = REGF_FLT;
+DO32(REGDEF_NEONS)
+
+#define REGDEF_NEOND(i)                                                        \
+       regsrc.d##i = (6 << REGF_WDSHIFT) | REGSRC_FP | i;              \
+       regty.d##i = REGF_32;                                           \
+       regfmt.d##i = REGF_HEX;
+DO32(REGDEF_NEOND)
+
+#define REGDEF_NEONQ(i)                                                        \
+       regsrc.q##i = (7 << REGF_WDSHIFT) | REGSRC_FP | i;              \
+       regty.q##i = REGF_32;                                           \
+       regfmt.q##i = REGF_HEX;
+DO16(REGDEF_NEONQ)
+
+       regsrc.fpscr = REGSRC_FP | REGIX_FPSCR
+       regty.fpscr = REGF_32
+       regfmt.fpscr = 0
+
+       REGDUMP_GPSIZE = 4*REGIX_GPLIM
+       REGDUMP_FPSIZE_D16 = 8 + 16*8
+       REGDUMP_FPSIZE_D32 = 8 + 32*8
+
+.macro _saveregs base=nil, off=#0
+       // Save the registers, leaving r4 pointing to the register map.
+
+       // Stash r14.  This is bletcherous: hope we don't get a signal in
+       // the next few instructions.
+       str     r14, [r13, #-REGDUMP_GPSIZE + 14*4]
+
+  .ifnes "\base,\off", "nil,#0"
+       // Collect the effective address for the following dump, leaving it
+       // in the `addr' slot of the dump.
+    .ifeqs "\base", "nil"
+       adrl    r14, \off
+    .else
+       add     r14, \base, \off
+    .endif
+       str     r14, [r13, #-REGDUMP_GPSIZE + 4*REGIX_ADDR]
+  .endif
+
+       // Make space for the register save area.
+       sub     r13, r13, #REGDUMP_GPSIZE
+
+       // Save flags and general-purpose registers.
+       str     r12, [r13, #4*12]
+       bl      regdump_gpsave
+
+       // Make space for the extended registers.
+       sub     r13, r13, r0
+       bl      regdump_xtsave
+
+       // Prepare for calling back into C.
+       ldgot
+       mov     r0, r13
+       bic     r0, r0, #15
+       mov     r13, r0
+.endm
+
+.macro _rstrregs
+       // Restore registers.
+
+       // We assume r4 still points to the register map.
+       bl      regdump_xtrstr
+       mov     r13, r4
+       bl      regdump_gprstr
+       ldr     r14, [r13, #14*4]
+       add     r13, r13, #REGDUMP_GPSIZE
+.endm
+
+.macro _regbase
+       mov     r0, r5
+.endm
+
+.macro _membase
+       mov     r0, r6
+.endm
+
+.macro _reglbl msg
+       adrl    r1, .L$_reglbl$\@
+    _LIT
+.L$_reglbl$\@:
+       .asciz  "\msg"
+       .balign 4
+    _ENDLIT
+.endm
+
+.macro _regfmt arg
+       movw    r2, #\arg&0xffff
+       movt    r2, #(\arg >> 16)&0xffff
+.endm
+
+#endif
+
+#endif
+
+/*----- ARM64 -------------------------------------------------------------*/
+
+#if CPUFAM_ARM64
+
+#define REGIX_NZCV 32
+#define REGIX_PC 33
+#define REGIX_ADDR 34
+#define REGIX_GPLIM 36
+
+#define REGIX_FPFLAGS 255
+
+#if !__ASSEMBLER__
+
+union v128 { SIMD_COMMON(128); };
+
+struct gpsave { union gp64 r[REGIX_GPLIM]; };
+
+struct fpsave {
+  unsigned fpsr, fpcr;
+  union v128 v[32];
+};
+
+struct regmap {
+  struct gpsave *gp;
+  struct fpsave *fp;
+};
+
+#else
+
+       .extern regdump_gpsave
+       .extern regdump_xtsave
+       .extern regdump_xtrstr
+       .extern regdump_gprstr
+
+       regmap_gp = 0
+       regmap_fp = 8
+       regmap_size = 16
+
+#define REGDEF_GP(i)                                                   \
+       regsrc.x##i = REGSRC_GP | i;                                    \
+       regty.x##i = REGF_64;                                           \
+       regfmt.x##i = REGF_HEX;                                         \
+       regsrc.w##i = REGSRC_GP | i;                                    \
+       regty.w##i = REGF_32;                                           \
+       regfmt.w##i = REGF_HEX;
+DO32(REGDEF_GP)
+
+       regsrc.sp = REGSRC_GP | 31
+       regty.sp = REGF_64
+       regfmt.sp = REGF_HEX
+
+       regsrc.pc = REGSRC_GP | REGIX_PC
+       regty.pc = REGF_64
+       regfmt.pc = REGF_HEX
+
+       regsrc.nzcv = REGSRC_GP | REGIX_NZCV
+       regty.nzcv = REGF_32
+       regfmt.nzcv = 0
+
+#define REGDEF_FP(i)                                                   \
+       regsrc.b##i = REGSRC_FP | i;                                    \
+       regty.b##i = REGF_8;                                            \
+       regfmt.b##i = REGF_HEX;                                         \
+       regsrc.h##i = REGSRC_FP | i;                                    \
+       regty.h##i = REGF_16;                                           \
+       regfmt.h##i = REGF_HEX;                                         \
+       regsrc.s##i = REGSRC_FP | i;                                    \
+       regty.s##i = REGF_32;                                           \
+       regfmt.s##i = REGF_FLT;                                         \
+       regsrc.d##i = REGSRC_FP | i;                                    \
+       regty.d##i = REGF_64;                                           \
+       regfmt.d##i = REGF_FLT;                                         \
+       regsrc.v##i = (7 << REGF_WDSHIFT) | REGSRC_FP | i;              \
+       regty.v##i = REGF_32;                                           \
+       regfmt.v##i = REGF_HEX;
+DO32(REGDEF_FP)
+
+       regsrc.fpflags = REGSRC_FP | REGIX_FPFLAGS
+       regty.fpflags = REGF_32
+       regfmt.fpflags = 0
+
+       REGDUMP_GPSIZE = 8*REGIX_GPLIM
+       REGDUMP_FPSIZE = 16 + 16 + 32*16
+
+.macro _saveregs base=nil, off=#0
+       // Save the registers, leaving x20 pointing to the register map.
+
+       // Stash x30.  This is bletcherous: hope we don't get a signal in
+       // the next few instructions.
+       str     x30, [sp, #-REGDUMP_GPSIZE + 30*8]
+
+  .ifnes "\base,\off", "nil,#0"
+       // Collect the effective address for the following dump, leaving it
+       // in the `addr' slot of the dump.
+    .ifeqs "\base", "nil"
+       adr     x30, \off
+    .else
+       add     x30, \base, \off
+    .endif
+       str     x30, [sp, #-REGDUMP_GPSIZE + 8*REGIX_ADDR]
+  .endif
+
+       // Make space for the register save area.
+       sub     sp, sp, #REGDUMP_GPSIZE
+
+       // Save flags and general-purpose registers.
+       stp     x16, x17, [sp, #8*16]
+       bl      regdump_gpsave
+
+       // Make space for the extended registers.
+       sub     sp, sp, x0
+       bl      regdump_xtsave
+.endm
+
+.macro _rstrregs
+       // Restore registers.
+
+       // We assume x21 still points to the register map.
+       bl      regdump_xtrstr
+       mov     sp, x20
+       bl      regdump_gprstr
+       ldr     x30, [sp, #30*8]
+       add     sp, sp, #REGDUMP_GPSIZE
+.endm
+
+.macro _regbase
+       mov     x0, x21
+.endm
+
+.macro _membase
+       mov     x0, x22
+.endm
+
+.macro _reglbl msg
+       adr     x1, .L$_reglbl$\@
+    _LIT
+.L$_reglbl$\@:
+       .asciz  "\msg"
+       .balign 4
+    _ENDLIT
+.endm
+
+.macro _regfmt arg
+       movz    w2, #\arg&0xffff
+       movk    w2, #(\arg >> 16)&0xffff, lsl #16
+.endm
+
+#endif
+
+#endif
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @regdump_init@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Performs one-time initialization for register dumping.  In
+ *             particular, this performs CPU feature detection on platforms
+ *             where that is a difficult task: without it, registers
+ *             corresponding to optional architectural features can be
+ *             neither printed nor preserved by the register-dump machinery.
+ */
+
+#if !__ASSEMBLER__
+extern void regdump_init(void);
+#endif
+
+/* --- @regdump@ --- *
+ *
+ * Arguments:  @const void *base@ = pointer to base structure, corresponding
+ *                     to the @REGF_SRCMASK@ part of @f@
+ *             @const char *lbl@ = label to print
+ *             @uint32 f@ = format control word; see @REGF_...@
+ *
+ * Returns:    ---
+ *
+ * Use:                Dump a register value, or chunk of memory.
+ *
+ *             This function is not usually called directly; instead, use
+ *             the `reg' or `mem' assembler macros.
+ */
+
+#if !__ASSEMBLER__
+extern void regdump(const void *base, const char *lbl, uint32 f);
+#else
+       .extern regdump
+#endif
+
+/* --- @regdump_gp@, @regdump_fp@, @regdump_simd@ --- *
+ *
+ * Arguments:  @const struct regmap *map@ = pointer to register map
+ *
+ * Returns:    ---
+ *
+ * Use:                Dump the general-purpose/floating-point/SIMD registers.
+ *
+ *             This function is not usually called directly; instead, use
+ *             the `regdump' assembler macro.
+ */
+
+#if !__ASSEMBLER__
+extern void regdump_gp(const struct regmap */*map*/);
+extern void regdump_fp(const struct regmap */*map*/);
+extern void regdump_simd(const struct regmap */*map*/);
+#else
+       .extern regdump_gp
+       .extern regdump_fp
+       .extern regdump_simd
+#endif
+
+/* --- @regdump_freshline@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Begin a fresh line of output.
+ */
+
+#if !__ASSEMBLER__
+extern void regdump_freshline(void);
+#else
+       .extern regdump_freshline
+#endif
+
+/*----- Main user interface macros ----------------------------------------*/
+
+#if __ASSEMBLER__
+
+.macro terpri
+       _saveregs
+       callext regdump_freshline
+       _rstrregs
+.endm
+
+.macro reg     lbl, rn, fmt=0
+       _saveregs
+       _regbase
+       _reglbl "\lbl"
+       .L$reg.fmt$\@ = regsrc.\rn | \fmt | \
+                (((\fmt&REGF_TYMASK) == 0)&regty.\rn) | \
+                (((\fmt&REGF_FMTMASK) == 0)&regfmt.\rn)
+       _regfmt .L$reg.fmt$\@
+       callext regdump
+       _rstrregs
+.endm
+
+.macro mem     lbl, addr, fmt=0
+       _saveregs \addr
+       _membase
+       _reglbl "\lbl"
+       .L$mem.fmt$\@ = REGSRC_ABS | \fmt | \
+                (((\fmt&REGF_TYMASK) == 0)&REGF_32) | \
+                (((\fmt&REGF_FMTMASK) == 0)&REGF_HEX)
+       _regfmt .L$mem.fmt$\@
+       callext regdump
+       _rstrregs
+.endm
+
+.macro regdump gp=nil, fp=nil, simd=nil
+       _saveregs
+  .ifnes "\gp", "nil"
+       _regbase
+       callext regdump_gp
+  .endif
+  .ifnes "\fp", "nil"
+       _regbase
+       callext regdump_fp
+  .endif
+  .ifnes "\simd", "nil"
+       _regbase
+       callext regdump_simd
+  .endif
+       _rstrregs
+.endm
+
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif