Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

i#7046 Add Linux support to dr_create_memory_dump. #7047

Merged
merged 26 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
162c902
Add Linux support to dr_create_memory_dump.
ivankyluk Oct 18, 2024
37f8abf
Merge branch 'master' into i7046-add-linux-support-to-dr_create_memor…
ivankyluk Oct 18, 2024
7a9b225
Merge branch 'master' into i7046-add-linux-support-to-dr_create_memor…
ivankyluk Oct 30, 2024
13f26ab
Incorporate review comments.
ivankyluk Oct 30, 2024
728ad87
Remove debug code in tracer.cpp.
ivankyluk Oct 30, 2024
b741aeb
Merge branch 'master' into i7046-add-linux-support-to-dr_create_memor…
ivankyluk Oct 31, 2024
838c276
Incorporate review comments. Use dr_suspend_all_other_threads_ex() in…
ivankyluk Nov 1, 2024
ed8e3da
Change FIXME in elf_defines.h to XXX.
ivankyluk Nov 1, 2024
badc146
Merge branch 'master' into i7046-add-linux-support-to-dr_create_memor…
ivankyluk Nov 4, 2024
78d97af
Add unit test to cover dr_create_memory_dump for X64 Linux.
ivankyluk Nov 5, 2024
26edb92
Remove strhash_table_t which triggers lock rack errors in debug build…
ivankyluk Nov 5, 2024
db46f71
Update release note.
ivankyluk Nov 5, 2024
2d5211e
Merge branch 'master' into i7046-add-linux-support-to-dr_create_memor…
ivankyluk Nov 5, 2024
fdaf544
Adding more details to error message.
ivankyluk Nov 6, 2024
d8a5173
Skip vsyscall region.
ivankyluk Nov 6, 2024
9d5a96c
Add a TODO to check the content of the memory dump file.
ivankyluk Nov 6, 2024
0e674a0
Use nudge event for memory dump test. Enable Windows memory dump test.
ivankyluk Nov 6, 2024
5444f30
Enable client.memory_dump_test for WIN32.
ivankyluk Nov 6, 2024
f014ac0
Add Windows specific code to memory_dump_test.dll.c.
ivankyluk Nov 6, 2024
4543ca4
Rewrite the memory dump test for win32.
ivankyluk Nov 6, 2024
5ad051a
Limit the memory dump test to X64 only.
ivankyluk Nov 6, 2024
f41c4aa
Merge branch 'master' into i7046-add-linux-support-to-dr_create_memor…
ivankyluk Nov 7, 2024
73fa0fd
Use a hashtable instead of strstr() to look up existing section names.
ivankyluk Nov 7, 2024
5b5a3fd
Fix the format.
ivankyluk Nov 7, 2024
780df5f
Merge branch 'master' into i7046-add-linux-support-to-dr_create_memor…
ivankyluk Nov 7, 2024
0e9584b
Incorporate review comments.
ivankyluk Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ if (UNIX)
set(OS_SRCS ${OS_SRCS} unix/loader_android.c)
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
else ()
set(OS_SRCS ${OS_SRCS} unix/loader_linux.c)
set(OS_SRCS ${OS_SRCS} unix/coredump.c)
endif ()
set(OS_SRCS ${OS_SRCS} unix/memquery_linux.c)
set(OS_SRCS ${OS_SRCS} unix/memquery.c)
Expand Down
6 changes: 6 additions & 0 deletions core/lib/dr_tools.h
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,12 @@ typedef enum {
* \note Windows only.
*/
DR_MEMORY_DUMP_LDMP = 0x0001,
/**
* Memory dump in Executable and Linkable Format.
*
* \note Linux and X64 only.
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
*/
DR_MEMORY_DUMP_ELF = 0x0002,
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
} dr_memory_dump_flags_t;

/** Indicates the type of memory dump for dr_create_memory_dump(). */
Expand Down
3 changes: 3 additions & 0 deletions core/lib/instrument.c
Original file line number Diff line number Diff line change
Expand Up @@ -2497,6 +2497,9 @@ 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();
#endif
return false;
}
Expand Down
362 changes: 362 additions & 0 deletions core/unix/coredump.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,362 @@
/* **********************************************************
* Copyright (c) 2024 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.
*
* 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 VMWARE, INC. 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.
*/

#include <elf.h>
#include <sys/mman.h>
#include "../globals.h"
#include "../os_shared.h"
#include "../hashtable.h"
#include "memquery.h"

#define MAX_SECTION_NAME_BUFFER_SIZE 8192
#define SECTION_HEADER_TABLE ".shstrtab"
#define VVAR_SECTION "[vvar]"

