diff options
Diffstat (limited to 'src/debug')
-rw-r--r-- | src/debug/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/debug/createdump/.gitmirrorall | 1 | ||||
-rw-r--r-- | src/debug/createdump/CMakeLists.txt | 38 | ||||
-rw-r--r-- | src/debug/createdump/crashinfo.cpp | 628 | ||||
-rw-r--r-- | src/debug/createdump/crashinfo.h | 73 | ||||
-rw-r--r-- | src/debug/createdump/createdump.cpp | 69 | ||||
-rw-r--r-- | src/debug/createdump/createdump.h | 57 | ||||
-rw-r--r-- | src/debug/createdump/datatarget.cpp | 263 | ||||
-rw-r--r-- | src/debug/createdump/datatarget.h | 90 | ||||
-rw-r--r-- | src/debug/createdump/dumpwriter.cpp | 486 | ||||
-rw-r--r-- | src/debug/createdump/dumpwriter.h | 77 | ||||
-rw-r--r-- | src/debug/createdump/main.cpp | 99 | ||||
-rw-r--r-- | src/debug/createdump/memoryregion.h | 97 | ||||
-rw-r--r-- | src/debug/createdump/threadinfo.cpp | 235 | ||||
-rw-r--r-- | src/debug/createdump/threadinfo.h | 42 | ||||
-rw-r--r-- | src/debug/daccess/dacfn.cpp | 2 | ||||
-rw-r--r-- | src/debug/daccess/enummem.cpp | 103 | ||||
-rw-r--r-- | src/debug/daccess/request_svr.cpp | 16 | ||||
-rw-r--r-- | src/debug/di/dbgtransportmanager.cpp | 4 | ||||
-rw-r--r-- | src/debug/inc/dump/dumpcommon.h | 29 |
20 files changed, 2351 insertions, 60 deletions
diff --git a/src/debug/CMakeLists.txt b/src/debug/CMakeLists.txt index 1940aa9c79..bcfc257b9d 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 0000000000..9ee5c57b99 --- /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 0000000000..5b5ec0a5c6 --- /dev/null +++ b/src/debug/createdump/CMakeLists.txt @@ -0,0 +1,38 @@ +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_library(createdump_lib + ${CREATEDUMP_SOURCES} +) + +_add_executable(createdump + main.cpp +) + +target_link_libraries(createdump + createdump_lib + # 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 0000000000..cae8857d65 --- /dev/null +++ b/src/debug/createdump/crashinfo.cpp @@ -0,0 +1,628 @@ +// 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, ICLRDataTarget* dataTarget, bool sos) : + m_ref(1), + m_pid(pid), + m_ppid(-1), + m_name(nullptr), + m_sos(sos), + 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<MemoryRegion&>(region).Cleanup(); + } + m_moduleMappings.clear(); + for (const MemoryRegion& region : m_otherMappings) + { + const_cast<MemoryRegion&>(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<pid_t>(strtol(entry->d_name, nullptr, 10)); + if (tid != 0) + { + // Don't suspend the threads if running under sos + if (!m_sos) + { + // 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); + } + else + { + fprintf(stderr, "ptrace(ATTACH, %d) FAILED %s\n", tid, strerror(errno)); + closedir(taskDir); + return false; + } + } + // Add to the list of threads + ThreadInfo* thread = new ThreadInfo(tid); + m_threads.push_back(thread); + } + } + + closedir(taskDir); + return true; +} + +bool +CrashInfo::GatherCrashInfo(const char* programPath, 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(m_sos ? m_dataTarget : nullptr)) + { + 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(programPath, 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() +{ + if (!m_sos) + { + 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/<pid>/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<void*>(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* programPath, 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 + std::string dacPath; + dacPath.append(programPath); + dacPath.append("/"); + dacPath.append(MAKEDLLNAME_A("mscordaccore")); + + // Load and initialize the DAC + hdac = LoadLibraryA(dacPath.c_str()); + if (hdac == nullptr) + { + fprintf(stderr, "LoadLibraryA(%s) FAILED %d\n", dacPath.c_str(), 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<Phdr*>(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<ElfW(Dyn)*>(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<struct r_debug*>(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<CLRDATA_ADDRESS>(address), reinterpret_cast<PBYTE>(buffer), size, &read))) + { + return false; + } + InsertMemoryRegion(reinterpret_cast<uint64_t>(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<MemoryRegion> 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"); + for (const MemoryRegion& region : m_memoryRegions) + { + region.Print(); + } + } +} + +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 0000000000..40d7f5da45 --- /dev/null +++ b/src/debug/createdump/crashinfo.h @@ -0,0 +1,73 @@ +// 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 + bool m_sos; // true if running under sos + ICLRDataTarget* m_dataTarget; // read process memory, etc. + std::array<elf_aux_val_t, AT_MAX> m_auxvValues; // auxv values + std::vector<elf_aux_entry> m_auxvEntries; // full auxv entries + std::vector<ThreadInfo*> m_threads; // threads found and suspended + std::set<MemoryRegion> m_moduleMappings; // module memory mappings + std::set<MemoryRegion> m_otherMappings; // other memory mappings + std::set<MemoryRegion> m_memoryRegions; // memory regions from DAC, etc. + +public: + CrashInfo(pid_t pid, ICLRDataTarget* dataTarget, bool sos); + virtual ~CrashInfo(); + bool EnumerateAndSuspendThreads(); + bool GatherCrashInfo(const char* programPath, 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; } + ICLRDataTarget* DataTarget() const { return m_dataTarget; } + + const std::vector<ThreadInfo*> Threads() const { return m_threads; } + const std::set<MemoryRegion> ModuleMappings() const { return m_moduleMappings; } + const std::set<MemoryRegion> OtherMappings() const { return m_otherMappings; } + const std::set<MemoryRegion> MemoryRegions() const { return m_memoryRegions; } + const std::vector<elf_aux_entry> 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* programPath, 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 0000000000..0a95e535aa --- /dev/null +++ b/src/debug/createdump/createdump.cpp @@ -0,0 +1,69 @@ +// 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 = false; + +// +// The common create dump code +// +bool +CreateDumpCommon(const char* programPath, const char* dumpPathTemplate, MINIDUMP_TYPE minidumpType, CrashInfo* crashInfo) +{ + ReleaseHolder<DumpWriter> dumpWriter = new DumpWriter(*crashInfo); + bool result = false; + + ArrayHolder<char> dumpPath = new char[MAX_LONGPATH]; + snprintf(dumpPath, MAX_LONGPATH, dumpPathTemplate, crashInfo->Pid()); + + const char* dumpType = "minidump"; + switch (minidumpType) + { + case MiniDumpWithPrivateReadWriteMemory: + dumpType = "minidump with heap"; + break; + + case MiniDumpFilterTriage: + dumpType = "triage minidump"; + break; + + default: + break; + } + printf("Writing %s to file %s\n", dumpType, (char*)dumpPath); + + // Suspend all the threads in the target process and build the list of threads + if (!crashInfo->EnumerateAndSuspendThreads()) + { + goto exit; + } + // Gather all the info about the process, threads (registers, etc.) and memory regions + if (!crashInfo->GatherCrashInfo(programPath, minidumpType)) + { + goto exit; + } + if (!dumpWriter->OpenDump(dumpPath)) + { + goto exit; + } + if (!dumpWriter->WriteDump()) + { + goto exit; + } + result = true; +exit: + crashInfo->ResumeThreads(); + return result; +} + +// +// Entry point for SOS createdump command +// +bool +CreateDumpForSOS(const char* programPath, const char* dumpPathTemplate, pid_t pid, MINIDUMP_TYPE minidumpType, ICLRDataTarget* dataTarget) +{ + ReleaseHolder<CrashInfo> crashInfo = new CrashInfo(pid, dataTarget, true); + return CreateDumpCommon(programPath, dumpPathTemplate, minidumpType, crashInfo); +}
\ 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 0000000000..38c3525f34 --- /dev/null +++ b/src/debug/createdump/createdump.h @@ -0,0 +1,57 @@ +// 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 <winternl.h> +#include <winver.h> +#include <windows.h> +#include <stdlib.h> +#include <stdint.h> +#include <stddef.h> +#include <string.h> +#include <corhdr.h> +#include <cor.h> +#include <corsym.h> +#include <clrdata.h> +#include <xclrdata.h> +#include <corerror.h> +#include <cordebug.h> +#include <xcordebug.h> +#include <mscoree.h> +#include <dumpcommon.h> +#include <arrayholder.h> +#include <releaseholder.h> +#include <unistd.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ptrace.h> +#include <sys/user.h> +#include <sys/wait.h> +#include <sys/procfs.h> +#include <dirent.h> +#include <fcntl.h> +#include <elf.h> +#include <link.h> +#include <map> +#include <set> +#include <vector> +#include "datatarget.h" +#include "threadinfo.h" +#include "memoryregion.h" +#include "crashinfo.h" +#include "dumpwriter.h"
\ No newline at end of file diff --git a/src/debug/createdump/datatarget.cpp b/src/debug/createdump/datatarget.cpp new file mode 100644 index 0000000000..38505e2d45 --- /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) + +DumpDataTarget::DumpDataTarget(pid_t pid) : + m_ref(1), + m_pid(pid), + m_fd(-1), + m_crashInfo(nullptr) +{ +} + +DumpDataTarget::~DumpDataTarget() +{ + if (m_fd != -1) + { + close(m_fd); + m_fd = -1; + } +} + +bool +DumpDataTarget::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 +DumpDataTarget::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) +DumpDataTarget::AddRef() +{ + LONG ref = InterlockedIncrement(&m_ref); + return ref; +} + +STDMETHODIMP_(ULONG) +DumpDataTarget::Release() +{ + LONG ref = InterlockedDecrement(&m_ref); + if (ref == 0) + { + delete this; + } + return ref; +} + +HRESULT STDMETHODCALLTYPE +DumpDataTarget::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 +DumpDataTarget::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 +DumpDataTarget::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 +DumpDataTarget::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 +DumpDataTarget::WriteVirtual( + /* [in] */ CLRDATA_ADDRESS address, + /* [size_is][in] */ PBYTE buffer, + /* [in] */ ULONG32 size, + /* [optional][out] */ ULONG32 *done) +{ + assert(false); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +DumpDataTarget::GetTLSValue( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 index, + /* [out] */ CLRDATA_ADDRESS* value) +{ + assert(false); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +DumpDataTarget::SetTLSValue( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 index, + /* [in] */ CLRDATA_ADDRESS value) +{ + assert(false); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +DumpDataTarget::GetCurrentThreadID( + /* [out] */ ULONG32* threadID) +{ + assert(false); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +DumpDataTarget::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*>(context)); + return S_OK; + } + } + return E_FAIL; +} + +HRESULT STDMETHODCALLTYPE +DumpDataTarget::SetThreadContext( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 contextSize, + /* [out, size_is(contextSize)] */ PBYTE context) +{ + assert(false); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +DumpDataTarget::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 +DumpDataTarget::VirtualUnwind( + /* [in] */ DWORD threadId, + /* [in] */ ULONG32 contextSize, + /* [in, out, size_is(contextSize)] */ PBYTE context) +{ + return E_NOTIMPL; +} diff --git a/src/debug/createdump/datatarget.h b/src/debug/createdump/datatarget.h new file mode 100644 index 0000000000..8ff6773fa3 --- /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 DumpDataTarget : public ICLRDataTarget, ICorDebugDataTarget4 +{ +private: + LONG m_ref; // reference count + pid_t m_pid; // process id + int m_fd; // /proc/<pid>/mem handle + CrashInfo* m_crashInfo; + +public: + DumpDataTarget(pid_t pid); + virtual ~DumpDataTarget(); + 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); +}; 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; +} diff --git a/src/debug/createdump/dumpwriter.h b/src/debug/createdump/dumpwriter.h new file mode 100644 index 0000000000..0b4f88cbd6 --- /dev/null +++ b/src/debug/createdump/dumpwriter.h @@ -0,0 +1,77 @@ +// 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 + +#ifndef NT_FILE +#define NT_FILE 0x46494c45 +#endif + +class DumpWriter : IUnknown +{ +private: + LONG m_ref; // reference count + int m_fd; + CrashInfo& m_crashInfo; + BYTE m_tempBuffer[0x4000]; + +public: + DumpWriter(CrashInfo& crashInfo); + virtual ~DumpWriter(); + bool OpenDump(const 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/main.cpp b/src/debug/createdump/main.cpp new file mode 100644 index 0000000000..03382779a5 --- /dev/null +++ b/src/debug/createdump/main.cpp @@ -0,0 +1,99 @@ +// 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" + +const char* g_help = "createdump [options] pid\n" +"-f, --name - dump path and file name. The pid can be placed in the name with %d. The default is '/tmp/coredump.%d'\n" +"-n, --normal - create minidump.\n" +"-h, --withheap - create minidump with heap (default).\n" +"-t, --triage - create triage minidump.\n" +"-d, --diag - enable diagnostic messages.\n"; + +bool CreateDumpCommon(const char* programPath, const char* dumpPathTemplate, MINIDUMP_TYPE minidumpType, CrashInfo* crashInfo); + +// +// Main entry point +// +int __cdecl main(const int argc, const char* argv[]) +{ + MINIDUMP_TYPE minidumpType = MiniDumpWithPrivateReadWriteMemory; +#ifdef ANDROID + const char* dumpPathTemplate = "/data/local/tmp/coredump.%d"; +#else + const char* dumpPathTemplate = "/tmp/coredump.%d"; +#endif + pid_t pid = 0; + + int exitCode = PAL_InitializeDLL(); + if (exitCode != 0) + { + fprintf(stderr, "PAL initialization FAILED %d\n", exitCode); + return exitCode; + } + + // Parse off the program name leaving just the path. Used to locate/load the DAC module. + std::string programPath; + programPath.append(*argv++); + size_t last = programPath.find_last_of('/'); + programPath = programPath.substr(0, last); + + // Parse the command line options and target pid + for (int i = 1; i < argc; i++) + { + if (*argv != nullptr) + { + if ((strcmp(*argv, "-f") == 0) || (strcmp(*argv, "--name") == 0)) + { + dumpPathTemplate = *++argv; + } + else if ((strcmp(*argv, "-n") == 0) || (strcmp(*argv, "--normal") == 0)) + { + minidumpType = MiniDumpNormal; + } + else if ((strcmp(*argv, "-h") == 0) || (strcmp(*argv, "--withheap") == 0)) + { + minidumpType = MiniDumpWithPrivateReadWriteMemory; + } + else if ((strcmp(*argv, "-t") == 0) || (strcmp(*argv, "--triage") == 0)) + { + minidumpType = MiniDumpFilterTriage; + } + else if ((strcmp(*argv, "-d") == 0) || (strcmp(*argv, "--diag") == 0)) + { + g_diagnostics = true; + } + else { + pid = atoll(*argv); + } + argv++; + } + } + if (pid != 0) + { + ReleaseHolder<DumpDataTarget> dataTarget = new DumpDataTarget(pid); + ReleaseHolder<CrashInfo> crashInfo = new CrashInfo(pid, dataTarget, false); + + // The initialize the data target's ReadVirtual support (opens /proc/$pid/mem) + if (dataTarget->Initialize(crashInfo)) + { + if (!CreateDumpCommon(programPath.c_str(), dumpPathTemplate, minidumpType, crashInfo)) + { + exitCode = -1; + } + } + else + { + exitCode = -1; + } + } + else + { + // if no pid or invalid command line option + fprintf(stderr, "%s", g_help); + exitCode = -1; + } + PAL_TerminateEx(exitCode); + return exitCode; +} diff --git a/src/debug/createdump/memoryregion.h b/src/debug/createdump/memoryregion.h new file mode 100644 index 0000000000..16c4d1c693 --- /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 0000000000..8e73fcf2cb --- /dev/null +++ b/src/debug/createdump/threadinfo.cpp @@ -0,0 +1,235 @@ +// 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 FPREG_ErrorOffset(fpregs) *(DWORD*)&((fpregs).rip) +#define FPREG_ErrorSelector(fpregs) *(((WORD*)&((fpregs).rip)) + 2) +#define FPREG_DataOffset(fpregs) *(DWORD*)&((fpregs).rdp) +#define FPREG_DataSelector(fpregs) *(((WORD*)&((fpregs).rdp)) + 2) + +ThreadInfo::ThreadInfo(pid_t tid) : + m_tid(tid) +{ +} + +ThreadInfo::~ThreadInfo() +{ +} + +bool +ThreadInfo::Initialize(ICLRDataTarget* dataTarget) +{ + if (!CrashInfo::GetStatus(m_tid, &m_ppid, &m_tgid, nullptr)) + { + return false; + } + if (dataTarget != nullptr) + { + if (!GetRegistersWithDataTarget(dataTarget)) + { + return false; + } + } + else { + if (!GetRegistersWithPTrace()) + { + 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::GetRegistersWithPTrace() +{ + 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; +} + +bool +ThreadInfo::GetRegistersWithDataTarget(ICLRDataTarget* dataTarget) +{ + CONTEXT context; + context.ContextFlags = CONTEXT_ALL; + if (dataTarget->GetThreadContext(m_tid, context.ContextFlags, sizeof(context), reinterpret_cast<PBYTE>(&context)) != S_OK) + { + return false; + } +#if defined(__x86_64__) + m_gpRegisters.rbp = context.Rbp; + m_gpRegisters.rip = context.Rip; + m_gpRegisters.cs = context.SegCs; + m_gpRegisters.eflags = context.EFlags; + m_gpRegisters.ss = context.SegSs; + m_gpRegisters.rsp = context.Rsp; + m_gpRegisters.rdi = context.Rdi; + + m_gpRegisters.rsi = context.Rsi; + m_gpRegisters.rbx = context.Rbx; + m_gpRegisters.rdx = context.Rdx; + m_gpRegisters.rcx = context.Rcx; + m_gpRegisters.rax = context.Rax; + m_gpRegisters.orig_rax = context.Rax; + m_gpRegisters.r8 = context.R8; + m_gpRegisters.r9 = context.R9; + m_gpRegisters.r10 = context.R10; + m_gpRegisters.r11 = context.R11; + m_gpRegisters.r12 = context.R12; + m_gpRegisters.r13 = context.R13; + m_gpRegisters.r14 = context.R14; + m_gpRegisters.r15 = context.R15; + + m_gpRegisters.ds = context.SegDs; + m_gpRegisters.es = context.SegEs; + m_gpRegisters.fs = context.SegFs; + m_gpRegisters.gs = context.SegGs; + m_gpRegisters.fs_base = 0; + m_gpRegisters.gs_base = 0; + + m_fpRegisters.cwd = context.FltSave.ControlWord; + m_fpRegisters.swd = context.FltSave.StatusWord; + m_fpRegisters.ftw = context.FltSave.TagWord; + m_fpRegisters.fop = context.FltSave.ErrorOpcode; + + FPREG_ErrorOffset(m_fpRegisters) = context.FltSave.ErrorOffset; + FPREG_ErrorSelector(m_fpRegisters) = context.FltSave.ErrorSelector; + FPREG_DataOffset(m_fpRegisters) = context.FltSave.DataOffset; + FPREG_DataSelector(m_fpRegisters) = context.FltSave.DataSelector; + + m_fpRegisters.mxcsr = context.FltSave.MxCsr; + m_fpRegisters.mxcr_mask = context.FltSave.MxCsr_Mask; + + assert(sizeof(context.FltSave.FloatRegisters) == sizeof(m_fpRegisters.st_space)); + memcpy(m_fpRegisters.st_space, context.FltSave.FloatRegisters, sizeof(m_fpRegisters.st_space)); + + assert(sizeof(context.FltSave.XmmRegisters) == sizeof(m_fpRegisters.xmm_space)); + memcpy(m_fpRegisters.xmm_space, context.FltSave.XmmRegisters, sizeof(m_fpRegisters.xmm_space)); +#else +#error Platform not supported +#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 defined(__x86_64__) + 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 = FPREG_ErrorOffset(m_fpRegisters); + context->FltSave.ErrorSelector = FPREG_ErrorSelector(m_fpRegisters); + context->FltSave.DataOffset = FPREG_DataOffset(m_fpRegisters); + context->FltSave.DataSelector = FPREG_DataSelector(m_fpRegisters); + + 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? +#else +#error Platform not supported +#endif +} diff --git a/src/debug/createdump/threadinfo.h b/src/debug/createdump/threadinfo.h new file mode 100644 index 0000000000..8620219747 --- /dev/null +++ b/src/debug/createdump/threadinfo.h @@ -0,0 +1,42 @@ +// 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(ICLRDataTarget* dataTarget); + 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 GetRegistersWithPTrace(); + bool GetRegistersWithDataTarget(ICLRDataTarget* dataTarget); +}; + diff --git a/src/debug/daccess/dacfn.cpp b/src/debug/daccess/dacfn.cpp index 0a167418a1..2f7a98de1a 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 027fe59543..9305bba488 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 <dactablerva.h> +#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<PTR_BYTE>(gcInfoToken.Info); - if (pGCInfo != NULL) + if (codeInfo.IsValid()) { - GcInfoDecoder gcDecoder(gcInfoToken, DECODE_PSP_SYM, 0); - DacEnumMemoryRegion(dac_cast<TADDR>(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<PTR_BYTE>(gcInfoToken.Info); + if (pGCInfo != NULL) + { + GcInfoDecoder gcDecoder(gcInfoToken, DECODE_PSP_SYM, 0); + DacEnumMemoryRegion(dac_cast<TADDR>(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 6a1de35ff9..40e3600f9f 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/di/dbgtransportmanager.cpp b/src/debug/di/dbgtransportmanager.cpp index 77a3548ea5..8c1079dc33 100644 --- a/src/debug/di/dbgtransportmanager.cpp +++ b/src/debug/di/dbgtransportmanager.cpp @@ -102,7 +102,7 @@ HRESULT DbgTransportTarget::GetTransportForProcess(DWORD dwPID entry->m_cProcessRef++; _ASSERTE(entry->m_cProcessRef > 0); _ASSERTE(entry->m_transport != NULL); - _ASSERTE(entry->m_hProcess > 0); + _ASSERTE((intptr_t)entry->m_hProcess > 0); *ppTransport = entry->m_transport; if (!DuplicateHandle(GetCurrentProcess(), @@ -139,7 +139,7 @@ void DbgTransportTarget::ReleaseTransport(DbgTransportSession *pTransport) _ASSERTE(entry->m_cProcessRef > 0); _ASSERTE(entry->m_transport != NULL); - _ASSERTE(entry->m_hProcess > 0); + _ASSERTE((intptr_t)entry->m_hProcess > 0); if (entry->m_transport == pTransport) { diff --git a/src/debug/inc/dump/dumpcommon.h b/src/debug/inc/dump/dumpcommon.h index 3e197ce29b..e57b4b3a12 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 |