From 2f563e3a456b2bbf37c53352955cbe380ca584c9 Mon Sep 17 00:00:00 2001 From: Mike McLaughlin Date: Wed, 3 Jun 2020 09:51:25 -0700 Subject: [PATCH] MacOS createdump and createdump cleanup. (#37224) * General createdump cleanup. Converted the char* name fields (process name in CrashInfo and module name in MemoryRegion) to std::string for easier cleanup and usage. Removed the vestiges of the SOS support (the m_sos flag, ThreadInfo::GetRegistersWithDataTarget(), etc.). Moved the Linux read memory code out of the DataTarget into CrashInfo. DataTarget is now a thin wrapper around CrashInfo. In the dumpwriter, fix the "number of program headers" calculate to just the ones backed by memory. Only write the memory regions backed by memory. This makes lldb fail the memory read in these regions instead of returning zeros. Removed the TRACE macro from the ElfReader; use the Trace() virtual method directly. * Add MacOS createdump Add MachO in-memory module reader (MachOReader). Refactor CrashInfo and ThreadInfo in Unix and MacOS versions. Enabled crash/unhandled exception createdump trigger. Add unwind and eh frame info to coredump --- src/coreclr/src/CMakeLists.txt | 4 +- src/coreclr/src/debug/createdump/CMakeLists.txt | 18 +- src/coreclr/src/debug/createdump/crashinfo.cpp | 486 +++------------------ src/coreclr/src/debug/createdump/crashinfo.h | 68 ++- src/coreclr/src/debug/createdump/crashinfomac.cpp | 390 +++++++++++++++++ src/coreclr/src/debug/createdump/crashinfounix.cpp | 402 +++++++++++++++++ src/coreclr/src/debug/createdump/createdump.cpp | 15 +- src/coreclr/src/debug/createdump/createdump.h | 15 + src/coreclr/src/debug/createdump/datatarget.cpp | 56 +-- src/coreclr/src/debug/createdump/datatarget.h | 9 +- src/coreclr/src/debug/createdump/dumpwriter.cpp | 88 ++-- src/coreclr/src/debug/createdump/dumpwriter.h | 8 +- src/coreclr/src/debug/createdump/mac.h | 192 ++++++++ src/coreclr/src/debug/createdump/memoryregion.h | 49 ++- src/coreclr/src/debug/createdump/threadinfo.cpp | 234 +--------- src/coreclr/src/debug/createdump/threadinfo.h | 36 +- src/coreclr/src/debug/createdump/threadinfomac.cpp | 89 ++++ .../src/debug/createdump/threadinfounix.cpp | 101 +++++ src/coreclr/src/debug/dbgutil/CMakeLists.txt | 11 +- src/coreclr/src/debug/dbgutil/elfreader.cpp | 64 ++- src/coreclr/src/debug/dbgutil/machoreader.cpp | 420 ++++++++++++++++++ src/coreclr/src/debug/dbgutil/machoreader.h | 64 +++ src/coreclr/src/pal/src/thread/process.cpp | 5 +- 23 files changed, 1948 insertions(+), 876 deletions(-) create mode 100644 src/coreclr/src/debug/createdump/crashinfomac.cpp create mode 100644 src/coreclr/src/debug/createdump/crashinfounix.cpp create mode 100644 src/coreclr/src/debug/createdump/mac.h create mode 100644 src/coreclr/src/debug/createdump/threadinfomac.cpp create mode 100644 src/coreclr/src/debug/createdump/threadinfounix.cpp create mode 100644 src/coreclr/src/debug/dbgutil/machoreader.cpp create mode 100644 src/coreclr/src/debug/dbgutil/machoreader.h diff --git a/src/coreclr/src/CMakeLists.txt b/src/coreclr/src/CMakeLists.txt index 68e7221..e269657 100644 --- a/src/coreclr/src/CMakeLists.txt +++ b/src/coreclr/src/CMakeLists.txt @@ -15,9 +15,9 @@ endif(CLR_CMAKE_TARGET_WIN32 AND FEATURE_EVENT_TRACE) add_subdirectory(debug/dbgutil) if(CLR_CMAKE_HOST_UNIX) - if(CLR_CMAKE_HOST_LINUX AND NOT CLR_CMAKE_HOST_UNIX_X86 AND NOT CLR_CMAKE_HOST_ANDROID) + if(CLR_CMAKE_HOST_OSX OR (CLR_CMAKE_HOST_LINUX AND NOT CLR_CMAKE_HOST_UNIX_X86 AND NOT CLR_CMAKE_HOST_ANDROID)) add_subdirectory(debug/createdump) - endif(CLR_CMAKE_HOST_LINUX AND NOT CLR_CMAKE_HOST_UNIX_X86 AND NOT CLR_CMAKE_HOST_ANDROID) + endif(CLR_CMAKE_HOST_OSX OR (CLR_CMAKE_HOST_LINUX AND NOT CLR_CMAKE_HOST_UNIX_X86 AND NOT CLR_CMAKE_HOST_ANDROID)) # Include the dummy c++ include files include_directories("pal/inc/rt/cpp") diff --git a/src/coreclr/src/debug/createdump/CMakeLists.txt b/src/coreclr/src/debug/createdump/CMakeLists.txt index 0915212..595a071 100644 --- a/src/coreclr/src/debug/createdump/CMakeLists.txt +++ b/src/coreclr/src/debug/createdump/CMakeLists.txt @@ -3,6 +3,9 @@ project(createdump) set(CMAKE_INCLUDE_CURRENT_DIR ON) include_directories(BEFORE ${VM_DIR}) +if(CLR_CMAKE_HOST_OSX) + include_directories(${CLR_DIR}/src/inc/llvm) +endif(CLR_CMAKE_HOST_OSX) remove_definitions(-DUNICODE) remove_definitions(-D_UNICODE) @@ -45,6 +48,7 @@ else(CLR_CMAKE_HOST_WIN32) add_definitions(-DPAL_STDCPP_COMPAT) set(CREATEDUMP_SOURCES + main.cpp createdump.cpp crashinfo.cpp threadinfo.cpp @@ -52,19 +56,23 @@ else(CLR_CMAKE_HOST_WIN32) dumpwriter.cpp ) - _add_library(createdump_lib +if(CLR_CMAKE_HOST_OSX) + _add_executable(createdump + crashinfomac.cpp + threadinfomac.cpp ${CREATEDUMP_SOURCES} ) - +else() _add_executable(createdump - main.cpp + crashinfounix.cpp + threadinfounix.cpp + ${CREATEDUMP_SOURCES} ${PAL_REDEFINES_FILE} ) - add_dependencies(createdump pal_redefines_file) +endif(CLR_CMAKE_HOST_OSX) target_link_libraries(createdump - createdump_lib corguids dbgutil # share the PAL in the dac module diff --git a/src/coreclr/src/debug/createdump/crashinfo.cpp b/src/coreclr/src/debug/createdump/crashinfo.cpp index a04a6e8..835fe78 100644 --- a/src/coreclr/src/debug/createdump/crashinfo.cpp +++ b/src/coreclr/src/debug/createdump/crashinfo.cpp @@ -7,44 +7,25 @@ // This is for the PAL_VirtualUnwindOutOfProc read memory adapter. CrashInfo* g_crashInfo; -CrashInfo::CrashInfo(pid_t pid, ICLRDataTarget* dataTarget, bool sos) : +CrashInfo::CrashInfo(pid_t pid) : m_ref(1), m_pid(pid), - m_ppid(-1), - m_name(nullptr), - m_sos(sos), - m_dataTarget(dataTarget) + m_ppid(-1) { g_crashInfo = this; - dataTarget->AddRef(); +#ifndef __APPLE__ m_auxvValues.fill(0); +#endif } CrashInfo::~CrashInfo() { - if (m_name != nullptr) - { - free(m_name); - } // Clean up the threads for (ThreadInfo* thread : m_threads) { delete thread; } m_threads.clear(); - - // Module and other mappings have a file name to clean up. - for (const MemoryRegion& region : m_moduleMappings) - { - const_cast(region).Cleanup(); - } - m_moduleMappings.clear(); - for (const MemoryRegion& region : m_otherMappings) - { - const_cast(region).Cleanup(); - } - m_otherMappings.clear(); - m_dataTarget->Release(); } STDMETHODIMP @@ -94,73 +75,25 @@ CrashInfo::EnumMemoryRegion( } // -// Suspends all the threads and creating a list of them. Should be the first before -// gather any info about the process. -// -bool -CrashInfo::EnumerateAndSuspendThreads() -{ - char taskPath[128]; - snprintf(taskPath, sizeof(taskPath), "/proc/%d/task", m_pid); - - DIR* taskDir = opendir(taskPath); - if (taskDir == nullptr) - { - fprintf(stderr, "opendir(%s) FAILED %s\n", taskPath, strerror(errno)); - return false; - } - - struct dirent* entry; - while ((entry = readdir(taskDir)) != nullptr) - { - pid_t tid = static_cast(strtol(entry->d_name, nullptr, 10)); - if (tid != 0) - { - // 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; -} - -// // Gather all the necessary crash dump info. // bool CrashInfo::GatherCrashInfo(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)) + if (!thread->Initialize()) { return false; } } +#ifdef __APPLE__ + if (!EnumerateMemoryRegions()) + { + return false; + } +#else // Get the auxv data if (!GetAuxvEntries()) { @@ -176,12 +109,12 @@ CrashInfo::GatherCrashInfo(MINIDUMP_TYPE minidumpType) { return false; } - +#endif + TRACE("Module addresses:\n"); for (const MemoryRegion& region : m_moduleAddresses) { region.Trace(); } - // If full memory dump, include everything regardless of permissions if (minidumpType & MiniDumpWithFullMemory) { @@ -198,283 +131,45 @@ CrashInfo::GatherCrashInfo(MINIDUMP_TYPE minidumpType) } } } - // Add all the heap read/write memory regions (m_otherMappings contains the heaps). On Alpine - // the heap regions are marked RWX instead of just RW. - else if (minidumpType & MiniDumpWithPrivateReadWriteMemory) + else { - for (const MemoryRegion& region : m_otherMappings) + // Add all the heap read/write memory regions (m_otherMappings contains the heaps). On Alpine + // the heap regions are marked RWX instead of just RW. + if (minidumpType & MiniDumpWithPrivateReadWriteMemory) { - uint32_t permissions = region.Permissions(); - if (permissions == (PF_R | PF_W) || permissions == (PF_R | PF_W | PF_X)) + for (const MemoryRegion& region : m_otherMappings) { - InsertMemoryBackedRegion(region); + uint32_t permissions = region.Permissions(); + if (permissions == (PF_R | PF_W) || permissions == (PF_R | PF_W | PF_X)) + { + InsertMemoryBackedRegion(region); + } } } - } - // Gather all the useful memory regions from the DAC - if (!EnumerateMemoryRegionsWithDAC(minidumpType)) - { - return false; - } - if ((minidumpType & MiniDumpWithFullMemory) == 0) - { // Add the thread's stack and some code memory to core for (ThreadInfo* thread : m_threads) { // Add the thread's stack - thread->GetThreadStack(*this); - } - // All the regions added so far has been backed by memory. Now add the rest of - // mappings so the debuggers like lldb see that an address is code (PF_X) even - // if it isn't actually in the core dump. - for (const MemoryRegion& region : m_moduleMappings) - { - assert(!region.IsBackedByMemory()); - InsertMemoryRegion(region); - } - for (const MemoryRegion& region : m_otherMappings) - { - assert(!region.IsBackedByMemory()); - InsertMemoryRegion(region); + thread->GetThreadStack(); } } - // Join all adjacent memory regions - CombineMemoryRegions(); - return true; -} - -void -CrashInfo::ResumeThreads() -{ - if (!m_sos) - { - for (ThreadInfo* thread : m_threads) - { - thread->ResumeThread(); - } - } -} - -// -// Get the auxv entries to use and add to the core dump -// -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: %" PRIu " = %" PRIxA "\n", auxvEntry.a_type, auxvEntry.a_un.a_val); - result = true; - } - } - - close(fd); - return result; -} - -// -// Get the module mappings for the core dump NT_FILE notes -// -bool -CrashInfo::EnumerateModuleMappings() -{ - // Here we read /proc//maps file in order to parse it and figure out what it says - // about a library we are looking for. This file looks something like this: - // - // [address] [perms] [offset] [dev] [inode] [pathname] - HEADER is not preset in an actual file - // - // 35b1800000-35b1820000 r-xp 00000000 08:02 135522 /usr/lib64/ld-2.15.so - // 35b1a1f000-35b1a20000 r--p 0001f000 08:02 135522 /usr/lib64/ld-2.15.so - // 35b1a20000-35b1a21000 rw-p 00020000 08:02 135522 /usr/lib64/ld-2.15.so - // 35b1a21000-35b1a22000 rw-p 00000000 00:00 0 [heap] - // 35b1c00000-35b1dac000 r-xp 00000000 08:02 135870 /usr/lib64/libc-2.15.so - // 35b1dac000-35b1fac000 ---p 001ac000 08:02 135870 /usr/lib64/libc-2.15.so - // 35b1fac000-35b1fb0000 r--p 001ac000 08:02 135870 /usr/lib64/libc-2.15.so - // 35b1fb0000-35b1fb2000 rw-p 001b0000 08:02 135870 /usr/lib64/libc-2.15.so - char* line = nullptr; - 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 && (size_t)chars <= sizeof(mapPath)); - - FILE* mapsFile = fopen(mapPath, "r"); - if (mapsFile == nullptr) + // Gather all the useful memory regions from the DAC + if (!EnumerateMemoryRegionsWithDAC(minidumpType)) { - 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 = sscanf(line, "%" PRIx64 "-%" PRIx64 " %m[-rwxsp] %" PRIx64 " %*[:0-9a-f] %*d %ms\n", &start, &end, &permissions, &offset, &moduleName); - if (c == 4 || c == 5) - { - // r = read - // w = write - // x = execute - // s = shared - // p = private (copy on write) - uint32_t regionFlags = 0; - if (strchr(permissions, 'r')) { - regionFlags |= PF_R; - } - if (strchr(permissions, 'w')) { - regionFlags |= PF_W; - } - if (strchr(permissions, 'x')) { - regionFlags |= PF_X; - } - if (strchr(permissions, 's')) { - regionFlags |= MEMORY_REGION_FLAG_SHARED; - } - if (strchr(permissions, 'p')) { - regionFlags |= MEMORY_REGION_FLAG_PRIVATE; - } - MemoryRegion memoryRegion(regionFlags, start, end, offset, moduleName); - - if (moduleName != nullptr && *moduleName == '/') - { - m_moduleMappings.insert(memoryRegion); - } - else - { - m_otherMappings.insert(memoryRegion); - } - if (linuxGateAddress != nullptr && reinterpret_cast(start) == linuxGateAddress) - { - InsertMemoryBackedRegion(memoryRegion); - } - free(permissions); - } - } - - if (g_diagnostics) - { - TRACE("Module mappings:\n"); - for (const MemoryRegion& region : m_moduleMappings) - { - region.Trace(); - } - TRACE("Other mappings:\n"); - for (const MemoryRegion& region : m_otherMappings) - { - region.Trace(); - } - } - - free(line); // We didn't allocate line, but as per contract of getline we should free it - fclose(mapsFile); - + // Join all adjacent memory regions + CombineMemoryRegions(); return true; } // -// All the shared (native) module info to the core dump -// -bool -CrashInfo::GetDSOInfo() -{ - Phdr* phdrAddr = reinterpret_cast(m_auxvValues[AT_PHDR]); - int phnum = m_auxvValues[AT_PHNUM]; - assert(m_auxvValues[AT_PHENT] == sizeof(Phdr)); - assert(phnum != PN_XNUM); - return EnumerateElfInfo(phdrAddr, phnum); -} - -// -// Add all the necessary ELF headers to the core dump -// -void -CrashInfo::VisitModule(uint64_t baseAddress, std::string& moduleName) -{ - if (baseAddress == 0 || baseAddress == m_auxvValues[AT_SYSINFO_EHDR] || baseAddress == m_auxvValues[AT_BASE]) { - return; - } - if (m_coreclrPath.empty()) - { - size_t last = moduleName.rfind(MAKEDLLNAME_A("coreclr")); - if (last != std::string::npos) { - m_coreclrPath = moduleName.substr(0, last); - - // Now populate the elfreader with the runtime module info and - // lookup the DAC table symbol to ensure that all the memory - // necessary is in the core dump. - if (PopulateForSymbolLookup(baseAddress)) { - uint64_t symbolOffset; - TryLookupSymbol("g_dacTable", &symbolOffset); - } - } - } - EnumerateProgramHeaders(baseAddress); -} - -// -// Called for each program header adding the build id note, unwind frame -// region and module addresses to the crash info. -// -void -CrashInfo::VisitProgramHeader(uint64_t loadbias, uint64_t baseAddress, Phdr* phdr) -{ - switch (phdr->p_type) - { - case PT_DYNAMIC: - case PT_NOTE: - case PT_GNU_EH_FRAME: - if (phdr->p_vaddr != 0 && phdr->p_memsz != 0) { - InsertMemoryRegion(loadbias + phdr->p_vaddr, phdr->p_memsz); - } - break; - - case PT_LOAD: - MemoryRegion region(0, loadbias + phdr->p_vaddr, loadbias + phdr->p_vaddr + phdr->p_memsz, baseAddress); - m_moduleAddresses.insert(region); - break; - } -} - -// // Enumerate all the memory regions using the DAC memory region support given a minidump type // bool CrashInfo::EnumerateMemoryRegionsWithDAC(MINIDUMP_TYPE minidumpType) { + ReleaseHolder dataTarget = new DumpDataTarget(*this); PFN_CLRDataCreateInstance pfnCLRDataCreateInstance = nullptr; ICLRDataEnumMemoryRegions* pClrDataEnumRegions = nullptr; IXCLRDataProcess* pClrDataProcess = nullptr; @@ -504,7 +199,7 @@ CrashInfo::EnumerateMemoryRegionsWithDAC(MINIDUMP_TYPE minidumpType) } if ((minidumpType & MiniDumpWithFullMemory) == 0) { - hr = pfnCLRDataCreateInstance(__uuidof(ICLRDataEnumMemoryRegions), m_dataTarget, (void**)&pClrDataEnumRegions); + hr = pfnCLRDataCreateInstance(__uuidof(ICLRDataEnumMemoryRegions), dataTarget, (void**)&pClrDataEnumRegions); if (FAILED(hr)) { fprintf(stderr, "CLRDataCreateInstance(ICLRDataEnumMemoryRegions) FAILED %08x\n", hr); @@ -518,7 +213,7 @@ CrashInfo::EnumerateMemoryRegionsWithDAC(MINIDUMP_TYPE minidumpType) goto exit; } } - hr = pfnCLRDataCreateInstance(__uuidof(IXCLRDataProcess), m_dataTarget, (void**)&pClrDataProcess); + hr = pfnCLRDataCreateInstance(__uuidof(IXCLRDataProcess), dataTarget, (void**)&pClrDataProcess); if (FAILED(hr)) { fprintf(stderr, "CLRDataCreateInstance(IXCLRDataProcess) FAILED %08x\n", hr); @@ -589,8 +284,8 @@ CrashInfo::EnumerateManagedModules(IXCLRDataProcess* pClrDataProcess) DacpGetModuleData moduleData; if (SUCCEEDED(hr = moduleData.Request(pClrDataModule.GetPtr()))) { - TRACE("MODULE: %" PRIA PRIx64 " dyn %d inmem %d file %d pe %" PRIA PRIx64 " pdb %" PRIA PRIx64, moduleData.LoadedPEAddress, moduleData.IsDynamic, - moduleData.IsInMemory, moduleData.IsFileLayout, moduleData.PEFile, moduleData.InMemoryPdbAddress); + TRACE("MODULE: %" PRIA PRIx64 " dyn %d inmem %d file %d pe %" PRIA PRIx64 " pdb %" PRIA PRIx64, (uint64_t)moduleData.LoadedPEAddress, moduleData.IsDynamic, + moduleData.IsInMemory, moduleData.IsFileLayout, (uint64_t)moduleData.PEFile, (uint64_t)moduleData.InMemoryPdbAddress); if (!moduleData.IsDynamic && moduleData.LoadedPEAddress != 0) { @@ -599,17 +294,17 @@ CrashInfo::EnumerateManagedModules(IXCLRDataProcess* pClrDataProcess) { // If the module file name isn't empty if (wszUnicodeName[0] != 0) { - char* pszName = (char*)malloc(MAX_LONGPATH + 1); + ArrayHolder pszName = new (std::nothrow) char[MAX_LONGPATH + 1]; if (pszName == nullptr) { fprintf(stderr, "Allocating module name FAILED\n"); result = false; break; } - sprintf_s(pszName, MAX_LONGPATH, "%S", (WCHAR*)wszUnicodeName); - TRACE(" %s\n", pszName); + sprintf_s(pszName.GetPtr(), MAX_LONGPATH, "%S", (WCHAR*)wszUnicodeName); + TRACE(" %s\n", pszName.GetPtr()); // Change the module mapping name - ReplaceModuleMapping(moduleData.LoadedPEAddress, pszName); + ReplaceModuleMapping(moduleData.LoadedPEAddress, moduleData.LoadedPESize, std::string(pszName.GetPtr())); } } else { @@ -641,7 +336,7 @@ CrashInfo::UnwindAllThreads(IXCLRDataProcess* pClrDataProcess) // For each native and managed thread for (ThreadInfo* thread : m_threads) { - if (!thread->UnwindThread(*this, pClrDataProcess)) { + if (!thread->UnwindThread(pClrDataProcess)) { return false; } } @@ -652,29 +347,34 @@ CrashInfo::UnwindAllThreads(IXCLRDataProcess* pClrDataProcess) // Replace an existing module mapping with one with a different name. // void -CrashInfo::ReplaceModuleMapping(CLRDATA_ADDRESS baseAddress, const char* pszName) +CrashInfo::ReplaceModuleMapping(CLRDATA_ADDRESS baseAddress, ULONG64 size, const std::string& pszName) { - // Add or change the module mapping for this PE image. The managed assembly images are - // already in the module mappings list but in .NET 2.0 they have the name "/dev/zero". - MemoryRegion region(PF_R | PF_W | PF_X, (ULONG_PTR)baseAddress, (ULONG_PTR)(baseAddress + PAGE_SIZE), 0, pszName); - const auto& found = m_moduleMappings.find(region); + uint64_t start = (uint64_t)baseAddress; + uint64_t end = ((baseAddress + size) + (PAGE_SIZE - 1)) & PAGE_MASK; + + // Add or change the module mapping for this PE image. The managed assembly images may already + // be in the module mappings list but they may not have the full assembly name (like in .NET 2.0 + // they have the name "/dev/zero"). On MacOS, the managed assembly modules have not been added. + MemoryRegion search(0, start, start + PAGE_SIZE); + const auto& found = m_moduleMappings.find(search); if (found == m_moduleMappings.end()) { - m_moduleMappings.insert(region); + // On MacOS the assemblies are always added. + MemoryRegion newRegion(GetMemoryRegionFlags(start), start, end, 0, pszName); + m_moduleMappings.insert(newRegion); if (g_diagnostics) { TRACE("MODULE: ADD "); - region.Trace(); + newRegion.Trace(); } } - else + else if (found->FileName().compare(pszName) != 0) { // Create the new memory region with the managed assembly name. MemoryRegion newRegion(*found, pszName); // Remove and cleanup the old one m_moduleMappings.erase(found); - const_cast(*found).Cleanup(); // Add the new memory region m_moduleMappings.insert(newRegion); @@ -687,7 +387,7 @@ CrashInfo::ReplaceModuleMapping(CLRDATA_ADDRESS baseAddress, const char* pszName } // -// Returns the module base address for the IP or 0. +// Returns the module base address for the IP or 0. Used by the thread unwind code. // uint64_t CrashInfo::GetBaseAddress(uint64_t ip) { @@ -706,11 +406,12 @@ uint64_t CrashInfo::GetBaseAddress(uint64_t ip) bool CrashInfo::ReadMemory(void* address, void* buffer, size_t size) { - uint32_t read = 0; - if (FAILED(m_dataTarget->ReadVirtual(reinterpret_cast(address), reinterpret_cast(buffer), size, &read))) + size_t read = 0; + if (!ReadProcessMemory(address, buffer, size, &read)) { return false; } + assert(read == size); InsertMemoryRegion(reinterpret_cast(address), size); return true; } @@ -795,25 +496,6 @@ CrashInfo::InsertMemoryRegion(const MemoryRegion& region) } // -// Get the memory region flags for a start address -// -uint32_t -CrashInfo::GetMemoryRegionFlags(uint64_t start) -{ - MemoryRegion search(0, start, start + PAGE_SIZE); - const MemoryRegion* region = SearchMemoryRegions(m_moduleMappings, search); - if (region != nullptr) { - return region->Flags(); - } - region = SearchMemoryRegions(m_otherMappings, search); - if (region != nullptr) { - return region->Flags(); - } - TRACE("GetMemoryRegionFlags: FAILED\n"); - return PF_R | PF_W | PF_X; -} - -// // Validates a memory region // bool @@ -827,9 +509,9 @@ CrashInfo::ValidRegion(const MemoryRegion& region) for (size_t p = 0; p < numberPages; p++, start += PAGE_SIZE) { BYTE buffer[1]; - uint32_t read; + size_t read; - if (FAILED(m_dataTarget->ReadVirtual(start, buffer, 1, &read))) + if (!ReadProcessMemory((void*)start, buffer, 1, &read)) { return false; } @@ -905,57 +587,7 @@ CrashInfo::SearchMemoryRegions(const std::set& regions, const Memo return &*found; } } - return nullptr; -} - -// -// Get the process or thread status -// -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 = atoll(line + 6); - } - else if (strncmp("Tgid:\t", line, 6) == 0) - { - *tgid = atoll(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; + return nullptr; } void diff --git a/src/coreclr/src/debug/createdump/crashinfo.h b/src/coreclr/src/debug/createdump/crashinfo.h index 341e3af..23a72fa 100644 --- a/src/coreclr/src/debug/createdump/crashinfo.h +++ b/src/coreclr/src/debug/createdump/crashinfo.h @@ -2,23 +2,27 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#ifdef __APPLE__ +#include "../dbgutil/machoreader.h" +#else #include "../dbgutil/elfreader.h" +#endif // 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; -#define PRIx PRIx32 -#define PRIu PRIu32 -#define PRId PRId32 -#define PRIA "08" -#define PRIxA PRIA PRIx -#elif defined(__x86_64__) || defined(__aarch64__) +#if TARGET_64BIT typedef Elf64_auxv_t elf_aux_entry; #define PRIx PRIx64 #define PRIu PRIu64 #define PRId PRId64 #define PRIA "016" #define PRIxA PRIA PRIx +#else +typedef Elf32_auxv_t elf_aux_entry; +#define PRIx PRIx32 +#define PRIu PRIu32 +#define PRId PRId32 +#define PRIA "08" +#define PRIxA PRIA PRIx #endif typedef __typeof__(((elf_aux_entry*) 0)->a_un.a_val) elf_aux_val_t; @@ -26,18 +30,32 @@ 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 ElfReader, public ICLRDataEnumMemoryRegionsCallback +class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, +#ifdef __APPLE__ + public MachOReader +#else + public ElfReader +#endif { 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; // if true, running under sos + std::string m_name; // exe name +#ifdef __APPLE__ + vm_map_t m_task; // the mach task for the process +#else +#ifndef HAVE_PROCESS_VM_READV + int m_fd; // /proc//mem handle +#endif +#endif std::string m_coreclrPath; // the path of the coreclr module or empty if none - ICLRDataTarget* m_dataTarget; // read process memory, etc. +#ifdef __APPLE__ + std::set m_allMemoryRegions; // all memory regions on MacOS +#else std::array m_auxvValues; // auxv values +#endif std::vector m_auxvEntries; // full auxv entries std::vector m_threads; // threads found and suspended std::set m_moduleMappings; // module memory mappings @@ -46,22 +64,26 @@ private: std::set m_moduleAddresses; // memory region to module base address public: - CrashInfo(pid_t pid, ICLRDataTarget* dataTarget, bool sos); + CrashInfo(pid_t pid); virtual ~CrashInfo(); + + bool Initialize(); + void CleanupAndResumeProcess(); bool EnumerateAndSuspendThreads(); bool GatherCrashInfo(MINIDUMP_TYPE minidumpType); - void ResumeThreads(); - bool ReadMemory(void* address, void* buffer, size_t size); + bool ReadMemory(void* address, void* buffer, size_t size); // read memory and add to dump + bool ReadProcessMemory(void* address, void* buffer, size_t size, size_t* read); // read raw memory uint64_t GetBaseAddress(uint64_t ip); void InsertMemoryRegion(uint64_t address, size_t size); - static bool GetStatus(pid_t pid, pid_t* ppid, pid_t* tgid, char** name); static const MemoryRegion* SearchMemoryRegions(const std::set& regions, const MemoryRegion& search); inline pid_t Pid() const { return m_pid; } inline pid_t Ppid() const { return m_ppid; } inline pid_t Tgid() const { return m_tgid; } - inline const char* Name() const { return m_name; } - inline ICLRDataTarget* DataTarget() const { return m_dataTarget; } +#ifdef __APPLE__ + inline vm_map_t Task() const { return m_task; } +#endif + inline const std::string& Name() const { return m_name; } inline const std::vector Threads() const { return m_threads; } inline const std::set ModuleMappings() const { return m_moduleMappings; } @@ -79,15 +101,23 @@ public: virtual HRESULT STDMETHODCALLTYPE EnumMemoryRegion(/* [in] */ CLRDATA_ADDRESS address, /* [in] */ ULONG32 size); private: +#ifdef __APPLE__ + bool EnumerateMemoryRegions(); + bool TryFindDyLinker(mach_vm_address_t address, mach_vm_size_t size, bool* found); + void VisitModule(MachOModule& module); + void VisitSegment(MachOModule& module, const segment_command_64& segment); + void VisitSection(MachOModule& module, const section_64& section); +#else bool GetAuxvEntries(); bool GetDSOInfo(); void VisitModule(uint64_t baseAddress, std::string& moduleName); void VisitProgramHeader(uint64_t loadbias, uint64_t baseAddress, ElfW(Phdr)* phdr); bool EnumerateModuleMappings(); +#endif bool EnumerateMemoryRegionsWithDAC(MINIDUMP_TYPE minidumpType); bool EnumerateManagedModules(IXCLRDataProcess* pClrDataProcess); bool UnwindAllThreads(IXCLRDataProcess* pClrDataProcess); - void ReplaceModuleMapping(CLRDATA_ADDRESS baseAddress, const char* pszName); + void ReplaceModuleMapping(CLRDATA_ADDRESS baseAddress, ULONG64 size, const std::string& pszName); void InsertMemoryBackedRegion(const MemoryRegion& region); void InsertMemoryRegion(const MemoryRegion& region); uint32_t GetMemoryRegionFlags(uint64_t start); diff --git a/src/coreclr/src/debug/createdump/crashinfomac.cpp b/src/coreclr/src/debug/createdump/crashinfomac.cpp new file mode 100644 index 0000000..6387141 --- /dev/null +++ b/src/coreclr/src/debug/createdump/crashinfomac.cpp @@ -0,0 +1,390 @@ +// 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 +CrashInfo::Initialize() +{ + m_ppid = 0; + m_tgid = 0; + + kern_return_t result = ::task_for_pid(mach_task_self(), m_pid, &m_task); + if (result != KERN_SUCCESS) + { + fprintf(stderr, "task_for_pid(%d) FAILED %x %s\n", m_pid, result, mach_error_string(result)); + return false; + } + m_auxvEntries.push_back(elf_aux_entry { AT_BASE, { 0 } }); + m_auxvEntries.push_back(elf_aux_entry { AT_NULL, { 0 } }); + return true; +} + +void +CrashInfo::CleanupAndResumeProcess() +{ + // Resume all the threads suspended in EnumerateAndSuspendThreads + for (ThreadInfo* thread : m_threads) + { + ::thread_resume(thread->Port()); + } +} + +// +// Suspends all the threads and creating a list of them. Should be the before gathering any info about the process. +// +bool +CrashInfo::EnumerateAndSuspendThreads() +{ + thread_act_port_array_t threadList; + mach_msg_type_number_t threadCount; + + kern_return_t result = ::task_threads(Task(), &threadList, &threadCount); + if (result != KERN_SUCCESS) + { + fprintf(stderr, "task_threads(%d) FAILED %x %s\n", m_pid, result, mach_error_string(result)); + return false; + } + + for (int i = 0; i < threadCount; i++) + { + thread_identifier_info_data_t tident; + mach_msg_type_number_t tident_count = THREAD_IDENTIFIER_INFO_COUNT; + int tid; + + result = ::thread_info(threadList[i], THREAD_IDENTIFIER_INFO, (thread_info_t)&tident, &tident_count); + if (result != KERN_SUCCESS) + { + TRACE("%d thread_info(%x) FAILED %x %s\n", i, threadList[i], result, mach_error_string(result)); + tid = (int)threadList[i]; + } + else + { + tid = tident.thread_id; + } + + result = ::thread_suspend(threadList[i]); + if (result != KERN_SUCCESS) + { + fprintf(stderr, "thread_suspend(%d) FAILED %x %s\n", tid, result, mach_error_string(result)); + return false; + } + // Add to the list of threads + ThreadInfo* thread = new ThreadInfo(*this, tid, threadList[i]); + m_threads.push_back(thread); + } + + return true; +} + +uint32_t ConvertProtectionFlags(vm_prot_t prot) +{ + uint32_t regionFlags = 0; + if (prot & VM_PROT_READ) { + regionFlags |= PF_R; + } + if (prot & VM_PROT_WRITE) { + regionFlags |= PF_W; + } + if (prot & VM_PROT_EXECUTE) { + regionFlags |= PF_X; + } + return regionFlags; +} + +bool +CrashInfo::EnumerateMemoryRegions() +{ + vm_region_submap_info_data_64_t info; + mach_vm_address_t address = 1; + mach_vm_size_t size = 0; + uint32_t depth = 0; + + // First enumerate and add all the regions + while (address > 0 && address < MACH_VM_MAX_ADDRESS) + { + mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64; + kern_return_t result = ::mach_vm_region_recurse(Task(), &address, &size, &depth, (vm_region_recurse_info_t)&info, &count); + if (result != KERN_SUCCESS) { + fprintf(stderr, "mach_vm_region_recurse for address %016llx %08llx FAILED %x %s\n", address, size, result, mach_error_string(result)); + return false; + } + TRACE("%016llx - %016llx (%06llx) %08llx %s %d %d %d %c%c%c\n", + address, + address + size, + size / PAGE_SIZE, + info.offset, + info.is_submap ? "sub" : " ", + info.user_wired_count, + info.share_mode, + depth, + (info.protection & VM_PROT_READ) ? 'r' : '-', + (info.protection & VM_PROT_WRITE) ? 'w' : '-', + (info.protection & VM_PROT_EXECUTE) ? 'x' : '-'); + + if (info.is_submap) { + depth++; + } + else + { + if (info.share_mode != SM_EMPTY && (info.protection & (VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE)) != 0) + { + MemoryRegion memoryRegion(ConvertProtectionFlags(info.protection), address, address + size, info.offset); + m_allMemoryRegions.insert(memoryRegion); + } + address += size; + } + } + + // Now find all the modules and add them to the module list + for (const MemoryRegion& region : m_allMemoryRegions) + { + bool found; + if (!TryFindDyLinker(region.StartAddress(), region.Size(), &found)) { + return false; + } + if (found) { + break; + } + } + + // Filter out the module regions from the memory regions gathered + for (const MemoryRegion& region : m_allMemoryRegions) + { + std::set::iterator found = m_moduleMappings.find(region); + if (found == m_moduleMappings.end()) + { + m_otherMappings.insert(region); + } + else + { + // Skip any region that is fully contained in a module region + if (!found->Contains(region)) + { + TRACE("Region: "); + region.Trace(); + + // Now add all the gaps in "region" left by the module regions + uint64_t previousEndAddress = region.StartAddress(); + + for (; found != m_moduleMappings.end(); found++) + { + if (region.Contains(*found)) + { + MemoryRegion gap(region.Flags(), previousEndAddress, found->StartAddress(), region.Offset(), std::string()); + if (gap.Size() > 0) + { + TRACE(" Gap: "); + gap.Trace(); + m_otherMappings.insert(gap); + } + previousEndAddress = found->EndAddress(); + } + } + + MemoryRegion endgap(region.Flags(), previousEndAddress, region.EndAddress(), region.Offset(), std::string()); + if (endgap.Size() > 0) + { + TRACE(" EndGap:"); + endgap.Trace(); + m_otherMappings.insert(endgap); + } + } + } + } + return true; +} + +bool +CrashInfo::TryFindDyLinker(mach_vm_address_t address, mach_vm_size_t size, bool* found) +{ + bool result = true; + *found = false; + + if (size > sizeof(mach_header_64)) + { + mach_header_64* header = nullptr; + mach_msg_type_number_t read = 0; + kern_return_t kresult = ::vm_read(Task(), address, sizeof(mach_header_64), (vm_offset_t*)&header, &read); + if (kresult == KERN_SUCCESS) + { + if (header->magic == MH_MAGIC_64) + { + TRACE("TryFindDyLinker: found module header at %016llx %08llx ncmds %d sizeofcmds %08x type %02x\n", + address, + size, + header->ncmds, + header->sizeofcmds, + header->filetype); + + if (header->filetype == MH_DYLINKER) + { + TRACE("TryFindDyLinker: found dylinker\n"); + *found = true; + + // Enumerate all the modules in dyld's image cache. VisitModule is called for every module found. + result = EnumerateModules(address, header); + } + } + } + if (header != nullptr) + { + ::vm_deallocate(Task(), (vm_address_t)header, sizeof(mach_header_64)); + } + } + + return result; +} + +void CrashInfo::VisitModule(MachOModule& module) +{ + // Get the process name from the executable module file type + if (m_name.empty() && module.Header().filetype == MH_EXECUTE) + { + size_t last = module.Name().rfind(DIRECTORY_SEPARATOR_STR_A); + if (last != std::string::npos) { + last++; + } + else { + last = 0; + } + m_name = module.Name().substr(last); + } + // Save the runtime module path + if (m_coreclrPath.empty()) + { + size_t last = module.Name().rfind(MAKEDLLNAME_A("coreclr")); + if (last != std::string::npos) { + m_coreclrPath = module.Name().substr(0, last); + } + } + // VisitSegment is called for each segment of the module + module.EnumerateSegments(); +} + +void CrashInfo::VisitSegment(MachOModule& module, const segment_command_64& segment) +{ + if (segment.initprot != 0) + { + // The __LINKEDIT segment contains the raw data used by dynamic linker, such as symbol, + // string and relocation table entries. More importantly, the same __LINKEDIT segment + // can be shared by multiple modules so we need to skip them to prevent overlapping + // module regions. + if (strcmp(segment.segname, SEG_LINKEDIT) != 0) + { + uint32_t regionFlags = ConvertProtectionFlags(segment.initprot); + uint64_t offset = segment.fileoff; + uint64_t start = segment.vmaddr + module.LoadBias(); + uint64_t end = start + segment.vmsize; + + // Round to page boundary + start = start & PAGE_MASK; + _ASSERTE(start > 0); + + // Round up to page boundary + end = (end + (PAGE_SIZE - 1)) & PAGE_MASK; + _ASSERTE(end > 0); + + // Add module memory region if not already on the list + MemoryRegion moduleRegion(regionFlags, start, end, offset, module.Name()); + const auto& found = m_moduleMappings.find(moduleRegion); + if (found == m_moduleMappings.end()) + { + TRACE("VisitSegment: "); + moduleRegion.Trace(); + + // Add this module segment to the module mappings list + m_moduleMappings.insert(moduleRegion); + + // Add module segment ip to base address lookup + MemoryRegion addressRegion(0, start, end, module.BaseAddress()); + m_moduleAddresses.insert(addressRegion); + } + else + { + TRACE("VisitSegment: WARNING: "); + moduleRegion.Trace(); + TRACE(" is overlapping: "); + found->Trace(); + } + } + } +} + +void +CrashInfo::VisitSection(MachOModule& module, const section_64& section) +{ + // Add the unwind and eh frame info to the dump + if ((strcmp(section.sectname, "__unwind_info") == 0) || (strcmp(section.sectname, "__eh_frame") == 0)) + { + InsertMemoryRegion(section.addr + module.LoadBias(), section.size); + } +} + +// +// Get the memory region flags for a start address +// +uint32_t +CrashInfo::GetMemoryRegionFlags(uint64_t start) +{ + MemoryRegion search(0, start, start + PAGE_SIZE); + const MemoryRegion* region = SearchMemoryRegions(m_allMemoryRegions, search); + if (region != nullptr) { + return region->Flags(); + } + TRACE("GetMemoryRegionFlags: %016llx FAILED\n", start); + return PF_R | PF_W | PF_X; +} + +// +// Read raw memory +// +bool +CrashInfo::ReadProcessMemory(void* address, void* buffer, size_t size, size_t* read) +{ + assert(buffer != nullptr); + assert(read != nullptr); + + // vm_read_overwrite usually requires that the address be page-aligned + // and the size be a multiple of the page size. We can't differentiate + // between the cases in which that's required and those in which it + // isn't, so we do it all the time. + int* addressAligned = (int*)((SIZE_T)address & ~(PAGE_SIZE - 1)); + ssize_t offset = ((SIZE_T)address & (PAGE_SIZE - 1)); + char *data = (char*)alloca(PAGE_SIZE); + ssize_t numberOfBytesRead = 0; + ssize_t bytesToRead; + + while (size > 0) + { + vm_size_t bytesRead; + + bytesToRead = PAGE_SIZE - offset; + if (bytesToRead > size) + { + bytesToRead = size; + } + bytesRead = PAGE_SIZE; + kern_return_t result = ::vm_read_overwrite(Task(), (vm_address_t)addressAligned, PAGE_SIZE, (vm_address_t)data, &bytesRead); + if (result != KERN_SUCCESS || bytesRead != PAGE_SIZE) + { + TRACE_VERBOSE("vm_read_overwrite failed for %d bytes from %p: %x %s\n", PAGE_SIZE, (char *)addressAligned, result, mach_error_string(result)); + *read = 0; + return false; + } + memcpy((LPSTR)buffer + numberOfBytesRead, data + offset, bytesToRead); + addressAligned = (int*)((char*)addressAligned + PAGE_SIZE); + numberOfBytesRead += bytesToRead; + size -= bytesToRead; + offset = 0; + } + *read = numberOfBytesRead; + return true; +} + +// For src/inc/llvm/ELF.h +Elf64_Ehdr::Elf64_Ehdr() +{ +} + diff --git a/src/coreclr/src/debug/createdump/crashinfounix.cpp b/src/coreclr/src/debug/createdump/crashinfounix.cpp new file mode 100644 index 0000000..9cc0dc3 --- /dev/null +++ b/src/coreclr/src/debug/createdump/crashinfounix.cpp @@ -0,0 +1,402 @@ +// 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 GetStatus(pid_t pid, pid_t* ppid, pid_t* tgid, std::string* name); + +bool +CrashInfo::Initialize() +{ +#ifndef HAVE_PROCESS_VM_READV + 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; + } +#endif + // Get the process info + if (!GetStatus(m_pid, &m_ppid, &m_tgid, &m_name)) + { + return false; + } + return true; +} + +void +CrashInfo::CleanupAndResumeProcess() +{ + // Resume all the threads suspended in EnumerateAndSuspendThreads + for (ThreadInfo* thread : m_threads) + { + if (ptrace(PTRACE_DETACH, thread->Tid(), nullptr, nullptr) != -1) + { + int waitStatus; + waitpid(thread->Tid(), &waitStatus, __WALL); + } + } +#ifndef HAVE_PROCESS_VM_READV + if (m_fd != -1) + { + close(m_fd); + m_fd = -1; + } +#endif +} + +// +// Suspends all the threads and creating a list of them. Should be the before gathering any info about the process. +// +bool +CrashInfo::EnumerateAndSuspendThreads() +{ + char taskPath[128]; + snprintf(taskPath, sizeof(taskPath), "/proc/%d/task", m_pid); + + DIR* taskDir = opendir(taskPath); + if (taskDir == nullptr) + { + fprintf(stderr, "opendir(%s) FAILED %s\n", taskPath, strerror(errno)); + return false; + } + + struct dirent* entry; + while ((entry = readdir(taskDir)) != nullptr) + { + pid_t tid = static_cast(strtol(entry->d_name, nullptr, 10)); + if (tid != 0) + { + // Reference: http://stackoverflow.com/questions/18577956/how-to-use-ptrace-to-get-a-consistent-view-of-multiple-threads + if (ptrace(PTRACE_ATTACH, tid, nullptr, nullptr) != -1) + { + int waitStatus; + waitpid(tid, &waitStatus, __WALL); + } + 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(*this, tid); + m_threads.push_back(thread); + } + } + + closedir(taskDir); + return true; +} + +// +// Get the auxv entries to use and add to the core dump +// +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: %" PRIu " = %" PRIxA "\n", auxvEntry.a_type, auxvEntry.a_un.a_val); + result = true; + } + } + + close(fd); + return result; +} + +// +// Get the module mappings for the core dump NT_FILE notes +// +bool +CrashInfo::EnumerateModuleMappings() +{ + // Here we read /proc//maps file in order to parse it and figure out what it says + // about a library we are looking for. This file looks something like this: + // + // [address] [perms] [offset] [dev] [inode] [pathname] - HEADER is not preset in an actual file + // + // 35b1800000-35b1820000 r-xp 00000000 08:02 135522 /usr/lib64/ld-2.15.so + // 35b1a1f000-35b1a20000 r--p 0001f000 08:02 135522 /usr/lib64/ld-2.15.so + // 35b1a20000-35b1a21000 rw-p 00020000 08:02 135522 /usr/lib64/ld-2.15.so + // 35b1a21000-35b1a22000 rw-p 00000000 00:00 0 [heap] + // 35b1c00000-35b1dac000 r-xp 00000000 08:02 135870 /usr/lib64/libc-2.15.so + // 35b1dac000-35b1fac000 ---p 001ac000 08:02 135870 /usr/lib64/libc-2.15.so + // 35b1fac000-35b1fb0000 r--p 001ac000 08:02 135870 /usr/lib64/libc-2.15.so + // 35b1fb0000-35b1fb2000 rw-p 001b0000 08:02 135870 /usr/lib64/libc-2.15.so + char* line = nullptr; + 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 && (size_t)chars <= sizeof(mapPath)); + + FILE* mapsFile = fopen(mapPath, "r"); + if (mapsFile == nullptr) + { + 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 = sscanf(line, "%" PRIx64 "-%" PRIx64 " %m[-rwxsp] %" PRIx64 " %*[:0-9a-f] %*d %ms\n", &start, &end, &permissions, &offset, &moduleName); + if (c == 4 || c == 5) + { + // r = read + // w = write + // x = execute + // s = shared + // p = private (copy on write) + uint32_t regionFlags = 0; + if (strchr(permissions, 'r')) { + regionFlags |= PF_R; + } + if (strchr(permissions, 'w')) { + regionFlags |= PF_W; + } + if (strchr(permissions, 'x')) { + regionFlags |= PF_X; + } + if (strchr(permissions, 's')) { + regionFlags |= MEMORY_REGION_FLAG_SHARED; + } + if (strchr(permissions, 'p')) { + regionFlags |= MEMORY_REGION_FLAG_PRIVATE; + } + MemoryRegion memoryRegion(regionFlags, start, end, offset, std::string(moduleName != nullptr ? moduleName : "")); + + if (moduleName != nullptr && *moduleName == '/') + { + m_moduleMappings.insert(memoryRegion); + } + else + { + m_otherMappings.insert(memoryRegion); + } + if (linuxGateAddress != nullptr && reinterpret_cast(start) == linuxGateAddress) + { + InsertMemoryBackedRegion(memoryRegion); + } + free(moduleName); + free(permissions); + } + } + + if (g_diagnostics) + { + TRACE("Module mappings:\n"); + for (const MemoryRegion& region : m_moduleMappings) + { + region.Trace(); + } + TRACE("Other mappings:\n"); + for (const MemoryRegion& region : m_otherMappings) + { + region.Trace(); + } + } + + free(line); // We didn't allocate line, but as per contract of getline we should free it + fclose(mapsFile); + + return true; +} + +// +// All the shared (native) module info to the core dump +// +bool +CrashInfo::GetDSOInfo() +{ + Phdr* phdrAddr = reinterpret_cast(m_auxvValues[AT_PHDR]); + int phnum = m_auxvValues[AT_PHNUM]; + assert(m_auxvValues[AT_PHENT] == sizeof(Phdr)); + assert(phnum != PN_XNUM); + return EnumerateElfInfo(phdrAddr, phnum); +} + +// +// Add all the necessary ELF headers to the core dump +// +void +CrashInfo::VisitModule(uint64_t baseAddress, std::string& moduleName) +{ + if (baseAddress == 0 || baseAddress == m_auxvValues[AT_SYSINFO_EHDR] || baseAddress == m_auxvValues[AT_BASE]) { + return; + } + if (m_coreclrPath.empty()) + { + size_t last = moduleName.rfind(MAKEDLLNAME_A("coreclr")); + if (last != std::string::npos) { + m_coreclrPath = moduleName.substr(0, last); + + // Now populate the elfreader with the runtime module info and + // lookup the DAC table symbol to ensure that all the memory + // necessary is in the core dump. + if (PopulateForSymbolLookup(baseAddress)) { + uint64_t symbolOffset; + TryLookupSymbol("g_dacTable", &symbolOffset); + } + } + } + EnumerateProgramHeaders(baseAddress); +} + +// +// Called for each program header adding the build id note, unwind frame +// region and module addresses to the crash info. +// +void +CrashInfo::VisitProgramHeader(uint64_t loadbias, uint64_t baseAddress, Phdr* phdr) +{ + switch (phdr->p_type) + { + case PT_DYNAMIC: + case PT_NOTE: + case PT_GNU_EH_FRAME: + if (phdr->p_vaddr != 0 && phdr->p_memsz != 0) { + InsertMemoryRegion(loadbias + phdr->p_vaddr, phdr->p_memsz); + } + break; + + case PT_LOAD: + MemoryRegion region(0, loadbias + phdr->p_vaddr, loadbias + phdr->p_vaddr + phdr->p_memsz, baseAddress); + m_moduleAddresses.insert(region); + break; + } +} + +// +// Get the memory region flags for a start address +// +uint32_t +CrashInfo::GetMemoryRegionFlags(uint64_t start) +{ + MemoryRegion search(0, start, start + PAGE_SIZE); + const MemoryRegion* region = SearchMemoryRegions(m_moduleMappings, search); + if (region != nullptr) { + return region->Flags(); + } + region = SearchMemoryRegions(m_otherMappings, search); + if (region != nullptr) { + return region->Flags(); + } + TRACE("GetMemoryRegionFlags: FAILED\n"); + return PF_R | PF_W | PF_X; +} + +// +// Read raw memory +// +bool +CrashInfo::ReadProcessMemory(void* address, void* buffer, size_t size, size_t* read) +{ + assert(buffer != nullptr); + assert(read != nullptr); + +#ifdef HAVE_PROCESS_VM_READV + iovec local{ buffer, size }; + iovec remote{ address, size }; + *read = process_vm_readv(m_pid, &local, 1, &remote, 1, 0); +#else + assert(m_fd != -1); + *read = pread64(m_fd, buffer, size, (off64_t)address); +#endif + if (*read == (size_t)-1) + { + return false; + } + return true; +} + +// +// Get the process or thread status +// +bool +GetStatus(pid_t pid, pid_t* ppid, pid_t* tgid, std::string* 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 = atoll(line + 6); + } + else if (strncmp("Tgid:\t", line, 6) == 0) + { + *tgid = atoll(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 = line + 6; + } + } + } + + free(line); + fclose(statusFile); + return true; +} diff --git a/src/coreclr/src/debug/createdump/createdump.cpp b/src/coreclr/src/debug/createdump/createdump.cpp index 8299054..9dda607 100644 --- a/src/coreclr/src/debug/createdump/createdump.cpp +++ b/src/coreclr/src/debug/createdump/createdump.cpp @@ -12,13 +12,12 @@ bool g_diagnostics = false; bool CreateDump(const char* dumpPath, int pid, MINIDUMP_TYPE minidumpType) { - ReleaseHolder dataTarget = new DumpDataTarget(pid); - ReleaseHolder crashInfo = new CrashInfo(pid, dataTarget, false); - ReleaseHolder dumpWriter = new DumpWriter(*crashInfo); + ReleaseHolder crashInfo = new CrashInfo(pid); + DumpWriter dumpWriter(*crashInfo); bool result = false; - // The initialize the data target's ReadVirtual support (opens /proc/$pid/mem) - if (!dataTarget->Initialize(crashInfo)) + // Initialize the crash info + if (!crashInfo->Initialize()) { goto exit; } @@ -32,16 +31,16 @@ CreateDump(const char* dumpPath, int pid, MINIDUMP_TYPE minidumpType) { goto exit; } - if (!dumpWriter->OpenDump(dumpPath)) + if (!dumpWriter.OpenDump(dumpPath)) { goto exit; } - if (!dumpWriter->WriteDump()) + if (!dumpWriter.WriteDump()) { goto exit; } result = true; exit: - crashInfo->ResumeThreads(); + crashInfo->CleanupAndResumeProcess(); return result; } diff --git a/src/coreclr/src/debug/createdump/createdump.h b/src/coreclr/src/debug/createdump/createdump.h index 96afcab..176952c 100644 --- a/src/coreclr/src/debug/createdump/createdump.h +++ b/src/coreclr/src/debug/createdump/createdump.h @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#pragma once + #define ___in _SAL1_Source_(__in, (), _In_) #define ___out _SAL1_Source_(__out, (), _Out_) @@ -16,10 +18,13 @@ extern bool g_diagnostics; if (g_diagnostics) { \ printf(args); \ } +#define TRACE_VERBOSE(args...) #else #define TRACE(args, ...) +#define TRACE_VERBOSE(args, ...) #endif + #ifdef HOST_UNIX #include "config.h" #endif @@ -53,14 +58,21 @@ typedef int T_CONTEXT; #include #include #include +#ifndef __APPLE__ #include +#include +#endif #ifdef HAVE_PROCESS_VM_READV #include #endif #include #include +#ifdef __APPLE__ +#include +#else #include #include +#endif #define __STDC_FORMAT_MACROS #include #else @@ -72,6 +84,9 @@ typedef int T_CONTEXT; #include #include #ifdef HOST_UNIX +#ifdef __APPLE__ +#include "mac.h" +#endif #include "datatarget.h" #include "threadinfo.h" #include "memoryregion.h" diff --git a/src/coreclr/src/debug/createdump/datatarget.cpp b/src/coreclr/src/debug/createdump/datatarget.cpp index 13df2e7..c6784ba 100644 --- a/src/coreclr/src/debug/createdump/datatarget.cpp +++ b/src/coreclr/src/debug/createdump/datatarget.cpp @@ -6,43 +6,14 @@ #define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8) -DumpDataTarget::DumpDataTarget(pid_t pid) : +DumpDataTarget::DumpDataTarget(CrashInfo& crashInfo) : m_ref(1), - m_pid(pid), -#ifndef HAVE_PROCESS_VM_READV - m_fd(-1), -#endif - m_crashInfo(nullptr) + m_crashInfo(crashInfo) { } DumpDataTarget::~DumpDataTarget() { -#ifndef HAVE_PROCESS_VM_READV - if (m_fd != -1) - { - close(m_fd); - m_fd = -1; - } -#endif -} - -bool -DumpDataTarget::Initialize(CrashInfo * crashInfo) -{ -#ifndef HAVE_PROCESS_VM_READV - 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; - } -#endif - m_crashInfo = crashInfo; - return true; } STDMETHODIMP @@ -120,23 +91,22 @@ 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()) + for (const MemoryRegion& image : m_crashInfo.ModuleMappings()) { - const char *name = strrchr(image.FileName(), '/'); + const char *name = strrchr(image.FileName().c_str(), '/'); if (name != nullptr) { name++; } else { - name = image.FileName(); + name = image.FileName().c_str(); } if (strcmp(name, tempModuleName) == 0) { @@ -155,20 +125,13 @@ DumpDataTarget::ReadVirtual( /* [in] */ ULONG32 size, /* [optional][out] */ ULONG32 *done) { -#ifdef HAVE_PROCESS_VM_READV - iovec local{ buffer, size }; - iovec remote{ (void*)(ULONG_PTR)address, size }; - ssize_t read = process_vm_readv(m_pid, &local, 1, &remote, 1, 0); -#else - assert(m_fd != -1); - ssize_t read = pread64(m_fd, buffer, size, (off64_t)(ULONG_PTR)address); -#endif - if (read == -1) + size_t read = 0; + if (!m_crashInfo.ReadProcessMemory((void*)(ULONG_PTR)address, buffer, size, &read)) { *done = 0; return E_FAIL; } - *done = (ULONG32)read; + *done = read; return S_OK; } @@ -218,14 +181,13 @@ DumpDataTarget::GetThreadContext( /* [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()) + for (const ThreadInfo* thread : m_crashInfo.Threads()) { if (thread->Tid() == (pid_t)threadID) { diff --git a/src/coreclr/src/debug/createdump/datatarget.h b/src/coreclr/src/debug/createdump/datatarget.h index f0c4219..5753d8b 100644 --- a/src/coreclr/src/debug/createdump/datatarget.h +++ b/src/coreclr/src/debug/createdump/datatarget.h @@ -8,16 +8,11 @@ class DumpDataTarget : public ICLRDataTarget { private: LONG m_ref; // reference count - pid_t m_pid; // process id -#ifndef HAVE_PROCESS_VM_READV - int m_fd; // /proc//mem handle -#endif - CrashInfo* m_crashInfo; + CrashInfo& m_crashInfo; public: - DumpDataTarget(pid_t pid); + DumpDataTarget(CrashInfo& crashInfo); virtual ~DumpDataTarget(); - bool Initialize(CrashInfo* crashInfo); // // IUnknown diff --git a/src/coreclr/src/debug/createdump/dumpwriter.cpp b/src/coreclr/src/debug/createdump/dumpwriter.cpp index 82635f4..574c288 100644 --- a/src/coreclr/src/debug/createdump/dumpwriter.cpp +++ b/src/coreclr/src/debug/createdump/dumpwriter.cpp @@ -5,7 +5,6 @@ #include "createdump.h" DumpWriter::DumpWriter(CrashInfo& crashInfo) : - m_ref(1), m_fd(-1), m_crashInfo(crashInfo) { @@ -22,49 +21,13 @@ DumpWriter::~DumpWriter() 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)); + fprintf(stderr, "Could not open output %s: %d %s\n", dumpFileName, errno, strerror(errno)); return false; } return true; @@ -113,7 +76,14 @@ DumpWriter::WriteDump() // is used to store the actual program header count. // PT_NOTE + number of memory regions - uint64_t phnum = 1 + m_crashInfo.MemoryRegions().size(); + uint64_t phnum = 1; + for (const MemoryRegion& memoryRegion : m_crashInfo.MemoryRegions()) + { + if (memoryRegion.IsBackedByMemory()) + { + phnum++; + } + } if (phnum < PH_HDR_CANARY) { ehdr.e_phnum = phnum; @@ -171,24 +141,19 @@ DumpWriter::WriteDump() // 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(); - if (memoryRegion.IsBackedByMemory()) { + 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; - } - else - { - phdr.p_filesz = 0; - phdr.p_offset = 0; - } - if (!WriteData(&phdr, sizeof(phdr))) { - return false; + if (!WriteData(&phdr, sizeof(phdr))) { + return false; + } } } @@ -220,14 +185,17 @@ DumpWriter::WriteDump() // 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)); + if (finalNoteAlignment > sizeof(m_tempBuffer)) { + fprintf(stderr, "finalNoteAlignment %zu > sizeof(m_tempBuffer)\n", finalNoteAlignment); + return false; + } memset(m_tempBuffer, 0, finalNoteAlignment); if (!WriteData(m_tempBuffer, finalNoteAlignment)) { return false; } } - TRACE("Writing %zd memory regions to core file\n", m_crashInfo.MemoryRegions().size()); + TRACE("Writing %" PRIu64 " memory regions to core file\n", phnum - 1); // Read from target process and write memory regions to core uint64_t total = 0; @@ -243,10 +211,10 @@ DumpWriter::WriteDump() while (size > 0) { uint32_t bytesToRead = std::min(size, (uint32_t)sizeof(m_tempBuffer)); - uint32_t read = 0; + size_t read = 0; - if (FAILED(m_crashInfo.DataTarget()->ReadVirtual(address, m_tempBuffer, bytesToRead, &read))) { - fprintf(stderr, "ReadVirtual(%" PRIA PRIx64 ", %08x) FAILED\n", address, bytesToRead); + if (!m_crashInfo.ReadProcessMemory((void*)address, m_tempBuffer, bytesToRead, &read)) { + fprintf(stderr, "ReadProcessMemory(%" PRIA PRIx64 ", %08x) FAILED\n", address, bytesToRead); return false; } @@ -274,7 +242,7 @@ DumpWriter::WriteProcessInfo() 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()); + m_crashInfo.Name().copy(processInfo.pr_fname, sizeof(processInfo.pr_fname)); Nhdr nhdr; memset(&nhdr, 0, sizeof(nhdr)); @@ -342,7 +310,7 @@ DumpWriter::GetNTFileInfoSize(size_t* alignmentBytes) // File name storage needed for (const MemoryRegion& image : m_crashInfo.ModuleMappings()) { - size += strlen(image.FileName()); + size += image.FileName().length(); } // Notes must end on 4 byte alignment size_t alignmentBytesNeeded = 4 - (size % 4); @@ -399,7 +367,7 @@ DumpWriter::WriteNTFileInfo() for (const MemoryRegion& image : m_crashInfo.ModuleMappings()) { - if (!WriteData(image.FileName(), strlen(image.FileName())) || + if (!WriteData(image.FileName().c_str(), image.FileName().length()) || !WriteData("\0", 1)) { return false; } @@ -494,7 +462,7 @@ DumpWriter::WriteData(const void* buffer, size_t length) } while (written == -1 && errno == EINTR); if (written < 1) { - fprintf(stderr, "WriteData FAILED %s\n", strerror(errno)); + fprintf(stderr, "WriteData FAILED %d %s\n", errno, strerror(errno)); return false; } done += written; diff --git a/src/coreclr/src/debug/createdump/dumpwriter.h b/src/coreclr/src/debug/createdump/dumpwriter.h index 01d1c50..f3c5c0a 100644 --- a/src/coreclr/src/debug/createdump/dumpwriter.h +++ b/src/coreclr/src/debug/createdump/dumpwriter.h @@ -30,10 +30,9 @@ #define NT_FILE 0x46494c45 #endif -class DumpWriter : IUnknown +class DumpWriter { private: - LONG m_ref; // reference count int m_fd; CrashInfo& m_crashInfo; BYTE m_tempBuffer[0x4000]; @@ -44,11 +43,6 @@ public: 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(); diff --git a/src/coreclr/src/debug/createdump/mac.h b/src/coreclr/src/debug/createdump/mac.h new file mode 100644 index 0000000..c83e04a --- /dev/null +++ b/src/coreclr/src/debug/createdump/mac.h @@ -0,0 +1,192 @@ +// 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 +#include + +#define AT_SYSINFO_EHDR 33 + +#if TARGET_64BIT +#define TARGET_WORDSIZE 64 +#else +#define TARGET_WORDSIZE 32 +#endif + +#ifndef ElfW +/* We use this macro to refer to ELF types independent of the native wordsize. + `ElfW(TYPE)' is used in place of `Elf32_TYPE' or `Elf64_TYPE'. */ +#define ElfW(type) _ElfW (Elf, TARGET_WORDSIZE, type) +#define _ElfW(e,w,t) _ElfW_1 (e, w, _##t) +#define _ElfW_1(e,w,t) e##w##t +#endif + +#define ELFMAG0 0x7f /* Magic number byte 0 */ +#define ELFMAG1 'E' /* Magic number byte 1 */ +#define ELFMAG2 'L' /* Magic number byte 2 */ +#define ELFMAG3 'F' /* Magic number byte 3 */ + +enum { + NT_PRSTATUS = 1, + NT_FPREGSET, + NT_PRPSINFO, + NT_TASKSTRUCT, + NT_PLATFORM, + NT_AUXV, + NT_FILE = 0x46494c45, + NT_SIGINFO = 0x53494749, + NT_PPC_VMX = 0x100, + NT_PPC_VSX = 0x102, + NT_PRXFPREG = 0x46e62b7f, +}; + +typedef struct +{ + uint64_t a_type; /* Entry type */ + union + { + uint64_t a_val; /* Integer value */ + /* We use to have pointer elements added here. We cannot do that, + though, since it does not work when using 32-bit definitions + on 64-bit platforms and vice versa. */ + } a_un; +} Elf64_auxv_t; + +#define AT_NULL 0 /* end of vector */ +#define AT_BASE 7 /* base address of interpreter */ + +/* Note header in a PT_NOTE section */ +typedef struct elf32_note { + Elf32_Word n_namesz; /* Name size */ + Elf32_Word n_descsz; /* Content size */ + Elf32_Word n_type; /* Content type */ +} Elf32_Nhdr; + +/* Note header in a PT_NOTE section */ +typedef struct elf64_note { + Elf64_Word n_namesz; /* Name size */ + Elf64_Word n_descsz; /* Content size */ + Elf64_Word n_type; /* Content type */ +} Elf64_Nhdr; + +struct user_fpregs_struct +{ + unsigned short int cwd; + unsigned short int swd; + unsigned short int ftw; + unsigned short int fop; + unsigned long long int rip; + unsigned long long int rdp; + unsigned int mxcsr; + unsigned int mxcr_mask; + unsigned int st_space[32]; /* 8*16 bytes for each FP-reg = 128 bytes */ + unsigned int xmm_space[64]; /* 16*16 bytes for each XMM-reg = 256 bytes */ + unsigned int padding[24]; +}; + +struct user_regs_struct +{ + unsigned long long int r15; + unsigned long long int r14; + unsigned long long int r13; + unsigned long long int r12; + unsigned long long int rbp; + unsigned long long int rbx; + unsigned long long int r11; + unsigned long long int r10; + unsigned long long int r9; + unsigned long long int r8; + unsigned long long int rax; + unsigned long long int rcx; + unsigned long long int rdx; + unsigned long long int rsi; + unsigned long long int rdi; + unsigned long long int orig_rax; + unsigned long long int rip; + unsigned long long int cs; + unsigned long long int eflags; + unsigned long long int rsp; + unsigned long long int ss; + unsigned long long int fs_base; + unsigned long long int gs_base; + unsigned long long int ds; + unsigned long long int es; + unsigned long long int fs; + unsigned long long int gs; +}; + +typedef pid_t __pid_t; + +/* Type for a general-purpose register. */ +#ifdef __x86_64__ +typedef unsigned long long elf_greg_t; +#else +typedef unsigned long elf_greg_t; +#endif + +/* And the whole bunch of them. We could have used `struct + user_regs_struct' directly in the typedef, but tradition says that + the register set is an array, which does have some peculiar + semantics, so leave it that way. */ +#define ELF_NGREG (sizeof (struct user_regs_struct) / sizeof(elf_greg_t)) +typedef elf_greg_t elf_gregset_t[ELF_NGREG]; + +/* Signal info. */ +struct elf_siginfo + { + int si_signo; /* Signal number. */ + int si_code; /* Extra code. */ + int si_errno; /* Errno. */ + }; + +/* Definitions to generate Intel SVR4-like core files. These mostly + have the same names as the SVR4 types with "elf_" tacked on the + front to prevent clashes with Linux definitions, and the typedef + forms have been avoided. This is mostly like the SVR4 structure, + but more Linuxy, with things that Linux does not support and which + GDB doesn't really use excluded. */ + +struct elf_prstatus + { + struct elf_siginfo pr_info; /* Info associated with signal. */ + short int pr_cursig; /* Current signal. */ + unsigned long int pr_sigpend; /* Set of pending signals. */ + unsigned long int pr_sighold; /* Set of held signals. */ + __pid_t pr_pid; + __pid_t pr_ppid; + __pid_t pr_pgrp; + __pid_t pr_sid; + struct timeval pr_utime; /* User time. */ + struct timeval pr_stime; /* System time. */ + struct timeval pr_cutime; /* Cumulative user time. */ + struct timeval pr_cstime; /* Cumulative system time. */ + elf_gregset_t pr_reg; /* GP registers. */ + int pr_fpvalid; /* True if math copro being used. */ + }; + + +#define ELF_PRARGSZ (80) /* Number of chars for args. */ + +struct elf_prpsinfo + { + char pr_state; /* Numeric process state. */ + char pr_sname; /* Char for pr_state. */ + char pr_zomb; /* Zombie. */ + char pr_nice; /* Nice val. */ + unsigned long int pr_flag; /* Flags. */ +#if __WORDSIZE == 32 + unsigned short int pr_uid; + unsigned short int pr_gid; +#else + unsigned int pr_uid; + unsigned int pr_gid; +#endif + int pr_pid, pr_ppid, pr_pgrp, pr_sid; + /* Lots missing */ + char pr_fname[16]; /* Filename of executable. */ + char pr_psargs[ELF_PRARGSZ]; /* Initial part of arg list. */ + }; + +/* Process status and info. In the end we do provide typedefs for them. */ +typedef struct elf_prstatus prstatus_t; +typedef struct elf_prpsinfo prpsinfo_t; diff --git a/src/coreclr/src/debug/createdump/memoryregion.h b/src/coreclr/src/debug/createdump/memoryregion.h index 234af4f..2697489 100644 --- a/src/coreclr/src/debug/createdump/memoryregion.h +++ b/src/coreclr/src/debug/createdump/memoryregion.h @@ -4,9 +4,11 @@ #if defined(__arm__) || defined(__aarch64__) #define PAGE_SIZE sysconf(_SC_PAGESIZE) -#define PAGE_MASK (~(PAGE_SIZE-1)) #endif +#undef PAGE_MASK +#define PAGE_MASK (~(PAGE_SIZE-1)) + #ifdef HOST_64BIT #define PRIA "016" #else @@ -33,21 +35,20 @@ private: uint64_t m_offset; // The name used for NT_FILE output - const char* m_fileName; + std::string m_fileName; public: MemoryRegion(uint32_t flags, uint64_t start, uint64_t end) : m_flags(flags), m_startAddress(start), m_endAddress(end), - m_offset(0), - m_fileName(nullptr) + m_offset(0) { assert((start & ~PAGE_MASK) == 0); assert((end & ~PAGE_MASK) == 0); } - MemoryRegion(uint32_t flags, uint64_t start, uint64_t end, uint64_t offset, const char* filename) : + MemoryRegion(uint32_t flags, uint64_t start, uint64_t end, uint64_t offset, const std::string& filename) : m_flags(flags), m_startAddress(start), m_endAddress(end), @@ -65,13 +66,12 @@ public: m_flags(flags), m_startAddress(start), m_endAddress(end), - m_offset(baseAddress), - m_fileName(nullptr) + m_offset(baseAddress) { } // copy with new file name constructor - MemoryRegion(const MemoryRegion& region, const char* fileName) : + MemoryRegion(const MemoryRegion& region, const std::string& fileName) : m_flags(region.m_flags), m_startAddress(region.m_startAddress), m_endAddress(region.m_endAddress), @@ -85,8 +85,7 @@ public: m_flags(flags), m_startAddress(region.m_startAddress), m_endAddress(region.m_endAddress), - m_offset(region.m_offset), - m_fileName(nullptr) + m_offset(region.m_offset) { } @@ -100,6 +99,10 @@ public: { } + ~MemoryRegion() + { + } + uint32_t Permissions() const { return m_flags & MEMORY_REGION_FLAG_PERMISSIONS_MASK; } uint32_t Flags() const { return m_flags; } bool IsBackedByMemory() const { return (m_flags & MEMORY_REGION_FLAG_MEMORY_BACKED) != 0; } @@ -107,7 +110,7 @@ public: uint64_t EndAddress() const { return m_endAddress; } uint64_t Size() const { return m_endAddress - m_startAddress; } uint64_t Offset() const { return m_offset; } - const char* FileName() const { return m_fileName; } + const std::string& FileName() const { return m_fileName; } bool operator<(const MemoryRegion& rhs) const { @@ -120,19 +123,19 @@ public: return (m_startAddress <= rhs.m_startAddress) && (m_endAddress >= rhs.m_endAddress); } - // Free the file name memory - void Cleanup() - { - if (m_fileName != nullptr) - { - free((void*)m_fileName); - m_fileName = nullptr; - } - } - void Trace() const { - TRACE("%s%" PRIA PRIx64 " - %" PRIA PRIx64 " (%06" PRId64 ") %" PRIA PRIx64 " %02x %s\n", IsBackedByMemory() ? "*" : " ", m_startAddress, m_endAddress, - Size() / PAGE_SIZE, m_offset, m_flags, m_fileName != nullptr ? m_fileName : ""); + TRACE("%" PRIA PRIx64 " - %" PRIA PRIx64 " (%06" PRId64 ") %" PRIA PRIx64 " %c%c%c%c%c%c %s\n", + m_startAddress, + m_endAddress, + Size() / PAGE_SIZE, + m_offset, + (m_flags & PF_R) ? 'r' : '-', + (m_flags & PF_W) ? 'w' : '-', + (m_flags & PF_X) ? 'x' : '-', + (m_flags & MEMORY_REGION_FLAG_SHARED) ? 's' : '-', + (m_flags & MEMORY_REGION_FLAG_PRIVATE) ? 'p' : '-', + (m_flags & MEMORY_REGION_FLAG_MEMORY_BACKED) ? 'b' : '-', + m_fileName.c_str()); } }; diff --git a/src/coreclr/src/debug/createdump/threadinfo.cpp b/src/coreclr/src/debug/createdump/threadinfo.cpp index 069db0a..1ccebb6 100644 --- a/src/coreclr/src/debug/createdump/threadinfo.cpp +++ b/src/coreclr/src/debug/createdump/threadinfo.cpp @@ -3,16 +3,6 @@ // See the LICENSE file in the project root for more information. #include "createdump.h" -#include - -#if defined(__aarch64__) -// See src/pal/src/include/pal/context.h -#define MCREG_Fp(mc) ((mc).regs[29]) -#define MCREG_Lr(mc) ((mc).regs[30]) -#define MCREG_Sp(mc) ((mc).sp) -#define MCREG_Pc(mc) ((mc).pc) -#define MCREG_Cpsr(mc) ((mc).pstate) -#endif #ifndef THUMB_CODE #define THUMB_CODE 1 @@ -22,65 +12,8 @@ typedef int __ptrace_request; #endif -#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) - extern CrashInfo* g_crashInfo; -ThreadInfo::ThreadInfo(pid_t tid) : - m_tid(tid) -{ -} - -ThreadInfo::~ThreadInfo() -{ -} - -bool -ThreadInfo::Initialize(ICLRDataTarget* pDataTarget) -{ - if (!CrashInfo::GetStatus(m_tid, &m_ppid, &m_tgid, nullptr)) - { - return false; - } - if (pDataTarget != nullptr) - { - if (!GetRegistersWithDataTarget(pDataTarget)) - { - return false; - } - } - else { - if (!GetRegistersWithPTrace()) - { - return false; - } - } - -#if defined(__aarch64__) - TRACE("Thread %04x PC %016llx SP %016llx\n", m_tid, (unsigned long long)MCREG_Pc(m_gpRegisters), (unsigned long long)MCREG_Sp(m_gpRegisters)); -#elif defined(__arm__) - TRACE("Thread %04x PC %08lx SP %08lx\n", m_tid, (unsigned long)m_gpRegisters.ARM_pc, (unsigned long)m_gpRegisters.ARM_sp); -#elif defined(__x86_64__) - TRACE("Thread %04x RIP %016llx RSP %016llx\n", m_tid, (unsigned long long)m_gpRegisters.rip, (unsigned long long)m_gpRegisters.rsp); -#else -#error "Unsupported architecture" -#endif - return true; -} - -void -ThreadInfo::ResumeThread() -{ - if (ptrace(PTRACE_DETACH, m_tid, nullptr, nullptr) != -1) - { - int waitStatus; - waitpid(m_tid, &waitStatus, __WALL); - } -} - // Helper for UnwindNativeFrames static void GetFrameLocation(CONTEXT* pContext, uint64_t* ip, uint64_t* sp) @@ -108,7 +41,7 @@ ReadMemoryAdapter(PVOID address, PVOID buffer, SIZE_T size) } void -ThreadInfo::UnwindNativeFrames(CrashInfo& crashInfo, CONTEXT* pContext) +ThreadInfo::UnwindNativeFrames(CONTEXT* pContext) { uint64_t previousSp = 0; @@ -124,10 +57,10 @@ ThreadInfo::UnwindNativeFrames(CrashInfo& crashInfo, CONTEXT* pContext) } // Add two pages around the instruction pointer to the core dump - crashInfo.InsertMemoryRegion(ip - PAGE_SIZE, PAGE_SIZE * 2); + m_crashInfo.InsertMemoryRegion(ip - PAGE_SIZE, PAGE_SIZE * 2); // Look up the ip address to get the module base address - uint64_t baseAddress = crashInfo.GetBaseAddress(ip); + uint64_t baseAddress = m_crashInfo.GetBaseAddress(ip); if (baseAddress == 0) { TRACE("Unwind: module base not found ip %" PRIA PRIx64 "\n", ip); break; @@ -144,7 +77,7 @@ ThreadInfo::UnwindNativeFrames(CrashInfo& crashInfo, CONTEXT* pContext) } bool -ThreadInfo::UnwindThread(CrashInfo& crashInfo, IXCLRDataProcess* pClrDataProcess) +ThreadInfo::UnwindThread(IXCLRDataProcess* pClrDataProcess) { TRACE("Unwind: thread %04x\n", Tid()); @@ -153,7 +86,7 @@ ThreadInfo::UnwindThread(CrashInfo& crashInfo, IXCLRDataProcess* pClrDataProcess GetThreadContext(CONTEXT_ALL, &context); // Unwind the native frames at the top of the stack - UnwindNativeFrames(crashInfo, &context); + UnwindNativeFrames(&context); if (pClrDataProcess != nullptr) { @@ -184,7 +117,7 @@ ThreadInfo::UnwindThread(CrashInfo& crashInfo, IXCLRDataProcess* pClrDataProcess } // Unwind all the native frames after the managed frame - UnwindNativeFrames(crashInfo, &context); + UnwindNativeFrames(&context); } while (pStackwalk->Next() == S_OK); } @@ -193,157 +126,8 @@ ThreadInfo::UnwindThread(CrashInfo& crashInfo, IXCLRDataProcess* pClrDataProcess return true; } -bool -ThreadInfo::GetRegistersWithPTrace() -{ - struct iovec gpRegsVec = { &m_gpRegisters, sizeof(m_gpRegisters) }; - if (ptrace((__ptrace_request)PTRACE_GETREGSET, m_tid, NT_PRSTATUS, &gpRegsVec) == -1) - { - fprintf(stderr, "ptrace(PTRACE_GETREGSET, %d, NT_PRSTATUS) FAILED %d (%s)\n", m_tid, errno, strerror(errno)); - return false; - } - assert(sizeof(m_gpRegisters) == gpRegsVec.iov_len); - - struct iovec fpRegsVec = { &m_fpRegisters, sizeof(m_fpRegisters) }; - if (ptrace((__ptrace_request)PTRACE_GETREGSET, m_tid, NT_FPREGSET, &fpRegsVec) == -1) - { -#if defined(__arm__) - // Some aarch64 kernels may not support NT_FPREGSET for arm processes. We treat this failure as non-fatal. -#else - fprintf(stderr, "ptrace(PTRACE_GETREGSET, %d, NT_FPREGSET) FAILED %d (%s)\n", m_tid, errno, strerror(errno)); - return false; -#endif - } - assert(sizeof(m_fpRegisters) == fpRegsVec.iov_len); - -#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; - } -#elif defined(__arm__) && defined(__VFP_FP__) && !defined(__SOFTFP__) - -#if defined(ARM_VFPREGS_SIZE) - assert(sizeof(m_vfpRegisters) == ARM_VFPREGS_SIZE); -#endif - - if (ptrace((__ptrace_request)PTRACE_GETVFPREGS, m_tid, nullptr, &m_vfpRegisters) == -1) - { - fprintf(stderr, "ptrace(PTRACE_GETVFPREGS, %d) FAILED %d (%s)\n", m_tid, errno, strerror(errno)); - return false; - } -#endif - return true; -} - -bool -ThreadInfo::GetRegistersWithDataTarget(ICLRDataTarget* pDataTarget) -{ - CONTEXT context; - context.ContextFlags = CONTEXT_ALL; - if (pDataTarget->GetThreadContext(m_tid, context.ContextFlags, sizeof(context), reinterpret_cast(&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)); -#elif defined(__aarch64__) - // See MCREG maps in PAL's context.h - assert(sizeof(m_gpRegisters.regs) == (sizeof(context.X) + sizeof(context.Fp) + sizeof(context.Lr))); - memcpy(m_gpRegisters.regs, context.X, sizeof(context.X)); - MCREG_Fp(m_gpRegisters) = context.Fp; - MCREG_Lr(m_gpRegisters) = context.Lr; - MCREG_Sp(m_gpRegisters) = context.Sp; - MCREG_Pc(m_gpRegisters) = context.Pc; - MCREG_Cpsr(m_gpRegisters) = context.Cpsr; - - assert(sizeof(m_fpRegisters.vregs) == sizeof(context.V)); - memcpy(m_fpRegisters.vregs, context.V, sizeof(context.V)); - m_fpRegisters.fpcr = context.Fpcr; - m_fpRegisters.fpsr = context.Fpsr; -#elif defined(__arm__) - m_gpRegisters.ARM_sp = context.Sp; - m_gpRegisters.ARM_lr = context.Lr; - m_gpRegisters.ARM_pc = context.Pc; - m_gpRegisters.ARM_cpsr = context.Cpsr; - - m_gpRegisters.ARM_r0 = context.R0; - m_gpRegisters.ARM_ORIG_r0 = context.R0; - m_gpRegisters.ARM_r1 = context.R1; - m_gpRegisters.ARM_r2 = context.R2; - m_gpRegisters.ARM_r3 = context.R3; - m_gpRegisters.ARM_r4 = context.R4; - m_gpRegisters.ARM_r5 = context.R5; - m_gpRegisters.ARM_r6 = context.R6; - m_gpRegisters.ARM_r7 = context.R7; - m_gpRegisters.ARM_r8 = context.R8; - m_gpRegisters.ARM_r9 = context.R9; - m_gpRegisters.ARM_r10 = context.R10; - m_gpRegisters.ARM_fp = context.R11; - m_gpRegisters.ARM_ip = context.R12; - -#if defined(__VFP_FP__) && !defined(__SOFTFP__) - m_vfpRegisters.fpscr = context.Fpscr; - - assert(sizeof(context.D) == sizeof(m_vfpRegisters.fpregs)); - memcpy(m_vfpRegisters.fpregs, context.D, sizeof(context.D)); -#endif -#else -#error Platform not supported -#endif - return true; -} - void -ThreadInfo::GetThreadStack(CrashInfo& crashInfo) +ThreadInfo::GetThreadStack() { uint64_t startAddress; size_t size; @@ -358,7 +142,7 @@ ThreadInfo::GetThreadStack(CrashInfo& crashInfo) size = 4 * PAGE_SIZE; MemoryRegion search(0, startAddress, startAddress + PAGE_SIZE); - const MemoryRegion* region = CrashInfo::SearchMemoryRegions(crashInfo.OtherMappings(), search); + const MemoryRegion* region = CrashInfo::SearchMemoryRegions(m_crashInfo.OtherMappings(), search); if (region != nullptr) { // Use the mapping found for the size of the thread's stack @@ -370,7 +154,7 @@ ThreadInfo::GetThreadStack(CrashInfo& crashInfo) region->Trace(); } } - crashInfo.InsertMemoryRegion(startAddress, size); + m_crashInfo.InsertMemoryRegion(startAddress, size); } void diff --git a/src/coreclr/src/debug/createdump/threadinfo.h b/src/coreclr/src/debug/createdump/threadinfo.h index 7f8a344..124a5c9 100644 --- a/src/coreclr/src/debug/createdump/threadinfo.h +++ b/src/coreclr/src/debug/createdump/threadinfo.h @@ -4,6 +4,19 @@ class CrashInfo; +#if defined(__aarch64__) +// See src/pal/src/include/pal/context.h +#define MCREG_Fp(mc) ((mc).regs[29]) +#define MCREG_Lr(mc) ((mc).regs[30]) +#define MCREG_Sp(mc) ((mc).sp) +#define MCREG_Pc(mc) ((mc).pc) +#define MCREG_Cpsr(mc) ((mc).pstate) +#endif + +#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) #if defined(__arm__) #define user_regs_struct user_regs #define user_fpregs_struct user_fpregs @@ -24,9 +37,13 @@ struct user_vfpregs_struct class ThreadInfo { private: + CrashInfo& m_crashInfo; // crashinfo instance pid_t m_tid; // thread id pid_t m_ppid; // parent process pid_t m_tgid; // thread group +#ifdef __APPLE__ + mach_port_t m_port; // MacOS thread port +#endif struct user_regs_struct m_gpRegisters; // general purpose registers struct user_fpregs_struct m_fpRegisters; // floating point registers #if defined(__i386__) @@ -36,12 +53,16 @@ private: #endif public: - ThreadInfo(pid_t tid); +#ifdef __APPLE__ + ThreadInfo(CrashInfo& crashInfo, pid_t tid, mach_port_t port); + inline mach_port_t Port() const { return m_port; } +#else + ThreadInfo(CrashInfo& crashInfo, pid_t tid); +#endif ~ThreadInfo(); - bool Initialize(ICLRDataTarget* pDataTarget); - void ResumeThread(); - bool UnwindThread(CrashInfo& crashInfo, IXCLRDataProcess* pClrDataProcess); - void GetThreadStack(CrashInfo& crashInfo); + bool Initialize(); + bool UnwindThread(IXCLRDataProcess* pClrDataProcess); + void GetThreadStack(); void GetThreadContext(uint32_t flags, CONTEXT* context) const; inline pid_t Tid() const { return m_tid; } @@ -57,7 +78,8 @@ public: #endif private: - void UnwindNativeFrames(CrashInfo& crashInfo, CONTEXT* pContext); + void UnwindNativeFrames(CONTEXT* pContext); +#ifndef __APPLE__ bool GetRegistersWithPTrace(); - bool GetRegistersWithDataTarget(ICLRDataTarget* dataTarget); +#endif }; diff --git a/src/coreclr/src/debug/createdump/threadinfomac.cpp b/src/coreclr/src/debug/createdump/threadinfomac.cpp new file mode 100644 index 0000000..dc6bfef --- /dev/null +++ b/src/coreclr/src/debug/createdump/threadinfomac.cpp @@ -0,0 +1,89 @@ +// 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(CrashInfo& crashInfo, pid_t tid, mach_port_t port) : + m_crashInfo(crashInfo), + m_tid(tid), + m_port(port) +{ +} + +ThreadInfo::~ThreadInfo() +{ +} + +bool +ThreadInfo::Initialize() +{ + m_ppid = 0; + m_tgid = 0; + + x86_thread_state64_t state; + mach_msg_type_number_t stateCount = x86_THREAD_STATE64_COUNT; + kern_return_t result = ::thread_get_state(Port(), x86_THREAD_STATE64, (thread_state_t)&state, &stateCount); + if (result != KERN_SUCCESS) + { + fprintf(stderr, "thread_get_state(%x) FAILED %x %s\n", m_tid, result, mach_error_string(result)); + return false; + } + + m_gpRegisters.rbp = state.__rbp; + m_gpRegisters.rip = state.__rip; + m_gpRegisters.cs = state.__cs; + m_gpRegisters.eflags = state.__rflags; + m_gpRegisters.ss = 0; + m_gpRegisters.rsp = state.__rsp; + m_gpRegisters.rdi = state.__rdi; + + m_gpRegisters.rsi = state.__rsi; + m_gpRegisters.rbx = state.__rbx; + m_gpRegisters.rdx = state.__rdx; + m_gpRegisters.rcx = state.__rcx; + m_gpRegisters.rax = state.__rax; + m_gpRegisters.orig_rax = state.__rax; + m_gpRegisters.r8 = state.__r8; + m_gpRegisters.r9 = state.__r9; + m_gpRegisters.r10 = state.__r10; + m_gpRegisters.r11 = state.__r11; + m_gpRegisters.r12 = state.__r12; + m_gpRegisters.r13 = state.__r13; + m_gpRegisters.r14 = state.__r14; + m_gpRegisters.r15 = state.__r15; + + m_gpRegisters.fs = state.__fs; + m_gpRegisters.gs = state.__gs; + m_gpRegisters.ds = 0; + m_gpRegisters.es = 0; + m_gpRegisters.gs_base = 0; + m_gpRegisters.fs_base = 0; + + x86_float_state64_t fpstate; + stateCount = x86_FLOAT_STATE64_COUNT; + result = ::thread_get_state(Port(), x86_FLOAT_STATE64, (thread_state_t)&fpstate, &stateCount); + if (result != KERN_SUCCESS) + { + fprintf(stderr, "thread_get_state(%x) FAILED %x %s\n", m_tid, result, mach_error_string(result)); + return false; + } + + m_fpRegisters.cwd = *((unsigned short *)&fpstate.__fpu_fcw); + m_fpRegisters.swd = *((unsigned short *)&fpstate.__fpu_fsw); + m_fpRegisters.ftw = fpstate.__fpu_ftw; + m_fpRegisters.fop = fpstate.__fpu_fop; + + FPREG_ErrorOffset(m_fpRegisters) = fpstate.__fpu_ip; + FPREG_ErrorSelector(m_fpRegisters) = fpstate.__fpu_cs; + FPREG_DataOffset(m_fpRegisters) = fpstate.__fpu_dp; + FPREG_DataSelector(m_fpRegisters) = fpstate.__fpu_ds; + + m_fpRegisters.mxcsr = fpstate.__fpu_mxcsr; + m_fpRegisters.mxcr_mask = fpstate.__fpu_mxcsrmask; + + memcpy(m_fpRegisters.st_space, &fpstate.__fpu_stmm0, sizeof(m_fpRegisters.st_space)); + memcpy(m_fpRegisters.xmm_space, &fpstate.__fpu_xmm0, sizeof(m_fpRegisters.xmm_space)); + + return true; +} diff --git a/src/coreclr/src/debug/createdump/threadinfounix.cpp b/src/coreclr/src/debug/createdump/threadinfounix.cpp new file mode 100644 index 0000000..f45ae51 --- /dev/null +++ b/src/coreclr/src/debug/createdump/threadinfounix.cpp @@ -0,0 +1,101 @@ +// 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" + +#if defined(__aarch64__) +// See src/pal/src/include/pal/context.h +#define MCREG_Fp(mc) ((mc).regs[29]) +#define MCREG_Lr(mc) ((mc).regs[30]) +#define MCREG_Sp(mc) ((mc).sp) +#define MCREG_Pc(mc) ((mc).pc) +#define MCREG_Cpsr(mc) ((mc).pstate) +#endif + +#ifndef THUMB_CODE +#define THUMB_CODE 1 +#endif + +#ifndef __GLIBC__ +typedef int __ptrace_request; +#endif + +bool GetStatus(pid_t pid, pid_t* ppid, pid_t* tgid, std::string* name); + +ThreadInfo::ThreadInfo(CrashInfo& crashInfo, pid_t tid) : + m_crashInfo(crashInfo), + m_tid(tid) +{ +} + +ThreadInfo::~ThreadInfo() +{ +} + +bool +ThreadInfo::Initialize() +{ + if (!GetStatus(m_tid, &m_ppid, &m_tgid, nullptr)) + { + return false; + } + if (!GetRegistersWithPTrace()) + { + return false; + } +#if defined(__aarch64__) + TRACE("Thread %04x PC %016llx SP %016llx\n", m_tid, (unsigned long long)MCREG_Pc(m_gpRegisters), (unsigned long long)MCREG_Sp(m_gpRegisters)); +#elif defined(__arm__) + TRACE("Thread %04x PC %08lx SP %08lx\n", m_tid, (unsigned long)m_gpRegisters.ARM_pc, (unsigned long)m_gpRegisters.ARM_sp); +#elif defined(__x86_64__) + TRACE("Thread %04x RIP %016llx RSP %016llx\n", m_tid, (unsigned long long)m_gpRegisters.rip, (unsigned long long)m_gpRegisters.rsp); +#else +#error "Unsupported architecture" +#endif + return true; +} + +bool +ThreadInfo::GetRegistersWithPTrace() +{ + struct iovec gpRegsVec = { &m_gpRegisters, sizeof(m_gpRegisters) }; + if (ptrace((__ptrace_request)PTRACE_GETREGSET, m_tid, NT_PRSTATUS, &gpRegsVec) == -1) + { + fprintf(stderr, "ptrace(PTRACE_GETREGSET, %d, NT_PRSTATUS) FAILED %d (%s)\n", m_tid, errno, strerror(errno)); + return false; + } + assert(sizeof(m_gpRegisters) == gpRegsVec.iov_len); + + struct iovec fpRegsVec = { &m_fpRegisters, sizeof(m_fpRegisters) }; + if (ptrace((__ptrace_request)PTRACE_GETREGSET, m_tid, NT_FPREGSET, &fpRegsVec) == -1) + { +#if defined(__arm__) + // Some aarch64 kernels may not support NT_FPREGSET for arm processes. We treat this failure as non-fatal. +#else + fprintf(stderr, "ptrace(PTRACE_GETREGSET, %d, NT_FPREGSET) FAILED %d (%s)\n", m_tid, errno, strerror(errno)); + return false; +#endif + } + assert(sizeof(m_fpRegisters) == fpRegsVec.iov_len); + +#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; + } +#elif defined(__arm__) && defined(__VFP_FP__) && !defined(__SOFTFP__) + +#if defined(ARM_VFPREGS_SIZE) + assert(sizeof(m_vfpRegisters) == ARM_VFPREGS_SIZE); +#endif + + if (ptrace((__ptrace_request)PTRACE_GETVFPREGS, m_tid, nullptr, &m_vfpRegisters) == -1) + { + fprintf(stderr, "ptrace(PTRACE_GETVFPREGS, %d) FAILED %d (%s)\n", m_tid, errno, strerror(errno)); + return false; + } +#endif + return true; +} diff --git a/src/coreclr/src/debug/dbgutil/CMakeLists.txt b/src/coreclr/src/debug/dbgutil/CMakeLists.txt index bd96e9b..bee929f 100644 --- a/src/coreclr/src/debug/dbgutil/CMakeLists.txt +++ b/src/coreclr/src/debug/dbgutil/CMakeLists.txt @@ -1,11 +1,14 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) if(CLR_CMAKE_HOST_WIN32) - include_directories(${CLR_DIR}/src/inc/llvm) #use static crt add_definitions(-MT) endif(CLR_CMAKE_HOST_WIN32) +if(CLR_CMAKE_HOST_WIN32 OR CLR_CMAKE_HOST_OSX) + include_directories(${CLR_DIR}/src/inc/llvm) +endif(CLR_CMAKE_HOST_WIN32 OR CLR_CMAKE_HOST_OSX) + add_definitions(-DPAL_STDCPP_COMPAT) if(CLR_CMAKE_TARGET_ALPINE_LINUX) @@ -22,4 +25,10 @@ if(CLR_CMAKE_TARGET_LINUX) ) endif(CLR_CMAKE_TARGET_LINUX) +if(CLR_CMAKE_HOST_OSX) + list(APPEND DBGUTIL_SOURCES + machoreader.cpp + ) +endif(CLR_CMAKE_HOST_OSX) + add_library_clr(dbgutil STATIC ${DBGUTIL_SOURCES}) diff --git a/src/coreclr/src/debug/dbgutil/elfreader.cpp b/src/coreclr/src/debug/dbgutil/elfreader.cpp index 0ebd7f6..cd3b148 100644 --- a/src/coreclr/src/debug/dbgutil/elfreader.cpp +++ b/src/coreclr/src/debug/dbgutil/elfreader.cpp @@ -31,17 +31,11 @@ #define PRIxA PRIA PRIx #endif -#ifdef HOST_UNIX -#define TRACE(args...) Trace(args) -#else -#define TRACE(args, ...) -#endif - #ifndef HOST_WINDOWS static const char ElfMagic[] = { 0x7f, 'E', 'L', 'F', '\0' }; #endif -class ElfReaderExport: public ElfReader +class ElfReaderExport : public ElfReader { private: ICorDebugDataTarget* m_dataTarget; @@ -115,7 +109,7 @@ ElfReader::~ElfReader() bool ElfReader::PopulateForSymbolLookup(uint64_t baseAddress) { - TRACE("PopulateForSymbolLookup: base %" PRIA PRIx64 "\n", baseAddress); + Trace("PopulateForSymbolLookup: base %" PRIA PRIx64 "\n", baseAddress); Elf_Dyn* dynamicAddr = nullptr; uint64_t loadbias = 0; @@ -145,10 +139,10 @@ ElfReader::PopulateForSymbolLookup(uint64_t baseAddress) { Elf_Dyn dyn; if (!ReadMemory(dynamicAddr, &dyn, sizeof(dyn))) { - TRACE("ERROR: ReadMemory(%p, %" PRIx ") dyn FAILED\n", dynamicAddr, sizeof(dyn)); + Trace("ERROR: ReadMemory(%p, %" PRIx ") dyn FAILED\n", dynamicAddr, sizeof(dyn)); return false; } - TRACE("DSO: dyn %p tag %" PRId " (%" PRIx ") d_ptr %" PRIxA "\n", dynamicAddr, dyn.d_tag, dyn.d_tag, dyn.d_un.d_ptr); + Trace("DSO: dyn %p tag %" PRId " (%" PRIx ") d_ptr %" PRIxA "\n", dynamicAddr, dyn.d_tag, dyn.d_tag, dyn.d_un.d_ptr); if (dyn.d_tag == DT_NULL) { break; } @@ -168,7 +162,7 @@ ElfReader::PopulateForSymbolLookup(uint64_t baseAddress) } if (m_gnuHashTableAddr == nullptr || m_stringTableAddr == nullptr || m_symbolTableAddr == nullptr) { - TRACE("ERROR: hash, string or symbol table address not found\n"); + Trace("ERROR: hash, string or symbol table address not found\n"); return false; } @@ -200,14 +194,14 @@ ElfReader::TryLookupSymbol(std::string symbolName, uint64_t* symbolOffset) if (symbolName.compare(possibleName) == 0) { *symbolOffset = symbol.st_value; - TRACE("TryLookupSymbol found '%s' at offset %" PRIxA "\n", symbolName.c_str(), *symbolOffset); + Trace("TryLookupSymbol found '%s' at offset %" PRIxA "\n", symbolName.c_str(), *symbolOffset); return true; } } } } } - TRACE("TryLookupSymbol '%s' not found\n", symbolName.c_str()); + Trace("TryLookupSymbol '%s' not found\n", symbolName.c_str()); *symbolOffset = 0; return false; } @@ -230,11 +224,11 @@ bool ElfReader::InitializeGnuHashTable() { if (!ReadMemory(m_gnuHashTableAddr, &m_hashTable, sizeof(m_hashTable))) { - TRACE("ERROR: InitializeGnuHashTable hashtable ReadMemory(%p) FAILED\n", m_gnuHashTableAddr); + Trace("ERROR: InitializeGnuHashTable hashtable ReadMemory(%p) FAILED\n", m_gnuHashTableAddr); return false; } if (m_hashTable.BucketCount <= 0 || m_hashTable.SymbolOffset == 0) { - TRACE("ERROR: InitializeGnuHashTable invalid BucketCount or SymbolOffset\n"); + Trace("ERROR: InitializeGnuHashTable invalid BucketCount or SymbolOffset\n"); return false; } m_buckets = (int32_t*)malloc(m_hashTable.BucketCount * sizeof(int32_t)); @@ -243,7 +237,7 @@ ElfReader::InitializeGnuHashTable() } void* bucketsAddress = (char*)m_gnuHashTableAddr + sizeof(GnuHashTable) + (m_hashTable.BloomSize * sizeof(size_t)); if (!ReadMemory(bucketsAddress, m_buckets, m_hashTable.BucketCount * sizeof(int32_t))) { - TRACE("ERROR: InitializeGnuHashTable buckets ReadMemory(%p) FAILED\n", bucketsAddress); + Trace("ERROR: InitializeGnuHashTable buckets ReadMemory(%p) FAILED\n", bucketsAddress); return false; } m_chainsAddress = (char*)bucketsAddress + (m_hashTable.BucketCount * sizeof(int32_t)); @@ -255,12 +249,12 @@ ElfReader::GetPossibleSymbolIndex(const std::string& symbolName, std::vector m_stringTableSize) { - TRACE("ERROR: GetStringAtIndex index %d > string table size\n", index); + Trace("ERROR: GetStringAtIndex index %d > string table size\n", index); return false; } char ch; void* address = (char*)m_stringTableAddr + index; if (!ReadMemory(address, &ch, sizeof(ch))) { - TRACE("ERROR: GetStringAtIndex ReadMemory(%p) FAILED\n", address); + Trace("ERROR: GetStringAtIndex ReadMemory(%p) FAILED\n", address); return false; } if (ch == '\0') { @@ -329,7 +323,7 @@ ElfReader::GetStringAtIndex(int index, std::string& result) bool ElfReader::EnumerateElfInfo(Elf_Phdr* phdrAddr, int phnum) { - TRACE("EnumerateElfInfo: phdr %p phnum %d\n", phdrAddr, phnum); + Trace("EnumerateElfInfo: phdr %p phnum %d\n", phdrAddr, phnum); if (phdrAddr == nullptr || phnum <= 0) { return false; @@ -360,10 +354,10 @@ ElfReader::EnumerateLinkMapEntries(Elf_Dyn* dynamicAddr) { Elf_Dyn dyn; if (!ReadMemory(dynamicAddr, &dyn, sizeof(dyn))) { - TRACE("ERROR: ReadMemory(%p, %" PRIx ") dyn FAILED\n", dynamicAddr, sizeof(dyn)); + Trace("ERROR: ReadMemory(%p, %" PRIx ") dyn FAILED\n", dynamicAddr, sizeof(dyn)); return false; } - TRACE("DSO: dyn %p tag %" PRId " (%" PRIx ") d_ptr %" PRIxA "\n", dynamicAddr, dyn.d_tag, dyn.d_tag, dyn.d_un.d_ptr); + Trace("DSO: dyn %p tag %" PRId " (%" PRIx ") d_ptr %" PRIxA "\n", dynamicAddr, dyn.d_tag, dyn.d_tag, dyn.d_un.d_ptr); if (dyn.d_tag == DT_NULL) { break; } @@ -373,14 +367,14 @@ ElfReader::EnumerateLinkMapEntries(Elf_Dyn* dynamicAddr) dynamicAddr++; } - TRACE("DSO: rdebugAddr %p\n", rdebugAddr); + Trace("DSO: rdebugAddr %p\n", rdebugAddr); if (rdebugAddr == nullptr) { return false; } struct r_debug debugEntry; if (!ReadMemory(rdebugAddr, &debugEntry, sizeof(debugEntry))) { - TRACE("ERROR: ReadMemory(%p, %" PRIx ") r_debug FAILED\n", rdebugAddr, sizeof(debugEntry)); + Trace("ERROR: ReadMemory(%p, %" PRIx ") r_debug FAILED\n", rdebugAddr, sizeof(debugEntry)); return false; } @@ -389,7 +383,7 @@ ElfReader::EnumerateLinkMapEntries(Elf_Dyn* dynamicAddr) { struct link_map map; if (!ReadMemory(linkMapAddr, &map, sizeof(map))) { - TRACE("ERROR: ReadMemory(%p, %" PRIx ") link_map FAILED\n", linkMapAddr, sizeof(map)); + Trace("ERROR: ReadMemory(%p, %" PRIx ") link_map FAILED\n", linkMapAddr, sizeof(map)); return false; } // Read the module's name and make sure the memory is added to the core dump @@ -401,7 +395,7 @@ ElfReader::EnumerateLinkMapEntries(Elf_Dyn* dynamicAddr) { char ch; if (!ReadMemory(map.l_name + i, &ch, sizeof(ch))) { - TRACE("DSO: ReadMemory link_map name %p + %d FAILED\n", map.l_name, i); + Trace("DSO: ReadMemory link_map name %p + %d FAILED\n", map.l_name, i); break; } if (ch == '\0') { @@ -410,7 +404,7 @@ ElfReader::EnumerateLinkMapEntries(Elf_Dyn* dynamicAddr) moduleName.append(1, ch); } } - TRACE("\nDSO: link_map entry %p l_ld %p l_addr (Ehdr) %" PRIx " %s\n", linkMapAddr, map.l_ld, map.l_addr, moduleName.c_str()); + Trace("\nDSO: link_map entry %p l_ld %p l_addr (Ehdr) %" PRIx " %s\n", linkMapAddr, map.l_ld, map.l_addr, moduleName.c_str()); // Call the derived class for each module VisitModule(map.l_addr, moduleName); @@ -428,11 +422,11 @@ ElfReader::EnumerateProgramHeaders(uint64_t baseAddress, uint64_t* ploadbias, El { Elf_Ehdr ehdr; if (!ReadMemory((void*)baseAddress, &ehdr, sizeof(ehdr))) { - TRACE("ERROR: EnumerateProgramHeaders ReadMemory(%p, %" PRIx ") ehdr FAILED\n", (void*)baseAddress, sizeof(ehdr)); + Trace("ERROR: EnumerateProgramHeaders ReadMemory(%p, %" PRIx ") ehdr FAILED\n", (void*)baseAddress, sizeof(ehdr)); return false; } if (memcmp(ehdr.e_ident, ElfMagic, strlen(ElfMagic)) != 0) { - TRACE("ERROR: EnumerateProgramHeaders Invalid elf header signature\n"); + Trace("ERROR: EnumerateProgramHeaders Invalid elf header signature\n"); return false; } _ASSERTE(ehdr.e_phentsize == sizeof(Elf_Phdr)); @@ -449,7 +443,7 @@ ElfReader::EnumerateProgramHeaders(uint64_t baseAddress, uint64_t* ploadbias, El if (ehdr.e_phoff == 0 || phnum <= 0) { return false; } - TRACE("ELF: type %d mach 0x%x ver %d flags 0x%x phnum %d phoff %" PRIxA " phentsize 0x%02x shnum %d shoff %" PRIxA " shentsize 0x%02x shstrndx %d\n", + Trace("ELF: type %d mach 0x%x ver %d flags 0x%x phnum %d phoff %" PRIxA " phentsize 0x%02x shnum %d shoff %" PRIxA " shentsize 0x%02x shstrndx %d\n", ehdr.e_type, ehdr.e_machine, ehdr.e_version, ehdr.e_flags, phnum, ehdr.e_phoff, ehdr.e_phentsize, ehdr.e_shnum, ehdr.e_shoff, ehdr.e_shentsize, ehdr.e_shstrndx); Elf_Phdr* phdrAddr = reinterpret_cast(baseAddress + ehdr.e_phoff); @@ -469,12 +463,12 @@ ElfReader::EnumerateProgramHeaders(Elf_Phdr* phdrAddr, int phnum, uint64_t baseA { Elf_Phdr ph; if (!ReadMemory(phdrAddr + i, &ph, sizeof(ph))) { - TRACE("ERROR: ReadMemory(%p, %" PRIx ") phdr FAILED\n", phdrAddr + i, sizeof(ph)); + Trace("ERROR: ReadMemory(%p, %" PRIx ") phdr FAILED\n", phdrAddr + i, sizeof(ph)); return false; } if (ph.p_type == PT_LOAD && ph.p_offset == 0) { loadbias -= ph.p_vaddr; - TRACE("PHDR: loadbias %" PRIA PRIx64 "\n", loadbias); + Trace("PHDR: loadbias %" PRIA PRIx64 "\n", loadbias); break; } } @@ -488,10 +482,10 @@ ElfReader::EnumerateProgramHeaders(Elf_Phdr* phdrAddr, int phnum, uint64_t baseA { Elf_Phdr ph; if (!ReadMemory(phdrAddr + i, &ph, sizeof(ph))) { - TRACE("ERROR: ReadMemory(%p, %" PRIx ") phdr FAILED\n", phdrAddr + i, sizeof(ph)); + Trace("ERROR: ReadMemory(%p, %" PRIx ") phdr FAILED\n", phdrAddr + i, sizeof(ph)); return false; } - TRACE("PHDR: %p type %d (%x) vaddr %" PRIxA " memsz %" PRIxA " paddr %" PRIxA " filesz %" PRIxA " offset %" PRIxA " align %" PRIxA "\n", + Trace("PHDR: %p type %d (%x) vaddr %" PRIxA " memsz %" PRIxA " paddr %" PRIxA " filesz %" PRIxA " offset %" PRIxA " align %" PRIxA "\n", phdrAddr + i, ph.p_type, ph.p_type, ph.p_vaddr, ph.p_memsz, ph.p_paddr, ph.p_filesz, ph.p_offset, ph.p_align); switch (ph.p_type) diff --git a/src/coreclr/src/debug/dbgutil/machoreader.cpp b/src/coreclr/src/debug/dbgutil/machoreader.cpp new file mode 100644 index 0000000..353060e --- /dev/null +++ b/src/coreclr/src/debug/dbgutil/machoreader.cpp @@ -0,0 +1,420 @@ +// 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 +#include +#include +#include +#define __STDC_FORMAT_MACROS +#include +#include +#include "machoreader.h" + +#if TARGET_64BIT +#define PRIx PRIx64 +#define PRIu PRIu64 +#define PRId PRId64 +#define PRIA "016" +#define PRIxA PRIA PRIx +#else +#define PRIx PRIx32 +#define PRIu PRIu32 +#define PRId PRId32 +#define PRIA "08" +#define PRIxA PRIA PRIx +#endif + +class MachOReaderExport : public MachOReader +{ +private: + ICorDebugDataTarget* m_dataTarget; + +public: + MachOReaderExport(ICorDebugDataTarget* dataTarget) : + m_dataTarget(dataTarget) + { + dataTarget->AddRef(); + } + + virtual ~MachOReaderExport() + { + m_dataTarget->Release(); + } + +private: + virtual bool ReadMemory(void* address, void* buffer, size_t size) + { + uint32_t read = 0; + return SUCCEEDED(m_dataTarget->ReadVirtual(reinterpret_cast(address), reinterpret_cast(buffer), (uint32_t)size, &read)); + } +}; + +// +// Main entry point to get an export symbol +// +bool +TryGetSymbol(ICorDebugDataTarget* dataTarget, uint64_t baseAddress, const char* symbolName, uint64_t* symbolAddress) +{ + MachOReaderExport reader(dataTarget); + MachOModule module(reader, baseAddress); + if (!module.ReadHeader()) + { + return false; + } + uint64_t symbolOffset; + if (module.TryLookupSymbol(symbolName, &symbolOffset)) + { + *symbolAddress = baseAddress + symbolOffset; + return true; + } + *symbolAddress = 0; + return false; +} + +//-------------------------------------------------------------------- +// MachO module +//-------------------------------------------------------------------- + +MachOModule::MachOModule(MachOReader& reader, mach_vm_address_t baseAddress, mach_header_64* header, std::string* name) : + m_reader(reader), + m_baseAddress(baseAddress), + m_loadBias(0), + m_commands(nullptr), + m_symtabCommand(nullptr), + m_nlists(nullptr), + m_strtab(nullptr) +{ + if (header != nullptr) { + m_header = *header; + } + if (name != nullptr) { + m_name = *name; + } +} + +MachOModule::~MachOModule() +{ + if (m_commands != nullptr) { + free(m_commands); + m_commands = nullptr; + } + if (m_nlists != nullptr) { + free(m_nlists); + m_nlists = nullptr; + } + if (m_strtab != nullptr) { + free(m_strtab); + m_strtab = nullptr; + } +} + +bool +MachOModule::ReadHeader() +{ + _ASSERTE(sizeof(m_header) == sizeof(mach_header_64)); + if (!m_reader.ReadMemory((void*)m_baseAddress, &m_header, sizeof(mach_header_64))) + { + m_reader.Trace("ERROR: failed to read header at %p\n", (void*)m_baseAddress); + return false; + } + return true; +} + +bool +MachOModule::TryLookupSymbol(const char* symbolName, uint64_t* symbolValue) +{ + _ASSERTE(symbolValue != nullptr); + + if (ReadSymbolTable()) + { + _ASSERTE(m_nlists != nullptr); + _ASSERTE(m_strtab != nullptr); + + for (int i = 0; i < m_symtabCommand->nsyms; i++) + { + char* name = m_strtab + m_nlists[i].n_un.n_strx; + if (strcmp(name, symbolName) == 0) + { + *symbolValue = m_nlists[i].n_value; + return true; + } + } + } + *symbolValue = 0; + return false; +} + +bool +MachOModule::EnumerateSegments() +{ + if (!ReadLoadCommands()) + { + return false; + } + _ASSERTE(!m_segments.empty()); + + for (const segment_command_64* segment : m_segments) + { + m_reader.VisitSegment(*this, *segment); + + const section_64* section = (section_64*)((uint64_t)segment + sizeof(segment_command_64)); + + for (int s = 0; s < segment->nsects; s++, section++) + { + m_reader.VisitSection(*this, *section); + } + } + return true; +} + +bool +MachOModule::ReadLoadCommands() +{ + if (m_commands == nullptr) + { + // Read load commands + void* commandsAddress = (void*)(m_baseAddress + sizeof(mach_header_64)); + m_commands = (load_command*)malloc(m_header.sizeofcmds); + if (m_commands == nullptr) + { + m_reader.Trace("ERROR: Failed to allocate %d byte load commands\n", m_header.sizeofcmds); + return false; + } + if (!m_reader.ReadMemory(commandsAddress, m_commands, m_header.sizeofcmds)) + { + m_reader.Trace("ERROR: Failed to read load commands at %p of %d\n", commandsAddress, m_header.sizeofcmds); + return false; + } + load_command* command = m_commands; + + for (int i = 0; i < m_header.ncmds; i++) + { + m_reader.Trace("CMD: load command cmd %02x (%d) size %d\n", command->cmd, command->cmd, command->cmdsize); + + switch (command->cmd) + { + case LC_SYMTAB: + m_symtabCommand = (symtab_command*)command; + break; + + case LC_SEGMENT_64: + segment_command_64* segment = (segment_command_64*)command; + m_segments.push_back(segment); + + // Calculate the load bias for the module. This is the value to add to the vmaddr of a + // segment to get the actual address. + if (strcmp(segment->segname, SEG_TEXT) == 0) + { + m_loadBias = m_baseAddress - segment->vmaddr; + m_reader.Trace("CMD: load bias %016llx\n", m_loadBias); + } + + m_reader.Trace("CMD: vmaddr %016llx vmsize %016llx fileoff %016llx filesize %016llx nsects %d max %c%c%c init %c%c%c %02x %s\n", + segment->vmaddr, + segment->vmsize, + segment->fileoff, + segment->filesize, + segment->nsects, + (segment->maxprot & VM_PROT_READ) ? 'r' : '-', + (segment->maxprot & VM_PROT_WRITE) ? 'w' : '-', + (segment->maxprot & VM_PROT_EXECUTE) ? 'x' : '-', + (segment->initprot & VM_PROT_READ) ? 'r' : '-', + (segment->initprot & VM_PROT_WRITE) ? 'w' : '-', + (segment->initprot & VM_PROT_EXECUTE) ? 'x' : '-', + segment->flags, + segment->segname); + + // TODO - remove + m_reader.Trace("CMD: base + fileoff %016llx vmaddr - fileoff %016llx base - vmaddr %016llx\n", + m_baseAddress + segment->fileoff, + segment->vmaddr - segment->fileoff, + m_baseAddress - segment->vmaddr); + + section_64* section = (section_64*)((uint64_t)segment + sizeof(segment_command_64)); + for (int s = 0; s < segment->nsects; s++, section++) + { + m_reader.Trace(" addr %016llx size %016llx off %08x align %02x flags %02x %s\n", + section->addr, + section->size, + section->offset, + section->align, + section->flags, + section->sectname); + } + break; + } + // Get next load command + command = (load_command*)((char*)command + command->cmdsize); + } + } + + return true; +} + +bool +MachOModule::ReadSymbolTable() +{ + if (m_nlists == nullptr) + { + if (!ReadLoadCommands()) + { + return false; + } + _ASSERTE(m_symtabCommand != nullptr); + _ASSERTE(m_strtab == nullptr); + + m_reader.Trace("SYM: symoff %08x nsyms %d stroff %08x strsize %d\n", + m_symtabCommand->symoff, + m_symtabCommand->nsyms, + m_symtabCommand->stroff, + m_symtabCommand->strsize); + + // Read symbol table. An array of "nlist" structs. + void* symtabAddress = GetAddressFromFileOffset(m_symtabCommand->symoff); + size_t symtabSize = sizeof(nlist_64) * m_symtabCommand->nsyms; + + m_nlists = (nlist_64*)malloc(symtabSize); + if (m_nlists == nullptr) + { + m_reader.Trace("ERROR: Failed to allocate %zu byte symbol table\n", symtabSize); + return false; + } + if (!m_reader.ReadMemory(symtabAddress, m_nlists, symtabSize)) + { + m_reader.Trace("ERROR: Failed to read symtab at %p of %zu\n", symtabAddress, symtabSize); + return false; + } + + // Read the symbol string table. + void* strtabAddress = GetAddressFromFileOffset(m_symtabCommand->stroff); + size_t strtabSize = m_symtabCommand->strsize; + + m_strtab = (char*)malloc(strtabSize); + if (m_strtab == nullptr) + { + m_reader.Trace("ERROR: Failed to allocate %zu byte symbol string table\n", strtabSize); + return false; + } + if (!m_reader.ReadMemory(strtabAddress, m_strtab, strtabSize)) + { + m_reader.Trace("ERROR: Failed to read string table at %p of %zu\n", strtabAddress, strtabSize); + return false; + } + } + return true; +} + +void* +MachOModule::GetAddressFromFileOffset(uint32_t offset) +{ + _ASSERTE(!m_segments.empty()); + + for (const segment_command_64* segment : m_segments) + { + if (offset >= segment->fileoff && offset < (segment->fileoff + segment->filesize)) + { + return (void*)(m_baseAddress + offset + segment->vmaddr - segment->fileoff); + } + } + return (void*)(m_baseAddress + offset); +} + +//-------------------------------------------------------------------- +// MachO reader +//-------------------------------------------------------------------- + +MachOReader::MachOReader() +{ +} + +bool +MachOReader::EnumerateModules(mach_vm_address_t address, mach_header_64* header) +{ + _ASSERTE(header->magic == MH_MAGIC_64); + _ASSERTE(header->filetype == MH_DYLINKER); + + MachOModule dylinker(*this, address, header); + + // Search for symbol for the dyld image info cache + uint64_t dyldInfoOffset = 0; + if (!dylinker.TryLookupSymbol("_dyld_all_image_infos", &dyldInfoOffset)) + { + Trace("ERROR: Can not find the _dyld_all_image_infos symbol\n"); + return false; + } + + // Read the all image info from the dylinker image + void* dyldInfoAddress = (void*)(address + dyldInfoOffset); + dyld_all_image_infos dyldInfo; + + if (!ReadMemory(dyldInfoAddress, &dyldInfo, sizeof(dyld_all_image_infos))) + { + Trace("ERROR: Failed to read dyld_all_image_infos at %p\n", dyldInfoAddress); + return false; + } + std::string dylinkerPath; + if (!ReadString(dyldInfo.dyldPath, dylinkerPath)) + { + Trace("ERROR: Failed to read name at %p\n", dyldInfo.dyldPath); + return false; + } + dylinker.SetName(dylinkerPath); + Trace("MOD: %016llx %08x %s\n", dylinker.BaseAddress(), dylinker.Header().flags, dylinker.Name().c_str()); + VisitModule(dylinker); + + void* imageInfosAddress = (void*)dyldInfo.infoArray; + size_t imageInfosSize = dyldInfo.infoArrayCount * sizeof(dyld_image_info); + Trace("MOD: infoArray %p infoArrayCount %d\n", dyldInfo.infoArray, dyldInfo.infoArrayCount); + + ArrayHolder imageInfos = new (std::nothrow) dyld_image_info[dyldInfo.infoArrayCount]; + if (imageInfos == nullptr) + { + Trace("ERROR: Failed to allocate %zu byte image infos\n", imageInfosSize); + return false; + } + if (!ReadMemory(imageInfosAddress, imageInfos, imageInfosSize)) + { + Trace("ERROR: Failed to read dyld_all_image_infos at %p\n", imageInfosAddress); + return false; + } + for (int i = 0; i < dyldInfo.infoArrayCount; i++) + { + mach_vm_address_t imageAddress = (mach_vm_address_t)imageInfos[i].imageLoadAddress; + const char* imageFilePathAddress = imageInfos[i].imageFilePath; + + std::string imagePath; + if (!ReadString(imageFilePathAddress, imagePath)) + { + Trace("ERROR: Failed to read image name at %p\n", imageFilePathAddress); + continue; + } + MachOModule module(*this, imageAddress, nullptr, &imagePath); + if (!module.ReadHeader()) + { + continue; + } + Trace("MOD: %016llx %08x %s\n", imageAddress, module.Header().flags, imagePath.c_str()); + VisitModule(module); + } + return true; +} + +bool +MachOReader::ReadString(const char* address, std::string& str) +{ + for (int i = 0; i < MAX_LONGPATH; i++) + { + char c = 0; + if (!ReadMemory((void*)(address + i), &c, sizeof(char))) + { + return false; + } + if (c == '\0') + { + break; + } + str.append(1, c); + } + return true; +} diff --git a/src/coreclr/src/debug/dbgutil/machoreader.h b/src/coreclr/src/debug/dbgutil/machoreader.h new file mode 100644 index 0000000..4f108c6 --- /dev/null +++ b/src/coreclr/src/debug/dbgutil/machoreader.h @@ -0,0 +1,64 @@ +// 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 +#include +#include +#include +#include +#include + +class MachOReader; + +class MachOModule +{ + friend MachOReader; +private: + MachOReader& m_reader; + mach_vm_address_t m_baseAddress; + mach_vm_address_t m_loadBias; + mach_header_64 m_header; + std::string m_name; + load_command* m_commands; + std::vector m_segments; + symtab_command* m_symtabCommand; + nlist_64* m_nlists; + char* m_strtab; + +public: + MachOModule(MachOReader& reader, mach_vm_address_t baseAddress, mach_header_64* header = nullptr, std::string* name = nullptr); + ~MachOModule(); + + inline mach_vm_address_t BaseAddress() const { return m_baseAddress; } + inline mach_vm_address_t LoadBias() const { return m_loadBias; } + inline const mach_header_64& Header() const { return m_header; } + inline const std::string& Name() const { return m_name; } + + bool ReadHeader(); + bool TryLookupSymbol(const char* symbolName, uint64_t* symbolValue); + bool EnumerateSegments(); + +private: + inline void SetName(std::string& name) { m_name = name; } + + bool ReadLoadCommands(); + bool ReadSymbolTable(); + void* GetAddressFromFileOffset(uint32_t offset); +}; + +class MachOReader +{ + friend MachOModule; +public: + MachOReader(); + bool EnumerateModules(mach_vm_address_t address, mach_header_64* header); + +private: + bool ReadString(const char* address, std::string& str); + virtual void VisitModule(MachOModule& module) { }; + virtual void VisitSegment(MachOModule& module, const segment_command_64& segment) { }; + virtual void VisitSection(MachOModule& module, const section_64& section) { }; + virtual bool ReadMemory(void* address, void* buffer, size_t size) = 0; + virtual void Trace(const char* format, ...) { }; +}; diff --git a/src/coreclr/src/pal/src/thread/process.cpp b/src/coreclr/src/pal/src/thread/process.cpp index e002f23..ebde5d3 100644 --- a/src/coreclr/src/pal/src/thread/process.cpp +++ b/src/coreclr/src/pal/src/thread/process.cpp @@ -3302,8 +3302,6 @@ Function: BOOL PROCCreateCrashDump(char** argv) { -#if HAVE_PRCTL_H && HAVE_PR_SET_PTRACER - // Fork the core dump child process. pid_t childpid = fork(); @@ -3324,6 +3322,7 @@ PROCCreateCrashDump(char** argv) } else { +#if HAVE_PRCTL_H && HAVE_PR_SET_PTRACER // Gives the child process permission to use /proc//mem and ptrace if (prctl(PR_SET_PTRACER, childpid, 0, 0, 0) == -1) { @@ -3331,6 +3330,7 @@ PROCCreateCrashDump(char** argv) // supported but createdump works just fine. ERROR("PPROCCreateCrashDump: prctl() FAILED %d (%s)\n", errno, strerror(errno)); } +#endif // HAVE_PRCTL_H && HAVE_PR_SET_PTRACER // Parent waits until the child process is done int wstatus = 0; int result = waitpid(childpid, &wstatus, 0); @@ -3342,7 +3342,6 @@ PROCCreateCrashDump(char** argv) } return !WIFEXITED(wstatus) || WEXITSTATUS(wstatus) == 0; } -#endif // HAVE_PRCTL_H && HAVE_PR_SET_PTRACER return true; } -- 2.7.4