#ifdef X64
# define ELF_HEADER_TYPE Elf64_Ehdr
# define ELF_PROGRAM_HEADER_TYPE Elf64_Phdr
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
# define ELF_SECTION_HEADER_TYPE Elf64_Shdr
# define ELF_ADDR Elf64_Addr
# define ELF_WORD Elf64_Xword
# define ELF_OFF Elf64_Word
#else
# define ELF_HEADER_TYPE Elf32_Ehdr
# define ELF_PROGRAM_HEADER_TYPE Elf32_Phdr
# define ELF_SECTION_HEADER_TYPE Elf32_Shdr
# define ELF_ADDR Elf32_Addr
# define ELF_WORD Elf32_Word
# define ELF_OFF Elf32_Word
#endif

DECLARE_CXTSWPROT_VAR(static mutex_t dump_core_lock, INIT_LOCK_FREE(dump_core_lock));

/*
* Writes an ELF header to the file. Returns true if the ELF header is written to the core
* dump file, false otherwise.
*/
static bool
write_elf_header(DR_PARAM_IN file_t elf_file, DR_PARAM_IN ELF_ADDR entry_point,
DR_PARAM_IN ELF_OFF section_header_table_offset,
DR_PARAM_IN ELF_OFF flags, DR_PARAM_IN ELF_OFF program_header_count,
DR_PARAM_IN ELF_OFF section_header_count,
DR_PARAM_IN ELF_OFF section_string_table_index)
{
ELF_HEADER_TYPE ehdr;
ehdr.e_ident[0] = ELFMAG0;
ehdr.e_ident[1] = ELFMAG1;
ehdr.e_ident[2] = ELFMAG2;
ehdr.e_ident[3] = ELFMAG3;
ehdr.e_ident[EI_CLASS] = ELFCLASS64;
ehdr.e_ident[EI_DATA] = IF_AARCHXX_ELSE(ELFDATA2MSB, ELFDATA2LSB);
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
ehdr.e_ident[EI_VERSION] = EV_CURRENT;
ehdr.e_ident[EI_OSABI] = ELFOSABI_LINUX;
ehdr.e_ident[EI_ABIVERSION] = 0;
ehdr.e_type = ET_CORE;
ehdr.e_machine = IF_AARCHXX_ELSE(EM_AARCH64, EM_X86_64);
ehdr.e_version = EV_CURRENT;
/* This is the memory address of the entry point from where the process starts
* executing. */
ehdr.e_entry = entry_point;
/* Points to the start of the program header table. */
ehdr.e_phoff = sizeof(ehdr);
/* Points to the start of the section header table. */
ehdr.e_shoff = section_header_table_offset;
ehdr.e_flags = 0;
/* Contains the size of this header */
ehdr.e_ehsize = sizeof(ehdr);
/* Contains the size of a program header table entry. As explained below, this will
* typically be 0x20 (32 bit) or 0x38 (64 bit). */
ehdr.e_phentsize = sizeof(ELF_PROGRAM_HEADER_TYPE);
/* Contains the number of entries in the program header table. */
ehdr.e_phnum = program_header_count;
ehdr.e_shentsize = sizeof(ELF_SECTION_HEADER_TYPE);
/* Contains the number of entries in the section header table. */
ehdr.e_shnum = section_header_count;
/* Contains index of the section header table entry that contains the section names.
*/
ehdr.e_shstrndx = section_string_table_index;

return os_write(elf_file, (void *)&ehdr, sizeof(ELF_HEADER_TYPE)) ==
sizeof(ELF_HEADER_TYPE);
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
}

/*
* Writes a program header to the file. Returns true if the program header is written to
* the core dump file, false otherwise.
*/
static bool
write_phdrs(DR_PARAM_IN file_t elf_file, DR_PARAM_IN ELF_WORD type,
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
DR_PARAM_IN ELF_WORD flags, DR_PARAM_IN ELF_OFF offset,
DR_PARAM_IN ELF_ADDR virtual_address, DR_PARAM_IN ELF_ADDR physical_address,
DR_PARAM_IN ELF_WORD file_size, DR_PARAM_IN ELF_WORD memory_size,
DR_PARAM_IN ELF_WORD alignment)
{
ELF_PROGRAM_HEADER_TYPE phdr;
phdr.p_type = type; /* Segment type */
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
phdr.p_flags = flags; /* Segment flags */
phdr.p_offset = offset; /* Segment file offset */
phdr.p_vaddr = virtual_address; /* Segment virtual address */
phdr.p_paddr = physical_address; /* Segment physical address */
phdr.p_filesz = file_size; /* Segment size in file */
phdr.p_memsz = memory_size; /* Segment size in memory */
phdr.p_align = alignment; /* Segment alignment */

return os_write(elf_file, (void *)&phdr, sizeof(ELF_PROGRAM_HEADER_TYPE)) ==
sizeof(ELF_PROGRAM_HEADER_TYPE);
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
}

