diff --git a/api/docs/release.dox b/api/docs/release.dox
index f29f7db5f02..44907b8488d 100644
--- a/api/docs/release.dox
+++ b/api/docs/release.dox
@@ -137,7 +137,9 @@ changes:
Further non-compatibility-affecting changes include:
- Added X64 Linux support to dr_create_memory_dump(). This API has the same
- restriction as dr_suspend_all_other_threads_ex().
+ restriction as dr_suspend_all_other_threads_ex(). For X86_64 platform, the feature is
+ supported only when fast FP save and restore is supported. And mixed mode is not
+ supported.
**************************************************
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index b23ebf77a66..de04cc9e328 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -390,7 +390,9 @@ if (UNIX)
set(OS_SRCS ${OS_SRCS} unix/loader_android.c)
else ()
set(OS_SRCS ${OS_SRCS} unix/loader_linux.c)
- set(OS_SRCS ${OS_SRCS} unix/coredump.c)
+ if (AARCH64 OR (X86 AND X64))
+ set(OS_SRCS ${OS_SRCS} unix/coredump.c)
+ endif ()
endif ()
set(OS_SRCS ${OS_SRCS} unix/memquery_linux.c)
set(OS_SRCS ${OS_SRCS} unix/memquery.c)
diff --git a/core/lib/dr_tools.h b/core/lib/dr_tools.h
index 3f453407bf3..8a56eb77205 100644
--- a/core/lib/dr_tools.h
+++ b/core/lib/dr_tools.h
@@ -380,7 +380,11 @@ DR_API
*
* \return whether successful.
*
- * \note this function is only supported on Windows for now.
+ * \note this function is only supported on Windows and X64 Linux only. For X64
+ * Linux, this API has the same restriction as
+ * dr_suspend_all_other_threads_ex(). For X86_64 platform, fast FP save and
+ * restore (fxsave64) support is required. And mixed mode (a process mixing
+ * 64-bit and 32-bit code) is not supported.
*/
bool
dr_create_memory_dump(dr_memory_dump_spec_t *spec);
diff --git a/core/lib/instrument.c b/core/lib/instrument.c
index 17b7ce86f2e..426c1a76869 100644
--- a/core/lib/instrument.c
+++ b/core/lib/instrument.c
@@ -2498,9 +2498,10 @@ dr_create_memory_dump(dr_memory_dump_spec_t *spec)
#ifdef WINDOWS
if (TEST(DR_MEMORY_DUMP_LDMP, spec->flags))
return os_dump_core_live(spec->label, spec->ldmp_path, spec->ldmp_path_size);
-#elif defined(LINUX) && defined(X64)
- if (TEST(DR_MEMORY_DUMP_ELF, spec->flags))
- return os_dump_core_live();
+#elif defined(LINUX) && ((defined(X64) && defined(X86)) || defined(AARCH64))
+ if (TEST(DR_MEMORY_DUMP_ELF, spec->flags)) {
+ return os_dump_core_live(get_thread_private_dcontext());
+ }
#endif
return false;
}
diff --git a/core/unix/coredump.c b/core/unix/coredump.c
index 9d5fa02acbc..2c1aa99e406 100644
--- a/core/unix/coredump.c
+++ b/core/unix/coredump.c
@@ -29,22 +29,50 @@
#include
#include
#include
+#include
#include "../globals.h"
#include "../hashtable.h"
#include "../os_shared.h"
#include "../synch.h"
#include "../utils.h"
+#include "../ir/decode.h"
+#include "../lib/dr_ir_utils.h"
#include "../lib/globals_api.h"
#include "../lib/globals_shared.h"
+#include "../lib/instrument.h"
+
+#include "dr_tools.h"
#include "elf_defines.h"
#include "memquery.h"
+/* Only X64 is supported. */
+#ifndef X64
+# error Unsupported architecture
+#endif
+
#define MAX_SECTION_HEADERS 300
#define MAX_SECTION_NAME_BUFFER_SIZE 8192
#define SECTION_HEADER_TABLE ".shstrtab"
#define VVAR_SECTION "[vvar]"
#define VSYSCALL_SECTION "[vsyscall]"
+/*
+ * The length of the name has to be a multiple of eight (for X64) to ensure the next
+ * field (descriptor) is 8-byte aligned. Null characters are added at the end as
+ * padding to increase the length to eight. The name CORE is used following the example
+ * of core dump files.
+ */
+#define NOTE_OWNER "CORE\0\0\0\0"
+#define NOTE_OWNER_LENGTH 8
+/*
+ * Two notes, NT_PRSTATUS (prstatus structure) and NT_FPREGSET (floating point registers),
+ * are written to the output file.
+ */
+#define PROGRAM_HEADER_NOTE_LENGTH \
+ (sizeof(ELF_NOTE_HEADER_TYPE) + NOTE_OWNER_LENGTH + sizeof(struct elf_prstatus) + \
+ sizeof(ELF_NOTE_HEADER_TYPE) + NOTE_OWNER_LENGTH + sizeof(elf_fpregset_t))
+#define PROGRAM_HEADER_LENGTH \
+ (sizeof(ELF_PROGRAM_HEADER_TYPE) + PROGRAM_HEADER_NOTE_LENGTH)
typedef struct _section_header_info_t {
app_pc vm_start;
@@ -53,6 +81,31 @@ typedef struct _section_header_info_t {
ELF_ADDR name_offset;
} section_header_info_t;
+/*
+ * Please reference
+ * https://www.intel.com/content/www/us/en/docs/cpp-compiler/developer-guide-reference/
+ * 2021-8/intrinsics-to-save-and-restore-ext-proc-states.html for the memory layout
+ * used by fxsave64.
+ */
+typedef struct _fxsave64_map_t {
+ unsigned short fcw;
+ unsigned short fsw;
+ unsigned short ftw;
+ unsigned short reserved1;
+ unsigned short fop;
+ unsigned int fip;
+ unsigned short fcs;
+ unsigned short reserved2;
+ unsigned int fdp;
+ unsigned int fds;
+ unsigned short reserved3;
+ unsigned short mxcsr;
+ unsigned short mxcsr_mask;
+ unsigned int st_space[32];
+ unsigned int xmm_space[64];
+ unsigned int padding[24];
+} fxsave64_map_t;
+
/*
* Writes an ELF header to the file. Returns true if the ELF header is written to the core
* dump file, false otherwise.
@@ -154,13 +207,186 @@ write_section_header(DR_PARAM_IN file_t elf_file,
return os_write(elf_file, (void *)&shdr, sizeof(shdr)) == sizeof(shdr);
}
+/*
+ * Copy dr_mcontext_t register values to user_regs_struct.
+ */
+static void
+mcontext_to_user_regs(DR_PARAM_IN priv_mcontext_t *mcontext,
+ DR_PARAM_OUT struct user_regs_struct *regs)
+{
+#ifdef DR_HOST_NOT_TARGET
+ return;
+#elif defined(X86)
+ regs->rax = mcontext->rax;
+ regs->rcx = mcontext->rcx;
+ regs->rdx = mcontext->rdx;
+ regs->rbx = mcontext->rbx;
+ regs->rsp = mcontext->rsp;
+ regs->rbp = mcontext->rbp;
+ regs->rsi = mcontext->rsi;
+ regs->rdi = mcontext->rdi;
+ regs->r8 = mcontext->r8;
+ regs->r9 = mcontext->r9;
+ regs->r10 = mcontext->r10;
+ regs->r11 = mcontext->r11;
+ regs->r12 = mcontext->r12;
+ regs->r13 = mcontext->r13;
+ regs->r14 = mcontext->r14;
+ regs->r15 = mcontext->r15;
+ regs->rip = (uint64_t)mcontext->rip;
+ regs->eflags = mcontext->rflags;
+#elif defined(AARCH64)
+ regs->regs[0] = mcontext->r0;
+ regs->regs[1] = mcontext->r1;
+ regs->regs[2] = mcontext->r2;
+ regs->regs[3] = mcontext->r3;
+ regs->regs[4] = mcontext->r4;
+ regs->regs[5] = mcontext->r5;
+ regs->regs[6] = mcontext->r6;
+ regs->regs[7] = mcontext->r7;
+ regs->regs[8] = mcontext->r8;
+ regs->regs[9] = mcontext->r9;
+ regs->regs[10] = mcontext->r10;
+ regs->regs[11] = mcontext->r11;
+ regs->regs[12] = mcontext->r12;
+ regs->regs[13] = mcontext->r13;
+ regs->regs[14] = mcontext->r14;
+ regs->regs[15] = mcontext->r15;
+ regs->regs[16] = mcontext->r16;
+ regs->regs[17] = mcontext->r17;
+ regs->regs[18] = mcontext->r18;
+ regs->regs[19] = mcontext->r19;
+ regs->regs[20] = mcontext->r20;
+ regs->regs[21] = mcontext->r21;
+ regs->regs[22] = mcontext->r22;
+ regs->regs[23] = mcontext->r23;
+ regs->regs[24] = mcontext->r24;
+ regs->regs[25] = mcontext->r25;
+ regs->regs[26] = mcontext->r26;
+ regs->regs[27] = mcontext->r27;
+ regs->regs[28] = mcontext->r28;
+ regs->regs[29] = mcontext->r29;
+ regs->regs[30] = mcontext->r30;
+ regs->sp = mcontext->sp;
+ regs->pc = (uint64_t)mcontext->pc;
+#else
+# error Unsupported architecture
+#endif
+}
+
+/*
+ * Write prstatus structure to the file. Returns true if the note is written to
+ * the file, false otherwise.
+ */
+static bool
+write_prstatus_note(DR_PARAM_IN priv_mcontext_t *mc, DR_PARAM_IN file_t elf_file)
+{
+ struct elf_prstatus prstatus;
+ struct user_regs_struct *regs = (struct user_regs_struct *)&prstatus.pr_reg;
+ mcontext_to_user_regs(mc, regs);
+
+ ELF_NOTE_HEADER_TYPE nhdr;
+ // Add one to include the terminating null character.
+ nhdr.n_namesz = strlen(NOTE_OWNER) + 1;
+ nhdr.n_descsz = sizeof(prstatus);
+ nhdr.n_type = NT_PRSTATUS;
+ if (os_write(elf_file, &nhdr, sizeof(nhdr)) != sizeof(nhdr)) {
+ return false;
+ }
+ if (os_write(elf_file, NOTE_OWNER, NOTE_OWNER_LENGTH) != NOTE_OWNER_LENGTH) {
+ return false;
+ }
+ return os_write(elf_file, &prstatus, sizeof(prstatus)) == sizeof(prstatus);
+}
+
+/*
+ * Copy register values from dr_mcontext_t to elf_fpregset_t for AARCH64. For
+ * X86, copy the floating point registers from the output of fxsave64.
+ */
+static bool
+get_floating_point_registers(DR_PARAM_IN dcontext_t *dcontext,
+ DR_PARAM_IN priv_mcontext_t *mc,
+ DR_PARAM_OUT elf_fpregset_t *regs)
+{
+#ifdef DR_HOST_NOT_TARGET
+ return false;
+#elif defined(X86)
+ // Fast FP save and restore support is required.
+ ASSERT(proc_has_feature(FEATURE_FXSR));
+ // Mixed mode is not supported.
+ ASSERT(X64_MODE_DC(dcontext));
+ byte fpstate_buf[MAX_FP_STATE_SIZE];
+ fxsave64_map_t *fpstate =
+ (fxsave64_map_t *)ALIGN_FORWARD(fpstate_buf, DR_FPSTATE_ALIGN);
+ if (proc_save_fpstate((byte *)fpstate) != DR_FPSTATE_BUF_SIZE) {
+ return false;
+ }
+
+ regs->cwd = fpstate->fcw;
+ regs->swd = fpstate->fsw;
+ regs->ftw = fpstate->ftw;
+ regs->fop = fpstate->fop;
+ regs->mxcsr = fpstate->mxcsr;
+ regs->mxcr_mask = fpstate->mxcsr;
+ /* 8*16 bytes for each FP-reg = 128 bytes */
+ for (int i = 0; i < 32; ++i) {
+ regs->st_space[i] = fpstate->st_space[i];
+ }
+ /* 16*16 bytes for each XMM-reg = 256 bytes */
+ for (int i = 0; i < 64; ++i) {
+ regs->xmm_space[i] = fpstate->xmm_space[i];
+ }
+ return true;
+#elif defined(AARCH64)
+ regs->fpsr = mc->fpsr;
+ regs->fpcr = mc->fpcr;
+ for (int i = 0; i < MCXT_NUM_SIMD_SVE_SLOTS; ++i) {
+ memcpy(®s->vregs[i], &mc->simd[i].q, sizeof(mc->simd[i].q));
+ }
+ return true;
+#else
+ return false;
+#endif
+}
+
+/*
+ * Write floating point registers to the file. Returns true if the note is written to
+ * the file, false otherwise.
+ */
+static bool
+write_fpregset_note(DR_PARAM_IN dcontext_t *dcontext, DR_PARAM_IN priv_mcontext_t *mc,
+ DR_PARAM_IN file_t elf_file)
+{
+ elf_fpregset_t fpregset;
+ if (!get_floating_point_registers(dcontext, mc, &fpregset)) {
+ return false;
+ }
+
+ ELF_NOTE_HEADER_TYPE nhdr;
+ // Add one to include the terminating null character.
+ nhdr.n_namesz = strlen(NOTE_OWNER) + 1;
+ nhdr.n_descsz = sizeof(fpregset);
+ nhdr.n_type = NT_FPREGSET;
+ if (os_write(elf_file, &nhdr, sizeof(nhdr)) != sizeof(nhdr)) {
+ return false;
+ }
+ if (os_write(elf_file, NOTE_OWNER, NOTE_OWNER_LENGTH) != NOTE_OWNER_LENGTH) {
+ return false;
+ }
+ return os_write(elf_file, &fpregset, sizeof(fpregset)) == sizeof(fpregset);
+}
+
/*
* Writes a memory dump file in ELF format. Returns true if a core dump file is written,
* false otherwise.
*/
static bool
-os_dump_core_internal(void)
+os_dump_core_internal(dcontext_t *dcontext)
{
+ priv_mcontext_t mc;
+ if (!dr_get_mcontext_priv(dcontext, NULL, &mc))
+ return false;
+
// Insert a null string at the beginning for sections with no comment.
char string_table[MAX_SECTION_NAME_BUFFER_SIZE];
// Reserve the first byte for sections without a name.
@@ -255,9 +481,7 @@ os_dump_core_internal(void)
if (!write_elf_header(elf_file, /*entry_point=*/0,
/*section_header_table_offset*/ sizeof(ELF_HEADER_TYPE) +
- 1 /*program_header_count*/ *
- sizeof(ELF_PROGRAM_HEADER_TYPE) +
- section_data_size,
+ PROGRAM_HEADER_LENGTH + section_data_size,
/*flags=*/0,
/*program_header_count=*/1,
/*section_header_count=*/section_count,
@@ -265,11 +489,21 @@ os_dump_core_internal(void)
os_close(elf_file);
return false;
}
- // TODO i#7046: Fill the program header with valid data.
- if (!write_program_header(elf_file, PT_NULL, PF_X, /*offset=*/0,
+ if (!write_program_header(elf_file, PT_NOTE, /*flags=*/0,
+ /*offset=*/sizeof(ELF_HEADER_TYPE) +
+ sizeof(ELF_PROGRAM_HEADER_TYPE),
/*virtual_address=*/0,
/*physical_address=*/0,
- /*file_size=*/0, /*memory_size=*/0, /*alignment=*/0)) {
+ /*file_size=*/PROGRAM_HEADER_NOTE_LENGTH,
+ /*memory_size=*/0, /*alignment=*/4)) {
+ os_close(elf_file);
+ return false;
+ }
+ if (!write_prstatus_note(&mc, elf_file)) {
+ os_close(elf_file);
+ return false;
+ }
+ if (!write_fpregset_note(dcontext, &mc, elf_file)) {
os_close(elf_file);
return false;
}
@@ -300,8 +534,8 @@ os_dump_core_internal(void)
}
// Write section headers to the core dump file.
// TODO i#7046: Handle multiple program headers.
- ELF_OFF file_offset = sizeof(ELF_HEADER_TYPE) +
- 1 /*program_header_count*/ * sizeof(ELF_PROGRAM_HEADER_TYPE);
+ ELF_OFF file_offset = sizeof(ELF_HEADER_TYPE) + PROGRAM_HEADER_LENGTH;
+
// The section_count includes the section name section, so we need to skip
// it in the loop. The section name section is handled differently after
// this loop.
@@ -343,8 +577,12 @@ os_dump_core_internal(void)
* Returns true if a core dump file is written, false otherwise.
*/
bool
-os_dump_core_live(void)
+os_dump_core_live(dcontext_t *dcontext)
{
+#ifdef DR_HOST_NOT_TARGET
+ // Memory dump is supported only when the host and the target are the same.
+ return false;
+#endif
// Suspend all threads including native threads to ensure the memory regions
// do not change in the middle of the core dump.
int num_threads;
@@ -359,7 +597,8 @@ os_dump_core_live(void)
return false;
}
- const bool ret = os_dump_core_internal();
+ // TODO i#7046: Add support to save register values for all threads.
+ const bool ret = os_dump_core_internal(dcontext);
end_synch_with_all_threads(threads, num_threads,
/*resume=*/true);
diff --git a/core/unix/elf_defines.h b/core/unix/elf_defines.h
index b9a51f3cde5..ff56ac81d27 100644
--- a/core/unix/elf_defines.h
+++ b/core/unix/elf_defines.h
@@ -54,6 +54,7 @@
# define ELF_HEADER_TYPE Elf64_Ehdr
# define ELF_ALTARCH_HEADER_TYPE Elf32_Ehdr
# define ELF_PROGRAM_HEADER_TYPE Elf64_Phdr
+# define ELF_NOTE_HEADER_TYPE Elf64_Nhdr
# define ELF_SECTION_HEADER_TYPE Elf64_Shdr
# define ELF_DYNAMIC_ENTRY_TYPE Elf64_Dyn
# define ELF_ADDR Elf64_Addr
@@ -76,6 +77,7 @@
# define ELF_HEADER_TYPE Elf32_Ehdr
# define ELF_ALTARCH_HEADER_TYPE Elf64_Ehdr
# define ELF_PROGRAM_HEADER_TYPE Elf32_Phdr
+# define ELF_NOTE_HEADER_TYPE Elf32_Nhdr
# define ELF_SECTION_HEADER_TYPE Elf32_Shdr
# define ELF_DYNAMIC_ENTRY_TYPE Elf32_Dyn
# define ELF_ADDR Elf32_Addr
diff --git a/core/unix/os_exports.h b/core/unix/os_exports.h
index 3ab2a11e55c..70e7145000d 100644
--- a/core/unix/os_exports.h
+++ b/core/unix/os_exports.h
@@ -246,8 +246,14 @@ ushort
os_get_app_tls_reg_offset(reg_id_t seg);
void *
os_get_app_tls_base(dcontext_t *dcontext, reg_id_t seg);
+#if defined(AARCH64) || (defined(X64) && defined(X86))
+/* os_dump_core_live has the same restriction as dr_suspend_all_other_threads_ex().
+ * For X86_64 platform, fast FP save and restore (fxsave64) support is required. And mixed
+ * mode (a process mixing 64-bit and 32-bit code) is not supported.
+ */
bool
-os_dump_core_live(void);
+os_dump_core_live(dcontext_t *dcontext);
+#endif
#if defined(AARCHXX) || defined(RISCV64)
bool