summaryrefslogtreecommitdiff
path: root/src/debug/createdump/dumpwriter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/createdump/dumpwriter.cpp')
-rw-r--r--src/debug/createdump/dumpwriter.cpp486
1 files changed, 486 insertions, 0 deletions
diff --git a/src/debug/createdump/dumpwriter.cpp b/src/debug/createdump/dumpwriter.cpp
new file mode 100644
index 0000000000..9057d180e1
--- /dev/null
+++ b/src/debug/createdump/dumpwriter.cpp
@@ -0,0 +1,486 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "createdump.h"
+
+DumpWriter::DumpWriter(CrashInfo& crashInfo) :
+ m_ref(1),
+ m_fd(-1),
+ m_crashInfo(crashInfo)
+{
+ m_crashInfo.AddRef();
+}
+
+DumpWriter::~DumpWriter()
+{
+ if (m_fd != -1)
+ {
+ close(m_fd);
+ m_fd = -1;
+ }
+ m_crashInfo.Release();
+}
+
+STDMETHODIMP
+DumpWriter::QueryInterface(
+ ___in REFIID InterfaceId,
+ ___out PVOID* Interface)
+{
+ if (InterfaceId == IID_IUnknown)
+ {
+ *Interface = (IUnknown*)this;
+ AddRef();
+ return S_OK;
+ }
+ else
+ {
+ *Interface = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+STDMETHODIMP_(ULONG)
+DumpWriter::AddRef()
+{
+ LONG ref = InterlockedIncrement(&m_ref);
+ return ref;
+}
+
+STDMETHODIMP_(ULONG)
+DumpWriter::Release()
+{
+ LONG ref = InterlockedDecrement(&m_ref);
+ if (ref == 0)
+ {
+ delete this;
+ }
+ return ref;
+}
+
+bool
+DumpWriter::OpenDump(const char* dumpFileName)
+{
+ m_fd = open(dumpFileName, O_WRONLY|O_CREAT|O_TRUNC, 0664);
+ if (m_fd == -1)
+ {
+ fprintf(stderr, "Could not open output %s: %s\n", dumpFileName, strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+// Write the core dump file:
+// ELF header
+// Single section header (Shdr) for 64 bit program header count
+// Phdr for the PT_NOTE
+// PT_LOAD
+// PT_NOTEs
+// process info (prpsinfo_t)
+// NT_FILE entries
+// threads
+// alignment
+// memory blocks
+bool
+DumpWriter::WriteDump()
+{
+ // Write the ELF header
+ Ehdr ehdr;
+ memset(&ehdr, 0, sizeof(Ehdr));
+ ehdr.e_ident[0] = ELFMAG0;
+ ehdr.e_ident[1] = ELFMAG1;
+ ehdr.e_ident[2] = ELFMAG2;
+ ehdr.e_ident[3] = ELFMAG3;
+ ehdr.e_ident[4] = ELF_CLASS;
+
+ // Note: The sex is the current system running minidump-2-core
+ // Big or Little endian. This means you have to create
+ // the core (minidump-2-core) on the system that matches
+ // your intent to debug properly.
+ ehdr.e_ident[5] = sex() ? ELFDATA2MSB : ELFDATA2LSB;
+ ehdr.e_ident[6] = EV_CURRENT;
+ ehdr.e_ident[EI_OSABI] = ELFOSABI_LINUX;
+
+ ehdr.e_type = ET_CORE;
+ ehdr.e_machine = ELF_ARCH;
+ ehdr.e_version = EV_CURRENT;
+ ehdr.e_shoff = sizeof(Ehdr);
+ ehdr.e_phoff = sizeof(Ehdr) + sizeof(Shdr);
+
+ ehdr.e_ehsize = sizeof(Ehdr);
+ ehdr.e_phentsize = sizeof(Phdr);
+ ehdr.e_shentsize = sizeof(Shdr);
+
+ // The ELF header only allows UINT16 for the number of program
+ // headers. In a core dump this equates to PT_NODE and PT_LOAD.
+ //
+ // When more program headers than 65534 the first section entry
+ // is used to store the actual program header count.
+
+ // PT_NOTE + number of memory regions
+ uint64_t phnum = 1 + m_crashInfo.MemoryRegions().size();
+
+ if (phnum < PH_HDR_CANARY) {
+ ehdr.e_phnum = phnum;
+ }
+ else {
+ ehdr.e_phnum = PH_HDR_CANARY;
+ }
+
+ if (!WriteData(&ehdr, sizeof(Ehdr))) {
+ return false;
+ }
+
+ size_t offset = sizeof(Ehdr) + sizeof(Shdr) + (phnum * sizeof(Phdr));
+ size_t filesz = GetProcessInfoSize() + GetAuxvInfoSize() + GetThreadInfoSize() + GetNTFileInfoSize();
+
+ // Add single section containing the actual count
+ // of the program headers to be written.
+ Shdr shdr;
+ memset(&shdr, 0, sizeof(shdr));
+ shdr.sh_info = phnum;
+ // When section header offset is present but ehdr section num = 0
+ // then is is expected that the sh_size indicates the size of the
+ // section array or 1 in our case.
+ shdr.sh_size = 1;
+ if (!WriteData(&shdr, sizeof(shdr))) {
+ return false;
+ }
+
+ // PT_NOTE header
+ Phdr phdr;
+ memset(&phdr, 0, sizeof(Phdr));
+ phdr.p_type = PT_NOTE;
+ phdr.p_offset = offset;
+ phdr.p_filesz = filesz;
+
+ if (!WriteData(&phdr, sizeof(phdr))) {
+ return false;
+ }
+
+ // PT_NOTE sections must end on 4 byte boundary
+ // We output the NT_FILE, AUX and Thread entries
+ // AUX is aligned, NT_FILE is aligned and then we
+ // check to pad end of the thread list
+ phdr.p_type = PT_LOAD;
+ phdr.p_align = 4096;
+
+ size_t finalNoteAlignment = phdr.p_align - ((offset + filesz) % phdr.p_align);
+ if (finalNoteAlignment == phdr.p_align) {
+ finalNoteAlignment = 0;
+ }
+ offset += finalNoteAlignment;
+
+ TRACE("Writing memory region headers to core file\n");
+
+ // Write memory region note headers
+ for (const MemoryRegion& memoryRegion : m_crashInfo.MemoryRegions())
+ {
+ phdr.p_flags = memoryRegion.Permissions();
+ phdr.p_vaddr = memoryRegion.StartAddress();
+ phdr.p_memsz = memoryRegion.Size();
+
+ offset += filesz;
+ phdr.p_filesz = filesz = memoryRegion.Size();
+ phdr.p_offset = offset;
+
+ if (!WriteData(&phdr, sizeof(phdr))) {
+ return false;
+ }
+ }
+
+ // Write process info data to core file
+ if (!WriteProcessInfo()) {
+ return false;
+ }
+
+ // Write auxv data to core file
+ if (!WriteAuxv()) {
+ return false;
+ }
+
+ // Write NT_FILE entries to the core file
+ if (!WriteNTFileInfo()) {
+ return false;
+ }
+
+ TRACE("Writing %ld thread entries to core file\n", m_crashInfo.Threads().size());
+
+ // Write all the thread's state and registers
+ for (const ThreadInfo* thread : m_crashInfo.Threads())
+ {
+ if (!WriteThread(*thread, 0)) {
+ return false;
+ }
+ }
+
+ // Zero out the end of the PT_NOTE section to the boundary
+ // and then laydown the memory blocks
+ if (finalNoteAlignment > 0) {
+ assert(finalNoteAlignment < sizeof(m_tempBuffer));
+ memset(m_tempBuffer, 0, finalNoteAlignment);
+ if (!WriteData(m_tempBuffer, finalNoteAlignment)) {
+ return false;
+ }
+ }
+
+ TRACE("Writing %ld memory regions to core file\n", m_crashInfo.MemoryRegions().size());
+
+ // Read from target process and write memory regions to core
+ uint64_t total = 0;
+ for (const MemoryRegion& memoryRegion : m_crashInfo.MemoryRegions())
+ {
+ uint32_t size = memoryRegion.Size();
+ uint64_t address = memoryRegion.StartAddress();
+ total += size;
+
+ while (size > 0)
+ {
+ uint32_t bytesToRead = std::min(size, (uint32_t)sizeof(m_tempBuffer));
+ uint32_t read = 0;
+
+ if (FAILED(m_crashInfo.DataTarget()->ReadVirtual(address, m_tempBuffer, bytesToRead, &read))) {
+ fprintf(stderr, "ReadVirtual(%016lx, %08x) FAILED\n", address, bytesToRead);
+ return false;
+ }
+
+ if (!WriteData(m_tempBuffer, read)) {
+ return false;
+ }
+
+ address += read;
+ size -= read;
+ }
+ }
+
+ printf("Written %ld bytes (%ld pages) to core file\n", total, total >> PAGE_SHIFT);
+
+ return true;
+}
+
+bool
+DumpWriter::WriteProcessInfo()
+{
+ prpsinfo_t processInfo;
+ memset(&processInfo, 0, sizeof(processInfo));
+ processInfo.pr_sname = 'R';
+ processInfo.pr_pid = m_crashInfo.Pid();
+ processInfo.pr_ppid = m_crashInfo.Ppid();
+ processInfo.pr_pgrp = m_crashInfo.Tgid();
+ strcpy_s(processInfo.pr_fname, sizeof(processInfo.pr_fname), m_crashInfo.Name());
+
+ Nhdr nhdr;
+ memset(&nhdr, 0, sizeof(nhdr));
+ nhdr.n_namesz = 5;
+ nhdr.n_descsz = sizeof(prpsinfo_t);
+ nhdr.n_type = NT_PRPSINFO;
+
+ TRACE("Writing process information to core file\n");
+
+ // Write process info data to core file
+ if (!WriteData(&nhdr, sizeof(nhdr)) ||
+ !WriteData("CORE\0PRP", 8) ||
+ !WriteData(&processInfo, sizeof(prpsinfo_t))) {
+ return false;
+ }
+ return true;
+}
+
+bool
+DumpWriter::WriteAuxv()
+{
+ Nhdr nhdr;
+ memset(&nhdr, 0, sizeof(nhdr));
+ nhdr.n_namesz = 5;
+ nhdr.n_descsz = m_crashInfo.GetAuxvSize();
+ nhdr.n_type = NT_AUXV;
+
+ TRACE("Writing %ld auxv entries to core file\n", m_crashInfo.AuxvEntries().size());
+
+ if (!WriteData(&nhdr, sizeof(nhdr)) ||
+ !WriteData("CORE\0AUX", 8)) {
+ return false;
+ }
+ for (const auto& auxvEntry : m_crashInfo.AuxvEntries())
+ {
+ if (!WriteData(&auxvEntry, sizeof(auxvEntry))) {
+ return false;
+ }
+ }
+ return true;
+}
+
+struct NTFileEntry
+{
+ uint64_t StartAddress;
+ uint64_t EndAddress;
+ uint64_t Offset;
+};
+
+// Calculate the NT_FILE entries total size
+size_t
+DumpWriter::GetNTFileInfoSize(size_t* alignmentBytes)
+{
+ size_t count = m_crashInfo.ModuleMappings().size();
+ size_t size = 0;
+
+ // Header, CORE, entry count, page size
+ size = sizeof(Nhdr) + sizeof(NTFileEntry);
+
+ // start_address, end_address, offset
+ size += count * sizeof(NTFileEntry);
+
+ // \0 terminator for each filename
+ size += count;
+
+ // File name storage needed
+ for (const MemoryRegion& image : m_crashInfo.ModuleMappings()) {
+ size += strlen(image.FileName());
+ }
+ // Notes must end on 4 byte alignment
+ size_t alignmentBytesNeeded = 4 - (size % 4);
+ size += alignmentBytesNeeded;
+
+ if (alignmentBytes != nullptr) {
+ *alignmentBytes = alignmentBytesNeeded;
+ }
+ return size;
+}
+
+// Write NT_FILE entries to the PT_NODE section
+//
+// Nhdr (NT_FILE)
+// Total entries
+// Page size
+// [0] start_address end_address offset
+// [1] start_address end_address offset
+// [file name]\0[file name]\0...
+bool
+DumpWriter::WriteNTFileInfo()
+{
+ Nhdr nhdr;
+ memset(&nhdr, 0, sizeof(nhdr));
+
+ // CORE + \0 and we align on 4 byte boundary
+ // so we can use CORE\0FIL for easier hex debugging
+ nhdr.n_namesz = 5;
+ nhdr.n_type = NT_FILE; // "FILE"
+
+ // Size of payload for NT_FILE after CORE tag written
+ size_t alignmentBytesNeeded = 0;
+ nhdr.n_descsz = GetNTFileInfoSize(&alignmentBytesNeeded) - sizeof(nhdr) - 8;
+
+ size_t count = m_crashInfo.ModuleMappings().size();
+ size_t pageSize = PAGE_SIZE;
+
+ TRACE("Writing %ld NT_FILE entries to core file\n", m_crashInfo.ModuleMappings().size());
+
+ if (!WriteData(&nhdr, sizeof(nhdr)) ||
+ !WriteData("CORE\0FIL", 8) ||
+ !WriteData(&count, 8) ||
+ !WriteData(&pageSize, 8)) {
+ return false;
+ }
+
+ for (const MemoryRegion& image : m_crashInfo.ModuleMappings())
+ {
+ struct NTFileEntry entry { image.StartAddress(), image.EndAddress(), image.Offset() };
+ if (!WriteData(&entry, sizeof(entry))) {
+ return false;
+ }
+ }
+
+ for (const MemoryRegion& image : m_crashInfo.ModuleMappings())
+ {
+ if (!WriteData(image.FileName(), strlen(image.FileName())) ||
+ !WriteData("\0", 1)) {
+ return false;
+ }
+ }
+
+ // Has to end on a 4 byte boundary. Debugger, readelf and such
+ // will automatically align on next 4 bytes and look for a PT_NOTE
+ // header.
+ if (alignmentBytesNeeded) {
+ if (!WriteData("\0\0\0\0", alignmentBytesNeeded)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+DumpWriter::WriteThread(const ThreadInfo& thread, int fatal_signal)
+{
+ prstatus_t pr;
+ memset(&pr, 0, sizeof(pr));
+
+ pr.pr_info.si_signo = fatal_signal;
+ pr.pr_cursig = fatal_signal;
+ pr.pr_pid = thread.Tid();
+ pr.pr_ppid = thread.Ppid();
+ pr.pr_pgrp = thread.Tgid();
+ memcpy(&pr.pr_reg, thread.GPRegisters(), sizeof(user_regs_struct));
+
+ Nhdr nhdr;
+ memset(&nhdr, 0, sizeof(nhdr));
+
+ // Name size is CORE plus the NULL terminator
+ // The format requires 4 byte alignment so the
+ // value written in 8 bytes. Stuff the last 3
+ // bytes with the type of NT_PRSTATUS so it is
+ // easier to debug in a hex editor.
+ nhdr.n_namesz = 5;
+ nhdr.n_descsz = sizeof(prstatus_t);
+ nhdr.n_type = NT_PRSTATUS;
+ if (!WriteData(&nhdr, sizeof(nhdr)) ||
+ !WriteData("CORE\0THR", 8) ||
+ !WriteData(&pr, sizeof(prstatus_t))) {
+ return false;
+ }
+
+#if defined(__i386__) || defined(__x86_64__)
+ nhdr.n_descsz = sizeof(user_fpregs_struct);
+ nhdr.n_type = NT_FPREGSET;
+ if (!WriteData(&nhdr, sizeof(nhdr)) ||
+ !WriteData("CORE\0FLT", 8) ||
+ !WriteData(thread.FPRegisters(), sizeof(user_fpregs_struct))) {
+ return false;
+ }
+#endif
+
+#if defined(__i386__)
+ nhdr.n_descsz = sizeof(user_fpxregs_struct);
+ nhdr.n_type = NT_PRXFPREG;
+ if (!WriteData(&nhdr, sizeof(nhdr)) ||
+ !WriteData("LINUX\0\0\0", 8) ||
+ !WriteData(&thread.FPXRegisters(), sizeof(user_fpxregs_struct))) {
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+// Write all of the given buffer, handling short writes and EINTR. Return true iff successful.
+bool
+DumpWriter::WriteData(const void* buffer, size_t length)
+{
+ const uint8_t* data = (const uint8_t*)buffer;
+
+ size_t done = 0;
+ while (done < length) {
+ ssize_t written;
+ do {
+ written = write(m_fd, data + done, length - done);
+ } while (written == -1 && errno == EINTR);
+
+ if (written < 1) {
+ fprintf(stderr, "WriteData FAILED %s\n", strerror(errno));
+ return false;
+ }
+ done += written;
+ }
+ return true;
+}