/*
* Write a section header to the file. Returns true if the section header is written to
* the core dump file, false otherwise.
*/
static bool
write_shdr(DR_PARAM_IN file_t elf_file, DR_PARAM_IN ELF_WORD string_table_offset,
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
DR_PARAM_IN ELF_WORD type, DR_PARAM_IN ELF_WORD flags,
DR_PARAM_IN ELF_ADDR virtual_address, DR_PARAM_IN ELF_OFF offset,
DR_PARAM_IN ELF_WORD section_size, DR_PARAM_IN ELF_WORD link,
DR_PARAM_IN ELF_WORD info, DR_PARAM_IN ELF_WORD alignment,
DR_PARAM_IN ELF_WORD entry_size)
{
ELF_SECTION_HEADER_TYPE shdr;
shdr.sh_name = string_table_offset; /* Section name (string tbl index) */
shdr.sh_type = type; /* Section type */
shdr.sh_flags = flags; /* Section flags */
shdr.sh_addr = virtual_address; /* Section virtual addr at execution */
shdr.sh_offset = offset; /* Section file offset */
shdr.sh_size = section_size; /* Section size in bytes */
shdr.sh_link = link; /* Link to another section */
shdr.sh_info = info; /* Additional section information */
shdr.sh_addralign = alignment; /* Section alignment */
shdr.sh_entsize = entry_size; /* Entry size if section holds table */

return os_write(elf_file, (void *)&shdr, sizeof(ELF_SECTION_HEADER_TYPE)) ==
sizeof(ELF_SECTION_HEADER_TYPE);
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
}

/*
* 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)
{
strhash_table_t *string_htable =
strhash_hash_create(GLOBAL_DCONTEXT, /*bits=*/8, /*load_factor_percent=*/80,
/*table_flags=*/0, NULL _IF_DEBUG("mmap-string-table"));

// Insert a null string at the beginning for sections with no comment.
char string_table[MAX_SECTION_NAME_BUFFER_SIZE];
string_table[0] = '\0';
string_table[1] = '\0';
ELF_ADDR string_table_offset = 1;
ELF_OFF section_count = 0;
ELF_OFF seciion_data_size = 0;
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
memquery_iter_t iter;
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
if (memquery_iterator_start(&iter, NULL, /*may_alloc=*/false)) {
while (memquery_iterator_next(&iter)) {
// Skip non-readable section.
if (iter.prot == MEMPROT_NONE || strcmp(iter.comment, VVAR_SECTION) == 0) {
continue;
}
ELF_ADDR offset = 0;
if (iter.comment != NULL && iter.comment[0] != '\0') {
offset = (ELF_ADDR)strhash_hash_lookup(GLOBAL_DCONTEXT, string_htable,
iter.comment);
if (offset == 0 &&
(string_table[1] == '\0' ||
d_r_strcmp(string_table, iter.comment) != 0)) {
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
strhash_hash_add(GLOBAL_DCONTEXT, string_htable, iter.comment,
(void *)string_table_offset);
offset = string_table_offset;
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
const size_t comment_len = d_r_strlen(iter.comment);
if (comment_len + string_table_offset >
MAX_SECTION_NAME_BUFFER_SIZE) {
SYSLOG_INTERNAL_ERROR("Section name table is too small to store "
"all the section names.");
return false;
}
d_r_strncpy(&string_table[string_table_offset], iter.comment,
comment_len);
string_table_offset += comment_len + 1;
string_table[string_table_offset - 1] = '\0';
}
}
seciion_data_size += iter.vm_end - iter.vm_start;
++section_count;
}
// Add the string table section. Append the section name ".shstrtab" to the
// section names table.
const int section_header_table_len = d_r_strlen(SECTION_HEADER_TABLE) + 1;
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
d_r_strncpy(&string_table[string_table_offset], SECTION_HEADER_TABLE,
section_header_table_len);
string_table_offset += section_header_table_len;
++section_count;
seciion_data_size += string_table_offset;
memquery_iterator_stop(&iter);
}

