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)
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
extern "C" PDEBUG_DATA_SPACES g_ExtData;
-template <class T>
-class ArrayHolder
-{
-public:
- ArrayHolder(T *ptr)
- : mPtr(ptr)
- {
- }
-
- ~ArrayHolder()
- {
- Clear();
- }
-
- ArrayHolder(const ArrayHolder &rhs)
- {
- mPtr = const_cast<ArrayHolder *>(&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 <arrayholder.h>
// 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
add_subdirectory(ildbsymlib)
add_subdirectory(ee)
add_subdirectory(di)
-add_subdirectory(shim)
+add_subdirectory(shim)
\ No newline at end of file
--- /dev/null
+This folder will be mirrored by the Git-TFS Mirror recursively.
\ No newline at end of file
--- /dev/null
+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)
--- /dev/null
+// 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<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)
+ {
+ // 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/<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 *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<char> 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<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");
+ 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;
+}
--- /dev/null
+// 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<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, 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<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* 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();
+};
--- /dev/null
+// 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<char> 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
--- /dev/null
+// 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 <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"
--- /dev/null
+// 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*>(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
--- /dev/null
+// 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/<pid>/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
--- /dev/null
+// 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
--- /dev/null
+// 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;
+}
--- /dev/null
+// 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);
+ }
+ }
+};
--- /dev/null
+// 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
+}
--- /dev/null
+// 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();
+};
+
//
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.
#include "binder.h"
#include "win32threadpool.h"
+#ifdef FEATURE_PAL
+#include <dactablerva.h>
+#endif
+
#ifdef FEATURE_APPX
#include "appxutil.h"
#endif // FEATURE_APPX
#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
}
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.
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;
HRESULT status = S_OK;
- m_instances.ClearEnumMemMarker();
-
// clear all of the previous cached memory
Flush();
// 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); )
// 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
{
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
// 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); )
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();
// 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); )
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();
// 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);
}
return status;
}
-#define MiniDumpWithPrivateReadWriteMemory 0x00000200
-#define MiniDumpWithFullAuxiliaryState 0x00008000
-#define MiniDumpFilterTriage 0x00100000
-
-
-
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// Entry function for generating CLR aware dump. This function is called
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.
m_instances.DumpAllInstances(m_enumMemCb);
}
}
-
+#endif
Flush();
}
EX_CATCH
int GCHeapCount()
{
+ if (g_gcDacGlobals->n_heaps == nullptr)
+ return 0;
return *g_gcDacGlobals->n_heaps;
}
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);
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;
#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
PAL_GetSymbolModuleBase
PAL_GetTransportPipeName
PAL_InitializeDLL
+PAL_TerminateEx
PAL_IsDebuggerPresent
PAL_ProbeMemory
PAL_iswspace
IID_ISequentialStream
IID_IStream
IID_IUnknown
+IID_ICLRDataTarget
+IID_ICorDebugDataTarget4
+IID_ICLRDataEnumMemoryRegionsCallback
InitializeCriticalSection
IsDBCSLeadByte
LeaveCriticalSection
--- /dev/null
+// 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 T>
+class ArrayHolder
+{
+public:
+ ArrayHolder(T *ptr)
+ : m_ptr(ptr)
+ {
+ }
+
+ ~ArrayHolder()
+ {
+ Clear();
+ }
+
+ ArrayHolder(const ArrayHolder &rhs)
+ {
+ m_ptr = const_cast<ArrayHolder *>(&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;
+};
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
#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
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")
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()
Does not return
--*/
PAL_NORETURN
-void PROCAbort();
+VOID PROCAbort();
/*++
Function:
(no return value)
--*/
-void PROCNotifyProcessShutdown();
+VOID PROCNotifyProcessShutdown();
/*++
Function:
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;
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
+#if HAVE_PRCTL_H
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+#endif
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <debugmacrosext.h>
#include <semaphore.h>
#include <stdint.h>
+#include <dlfcn.h>
#ifdef __APPLE__
#include <sys/sysctl.h>
#include <kvm.h>
#endif
+extern char *g_szCoreCLRPath;
+
using namespace CorUnix;
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
//
// Function to call during PAL/process shutdown/abort
Volatile<PSHUTDOWN_CALLBACK> 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)
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);
// (1) it doesn't run atexit handlers
// (2) can invoke CrashReporter or produce a coredump,
// which is appropriate for TerminateProcess calls
- abort();
+ PROCAbort();
}
else
{
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);
None
--*/
-void
+VOID
DestroyProcessModules(IN ProcessModules *listHead)
{
for (ProcessModules *entry = listHead; entry != NULL; )
}
/*++
-Function:
+Function
PROCNotifyProcessShutdown
Calls the abort handler to do any shutdown cleanup. Call be called
(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);
}
/*++
+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()
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/<pid>/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();
}
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);
pThread: Thread object
--*/
-void
+VOID
CorUnix::PROCAddThread(
CPalThread *pCurrentThread,
CPalThread *pTargetThread
(no return value)
--*/
-void
+VOID
CorUnix::PROCRemoveThread(
CPalThread *pCurrentThread,
CPalThread *pTargetThread
--*/
INT
CorUnix::PROCGetNumberOfThreads(
- void)
+ VOID)
{
return g_dwThreadCount;
}
This function is used in ExitThread and TerminateProcess
--*/
-void
+VOID
CorUnix::TerminateCurrentProcessNoExit(BOOL bTerminateUnconditionally)
{
BOOL locked;