From 63cf53abae162743f1b217ee0ef3bbd3c505d097 Mon Sep 17 00:00:00 2001 From: Mike McLaughlin Date: Thu, 13 Apr 2017 00:34:01 -0700 Subject: [PATCH] Create ELF core dump on coreclr exceptions and signals. (#10902) * Create ELF core dump on coreclr exceptions and signals. When coreclr aborts because of a unhandled managed exception or hardware signal, the "createdump" utility is launched if the "COMPlus_DbgEnableMiniDump" environment variable is set to "1". This utility uses the DAC enumerate memory interface to determine what memory regions should be written for the managed state plus all the normal Linux memory regions for the native state like threads, stacks, DSO, etc. There still needs some to configure some of the "policies" like the name/location of the core dump and the type of minidump (MiniDumpNormal, MiniDumpWithFullAuxiliaryState, MiniDumpWithPrivateReadWriteMemory, etc.). Currently only supported on Ubuntu Linux. Make createdump smaller by using PAL in DAC module. --- CMakeLists.txt | 3 + src/CMakeLists.txt | 6 + src/ToolBox/SOS/Strike/util.h | 79 +--- src/debug/CMakeLists.txt | 2 +- src/debug/createdump/.gitmirrorall | 1 + src/debug/createdump/CMakeLists.txt | 33 ++ src/debug/createdump/crashinfo.cpp | 630 +++++++++++++++++++++++++++++ src/debug/createdump/crashinfo.h | 71 ++++ src/debug/createdump/createdump.cpp | 88 ++++ src/debug/createdump/createdump.h | 56 +++ src/debug/createdump/datatarget.cpp | 263 ++++++++++++ src/debug/createdump/datatarget.h | 90 +++++ src/debug/createdump/dumpwriter.cpp | 486 ++++++++++++++++++++++ src/debug/createdump/dumpwriter.h | 74 ++++ src/debug/createdump/memoryregion.h | 97 +++++ src/debug/createdump/threadinfo.cpp | 154 +++++++ src/debug/createdump/threadinfo.h | 41 ++ src/debug/daccess/dacfn.cpp | 2 + src/debug/daccess/enummem.cpp | 103 +++-- src/debug/daccess/request_svr.cpp | 16 +- src/debug/inc/dump/dumpcommon.h | 29 ++ src/dlls/mscordac/mscordac_unixexports.src | 4 + src/inc/arrayholder.h | 80 ++++ src/pal/inc/pal.h | 7 +- src/pal/src/config.h.in | 1 + src/pal/src/configure.cmake | 1 + src/pal/src/include/pal/process.h | 19 +- src/pal/src/init/pal.cpp | 6 + src/pal/src/thread/process.cpp | 177 ++++++-- 29 files changed, 2447 insertions(+), 172 deletions(-) create mode 100644 src/debug/createdump/.gitmirrorall create mode 100644 src/debug/createdump/CMakeLists.txt create mode 100644 src/debug/createdump/crashinfo.cpp create mode 100644 src/debug/createdump/crashinfo.h create mode 100644 src/debug/createdump/createdump.cpp create mode 100644 src/debug/createdump/createdump.h create mode 100644 src/debug/createdump/datatarget.cpp create mode 100644 src/debug/createdump/datatarget.h create mode 100644 src/debug/createdump/dumpwriter.cpp create mode 100644 src/debug/createdump/dumpwriter.h create mode 100644 src/debug/createdump/memoryregion.h create mode 100644 src/debug/createdump/threadinfo.cpp create mode 100644 src/debug/createdump/threadinfo.h create mode 100644 src/inc/arrayholder.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4aad5a0..257cf2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,6 +169,9 @@ if(CMAKE_SYSTEM_NAME STREQUAL Linux) if(CLR_CMAKE_LINUX_ID STREQUAL alpine) set(CLR_CMAKE_PLATFORM_ALPINE_LINUX 1) endif() + if(CLR_CMAKE_LINUX_ID STREQUAL ubuntu) + set(CLR_CMAKE_PLATFORM_UBUNTU_LINUX 1) + endif() endif(DEFINED CLR_CMAKE_LINUX_ID) endif(CMAKE_SYSTEM_NAME STREQUAL Linux) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d13e8f9..c2e0260 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -96,6 +96,12 @@ function(add_executable_clr) endfunction() if(CLR_CMAKE_PLATFORM_UNIX) + if(CLR_CMAKE_PLATFORM_UBUNTU_LINUX) + if(CLR_CMAKE_PLATFORM_UNIX_AMD64) + add_subdirectory(debug/createdump) + endif(CLR_CMAKE_PLATFORM_UNIX_AMD64) + endif(CLR_CMAKE_PLATFORM_UBUNTU_LINUX) + add_subdirectory(ToolBox/SOS/Strike) # Include the dummy c++ include files diff --git a/src/ToolBox/SOS/Strike/util.h b/src/ToolBox/SOS/Strike/util.h index 4612acc..6d0e796 100644 --- a/src/ToolBox/SOS/Strike/util.h +++ b/src/ToolBox/SOS/Strike/util.h @@ -1429,84 +1429,7 @@ SafeReadMemory (TO_TADDR(src), &(dst), sizeof(dst), NULL) extern "C" PDEBUG_DATA_SPACES g_ExtData; -template -class ArrayHolder -{ -public: - ArrayHolder(T *ptr) - : mPtr(ptr) - { - } - - ~ArrayHolder() - { - Clear(); - } - - ArrayHolder(const ArrayHolder &rhs) - { - mPtr = const_cast(&rhs)->Detach(); - } - - ArrayHolder &operator=(T *ptr) - { - Clear(); - mPtr = ptr; - return *this; - } - - const T &operator[](int i) const - { - return mPtr[i]; - } - - T &operator[](int i) - { - return mPtr[i]; - } - - operator const T *() const - { - return mPtr; - } - - operator T *() - { - return mPtr; - } - - T **operator&() - { - return &mPtr; - } - - T *GetPtr() - { - return mPtr; - } - - T *Detach() - { - T *ret = mPtr; - mPtr = NULL; - return ret; - } - -private: - void Clear() - { - if (mPtr) - { - delete [] mPtr; - mPtr = NULL; - } - } - -private: - T *mPtr; -}; - - +#include // This class acts a smart pointer which calls the Release method on any object // you place in it when the ToRelease class falls out of scope. You may use it diff --git a/src/debug/CMakeLists.txt b/src/debug/CMakeLists.txt index 1940aa9..bcfc257 100644 --- a/src/debug/CMakeLists.txt +++ b/src/debug/CMakeLists.txt @@ -3,4 +3,4 @@ add_subdirectory(dbgutil) add_subdirectory(ildbsymlib) add_subdirectory(ee) add_subdirectory(di) -add_subdirectory(shim) +add_subdirectory(shim) \ No newline at end of file diff --git a/src/debug/createdump/.gitmirrorall b/src/debug/createdump/.gitmirrorall new file mode 100644 index 0000000..9ee5c57 --- /dev/null +++ b/src/debug/createdump/.gitmirrorall @@ -0,0 +1 @@ +This folder will be mirrored by the Git-TFS Mirror recursively. \ No newline at end of file diff --git a/src/debug/createdump/CMakeLists.txt b/src/debug/createdump/CMakeLists.txt new file mode 100644 index 0000000..081a308 --- /dev/null +++ b/src/debug/createdump/CMakeLists.txt @@ -0,0 +1,33 @@ +project(createdump) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +remove_definitions(-DUNICODE) +remove_definitions(-D_UNICODE) + +include_directories(BEFORE ${VM_DIR}) + +add_definitions(-DPAL_STDCPP_COMPAT=1) + +add_compile_options(-fPIC) + +set(CREATEDUMP_SOURCES + createdump.cpp + crashinfo.cpp + threadinfo.cpp + datatarget.cpp + dumpwriter.cpp +) + +_add_executable(createdump + ${CREATEDUMP_SOURCES} +) + +target_link_libraries(createdump + # share the PAL in the dac module + mscordaccore +) + +add_dependencies(createdump mscordaccore) + +install_clr(createdump) diff --git a/src/debug/createdump/crashinfo.cpp b/src/debug/createdump/crashinfo.cpp new file mode 100644 index 0000000..8f72542 --- /dev/null +++ b/src/debug/createdump/crashinfo.cpp @@ -0,0 +1,630 @@ +// 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" + +CrashInfo::CrashInfo(pid_t pid, DataTarget& dataTarget) : + m_ref(1), + m_pid(pid), + m_ppid(-1), + m_name(nullptr), + m_dataTarget(dataTarget) +{ + dataTarget.AddRef(); + m_auxvValues.fill(0); +} + +CrashInfo::~CrashInfo() +{ + if (m_name != nullptr) + { + free(m_name); + } + // Clean up the threads + for (ThreadInfo* thread : m_threads) + { + delete thread; + } + m_threads.clear(); + + // Module and other mappings have a file name to clean up. + for (const MemoryRegion& region : m_moduleMappings) + { + const_cast(region).Cleanup(); + } + m_moduleMappings.clear(); + for (const MemoryRegion& region : m_otherMappings) + { + const_cast(region).Cleanup(); + } + m_otherMappings.clear(); + m_dataTarget.Release(); +} + +STDMETHODIMP +CrashInfo::QueryInterface( + ___in REFIID InterfaceId, + ___out PVOID* Interface) +{ + if (InterfaceId == IID_IUnknown || + InterfaceId == IID_ICLRDataEnumMemoryRegionsCallback) + { + *Interface = (ICLRDataEnumMemoryRegionsCallback*)this; + AddRef(); + return S_OK; + } + else + { + *Interface = NULL; + return E_NOINTERFACE; + } +} + +STDMETHODIMP_(ULONG) +CrashInfo::AddRef() +{ + LONG ref = InterlockedIncrement(&m_ref); + return ref; +} + +STDMETHODIMP_(ULONG) +CrashInfo::Release() +{ + LONG ref = InterlockedDecrement(&m_ref); + if (ref == 0) + { + delete this; + } + return ref; +} + +HRESULT STDMETHODCALLTYPE +CrashInfo::EnumMemoryRegion( + /* [in] */ CLRDATA_ADDRESS address, + /* [in] */ ULONG32 size) +{ + InsertMemoryRegion(address, size); + return S_OK; +} + +bool +CrashInfo::EnumerateAndSuspendThreads() +{ + char taskPath[128]; + snprintf(taskPath, sizeof(taskPath), "/proc/%d/task", m_pid); + + DIR* taskDir = opendir(taskPath); + if (taskDir == nullptr) + { + fprintf(stderr, "opendir(%s) FAILED %s\n", taskPath, strerror(errno)); + return false; + } + + struct dirent* entry; + while ((entry = readdir(taskDir)) != nullptr) + { + pid_t tid = static_cast(strtol(entry->d_name, nullptr, 10)); + if (tid != 0) + { + // Reference: http://stackoverflow.com/questions/18577956/how-to-use-ptrace-to-get-a-consistent-view-of-multiple-threads + if (ptrace(PTRACE_ATTACH, tid, nullptr, nullptr) != -1) + { + int waitStatus; + waitpid(tid, &waitStatus, __WALL); + + // Add to the list of (suspended) threads + ThreadInfo* thread = new ThreadInfo(tid); + m_threads.push_back(thread); + } + else + { + fprintf(stderr, "ptrace(ATTACH, %d) FAILED %s\n", tid, strerror(errno)); + } + } + } + + closedir(taskDir); + return true; +} + +bool +CrashInfo::GatherCrashInfo(const char* pszExePath, MINIDUMP_TYPE minidumpType) +{ + // Get the process info + if (!GetStatus(m_pid, &m_ppid, &m_tgid, &m_name)) + { + return false; + } + // Get the info about the threads (registers, etc.) + for (ThreadInfo* thread : m_threads) + { + if (!thread->Initialize()) + { + return false; + } + } + // Get the auxv data + if (!GetAuxvEntries()) + { + return false; + } + // Get shared module debug info + if (!GetDSOInfo()) + { + return false; + } + // Gather all the module memory mappings (from /dev/$pid/maps) + if (!EnumerateModuleMappings()) + { + return false; + } + // Gather all the useful memory regions from the DAC + if (!EnumerateMemoryRegionsWithDAC(pszExePath, minidumpType)) + { + return false; + } + // Add the thread's stack and some code memory to core + for (ThreadInfo* thread : m_threads) + { + uint64_t start; + size_t size; + + // Add the thread's stack and some of the code + thread->GetThreadStack(*this, &start, &size); + InsertMemoryRegion(start, size); + + thread->GetThreadCode(&start, &size); + InsertMemoryRegion(start, size); + } + // Join all adjacent memory regions + CombineMemoryRegions(); + return true; +} + +void +CrashInfo::ResumeThreads() +{ + for (ThreadInfo* thread : m_threads) + { + thread->ResumeThread(); + } +} + +bool +CrashInfo::GetAuxvEntries() +{ + char auxvPath[128]; + snprintf(auxvPath, sizeof(auxvPath), "/proc/%d/auxv", m_pid); + + int fd = open(auxvPath, O_RDONLY, 0); + if (fd == -1) + { + fprintf(stderr, "open(%s) FAILED %s\n", auxvPath, strerror(errno)); + return false; + } + bool result = false; + elf_aux_entry auxvEntry; + + while (read(fd, &auxvEntry, sizeof(elf_aux_entry)) == sizeof(elf_aux_entry)) + { + m_auxvEntries.push_back(auxvEntry); + if (auxvEntry.a_type == AT_NULL) + { + break; + } + if (auxvEntry.a_type < AT_MAX) + { + m_auxvValues[auxvEntry.a_type] = auxvEntry.a_un.a_val; + TRACE("AUXV: %lu = %016lx\n", auxvEntry.a_type, auxvEntry.a_un.a_val); + result = true; + } + } + + close(fd); + return result; +} + +bool +CrashInfo::EnumerateModuleMappings() +{ + // Here we read /proc//maps file in order to parse it and figure out what it says + // about a library we are looking for. This file looks something like this: + // + // [address] [perms] [offset] [dev] [inode] [pathname] - HEADER is not preset in an actual file + // + // 35b1800000-35b1820000 r-xp 00000000 08:02 135522 /usr/lib64/ld-2.15.so + // 35b1a1f000-35b1a20000 r--p 0001f000 08:02 135522 /usr/lib64/ld-2.15.so + // 35b1a20000-35b1a21000 rw-p 00020000 08:02 135522 /usr/lib64/ld-2.15.so + // 35b1a21000-35b1a22000 rw-p 00000000 00:00 0 [heap] + // 35b1c00000-35b1dac000 r-xp 00000000 08:02 135870 /usr/lib64/libc-2.15.so + // 35b1dac000-35b1fac000 ---p 001ac000 08:02 135870 /usr/lib64/libc-2.15.so + // 35b1fac000-35b1fb0000 r--p 001ac000 08:02 135870 /usr/lib64/libc-2.15.so + // 35b1fb0000-35b1fb2000 rw-p 001b0000 08:02 135870 /usr/lib64/libc-2.15.so + char* line = NULL; + size_t lineLen = 0; + int count = 0; + ssize_t read; + + // Making something like: /proc/123/maps + char mapPath[128]; + int chars = snprintf(mapPath, sizeof(mapPath), "/proc/%d/maps", m_pid); + assert(chars > 0 && chars <= sizeof(mapPath)); + + FILE* mapsFile = fopen(mapPath, "r"); + if (mapsFile == NULL) + { + fprintf(stderr, "fopen(%s) FAILED %s\n", mapPath, strerror(errno)); + return false; + } + // linuxGateAddress is the beginning of the kernel's mapping of + // linux-gate.so in the process. It doesn't actually show up in the + // maps list as a filename, but it can be found using the AT_SYSINFO_EHDR + // aux vector entry, which gives the information necessary to special + // case its entry when creating the list of mappings. + // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more + // information. + const void* linuxGateAddress = (const void*)m_auxvValues[AT_SYSINFO_EHDR]; + + // Reading maps file line by line + while ((read = getline(&line, &lineLen, mapsFile)) != -1) + { + uint64_t start, end, offset; + char* permissions = nullptr; + char* moduleName = nullptr; + + int c = 0; + if ((c = sscanf(line, "%lx-%lx %m[-rwxsp] %lx %*[:0-9a-f] %*d %ms\n", &start, &end, &permissions, &offset, &moduleName)) == 5) + { + if (linuxGateAddress != nullptr && reinterpret_cast(start) == linuxGateAddress) + { + InsertMemoryRegion(start, end - start); + free(moduleName); + } + else { + uint32_t permissionFlags = 0; + if (strchr(permissions, 'r')) { + permissionFlags |= PF_R; + } + if (strchr(permissions, 'w')) { + permissionFlags |= PF_W; + } + if (strchr(permissions, 'x')) { + permissionFlags |= PF_X; + } + MemoryRegion memoryRegion(permissionFlags, start, end, offset, moduleName); + + if (moduleName != nullptr && *moduleName == '/') { + m_moduleMappings.insert(memoryRegion); + } + else { + m_otherMappings.insert(memoryRegion); + } + } + free(permissions); + } + } + + if (g_diagnostics) + { + TRACE("Module mappings:\n"); + for (const MemoryRegion& region : m_moduleMappings) + { + region.Print(); + } + TRACE("Other mappings:\n"); + for (const MemoryRegion& region : m_otherMappings) + { + region.Print(); + } + } + + free(line); // We didn't allocate line, but as per contract of getline we should free it + fclose(mapsFile); + + return true; +} + +bool +CrashInfo::EnumerateMemoryRegionsWithDAC(const char *pszExePath, MINIDUMP_TYPE minidumpType) +{ + PFN_CLRDataCreateInstance pfnCLRDataCreateInstance = nullptr; + ICLRDataEnumMemoryRegions *clrDataEnumRegions = nullptr; + HMODULE hdac = nullptr; + HRESULT hr = S_OK; + bool result = false; + + // We assume that the DAC is in the same location as this createdump exe + ArrayHolder dacPath = new char[MAX_LONGPATH]; + strcpy_s(dacPath, MAX_LONGPATH, pszExePath); + char *last = strrchr(dacPath, '/'); + if (last != nullptr) + { + *(last + 1) = '\0'; + } + else + { + dacPath[0] = '\0'; + } + strcat_s(dacPath, MAX_LONGPATH, MAKEDLLNAME_A("mscordaccore")); + + // Load and initialize the DAC + hdac = LoadLibraryA(dacPath); + if (hdac == nullptr) + { + fprintf(stderr, "LoadLibraryA(%s) FAILED %d\n", (char*)dacPath, GetLastError()); + goto exit; + } + pfnCLRDataCreateInstance = (PFN_CLRDataCreateInstance)GetProcAddress(hdac, "CLRDataCreateInstance"); + if (pfnCLRDataCreateInstance == nullptr) + { + fprintf(stderr, "GetProcAddress(CLRDataCreateInstance) FAILED %d\n", GetLastError()); + goto exit; + } + hr = pfnCLRDataCreateInstance(__uuidof(ICLRDataEnumMemoryRegions), &m_dataTarget, (void**)&clrDataEnumRegions); + if (FAILED(hr)) + { + fprintf(stderr, "CLRDataCreateInstance(ICLRDataEnumMemoryRegions) FAILED %08x\n", hr); + goto exit; + } + // Calls CrashInfo::EnumMemoryRegion for each memory region found by the DAC + hr = clrDataEnumRegions->EnumMemoryRegions(this, minidumpType, CLRDATA_ENUM_MEM_DEFAULT); + if (FAILED(hr)) + { + fprintf(stderr, "EnumMemoryRegions FAILED %08x\n", hr); + goto exit; + } + result = true; +exit: + if (clrDataEnumRegions != nullptr) + { + clrDataEnumRegions->Release(); + } + if (hdac != nullptr) + { + FreeLibrary(hdac); + } + return result; +} + +bool +CrashInfo::GetDSOInfo() +{ + Phdr* phdrAddr = reinterpret_cast(m_auxvValues[AT_PHDR]); + int phnum = m_auxvValues[AT_PHNUM]; + assert(m_auxvValues[AT_PHENT] == sizeof(Phdr)); + + if (phnum <= 0 || phdrAddr == nullptr) { + return false; + } + TRACE("DSO: phdr %p phnum %d\n", phdrAddr, phnum); + + // Search for the program PT_DYNAMIC header + ElfW(Dyn)* dynamicAddr = nullptr; + for (int i = 0; i < phnum; i++, phdrAddr++) + { + Phdr ph; + if (!ReadMemory(phdrAddr, &ph, sizeof(ph))) { + return false; + } + TRACE("DSO: phdr %p type %d (%x) vaddr %016lx memsz %016lx offset %016lx\n", + phdrAddr, ph.p_type, ph.p_type, ph.p_vaddr, ph.p_memsz, ph.p_offset); + + if (ph.p_type == PT_DYNAMIC) + { + dynamicAddr = reinterpret_cast(ph.p_vaddr); + } + else if (ph.p_type == PT_GNU_EH_FRAME) + { + if (ph.p_vaddr != 0 && ph.p_memsz != 0) + { + InsertMemoryRegion(ph.p_vaddr, ph.p_memsz); + } + } + } + + if (dynamicAddr == nullptr) { + return false; + } + + // Search for dynamic debug (DT_DEBUG) entry + struct r_debug* rdebugAddr = nullptr; + for (;;) { + ElfW(Dyn) dyn; + if (!ReadMemory(dynamicAddr, &dyn, sizeof(dyn))) { + return false; + } + TRACE("DSO: dyn %p tag %ld (%lx) d_ptr %016lx\n", dynamicAddr, dyn.d_tag, dyn.d_tag, dyn.d_un.d_ptr); + if (dyn.d_tag == DT_DEBUG) { + rdebugAddr = reinterpret_cast(dyn.d_un.d_ptr); + } + else if (dyn.d_tag == DT_NULL) { + break; + } + dynamicAddr++; + } + + // Add the DSO r_debug entry + TRACE("DSO: rdebugAddr %p\n", rdebugAddr); + struct r_debug debugEntry; + if (!ReadMemory(rdebugAddr, &debugEntry, sizeof(debugEntry))) { + return false; + } + + // Add the DSO link_map entries + for (struct link_map* linkMapAddr = debugEntry.r_map; linkMapAddr != nullptr;) { + struct link_map map; + if (!ReadMemory(linkMapAddr, &map, sizeof(map))) { + return false; + } + char moduleName[257] = { 0 }; + if (map.l_name != nullptr) { + if (!ReadMemory(map.l_name, &moduleName, sizeof(moduleName) - 1)) { + return false; + } + } + TRACE("DSO: link_map entry %p l_ld %p l_addr %lx %s\n", linkMapAddr, map.l_ld, map.l_addr, moduleName); + linkMapAddr = map.l_next; + } + + return true; +} + +// +// ReadMemory from target and add to memory regions list +// +bool +CrashInfo::ReadMemory(void* address, void* buffer, size_t size) +{ + uint32_t read = 0; + if (FAILED(m_dataTarget.ReadVirtual(reinterpret_cast(address), reinterpret_cast(buffer), size, &read))) + { + return false; + } + InsertMemoryRegion(reinterpret_cast(address), size); + return true; +} + +// +// Add this memory chunk to the list of regions to be +// written to the core dump. +// +void +CrashInfo::InsertMemoryRegion(uint64_t address, size_t size) +{ + // Round to page boundary + uint64_t start = address & PAGE_MASK; + assert(start > 0); + + // Round up to page boundary + uint64_t end = ((address + size) + (PAGE_SIZE - 1)) & PAGE_MASK; + assert(end > 0); + + MemoryRegion memoryRegionFull(start, end); + + // First check if the full memory region can be added without conflicts + const auto& found = m_memoryRegions.find(memoryRegionFull); + if (found == m_memoryRegions.end()) + { + // Add full memory region + m_memoryRegions.insert(memoryRegionFull); + } + else + { + // The memory region is not wholely contained in region found + if (!found->Contains(memoryRegionFull)) + { + // The region overlaps/conflicts with one already in the set so + // add one page at a time to avoid the overlapping pages. + uint64_t numberPages = (end - start) >> PAGE_SHIFT; + + for (int p = 0; p < numberPages; p++, start += PAGE_SIZE) + { + MemoryRegion memoryRegion(start, start + PAGE_SIZE); + + const auto& found = m_memoryRegions.find(memoryRegion); + if (found == m_memoryRegions.end()) + { + m_memoryRegions.insert(memoryRegion); + } + } + } + } +} + +// +// Combine any adjacent memory regions into one +// +void +CrashInfo::CombineMemoryRegions() +{ + assert(!m_memoryRegions.empty()); + + std::set memoryRegionsNew; + + uint64_t start = m_memoryRegions.begin()->StartAddress(); + uint64_t end = start; + + for (const MemoryRegion& region : m_memoryRegions) + { + if (end == region.StartAddress()) + { + end = region.EndAddress(); + } + else + { + MemoryRegion memoryRegion(start, end); + assert(memoryRegionsNew.find(memoryRegion) == memoryRegionsNew.end()); + memoryRegionsNew.insert(memoryRegion); + + start = region.StartAddress(); + end = region.EndAddress(); + } + } + + assert(start != end); + MemoryRegion memoryRegion(start, end); + assert(memoryRegionsNew.find(memoryRegion) == memoryRegionsNew.end()); + memoryRegionsNew.insert(memoryRegion); + + m_memoryRegions = memoryRegionsNew; + + if (g_diagnostics) + { + TRACE("Memory Regions:\n"); + uint64_t total = 0; + for (const MemoryRegion& region : m_memoryRegions) + { + region.Print(); + total += region.Size(); + } + TRACE("Total %ld bytes (%ld pages) to write\n", total, total >> PAGE_SHIFT); + } +} + +bool +CrashInfo::GetStatus(pid_t pid, pid_t* ppid, pid_t* tgid, char** name) +{ + char statusPath[128]; + snprintf(statusPath, sizeof(statusPath), "/proc/%d/status", pid); + + FILE *statusFile = fopen(statusPath, "r"); + if (statusFile == nullptr) + { + fprintf(stderr, "GetStatus fopen(%s) FAILED\n", statusPath); + return false; + } + + *ppid = -1; + + char *line = nullptr; + size_t lineLen = 0; + ssize_t read; + while ((read = getline(&line, &lineLen, statusFile)) != -1) + { + if (strncmp("PPid:\t", line, 6) == 0) + { + *ppid = _atoi64(line + 6); + } + else if (strncmp("Tgid:\t", line, 6) == 0) + { + *tgid = _atoi64(line + 6); + } + else if (strncmp("Name:\t", line, 6) == 0) + { + if (name != nullptr) + { + char* n = strchr(line + 6, '\n'); + if (n != nullptr) + { + *n = '\0'; + } + *name = strdup(line + 6); + } + } + } + + free(line); + fclose(statusFile); + return true; +} diff --git a/src/debug/createdump/crashinfo.h b/src/debug/createdump/crashinfo.h new file mode 100644 index 0000000..a03ebe3 --- /dev/null +++ b/src/debug/createdump/crashinfo.h @@ -0,0 +1,71 @@ +// 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. + +// typedef for our parsing of the auxv variables in /proc/pid/auxv. +#if defined(__i386) || defined(__ARM_EABI__) +typedef Elf32_auxv_t elf_aux_entry; +#elif defined(__x86_64) || defined(__aarch64__) +typedef Elf64_auxv_t elf_aux_entry; +#endif + +typedef __typeof__(((elf_aux_entry*) 0)->a_un.a_val) elf_aux_val_t; + +// All interesting auvx entry types are AT_SYSINFO_EHDR and below +#define AT_MAX (AT_SYSINFO_EHDR + 1) + +class CrashInfo : public ICLRDataEnumMemoryRegionsCallback +{ +private: + LONG m_ref; // reference count + pid_t m_pid; // pid + pid_t m_ppid; // parent pid + pid_t m_tgid; // process group + char* m_name; // exe name + DataTarget& m_dataTarget; // read process memory + std::array m_auxvValues; // auxv values + std::vector m_auxvEntries; // full auxv entries + std::vector m_threads; // threads found and suspended + std::set m_moduleMappings; // module memory mappings + std::set m_otherMappings; // other memory mappings + std::set m_memoryRegions; // memory regions from DAC, etc. + +public: + CrashInfo(pid_t pid, DataTarget& dataTarget); + virtual ~CrashInfo(); + bool EnumerateAndSuspendThreads(); + bool GatherCrashInfo(const char* pszExePath, MINIDUMP_TYPE minidumpType); + void ResumeThreads(); + static bool GetStatus(pid_t pid, pid_t* ppid, pid_t* tgid, char** name); + + const pid_t Pid() const { return m_pid; } + const pid_t Ppid() const { return m_ppid; } + const pid_t Tgid() const { return m_tgid; } + const char* Name() const { return m_name; } + + const std::vector Threads() const { return m_threads; } + const std::set ModuleMappings() const { return m_moduleMappings; } + const std::set OtherMappings() const { return m_otherMappings; } + const std::set MemoryRegions() const { return m_memoryRegions; } + const std::vector AuxvEntries() const { return m_auxvEntries; } + const size_t GetAuxvSize() const { return m_auxvEntries.size() * sizeof(elf_aux_entry); } + + // IUnknown + STDMETHOD(QueryInterface)(___in REFIID InterfaceId, ___out PVOID* Interface); + STDMETHOD_(ULONG, AddRef)(); + STDMETHOD_(ULONG, Release)(); + + // ICLRDataEnumMemoryRegionsCallback + virtual HRESULT STDMETHODCALLTYPE EnumMemoryRegion( + /* [in] */ CLRDATA_ADDRESS address, + /* [in] */ ULONG32 size); + +private: + bool GetAuxvEntries(); + bool EnumerateModuleMappings(); + bool EnumerateMemoryRegionsWithDAC(const char* pszExePath, MINIDUMP_TYPE minidumpType); + bool GetDSOInfo(); + bool ReadMemory(void* address, void* buffer, size_t size); + void InsertMemoryRegion(uint64_t address, size_t size); + void CombineMemoryRegions(); +}; diff --git a/src/debug/createdump/createdump.cpp b/src/debug/createdump/createdump.cpp new file mode 100644 index 0000000..863b3ec --- /dev/null +++ b/src/debug/createdump/createdump.cpp @@ -0,0 +1,88 @@ +// 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" + +bool g_diagnostics = true; + +// +// Create a minidump using the DAC's enum memory regions interface +// +static bool +CreateDump(const char* pszExePath, const char* dumpPathTemplate, pid_t pid, MINIDUMP_TYPE minidumpType) +{ + DataTarget* dataTarget = new DataTarget(pid); + CrashInfo* crashInfo = new CrashInfo(pid, *dataTarget); + DumpWriter* dumpWriter = new DumpWriter(*dataTarget, *crashInfo); + ArrayHolder dumpPath = new char[MAX_LONGPATH]; + bool result = false; + + // Suspend all the threads in the target process and build the list of threads + if (!crashInfo->EnumerateAndSuspendThreads()) + { + goto exit; + } + // The initialize the data target's ReadVirtual support (opens /proc/$pid/mem) + if (!dataTarget->Initialize(crashInfo)) + { + goto exit; + } + // Gather all the info about the process, threads (registers, etc.) and memory regions + if (!crashInfo->GatherCrashInfo(pszExePath, minidumpType)) + { + goto exit; + } + snprintf(dumpPath, MAX_LONGPATH, dumpPathTemplate, pid); + if (!dumpWriter->OpenDump(dumpPath)) + { + goto exit; + } + if (!dumpWriter->WriteDump()) + { + goto exit; + } + result = true; +exit: + dumpWriter->Release(); + crashInfo->ResumeThreads(); + crashInfo->Release(); + dataTarget->Release(); + return result; +} + +// +// main entry point +// +int __cdecl main(const int argc, const char* argv[]) +{ + const char* dumpPathTemplate = "/tmp/coredump.%lu"; + + char* diagnostics = getenv("COMPlus_CreateDumpDiagnostics"); + g_diagnostics = diagnostics != nullptr && strcmp(diagnostics, "1") == 0; + + int exitCode = PAL_InitializeDLL(); + if (exitCode != 0) + { + fprintf(stderr, "PAL_Initialize FAILED %d\n", exitCode); + return exitCode; + } + pid_t pid; + if (argc < 2) + { + fprintf(stderr, "Not enough arguments\n"); + exitCode = -1; + goto exit; + } + pid = _atoi64(argv[1]); + + //if (!CreateDump(argv[0], dumpPathTemplate, pid, MiniDumpWithPrivateReadWriteMemory)) + if (!CreateDump(argv[0], dumpPathTemplate, pid, MiniDumpNormal)) + { + exitCode = -1; + goto exit; + } +exit: + PAL_TerminateEx(exitCode); + return exitCode; +} \ No newline at end of file diff --git a/src/debug/createdump/createdump.h b/src/debug/createdump/createdump.h new file mode 100644 index 0000000..5a8d52d --- /dev/null +++ b/src/debug/createdump/createdump.h @@ -0,0 +1,56 @@ +// 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. + +#define ___in _SAL1_Source_(__in, (), _In_) +#define ___out _SAL1_Source_(__out, (), _Out_) + +#ifndef _countof +#define _countof(x) (sizeof(x)/sizeof(x[0])) +#endif + +extern bool g_diagnostics; + +#define TRACE(args...) \ + if (g_diagnostics) { \ + printf(args); \ + } + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "datatarget.h" +#include "threadinfo.h" +#include "memoryregion.h" +#include "crashinfo.h" +#include "dumpwriter.h" diff --git a/src/debug/createdump/datatarget.cpp b/src/debug/createdump/datatarget.cpp new file mode 100644 index 0000000..b8c09e0 --- /dev/null +++ b/src/debug/createdump/datatarget.cpp @@ -0,0 +1,263 @@ +// 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" + +#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8) + +DataTarget::DataTarget(pid_t pid) : + m_ref(1), + m_pid(pid), + m_fd(-1), + m_crashInfo(nullptr) +{ +} + +DataTarget::~DataTarget() +{ + if (m_fd != -1) + { + close(m_fd); + m_fd = -1; + } +} + +bool +DataTarget::Initialize(CrashInfo * crashInfo) +{ + char memPath[128]; + _snprintf_s(memPath, sizeof(memPath), sizeof(memPath), "/proc/%lu/mem", m_pid); + + m_fd = open(memPath, O_RDONLY); + if (m_fd == -1) + { + fprintf(stderr, "open(%s) FAILED %d (%s)\n", memPath, errno, strerror(errno)); + return false; + } + m_crashInfo = crashInfo; + return true; +} + +STDMETHODIMP +DataTarget::QueryInterface( + ___in REFIID InterfaceId, + ___out PVOID* Interface + ) +{ + if (InterfaceId == IID_IUnknown || + InterfaceId == IID_ICLRDataTarget) + { + *Interface = (ICLRDataTarget*)this; + AddRef(); + return S_OK; + } + else if (InterfaceId == IID_ICorDebugDataTarget4) + { + *Interface = (ICorDebugDataTarget4*)this; + AddRef(); + return S_OK; + } + else + { + *Interface = NULL; + return E_NOINTERFACE; + } +} + +STDMETHODIMP_(ULONG) +DataTarget::AddRef() +{ + LONG ref = InterlockedIncrement(&m_ref); + return ref; +} + +STDMETHODIMP_(ULONG) +DataTarget::Release() +{ + LONG ref = InterlockedDecrement(&m_ref); + if (ref == 0) + { + delete this; + } + return ref; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::GetMachineType( + /* [out] */ ULONG32 *machine) +{ +#ifdef _AMD64_ + *machine = IMAGE_FILE_MACHINE_AMD64; +#elif _ARM_ + *machine = IMAGE_FILE_MACHINE_ARMNT; +#elif _ARM64_ + *machine = IMAGE_FILE_MACHINE_ARM64; +#elif _X86_ + *machine = IMAGE_FILE_MACHINE_I386; +#else +#error Unsupported architecture +#endif + return S_OK; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::GetPointerSize( + /* [out] */ ULONG32 *size) +{ +#if defined(_AMD64_) || defined(_ARM64_) + *size = 8; +#elif defined(_ARM_) || defined(_X86_) + *size = 4; +#else +#error Unsupported architecture +#endif + return S_OK; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::GetImageBase( + /* [string][in] */ LPCWSTR moduleName, + /* [out] */ CLRDATA_ADDRESS *baseAddress) +{ + assert(m_crashInfo != nullptr); + *baseAddress = 0; + + char tempModuleName[MAX_PATH]; + int length = WideCharToMultiByte(CP_ACP, 0, moduleName, -1, tempModuleName, sizeof(tempModuleName), NULL, NULL); + if (length > 0) + { + for (const MemoryRegion& image : m_crashInfo->ModuleMappings()) + { + const char *name = strrchr(image.FileName(), '/'); + if (name != nullptr) + { + name++; + } + else + { + name = image.FileName(); + } + if (strcmp(name, tempModuleName) == 0) + { + *baseAddress = image.StartAddress(); + return S_OK; + } + } + } + return E_FAIL; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::ReadVirtual( + /* [in] */ CLRDATA_ADDRESS address, + /* [length_is][size_is][out] */ PBYTE buffer, + /* [in] */ ULONG32 size, + /* [optional][out] */ ULONG32 *done) +{ + assert(m_fd != -1); + size_t read = pread64(m_fd, buffer, size, (off64_t)address); + if (read == -1) + { + fprintf(stderr, "ReadVirtual FAILED %016lx %08x\n", address, size); + *done = 0; + return E_FAIL; + } + *done = read; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::WriteVirtual( + /* [in] */ CLRDATA_ADDRESS address, + /* [size_is][in] */ PBYTE buffer, + /* [in] */ ULONG32 size, + /* [optional][out] */ ULONG32 *done) +{ + assert(false); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::GetTLSValue( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 index, + /* [out] */ CLRDATA_ADDRESS* value) +{ + assert(false); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::SetTLSValue( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 index, + /* [in] */ CLRDATA_ADDRESS value) +{ + assert(false); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::GetCurrentThreadID( + /* [out] */ ULONG32* threadID) +{ + assert(false); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::GetThreadContext( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 contextFlags, + /* [in] */ ULONG32 contextSize, + /* [out, size_is(contextSize)] */ PBYTE context) +{ + assert(m_crashInfo != nullptr); + if (contextSize < sizeof(CONTEXT)) + { + assert(false); + return E_INVALIDARG; + } + memset(context, 0, contextSize); + for (const ThreadInfo* thread : m_crashInfo->Threads()) + { + if (thread->Tid() == threadID) + { + thread->GetThreadContext(contextFlags, reinterpret_cast(context)); + return S_OK; + } + } + return E_FAIL; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::SetThreadContext( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 contextSize, + /* [out, size_is(contextSize)] */ PBYTE context) +{ + assert(false); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::Request( + /* [in] */ ULONG32 reqCode, + /* [in] */ ULONG32 inBufferSize, + /* [size_is][in] */ BYTE *inBuffer, + /* [in] */ ULONG32 outBufferSize, + /* [size_is][out] */ BYTE *outBuffer) +{ + assert(false); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +DataTarget::VirtualUnwind( + /* [in] */ DWORD threadId, + /* [in] */ ULONG32 contextSize, + /* [in, out, size_is(contextSize)] */ PBYTE context) +{ + return E_NOTIMPL; +} \ No newline at end of file diff --git a/src/debug/createdump/datatarget.h b/src/debug/createdump/datatarget.h new file mode 100644 index 0000000..802c9d6 --- /dev/null +++ b/src/debug/createdump/datatarget.h @@ -0,0 +1,90 @@ +// 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. + +class CrashInfo; + +class DataTarget : public ICLRDataTarget, ICorDebugDataTarget4 +{ +private: + LONG m_ref; // reference count + pid_t m_pid; // process id + int m_fd; // /proc//mem handle + CrashInfo* m_crashInfo; + +public: + DataTarget(pid_t pid); + virtual ~DataTarget(); + bool Initialize(CrashInfo* crashInfo); + + // + // IUnknown + // + STDMETHOD(QueryInterface)(___in REFIID InterfaceId, ___out PVOID* Interface); + STDMETHOD_(ULONG, AddRef)(); + STDMETHOD_(ULONG, Release)(); + + // + // ICLRDataTarget + // + virtual HRESULT STDMETHODCALLTYPE GetMachineType( + /* [out] */ ULONG32 *machine); + + virtual HRESULT STDMETHODCALLTYPE GetPointerSize( + /* [out] */ ULONG32 *size); + + virtual HRESULT STDMETHODCALLTYPE GetImageBase( + /* [string][in] */ LPCWSTR moduleName, + /* [out] */ CLRDATA_ADDRESS *baseAddress); + + virtual HRESULT STDMETHODCALLTYPE ReadVirtual( + /* [in] */ CLRDATA_ADDRESS address, + /* [length_is][size_is][out] */ PBYTE buffer, + /* [in] */ ULONG32 size, + /* [optional][out] */ ULONG32 *done); + + virtual HRESULT STDMETHODCALLTYPE WriteVirtual( + /* [in] */ CLRDATA_ADDRESS address, + /* [size_is][in] */ PBYTE buffer, + /* [in] */ ULONG32 size, + /* [optional][out] */ ULONG32 *done); + + virtual HRESULT STDMETHODCALLTYPE GetTLSValue( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 index, + /* [out] */ CLRDATA_ADDRESS* value); + + virtual HRESULT STDMETHODCALLTYPE SetTLSValue( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 index, + /* [in] */ CLRDATA_ADDRESS value); + + virtual HRESULT STDMETHODCALLTYPE GetCurrentThreadID( + /* [out] */ ULONG32* threadID); + + virtual HRESULT STDMETHODCALLTYPE GetThreadContext( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 contextFlags, + /* [in] */ ULONG32 contextSize, + /* [out, size_is(contextSize)] */ PBYTE context); + + virtual HRESULT STDMETHODCALLTYPE SetThreadContext( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 contextSize, + /* [in, size_is(contextSize)] */ PBYTE context); + + virtual HRESULT STDMETHODCALLTYPE Request( + /* [in] */ ULONG32 reqCode, + /* [in] */ ULONG32 inBufferSize, + /* [size_is][in] */ BYTE *inBuffer, + /* [in] */ ULONG32 outBufferSize, + /* [size_is][out] */ BYTE *outBuffer); + + // + // ICorDebugDataTarget4 + // + virtual HRESULT STDMETHODCALLTYPE VirtualUnwind( + /* [in] */ DWORD threadId, + /* [in] */ ULONG32 contextSize, + /* [in, out, size_is(contextSize)] */ PBYTE context); +}; \ No newline at end of file diff --git a/src/debug/createdump/dumpwriter.cpp b/src/debug/createdump/dumpwriter.cpp new file mode 100644 index 0000000..ef3adac --- /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(DataTarget& dataTarget, CrashInfo& crashInfo) : + m_ref(1), + m_fd(-1), + m_dataTarget(dataTarget), + m_crashInfo(crashInfo) +{ + m_dataTarget.AddRef(); + m_crashInfo.AddRef(); +} + +DumpWriter::~DumpWriter() +{ + if (m_fd != -1) + { + close(m_fd); + m_fd = -1; + } + m_dataTarget.Release(); + 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(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; + } + printf("Writing core file %s\n", dumpFileName); + 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; + + printf("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; + } + + printf("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; + } + } + + printf("Writing %ld memory regions to core file\n", m_crashInfo.MemoryRegions().size()); + + // Read from target process and write memory regions to core + for (const MemoryRegion& memoryRegion : m_crashInfo.MemoryRegions()) + { + uint32_t size = memoryRegion.Size(); + uint64_t address = memoryRegion.StartAddress(); + + while (size > 0) + { + uint32_t bytesToRead = std::min(size, (uint32_t)sizeof(m_tempBuffer)); + uint32_t read = 0; + + if (FAILED(m_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; + } + } + + 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; + + printf("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; + + printf("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; + + printf("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; +} \ No newline at end of file diff --git a/src/debug/createdump/dumpwriter.h b/src/debug/createdump/dumpwriter.h new file mode 100644 index 0000000..61e3936 --- /dev/null +++ b/src/debug/createdump/dumpwriter.h @@ -0,0 +1,74 @@ +// 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. + +#ifdef BIT64 +#define ELF_CLASS ELFCLASS64 +#else +#define ELF_CLASS ELFCLASS32 +#endif + +#define Ehdr ElfW(Ehdr) +#define Phdr ElfW(Phdr) +#define Shdr ElfW(Shdr) +#define Nhdr ElfW(Nhdr) +#define auxv_t ElfW(auxv_t) + +#if defined(__x86_64__) +#define ELF_ARCH EM_X86_64 +#elif defined(__i386__) +#define ELF_ARCH EM_386 +#elif defined(__arm__) +#define ELF_ARCH EM_ARM +#endif + +#define PH_HDR_CANARY 0xFFFF + +class DumpWriter : IUnknown +{ +private: + LONG m_ref; // reference count + int m_fd; + DataTarget& m_dataTarget; + CrashInfo& m_crashInfo; + BYTE m_tempBuffer[0x4000]; + +public: + DumpWriter(DataTarget& dataTarget, CrashInfo& crashInfo); + virtual ~DumpWriter(); + bool OpenDump(char* dumpFileName); + bool WriteDump(); + + // IUnknown + STDMETHOD(QueryInterface)(___in REFIID InterfaceId, ___out PVOID* Interface); + STDMETHOD_(ULONG, AddRef)(); + STDMETHOD_(ULONG, Release)(); + +private: + bool WriteProcessInfo(); + bool WriteAuxv(); + size_t GetNTFileInfoSize(size_t* alignmentBytes = nullptr); + bool WriteNTFileInfo(); + bool WriteThread(const ThreadInfo& thread, int fatal_signal); + bool WriteData(const void* buffer, size_t length); + + const size_t GetProcessInfoSize() const { return sizeof(Nhdr) + 8 + sizeof(prpsinfo_t); } + const size_t GetAuxvInfoSize() const { return sizeof(Nhdr) + 8 + m_crashInfo.GetAuxvSize(); } + const size_t GetThreadInfoSize() const + { + return m_crashInfo.Threads().size() * ((sizeof(Nhdr) + 8 + sizeof(prstatus_t)) +#if defined(__i386__) || defined(__x86_64__) + + sizeof(Nhdr) + 8 + sizeof(user_fpregs_struct) +#endif +#if defined(__i386__) + + sizeof(Nhdr) + 8 + sizeof(user_fpxregs_struct) +#endif + ); + } +}; + +static inline int sex() +{ + int probe = 1; + return !*(char *)&probe; +} diff --git a/src/debug/createdump/memoryregion.h b/src/debug/createdump/memoryregion.h new file mode 100644 index 0000000..16c4d1c --- /dev/null +++ b/src/debug/createdump/memoryregion.h @@ -0,0 +1,97 @@ +// 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. + +struct MemoryRegion +{ +private: + uint32_t m_permissions; + uint64_t m_startAddress; + uint64_t m_endAddress; + uint64_t m_offset; + + // The name used for NT_FILE output + char* m_fileName; + +public: + MemoryRegion(uint64_t start, uint64_t end) : + m_permissions(PF_R | PF_W | PF_X), + m_startAddress(start), + m_endAddress(end), + m_offset(0), + m_fileName(nullptr) + { + assert((start & ~PAGE_MASK) == 0); + assert((end & ~PAGE_MASK) == 0); + } + + MemoryRegion(uint32_t permissions, uint64_t start, uint64_t end, uint64_t offset, char* filename) : + m_permissions(permissions), + m_startAddress(start), + m_endAddress(end), + m_offset(offset), + m_fileName(filename) + { + assert((start & ~PAGE_MASK) == 0); + assert((end & ~PAGE_MASK) == 0); + } + + const uint32_t Permissions() const + { + return m_permissions; + } + + const uint64_t StartAddress() const + { + return m_startAddress; + } + + const uint64_t EndAddress() const + { + return m_endAddress; + } + + const uint64_t Size() const + { + return m_endAddress - m_startAddress; + } + + const uint64_t Offset() const + { + return m_offset; + } + + const char* FileName() const + { + return m_fileName; + } + + bool operator<(const MemoryRegion& rhs) const + { + return (m_startAddress < rhs.m_startAddress) && (m_endAddress <= rhs.m_startAddress); + } + + bool Contains(const MemoryRegion& rhs) const + { + return (m_startAddress <= rhs.m_startAddress) && (m_endAddress >= rhs.m_endAddress); + } + + void Cleanup() + { + if (m_fileName != nullptr) + { + free(m_fileName); + m_fileName = nullptr; + } + } + + void Print() const + { + if (m_fileName != nullptr) { + TRACE("%016lx - %016lx (%04ld) %016lx %x %s\n", m_startAddress, m_endAddress, (Size() >> PAGE_SHIFT), m_offset, m_permissions, m_fileName); + } + else { + TRACE("%016lx - %016lx (%04ld) %02x\n", m_startAddress, m_endAddress, (Size() >> PAGE_SHIFT), m_permissions); + } + } +}; diff --git a/src/debug/createdump/threadinfo.cpp b/src/debug/createdump/threadinfo.cpp new file mode 100644 index 0000000..52af060 --- /dev/null +++ b/src/debug/createdump/threadinfo.cpp @@ -0,0 +1,154 @@ +// 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" + +ThreadInfo::ThreadInfo(pid_t tid) : + m_tid(tid) +{ +} + +ThreadInfo::~ThreadInfo() +{ +} + +bool +ThreadInfo::Initialize() +{ + if (!CrashInfo::GetStatus(m_tid, &m_ppid, &m_tgid, nullptr)) + { + return false; + } + if (!GetRegisters()) + { + return false; + } + TRACE("Thread %04x RIP %016llx RSP %016llx\n", m_tid, m_gpRegisters.rip, m_gpRegisters.rsp); + return true; +} + +void +ThreadInfo::ResumeThread() +{ + if (ptrace(PTRACE_DETACH, m_tid, nullptr, nullptr) != -1) + { + int waitStatus; + waitpid(m_tid, &waitStatus, __WALL); + } +} + +bool +ThreadInfo::GetRegisters() +{ + if (ptrace((__ptrace_request)PTRACE_GETREGS, m_tid, nullptr, &m_gpRegisters) == -1) + { + fprintf(stderr, "ptrace(GETREGS, %d) FAILED %d (%s)\n", m_tid, errno, strerror(errno)); + return false; + } + if (ptrace((__ptrace_request)PTRACE_GETFPREGS, m_tid, nullptr, &m_fpRegisters) == -1) + { + fprintf(stderr, "ptrace(GETFPREGS, %d) FAILED %d (%s)\n", m_tid, errno, strerror(errno)); + return false; + } +#if defined(__i386__) + if (ptrace((__ptrace_request)PTRACE_GETFPXREGS, m_tid, nullptr, &m_fpxRegisters) == -1) + { + fprintf(stderr, "ptrace(GETFPXREGS, %d) FAILED %d (%s)\n", m_tid, errno, strerror(errno)); + return false; + } +#endif + return true; +} + +void +ThreadInfo::GetThreadStack(const CrashInfo& crashInfo, uint64_t* startAddress, size_t* size) const +{ + *startAddress = m_gpRegisters.rsp & PAGE_MASK; + *size = 4 * PAGE_SIZE; + + for (const MemoryRegion& mapping : crashInfo.OtherMappings()) + { + if (*startAddress >= mapping.StartAddress() && *startAddress < mapping.EndAddress()) + { + // Use the mapping found for the size of the thread's stack + *size = mapping.EndAddress() - *startAddress; + + if (g_diagnostics) + { + TRACE("Thread %04x stack found in other mapping (size %08lx): ", m_tid, *size); + mapping.Print(); + } + break; + } + } +} + +void +ThreadInfo::GetThreadCode(uint64_t* startAddress, size_t* size) const +{ + *startAddress = m_gpRegisters.rip & PAGE_MASK; + *size = PAGE_SIZE; +} + +void +ThreadInfo::GetThreadContext(uint32_t flags, CONTEXT* context) const +{ + context->ContextFlags = flags; + if ((flags & CONTEXT_CONTROL) == CONTEXT_CONTROL) + { + context->Rbp = m_gpRegisters.rbp; + context->Rip = m_gpRegisters.rip; + context->SegCs = m_gpRegisters.cs; + context->EFlags = m_gpRegisters.eflags; + context->SegSs = m_gpRegisters.ss; + context->Rsp = m_gpRegisters.rsp; + } + if ((flags & CONTEXT_INTEGER) == CONTEXT_INTEGER) + { + context->Rdi = m_gpRegisters.rdi; + context->Rsi = m_gpRegisters.rsi; + context->Rbx = m_gpRegisters.rbx; + context->Rdx = m_gpRegisters.rdx; + context->Rcx = m_gpRegisters.rcx; + context->Rax = m_gpRegisters.rax; + context->R8 = m_gpRegisters.r8; + context->R9 = m_gpRegisters.r9; + context->R10 = m_gpRegisters.r10; + context->R11 = m_gpRegisters.r11; + context->R12 = m_gpRegisters.r12; + context->R13 = m_gpRegisters.r13; + context->R14 = m_gpRegisters.r14; + context->R15 = m_gpRegisters.r15; + } + if ((flags & CONTEXT_SEGMENTS) == CONTEXT_SEGMENTS) + { + context->SegDs = m_gpRegisters.ds; + context->SegEs = m_gpRegisters.es; + context->SegFs = m_gpRegisters.fs; + context->SegGs = m_gpRegisters.gs; + } + if ((flags & CONTEXT_FLOATING_POINT) == CONTEXT_FLOATING_POINT) + { + context->FltSave.ControlWord = m_fpRegisters.cwd; + context->FltSave.StatusWord = m_fpRegisters.swd; + context->FltSave.TagWord = m_fpRegisters.ftw; + context->FltSave.ErrorOpcode = m_fpRegisters.fop; + + context->FltSave.ErrorOffset = (DWORD)m_fpRegisters.rip; + context->FltSave.ErrorSelector = *(((WORD *)&m_fpRegisters.rip) + 2); + context->FltSave.DataOffset = (DWORD)m_fpRegisters.rdp; + context->FltSave.DataSelector = *(((WORD *)&m_fpRegisters.rdp) + 2); + + context->FltSave.MxCsr = m_fpRegisters.mxcsr; + context->FltSave.MxCsr_Mask = m_fpRegisters.mxcr_mask; + + assert(sizeof(context->FltSave.FloatRegisters) == sizeof(m_fpRegisters.st_space)); + memcpy(context->FltSave.FloatRegisters, m_fpRegisters.st_space, sizeof(context->FltSave.FloatRegisters)); + + assert(sizeof(context->FltSave.XmmRegisters) == sizeof(m_fpRegisters.xmm_space)); + memcpy(context->FltSave.XmmRegisters, m_fpRegisters.xmm_space, sizeof(context->FltSave.XmmRegisters)); + } + // TODO: debug registers? + // TODO: x86 registers +} diff --git a/src/debug/createdump/threadinfo.h b/src/debug/createdump/threadinfo.h new file mode 100644 index 0000000..a2aa3ce --- /dev/null +++ b/src/debug/createdump/threadinfo.h @@ -0,0 +1,41 @@ +// 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. + +class CrashInfo; + +class ThreadInfo +{ +private: + pid_t m_tid; // thread id + pid_t m_ppid; // parent process + pid_t m_tgid; // thread group + struct user_regs_struct m_gpRegisters; // general purpose registers + struct user_fpregs_struct m_fpRegisters; // floating point registers +#if defined(__i386__) + struct user_fpxregs_struct m_fpxRegisters; // x86 floating point registers +#endif + +public: + ThreadInfo(pid_t tid); + ~ThreadInfo(); + bool Initialize(); + void ResumeThread(); + void GetThreadStack(const CrashInfo& crashInfo, uint64_t* startAddress, size_t* size) const; + void GetThreadCode(uint64_t* startAddress, size_t* size) const; + void GetThreadContext(uint32_t flags, CONTEXT* context) const; + + const pid_t Tid() const { return m_tid; } + const pid_t Ppid() const { return m_ppid; } + const pid_t Tgid() const { return m_tgid; } + + const user_regs_struct* GPRegisters() const { return &m_gpRegisters; } + const user_fpregs_struct* FPRegisters() const { return &m_fpRegisters; } +#if defined(__i386__) + const user_fpxregs_struct* FPXRegisters() const { return &m_fpxRegisters; } +#endif + +private: + bool GetRegisters(); +}; + diff --git a/src/debug/daccess/dacfn.cpp b/src/debug/daccess/dacfn.cpp index 0a16741..2f7a98d 100644 --- a/src/debug/daccess/dacfn.cpp +++ b/src/debug/daccess/dacfn.cpp @@ -1386,6 +1386,8 @@ bool DacTargetConsistencyAssertsEnabled() // void DacEnumCodeForStackwalk(TADDR taCallEnd) { + if (taCallEnd == 0) + return; // // x86 stack walkers often end up having to guess // about what's a return address on the stack. diff --git a/src/debug/daccess/enummem.cpp b/src/debug/daccess/enummem.cpp index 027fe59..9305bba 100644 --- a/src/debug/daccess/enummem.cpp +++ b/src/debug/daccess/enummem.cpp @@ -22,6 +22,10 @@ #include "binder.h" #include "win32threadpool.h" +#ifdef FEATURE_PAL +#include +#endif + #ifdef FEATURE_APPX #include "appxutil.h" #endif // FEATURE_APPX @@ -220,6 +224,11 @@ HRESULT ClrDataAccess::EnumMemCLRStatic(IN CLRDataEnumMemoryFlags flags) #define DEFINE_DACVAR_SVR(id_type, size_type, id, var) \ ReportMem(m_globalBase + g_dacGlobals.id, sizeof(size_type)); +#ifdef FEATURE_PAL + // Add the dac table memory in coreclr + CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED ( ReportMem(m_globalBase + DAC_TABLE_RVA, sizeof(g_dacGlobals)); ) +#endif + // Cannot use CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED // around conditional preprocessor directives in a sane fashion. EX_TRY @@ -233,39 +242,33 @@ HRESULT ClrDataAccess::EnumMemCLRStatic(IN CLRDataEnumMemoryFlags flags) } EX_END_CATCH(RethrowCancelExceptions) - CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED - ( - // StressLog is not defined on Rotor build for DAC - ReportMem(m_globalBase + g_dacGlobals.dac__g_pStressLog, sizeof(StressLog *)); - ); + // StressLog is not defined on Rotor build for DAC + CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED ( ReportMem(m_globalBase + g_dacGlobals.dac__g_pStressLog, sizeof(StressLog *)); ) EX_TRY { // These two static pointers are pointed to static data of byte[] // then run constructor in place // - ReportMem(m_globalBase + g_dacGlobals.SystemDomain__m_pSystemDomain, - sizeof(SystemDomain)); - ReportMem(m_globalBase + g_dacGlobals.SharedDomain__m_pSharedDomain, - sizeof(SharedDomain)); + ReportMem(m_globalBase + g_dacGlobals.SystemDomain__m_pSystemDomain, sizeof(SystemDomain)); + ReportMem(m_globalBase + g_dacGlobals.SharedDomain__m_pSharedDomain, sizeof(SharedDomain)); // We need IGCHeap pointer to make EEVersion work - ReportMem(m_globalBase + g_dacGlobals.dac__g_pGCHeap, - sizeof(IGCHeap *)); + ReportMem(m_globalBase + g_dacGlobals.dac__g_pGCHeap, sizeof(IGCHeap *)); // see synblk.cpp, the pointer is pointed to a static byte[] SyncBlockCache::s_pSyncBlockCache.EnumMem(); #ifndef FEATURE_IMPLICIT_TLS - ReportMem(m_globalBase + g_dacGlobals.dac__gThreadTLSIndex, - sizeof(DWORD)); - ReportMem(m_globalBase + g_dacGlobals.dac__gAppDomainTLSIndex, - sizeof(DWORD)); + ReportMem(m_globalBase + g_dacGlobals.dac__gThreadTLSIndex, sizeof(DWORD)); + ReportMem(m_globalBase + g_dacGlobals.dac__gAppDomainTLSIndex, sizeof(DWORD)); #endif - ReportMem( m_globalBase + g_dacGlobals.dac__g_FCDynamicallyAssignedImplementations, + ReportMem(m_globalBase + g_dacGlobals.dac__g_FCDynamicallyAssignedImplementations, sizeof(TADDR)*ECall::NUM_DYNAMICALLY_ASSIGNED_FCALL_IMPLEMENTATIONS); + ReportMem(g_gcDacGlobals.GetAddr(), sizeof(GcDacVars)); + // We need all of the dac variables referenced by the GC DAC global struct. // This struct contains pointers to pointers, so we first dereference the pointers // to obtain the location of the variable that's reported. @@ -316,11 +319,8 @@ HRESULT ClrDataAccess::EnumMemCLRStatic(IN CLRDataEnumMemoryFlags flags) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( StubManager::EnumMemoryRegions(flags); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pFinalizerThread.EnumMem(); ) CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pSuspensionThread.EnumMem(); ) - - CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED - ( - g_heap_type.EnumMem(); - ); + + CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_heap_type.EnumMem(); ) m_dumpStats.m_cbClrStatics = m_cbMemoryReported - cbMemoryReported; @@ -345,8 +345,6 @@ HRESULT ClrDataAccess::EnumMemoryRegionsWorkerHeap(IN CLRDataEnumMemoryFlags fla HRESULT status = S_OK; - m_instances.ClearEnumMemMarker(); - // clear all of the previous cached memory Flush(); @@ -365,7 +363,6 @@ HRESULT ClrDataAccess::EnumMemoryRegionsWorkerHeap(IN CLRDataEnumMemoryFlags fla // would be true, but I don't think we have that guarantee here. CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpModuleList(flags); ); -#ifdef FEATURE_LAZY_COW_PAGES // Iterating to all threads' stacks, as we have to collect data stored inside (core)clr.dll CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAllThreadsStack(flags); ) @@ -377,11 +374,11 @@ HRESULT ClrDataAccess::EnumMemoryRegionsWorkerHeap(IN CLRDataEnumMemoryFlags fla // now dump the memory get dragged in by using DAC API implicitly. m_dumpStats.m_cbImplicity = m_instances.DumpAllInstances(m_enumMemCb); -#endif // FEATURE_LAZY_COW_PAGES - - // end of code status = m_memStatus; + // Do not let any remaining implicitly enumerated memory leak out. + Flush(); + return S_OK; } // EnumMemoryRegionsWorkerHeap @@ -976,16 +973,19 @@ HRESULT ClrDataAccess::EnumMemWalkStackHelper(CLRDataEnumMemoryFlags flags, { EECodeInfo codeInfo(addr); - // We want IsFilterFunclet to work for anything on the stack - codeInfo.GetJitManager()->IsFilterFunclet(&codeInfo); - - // The stackwalker needs GC info to find the parent 'stack pointer' or PSP - GCInfoToken gcInfoToken = codeInfo.GetGCInfoToken(); - PTR_BYTE pGCInfo = dac_cast(gcInfoToken.Info); - if (pGCInfo != NULL) + if (codeInfo.IsValid()) { - GcInfoDecoder gcDecoder(gcInfoToken, DECODE_PSP_SYM, 0); - DacEnumMemoryRegion(dac_cast(pGCInfo), gcDecoder.GetNumBytesRead(), true); + // We want IsFilterFunclet to work for anything on the stack + codeInfo.GetJitManager()->IsFilterFunclet(&codeInfo); + + // The stackwalker needs GC info to find the parent 'stack pointer' or PSP + GCInfoToken gcInfoToken = codeInfo.GetGCInfoToken(); + PTR_BYTE pGCInfo = dac_cast(gcInfoToken.Info); + if (pGCInfo != NULL) + { + GcInfoDecoder gcDecoder(gcInfoToken, DECODE_PSP_SYM, 0); + DacEnumMemoryRegion(dac_cast(pGCInfo), gcDecoder.GetNumBytesRead(), true); + } } } #endif // WIN64EXCEPTIONS && USE_GC_INFO_DECODER @@ -1603,10 +1603,6 @@ HRESULT ClrDataAccess::EnumMemoryRegionsWorkerSkinny(IN CLRDataEnumMemoryFlags f // collect CLR static CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRStatic(flags); ) - // now dump the memory get dragged in by using DAC API implicitly. - m_dumpStats.m_cbImplicity = m_instances.DumpAllInstances(m_enumMemCb); - status = m_memStatus; - // Dump AppDomain-specific info needed for MiniDumpNormal. CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAppDomainInfo(flags); ) @@ -1618,6 +1614,10 @@ HRESULT ClrDataAccess::EnumMemoryRegionsWorkerSkinny(IN CLRDataEnumMemoryFlags f CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( EnumStreams(flags); ) #endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS + // now dump the memory get dragged in by using DAC API implicitly. + m_dumpStats.m_cbImplicity = m_instances.DumpAllInstances(m_enumMemCb); + status = m_memStatus; + // Do not let any remaining implicitly enumerated memory leak out. Flush(); @@ -1654,10 +1654,6 @@ HRESULT ClrDataAccess::EnumMemoryRegionsWorkerMicroTriage(IN CLRDataEnumMemoryFl // collect CLR static CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRStatic(flags); ) - // now dump the memory get dragged in by using DAC API implicitly. - m_dumpStats.m_cbImplicity = m_instances.DumpAllInstances(m_enumMemCb); - status = m_memStatus; - // Dump AppDomain-specific info needed for triage dumps methods enumeration (k command). CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAppDomainInfo(flags); ) @@ -1669,6 +1665,10 @@ HRESULT ClrDataAccess::EnumMemoryRegionsWorkerMicroTriage(IN CLRDataEnumMemoryFl CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( EnumStreams(flags); ) #endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS + // now dump the memory get dragged in by using DAC API implicitly. + m_dumpStats.m_cbImplicity = m_instances.DumpAllInstances(m_enumMemCb); + status = m_memStatus; + // Do not let any remaining implicitly enumerated memory leak out. Flush(); @@ -1847,17 +1847,17 @@ HRESULT ClrDataAccess::EnumMemoryRegionsWrapper(IN CLRDataEnumMemoryFlags flags) // The various EnumMemoryRegions() implementations should understand // CLRDATA_ENUM_MEM_MINI to mean that the bare minimimum memory // to make a MiniDumpNormal work should be included. - if ( flags == CLRDATA_ENUM_MEM_MINI) + if (flags == CLRDATA_ENUM_MEM_MINI) { // skinny mini-dump status = EnumMemoryRegionsWorkerSkinny(flags); } - else if ( flags == CLRDATA_ENUM_MEM_TRIAGE) + else if (flags == CLRDATA_ENUM_MEM_TRIAGE) { // triage micro-dump status = EnumMemoryRegionsWorkerMicroTriage(flags); } - else if ( flags == CLRDATA_ENUM_MEM_HEAP) + else if (flags == CLRDATA_ENUM_MEM_HEAP) { status = EnumMemoryRegionsWorkerHeap(flags); } @@ -1874,12 +1874,6 @@ HRESULT ClrDataAccess::EnumMemoryRegionsWrapper(IN CLRDataEnumMemoryFlags flags) return status; } -#define MiniDumpWithPrivateReadWriteMemory 0x00000200 -#define MiniDumpWithFullAuxiliaryState 0x00008000 -#define MiniDumpFilterTriage 0x00100000 - - - //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // Entry function for generating CLR aware dump. This function is called @@ -1972,6 +1966,7 @@ ClrDataAccess::EnumMemoryRegions(IN ICLRDataEnumMemoryRegionsCallback* callback, status = EnumMemoryRegionsWrapper(CLRDATA_ENUM_MEM_MINI); } +#ifndef FEATURE_PAL // For all dump types, we need to capture the chain to the IMAGE_DIRECTORY_ENTRY_DEBUG // contents, so that DAC can validate against the TimeDateStamp even if the // debugger can't find the main CLR module on disk. @@ -1986,7 +1981,7 @@ ClrDataAccess::EnumMemoryRegions(IN ICLRDataEnumMemoryRegionsCallback* callback, m_instances.DumpAllInstances(m_enumMemCb); } } - +#endif Flush(); } EX_CATCH diff --git a/src/debug/daccess/request_svr.cpp b/src/debug/daccess/request_svr.cpp index 6a1de35..40e3600 100644 --- a/src/debug/daccess/request_svr.cpp +++ b/src/debug/daccess/request_svr.cpp @@ -22,6 +22,8 @@ int GCHeapCount() { + if (g_gcDacGlobals->n_heaps == nullptr) + return 0; return *g_gcDacGlobals->n_heaps; } @@ -206,14 +208,19 @@ void ClrDataAccess::EnumSvrGlobalMemoryRegions(CLRDataEnumMemoryFlags flags) { SUPPORTS_DAC; + + if (g_gcDacGlobals->n_heaps == nullptr || g_gcDacGlobals->g_heaps == nullptr) + return; + g_gcDacGlobals->n_heaps.EnumMem(); - DacEnumMemoryRegion(g_gcDacGlobals->g_heaps.GetAddr(), - sizeof(TADDR) * *g_gcDacGlobals->n_heaps); + + int heaps = *g_gcDacGlobals->n_heaps; + DacEnumMemoryRegion(g_gcDacGlobals->g_heaps.GetAddr(), sizeof(TADDR) * heaps); g_gcDacGlobals->gc_structures_invalid_cnt.EnumMem(); g_gcDacGlobals->g_heaps.EnumMem(); - for (int i=0; i < *g_gcDacGlobals->n_heaps; i++) + for (int i = 0; i < heaps; i++) { DPTR(dac_gc_heap) pHeap = HeapTableIndex(g_gcDacGlobals->g_heaps, i); @@ -249,6 +256,9 @@ DWORD DacGetNumHeaps() HRESULT DacHeapWalker::InitHeapDataSvr(HeapData *&pHeaps, size_t &pCount) { + if (g_gcDacGlobals->n_heaps == nullptr || g_gcDacGlobals->g_heaps == nullptr) + return S_OK; + // Scrape basic heap details int heaps = *g_gcDacGlobals->n_heaps; pCount = heaps; diff --git a/src/debug/inc/dump/dumpcommon.h b/src/debug/inc/dump/dumpcommon.h index 3e197ce..e57b4b3 100644 --- a/src/debug/inc/dump/dumpcommon.h +++ b/src/debug/inc/dump/dumpcommon.h @@ -5,6 +5,35 @@ #ifndef DEBUGGER_DUMPCOMMON_H #define DEBUGGER_DUMPCOMMON_H +#ifdef FEATURE_PAL +typedef enum _MINIDUMP_TYPE { + MiniDumpNormal = 0x00000000, + MiniDumpWithDataSegs = 0x00000001, + MiniDumpWithFullMemory = 0x00000002, + MiniDumpWithHandleData = 0x00000004, + MiniDumpFilterMemory = 0x00000008, + MiniDumpScanMemory = 0x00000010, + MiniDumpWithUnloadedModules = 0x00000020, + MiniDumpWithIndirectlyReferencedMemory = 0x00000040, + MiniDumpFilterModulePaths = 0x00000080, + MiniDumpWithProcessThreadData = 0x00000100, + MiniDumpWithPrivateReadWriteMemory = 0x00000200, + MiniDumpWithoutOptionalData = 0x00000400, + MiniDumpWithFullMemoryInfo = 0x00000800, + MiniDumpWithThreadInfo = 0x00001000, + MiniDumpWithCodeSegs = 0x00002000, + MiniDumpWithoutAuxiliaryState = 0x00004000, + MiniDumpWithFullAuxiliaryState = 0x00008000, + MiniDumpWithPrivateWriteCopyMemory = 0x00010000, + MiniDumpIgnoreInaccessibleMemory = 0x00020000, + MiniDumpWithTokenInformation = 0x00040000, + MiniDumpWithModuleHeaders = 0x00080000, + MiniDumpFilterTriage = 0x00100000, + MiniDumpWithAvxXStateContext = 0x00200000, + MiniDumpValidTypeFlags = 0x003fffff, +} MINIDUMP_TYPE; +#endif // FEATURE_PAL + #if defined(DACCESS_COMPILE) || defined(RIGHT_SIDE_COMPILE) // When debugging against minidumps, we frequently need to ignore errors diff --git a/src/dlls/mscordac/mscordac_unixexports.src b/src/dlls/mscordac/mscordac_unixexports.src index ab73c4f..c2c96fa 100644 --- a/src/dlls/mscordac/mscordac_unixexports.src +++ b/src/dlls/mscordac/mscordac_unixexports.src @@ -24,6 +24,7 @@ PAL_get_stderr PAL_GetSymbolModuleBase PAL_GetTransportPipeName PAL_InitializeDLL +PAL_TerminateEx PAL_IsDebuggerPresent PAL_ProbeMemory PAL_iswspace @@ -119,6 +120,9 @@ IID_IClassFactory IID_ISequentialStream IID_IStream IID_IUnknown +IID_ICLRDataTarget +IID_ICorDebugDataTarget4 +IID_ICLRDataEnumMemoryRegionsCallback InitializeCriticalSection IsDBCSLeadByte LeaveCriticalSection diff --git a/src/inc/arrayholder.h b/src/inc/arrayholder.h new file mode 100644 index 0000000..681014f --- /dev/null +++ b/src/inc/arrayholder.h @@ -0,0 +1,80 @@ +// 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. + +template +class ArrayHolder +{ +public: + ArrayHolder(T *ptr) + : m_ptr(ptr) + { + } + + ~ArrayHolder() + { + Clear(); + } + + ArrayHolder(const ArrayHolder &rhs) + { + m_ptr = const_cast(&rhs)->Detach(); + } + + ArrayHolder &operator=(T *ptr) + { + Clear(); + m_ptr = ptr; + return *this; + } + + const T &operator[](int i) const + { + return m_ptr[i]; + } + + T &operator[](int i) + { + return m_ptr[i]; + } + + operator const T *() const + { + return m_ptr; + } + + operator T *() + { + return m_ptr; + } + + T **operator&() + { + return &m_ptr; + } + + T *GetPtr() + { + return m_ptr; + } + + T *Detach() + { + T *ret = m_ptr; + m_ptr = NULL; + return ret; + } + +private: + void Clear() + { + if (m_ptr) + { + delete [] m_ptr; + m_ptr = NULL; + } + } + +private: + T *m_ptr; +}; diff --git a/src/pal/inc/pal.h b/src/pal/inc/pal.h index 8430ea9..5d2739e 100644 --- a/src/pal/inc/pal.h +++ b/src/pal/inc/pal.h @@ -487,9 +487,12 @@ PAL_NotifyRuntimeStarted(VOID); static const int MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH = 64; PALIMPORT -void +VOID PALAPI -PAL_GetTransportPipeName(char *name, DWORD id, const char *suffix); +PAL_GetTransportPipeName( + OUT char *name, + IN DWORD id, + IN const char *suffix); PALIMPORT void diff --git a/src/pal/src/config.h.in b/src/pal/src/config.h.in index ab5fa03..c2939f3 100644 --- a/src/pal/src/config.h.in +++ b/src/pal/src/config.h.in @@ -20,6 +20,7 @@ #cmakedefine01 HAVE_RUNETYPE_H #cmakedefine01 HAVE_SYS_SYSCTL_H #cmakedefine01 HAVE_GNU_LIBNAMES_H +#cmakedefine01 HAVE_PRCTL_H #cmakedefine01 HAVE_KQUEUE #cmakedefine01 HAVE_GETPWUID_R diff --git a/src/pal/src/configure.cmake b/src/pal/src/configure.cmake index 4d78f54..03c7343 100644 --- a/src/pal/src/configure.cmake +++ b/src/pal/src/configure.cmake @@ -34,6 +34,7 @@ check_include_files(lwp.h HAVE_LWP_H) check_include_files(libunwind.h HAVE_LIBUNWIND_H) check_include_files(runetype.h HAVE_RUNETYPE_H) check_include_files(semaphore.h HAVE_SEMAPHORE_H) +check_include_files(sys/prctl.h HAVE_PRCTL_H) if(NOT CMAKE_SYSTEM_NAME STREQUAL FreeBSD AND NOT CMAKE_SYSTEM_NAME STREQUAL NetBSD) set(CMAKE_REQUIRED_FLAGS "-ldl") diff --git a/src/pal/src/include/pal/process.h b/src/pal/src/include/pal/process.h index 990aec5..63ef5c5 100644 --- a/src/pal/src/include/pal/process.h +++ b/src/pal/src/include/pal/process.h @@ -121,6 +121,21 @@ Abstract VOID PROCProcessUnlock(VOID); /*++ +Function + PROCAbortInitialize() + +Abstract + Initialize the process abort crash dump program file path and + name. Doing all of this ahead of time so nothing is allocated + or copied in PROCAbort/signal handler. + +Return + TRUE - succeeds, FALSE - fails + +--*/ +BOOL PROCAbortInitialize(); + +/*++ Function: PROCAbort() @@ -130,7 +145,7 @@ Function: Does not return --*/ PAL_NORETURN -void PROCAbort(); +VOID PROCAbort(); /*++ Function: @@ -141,7 +156,7 @@ Function: (no return value) --*/ -void PROCNotifyProcessShutdown(); +VOID PROCNotifyProcessShutdown(); /*++ Function: diff --git a/src/pal/src/init/pal.cpp b/src/pal/src/init/pal.cpp index fa94922..8b0e0f5 100644 --- a/src/pal/src/init/pal.cpp +++ b/src/pal/src/init/pal.cpp @@ -650,6 +650,12 @@ PAL_InitializeCoreCLR(const char *szExePath) return ERROR_DLL_INIT_FAILED; } + if (!PROCAbortInitialize()) + { + printf("PROCAbortInitialize FAILED %d (%s)\n", errno, strerror(errno)); + return ERROR_GEN_FAILURE; + } + if (!InitializeFlushProcessWriteBuffers()) { return ERROR_GEN_FAILURE; diff --git a/src/pal/src/thread/process.cpp b/src/pal/src/thread/process.cpp index 050665c..e7380ee 100644 --- a/src/pal/src/thread/process.cpp +++ b/src/pal/src/thread/process.cpp @@ -49,12 +49,17 @@ SET_DEFAULT_DEBUG_CHANNEL(PROCESS); // some headers have code with asserts, so d #include #include #include +#if HAVE_PRCTL_H +#include +#include +#endif #include #include #include #include #include #include +#include #ifdef __APPLE__ #include @@ -67,6 +72,8 @@ SET_DEFAULT_DEBUG_CHANNEL(PROCESS); // some headers have code with asserts, so d #include #endif +extern char *g_szCoreCLRPath; + using namespace CorUnix; CObjectType CorUnix::otProcess( @@ -87,18 +94,6 @@ CObjectType CorUnix::otProcess( CObjectType::NoOwner ); -static -DWORD -PALAPI -StartupHelperThread( - LPVOID p); - -static -BOOL -GetProcessIdDisambiguationKey( - IN DWORD processId, - OUT UINT64 *disambiguationKey); - // // Helper memory page used by the FlushProcessWriteBuffers // @@ -153,6 +148,9 @@ DWORD gSID = (DWORD) -1; // Function to call during PAL/process shutdown/abort Volatile g_shutdownCallback = nullptr; +// Crash dump generating program arguments. Initialized in PROCAbortInitialize(). +char *g_argvCreateDump[3] = { nullptr, nullptr, nullptr }; + // // Key used for associating CPalThread's with the underlying pthread // (through pthread_setspecific) @@ -172,22 +170,30 @@ enum FILETYPE FILE_DIR /*Directory*/ }; +static +DWORD +PALAPI +StartupHelperThread( + LPVOID p); + +static +BOOL +GetProcessIdDisambiguationKey( + IN DWORD processId, + OUT UINT64 *disambiguationKey); + PAL_ERROR PROCGetProcessStatus( CPalThread *pThread, HANDLE hProcess, PROCESS_STATE *pps, - DWORD *pdwExitCode - ); + DWORD *pdwExitCode); -static BOOL getFileName(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, - PathCharString& lpFileName); -static char ** buildArgv(LPCWSTR lpCommandLine, PathCharString& lpAppPath, - UINT *pnArg, BOOL prependLoader); +static BOOL getFileName(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, PathCharString& lpFileName); +static char ** buildArgv(LPCWSTR lpCommandLine, PathCharString& lpAppPath, UINT *pnArg, BOOL prependLoader); static BOOL getPath(PathCharString& lpFileName, PathCharString& lpPathFileName); static int checkFileType(LPCSTR lpFileName); -static BOOL PROCEndProcess(HANDLE hProcess, UINT uExitCode, - BOOL bTerminateUnconditionally); +static BOOL PROCEndProcess(HANDLE hProcess, UINT uExitCode, BOOL bTerminateUnconditionally); ProcessModules *GetProcessModulesFromHandle(IN HANDLE hProcess, OUT LPDWORD lpCount); ProcessModules *CreateProcessModules(IN DWORD dwProcessId, OUT LPDWORD lpCount); @@ -1382,7 +1388,7 @@ static BOOL PROCEndProcess(HANDLE hProcess, UINT uExitCode, BOOL bTerminateUncon // (1) it doesn't run atexit handlers // (2) can invoke CrashReporter or produce a coredump, // which is appropriate for TerminateProcess calls - abort(); + PROCAbort(); } else { @@ -2081,9 +2087,12 @@ GetProcessIdDisambiguationKey(DWORD processId, UINT64 *disambiguationKey) Builds the transport pipe names from the process id. --*/ -void +VOID PALAPI -PAL_GetTransportPipeName(char *name, DWORD id, const char *suffix) +PAL_GetTransportPipeName( + OUT char *name, + IN DWORD id, + IN const char *suffix) { UINT64 disambiguationKey = 0; BOOL ret = GetProcessIdDisambiguationKey(id, &disambiguationKey); @@ -2829,7 +2838,7 @@ Return None --*/ -void +VOID DestroyProcessModules(IN ProcessModules *listHead) { for (ProcessModules *entry = listHead; entry != NULL; ) @@ -2841,7 +2850,7 @@ DestroyProcessModules(IN ProcessModules *listHead) } /*++ -Function: +Function PROCNotifyProcessShutdown Calls the abort handler to do any shutdown cleanup. Call be called @@ -2850,7 +2859,8 @@ Function: (no return value) --*/ __attribute__((destructor)) -void PROCNotifyProcessShutdown() +VOID +PROCNotifyProcessShutdown() { // Call back into the coreclr to clean up the debugger transport pipes PSHUTDOWN_CALLBACK callback = InterlockedExchangePointer(&g_shutdownCallback, NULL); @@ -2861,6 +2871,67 @@ void PROCNotifyProcessShutdown() } /*++ +Function + PROCAbortInitialize() + +Abstract + Initialize the process abort crash dump program file path and + name. Doing all of this ahead of time so nothing is allocated + or copied in PROCAbort/signal handler. + +Return + TRUE - succeeds, FALSE - fails + +--*/ +BOOL +PROCAbortInitialize() +{ + char* enabled = getenv("COMPlus_DbgEnableMiniDump"); + if (enabled != nullptr && _stricmp(enabled, "1") == 0) + { + if (g_szCoreCLRPath == nullptr) + { + return FALSE; + } + const char* DumpGeneratorName = "createdump"; + int programLen = strlen(g_szCoreCLRPath) + strlen(DumpGeneratorName); + char* program = new char[programLen]; + + if (strcpy_s(program, programLen, g_szCoreCLRPath) != SAFECRT_SUCCESS) + { + return FALSE; + } + char *last = strrchr(program, '/'); + if (last != nullptr) + { + *(last + 1) = '\0'; + } + else + { + program[0] = '\0'; + } + if (strcat_s(program, programLen, DumpGeneratorName) != SAFECRT_SUCCESS) + { + return FALSE; + } + char pidarg[128]; + if (sprintf_s(pidarg, sizeof(pidarg), "%d", gPID) == -1) + { + return FALSE; + } + g_argvCreateDump[0] = program; + g_argvCreateDump[1] = _strdup(pidarg); + g_argvCreateDump[2] = nullptr; + + if (g_argvCreateDump[0] == nullptr || g_argvCreateDump[1] == nullptr) + { + return FALSE; + } + } + return TRUE; +} + +/*++ Function: PROCAbort() @@ -2870,10 +2941,51 @@ Function: Does not return --*/ PAL_NORETURN -void +VOID PROCAbort() { + // Do any shutdown cleanup before aborting or creating a core dump PROCNotifyProcessShutdown(); + +#if HAVE_PRCTL_H + // If enabled, launch the create minidump utility and wait until it completes + if (g_argvCreateDump[0] != nullptr) + { + // Fork the core dump child process. + pid_t childpid = fork(); + + // If error, write an error to trace log and abort + if (childpid == -1) + { + ERROR("PROCAbort: fork() FAILED %d (%s)\n", errno, strerror(errno)); + } + else if (childpid == 0) + { + // Child process + if (execve(g_argvCreateDump[0], g_argvCreateDump, palEnvironment) == -1) + { + ERROR("PROCAbort: execve FAILED %d (%s)\n", errno, strerror(errno)); + } + } + else + { + // Gives the child process permission to use /proc//mem and ptrace + if (prctl(PR_SET_PTRACER, childpid, 0, 0, 0) == -1) + { + ERROR("PROCAbort: prctl() FAILED %d (%s)\n", errno, strerror(errno)); + } + // Parent waits until the child process is done + int wstatus; + int result = waitpid(childpid, &wstatus, 0); + if (result != childpid) + { + ERROR("PROCAbort: waitpid FAILED result %d wstatus %d errno %d (%s)\n", + result, wstatus, errno, strerror(errno)); + } + } + } +#endif // HAVE_PRCTL_H + // Abort the process after waiting for the core dump to complete abort(); } @@ -2886,7 +2998,8 @@ Abstract Return TRUE if it succeeded, FALSE otherwise --*/ -BOOL InitializeFlushProcessWriteBuffers() +BOOL +InitializeFlushProcessWriteBuffers() { // Verify that the s_helperPage is really aligned to the VIRTUAL_PAGE_SIZE _ASSERTE((((SIZE_T)s_helperPage) & (VIRTUAL_PAGE_SIZE - 1)) == 0); @@ -3273,7 +3386,7 @@ Parameter pThread: Thread object --*/ -void +VOID CorUnix::PROCAddThread( CPalThread *pCurrentThread, CPalThread *pTargetThread @@ -3306,7 +3419,7 @@ Parameter (no return value) --*/ -void +VOID CorUnix::PROCRemoveThread( CPalThread *pCurrentThread, CPalThread *pTargetThread @@ -3376,7 +3489,7 @@ Return --*/ INT CorUnix::PROCGetNumberOfThreads( - void) + VOID) { return g_dwThreadCount; } @@ -3479,7 +3592,7 @@ Note: This function is used in ExitThread and TerminateProcess --*/ -void +VOID CorUnix::TerminateCurrentProcessNoExit(BOOL bTerminateUnconditionally) { BOOL locked; -- 2.7.4