file_t elf_file;
char dump_core_file_name[MAXIMUM_PATH];
if (!get_unique_logfile(".elf", dump_core_file_name, sizeof(dump_core_file_name),
false, &elf_file) ||
elf_file == INVALID_FILE) {
SYSLOG_INTERNAL_ERROR("Unable to open the core dump file.");
return false;
}

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) +
seciion_data_size,
/*flags=*/0,
/*program_header_count=*/1,
/*section_header_count=*/section_count,
/*section_string_table_index=*/section_count - 1)) {
os_close(elf_file);
return false;
}
// TODO i#7046: Fill the program header with valid data.
if (!write_phdrs(elf_file, PT_NULL, PF_X, /*offset=*/0, /*virtual_address=*/0,
/*physical_address=*/0,
/*file_size=*/0, /*memory_size=*/0, /*alignment=*/0)) {
os_close(elf_file);
return false;
}
int total = 0;
if (memquery_iterator_start(&iter, NULL, /*may_alloc=*/false)) {
while (memquery_iterator_next(&iter)) {
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
// Skip non-readable sections.
if (iter.prot == MEMPROT_NONE || strcmp(iter.comment, VVAR_SECTION) == 0) {
continue;
}
const size_t length = iter.vm_end - iter.vm_start;
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
const int written = os_write(elf_file, (void *)iter.vm_start, length);
if (written != length) {
SYSLOG_INTERNAL_ERROR("Failed to write the requested memory content into "
"the core dump file.");
os_close(elf_file);
return false;
}
total += length;
}
// Write the section names section.
if (os_write(elf_file, (void *)string_table, string_table_offset) !=
string_table_offset) {
os_close(elf_file);
return false;
}
memquery_iterator_stop(&iter);
}

if (memquery_iterator_start(&iter, NULL, /*may_alloc=*/false)) {
// TODO i#7046: Handle multiple program headers.
ELF_OFF file_offset = sizeof(ELF_HEADER_TYPE) +
1 /*program_header_count*/ * sizeof(ELF_PROGRAM_HEADER_TYPE);
while (memquery_iterator_next(&iter)) {
// Skip non-readable section.
if (iter.prot == MEMPROT_NONE || strcmp(iter.comment, VVAR_SECTION) == 0) {
continue;
}
ELF_WORD flags = SHF_ALLOC | SHF_MERGE;
if (iter.prot & PROT_WRITE) {
flags |= SHF_WRITE;
}
ELF_ADDR name_offset = 0;
if (iter.comment != NULL && iter.comment[0] != '\0') {
name_offset = (ELF_ADDR)strhash_hash_lookup(GLOBAL_DCONTEXT,
string_htable, iter.comment);
}
if (!write_shdr(elf_file, name_offset, SHT_PROGBITS, flags,
(ELF_ADDR)iter.vm_start, file_offset,
iter.vm_end - iter.vm_start, /*link=*/0,
/*info=*/0, /*alignment=*/sizeof(ELF_WORD),
/*entry_size=*/0)) {
os_close(elf_file);
return false;
}
file_offset += iter.vm_end - iter.vm_start;
}
memquery_iterator_stop(&iter);
// Write the section names section.
if (!write_shdr(elf_file, string_table_offset - strlen(".shstrtab"), SHT_STRTAB,
/*flags=*/0, /*virtual_address=*/0, file_offset,
/*section_size=*/string_table_offset, /*link=*/0,
/*info=*/0, /*alignment=*/1,
/*entry_size=*/0)) {
os_close(elf_file);
return false;
}
}
os_close(elf_file);
strhash_hash_destroy(GLOBAL_DCONTEXT, string_htable);
return true;
}

/*
* Returns true if a core dump file is written, false otherwise.
*/
bool
os_dump_core_live(void)
{
static thread_id_t current_dumping_thread_id VAR_IN_SECTION(NEVER_PROTECTED_SECTION) =
0;
thread_id_t current_id = d_r_get_thread_id();
#ifdef DEADLOCK_AVOIDANCE
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
dcontext_t *dcontext = get_thread_private_dcontext();
thread_locks_t *old_thread_owned_locks = NULL;
#endif

if (current_id == current_dumping_thread_id) {
return false; /* avoid infinite loop */
}

#ifdef DEADLOCK_AVOIDANCE
/* first turn off deadlock avoidance for this thread (needed for live dump
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
* to try to grab all_threads and thread_initexit locks) */
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
if (dcontext != NULL) {
old_thread_owned_locks = dcontext->thread_owned_locks;
dcontext->thread_owned_locks = NULL;
}
#endif
/* only allow one thread to dumpcore at a time, also protects static
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
* buffers and current_dumping_thread_id */
d_r_mutex_lock(&dump_core_lock);
current_dumping_thread_id = current_id;
const bool ret = os_dump_core_internal();
current_dumping_thread_id = 0;
d_r_mutex_unlock(&dump_core_lock);

#ifdef DEADLOCK_AVOIDANCE
/* restore deadlock avoidance for this thread */
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
if (dcontext != NULL) {
dcontext->thread_owned_locks = old_thread_owned_locks;
}
#endif
return ret;
}
ivankyluk marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading