diff --git a/src/coreclr/debug/createdump/CMakeLists.txt b/src/coreclr/debug/createdump/CMakeLists.txt index bdd648c2670c3c..9488b9d1610473 100644 --- a/src/coreclr/debug/createdump/CMakeLists.txt +++ b/src/coreclr/debug/createdump/CMakeLists.txt @@ -15,7 +15,8 @@ if(CLR_CMAKE_HOST_WIN32) set(CREATEDUMP_SOURCES main.cpp createdumpmain.cpp - dumpname.cpp + shared/createdumpcore.cpp + shared/dumpname.cpp createdumpwindows.cpp createdump.rc ) @@ -59,15 +60,21 @@ else(CLR_CMAKE_HOST_WIN32) # This is so we can include "version.c" include_directories(${CMAKE_BINARY_DIR}) + set(CREATEDUMP_SHARED_SOURCES + shared/createdumpcore.cpp + shared/dumpname.cpp + shared/crashinfocore.cpp + shared/dumpwriter.cpp + ) + set(CREATEDUMP_SOURCES createdumpmain.cpp - dumpname.cpp createdumpunix.cpp crashinfo.cpp threadinfo.cpp datatarget.cpp - dumpwriter.cpp crashreportwriter.cpp + ${CREATEDUMP_SHARED_SOURCES} ) if(CLR_CMAKE_HOST_OSX) @@ -75,15 +82,22 @@ if(CLR_CMAKE_HOST_OSX) STATIC crashinfomac.cpp threadinfomac.cpp - dumpwritermacho.cpp + shared/dumpwritermacho.cpp ${CREATEDUMP_SOURCES} ) else() + set(CREATEDUMP_SHARED_LINUX_SOURCES + shared/crashinfounixcore.cpp + shared/createdumpunixcore.cpp + shared/dumpwriterelf.cpp + shared/threadsnapshot.cpp + ) + add_library_clr(createdump_static STATIC crashinfounix.cpp threadinfounix.cpp - dumpwriterelf.cpp + ${CREATEDUMP_SHARED_LINUX_SOURCES} ${CREATEDUMP_SOURCES} ) endif(CLR_CMAKE_HOST_OSX) @@ -111,3 +125,7 @@ if (CLR_CMAKE_HOST_APPLE) endif() install_clr(TARGETS createdump DESTINATIONS . sharedFramework COMPONENT runtime) + +if(CLR_CMAKE_TARGET_LINUX OR CLR_CMAKE_TARGET_OSX) + install_clr(TARGETS createdump DESTINATIONS . COMPONENT nativeaot) +endif() diff --git a/src/coreclr/debug/createdump/crashinfo.cpp b/src/coreclr/debug/createdump/crashinfo.cpp index 4a40c6d38630d0..685c8af8912dae 100644 --- a/src/coreclr/debug/createdump/crashinfo.cpp +++ b/src/coreclr/debug/createdump/crashinfo.cpp @@ -11,18 +11,15 @@ CrashInfo* g_crashInfo; static bool ModuleInfoCompare(const ModuleInfo* lhs, const ModuleInfo* rhs) { return lhs->BaseAddress() < rhs->BaseAddress(); } -CrashInfo::CrashInfo(const CreateDumpOptions& options) : +CrashInfo::CrashInfo(const CreateDumpOptions& options, ProcessInfo& processInfo) : m_ref(1), - m_pid(options.Pid), - m_ppid(-1), + m_processInfo(processInfo), m_dacModule(nullptr), m_pClrDataEnumRegions(nullptr), m_pClrDataProcess(nullptr), m_appModel(options.AppModel), m_gatherFrames(options.CrashReport), - m_crashThread(options.CrashThread), - m_signal(options.Signal), - m_exceptionRecord(options.ExceptionRecord), + m_dumpRegionStore(&m_memoryRegions, &CrashInfo::FindMemoryRegionOverlap, &CrashInfo::InsertDumpRegion), m_moduleInfos(&ModuleInfoCompare), m_mainModule(nullptr), m_cbModuleMappings(0), @@ -30,18 +27,33 @@ CrashInfo::CrashInfo(const CreateDumpOptions& options) : m_enumMemoryPagesAdded(0) { g_crashInfo = this; - m_runtimeBaseAddress = 0; -#ifdef __APPLE__ - m_task = 0; -#else - m_auxvValues.fill(0); - m_fdMem = -1; -#endif - memset(&m_siginfo, 0, sizeof(m_siginfo)); - m_siginfo.si_signo = options.Signal; - m_siginfo.si_code = options.SignalCode; - m_siginfo.si_errno = options.SignalErrno; - m_siginfo.si_addr = (void*)options.SignalAddress; +} + +bool +CrashInfo::PopulateFromProcessInfo() +{ + for (const ThreadSnapshot& snapshot : m_processInfo.Threads()) + { + m_threads.push_back(new ThreadInfo(*this, snapshot)); + } + + for (const ModuleRegion& mapping : m_processInfo.ModuleMappings()) + { + ModuleRegion moduleRegion(mapping.Flags(), mapping.StartAddress(), mapping.EndAddress(), mapping.Offset()); + if (!moduleRegion.SetFileName(mapping.FileName())) + { + return false; + } + m_cbModuleMappings += moduleRegion.Size(); + m_moduleMappings.insert(Move(moduleRegion)); + } + + for (const MemoryRegion& mapping : m_processInfo.OtherMappings()) + { + m_otherMappings.insert(mapping); + } + + return true; } CrashInfo::~CrashInfo() @@ -76,9 +88,9 @@ CrashInfo::~CrashInfo() m_dacModule = nullptr; } #ifdef __APPLE__ - if (m_task != 0) + if (Task() != 0) { - kern_return_t result = ::mach_port_deallocate(mach_task_self(), m_task); + kern_return_t result = ::mach_port_deallocate(mach_task_self(), Task()); if (result != KERN_SUCCESS) { printf_error("Internal error: mach_port_deallocate FAILED %s (%x)\n", mach_error_string(result), result); @@ -87,6 +99,33 @@ CrashInfo::~CrashInfo() #endif } +bool +CrashInfo::FindMemoryRegionOverlap( + void* container, + uint64_t startAddress, + uint64_t endAddress, + MemoryRegion* result) +{ + std::set* regions = static_cast*>(container); + MemoryRegion search(0, startAddress, endAddress, 0); + std::set::const_iterator found = regions->find(search); + if (found == regions->end()) + { + return false; + } + + *result = *found; + return true; +} + +bool +CrashInfo::InsertDumpRegion(void* container, const MemoryRegion* region) +{ + std::set* regions = static_cast*>(container); + regions->insert(*region); + return true; +} + STDMETHODIMP CrashInfo::QueryInterface( ___in REFIID InterfaceId, @@ -154,30 +193,12 @@ CrashInfo::LogMessage( bool CrashInfo::GatherCrashInfo(DumpType dumpType) { - // Get the info about the threads (registers, etc.) - for (ThreadInfo* thread : m_threads) - { - if (!thread->Initialize()) - { - return false; - } - } #ifdef __APPLE__ if (!EnumerateMemoryRegions()) { return false; } #else - // Get the auxv data - if (!GetAuxvEntries()) - { - return false; - } - // Gather all the module memory mappings (from /dev/$pid/maps) - if (!EnumerateMemoryRegions()) - { - return false; - } // Get shared module debug info if (!GetDSOInfo()) { @@ -196,11 +217,11 @@ CrashInfo::GatherCrashInfo(DumpType dumpType) { return false; } - // Add the special (fake) memory region for the special diagnostics info. Use constructor that doesn't assert PAGE_SIZE alignment. - MemoryRegion special(PF_R, SpecialDiagInfoAddress, SpecialDiagInfoAddress + SpecialDiagInfoSize, /* offset */ 0); - m_memoryRegions.insert(special); #ifdef __APPLE__ - InitializeOtherMappings(); + if (!InitializeOtherMappings()) + { + return false; + } #endif if (!UnwindAllThreads()) { @@ -214,49 +235,15 @@ CrashInfo::GatherCrashInfo(DumpType dumpType) region.Trace(); } } - // If full memory dump, include everything regardless of permissions - if (dumpType == DumpType::Full) - { - for (const MemoryRegion& region : m_moduleMappings) - { - InsertMemoryRegion(region); - } - for (const MemoryRegion& region : m_otherMappings) - { - // Don't add uncommitted pages to the full dump - if ((region.Permissions() & (PF_R | PF_W | PF_X)) != 0) - { - InsertMemoryRegion(region); - } - } - } - else + return true; +} + +void CrashInfo::AddThreadStacks() +{ + for (ThreadInfo* thread : m_threads) { - // 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 (dumpType == DumpType::Heap) - { - for (const MemoryRegion& region : m_otherMappings) - { - uint32_t permissions = region.Permissions(); -#ifdef __APPLE__ - if (permissions == (PF_R | PF_W)) -#else - if (permissions == (PF_R | PF_W) || permissions == (PF_R | PF_W | PF_X)) -#endif - { - InsertMemoryRegion(region); - } - } - } - // Add the thread's stack and some code memory to core - for (ThreadInfo* thread : m_threads) - { - // Add the thread's stack - thread->GetThreadStack(); - } + thread->GetThreadStack(); } - return true; } static const char* @@ -524,30 +511,40 @@ CrashInfo::AddOrReplaceModuleMapping(uint64_t baseAddress, uint64_t size, const if (found == m_moduleMappings.end()) { // On MacOS the assemblies are always added. - ModuleRegion newRegion(flags, start, end, 0, name); - m_moduleMappings.insert(newRegion); + ModuleRegion newRegion(flags, start, end, 0); + if (!newRegion.SetFileName(name.c_str())) + { + return; + } m_cbModuleMappings += newRegion.Size(); if (g_diagnostics) { newRegion.Trace("MODULE: ADD "); } + m_moduleMappings.insert(Move(newRegion)); } - else if (found->FileName().compare(name) != 0) + else if (!found->FileNameEquals(name.c_str())) { // Create the new memory region with the managed assembly name. - ModuleRegion newRegion(*found, name); + ModuleRegion newRegion(static_cast(*found)); + if (!newRegion.SetFileName(name.c_str())) + { + return; + } + + uint64_t oldRegionSize = found->Size(); // Remove and cleanup the old one m_moduleMappings.erase(found); - m_cbModuleMappings -= found->Size(); + m_cbModuleMappings -= oldRegionSize; // Add the new memory region. - m_moduleMappings.insert(newRegion); m_cbModuleMappings += newRegion.Size(); if (g_diagnostics) { newRegion.Trace("MODULE: REPLACE "); } + m_moduleMappings.insert(Move(newRegion)); } } @@ -709,113 +706,10 @@ CrashInfo::InsertMemoryRegion(uint64_t address, size_t size) return InsertMemoryRegion(MemoryRegion(GetMemoryRegionFlags(start), start, end)); } -// -// Add a memory region to the list. Returns the number of pages actually added. -// int CrashInfo::InsertMemoryRegion(const MemoryRegion& region) { - // Check if the new region overlaps with the previously added ones - const auto& conflictingRegion = m_memoryRegions.find(region); - const bool hasConflict = conflictingRegion != m_memoryRegions.end(); - if (hasConflict && conflictingRegion->Contains(region)) - { - // The region is contained in the one we added before - // Nothing to do - return 0; - } - - // Go page by page and split the region into valid sub-regions - uint64_t pageStart = region.StartAddress(); - uint64_t numberPages = region.Size() / PAGE_SIZE; - uint64_t subRegionStart, subRegionEnd; - int pagesAdded = 0; - subRegionStart = subRegionEnd = pageStart; - for (size_t p = 0; p < numberPages; p++, pageStart += PAGE_SIZE) - { - MemoryRegion page(region.Flags(), pageStart, pageStart + PAGE_SIZE); - - // avoid searching for conflicts if we know we don't have one - const bool pageHasConflicts = hasConflict && m_memoryRegions.find(page) != m_memoryRegions.end(); - // avoid validating the page if it conflicts: we won't add it in any case - const bool pageIsValid = !pageHasConflicts && PageMappedToPhysicalMemory(pageStart) && PageCanBeRead(pageStart); - - if (pageIsValid) - { - subRegionEnd = page.EndAddress(); - pagesAdded++; - } - else - { - // the next page is not valid thus sub-region is complete - if (subRegionStart != subRegionEnd) - { - m_memoryRegions.insert(MemoryRegion(region.Flags(), subRegionStart, subRegionEnd)); - } - subRegionStart = subRegionEnd = page.EndAddress(); - } - } - // add the last sub-region if it's not empty - if (subRegionStart != subRegionEnd) - { - m_memoryRegions.insert(MemoryRegion(region.Flags(), subRegionStart, subRegionEnd)); - } - - return pagesAdded; -} - -// -// Check the page is really used by the application before adding it to the dump -// On some kernels reading a region from createdump results in committing this region in the parent application -// That leads to OOM in container environment and unnecesserally increses the size of the dump file -// However this is an optimization: if it fails we still try to add the page to the dump -// -bool -CrashInfo::PageMappedToPhysicalMemory(uint64_t start) -{ - #if !defined(__linux__) - // this check has not been implemented yet for other unix systems - return true; - #else - // https://www.kernel.org/doc/Documentation/vm/pagemap.txt - if (m_fdPagemap == -1) - { - // Weren't able to open pagemap file, so don't run this check - // Expected on kernels 4.0 and 4.1 as we need CAP_SYS_ADMIN to open /proc/pid/pagemap - // On kernels after 4.2 we only need PTRACE_MODE_READ_FSCREDS as we are ok with zeroed PFNs - return true; - } - - uint64_t pagemapOffset = (start / PAGE_SIZE) * sizeof(uint64_t); - uint64_t seekResult = lseek(m_fdPagemap, (off_t) pagemapOffset, SEEK_SET); - if (seekResult != pagemapOffset) - { - int seekErrno = errno; - TRACE("Seeking in pagemap file FAILED, addr: %" PRIA PRIx64 ", pagemap offset: %" PRIA PRIx64 ", ERRNO %d: %s\n", start, pagemapOffset, seekErrno, strerror(seekErrno)); - return true; - } - uint64_t value; - size_t readResult = read(m_fdPagemap, (void*)&value, sizeof(value)); - if (readResult == (size_t) -1) - { - int readErrno = errno; - TRACE("Reading of pagemap file FAILED, addr: %" PRIA PRIx64 ", pagemap offset: %" PRIA PRIx64 ", size: %zu, ERRNO %d: %s\n", start, pagemapOffset, sizeof(value), readErrno, strerror(readErrno)); - return true; - } - - bool is_page_present = (value & ((uint64_t)1 << 63)) != 0; - bool is_page_swapped = (value & ((uint64_t)1 << 62)) != 0; - TRACE_VERBOSE("Pagemap value for %" PRIA PRIx64 ", pagemap offset %" PRIA PRIx64 " is %" PRIA PRIx64 " -> %s\n", start, pagemapOffset, value, is_page_present ? "in memory" : (is_page_swapped ? "in swap" : "NOT in memory")); - return is_page_present || is_page_swapped; - #endif -} - -bool -CrashInfo::PageCanBeRead(uint64_t start) -{ - BYTE buffer[1]; - size_t read; - return ReadProcessMemory(start, buffer, 1, &read); + return m_processInfo.InsertMemoryRegion(m_dumpRegionStore, region); } // @@ -824,53 +718,22 @@ CrashInfo::PageCanBeRead(uint64_t start) void CrashInfo::CombineMemoryRegions() { - TRACE("CombineMemoryRegions: STARTED\n"); - assert(!m_memoryRegions.empty()); std::set memoryRegionsNew; - // MEMORY_REGION_FLAG_SHARED and MEMORY_REGION_FLAG_PRIVATE are internal flags that - // don't affect the core dump so ignore them when comparing the flags. - uint32_t flags = m_memoryRegions.begin()->Flags() & MEMORY_REGION_FLAG_PERMISSIONS_MASK; - uint64_t start = m_memoryRegions.begin()->StartAddress(); - uint64_t end = start; - - for (const MemoryRegion& region : m_memoryRegions) + if (!::CombineMemoryRegions( + m_memoryRegions, + memoryRegionsNew, + [&memoryRegionsNew](const MemoryRegion& region) + { + assert(memoryRegionsNew.find(region) == memoryRegionsNew.end()); + return memoryRegionsNew.insert(region).second; + })) { - // To combine a region it needs to be contiguous, same permissions and memory backed flag. - if ((end == region.StartAddress()) && - (flags == (region.Flags() & MEMORY_REGION_FLAG_PERMISSIONS_MASK))) - { - end = region.EndAddress(); - } - else - { - MemoryRegion memoryRegion(flags, start, end); - assert(memoryRegionsNew.find(memoryRegion) == memoryRegionsNew.end()); - memoryRegionsNew.insert(memoryRegion); - - flags = region.Flags() & MEMORY_REGION_FLAG_PERMISSIONS_MASK; - start = region.StartAddress(); - end = region.EndAddress(); - } + assert(!"Could not combine memory regions"); + return; } - assert(start != end); - MemoryRegion memoryRegion(flags, start, end); - assert(memoryRegionsNew.find(memoryRegion) == memoryRegionsNew.end()); - memoryRegionsNew.insert(memoryRegion); - m_memoryRegions = memoryRegionsNew; - - TRACE("CombineMemoryRegions: FINISHED\n"); - - if (g_diagnosticsVerbose) - { - TRACE("Final Memory Regions:\n"); - for (const MemoryRegion& region : m_memoryRegions) - { - region.Trace(); - } - } } // diff --git a/src/coreclr/debug/createdump/crashinfo.h b/src/coreclr/debug/createdump/crashinfo.h index 7a51cceb81d449..23345ac93b380d 100644 --- a/src/coreclr/debug/createdump/crashinfo.h +++ b/src/coreclr/debug/createdump/crashinfo.h @@ -3,35 +3,6 @@ #include -#ifdef __APPLE__ -#include "../dbgutil/machoreader.h" -#else -#include "../dbgutil/elfreader.h" - -// typedef for our parsing of the auxv variables in /proc/pid/auxv. -#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; - -// All interesting auvx entry types are AT_SYSINFO_EHDR and below -#define AT_MAX (AT_SYSINFO_EHDR + 1) - -#endif - extern const std::string GetFileName(const std::string& fileName); extern const std::string GetDirectory(const std::string& fileName); extern std::string FormatString(const char* format, ...) MINIPAL_ATTR_FORMAT_PRINTF(1, 2); @@ -47,35 +18,21 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi { private: LONG m_ref; // reference count - pid_t m_pid; // pid - pid_t m_ppid; // parent pid - pid_t m_tgid; // process group + ProcessInfo& m_processInfo; // shared captured process state void* m_dacModule; // dac module pointer when loaded ICLRDataEnumMemoryRegions* m_pClrDataEnumRegions; // dac enumerate memory interface instance IXCLRDataProcess* m_pClrDataProcess; // dac process interface instance AppModelType m_appModel; // Normal, single-file or native AOT app. bool m_gatherFrames; // if true, add the native and managed stack frames to the thread info - pid_t m_crashThread; // crashing thread id or 0 if none - uint32_t m_signal; // crash signal code or 0 if none - uint64_t m_exceptionRecord; // exception record address or 0 if none - std::string m_name; // exe name - siginfo_t m_siginfo; // signal info (if any) std::string m_coreclrPath; // the path of the coreclr module or empty if none - uint64_t m_runtimeBaseAddress; // base address of the runtime module #ifdef __APPLE__ - vm_map_t m_task; // the mach task for the process std::set m_allMemoryRegions; // all memory regions on MacOS -#else - bool m_canUseProcVmReadSyscall; - int m_fdMem; // /proc//mem handle - int m_fdPagemap; // /proc//pagemap handle - std::array m_auxvValues; // auxv values - std::vector m_auxvEntries; // full auxv entries #endif std::vector m_threads; // threads found and suspended std::set m_moduleMappings; // module memory mappings std::set m_otherMappings; // other memory mappings std::set m_memoryRegions; // memory regions from DAC, etc. + DumpRegionStore m_dumpRegionStore; // shared adapter over m_memoryRegions std::set m_moduleAddresses; // memory region to module base address std::set m_moduleInfos; // module infos (base address and module name) ModuleInfo* m_mainModule; // the module containing "Main" @@ -85,7 +42,7 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi void operator=(const CrashInfo&) = delete; public: - CrashInfo(const CreateDumpOptions& options); + CrashInfo(const CreateDumpOptions& options, ProcessInfo& processInfo); virtual ~CrashInfo(); // Memory usage stats @@ -93,9 +50,9 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi int m_dataTargetPagesAdded; int m_enumMemoryPagesAdded; - bool Initialize(); + bool PopulateFromProcessInfo(); + void AddThreadStacks(); void CleanupAndResumeProcess(); - bool EnumerateAndSuspendThreads(); bool GatherCrashInfo(DumpType dumpType); void CombineMemoryRegions(); bool EnumerateMemoryRegionsWithDAC(DumpType dumpType); @@ -110,28 +67,29 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi const ModuleRegion* SearchModuleRegions(const ModuleRegion& search); 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 pid_t Pid() const { return m_processInfo.Pid(); } + inline pid_t Ppid() const { return m_processInfo.Ppid(); } + inline pid_t Tgid() const { return m_processInfo.Tgid(); } #ifdef __APPLE__ - inline vm_map_t Task() const { return m_task; } + inline vm_map_t Task() const { return m_processInfo.Task(); } #endif inline const bool GatherFrames() const { return m_gatherFrames; } - inline const pid_t CrashThread() const { return m_crashThread; } - inline const uint32_t Signal() const { return m_signal; } - inline const uint64_t ExceptionRecord () const { return m_exceptionRecord; } - inline const std::string& Name() const { return m_name; } + inline pid_t CrashThread() const { return m_processInfo.CrashThread(); } + inline uint32_t Signal() const { return m_processInfo.Signal(); } + inline uint64_t ExceptionRecord() const { return m_processInfo.ExceptionRecord(); } + inline const char* Name() const { return m_processInfo.Name(); } inline const ModuleInfo* MainModule() const { return m_mainModule; } - inline const uint64_t RuntimeBaseAddress() const { return m_runtimeBaseAddress; } + inline uint64_t RuntimeBaseAddress() const { return m_processInfo.RuntimeBaseAddress(); } inline const std::vector& Threads() const { return m_threads; } inline const std::set& ModuleMappings() const { return m_moduleMappings; } inline const std::set& OtherMappings() const { return m_otherMappings; } inline const std::set& MemoryRegions() const { return m_memoryRegions; } - inline const siginfo_t* SigInfo() const { return &m_siginfo; } + inline DumpRegionStore& GetDumpRegionStore() { return m_dumpRegionStore; } #ifndef __APPLE__ - inline const std::vector& AuxvEntries() const { return m_auxvEntries; } - inline size_t GetAuxvSize() const { return m_auxvEntries.size() * sizeof(elf_aux_entry); } + bool CopyDumpWriterRegions(DynamicArray& moduleMappings, DynamicArray& dumpRegions) const; + inline const DynamicArray& AuxvEntries() const { return m_processInfo.AuxvEntries(); } + inline size_t GetAuxvSize() const { return m_processInfo.GetAuxvSize(); } #endif bool ReadMemory(void* address, void* buffer, size_t size) { return ReadMemory((uint64_t)address, buffer, size); } @@ -149,12 +107,11 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi private: #ifdef __APPLE__ bool EnumerateMemoryRegions(); - void InitializeOtherMappings(); + bool InitializeOtherMappings(); 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); @@ -168,6 +125,8 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi uint32_t GetMemoryRegionFlags(uint64_t start); bool PageCanBeRead(uint64_t start); bool PageMappedToPhysicalMemory(uint64_t start); + static bool FindMemoryRegionOverlap(void* container, uint64_t startAddress, uint64_t endAddress, MemoryRegion* result); + static bool InsertDumpRegion(void* container, const MemoryRegion* region); void Trace(const char* format, ...) MINIPAL_ATTR_FORMAT_PRINTF(2, 3); void TraceVerbose(const char* format, ...) MINIPAL_ATTR_FORMAT_PRINTF(2, 3); }; diff --git a/src/coreclr/debug/createdump/crashinfomac.cpp b/src/coreclr/debug/createdump/crashinfomac.cpp index d99a8b9878bde6..04789f07d309cf 100644 --- a/src/coreclr/debug/createdump/crashinfomac.cpp +++ b/src/coreclr/debug/createdump/crashinfomac.cpp @@ -6,11 +6,8 @@ int g_readProcessMemoryResult = KERN_SUCCESS; bool -CrashInfo::Initialize() +ProcessInfo::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) { @@ -22,6 +19,19 @@ CrashInfo::Initialize() return true; } +void +ProcessInfo::CleanupAndResumeProcess() +{ + for (const ThreadSnapshot& thread : m_threads) + { + kern_return_t result = ::mach_port_deallocate(mach_task_self(), thread.Port()); + if (result != KERN_SUCCESS) + { + printf_error("Internal error: mach_port_deallocate FAILED %s (%x)\n", mach_error_string(result), result); + } + } +} + void CrashInfo::CleanupAndResumeProcess() { @@ -29,23 +39,20 @@ CrashInfo::CleanupAndResumeProcess() ::task_resume(Task()); } -// -// Suspends all the threads and creating a list of them. Should be the before gathering any info about the process. -// bool -CrashInfo::EnumerateAndSuspendThreads() +ProcessInfo::EnumerateAndSuspendThreads() { thread_act_port_array_t threadList; mach_msg_type_number_t threadCount; - kern_return_t result = ::task_suspend(Task()); + kern_return_t result = ::task_suspend(m_task); if (result != KERN_SUCCESS) { printf_error("Problem suspending process: task_suspend(%d) FAILED %s (%x)\n", m_pid, mach_error_string(result), result); return false; } - result = ::task_threads(Task(), &threadList, &threadCount); + result = ::task_threads(m_task, &threadList, &threadCount); if (result != KERN_SUCCESS) { printf_error("Problem enumerating threads: task_threads(%d) FAILED %s (%x)\n", m_pid, mach_error_string(result), result); @@ -69,9 +76,16 @@ CrashInfo::EnumerateAndSuspendThreads() tid = tident.thread_id; } - // Add to the list of threads - ThreadInfo* thread = new ThreadInfo(*this, tid, threadList[i]); - m_threads.push_back(thread); + ThreadSnapshot thread(tid, threadList[i]); + if (!m_threads.Add(thread)) + { + for (mach_msg_type_number_t remaining = i; remaining < threadCount; remaining++) + { + ::mach_port_deallocate(mach_task_self(), threadList[remaining]); + } + ::vm_deallocate(mach_task_self(), reinterpret_cast(threadList), threadCount * sizeof(thread_act_t)); + return false; + } } result = ::vm_deallocate(mach_task_self(), reinterpret_cast(threadList), threadCount * sizeof(thread_act_t)); @@ -168,7 +182,7 @@ CrashInfo::EnumerateMemoryRegions() return true; } -void +bool CrashInfo::InitializeOtherMappings() { uint64_t cbOtherMappings = 0; @@ -219,6 +233,22 @@ CrashInfo::InitializeOtherMappings() } } TRACE("OtherMappings: %06llx\n", cbOtherMappings / PAGE_SIZE); + + for (const ModuleRegion& mapping : m_moduleMappings) + { + if (!m_processInfo.AddMapping(mapping)) + { + return false; + } + } + for (const MemoryRegion& mapping : m_otherMappings) + { + if (!m_processInfo.AddMapping(mapping)) + { + return false; + } + } + return true; } void CrashInfo::VisitModule(MachOModule& module) @@ -226,9 +256,9 @@ void CrashInfo::VisitModule(MachOModule& module) AddModuleInfo(false, module.BaseAddress(), nullptr, module.Name()); // Get the process name from the executable module file type - if (m_name.empty() && module.Header().filetype == MH_EXECUTE) + if (*Name() == '\0' && module.Header().filetype == MH_EXECUTE) { - m_name = GetFileName(module.Name()); + m_processInfo.SetName(GetFileName(module.Name()).c_str()); } // Save the runtime module path if (m_coreclrPath.empty()) @@ -237,7 +267,7 @@ void CrashInfo::VisitModule(MachOModule& module) if (last != std::string::npos) { m_coreclrPath = module.Name().substr(0, last + 1); - m_runtimeBaseAddress = module.BaseAddress(); + m_processInfo.SetRuntimeBaseAddress(module.BaseAddress()); uint64_t symbolAddress; if (!module.TryLookupSymbol(DACCESS_TABLE_SYMBOL, &symbolAddress)) @@ -251,7 +281,7 @@ void CrashInfo::VisitModule(MachOModule& module) if (module.TryLookupSymbol("DotNetRuntimeInfo", &symbolAddress)) { m_coreclrPath = GetDirectory(module.Name()); - m_runtimeBaseAddress = module.BaseAddress(); + m_processInfo.SetRuntimeBaseAddress(module.BaseAddress()); RuntimeInfo runtimeInfo { }; if (ReadMemory(symbolAddress, &runtimeInfo, sizeof(RuntimeInfo))) @@ -269,7 +299,7 @@ void CrashInfo::VisitModule(MachOModule& module) if (module.TryLookupSymbol("DotNetRuntimeContractDescriptor", &symbolAddress)) { m_coreclrPath = GetDirectory(module.Name()); - m_runtimeBaseAddress = module.BaseAddress(); + m_processInfo.SetRuntimeBaseAddress(module.BaseAddress()); TRACE("Found valid NativeAOT runtime module\n"); } } @@ -305,7 +335,11 @@ void CrashInfo::VisitSegment(MachOModule& module, const segment_command_64& segm assert(end > 0); // Add module memory region if not already on the list - ModuleRegion newModule(regionFlags, start, end, offset, module.Name()); + ModuleRegion newModule(regionFlags, start, end, offset); + if (!newModule.SetFileName(module.Name().c_str())) + { + return; + } std::set::iterator existingModule = m_moduleMappings.find(newModule); if (existingModule == m_moduleMappings.end()) { @@ -314,8 +348,8 @@ void CrashInfo::VisitSegment(MachOModule& module, const segment_command_64& segm newModule.Trace("VisitSegment: "); } // Add this module segment to the module mappings list - m_moduleMappings.insert(newModule); m_cbModuleMappings += newModule.Size(); + m_moduleMappings.insert(Move(newModule)); } else { @@ -330,7 +364,11 @@ void CrashInfo::VisitSegment(MachOModule& module, const segment_command_64& segm uint64_t numberPages = newModule.SizeInPages(); for (size_t p = 0; p < numberPages; p++, start += PAGE_SIZE, offset += PAGE_SIZE) { - ModuleRegion gap(newModule.Flags(), start, start + PAGE_SIZE, offset, newModule.FileName()); + ModuleRegion gap(newModule.Flags(), start, start + PAGE_SIZE, offset); + if (!gap.SetFileName(newModule.FileName())) + { + return; + } const auto& found = m_moduleMappings.find(gap); if (found != m_moduleMappings.end()) @@ -339,8 +377,8 @@ void CrashInfo::VisitSegment(MachOModule& module, const segment_command_64& segm { gap.Trace("VisitSegment: *"); } - m_moduleMappings.insert(gap); m_cbModuleMappings += gap.Size(); + m_moduleMappings.insert(Move(gap)); } } } @@ -380,7 +418,7 @@ CrashInfo::GetMemoryRegionFlags(uint64_t start) // Read raw memory // bool -CrashInfo::ReadProcessMemory(uint64_t address, void* buffer, size_t size, size_t* read) +ProcessInfo::ReadProcessMemory(uint64_t address, void* buffer, size_t size, size_t* read) { assert(buffer != nullptr); assert(read != nullptr); @@ -398,7 +436,7 @@ CrashInfo::ReadProcessMemory(uint64_t address, void* buffer, size_t size, size_t while (bytesLeft > 0) { vm_size_t bytesRead = PAGE_SIZE; - kern_return_t result = ::vm_read_overwrite(Task(), addressAligned, PAGE_SIZE, (vm_address_t)data, &bytesRead); + kern_return_t result = ::vm_read_overwrite(m_task, addressAligned, PAGE_SIZE, (vm_address_t)data, &bytesRead); if (result != KERN_SUCCESS || bytesRead != PAGE_SIZE) { g_readProcessMemoryResult = result; @@ -421,6 +459,12 @@ CrashInfo::ReadProcessMemory(uint64_t address, void* buffer, size_t size, size_t return size == 0 || numberOfBytesRead > 0; } +bool +CrashInfo::ReadProcessMemory(uint64_t address, void* buffer, size_t size, size_t* read) +{ + return m_processInfo.ReadProcessMemory(address, buffer, size, read); +} + const struct dyld_all_image_infos* g_image_infos = nullptr; void diff --git a/src/coreclr/debug/createdump/crashinfounix.cpp b/src/coreclr/debug/createdump/crashinfounix.cpp index 854301453f4824..6645b18f4e3943 100644 --- a/src/coreclr/debug/createdump/crashinfounix.cpp +++ b/src/coreclr/debug/createdump/crashinfounix.cpp @@ -9,187 +9,36 @@ extern CrashInfo* g_crashInfo; -int g_readProcessMemoryErrno = 0; - bool GetProcessInfo(pid_t pid, pid_t* ppid, pid_t* tgid, std::string* name); -bool -CrashInfo::Initialize() -{ - char memPath[128]; - int chars = snprintf(memPath, sizeof(memPath), "/proc/%u/mem", m_pid); - if (chars <= 0 || (size_t)chars >= sizeof(memPath)) - { - printf_error("snprintf failed building /proc//mem name\n"); - return false; - } - - m_fdMem = open(memPath, O_RDONLY); - if (m_fdMem == -1) - { - int err = errno; - const char* message = "Problem accessing memory"; - if (err == EPERM || err == EACCES) - { - message = "The process or container does not have permissions or access"; - } - else if (err == ENOENT) - { - message = "Invalid process id"; - } - printf_error("%s: open(%s) FAILED %s (%d)\n", message, memPath, strerror(err), err); - return false; - } - - CLRConfigNoCache disablePagemapUse = CLRConfigNoCache::Get("DbgDisablePagemapUse", /*noprefix*/ false, &getenv); - DWORD val = 0; - if (disablePagemapUse.IsSet() && disablePagemapUse.TryAsInteger(10, val) && val == 0) - { - TRACE("DbgDisablePagemapUse detected - pagemap file checking is enabled\n"); - char pagemapPath[128]; - chars = snprintf(pagemapPath, sizeof(pagemapPath), "/proc/%u/pagemap", m_pid); - if (chars <= 0 || (size_t)chars >= sizeof(pagemapPath)) - { - printf_error("snprintf failed building /proc//pagemap name\n"); - return false; - } - m_fdPagemap = open(pagemapPath, O_RDONLY); - if (m_fdPagemap == -1) - { - TRACE("open(%s) FAILED %d (%s), will fallback to dumping all memory regions without checking if they are committed\n", pagemapPath, errno, strerror(errno)); - } - } - else - { - m_fdPagemap = -1; - } - - if (!GetProcessInfo(m_pid, &m_ppid, &m_tgid, &m_name)) - { - return false; - } - - m_canUseProcVmReadSyscall = true; - 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); - } - } - if (m_fdMem != -1) - { - close(m_fdMem); - m_fdMem = -1; - } - if (m_fdPagemap != -1) - { - close(m_fdPagemap); - m_fdPagemap = -1; - } + // Nothing to clean up on linux } -// -// Suspends all the threads and creating a list of them. Should be the before gathering any info about the process. -// bool -CrashInfo::EnumerateAndSuspendThreads() +CrashInfo::CopyDumpWriterRegions( + DynamicArray& moduleMappings, + DynamicArray& dumpRegions) const { - char taskPath[128]; - int chars = snprintf(taskPath, sizeof(taskPath), "/proc/%u/task", m_pid); - if (chars <= 0 || (size_t)chars >= sizeof(taskPath)) - { - printf_error("snprintf failed building /proc//task\n"); - return false; - } - - DIR* taskDir = opendir(taskPath); - if (taskDir == nullptr) - { - printf_error("Problem enumerating threads: opendir(%s) FAILED %s (%d)\n", taskPath, strerror(errno), errno); - return false; - } - - struct dirent* entry; - while ((entry = readdir(taskDir)) != nullptr) + for (const ModuleRegion& mapping : m_moduleMappings) { - pid_t tid = static_cast(strtol(entry->d_name, nullptr, 10)); - if (tid != 0) + ModuleRegion moduleMapping(static_cast(mapping)); + if (!moduleMapping.SetFileName(mapping.FileName()) || !moduleMappings.Add(Move(moduleMapping))) { - // 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 - { - printf_error("Problem suspending thread: ptrace(ATTACH, %d) FAILED %s (%d)\n", tid, strerror(errno), errno); - // If the ptrace on a thread that has already terminated, skip/ignore - if (errno == ESRCH && tid != CrashThread()) - { - continue; - } - closedir(taskDir); - return false; - } - // Add to the list of threads - ThreadInfo* thread = new ThreadInfo(*this, tid); - m_threads.push_back(thread); + return false; } } - closedir(taskDir); - return true; -} - -// -// Get the auxv entries to use and add to the core dump -// -bool -CrashInfo::GetAuxvEntries() -{ - char auxvPath[128]; - int chars = snprintf(auxvPath, sizeof(auxvPath), "/proc/%u/auxv", m_pid); - if (chars <= 0 || (size_t)chars >= sizeof(auxvPath)) - { - printf_error("snprintf failed building /proc//auxv\n"); - return false; - } - int fd = open(auxvPath, O_RDONLY, 0); - if (fd == -1) - { - printf_error("Problem reading aux info: open(%s) FAILED %s (%d)\n", auxvPath, strerror(errno), errno); - return false; - } - bool result = false; - elf_aux_entry auxvEntry; - - while (read(fd, &auxvEntry, sizeof(elf_aux_entry)) == sizeof(elf_aux_entry)) + for (const MemoryRegion& region : m_memoryRegions) { - m_auxvEntries.push_back(auxvEntry); - if (auxvEntry.a_type == AT_NULL) - { - break; - } - if (auxvEntry.a_type < AT_MAX) + if (!dumpRegions.Add(region)) { - 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; + return false; } } - - close(fd); - return result; + return true; } // @@ -198,124 +47,7 @@ CrashInfo::GetAuxvEntries() bool CrashInfo::EnumerateMemoryRegions() { - // 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/%u/maps", m_pid); - if (chars <= 0 || (size_t)chars >= sizeof(mapPath)) - { - printf_error("snprintf failed building /proc//maps\n"); - return false; - } - FILE* mapsFile = fopen(mapPath, "rb"); - if (mapsFile == nullptr) - { - printf_error("Problem reading maps file: fopen(%s) FAILED %s (%d)\n", mapPath, strerror(errno), 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 %m[^\n]\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; - } - ModuleRegion moduleRegion(regionFlags, start, end, offset, moduleName); - - if (moduleName != nullptr && *moduleName == '/') - { - // Don't add files that don't exists anymore especially /memfd:doublemapper. - size_t last = moduleRegion.FileName().rfind(" (deleted)"); - if (last == std::string::npos) - { - m_moduleMappings.insert(moduleRegion); - m_cbModuleMappings += moduleRegion.Size(); - } - else - { - m_otherMappings.insert(moduleRegion); - } - } - else - { - m_otherMappings.insert(moduleRegion); - } - if (linuxGateAddress != nullptr && reinterpret_cast(start) == linuxGateAddress) - { - InsertMemoryRegion(moduleRegion); - } - free(moduleName); - free(permissions); - } - } - - if (g_diagnostics) - { - TRACE("Module mappings (%06" PRIx64 "):\n", m_cbModuleMappings / PAGE_SIZE); - for (const ModuleRegion& 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); - + // On unix, this is done by ProcessInfo, so we don't need to do anything here. return true; } @@ -325,9 +57,9 @@ CrashInfo::EnumerateMemoryRegions() bool CrashInfo::GetDSOInfo() { - Phdr* phdrAddr = reinterpret_cast(m_auxvValues[AT_PHDR]); - int phnum = m_auxvValues[AT_PHNUM]; - assert(m_auxvValues[AT_PHENT] == sizeof(Phdr)); + Phdr* phdrAddr = reinterpret_cast(m_processInfo.AuxvValue(AT_PHDR)); + int phnum = m_processInfo.AuxvValue(AT_PHNUM); + assert(m_processInfo.AuxvValue(AT_PHENT) == sizeof(Phdr)); assert(phnum != PN_XNUM); return EnumerateElfInfo(phdrAddr, phnum); } @@ -338,7 +70,7 @@ CrashInfo::GetDSOInfo() void CrashInfo::VisitModule(uint64_t baseAddress, std::string& moduleName) { - if (baseAddress == 0 || baseAddress == m_auxvValues[AT_SYSINFO_EHDR]) { + if (baseAddress == 0 || baseAddress == m_processInfo.AuxvValue(AT_SYSINFO_EHDR)) { return; } // For reasons unknown the main app singlefile module name is empty in the DSO. This replaces @@ -360,7 +92,7 @@ CrashInfo::VisitModule(uint64_t baseAddress, std::string& moduleName) if (last != std::string::npos) { m_coreclrPath = moduleName.substr(0, last + 1); - m_runtimeBaseAddress = baseAddress; + m_processInfo.SetRuntimeBaseAddress(baseAddress); // Now populate the elfreader with the runtime module info and // lookup the DAC table symbol to ensure that all the memory @@ -382,7 +114,7 @@ CrashInfo::VisitModule(uint64_t baseAddress, std::string& moduleName) if (TryLookupSymbol("DotNetRuntimeInfo", &symbolOffset)) { m_coreclrPath = GetDirectory(moduleName); - m_runtimeBaseAddress = baseAddress; + m_processInfo.SetRuntimeBaseAddress(baseAddress); // explicit initialization for old gcc support; instead of just runtimeInfo { } RuntimeInfo runtimeInfo { .Signature = { }, .Version = 0, .RuntimeModuleIndex = { }, .DacModuleIndex = { }, .DbiModuleIndex = { }, .RuntimeVersion = { } }; @@ -404,7 +136,7 @@ CrashInfo::VisitModule(uint64_t baseAddress, std::string& moduleName) if (TryLookupSymbol("DotNetRuntimeContractDescriptor", &symbolOffset)) { m_coreclrPath = GetDirectory(moduleName); - m_runtimeBaseAddress = baseAddress; + m_processInfo.SetRuntimeBaseAddress(baseAddress); TRACE("Found valid NativeAOT runtime module\n"); } } @@ -502,48 +234,7 @@ CrashInfo::GetMemoryRegionFlags(uint64_t start) bool CrashInfo::ReadProcessMemory(uint64_t address, void* buffer, size_t size, size_t* read) { - assert(buffer != nullptr); - assert(read != nullptr); - *read = 0; - -#ifdef HAVE_PROCESS_VM_READV - if (m_canUseProcVmReadSyscall) - { - iovec local{ buffer, size }; - iovec remote{ (void*)address, size }; - *read = process_vm_readv(m_pid, &local, 1, &remote, 1, 0); - } - - if (!m_canUseProcVmReadSyscall || (*read == (size_t)-1 && (errno == EPERM || errno == ENOSYS))) -#endif - { - // If we've failed, avoid going through expensive syscalls - // After all, the use of process_vm_readv is largely as a - // performance optimization. - m_canUseProcVmReadSyscall = false; - assert(m_fdMem != -1); -#ifdef TARGET_ARM64 - // Android's heap allocator (scudo) uses ARM64 Top-Byte Ignore (TBI) for memory tagging. - // pread on /proc//mem treats the offset as a file position, not a virtual address, - // so the kernel does not apply TBI — tagged pointers cause EINVAL. - // See https://www.kernel.org/doc/html/latest/arch/arm64/tagged-address-abi.html - // - // Currently only Android allocators set a non-zero top byte, so on other ARM64 Linux - // configurations this is a no-op. However, any future use of TBI tagging (e.g., ARM MTE) - // on other Linux distros would hit the same issue. - address &= 0x00FFFFFFFFFFFFFFULL; -#endif - *read = pread(m_fdMem, buffer, size, (off_t)address); - } - - if (*read == (size_t)-1) - { - // Preserve errno for the ELF dump writer call - g_readProcessMemoryErrno = errno; - TRACE_VERBOSE("ReadProcessMemory FAILED addr: %" PRIA PRIx64 " size: %zu error: %s (%d)\n", address, size, strerror(g_readProcessMemoryErrno), g_readProcessMemoryErrno); - return false; - } - return true; + return m_processInfo.ReadProcessMemory(address, buffer, size, read); } // diff --git a/src/coreclr/debug/createdump/createdump.h b/src/coreclr/debug/createdump/createdump.h index d647ec49502d1e..293441ad5dcc6e 100644 --- a/src/coreclr/debug/createdump/createdump.h +++ b/src/coreclr/debug/createdump/createdump.h @@ -6,21 +6,8 @@ #define ___in _SAL1_Source_(__in, (), _In_) #define ___out _SAL1_Source_(__out, (), _Out_) -extern bool g_diagnostics; -extern bool g_diagnosticsVerbose; - #include -#ifdef HOST_UNIX -extern void trace_printf(const char* format, ...) MINIPAL_ATTR_FORMAT_PRINTF(1, 2); -extern void trace_verbose_printf(const char* format, ...) MINIPAL_ATTR_FORMAT_PRINTF(1, 2); -#define TRACE(args...) trace_printf(args) -#define TRACE_VERBOSE(args...) trace_verbose_printf(args) -#else -#define TRACE(args, ...) -#define TRACE_VERBOSE(args, ...) -#endif - // Keep in sync with the definitions in dbgutil.cpp and daccess.h #define DACCESS_TABLE_SYMBOL "g_dacTable" @@ -104,61 +91,25 @@ typedef int T_CONTEXT; #include #include -enum class DumpType -{ - Mini, - Heap, - Triage, - Full -}; - -enum class AppModelType -{ - Normal, - SingleFile, - NativeAOT -}; - -typedef struct -{ - const char* DumpPathTemplate; - enum DumpType DumpType; - enum AppModelType AppModel; - bool CreateDump; - bool CrashReport; - int Pid; - int CrashThread; - int Signal; - int SignalCode; - int SignalErrno; - uint64_t SignalAddress; - uint64_t ExceptionRecord; -} CreateDumpOptions; - +#include "shared/createdumpcore.h" #ifdef HOST_UNIX #ifdef __APPLE__ #include #include +#include "../dbgutil/machoreader.h" +#else +#include "../dbgutil/elfreader.h" #endif #include "moduleinfo.h" #include "datatarget.h" #include "stackframe.h" #include "threadinfo.h" -#include "memoryregion.h" #include "crashinfo.h" #include "crashreportwriter.h" -#include "dumpwriter.h" +#include "shared/dumpwriter.h" #include "runtimeinfo.h" -#include "specialdiaginfo.h" #endif -#ifndef MAX_LONGPATH -#define MAX_LONGPATH 1024 -#endif - -extern bool CreateDump(const CreateDumpOptions& options); -extern bool FormatDumpName(std::string& name, const char* pattern, const char* exename, int pid); -extern const char* GetDumpTypeString(DumpType dumpType); extern MINIDUMP_TYPE GetMiniDumpType(DumpType dumpType); #ifdef HOST_WINDOWS @@ -166,6 +117,4 @@ extern std::string GetLastErrorString(); extern DWORD GetTempPathWrapper(IN DWORD nBufferLength, OUT LPSTR lpBuffer); #else #define GetTempPathWrapper GetTempPathA -#endif -extern void printf_status(const char* format, ...) MINIPAL_ATTR_FORMAT_PRINTF(1, 2); -extern void printf_error(const char* format, ...) MINIPAL_ATTR_FORMAT_PRINTF(1, 2); +#endif \ No newline at end of file diff --git a/src/coreclr/debug/createdump/createdumpmain.cpp b/src/coreclr/debug/createdump/createdumpmain.cpp index 694a3af126c6da..fc0fe94a76ecd8 100644 --- a/src/coreclr/debug/createdump/createdumpmain.cpp +++ b/src/coreclr/debug/createdump/createdumpmain.cpp @@ -4,46 +4,30 @@ #include "createdump.h" #include "minipal/time.h" -#ifdef HOST_WINDOWS -#define DEFAULT_DUMP_PATH "%TEMP%\\" -#define DEFAULT_DUMP_TEMPLATE "dump.%p.dmp" -#else -#define DEFAULT_DUMP_PATH "/tmp/" -#define DEFAULT_DUMP_TEMPLATE "coredump.%p" -#endif - -const char* g_help = "createdump [options]\n" -"createdump writes a dump of its parent process; a target PID cannot be specified.\n" -"-f, --name - dump path and file name. The default is '" DEFAULT_DUMP_PATH DEFAULT_DUMP_TEMPLATE "'. These specifiers are substituted with following values:\n" -" %p PID of dumped process.\n" -" %e The process executable filename.\n" -" %h Hostname return by gethostname().\n" -" %t Time of dump, expressed as seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).\n" -"-n, --normal - create minidump.\n" -"-h, --withheap - create minidump with heap (default).\n" -"-t, --triage - create triage minidump.\n" -"-u, --full - create full core dump.\n" -"-d, --diag - enable diagnostic messages.\n" -"-v, --verbose - enable verbose diagnostic messages.\n" -"-l, --logtofile - file path and name to log diagnostic messages.\n" -#ifdef HOST_UNIX -"--crashreport - write crash report file (dump file path + .crashreport.json).\n" -"--crashreportonly - write crash report file only (no dump).\n" -"--crashthread - the thread id of the crashing thread.\n" -"--signal - the signal code of the crash.\n" -"--singlefile - single-file app model.\n" -"--nativeaot - native AOT app model.\n" -#endif -; - - -FILE *g_logfile = nullptr; -FILE *g_stdout = stdout; -bool g_diagnostics = false; -bool g_diagnosticsVerbose = false; uint64_t g_ticksPerMS = 0; uint64_t g_startTime = 0; +void print_trace_timestamp() +{ + fprintf(g_stdout, "%08" PRIx64 " ", minipal_hires_ticks() / g_ticksPerMS); +} + +bool GetDefaultDumpPath(char* buffer, size_t bufferSize) +{ + if (bufferSize > MAXDWORD || GetTempPathWrapper(static_cast(bufferSize), buffer) == 0) + { + printf_error("GetTempPath failed\n"); + return false; + } + int exitCode = strcat_s(buffer, bufferSize, DEFAULT_DUMP_TEMPLATE); + if (exitCode != 0) + { + printf_error("strcat_s failed (%d)", exitCode); + return false; + } + return true; +} + // // Common entry point // @@ -58,153 +42,27 @@ int createdump_main(const int argc, const char* argv[]) fgetc(stdin); } #endif - CreateDumpOptions options; - options.DumpType = DumpType::Heap; - options.DumpPathTemplate = nullptr; - options.AppModel = AppModelType::Normal; - options.CrashReport = false; - options.CreateDump = true; - options.Signal = 0; - options.CrashThread = 0; -#ifdef HOST_UNIX - options.Pid = static_cast(getppid()); -#else - options.Pid = 0; -#endif - options.SignalCode = 0; - options.SignalErrno = 0; - options.SignalAddress = 0; - options.ExceptionRecord = 0; - bool help = false; - int exitCode = 0; - - // Parse the command line options - argv++; - for (int i = 1; i < argc; i++) - { - if (*argv != nullptr) - { - if ((strcmp(*argv, "-f") == 0) || (strcmp(*argv, "--name") == 0)) - { - options.DumpPathTemplate = *++argv; - } - else if ((strcmp(*argv, "-n") == 0) || (strcmp(*argv, "--normal") == 0)) - { - options.DumpType = DumpType::Mini; - } - else if ((strcmp(*argv, "-h") == 0) || (strcmp(*argv, "--withheap") == 0)) - { - options.DumpType = DumpType::Heap; - } - else if ((strcmp(*argv, "-t") == 0) || (strcmp(*argv, "--triage") == 0)) - { - options.DumpType = DumpType::Triage; - } - else if ((strcmp(*argv, "-u") == 0) || (strcmp(*argv, "--full") == 0)) - { - options.DumpType = DumpType::Full; - } -#ifdef HOST_UNIX - else if (strcmp(*argv, "--crashreport") == 0) - { - options.CrashReport = true; - } - else if (strcmp(*argv, "--crashreportonly") == 0) - { - options.CrashReport = true; - options.CreateDump = false; - } - else if (strcmp(*argv, "--crashthread") == 0) - { - options.CrashThread = atoi(*++argv); - } - else if (strcmp(*argv, "--signal") == 0) - { - options.Signal = atoi(*++argv); - } - else if (strcmp(*argv, "--singlefile") == 0) - { - options.AppModel = AppModelType::SingleFile; - } - else if (strcmp(*argv, "--nativeaot") == 0) - { - options.AppModel = AppModelType::NativeAOT; - } - else if (strcmp(*argv, "--code") == 0) - { - options.SignalCode = atoi(*++argv); - } - else if (strcmp(*argv, "--errno") == 0) - { - options.SignalErrno = atoi(*++argv); - } - else if (strcmp(*argv, "--address") == 0) - { - options.SignalAddress = atoll(*++argv); - } - else if (strcmp(*argv, "--exception-record") == 0) - { - options.ExceptionRecord = atoll(*++argv); - } -#endif - else if ((strcmp(*argv, "-d") == 0) || (strcmp(*argv, "--diag") == 0)) - { - g_diagnostics = true; - } - else if ((strcmp(*argv, "-v") == 0) || (strcmp(*argv, "--verbose") == 0)) - { - g_diagnostics = true; - g_diagnosticsVerbose = true; - } - else if ((strcmp(*argv, "-l") == 0) || (strcmp(*argv, "--logtofile") == 0)) - { - const char* logFilePath = *++argv; - g_logfile = fopen(logFilePath, "w"); - if (g_logfile == nullptr) - { - printf_error("Can not create log file '%s': %s (%d)\n", logFilePath, strerror(errno), errno); - return errno; - } - g_stdout = g_logfile; - } - else if ((strcmp(*argv, "-?") == 0) || (strcmp(*argv, "--help") == 0)) - { - help = true; - } - else - { - printf_error("Unrecognized argument '%s'\n", *argv); - return -1; - } - argv++; - } - } - if (help) + CreateDumpOptions options; + int exitCode = ParseCreateDumpOptions(argc, (char**)argv, &options); + if (exitCode != 0) { - printf_error("%s", g_help); - return -1; + return exitCode; } g_ticksPerMS = minipal_hires_tick_frequency() / 1000UL; g_startTime = minipal_hires_ticks(); TRACE("TickFrequency: %" PRIu64 " ticks per ms\n", g_ticksPerMS); - AStringHolder tmpPath = new char[MAX_LONGPATH]; + char defaultDumpPath[MAX_LONGPATH]; if (options.DumpPathTemplate == nullptr) { - if (GetTempPathWrapper(MAX_LONGPATH, tmpPath) == 0) + if (!GetDefaultDumpPath(defaultDumpPath, MAX_LONGPATH)) { - printf_error("GetTempPath failed\n"); + printf_error("Could not get default dump path\n"); return -1; } - exitCode = strcat_s(tmpPath, MAX_LONGPATH, DEFAULT_DUMP_TEMPLATE); - if (exitCode != 0) - { - printf_error("strcat_s failed (%d)", exitCode); - return exitCode; - } - options.DumpPathTemplate = tmpPath; + options.DumpPathTemplate = defaultDumpPath; } if (CreateDump(options)) @@ -228,24 +86,6 @@ int createdump_main(const int argc, const char* argv[]) return exitCode; } -const char* -GetDumpTypeString(DumpType dumpType) -{ - switch (dumpType) - { - case DumpType::Mini: - return "minidump"; - case DumpType::Heap: - return "minidump with heap"; - case DumpType::Triage: - return "triage minidump"; - case DumpType::Full: - return "full dump"; - default: - return "unknown"; - } -} - MINIDUMP_TYPE GetMiniDumpType(DumpType dumpType) { @@ -285,80 +125,8 @@ GetMiniDumpType(DumpType dumpType) } } -void -printf_status(const char* format, ...) -{ - va_list args; - va_start(args, format); - if (g_logfile == nullptr) - { - fprintf(g_stdout, "[createdump] "); - } - vfprintf(g_stdout, format, args); - fflush(g_stdout); - va_end(args); -} - -void -printf_error(const char* format, ...) -{ - va_list args; - va_start(args, format); - - // Log error message to file - if (g_logfile != nullptr) - { - va_list args2; - va_copy(args2, args); - vfprintf(g_logfile, format, args2); - fflush(g_logfile); - } - // Always print errors on stderr - fprintf(stderr, "[createdump] "); - vfprintf(stderr, format, args); - fflush(stderr); - va_end(args); -} - #ifdef HOST_UNIX -static void -trace_prefix(const char* format, va_list args) -{ - // Only add this prefix if logging to the console - if (g_logfile == nullptr) - { - fprintf(g_stdout, "[createdump] "); - } - fprintf(g_stdout, "%08" PRIx64 " ", minipal_hires_ticks() / g_ticksPerMS); - vfprintf(g_stdout, format, args); - fflush(g_stdout); -} - -void -trace_printf(const char* format, ...) -{ - if (g_diagnostics) - { - va_list args; - va_start(args, format); - trace_prefix(format, args); - va_end(args); - } -} - -void -trace_verbose_printf(const char* format, ...) -{ - if (g_diagnosticsVerbose) - { - va_list args; - va_start(args, format); - trace_prefix(format, args); - va_end(args); - } -} - void CrashInfo::Trace(const char* format, ...) { diff --git a/src/coreclr/debug/createdump/createdumpunix.cpp b/src/coreclr/debug/createdump/createdumpunix.cpp index 44a8c6a154ce66..a26aa5e528fa0a 100644 --- a/src/coreclr/debug/createdump/createdumpunix.cpp +++ b/src/coreclr/debug/createdump/createdumpunix.cpp @@ -4,45 +4,39 @@ #include "createdump.h" #include -#if defined(__arm__) || defined(__aarch64__) || defined(__loongarch64) || defined(__riscv) -long g_pageSize = 0; -#endif - // // The Linux/MacOS create dump code // +// There is a simplified version of this CreateDump function available in nativeaot_createdump_main.cpp. +// If changes are made to this function, consider updating the other one. bool CreateDump(const CreateDumpOptions& options) { - ReleaseHolder crashInfo{ new CrashInfo(options) }; - DumpWriter dumpWriter(*crashInfo); + ProcessInfo processInfo(options); + ReleaseHolder crashInfo{ new CrashInfo(options, processInfo) }; std::string dumpPath; + bool processInitialized = false; bool result = false; // Initialize PAGE_SIZE -#if defined(__arm__) || defined(__aarch64__) || defined(__loongarch64) || defined(__riscv) +#ifdef CREATEDUMP_RUNTIME_PAGE_SIZE g_pageSize = minipal_getpagesize(); #endif TRACE("PAGE_SIZE %lu\n", (unsigned long)PAGE_SIZE); - if (options.CrashReport && (options.AppModel == AppModelType::SingleFile || options.AppModel == AppModelType::NativeAOT)) + if (!ValidateDumpOptions(&options)) { - printf_error("The app model does not support crash report generation\n"); goto exit; } - if (options.DumpType != DumpType::Full && options.AppModel == AppModelType::NativeAOT) + if (!processInfo.Initialize()) { - printf_error("The app model only supports full dump generation\n"); goto exit; } - // Initialize the crash info - if (!crashInfo->Initialize()) - { - goto exit; - } - printf_status("Gathering state for process %d %s\n", options.Pid, crashInfo->Name().c_str()); + processInitialized = true; + + printf_status("Gathering state for process %d %s\n", options.Pid, crashInfo->Name()); if (options.Signal != 0 || options.CrashThread != 0) { @@ -50,20 +44,50 @@ CreateDump(const CreateDumpOptions& options) } // Suspend all the threads in the target process and build the list of threads - if (!crashInfo->EnumerateAndSuspendThreads()) + if (!processInfo.EnumerateAndSuspendThreads()) + { + goto exit; + } + + // The following three steps gather all the info about the process, threads (registers, etc.) and memory regions + if (!processInfo.GatherCrashInfo(crashInfo->GetDumpRegionStore())) + { + goto exit; + } + if (!crashInfo->PopulateFromProcessInfo()) { goto exit; } - // Gather all the info about the process, threads (registers, etc.) and memory regions if (!crashInfo->GatherCrashInfo(options.DumpType)) { goto exit; } + + // Add the special (fake) memory region for the special diagnostics info. Use constructor that doesn't assert PAGE_SIZE alignment. + if (!AddSpecialDiagInfoRegion(crashInfo->GetDumpRegionStore())) + { + goto exit; + } + + // Determine which memory regions should be included in the dump based on the dump type + if (!processInfo.SelectDumpRegions(crashInfo->GetDumpRegionStore(), options.DumpType)) + { + goto exit; + } + + if (options.DumpType != DumpType::Full) + { + crashInfo->AddThreadStacks(); + } + + char pathName[MAX_DUMP_PATH]; // Format the dump pattern template now that the process name on MacOS has been obtained - if (!FormatDumpName(dumpPath, options.DumpPathTemplate, crashInfo->Name().c_str(), options.Pid)) + if (!FormatDumpName(pathName, sizeof(pathName), options.DumpPathTemplate, crashInfo->Name(), options.Pid)) { goto exit; } + + dumpPath = pathName; // Write the crash report json file if enabled if (options.CrashReport) { @@ -82,38 +106,31 @@ CreateDump(const CreateDumpOptions& options) printf_status("Writing %s to file %s\n", GetDumpTypeString(options.DumpType), dumpPath.c_str()); - // Write the actual dump file - if (!dumpWriter.OpenDump(dumpPath.c_str())) +#ifdef __APPLE__ + DumpWriter dumpWriter(*crashInfo); +#else + DynamicArray moduleMappings; + DynamicArray dumpRegions; + if (!crashInfo->CopyDumpWriterRegions(moduleMappings, dumpRegions)) { goto exit; } - if (!dumpWriter.WriteDump()) + DumpWriter dumpWriter(processInfo, moduleMappings, dumpRegions); +#endif + // Write the actual dump file + if (!dumpWriter.OpenAndWriteDump(dumpPath.c_str())) { - printf_error("Writing dump FAILED\n"); - - // Delete the partial dump file on error - remove(dumpPath.c_str()); goto exit; } } result = true; exit: - if (kill(options.Pid, 0) == 0) + LogProcessStatus(options.Pid); + crashInfo->CleanupAndResumeProcess(); + if (processInitialized) { - printf_status("Target process is alive\n"); + processInfo.CleanupAndResumeProcess(); } - else - { - int err = errno; - if (err == ESRCH) - { - printf_error("Target process terminated\n"); - } - else - { - printf_error("kill(%d, 0) FAILED %s (%d)\n", options.Pid, strerror(err), err); - } - } - crashInfo->CleanupAndResumeProcess(); + return result; } diff --git a/src/coreclr/debug/createdump/createdumpwindows.cpp b/src/coreclr/debug/createdump/createdumpwindows.cpp index 652b8ab048e525..34a23f53073daf 100644 --- a/src/coreclr/debug/createdump/createdumpwindows.cpp +++ b/src/coreclr/debug/createdump/createdumpwindows.cpp @@ -28,6 +28,7 @@ CreateDump(const CreateDumpOptions& options) _ASSERTE(!options.CrashReport); AStringHolder pszName = new char[MAX_LONGPATH + 1]; + char pathName[MAX_DUMP_PATH]; std::string dumpPath; // On Windows, createdump is restricted for security reasons to only the .NET process (parent process) that launched createdump @@ -51,10 +52,11 @@ CreateDump(const CreateDumpOptions& options) printf_error("Get process name FAILED - %s\n", GetLastErrorString().c_str()); goto exit; } - if (!FormatDumpName(dumpPath, options.DumpPathTemplate, pszName, pid)) + if (!FormatDumpName(pathName, sizeof(pathName), options.DumpPathTemplate, pszName, pid)) { goto exit; } + dumpPath = pathName; printf_status("Writing %s for process %d to file %s\n", GetDumpTypeString(options.DumpType), pid, dumpPath.c_str()); hFile = CreateFileA(dumpPath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); diff --git a/src/coreclr/debug/createdump/dumpwritermacho.h b/src/coreclr/debug/createdump/dumpwritermacho.h deleted file mode 100644 index 866ef7361d5fdb..00000000000000 --- a/src/coreclr/debug/createdump/dumpwritermacho.h +++ /dev/null @@ -1,54 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -struct ThreadCommand -{ - thread_command command; - uint32_t gpflavor; - uint32_t gpcount; -#if defined(__x86_64__) - x86_thread_state64_t gpregisters; -#elif defined(__aarch64__) - arm_thread_state64_t gpregisters; -#endif - uint32_t fpflavor; - uint32_t fpcount; -#if defined(__x86_64__) - x86_float_state64_t fpregisters; -#elif defined(__aarch64__) - arm_neon_state64_t fpregisters; -#endif -}; - -class DumpWriter -{ -private: - int m_fd; - CrashInfo& m_crashInfo; - - std::vector m_segmentLoadCommands; - std::vector m_threadLoadCommands; - note_command m_processMetadataNote{}; - std::string m_processMetadata; - BYTE m_tempBuffer[0x4000]; - - // no public copy constructor - DumpWriter(const DumpWriter&) = delete; - void operator=(const DumpWriter&) = delete; - -public: - DumpWriter(CrashInfo& crashInfo); - virtual ~DumpWriter(); - bool OpenDump(const char* dumpFileName); - bool WriteDump(); - static bool WriteData(int fd, const void* buffer, size_t length); - -private: - bool WriteDiagInfo(size_t size); - void BuildProcessMetadataNote(); - void BuildSegmentLoadCommands(); - void BuildThreadLoadCommands(); - bool WriteHeader(uint64_t* pFileOffset); - bool WriteSegments(); - bool WriteData(const void* buffer, size_t length) { return WriteData(m_fd, buffer, length); } -}; diff --git a/src/coreclr/debug/createdump/shared/coreutils.h b/src/coreclr/debug/createdump/shared/coreutils.h new file mode 100644 index 00000000000000..c9df57c1c7513b --- /dev/null +++ b/src/coreclr/debug/createdump/shared/coreutils.h @@ -0,0 +1,253 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef COREUTILS_H +#define COREUTILS_H + +#include + +#define INITIAL_CAPACITY 64 + +// We have an OwnedString and a DynamicArray class to avoid using the standard library containers. + +template +T&& Move(T& value) noexcept +{ + return static_cast(value); +} + +class OwnedString +{ +private: + char* m_value; + size_t m_length; +public: + OwnedString() noexcept : + m_value(nullptr), + m_length(0) + { + } + + ~OwnedString() noexcept + { + free(m_value); + } + + OwnedString(OwnedString&& other) noexcept : + m_value(other.m_value), + m_length(other.m_length) + { + other.m_value = nullptr; + other.m_length = 0; + } + + OwnedString& operator=(OwnedString&& other) noexcept + { + if (this != &other) + { + free(m_value); + + m_value = other.m_value; + m_length = other.m_length; + + other.m_value = nullptr; + other.m_length = 0; + } + + return *this; + } + + OwnedString(const OwnedString&) = delete; + OwnedString& operator=(const OwnedString&) = delete; + + void TakeOwnership(char* value) noexcept + { + free(m_value); + + m_value = value; + m_length = value != nullptr ? strlen(value) : 0; + } + + bool Assign(const char* value) noexcept + { + size_t length = value != nullptr ? strlen(value) : 0; + + char* copy = static_cast(malloc(length + 1)); + if (copy == nullptr) + { + return false; + } + + if (length != 0) + { + memcpy(copy, value, length); + } + + copy[length] = '\0'; + + free(m_value); + m_value = copy; + m_length = length; + return true; + } + + const char* CStr() const noexcept + { + return m_value != nullptr ? m_value : ""; + } + + size_t Length() const noexcept + { + return m_length; + } + + bool IsEmpty() const noexcept + { + return m_length == 0; + } +}; + +template +class DynamicArray +{ +private: + T* m_items = nullptr; + size_t m_count = 0; + size_t m_capacity = 0; + + bool Grow() noexcept + { + if (m_capacity > SIZE_MAX / 2) + { + return false; + } + + size_t newCapacity = m_capacity == 0 ? INITIAL_CAPACITY : m_capacity * 2; + + T* items = new (std::nothrow) T[newCapacity]; + + if (items == nullptr) + { + return false; + } + + for (size_t index = 0; index < m_count; index++) + { + items[index] = Move(m_items[index]); + } + + delete[] m_items; + m_items = items; + m_capacity = newCapacity; + return true; + } + +public: + DynamicArray() noexcept = default; + + ~DynamicArray() noexcept + { + delete[] m_items; + } + + DynamicArray(const DynamicArray&) = delete; + DynamicArray& operator=(const DynamicArray&) = delete; + + DynamicArray(DynamicArray&& other) noexcept : + m_items(other.m_items), + m_count(other.m_count), + m_capacity(other.m_capacity) + { + other.m_items = nullptr; + other.m_count = 0; + other.m_capacity = 0; + } + + DynamicArray& operator=(DynamicArray&& other) noexcept + { + if (this != &other) + { + delete[] m_items; + + m_items = other.m_items; + m_count = other.m_count; + m_capacity = other.m_capacity; + + other.m_items = nullptr; + other.m_count = 0; + other.m_capacity = 0; + } + return *this; + } + + bool Add(const T& item) noexcept + { + if (m_count == m_capacity && !Grow()) + { + return false; + } + + m_items[m_count++] = item; + return true; + } + + bool Add(T&& item) noexcept + { + if (m_count == m_capacity && !Grow()) + { + return false; + } + + m_items[m_count++] = Move(item); + return true; + } + + T& operator[](size_t index) noexcept + { + assert(index < m_count); + return m_items[index]; + } + + const T& operator[](size_t index) const noexcept + { + assert(index < m_count); + return m_items[index]; + } + + T* Data() noexcept + { + return m_items; + } + + T* begin() noexcept + { + return m_items; + } + + T* end() noexcept + { + return m_items != nullptr ? m_items + m_count : nullptr; + } + + const T* begin() const noexcept + { + return m_items; + } + + const T* end() const noexcept + { + return m_items != nullptr ? m_items + m_count : nullptr; + } + + bool empty() const noexcept + { + return m_count == 0; + } + + size_t Count() const noexcept + { + return m_count; + } +}; + +#endif // COREUTILS_H \ No newline at end of file diff --git a/src/coreclr/debug/createdump/shared/crashinfocore.cpp b/src/coreclr/debug/createdump/shared/crashinfocore.cpp new file mode 100644 index 00000000000000..bfee2daa191096 --- /dev/null +++ b/src/coreclr/debug/createdump/shared/crashinfocore.cpp @@ -0,0 +1,206 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "createdumpcore.h" + +bool ProcessInfo::PageCanBeRead(uint64_t start) +{ + uint8_t buffer[1]; + size_t read; + return ReadProcessMemory(start, buffer, 1, &read); +} + +// +// Check the page is really used by the application before adding it to the dump +// On some kernels reading a region from createdump results in committing this region in the parent application +// That leads to OOM in container environment and unnecesserally increses the size of the dump file +// However this is an optimization: if it fails we still try to add the page to the dump +// +bool ProcessInfo::PageMappedToPhysicalMemory(uint64_t start) +{ + #if !defined(__linux__) + // this check has not been implemented yet for other unix systems + return true; + #else + // https://www.kernel.org/doc/Documentation/vm/pagemap.txt + if (m_fdPagemap == -1) + { + // Weren't able to open pagemap file, so don't run this check + // Expected on kernels 4.0 and 4.1 as we need CAP_SYS_ADMIN to open /proc/pid/pagemap + // On kernels after 4.2 we only need PTRACE_MODE_READ_FSCREDS as we are ok with zeroed PFNs + return true; + } + + uint64_t pagemapOffset = (start / PAGE_SIZE) * sizeof(uint64_t); + uint64_t seekResult = lseek(m_fdPagemap, (off_t) pagemapOffset, SEEK_SET); + if (seekResult != pagemapOffset) + { + int seekErrno = errno; + TRACE("Seeking in pagemap file FAILED, addr: %" PRIA PRIx64 ", pagemap offset: %" PRIA PRIx64 ", ERRNO %d: %s\n", start, pagemapOffset, seekErrno, strerror(seekErrno)); + return true; + } + uint64_t value; + size_t readResult = read(m_fdPagemap, (void*)&value, sizeof(value)); + if (readResult == (size_t) -1) + { + int readErrno = errno; + TRACE("Reading of pagemap file FAILED, addr: %" PRIA PRIx64 ", pagemap offset: %" PRIA PRIx64 ", size: %zu, ERRNO %d: %s\n", start, pagemapOffset, sizeof(value), readErrno, strerror(readErrno)); + return true; + } + + bool is_page_present = (value & ((uint64_t)1 << 63)) != 0; + bool is_page_swapped = (value & ((uint64_t)1 << 62)) != 0; + TRACE_VERBOSE("Pagemap value for %" PRIA PRIx64 ", pagemap offset %" PRIA PRIx64 " is %" PRIA PRIx64 " -> %s\n", start, pagemapOffset, value, is_page_present ? "in memory" : (is_page_swapped ? "in swap" : "NOT in memory")); + return is_page_present || is_page_swapped; + #endif +} + +bool ProcessInfo::SelectDumpRegions(DumpRegionStore& regionStore, DumpType dumpType) +{ + // If full memory dump, include everything regardless of permissions + if (dumpType == DumpType::Full) + { + for (const MemoryRegion& region : m_moduleMappings) + { + if (InsertMemoryRegion(regionStore, region) < 0) + return false; + } + for (const MemoryRegion& region : m_otherMappings) + { + // Don't add uncommitted pages to the full dump + if ((region.Permissions() & (PF_R | PF_W | PF_X)) != 0) + { + if (InsertMemoryRegion(regionStore, region) < 0) + return false; + } + } + } + else + { + // 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 (dumpType == DumpType::Heap) + { + for (const MemoryRegion& region : m_otherMappings) + { + uint32_t permissions = region.Permissions(); +#ifdef __APPLE__ + if (permissions == (PF_R | PF_W)) +#else + if (permissions == (PF_R | PF_W) || permissions == (PF_R | PF_W | PF_X)) +#endif + { + if (InsertMemoryRegion(regionStore, region) < 0) + return false; + } + } + } + } + + return true; +} + +bool ProcessInfo::AddMapping(const MemoryRegion& region) +{ + return m_otherMappings.Add(region); +} + +bool ProcessInfo::AddMapping(const ModuleRegion& region) +{ + ModuleRegion mapping(static_cast(region)); + if (*region.FileName() != '\0' && !mapping.SetFileName(region.FileName())) + { + return false; + } + return m_moduleMappings.Add(Move(mapping)); +} + +bool AddSpecialDiagInfoRegion(DumpRegionStore& regionStore) +{ + MemoryRegion special(PF_R, SpecialDiagInfoAddress, SpecialDiagInfoAddress + SpecialDiagInfoSize, /* offset */ 0); + return regionStore.Insert(&special); +} + +// +// Add a memory region to the list. Returns the number of pages actually added. +// +int ProcessInfo::InsertMemoryRegion(DumpRegionStore& regionStore, const MemoryRegion& memoryRegion) +{ + // Check if the new region overlaps with the previously added ones + MemoryRegion conflictingRegion; + bool hasConflict = regionStore.FindOverlap(memoryRegion.StartAddress(), memoryRegion.EndAddress(), &conflictingRegion); + if (hasConflict && conflictingRegion.Contains(memoryRegion)) + { + // The region is contained in the one we added before + // Nothing to do + return 0; + } + // Go page by page and split the region into valid sub-regions + uint64_t pageStart = memoryRegion.StartAddress(); + uint64_t numberPages = memoryRegion.Size() / PAGE_SIZE; + uint64_t subRegionStart, subRegionEnd; + int pagesAdded = 0; + subRegionStart = subRegionEnd = pageStart; + for (size_t p = 0; p < numberPages; p++, pageStart += PAGE_SIZE) + { + MemoryRegion pageRegion(memoryRegion.Flags(), pageStart, pageStart + PAGE_SIZE); + // avoid searching for conflicts if we know we don't have one + bool pageHasConflicts = hasConflict && + regionStore.FindOverlap(pageRegion.StartAddress(), pageRegion.EndAddress(), &conflictingRegion); + // avoid validating the page if it conflicts: we won't add it in any case + bool pageIsValid = !pageHasConflicts && PageMappedToPhysicalMemory(pageStart) && PageCanBeRead(pageStart); + + if (pageIsValid) + { + subRegionEnd = pageRegion.EndAddress(); + pagesAdded++; + } + else + { + // the next page is not valid thus sub-region is complete + if (subRegionStart != subRegionEnd) + { + MemoryRegion subRegion(memoryRegion.Flags(), subRegionStart, subRegionEnd); + if (!regionStore.Insert(&subRegion)) + { + return -1; + } + } + subRegionStart = subRegionEnd = pageRegion.EndAddress(); + } + } + + // add the last sub-region if it's not empty + if (subRegionStart != subRegionEnd) + { + MemoryRegion subRegion(memoryRegion.Flags(), subRegionStart, subRegionEnd); + if (!regionStore.Insert(&subRegion)) + { + return -1; + } + } + + return pagesAdded; +} + +bool ProcessInfo::GatherCrashInfo(DumpRegionStore& regionStore) +{ + for (ThreadSnapshot& thread : m_threads) + { + if (!thread.Initialize()) + { + return false; + } + } +#ifndef __APPLE__ + if (!GetAuxvEntries()) + { + return false; + } + if (!EnumerateMemoryRegions(regionStore)) + { + return false; + } +#endif + return true; +} \ No newline at end of file diff --git a/src/coreclr/debug/createdump/shared/crashinfounixcore.cpp b/src/coreclr/debug/createdump/shared/crashinfounixcore.cpp new file mode 100644 index 00000000000000..9df989fece240a --- /dev/null +++ b/src/coreclr/debug/createdump/shared/crashinfounixcore.cpp @@ -0,0 +1,341 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "createdumpcore.h" + +int g_readProcessMemoryErrno = 0; + +// +// Suspends all the threads and creating a list of them. Should be the before gathering any info about the process. +// +bool ProcessInfo::EnumerateAndSuspendThreads() +{ + char taskPath[128]; + int chars = snprintf(taskPath, sizeof(taskPath), "/proc/%u/task", m_pid); + if (chars <= 0 || (size_t)chars >= sizeof(taskPath)) + { + printf_error("snprintf failed building /proc//task\n"); + return false; + } + + DIR* taskDir = opendir(taskPath); + if (taskDir == nullptr) + { + printf_error("Problem enumerating threads: opendir(%s) FAILED %s (%d)\n", taskPath, strerror(errno), 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 + { + printf_error("Problem suspending thread: ptrace(ATTACH, %d) FAILED %s (%d)\n", tid, strerror(errno), errno); + // If the ptrace on a thread that has already terminated, skip/ignore + if (errno == ESRCH && tid != CrashThread()) + { + continue; + } + closedir(taskDir); + return false; + } + // Add to the list of threads + ThreadSnapshot thread(tid); + if (!m_threads.Add(thread)) + { + ptrace(PTRACE_DETACH, tid, nullptr, nullptr); + closedir(taskDir); + return false; + } + } + } + + closedir(taskDir); + return true; +} + +// +// Get the auxv entries to use and add to the core dump +// +bool ProcessInfo::GetAuxvEntries() +{ + char auxvPath[128]; + int chars = snprintf(auxvPath, sizeof(auxvPath), "/proc/%u/auxv", m_pid); + if (chars <= 0 || (size_t)chars >= sizeof(auxvPath)) + { + printf_error("snprintf failed building /proc//auxv\n"); + return false; + } + int fd = open(auxvPath, O_RDONLY, 0); + if (fd == -1) + { + printf_error("Problem reading aux info: open(%s) FAILED %s (%d)\n", auxvPath, strerror(errno), errno); + return false; + } + bool result = false; + elf_aux_entry auxvEntry; + + while (read(fd, &auxvEntry, sizeof(elf_aux_entry)) == sizeof(elf_aux_entry)) + { + if (!m_auxvEntries.Add(auxvEntry)) + { + close(fd); + return false; + } + 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; +} + +void ProcessInfo::CalculateRuntimeBaseAddress() +{ + uint64_t programHeadersStartAddress = m_auxvValues[AT_PHDR]; + uint64_t programHeaderCount = m_auxvValues[AT_PHNUM]; + uint64_t programHeaderSize = m_auxvValues[AT_PHENT]; + if (programHeadersStartAddress == 0 || programHeaderCount == 0 || programHeaderSize != sizeof(elf_program_header) || + programHeaderCount > (UINT64_MAX - programHeadersStartAddress) / programHeaderSize) + { + return; + } + + for (uint64_t index = 0; index < programHeaderCount; index++) + { + elf_program_header programHeader; + size_t read = 0; + if (!ReadProcessMemory(programHeadersStartAddress + (index * programHeaderSize), &programHeader, sizeof(programHeader), &read) || + read != sizeof(programHeader)) + { + return; + } + + if (programHeader.p_type == PT_PHDR && programHeadersStartAddress >= programHeader.p_offset) + { + // AT_PHDR points to PT_PHDR.p_offset in the mapped ELF image. + m_runtimeBaseAddress = programHeadersStartAddress - programHeader.p_offset; + TRACE("Runtime base address: %" PRIA PRIx64 "\n", m_runtimeBaseAddress); + return; + } + } +} + +bool HasDeletedSuffix(const char* fileName) +{ + if (fileName == nullptr) + return false; + size_t len = strlen(fileName); + if (len < 10) // " (deleted)" is 10 characters + return false; + return strcmp(fileName + len - 10, " (deleted)") == 0; +} + +static void TraceModuleRegion( + const ModuleRegion* mapping, + const char* prefix = "", + const char* suffix = "") +{ + mapping->MemoryRegion::Trace(prefix, suffix); +} + +bool ProcessInfo::EnumerateMemoryRegions(DumpRegionStore& regionStore) +{ + // 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; + ssize_t read; + uint64_t cbModuleMappings = 0; + + // Making something like: /proc/123/maps + char mapPath[128]; + int chars = snprintf(mapPath, sizeof(mapPath), "/proc/%u/maps", m_pid); + if (chars <= 0 || (size_t)chars >= sizeof(mapPath)) + { + printf_error("snprintf failed building /proc//maps\n"); + return false; + } + FILE* mapsFile = fopen(mapPath, "rb"); + if (mapsFile == nullptr) + { + printf_error("Problem reading maps file: fopen(%s) FAILED %s (%d)\n", mapPath, strerror(errno), 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 %m[^\n]\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; + } + bool includeInNtFile = (moduleName != nullptr && *moduleName == '/') && !HasDeletedSuffix(moduleName); + if (includeInNtFile) + { + ModuleRegion moduleRegion(regionFlags, start, end, offset); + moduleRegion.TakeFileNameOwnership(moduleName); + moduleName = nullptr; + + if (!m_moduleMappings.Add(Move(moduleRegion))) + { + free(permissions); + free(line); + fclose(mapsFile); + return false; + } + cbModuleMappings += moduleRegion.Size(); + } + else if (!m_otherMappings.Add(MemoryRegion(regionFlags, start, end, offset))) + { + free(moduleName); + free(permissions); + free(line); + fclose(mapsFile); + return false; + } + + if (linuxGateAddress != nullptr && reinterpret_cast(start) == linuxGateAddress) + { + MemoryRegion memoryRegion(regionFlags, start, end); + if (InsertMemoryRegion(regionStore, memoryRegion) < 0) + { + free(permissions); + free(line); + fclose(mapsFile); + return false; + } + } + } + free(moduleName); + free(permissions); + } + + if (g_diagnostics) + { + TRACE("Module mappings (%06" PRIx64 "):\n", cbModuleMappings / PAGE_SIZE); + for (const ModuleRegion& mapping : m_moduleMappings) + { + mapping.Trace(); + } + TRACE("Other mappings:\n"); + for (const MemoryRegion& mapping : m_otherMappings) + { + mapping.Trace(); + } + } + + free(line); // We didn't allocate line, but as per contract of getline we should free it + fclose(mapsFile); + + return true; +} + +// +// Read raw memory +// +bool ProcessInfo::ReadProcessMemory(uint64_t address, void* buffer, size_t size, size_t* read) +{ + assert(buffer != nullptr); + assert(read != nullptr); + *read = 0; + +#ifdef HAVE_PROCESS_VM_READV + if (m_canUseProcVmReadSyscall) + { + iovec local{ buffer, size }; + iovec remote{ (void*)address, size }; + *read = process_vm_readv(m_pid, &local, 1, &remote, 1, 0); + } + + if (!m_canUseProcVmReadSyscall || (*read == (size_t)-1 && (errno == EPERM || errno == ENOSYS))) +#endif + { + // If we've failed, avoid going through expensive syscalls + // After all, the use of process_vm_readv is largely as a + // performance optimization. + m_canUseProcVmReadSyscall = false; + assert(m_fdMem != -1); +#ifdef TARGET_ARM64 + // Android's heap allocator (scudo) uses ARM64 Top-Byte Ignore (TBI) for memory tagging. + // pread on /proc//mem treats the offset as a file position, not a virtual address, + // so the kernel does not apply TBI — tagged pointers cause EINVAL. + // See https://www.kernel.org/doc/html/latest/arch/arm64/tagged-address-abi.html + // + // Currently only Android allocators set a non-zero top byte, so on other ARM64 Linux + // configurations this is a no-op. However, any future use of TBI tagging (e.g., ARM MTE) + // on other Linux distros would hit the same issue. + address &= 0x00FFFFFFFFFFFFFFULL; +#endif + *read = pread(m_fdMem, buffer, size, (off_t)address); + } + + if (*read == (size_t)-1) + { + // Preserve errno for the ELF dump writer call + g_readProcessMemoryErrno = errno; + TRACE_VERBOSE("ReadProcessMemory FAILED addr: %" PRIA PRIx64 " size: %zu error: %s (%d)\n", address, size, strerror(g_readProcessMemoryErrno), g_readProcessMemoryErrno); + return false; + } + return true; +} \ No newline at end of file diff --git a/src/coreclr/debug/createdump/shared/createdumpcore.cpp b/src/coreclr/debug/createdump/shared/createdumpcore.cpp new file mode 100644 index 00000000000000..e54c84952b3bad --- /dev/null +++ b/src/coreclr/debug/createdump/shared/createdumpcore.cpp @@ -0,0 +1,283 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "createdumpcore.h" + +#ifdef HOST_WINDOWS +#include +#endif + +const char* g_help = "createdump [options]\n" +"createdump writes a dump of its parent process; a target PID cannot be specified.\n" +"-f, --name - dump path and file name. The default is '" DEFAULT_DUMP_PATH DEFAULT_DUMP_TEMPLATE "'. These specifiers are substituted with following values:\n" +" %p PID of dumped process.\n" +" %e The process executable filename.\n" +" %h Hostname return by gethostname().\n" +" %t Time of dump, expressed as seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).\n" +"-n, --normal - create minidump.\n" +"-h, --withheap - create minidump with heap (default).\n" +"-t, --triage - create triage minidump.\n" +"-u, --full - create full core dump.\n" +"-d, --diag - enable diagnostic messages.\n" +"-v, --verbose - enable verbose diagnostic messages.\n" +"-l, --logtofile - file path and name to log diagnostic messages.\n" +#ifdef HOST_UNIX +"--crashreport - write crash report file (dump file path + .crashreport.json).\n" +"--crashreportonly - write crash report file only (no dump).\n" +"--crashthread - the thread id of the crashing thread.\n" +"--signal - the signal code of the crash.\n" +"--singlefile - single-file app model.\n" +"--nativeaot - native AOT app model.\n" +#endif +; + +FILE *g_logfile = nullptr; +FILE *g_stdout = stdout; +bool g_diagnostics = false; +bool g_diagnosticsVerbose = false; + +#ifdef CREATEDUMP_RUNTIME_PAGE_SIZE +long g_pageSize = 0; +#endif + +bool ValidateDumpOptions(const CreateDumpOptions* options) +{ + if (options->CrashReport && (options->AppModel == AppModelType::SingleFile || options->AppModel == AppModelType::NativeAOT)) + { + printf_error("The app model does not support crash report generation\n"); + return false; + } + + if (options->DumpType != DumpType::Full && options->AppModel == AppModelType::NativeAOT) + { + printf_error("The app model only supports full dump generation\n"); + return false; + } + + return true; +} + +int ParseCreateDumpOptions(int argc, char* argv[], CreateDumpOptions* options) +{ + options->DumpType = DumpType::Heap; + options->DumpPathTemplate = nullptr; + options->AppModel = AppModelType::Normal; + options->CrashReport = false; + options->CreateDump = true; + options->Signal = 0; + options->CrashThread = 0; +#ifdef HOST_UNIX + options->Pid = static_cast(getppid()); +#else + options->Pid = 0; +#endif + options->SignalCode = 0; + options->SignalErrno = 0; + options->SignalAddress = 0; + options->ExceptionRecord = 0; + + bool help = false; + // Parse the command line options + argv++; + for (int i = 1; i < argc && *argv != nullptr; i++) + { + if ((strcmp(*argv, "-f") == 0) || (strcmp(*argv, "--name") == 0)) + { + options->DumpPathTemplate = *++argv; + } + else if ((strcmp(*argv, "-n") == 0) || (strcmp(*argv, "--normal") == 0)) + { + options->DumpType = DumpType::Mini; + } + else if ((strcmp(*argv, "-h") == 0) || (strcmp(*argv, "--withheap") == 0)) + { + options->DumpType = DumpType::Heap; + } + else if ((strcmp(*argv, "-t") == 0) || (strcmp(*argv, "--triage") == 0)) + { + options->DumpType = DumpType::Triage; + } + else if ((strcmp(*argv, "-u") == 0) || (strcmp(*argv, "--full") == 0)) + { + options->DumpType = DumpType::Full; + } +#ifdef HOST_UNIX + else if (strcmp(*argv, "--crashreport") == 0) + { + options->CrashReport = true; + } + else if (strcmp(*argv, "--crashreportonly") == 0) + { + options->CrashReport = true; + options->CreateDump = false; + } + else if (strcmp(*argv, "--crashthread") == 0) + { + options->CrashThread = atoi(*++argv); + } + else if (strcmp(*argv, "--signal") == 0) + { + options->Signal = atoi(*++argv); + } + else if (strcmp(*argv, "--singlefile") == 0) + { + options->AppModel = AppModelType::SingleFile; + } + else if (strcmp(*argv, "--nativeaot") == 0) + { + options->AppModel = AppModelType::NativeAOT; + } + else if (strcmp(*argv, "--code") == 0) + { + options->SignalCode = atoi(*++argv); + } + else if (strcmp(*argv, "--errno") == 0) + { + options->SignalErrno = atoi(*++argv); + } + else if (strcmp(*argv, "--address") == 0) + { + options->SignalAddress = atoll(*++argv); + } + else if (strcmp(*argv, "--exception-record") == 0) + { + options->ExceptionRecord = atoll(*++argv); + } +#endif + else if ((strcmp(*argv, "-d") == 0) || (strcmp(*argv, "--diag") == 0)) + { + g_diagnostics = true; + } + else if ((strcmp(*argv, "-v") == 0) || (strcmp(*argv, "--verbose") == 0)) + { + g_diagnostics = true; + g_diagnosticsVerbose = true; + } + else if ((strcmp(*argv, "-l") == 0) || (strcmp(*argv, "--logtofile") == 0)) + { + const char* logFilePath = *++argv; + g_logfile = fopen(logFilePath, "w"); + if (g_logfile == nullptr) + { + printf_error("Can not create log file '%s': %s (%d)\n", logFilePath, strerror(errno), errno); + return errno; + } + g_stdout = g_logfile; + } + else if ((strcmp(*argv, "-?") == 0) || (strcmp(*argv, "--help") == 0)) + { + help = true; + } + else + { + printf_error("Unrecognized argument '%s'\n", *argv); + return -1; + } + argv++; + } + + if (help) + { + printf_error("%s", g_help); + return -1; + } + + return 0; +} + +void +printf_status(const char* format, ...) +{ + va_list args; + va_start(args, format); + if (g_logfile == nullptr) + { + fprintf(g_stdout, "[createdump] "); + } + vfprintf(g_stdout, format, args); + fflush(g_stdout); + va_end(args); +} + +void +printf_error(const char* format, ...) +{ + va_list args; + va_start(args, format); + + // Log error message to file + if (g_logfile != nullptr) + { + va_list args2; + va_copy(args2, args); + vfprintf(g_logfile, format, args2); + fflush(g_logfile); + } + // Always print errors on stderr + fprintf(stderr, "[createdump] "); + vfprintf(stderr, format, args); + fflush(stderr); + va_end(args); +} + +#ifdef HOST_UNIX + +void +LogProcessStatus(int pid) +{ + if (kill(pid, 0) == 0) + { + printf_status("Target process is alive\n"); + } + else + { + int err = errno; + if (err == ESRCH) + { + printf_error("Target process terminated\n"); + } + else + { + printf_error("kill(%d, 0) FAILED %s (%d)\n", pid, strerror(err), err); + } + } +} + +void +trace_prefix(const char* format, va_list args) +{ + // Only add this prefix if logging to the console + if (g_logfile == nullptr) + { + fprintf(g_stdout, "[createdump] "); + } + print_trace_timestamp(); + vfprintf(g_stdout, format, args); + fflush(g_stdout); +} + +void +trace_printf(const char* format, ...) +{ + if (g_diagnostics) + { + va_list args; + va_start(args, format); + trace_prefix(format, args); + va_end(args); + } +} + +void +trace_verbose_printf(const char* format, ...) +{ + if (g_diagnosticsVerbose) + { + va_list args; + va_start(args, format); + trace_prefix(format, args); + va_end(args); + } +} + +#endif // HOST_UNIX \ No newline at end of file diff --git a/src/coreclr/debug/createdump/shared/createdumpcore.h b/src/coreclr/debug/createdump/shared/createdumpcore.h new file mode 100644 index 00000000000000..ac0f43d3612fa5 --- /dev/null +++ b/src/coreclr/debug/createdump/shared/createdumpcore.h @@ -0,0 +1,218 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef CREATEDUMPCORE_H +#define CREATEDUMPCORE_H + +extern bool g_diagnostics; +extern bool g_diagnosticsVerbose; + +#ifdef __GNUC__ +#define CREATEDUMP_FORMAT_PRINTF(fmt_pos, arg_pos) __attribute__ ((__format__(__printf__, fmt_pos, arg_pos))) +#else +#define CREATEDUMP_FORMAT_PRINTF(fmt_pos, arg_pos) +#endif + +#ifdef HOST_UNIX +extern void trace_printf(const char* format, ...) CREATEDUMP_FORMAT_PRINTF(1, 2); +extern void trace_verbose_printf(const char* format, ...) CREATEDUMP_FORMAT_PRINTF(1, 2); +void LogProcessStatus(int pid); +#define TRACE(args...) trace_printf(args) +#define TRACE_VERBOSE(args...) trace_verbose_printf(args) +#else +#define TRACE(args, ...) +#define TRACE_VERBOSE(args, ...) +#endif + +#ifdef HOST_64BIT +#define PRIA "016" +#else +#define PRIA "08" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +extern FILE* g_logfile; +extern FILE* g_stdout; + +#ifdef HOST_UNIX +#include +#include +#include +#include +#include +#include +#include +#ifndef __APPLE__ +#include +#include +#endif +#ifdef HAVE_PROCESS_VM_READV +#include +#endif +#include +#include +#ifdef __APPLE__ +#include +#else +#include +#include +#endif +#endif + +#ifndef MAX_LONGPATH +#define MAX_LONGPATH 1024 +#endif + +#define MAX_DUMP_PATH 4096 + +#ifdef HOST_WINDOWS +#define DEFAULT_DUMP_PATH "%TEMP%\\" +#define DEFAULT_DUMP_TEMPLATE "dump.%p.dmp" +#else +#define DEFAULT_DUMP_PATH "/tmp/" +#define DEFAULT_DUMP_TEMPLATE "coredump.%p" +#endif + +enum class DumpType +{ + Mini, + Heap, + Triage, + Full +}; + +enum class AppModelType +{ + Normal, + SingleFile, + NativeAOT +}; + +typedef struct +{ + const char* DumpPathTemplate; + enum DumpType DumpType; + enum AppModelType AppModel; + bool CreateDump; + bool CrashReport; + int Pid; + int CrashThread; + int Signal; + int SignalCode; + int SignalErrno; + uint64_t SignalAddress; + uint64_t ExceptionRecord; +} CreateDumpOptions; + +inline const char* +GetDumpTypeString(DumpType dumpType) +{ + switch (dumpType) + { + case DumpType::Mini: + return "minidump"; + case DumpType::Heap: + return "minidump with heap"; + case DumpType::Triage: + return "triage minidump"; + case DumpType::Full: + return "full dump"; + default: + return "unknown"; + } +} + +#ifdef HOST_UNIX +#ifdef __APPLE__ +#include +#include +#endif +#include "coreutils.h" +#include "memoryregion.h" +#include "specialdiaginfo.h" +#include "threadsnapshot.h" +#include "processinfo.h" +#endif + +#ifdef HOST_UNIX +// This algorithm allows for both implementations of createdump to combine contiguous memory regions. +// Each supplies its own list of memory regions and a way to insert the combined regions so linked +// createdump doesn't need to depend on libstdc++ containers. +template +bool CombineMemoryRegions(const TRegions& regions, TCombinedRegions& combinedRegions, TInsert insert) +{ + TRACE("CombineMemoryRegions: STARTED\n"); + assert(!regions.empty()); + + // MEMORY_REGION_FLAG_SHARED and MEMORY_REGION_FLAG_PRIVATE are internal flags that + // don't affect the core dump so ignore them when comparing the flags. + uint32_t flags = regions.begin()->Flags() & MEMORY_REGION_FLAG_PERMISSIONS_MASK; + uint64_t start = regions.begin()->StartAddress(); + uint64_t end = start; + + for (const MemoryRegion& region : regions) + { + // To combine a region it needs to be contiguous, same permissions and memory backed flag. + if ((end == region.StartAddress()) && + (flags == (region.Flags() & MEMORY_REGION_FLAG_PERMISSIONS_MASK))) + { + end = region.EndAddress(); + } + else + { + if (!insert(MemoryRegion(flags, start, end))) + { + return false; + } + flags = region.Flags() & MEMORY_REGION_FLAG_PERMISSIONS_MASK; + start = region.StartAddress(); + end = region.EndAddress(); + } + } + + assert(start != end); + if (!insert(MemoryRegion(flags, start, end))) + { + return false; + } + + TRACE("CombineMemoryRegions: FINISHED\n"); + + if (g_diagnosticsVerbose) + { + TRACE("Final Memory Regions:\n"); + for (const MemoryRegion& region : combinedRegions) + { + region.Trace(); + } + } + + return true; +} +#endif + +void printf_status(const char* format, ...); +void printf_error(const char* format, ...); +void print_trace_timestamp(); +void trace_prefix(const char* format, va_list args); + +bool CreateDump(const CreateDumpOptions& options); +bool GetDefaultDumpPath(char* buffer, size_t bufferSize); +bool FormatDumpName(char* name, size_t nameSize, const char* pattern, const char* exeName, int pid); +int ParseCreateDumpOptions(int argc, char* argv[], CreateDumpOptions* options); +bool ValidateDumpOptions(const CreateDumpOptions* options); +#ifdef HOST_UNIX +bool GetStatus(pid_t pid, pid_t* ppid, pid_t* tgid, char *name, size_t nameSize); +bool AddSpecialDiagInfoRegion(DumpRegionStore& regionStore); +bool CreateDumpCore(const CreateDumpOptions* options, DumpRegionStore* regionStore); +#endif + +#endif // CREATEDUMPCORE_H diff --git a/src/coreclr/debug/createdump/shared/createdumpunixcore.cpp b/src/coreclr/debug/createdump/shared/createdumpunixcore.cpp new file mode 100644 index 00000000000000..a5937c53e7925c --- /dev/null +++ b/src/coreclr/debug/createdump/shared/createdumpunixcore.cpp @@ -0,0 +1,198 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "createdumpcore.h" + +bool GetProcessInfo(pid_t pid, pid_t* ppid, pid_t* tgid, char *name, size_t nameSize); + +bool ProcessInfo::Initialize() +{ +#ifdef CREATEDUMP_RUNTIME_PAGE_SIZE + // g_pageSize may have been initialized in the external createdump + if (g_pageSize == 0) + { + long pageSize = sysconf(_SC_PAGESIZE); + if (pageSize <= 0 || ((uint64_t)pageSize & ((uint64_t)pageSize - 1)) != 0) + { + fprintf(stderr, "[createdump] Invalid system page size: %ld\n", pageSize); + return false; + } + g_pageSize = pageSize; + } +#endif + + char memPath[128]; + int chars = snprintf(memPath, sizeof(memPath), "/proc/%u/mem", m_pid); + if (chars <= 0 || (size_t)chars >= sizeof(memPath)) + { + printf_error("snprintf failed building /proc//mem name\n"); + return false; + } + + m_fdMem = open(memPath, O_RDONLY); + if (m_fdMem == -1) + { + int err = errno; + const char* message = "Problem accessing memory"; + if (err == EPERM || err == EACCES) + { + message = "The process or container does not have permissions or access"; + } + else if (err == ENOENT) + { + message = "Invalid process id"; + } + printf_error("%s: open(%s) FAILED %s (%d)\n", message, memPath, strerror(err), err); + return false; + } + +#ifndef __APPLE__ + const char* disablePagemapUse = getenv("DOTNET_DbgDisablePagemapUse"); + if (disablePagemapUse == nullptr) + { + disablePagemapUse = getenv("COMPlus_DbgDisablePagemapUse"); + } + char* end = nullptr; + errno = 0; + unsigned long value = disablePagemapUse != nullptr ? strtoul(disablePagemapUse, &end, 10) : 1; + if (disablePagemapUse != nullptr && end != disablePagemapUse && errno != ERANGE && value == 0) + { + TRACE("DbgDisablePagemapUse detected - pagemap file checking is enabled\n"); + char pagemapPath[128]; + chars = snprintf(pagemapPath, sizeof(pagemapPath), "/proc/%u/pagemap", m_pid); + if (chars <= 0 || (size_t)chars >= sizeof(pagemapPath)) + { + printf_error("snprintf failed building /proc//pagemap name\n"); + CleanupAndResumeProcess(); + return false; + } + m_fdPagemap = open(pagemapPath, O_RDONLY); + if (m_fdPagemap == -1) + { + TRACE("open(%s) FAILED %d (%s), will fallback to dumping all memory regions without checking if they are committed\n", pagemapPath, errno, strerror(errno)); + } + } + else + { + m_fdPagemap = -1; + } +#endif + + if (!GetProcessInfo(m_pid, &m_ppid, &m_tgid, m_exeName, sizeof(m_exeName))) + { + CleanupAndResumeProcess(); + return false; + } + + m_canUseProcVmReadSyscall = true; + return true; +} + +void ProcessInfo::CleanupAndResumeProcess() +{ + for (const ThreadSnapshot& thread : m_threads) + { + pid_t tid = thread.Tid(); + if (ptrace(PTRACE_DETACH, tid, nullptr, nullptr) != -1) + { + int waitStatus; + waitpid(tid, &waitStatus, __WALL); + } + } + + if (m_fdMem != -1) + { + close(m_fdMem); + m_fdMem = -1; + } + if (m_fdPagemap != -1) + { + close(m_fdPagemap); + m_fdPagemap = -1; + } +} + +// +// Get the process or thread status +// +bool GetStatus(pid_t pid, pid_t* ppid, pid_t* tgid, char *name, size_t nameSize) +{ + char statusPath[128]; + int chars = snprintf(statusPath, sizeof(statusPath), "/proc/%d/status", pid); + if (chars <= 0 || (size_t)chars >= sizeof(statusPath)) + { + printf_error("snprintf failed building /proc//status\n"); + return false; + } + + FILE *statusFile = fopen(statusPath, "rb"); + if (statusFile == nullptr) + { + printf_error("GetStatus fopen(%s) FAILED %s (%d)\n", statusPath, strerror(errno), errno); + 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 && nameSize > 0) + { + const char* source = line + 6; + size_t length = strcspn(source, "\n"); + + if (length >= nameSize) + { + length = nameSize - 1; + } + + memcpy(name, source, length); + name[length] = '\0'; + } + } + } + + free(line); + fclose(statusFile); + return true; +} + +bool GetProcessInfo(pid_t pid, pid_t* ppid, pid_t* tgid, char *name, size_t nameSize) +{ + if (!GetStatus(pid, ppid, tgid, name, nameSize)) + { + return false; + } + + // Try reading the executable name from the /proc//exe link. Prefer this name to the + // one reported by status if it is available because the status name is often truncated + char exePath[128]; + int chars = snprintf(exePath, sizeof(exePath), "/proc/%d/exe", pid); + if (chars > 0 && (size_t)chars < sizeof(exePath)) + { + char buf[4096]; + ssize_t nbytes = readlink(exePath, buf, sizeof(buf) - 1); + if (nbytes != -1) + { + buf[nbytes] = '\0'; + const char* executableName = strrchr(buf, '/'); + executableName = executableName != nullptr ? executableName + 1 : buf; + snprintf(name, nameSize, "%s", executableName); + } + } + + return true; +} diff --git a/src/coreclr/debug/createdump/dumpname.cpp b/src/coreclr/debug/createdump/shared/dumpname.cpp similarity index 63% rename from src/coreclr/debug/createdump/dumpname.cpp rename to src/coreclr/debug/createdump/shared/dumpname.cpp index 76b6a3790ae9dc..4255a6c7f7fc5e 100644 --- a/src/coreclr/debug/createdump/dumpname.cpp +++ b/src/coreclr/debug/createdump/shared/dumpname.cpp @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#include "createdump.h" +#include "createdumpcore.h" #include #ifdef HOST_WINDOWS #include @@ -31,9 +31,33 @@ // %s Number of signal causing dump. // %u Numeric real UID of dumped process. // + +static bool AppendFormat( + char** output, + size_t* remaining, + const char* format, + ...) +{ + va_list args; + va_start(args, format); + int written = vsnprintf(*output, *remaining, format, args); + va_end(args); + + if (written < 0 || (size_t)written >= *remaining) + { + return false; + } + + *output += written; + *remaining -= written; + return true; +} + bool -FormatDumpName(std::string& name, const char* pattern, const char* exename, int pid) +FormatDumpName(char* name, size_t nameSize, const char* pattern, const char* exeName, int pid) { + assert(name != nullptr && nameSize >= MAX_LONGPATH && pattern != nullptr && exeName != nullptr); + const char* p = pattern; if (*p == '|') { @@ -46,11 +70,17 @@ FormatDumpName(std::string& name, const char* pattern, const char* exename, int int wsaerr = WSAStartup(1, &wsadata); #endif + char *output = name; + size_t remaining = nameSize; + name[0] = '\0'; while (*p) { if (*p != '%') { - name.append(1, *p); + if (!AppendFormat(&output, &remaining, "%c", *p)) + { + goto too_long; + } } else { @@ -60,26 +90,35 @@ FormatDumpName(std::string& name, const char* pattern, const char* exename, int return true; case '%': - name.append(1, '%'); + if (!AppendFormat(&output, &remaining, "%%")) + { + goto too_long; + } break; // process Id case 'd': case 'p': - name.append(std::to_string(pid)); + if (!AppendFormat(&output, &remaining, "%d", pid)) + { + goto too_long; + } break; // time of dump case 't': time_t dumptime; time(&dumptime); - name.append(std::to_string(dumptime)); + if (!AppendFormat(&output, &remaining, "%" PRId64, (int64_t)dumptime)) + { + goto too_long; + } break; // hostname case 'h': { - AStringHolder buffer = new char[MAX_LONGPATH + 1]; - if (gethostname(buffer, MAX_LONGPATH) != 0) + char hostName[MAX_LONGPATH + 1]; + if (gethostname(hostName, sizeof(hostName)) != 0) { printf_error("Could not get the host name for dump name: %d\n", #ifdef HOST_WINDOWS @@ -89,13 +128,21 @@ FormatDumpName(std::string& name, const char* pattern, const char* exename, int #endif return false; } - name.append(buffer); + + hostName[sizeof(hostName) - 1] = '\0'; + if (!AppendFormat(&output, &remaining, "%s", hostName)) + { + goto too_long; + } break; } // executable file name case 'e': - name.append(exename); + if (!AppendFormat(&output, &remaining, "%s", exeName)) + { + goto too_long; + } break; // executable file path with / replaced with ! @@ -121,4 +168,8 @@ FormatDumpName(std::string& name, const char* pattern, const char* exename, int ++p; } return true; + +too_long: + printf_error("The formatted dump path is too long\n"); + return false; } diff --git a/src/coreclr/debug/createdump/dumpwriter.cpp b/src/coreclr/debug/createdump/shared/dumpwriter.cpp similarity index 78% rename from src/coreclr/debug/createdump/dumpwriter.cpp rename to src/coreclr/debug/createdump/shared/dumpwriter.cpp index 0e39b52b3a7d4c..05de83cd4e7126 100644 --- a/src/coreclr/debug/createdump/dumpwriter.cpp +++ b/src/coreclr/debug/createdump/shared/dumpwriter.cpp @@ -1,14 +1,21 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#ifdef __APPLE__ #include "createdump.h" +#else +#include "createdumpcore.h" +#include "dumpwriter.h" +#endif +#ifdef __APPLE__ DumpWriter::DumpWriter(CrashInfo& crashInfo) : m_fd(-1), m_crashInfo(crashInfo) { m_crashInfo.AddRef(); } +#endif DumpWriter::~DumpWriter() { @@ -17,7 +24,25 @@ DumpWriter::~DumpWriter() close(m_fd); m_fd = -1; } +#ifdef __APPLE__ m_crashInfo.Release(); +#endif +} + +bool +DumpWriter::OpenAndWriteDump(const char* dumpFileName) +{ + if (!OpenDump(dumpFileName)) + { + return false; + } + if (!WriteDump()) + { + printf_error("Writing dump FAILED\n"); + remove(dumpFileName); + return false; + } + return true; } bool @@ -39,8 +64,13 @@ DumpWriter::WriteDiagInfo(size_t size) SpecialDiagInfoHeader header = { {SPECIAL_DIAGINFO_SIGNATURE}, SPECIAL_DIAGINFO_VERSION, +#ifdef __APPLE__ m_crashInfo.ExceptionRecord(), m_crashInfo.RuntimeBaseAddress() +#else + m_processInfo.ExceptionRecord(), + m_processInfo.RuntimeBaseAddress() +#endif }; if (!WriteData(&header, sizeof(header))) { return false; diff --git a/src/coreclr/debug/createdump/dumpwriter.h b/src/coreclr/debug/createdump/shared/dumpwriter.h similarity index 77% rename from src/coreclr/debug/createdump/dumpwriter.h rename to src/coreclr/debug/createdump/shared/dumpwriter.h index 96e37b9bdf11ce..d9b2759df227be 100644 --- a/src/coreclr/debug/createdump/dumpwriter.h +++ b/src/coreclr/debug/createdump/shared/dumpwriter.h @@ -1,9 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#ifndef DUMPWRITER_H +#define DUMPWRITER_H + #ifdef __APPLE__ #include "dumpwritermacho.h" #else #include "dumpwriterelf.h" #endif +#endif // DUMPWRITER_H + diff --git a/src/coreclr/debug/createdump/dumpwriterelf.cpp b/src/coreclr/debug/createdump/shared/dumpwriterelf.cpp similarity index 85% rename from src/coreclr/debug/createdump/dumpwriterelf.cpp rename to src/coreclr/debug/createdump/shared/dumpwriterelf.cpp index 48d4e609876e4a..5523ec32f59b26 100644 --- a/src/coreclr/debug/createdump/dumpwriterelf.cpp +++ b/src/coreclr/debug/createdump/shared/dumpwriterelf.cpp @@ -1,10 +1,22 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#include "createdump.h" +#include "createdumpcore.h" +#include "dumpwriter.h" extern int g_readProcessMemoryErrno; +DumpWriter::DumpWriter( + ProcessInfo& processInfo, + const DynamicArray& moduleMappings, + const DynamicArray& dumpRegions) : + m_fd(-1), + m_processInfo(processInfo), + m_moduleMappings(moduleMappings), + m_dumpRegions(dumpRegions) +{ +} + // Write the core dump file: // ELF header // Single section header (Shdr) for 64 bit program header count @@ -47,7 +59,7 @@ DumpWriter::WriteDump() // PT_NOTE + number of memory regions uint64_t phnum = 1; - for (const MemoryRegion& memoryRegion : m_crashInfo.MemoryRegions()) + for (const MemoryRegion& memoryRegion : m_dumpRegions) { phnum++; } @@ -113,7 +125,7 @@ DumpWriter::WriteDump() TRACE("Writing memory region headers to core file\n"); // Write memory region note headers - for (const MemoryRegion& memoryRegion : m_crashInfo.MemoryRegions()) + for (const MemoryRegion& memoryRegion : m_dumpRegions) { phdr.p_flags = memoryRegion.Permissions(); phdr.p_vaddr = memoryRegion.StartAddress(); @@ -143,12 +155,12 @@ DumpWriter::WriteDump() return false; } - TRACE("Writing %zd thread entries to core file\n", m_crashInfo.Threads().size()); + TRACE("Writing %zd thread entries to core file\n", m_processInfo.Threads().Count()); // Write all the thread's state and registers - for (const ThreadInfo* thread : m_crashInfo.Threads()) + for (const ThreadSnapshot& thread : m_processInfo.Threads()) { - if (!WriteThread(*thread)) { + if (!WriteThread(thread)) { return false; } } @@ -170,7 +182,7 @@ DumpWriter::WriteDump() // Read from target process and write memory regions to core uint64_t total = 0; - for (const MemoryRegion& memoryRegion : m_crashInfo.MemoryRegions()) + for (const MemoryRegion& memoryRegion : m_dumpRegions) { uint64_t address = memoryRegion.StartAddress(); size_t size = memoryRegion.Size(); @@ -186,10 +198,10 @@ DumpWriter::WriteDump() { while (size > 0) { - size_t bytesToRead = std::min(size, sizeof(m_tempBuffer)); + size_t bytesToRead = size < sizeof(m_tempBuffer) ? size : sizeof(m_tempBuffer); size_t read = 0; - if (!m_crashInfo.ReadProcessMemory(address, m_tempBuffer, bytesToRead, &read)) { + if (!m_processInfo.ReadProcessMemory(address, m_tempBuffer, bytesToRead, &read)) { printf_error("Error reading memory at %" PRIA PRIx64 " size %08zx FAILED %s (%d)\n", address, bytesToRead, strerror(g_readProcessMemoryErrno), g_readProcessMemoryErrno); return false; } @@ -220,10 +232,10 @@ DumpWriter::WriteProcessInfo() prpsinfo_t processInfo; memset(&processInfo, 0, sizeof(processInfo)); processInfo.pr_sname = 'R'; - processInfo.pr_pid = m_crashInfo.Pid(); - processInfo.pr_ppid = m_crashInfo.Ppid(); - processInfo.pr_pgrp = m_crashInfo.Tgid(); - m_crashInfo.Name().copy(processInfo.pr_fname, sizeof(processInfo.pr_fname)); + processInfo.pr_pid = m_processInfo.Pid(); + processInfo.pr_ppid = m_processInfo.Ppid(); + processInfo.pr_pgrp = m_processInfo.Tgid(); + strncpy(processInfo.pr_fname, m_processInfo.Name(), sizeof(processInfo.pr_fname)); Nhdr nhdr; memset(&nhdr, 0, sizeof(nhdr)); @@ -248,16 +260,16 @@ DumpWriter::WriteAuxv() Nhdr nhdr; memset(&nhdr, 0, sizeof(nhdr)); nhdr.n_namesz = 5; - nhdr.n_descsz = m_crashInfo.GetAuxvSize(); + nhdr.n_descsz = m_processInfo.GetAuxvSize(); nhdr.n_type = NT_AUXV; - TRACE("Writing %zd auxv entries to core file\n", m_crashInfo.AuxvEntries().size()); + TRACE("Writing %zd auxv entries to core file\n", m_processInfo.AuxvEntries().Count()); if (!WriteData(&nhdr, sizeof(nhdr)) || !WriteData("CORE\0AUX", 8)) { return false; } - for (const auto& auxvEntry : m_crashInfo.AuxvEntries()) + for (const elf_aux_entry& auxvEntry : m_processInfo.AuxvEntries()) { if (!WriteData(&auxvEntry, sizeof(auxvEntry))) { return false; @@ -277,7 +289,7 @@ struct NTFileEntry size_t DumpWriter::GetNTFileInfoSize(size_t* alignmentBytes) { - size_t count = m_crashInfo.ModuleMappings().size(); + size_t count = m_moduleMappings.Count(); size_t size = 0; // Header, CORE, entry count, page size @@ -290,8 +302,8 @@ DumpWriter::GetNTFileInfoSize(size_t* alignmentBytes) size += count; // File name storage needed - for (const ModuleRegion& image : m_crashInfo.ModuleMappings()) { - size += image.FileName().length(); + for (const ModuleRegion& image : m_moduleMappings) { + size += image.FileNameLength(); } // Notes must end on 4 byte alignment size_t alignmentBytesNeeded = 4 - (size % 4); @@ -326,10 +338,10 @@ DumpWriter::WriteNTFileInfo() size_t alignmentBytesNeeded = 0; nhdr.n_descsz = GetNTFileInfoSize(&alignmentBytesNeeded) - sizeof(nhdr) - 8; - size_t count = m_crashInfo.ModuleMappings().size(); + size_t count = m_moduleMappings.Count(); size_t pageSize = PAGE_SIZE; - TRACE("Writing %zd NT_FILE entries to core file\n", m_crashInfo.ModuleMappings().size()); + TRACE("Writing %zd NT_FILE entries to core file\n", m_moduleMappings.Count()); if (!WriteData(&nhdr, sizeof(nhdr)) || !WriteData("CORE\0FIL", 8) || @@ -338,7 +350,7 @@ DumpWriter::WriteNTFileInfo() return false; } - for (const ModuleRegion& image : m_crashInfo.ModuleMappings()) + for (const ModuleRegion& image : m_moduleMappings) { struct NTFileEntry entry { (unsigned long)image.StartAddress(), (unsigned long)image.EndAddress(), (unsigned long)(image.Offset() / pageSize) }; if (!WriteData(&entry, sizeof(entry))) { @@ -346,9 +358,9 @@ DumpWriter::WriteNTFileInfo() } } - for (const ModuleRegion& image : m_crashInfo.ModuleMappings()) + for (const ModuleRegion& image : m_moduleMappings) { - if (!WriteData(image.FileName().c_str(), image.FileName().length()) || + if (!WriteData(image.FileName(), image.FileNameLength()) || !WriteData("\0", 1)) { return false; } @@ -367,15 +379,15 @@ DumpWriter::WriteNTFileInfo() } bool -DumpWriter::WriteThread(const ThreadInfo& thread) +DumpWriter::WriteThread(const ThreadSnapshot& thread) { prstatus_t pr; memset(&pr, 0, sizeof(pr)); const siginfo_t* siginfo = nullptr; - if (m_crashInfo.Signal() != 0 && thread.IsCrashThread()) + if (m_processInfo.Signal() != 0 && thread.Tid() == m_processInfo.CrashThread()) { - siginfo = m_crashInfo.SigInfo(); + siginfo = m_processInfo.SigInfo(); pr.pr_info.si_signo = siginfo->si_signo; pr.pr_info.si_code = siginfo->si_code; pr.pr_info.si_errno = siginfo->si_errno; diff --git a/src/coreclr/debug/createdump/dumpwriterelf.h b/src/coreclr/debug/createdump/shared/dumpwriterelf.h similarity index 73% rename from src/coreclr/debug/createdump/dumpwriterelf.h rename to src/coreclr/debug/createdump/shared/dumpwriterelf.h index 912befddf03b90..b0bcfba1ca477c 100644 --- a/src/coreclr/debug/createdump/dumpwriterelf.h +++ b/src/coreclr/debug/createdump/shared/dumpwriterelf.h @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#ifndef DUMPWRITERELF_H +#define DUMPWRITERELF_H + #ifdef HOST_64BIT #define ELF_CLASS ELFCLASS64 #else @@ -41,35 +44,38 @@ class DumpWriter { private: int m_fd; - CrashInfo& m_crashInfo; - BYTE m_tempBuffer[0x4000]; + uint8_t m_tempBuffer[0x4000]; + ProcessInfo& m_processInfo; + const DynamicArray& m_moduleMappings; + const DynamicArray& m_dumpRegions; // no public copy constructor DumpWriter(const DumpWriter&) = delete; void operator=(const DumpWriter&) = delete; public: - DumpWriter(CrashInfo& crashInfo); + DumpWriter(ProcessInfo& processInfo, const DynamicArray& moduleMappings, const DynamicArray& dumpRegions); virtual ~DumpWriter(); - bool OpenDump(const char* dumpFileName); - bool WriteDump(); + bool OpenAndWriteDump(const char* dumpFileName); static bool WriteData(int fd, const void* buffer, size_t length); private: + bool OpenDump(const char* dumpFileName); + bool WriteDump(); bool WriteDiagInfo(size_t size); bool WriteProcessInfo(); bool WriteAuxv(); size_t GetNTFileInfoSize(size_t* alignmentBytes = nullptr); bool WriteNTFileInfo(); - bool WriteThread(const ThreadInfo& thread); + bool WriteThread(const ThreadSnapshot& thread); bool WriteData(const void* buffer, size_t length) { return WriteData(m_fd, buffer, length); } size_t GetProcessInfoSize() const { return sizeof(Nhdr) + 8 + sizeof(prpsinfo_t); } - size_t GetAuxvInfoSize() const { return sizeof(Nhdr) + 8 + m_crashInfo.GetAuxvSize(); } + size_t GetAuxvInfoSize() const { return sizeof(Nhdr) + 8 + m_processInfo.GetAuxvSize(); } size_t GetThreadInfoSize() const { - return (m_crashInfo.Signal() != 0 ? (sizeof(Nhdr) + 8 + sizeof(siginfo_t)) : 0) - + (m_crashInfo.Threads().size() * ((sizeof(Nhdr) + 8 + sizeof(prstatus_t)) + return (m_processInfo.Signal() != 0 ? (sizeof(Nhdr) + 8 + sizeof(siginfo_t)) : 0) + + (m_processInfo.Threads().Count() * ((sizeof(Nhdr) + 8 + sizeof(prstatus_t)) + (sizeof(Nhdr) + 8 + sizeof(user_fpregs_struct)) #if defined(__i386__) + (sizeof(Nhdr) + 8 + sizeof(user_fpxregs_struct)) @@ -80,3 +86,5 @@ class DumpWriter )); } }; + +#endif // DUMPWRITERELF_H diff --git a/src/coreclr/debug/createdump/dumpwritermacho.cpp b/src/coreclr/debug/createdump/shared/dumpwritermacho.cpp similarity index 90% rename from src/coreclr/debug/createdump/dumpwritermacho.cpp rename to src/coreclr/debug/createdump/shared/dumpwritermacho.cpp index 3b024edd7828ad..1022a00ca031ae 100644 --- a/src/coreclr/debug/createdump/dumpwritermacho.cpp +++ b/src/coreclr/debug/createdump/shared/dumpwritermacho.cpp @@ -172,6 +172,18 @@ DumpWriter::BuildSegmentLoadCommands() void DumpWriter::BuildThreadLoadCommands() { +#if defined(__x86_64__) + static_assert(x86_THREAD_STATE64_COUNT == sizeof(x86_thread_state64_t) / sizeof(uint32_t)); + static_assert(x86_FLOAT_STATE64_COUNT == sizeof(x86_float_state64_t) / sizeof(uint32_t)); + static_assert(sizeof(ThreadCommand) == + sizeof(thread_command) + (5 * sizeof(uint32_t)) + sizeof(x86_thread_state64_t) + sizeof(x86_float_state64_t)); +#elif defined(__aarch64__) + static_assert(ARM_THREAD_STATE64_COUNT == sizeof(arm_thread_state64_t) / sizeof(uint32_t)); + static_assert(ARM_NEON_STATE64_COUNT == sizeof(arm_neon_state64_t) / sizeof(uint32_t)); + static_assert(sizeof(ThreadCommand) == + sizeof(thread_command) + (4 * sizeof(uint32_t)) + sizeof(arm_thread_state64_t) + sizeof(arm_neon_state64_t)); +#endif + for (const ThreadInfo* thread : m_crashInfo.Threads()) { ThreadCommand threadCommand = { @@ -182,19 +194,15 @@ DumpWriter::BuildThreadLoadCommands() threadCommand.gpcount = x86_THREAD_STATE64_COUNT; threadCommand.fpflavor = x86_FLOAT_STATE64; threadCommand.fpcount = x86_FLOAT_STATE64_COUNT; - assert(x86_THREAD_STATE64_COUNT == sizeof(x86_thread_state64_t) / sizeof(uint32_t)); - assert(x86_FLOAT_STATE64_COUNT == sizeof(x86_float_state64_t) / sizeof(uint32_t)); - memcpy(&threadCommand.gpregisters, thread->GPRegisters(), sizeof(x86_thread_state64_t)); - memcpy(&threadCommand.fpregisters, thread->FPRegisters(), sizeof(x86_float_state64_t)); + memcpy(threadCommand.gpregisters, thread->GPRegisters(), sizeof(x86_thread_state64_t)); + memcpy(threadCommand.fpregisters, thread->FPRegisters(), sizeof(x86_float_state64_t)); #elif defined(__aarch64__) threadCommand.gpflavor = ARM_THREAD_STATE64; threadCommand.gpcount = ARM_THREAD_STATE64_COUNT; threadCommand.fpflavor = ARM_NEON_STATE64; threadCommand.fpcount = ARM_NEON_STATE64_COUNT; - assert(ARM_THREAD_STATE64_COUNT == sizeof(arm_thread_state64_t) / sizeof(uint32_t)); - assert(ARM_NEON_STATE64_COUNT == sizeof(arm_neon_state64_t) / sizeof(uint32_t)); - memcpy(&threadCommand.gpregisters, thread->GPRegisters(), sizeof(arm_thread_state64_t)); - memcpy(&threadCommand.fpregisters, thread->FPRegisters(), sizeof(arm_neon_state64_t)); + memcpy(threadCommand.gpregisters, thread->GPRegisters(), sizeof(arm_thread_state64_t)); + memcpy(threadCommand.fpregisters, thread->FPRegisters(), sizeof(arm_neon_state64_t)); #endif m_threadLoadCommands.push_back(threadCommand); } diff --git a/src/coreclr/debug/createdump/shared/dumpwritermacho.h b/src/coreclr/debug/createdump/shared/dumpwritermacho.h new file mode 100644 index 00000000000000..b927de2f6a8a53 --- /dev/null +++ b/src/coreclr/debug/createdump/shared/dumpwritermacho.h @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +struct ThreadCommand +{ + thread_command command; + uint32_t gpflavor; + uint32_t gpcount; + // LC_THREAD state data is a tightly packed array of uint32_t values. Typed + // state structures can add architecture-dependent padding to the command. +#if defined(__x86_64__) + uint32_t gpregisters[x86_THREAD_STATE64_COUNT]; +#elif defined(__aarch64__) + uint32_t gpregisters[ARM_THREAD_STATE64_COUNT]; +#endif + uint32_t fpflavor; + uint32_t fpcount; +#if defined(__x86_64__) + uint32_t fpregisters[x86_FLOAT_STATE64_COUNT]; +#elif defined(__aarch64__) + uint32_t fpregisters[ARM_NEON_STATE64_COUNT]; +#endif +#if defined(__x86_64__) + // 64-bit Mach-O load commands must have a size that is a multiple of 8. + uint32_t padding; +#endif +}; + +// ThreadCommand is serialized directly as an LC_THREAD load command. Verify +// that each state immediately follows its flavor and count without padding. +static_assert(offsetof(ThreadCommand, gpregisters) == sizeof(thread_command) + (2 * sizeof(uint32_t))); +#if defined(__x86_64__) +static_assert(offsetof(ThreadCommand, fpflavor) == offsetof(ThreadCommand, gpregisters) + sizeof(x86_thread_state64_t)); +static_assert(offsetof(ThreadCommand, fpregisters) == offsetof(ThreadCommand, fpflavor) + (2 * sizeof(uint32_t))); +static_assert(offsetof(ThreadCommand, padding) == offsetof(ThreadCommand, fpregisters) + sizeof(x86_float_state64_t)); +static_assert(sizeof(ThreadCommand) == offsetof(ThreadCommand, padding) + sizeof(uint32_t)); +#elif defined(__aarch64__) +static_assert(offsetof(ThreadCommand, fpflavor) == offsetof(ThreadCommand, gpregisters) + sizeof(arm_thread_state64_t)); +static_assert(offsetof(ThreadCommand, fpregisters) == offsetof(ThreadCommand, fpflavor) + (2 * sizeof(uint32_t))); +static_assert(sizeof(ThreadCommand) == offsetof(ThreadCommand, fpregisters) + sizeof(arm_neon_state64_t)); +#endif +static_assert((sizeof(ThreadCommand) & 7) == 0); + +class DumpWriter +{ +private: + int m_fd; + CrashInfo& m_crashInfo; + + std::vector m_segmentLoadCommands; + std::vector m_threadLoadCommands; + note_command m_processMetadataNote{}; + std::string m_processMetadata; + BYTE m_tempBuffer[0x4000]; + + // no public copy constructor + DumpWriter(const DumpWriter&) = delete; + void operator=(const DumpWriter&) = delete; + +public: + DumpWriter(CrashInfo& crashInfo); + virtual ~DumpWriter(); + bool OpenAndWriteDump(const char* dumpFileName); + static bool WriteData(int fd, const void* buffer, size_t length); + +private: + bool OpenDump(const char* dumpFileName); + bool WriteDump(); + bool WriteDiagInfo(size_t size); + void BuildProcessMetadataNote(); + void BuildSegmentLoadCommands(); + void BuildThreadLoadCommands(); + bool WriteHeader(uint64_t* pFileOffset); + bool WriteSegments(); + bool WriteData(const void* buffer, size_t length) { return WriteData(m_fd, buffer, length); } +}; diff --git a/src/coreclr/debug/createdump/memoryregion.h b/src/coreclr/debug/createdump/shared/memoryregion.h similarity index 72% rename from src/coreclr/debug/createdump/memoryregion.h rename to src/coreclr/debug/createdump/shared/memoryregion.h index e828984d105847..f4e839b9c2bc44 100644 --- a/src/coreclr/debug/createdump/memoryregion.h +++ b/src/coreclr/debug/createdump/shared/memoryregion.h @@ -1,9 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#ifndef MEMORYREGION_H +#define MEMORYREGION_H + #include "specialdiaginfo.h" +#include "coreutils.h" -#if !defined(PAGE_SIZE) && (defined(__arm__) || defined(__aarch64__) || defined(__loongarch64)) || defined(__riscv) +#if !defined(PAGE_SIZE) && (defined(__arm__) || defined(__aarch64__) || defined(__loongarch64) || defined(__riscv)) +#define CREATEDUMP_RUNTIME_PAGE_SIZE extern long g_pageSize; #define PAGE_SIZE g_pageSize #endif @@ -11,7 +16,7 @@ extern long g_pageSize; #undef PAGE_MASK #define PAGE_MASK (~(PAGE_SIZE-1)) -#define CONVERT_FROM_SIGN_EXTENDED(offset) ((ULONG_PTR)(offset)) +#define CONVERT_FROM_SIGN_EXTENDED(offset) ((uintptr_t)(offset)) enum MEMORY_REGION_FLAGS : uint32_t { @@ -32,6 +37,14 @@ struct MemoryRegion uint64_t m_offset; public: + MemoryRegion() noexcept : + m_flags(0), + m_startAddress(0), + m_endAddress(0), + m_offset(0) + { + } + MemoryRegion(uint32_t flags, uint64_t start, uint64_t end, uint64_t offset) : m_flags(flags), m_startAddress(start), @@ -119,46 +132,63 @@ struct MemoryRegion struct ModuleRegion : MemoryRegion { private: - std::string m_fileName; + OwnedString m_fileName; public: - ModuleRegion(uint32_t flags, uint64_t start, uint64_t end, uint64_t offset, char* filename) : MemoryRegion(flags, start, end, offset), - m_fileName(filename != nullptr ? filename : "") + ModuleRegion() noexcept : MemoryRegion(0, 0, 0, 0) { } - ModuleRegion(uint32_t flags, uint64_t start, uint64_t end, uint64_t offset, const std::string& filename) : MemoryRegion(flags, start, end, offset), - m_fileName(filename) + ModuleRegion(uint32_t flags, uint64_t start, uint64_t end, uint64_t offset) : + MemoryRegion(flags, start, end, offset) { } - ModuleRegion(uint32_t flags, uint64_t start, uint64_t end, uint64_t offset) : MemoryRegion(flags, start, end, offset) + ModuleRegion(uint32_t flags, uint64_t start, uint64_t end) : + MemoryRegion(flags, start, end) { } - ModuleRegion(uint32_t flags, uint64_t start, uint64_t end) : MemoryRegion(flags, start, end) + ModuleRegion(const MemoryRegion& region) : + MemoryRegion(region) { } - // copy with new file name constructor - ModuleRegion(const ModuleRegion& region, const std::string& fileName) : MemoryRegion(region), - m_fileName(fileName) + ModuleRegion(ModuleRegion&&) noexcept = default; + ModuleRegion& operator=(ModuleRegion&&) noexcept = default; + + ModuleRegion(const ModuleRegion&) = delete; + ModuleRegion& operator=(const ModuleRegion&) = delete; + + const char* FileName() const noexcept { + return m_fileName.CStr(); } - // copy constructor from MemoryRegion - ModuleRegion(const MemoryRegion& region) : MemoryRegion(region) + size_t FileNameLength() const noexcept { + return m_fileName.Length(); } - ~ModuleRegion() + bool FileNameEquals(const char* value) const noexcept { + return strcmp(m_fileName.CStr(), value != nullptr ? value : "") == 0; } - inline const std::string& FileName() const { return m_fileName; } + bool SetFileName(const char* value) noexcept + { + return m_fileName.Assign(value); + } + + void TakeFileNameOwnership(char* value) noexcept + { + m_fileName.TakeOwnership(value); + } void Trace(const char* prefix = "") const { - MemoryRegion::Trace(prefix, m_fileName.c_str()); + MemoryRegion::Trace(prefix, m_fileName.CStr()); } }; + +#endif // MEMORYREGION_H diff --git a/src/coreclr/debug/createdump/shared/processinfo.h b/src/coreclr/debug/createdump/shared/processinfo.h new file mode 100644 index 00000000000000..31f91bbc98cb58 --- /dev/null +++ b/src/coreclr/debug/createdump/shared/processinfo.h @@ -0,0 +1,176 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef PROCESS_READER_H +#define PROCESS_READER_H + +#include +#include + +#ifndef __APPLE__ +// Typedefs for parsing the target process ELF metadata. +#if TARGET_64BIT +typedef Elf64_auxv_t elf_aux_entry; +typedef Elf64_Phdr elf_program_header; +#define PRIx PRIx64 +#define PRIu PRIu64 +#define PRId PRId64 +#define PRIA "016" +#define PRIxA PRIA PRIx +#else +typedef Elf32_auxv_t elf_aux_entry; +typedef Elf32_Phdr elf_program_header; +#define PRIx PRIx32 +#define PRIu PRIu32 +#define PRId PRId32 +#define PRIA "08" +#define PRIxA PRIA PRIx +#endif // __APPLE__ + +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) + +#endif + +#ifndef MAX_LONGPATH +#define MAX_LONGPATH 1024 +#endif + +// To allow both external and linked createdump to share some common algorithms +// we have this class that allows each to supply its container and the necessary callbacks for managing memory regions. +class DumpRegionStore +{ +public: + DumpRegionStore( + void* container, + bool (*findOverlap)(void*, uint64_t, uint64_t, MemoryRegion*), + bool (*insert)(void*, const MemoryRegion*)) noexcept : + m_container(container), + m_findOverlap(findOverlap), + m_insert(insert) + { + } + + bool FindOverlap(uint64_t startAddress, uint64_t endAddress, MemoryRegion* result) const + { + return m_findOverlap(m_container, startAddress, endAddress, result); + } + + bool Insert(const MemoryRegion* region) const + { + return m_insert(m_container, region); + } + + void* Container() const { return m_container; } + +private: + void* m_container; + + bool (*m_findOverlap)( + void* container, + uint64_t startAddress, + uint64_t endAddress, + MemoryRegion* result); + + bool (*m_insert)( + void* container, + const MemoryRegion* region); +}; + +// Process snapshot containing all data needed for dump generation +class ProcessInfo +{ + pid_t m_pid; // pid + pid_t m_ppid; // parent pid + pid_t m_tgid; // process group + uint64_t m_runtimeBaseAddress; // base address of the runtime module + + char m_exeName[MAX_LONGPATH]; // prefer a constant here + int m_crashSignal; // crash signal code or 0 if none + pid_t m_crashThread; // crashing thread id or 0 if none + siginfo_t m_siginfo; // signal info (if any) + uint64_t m_exceptionRecord; // exception record address or 0 if none + +#ifdef __APPLE__ + vm_map_t m_task = 0; // the mach task for the process +#else + int m_fdMem = -1; // /proc//mem handle + int m_fdPagemap = -1; // /proc//pagemap handle + bool m_canUseProcVmReadSyscall; + DynamicArray m_auxvEntries; // full auxv entries + elf_aux_val_t m_auxvValues[AT_MAX]{}; // auxv values +#endif + DynamicArray m_threads; // threads found and suspended + DynamicArray m_moduleMappings; // module memory mappings + DynamicArray m_otherMappings; // other memory mappings + +public: + explicit ProcessInfo(const CreateDumpOptions& options) noexcept : + m_pid(options.Pid), + m_ppid(0), + m_tgid(0), + m_runtimeBaseAddress(0), + m_exeName{}, + m_crashSignal(options.Signal), + m_crashThread(options.CrashThread), + m_exceptionRecord(options.ExceptionRecord) + { + memset(&m_siginfo, 0, sizeof(m_siginfo)); + m_siginfo.si_signo = options.Signal; + m_siginfo.si_code = options.SignalCode; + m_siginfo.si_errno = options.SignalErrno; + m_siginfo.si_addr = (void*)options.SignalAddress; + } + + bool Initialize(); + void CleanupAndResumeProcess(); + bool EnumerateAndSuspendThreads(); + bool GatherCrashInfo(DumpRegionStore& regionStore); + bool SelectDumpRegions(DumpRegionStore& regionStore, DumpType dumpType); + bool ReadProcessMemory(uint64_t address, void* buffer, size_t size, size_t* read); + bool AddMapping(const MemoryRegion& region); + bool AddMapping(const ModuleRegion& region); + int InsertMemoryRegion(DumpRegionStore& regionStore, const MemoryRegion& memoryRegion); +#ifndef __APPLE__ + void CalculateRuntimeBaseAddress(); +#endif + + pid_t Pid() const { return m_pid; } + pid_t Ppid() const { return m_ppid; } + pid_t Tgid() const { return m_tgid; } + pid_t CrashThread() const { return m_crashThread; } + int Signal() const { return m_crashSignal; } + const siginfo_t* SigInfo() const { return &m_siginfo; } + uint64_t ExceptionRecord() const { return m_exceptionRecord; } + uint64_t RuntimeBaseAddress() const { return m_runtimeBaseAddress; } + const char* Name() const { return m_exeName; } + void SetRuntimeBaseAddress(uint64_t address) { m_runtimeBaseAddress = address; } +#ifdef __APPLE__ + vm_map_t Task() const { return m_task; } +#endif + void SetName(const char* name) + { + strncpy(m_exeName, name != nullptr ? name : "", sizeof(m_exeName)); + m_exeName[sizeof(m_exeName) - 1] = '\0'; + } + + DynamicArray& Threads() noexcept { return m_threads; } + const DynamicArray& Threads() const noexcept { return m_threads; } + const DynamicArray& ModuleMappings() const noexcept { return m_moduleMappings; } + const DynamicArray& OtherMappings() const noexcept { return m_otherMappings; } +#ifndef __APPLE__ + const DynamicArray& AuxvEntries() const noexcept { return m_auxvEntries; } + size_t GetAuxvSize() const { return m_auxvEntries.Count() * sizeof(elf_aux_entry); } + elf_aux_val_t AuxvValue(size_t index) const { return m_auxvValues[index]; } +#endif + +private: + bool EnumerateMemoryRegions(DumpRegionStore& regionStore); + bool GetAuxvEntries(); + bool PageCanBeRead(uint64_t start); + bool PageMappedToPhysicalMemory(uint64_t start); +}; + +#endif // PROCESS_READER_H \ No newline at end of file diff --git a/src/coreclr/debug/createdump/specialdiaginfo.h b/src/coreclr/debug/createdump/shared/specialdiaginfo.h similarity index 100% rename from src/coreclr/debug/createdump/specialdiaginfo.h rename to src/coreclr/debug/createdump/shared/specialdiaginfo.h diff --git a/src/coreclr/debug/createdump/shared/threadsnapshot.cpp b/src/coreclr/debug/createdump/shared/threadsnapshot.cpp new file mode 100644 index 00000000000000..c294220cc85d11 --- /dev/null +++ b/src/coreclr/debug/createdump/shared/threadsnapshot.cpp @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "createdumpcore.h" +#include "threadsnapshot.h" + +#ifndef __GLIBC__ +typedef int __ptrace_request; +#endif + +bool ThreadSnapshot::Initialize() +{ + if (!GetStatus(m_tid, &m_ppid, &m_tgid, nullptr, 0)) + { + 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); +#elif defined(__loongarch64) + TRACE("Thread %04x PC %016llx SP %016llx\n", m_tid, (unsigned long long)m_gpRegisters.csr_era, (unsigned long long)m_gpRegisters.regs[3]); +#elif defined(__riscv) + TRACE("Thread %04x PC %016llx SP %016llx\n", m_tid, (unsigned long long)m_gpRegisters.pc, (unsigned long long)m_gpRegisters.sp); +#else +#error "Unsupported architecture" +#endif + return true; +} + +bool ThreadSnapshot::GetRegistersWithPTrace() +{ + struct iovec gpRegsVec = { &m_gpRegisters, sizeof(m_gpRegisters) }; + if (ptrace((__ptrace_request)PTRACE_GETREGSET, m_tid, NT_PRSTATUS, &gpRegsVec) == -1) + { + printf_error("ptrace(PTRACE_GETREGSET, %d, NT_PRSTATUS) FAILED %s (%d)\n", m_tid, strerror(errno), 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 + printf_error("ptrace(PTRACE_GETREGSET, %d, NT_FPREGSET) FAILED %s (%d)\n", m_tid, strerror(errno), 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) + { + printf_error("ptrace(GETFPXREGS, %d) FAILED %s (%d)\n", m_tid, strerror(errno), 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) + { + printf_error("ptrace(PTRACE_GETVFPREGS, %d) FAILED %s (%d)\n", m_tid, strerror(errno), errno); + return false; + } +#endif + return true; +} \ No newline at end of file diff --git a/src/coreclr/debug/createdump/shared/threadsnapshot.h b/src/coreclr/debug/createdump/shared/threadsnapshot.h new file mode 100644 index 00000000000000..b0c5ebda4a4134 --- /dev/null +++ b/src/coreclr/debug/createdump/shared/threadsnapshot.h @@ -0,0 +1,172 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef THREAD_INFO_CORE_H +#define THREAD_INFO_CORE_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 + +#if defined(__loongarch64) +// See src/coreclr/pal/src/include/pal/context.h +#define MCREG_Ra(mc) ((mc).regs[1]) +#define MCREG_Fp(mc) ((mc).regs[22]) +#define MCREG_Sp(mc) ((mc).regs[3]) +#define MCREG_Pc(mc) ((mc).csr_era) +#endif + +#if defined(__riscv) +// See src/coreclr/pal/src/include/pal/context.h +#define MCREG_Ra(mc) ((mc).ra) +#define MCREG_Fp(mc) ((mc).s0) +#define MCREG_Sp(mc) ((mc).sp) +#define MCREG_Pc(mc) ((mc).pc) +#endif + +#define FPREG_ErrorOffset(fpregs) *(uint32_t*)&((fpregs).rip) +#define FPREG_ErrorSelector(fpregs) *(((uint16_t*)&((fpregs).rip)) + 2) +#define FPREG_DataOffset(fpregs) *(uint32_t*)&((fpregs).rdp) +#define FPREG_DataSelector(fpregs) *(((uint16_t*)&((fpregs).rdp)) + 2) +#if defined(__arm__) +#define user_regs_struct user_regs +#define user_fpregs_struct user_fpregs +#elif defined(__riscv) +struct user_fpregs_struct +{ + unsigned long long fpregs[32]; + unsigned long fcsr; +} __attribute__((__packed__)); +#endif + +#if defined(__aarch64__) +#define user_fpregs_struct user_fpsimd_struct +#endif + +#if defined(__arm__) && defined(__VFP_FP__) && !defined(__SOFTFP__) +struct user_vfpregs_struct +{ + unsigned long long fpregs[32]; + unsigned long fpscr; +} __attribute__((__packed__)); +#endif + +#if defined(__loongarch64) +#define user_fpregs_struct lasx_context +#endif + +class ThreadInfo; + +// Thread info +class ThreadSnapshot +{ + friend class ThreadInfo; + +private: + 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 +#if defined(__x86_64__) + x86_thread_state64_t m_gpRegisters; // MacOS general purpose registers + x86_float_state64_t m_fpRegisters; // MacOS floating point registers +#elif defined(__aarch64__) + arm_thread_state64_t m_gpRegisters; // MacOS general purpose arm64 registers + arm_neon_state64_t m_fpRegisters; // MacOS floating point arm64 registers +#endif +#else // __APPLE__ + struct user_regs_struct m_gpRegisters; // general purpose registers + struct user_fpregs_struct m_fpRegisters; // floating point registersReal +#if defined(__i386__) + struct user_fpxregs_struct m_fpxRegisters; // x86 floating point registers +#elif defined(__arm__) && defined(__VFP_FP__) && !defined(__SOFTFP__) + struct user_vfpregs_struct m_vfpRegisters; // ARM VFP/NEON registers +#endif +#endif // __APPLE__ + +public: + ThreadSnapshot() noexcept = default; + + explicit ThreadSnapshot(pid_t tid) noexcept : + m_tid(tid), + m_ppid(0), + m_tgid(0) + { + } + +#ifdef __APPLE__ + inline mach_port_t Port() const { return m_port; } + ThreadSnapshot(pid_t tid, mach_port_t port) noexcept : + m_tid(tid), + m_ppid(0), + m_tgid(0), + m_port(port) + { + } +#endif + + bool Initialize(); + + inline pid_t Tid() const { return m_tid; } + inline pid_t Ppid() const { return m_ppid; } + inline pid_t Tgid() const { return m_tgid; } + +#ifdef __APPLE__ +#if defined(__x86_64__) + inline const x86_thread_state64_t* GPRegisters() const { return &m_gpRegisters; } + inline const x86_float_state64_t* FPRegisters() const { return &m_fpRegisters; } + inline const uint64_t GetInstructionPointer() const { return m_gpRegisters.__rip; } + inline const uint64_t GetFramePointer() const { return m_gpRegisters.__rbp; } + inline const uint64_t GetStackPointer() const { return m_gpRegisters.__rsp; } +#elif defined(__aarch64__) + inline const arm_thread_state64_t* GPRegisters() const { return &m_gpRegisters; } + inline const arm_neon_state64_t* FPRegisters() const { return &m_fpRegisters; } + inline const uint64_t GetInstructionPointer() const { return arm_thread_state64_get_pc(m_gpRegisters); } + inline const uint64_t GetFramePointer() const { return arm_thread_state64_get_fp(m_gpRegisters); } + inline const uint64_t GetStackPointer() const { return arm_thread_state64_get_sp(m_gpRegisters); } +#endif +#else // __APPLE__ + inline const user_regs_struct* GPRegisters() const { return &m_gpRegisters; } + inline const user_fpregs_struct* FPRegisters() const { return &m_fpRegisters; } +#if defined(__i386__) + inline const user_fpxregs_struct* FPXRegisters() const { return &m_fpxRegisters; } +#elif defined(__arm__) && defined(__VFP_FP__) && !defined(__SOFTFP__) + inline const user_vfpregs_struct* VFPRegisters() const { return &m_vfpRegisters; } +#endif +#if defined(__x86_64__) + inline const uint64_t GetInstructionPointer() const { return m_gpRegisters.rip; } + inline const uint64_t GetStackPointer() const { return m_gpRegisters.rsp; } + inline const uint64_t GetFramePointer() const { return m_gpRegisters.rbp; } +#elif defined(__aarch64__) + inline const uint64_t GetInstructionPointer() const { return MCREG_Pc(m_gpRegisters); } + inline const uint64_t GetStackPointer() const { return MCREG_Sp(m_gpRegisters); } + inline const uint64_t GetFramePointer() const { return MCREG_Fp(m_gpRegisters); } +#elif defined(__loongarch64) + inline const uint64_t GetInstructionPointer() const { return MCREG_Pc(m_gpRegisters); } + inline const uint64_t GetStackPointer() const { return MCREG_Sp(m_gpRegisters); } + inline const uint64_t GetFramePointer() const { return MCREG_Fp(m_gpRegisters); } +#elif defined(__arm__) + inline const uint64_t GetInstructionPointer() const { return m_gpRegisters.ARM_pc; } + inline const uint64_t GetStackPointer() const { return m_gpRegisters.ARM_sp; } + inline const uint64_t GetFramePointer() const { return m_gpRegisters.ARM_fp; } +#elif defined(__riscv) + inline const uint64_t GetInstructionPointer() const { return MCREG_Pc(m_gpRegisters); } + inline const uint64_t GetStackPointer() const { return MCREG_Sp(m_gpRegisters); } + inline const uint64_t GetFramePointer() const { return MCREG_Fp(m_gpRegisters); } +#endif +#endif // __APPLE__ + +private: +#ifndef __APPLE__ + bool GetRegistersWithPTrace(); +#endif +}; + +#endif // THREAD_INFO_CORE_H \ No newline at end of file diff --git a/src/coreclr/debug/createdump/threadinfo.cpp b/src/coreclr/debug/createdump/threadinfo.cpp index 54ece183dad2a1..a3fc14e96022cb 100644 --- a/src/coreclr/debug/createdump/threadinfo.cpp +++ b/src/coreclr/debug/createdump/threadinfo.cpp @@ -7,10 +7,6 @@ #define THUMB_CODE 1 #endif -#ifndef __GLIBC__ -typedef int __ptrace_request; -#endif - extern CrashInfo* g_crashInfo; // Helper for UnwindNativeFrames @@ -273,7 +269,7 @@ ThreadInfo::GatherStackFrames(CONTEXT* pContext, IXCLRDataStackWalk* pStackwalk) { TRACE("Unwind: GetTokenAndScope sp %p ip %p FAILED\n", (void*)sp, (void*)ip); } - if (FAILED(pMethod->GetILOffsetsByAddress(ip, 1, NULL, &ilOffset))) + if (FAILED(pMethod->GetILOffsetsByAddress(ip, 1, nullptr, &ilOffset))) { TRACE("Unwind: GetILOffsetsByAddress sp %p ip %p FAILED\n", (void*)sp, (void*)ip); } @@ -395,7 +391,7 @@ ThreadInfo::GetThreadStack() if (g_diagnostics) { - TRACE("Thread %04x stack found in other mapping (size %08zx): ", m_tid, size); + TRACE("Thread %04x stack found in other mapping (size %08zx): ", Tid(), size); region->Trace(); } } @@ -403,12 +399,12 @@ ThreadInfo::GetThreadStack() } else { - TRACE("Thread %04x null stack pointer\n", m_tid); + TRACE("Thread %04x null stack pointer\n", Tid()); } } bool ThreadInfo::IsCrashThread() const { - return m_tid == m_crashInfo.CrashThread(); + return Tid() == m_crashInfo.CrashThread(); } diff --git a/src/coreclr/debug/createdump/threadinfo.h b/src/coreclr/debug/createdump/threadinfo.h index 31e22b46caaa42..178493923388c1 100644 --- a/src/coreclr/debug/createdump/threadinfo.h +++ b/src/coreclr/debug/createdump/threadinfo.h @@ -3,71 +3,14 @@ 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 - -#if defined(__loongarch64) -// See src/coreclr/pal/src/include/pal/context.h -#define MCREG_Ra(mc) ((mc).regs[1]) -#define MCREG_Fp(mc) ((mc).regs[22]) -#define MCREG_Sp(mc) ((mc).regs[3]) -#define MCREG_Pc(mc) ((mc).csr_era) -#endif - -#if defined(__riscv) -// See src/coreclr/pal/src/include/pal/context.h -#define MCREG_Ra(mc) ((mc).ra) -#define MCREG_Fp(mc) ((mc).s0) -#define MCREG_Sp(mc) ((mc).sp) -#define MCREG_Pc(mc) ((mc).pc) -#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 -#elif defined(__riscv) -struct user_fpregs_struct -{ - unsigned long long fpregs[32]; - unsigned long fcsr; -} __attribute__((__packed__)); -#endif - -#if defined(__aarch64__) -#define user_fpregs_struct user_fpsimd_struct -#endif - -#if defined(__arm__) && defined(__VFP_FP__) && !defined(__SOFTFP__) -struct user_vfpregs_struct -{ - unsigned long long fpregs[32]; - unsigned long fpscr; -} __attribute__((__packed__)); -#endif - -#if defined(__loongarch64) -#define user_fpregs_struct lasx_context -#endif - #define STACK_OVERFLOW_EXCEPTION 0x800703e9 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 + ThreadSnapshot m_snapshot; // thread snapshot + bool m_managed; // if true, thread has managed code running uint64_t m_exceptionObject; // exception object address std::string m_exceptionType; // exception type @@ -77,45 +20,20 @@ class ThreadInfo std::set::const_iterator m_beginRepeat; // beginning of stack overflow repeated frame sequence std::set::const_iterator m_endRepeat; // end of repeated frame sequence -#ifdef __APPLE__ - mach_port_t m_port; // MacOS thread port -#if defined(__x86_64__) - x86_thread_state64_t m_gpRegisters; // MacOS general purpose registers - x86_float_state64_t m_fpRegisters; // MacOS floating point registers -#elif defined(__aarch64__) - arm_thread_state64_t m_gpRegisters; // MacOS general purpose arm64 registers - arm_neon_state64_t m_fpRegisters; // MacOS floating point arm64 registers -#endif -#else // __APPLE__ - struct user_regs_struct m_gpRegisters; // general purpose registers - struct user_fpregs_struct m_fpRegisters; // floating point registers -#if defined(__i386__) - struct user_fpxregs_struct m_fpxRegisters; // x86 floating point registers -#elif defined(__arm__) && defined(__VFP_FP__) && !defined(__SOFTFP__) - struct user_vfpregs_struct m_vfpRegisters; // ARM VFP/NEON registers -#endif -#endif // __APPLE__ - // no public copy constructor ThreadInfo(const ThreadInfo&) = delete; void operator=(const ThreadInfo&) = delete; public: -#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(CrashInfo& crashInfo, const ThreadSnapshot& snapshot); ~ThreadInfo(); - bool Initialize(); bool UnwindThread(IXCLRDataProcess* pClrDataProcess, ISOSDacInterface* pSos); void GetThreadStack(); void GetThreadContext(uint32_t flags, CONTEXT* context) const; - inline pid_t Tid() const { return m_tid; } - inline pid_t Ppid() const { return m_ppid; } - inline pid_t Tgid() const { return m_tgid; } + inline pid_t Tid() const { return m_snapshot.Tid(); } + inline pid_t Ppid() const { return m_snapshot.Ppid(); } + inline pid_t Tgid() const { return m_snapshot.Tgid(); } inline bool IsManaged() const { return m_managed; } inline uint64_t ManagedExceptionObject() const { return m_exceptionObject; } @@ -128,55 +46,28 @@ class ThreadInfo #ifdef __APPLE__ #if defined(__x86_64__) - inline const x86_thread_state64_t* GPRegisters() const { return &m_gpRegisters; } - inline const x86_float_state64_t* FPRegisters() const { return &m_fpRegisters; } - inline const uint64_t GetInstructionPointer() const { return m_gpRegisters.__rip; } - inline const uint64_t GetFramePointer() const { return m_gpRegisters.__rbp; } - inline const uint64_t GetStackPointer() const { return m_gpRegisters.__rsp; } + inline const x86_thread_state64_t* GPRegisters() const { return m_snapshot.GPRegisters(); } + inline const x86_float_state64_t* FPRegisters() const { return m_snapshot.FPRegisters(); } #elif defined(__aarch64__) - inline const arm_thread_state64_t* GPRegisters() const { return &m_gpRegisters; } - inline const arm_neon_state64_t* FPRegisters() const { return &m_fpRegisters; } - inline const uint64_t GetInstructionPointer() const { return arm_thread_state64_get_pc(m_gpRegisters); } - inline const uint64_t GetFramePointer() const { return arm_thread_state64_get_fp(m_gpRegisters); } - inline const uint64_t GetStackPointer() const { return arm_thread_state64_get_sp(m_gpRegisters); } + inline const arm_thread_state64_t* GPRegisters() const { return m_snapshot.GPRegisters(); } + inline const arm_neon_state64_t* FPRegisters() const { return m_snapshot.FPRegisters(); } #endif -#else // __APPLE__ - inline const user_regs_struct* GPRegisters() const { return &m_gpRegisters; } - inline const user_fpregs_struct* FPRegisters() const { return &m_fpRegisters; } +#else + inline const user_regs_struct* GPRegisters() const { return m_snapshot.GPRegisters(); } + inline const user_fpregs_struct* FPRegisters() const { return m_snapshot.FPRegisters(); } #if defined(__i386__) - inline const user_fpxregs_struct* FPXRegisters() const { return &m_fpxRegisters; } + inline const user_fpxregs_struct* FPXRegisters() const { return m_snapshot.FPXRegisters(); } #elif defined(__arm__) && defined(__VFP_FP__) && !defined(__SOFTFP__) - inline const user_vfpregs_struct* VFPRegisters() const { return &m_vfpRegisters; } -#endif -#if defined(__x86_64__) - inline const uint64_t GetInstructionPointer() const { return m_gpRegisters.rip; } - inline const uint64_t GetStackPointer() const { return m_gpRegisters.rsp; } - inline const uint64_t GetFramePointer() const { return m_gpRegisters.rbp; } -#elif defined(__aarch64__) - inline const uint64_t GetInstructionPointer() const { return MCREG_Pc(m_gpRegisters); } - inline const uint64_t GetStackPointer() const { return MCREG_Sp(m_gpRegisters); } - inline const uint64_t GetFramePointer() const { return MCREG_Fp(m_gpRegisters); } -#elif defined(__loongarch64) - inline const uint64_t GetInstructionPointer() const { return MCREG_Pc(m_gpRegisters); } - inline const uint64_t GetStackPointer() const { return MCREG_Sp(m_gpRegisters); } - inline const uint64_t GetFramePointer() const { return MCREG_Fp(m_gpRegisters); } -#elif defined(__arm__) - inline const uint64_t GetInstructionPointer() const { return m_gpRegisters.ARM_pc; } - inline const uint64_t GetStackPointer() const { return m_gpRegisters.ARM_sp; } - inline const uint64_t GetFramePointer() const { return m_gpRegisters.ARM_fp; } -#elif defined(__riscv) - inline const uint64_t GetInstructionPointer() const { return MCREG_Pc(m_gpRegisters); } - inline const uint64_t GetStackPointer() const { return MCREG_Sp(m_gpRegisters); } - inline const uint64_t GetFramePointer() const { return MCREG_Fp(m_gpRegisters); } + inline const user_vfpregs_struct* VFPRegisters() const { return m_snapshot.VFPRegisters(); } #endif #endif // __APPLE__ + inline uint64_t GetInstructionPointer() const { return m_snapshot.GetInstructionPointer(); } + inline uint64_t GetStackPointer() const { return m_snapshot.GetStackPointer(); } + inline uint64_t GetFramePointer() const { return m_snapshot.GetFramePointer(); } bool IsCrashThread() const; private: void UnwindNativeFrames(CONTEXT* pContext); void GatherStackFrames(CONTEXT* pContext, IXCLRDataStackWalk* pStackwalk); void AddStackFrame(const StackFrame& frame); -#ifndef __APPLE__ - bool GetRegistersWithPTrace(); -#endif }; diff --git a/src/coreclr/debug/createdump/threadinfomac.cpp b/src/coreclr/debug/createdump/threadinfomac.cpp index cc4e23ea3cfa02..732357cb4b3428 100644 --- a/src/coreclr/debug/createdump/threadinfomac.cpp +++ b/src/coreclr/debug/createdump/threadinfomac.cpp @@ -3,16 +3,13 @@ #include "createdump.h" -ThreadInfo::ThreadInfo(CrashInfo& crashInfo, pid_t tid, mach_port_t port) : +ThreadInfo::ThreadInfo(CrashInfo& crashInfo, const ThreadSnapshot& snapshot) : m_crashInfo(crashInfo), - m_tid(tid), - m_ppid(0), - m_tgid(0), + m_snapshot(snapshot), m_managed(false), m_exceptionObject(0), m_exceptionHResult(0), - m_repeatedFrames(0), - m_port(port) + m_repeatedFrames(0) { m_beginRepeat = m_frames.end(); m_endRepeat = m_frames.end(); @@ -20,18 +17,11 @@ ThreadInfo::ThreadInfo(CrashInfo& crashInfo, pid_t tid, mach_port_t port) : ThreadInfo::~ThreadInfo() { - kern_return_t result = ::mach_port_deallocate(mach_task_self(), m_port); - if (result != KERN_SUCCESS) - { - printf_error("Internal error: ~ThreadInfo: mach_port_deallocate FAILED %s (%x)\n", mach_error_string(result), result); - } } bool -ThreadInfo::Initialize() +ThreadSnapshot::Initialize() { - m_ppid = 0; - m_tgid = 0; #if defined(__x86_64__) mach_msg_type_number_t stateCount = x86_THREAD_STATE64_COUNT; @@ -78,83 +68,83 @@ ThreadInfo::GetThreadContext(uint32_t flags, CONTEXT* context) const #if defined(__x86_64__) if ((flags & CONTEXT_CONTROL) == CONTEXT_CONTROL) { - context->Rbp = m_gpRegisters.__rbp; - context->Rip = m_gpRegisters.__rip; - context->SegCs = m_gpRegisters.__cs; - context->EFlags = m_gpRegisters.__rflags; + context->Rbp = m_snapshot.m_gpRegisters.__rbp; + context->Rip = m_snapshot.m_gpRegisters.__rip; + context->SegCs = m_snapshot.m_gpRegisters.__cs; + context->EFlags = m_snapshot.m_gpRegisters.__rflags; // TODO: get "full" register state for the segment regs context->SegSs = 0; // m_gpRegisters.__ss; - context->Rsp = m_gpRegisters.__rsp; + context->Rsp = m_snapshot.m_gpRegisters.__rsp; } if ((flags & CONTEXT_INTEGER) == CONTEXT_INTEGER) { - context->Rdi = m_gpRegisters.__rdi; - context->Rsi = m_gpRegisters.__rsi; - context->Rbx = m_gpRegisters.__rbx; - context->Rdx = m_gpRegisters.__rdx; - context->Rcx = m_gpRegisters.__rcx; - context->Rax = m_gpRegisters.__rax; - context->R8 = m_gpRegisters.__r8; - context->R9 = m_gpRegisters.__r9; - context->R10 = m_gpRegisters.__r10; - context->R11 = m_gpRegisters.__r11; - context->R12 = m_gpRegisters.__r12; - context->R13 = m_gpRegisters.__r13; - context->R14 = m_gpRegisters.__r14; - context->R15 = m_gpRegisters.__r15; + context->Rdi = m_snapshot.m_gpRegisters.__rdi; + context->Rsi = m_snapshot.m_gpRegisters.__rsi; + context->Rbx = m_snapshot.m_gpRegisters.__rbx; + context->Rdx = m_snapshot.m_gpRegisters.__rdx; + context->Rcx = m_snapshot.m_gpRegisters.__rcx; + context->Rax = m_snapshot.m_gpRegisters.__rax; + context->R8 = m_snapshot.m_gpRegisters.__r8; + context->R9 = m_snapshot.m_gpRegisters.__r9; + context->R10 = m_snapshot.m_gpRegisters.__r10; + context->R11 = m_snapshot.m_gpRegisters.__r11; + context->R12 = m_snapshot.m_gpRegisters.__r12; + context->R13 = m_snapshot.m_gpRegisters.__r13; + context->R14 = m_snapshot.m_gpRegisters.__r14; + context->R15 = m_snapshot.m_gpRegisters.__r15; } if ((flags & CONTEXT_SEGMENTS) == CONTEXT_SEGMENTS) { // TODO: get "full" register state for the segment regs context->SegDs = 0; // m_gpRegisters.__ds; context->SegEs = 0; // m_gpRegisters.__es; - context->SegFs = m_gpRegisters.__fs; - context->SegGs = m_gpRegisters.__gs; + context->SegFs = m_snapshot.m_gpRegisters.__fs; + context->SegGs = m_snapshot.m_gpRegisters.__gs; } if ((flags & CONTEXT_FLOATING_POINT) == CONTEXT_FLOATING_POINT) { - context->FltSave.ControlWord = *((unsigned short *)&m_fpRegisters.__fpu_fcw); - context->FltSave.StatusWord = *((unsigned short *)&m_fpRegisters.__fpu_fsw); - context->FltSave.TagWord = m_fpRegisters.__fpu_ftw; - context->FltSave.ErrorOpcode = m_fpRegisters.__fpu_fop; + context->FltSave.ControlWord = *((unsigned short *)&m_snapshot.m_fpRegisters.__fpu_fcw); + context->FltSave.StatusWord = *((unsigned short *)&m_snapshot.m_fpRegisters.__fpu_fsw); + context->FltSave.TagWord = m_snapshot.m_fpRegisters.__fpu_ftw; + context->FltSave.ErrorOpcode = m_snapshot.m_fpRegisters.__fpu_fop; - context->FltSave.ErrorOffset = m_fpRegisters.__fpu_ip; - context->FltSave.ErrorSelector = m_fpRegisters.__fpu_cs; - context->FltSave.DataOffset = m_fpRegisters.__fpu_dp; - context->FltSave.DataSelector = m_fpRegisters.__fpu_ds; + context->FltSave.ErrorOffset = m_snapshot.m_fpRegisters.__fpu_ip; + context->FltSave.ErrorSelector = m_snapshot.m_fpRegisters.__fpu_cs; + context->FltSave.DataOffset = m_snapshot.m_fpRegisters.__fpu_dp; + context->FltSave.DataSelector = m_snapshot.m_fpRegisters.__fpu_ds; - context->FltSave.MxCsr = m_fpRegisters.__fpu_mxcsr; - context->FltSave.MxCsr_Mask = m_fpRegisters.__fpu_mxcsrmask; + context->FltSave.MxCsr = m_snapshot.m_fpRegisters.__fpu_mxcsr; + context->FltSave.MxCsr_Mask = m_snapshot.m_fpRegisters.__fpu_mxcsrmask; - assert(sizeof(context->FltSave.FloatRegisters) == sizeof(m_fpRegisters.__fpu_stmm0) * 8); - memcpy(context->FltSave.FloatRegisters, &m_fpRegisters.__fpu_stmm0, sizeof(context->FltSave.FloatRegisters)); + assert(sizeof(context->FltSave.FloatRegisters) == sizeof(m_snapshot.m_fpRegisters.__fpu_stmm0) * 8); + memcpy(context->FltSave.FloatRegisters, &m_snapshot.m_fpRegisters.__fpu_stmm0, sizeof(context->FltSave.FloatRegisters)); - assert(sizeof(context->FltSave.XmmRegisters) == sizeof(m_fpRegisters.__fpu_xmm0) * 16); - memcpy(context->FltSave.XmmRegisters, &m_fpRegisters.__fpu_xmm0, sizeof(context->FltSave.XmmRegisters)); + assert(sizeof(context->FltSave.XmmRegisters) == sizeof(m_snapshot.m_fpRegisters.__fpu_xmm0) * 16); + memcpy(context->FltSave.XmmRegisters, &m_snapshot.m_fpRegisters.__fpu_xmm0, sizeof(context->FltSave.XmmRegisters)); } // TODO: debug registers? #elif defined(__aarch64__) if ((flags & CONTEXT_CONTROL) == CONTEXT_CONTROL) { - context->Fp = arm_thread_state64_get_fp(m_gpRegisters); - context->Lr = (uint64_t)arm_thread_state64_get_lr_fptr(m_gpRegisters); - context->Sp = arm_thread_state64_get_sp(m_gpRegisters); - context->Pc = (uint64_t)arm_thread_state64_get_pc_fptr(m_gpRegisters); - context->Cpsr = m_gpRegisters.__cpsr; + context->Fp = arm_thread_state64_get_fp(m_snapshot.m_gpRegisters); + context->Lr = (uint64_t)arm_thread_state64_get_lr_fptr(m_snapshot.m_gpRegisters); + context->Sp = arm_thread_state64_get_sp(m_snapshot.m_gpRegisters); + context->Pc = (uint64_t)arm_thread_state64_get_pc_fptr(m_snapshot.m_gpRegisters); + context->Cpsr = m_snapshot.m_gpRegisters.__cpsr; } if ((flags & CONTEXT_INTEGER) == CONTEXT_INTEGER) { - assert(sizeof(m_gpRegisters.__x) == (sizeof(context->X))); - memcpy(context->X, m_gpRegisters.__x, sizeof(context->X)); + assert(sizeof(m_snapshot.m_gpRegisters.__x) == (sizeof(context->X))); + memcpy(context->X, m_snapshot.m_gpRegisters.__x, sizeof(context->X)); } if ((flags & CONTEXT_FLOATING_POINT) == CONTEXT_FLOATING_POINT) { - assert(sizeof(m_fpRegisters.__v) == sizeof(context->V)); - memcpy(context->V, m_fpRegisters.__v, sizeof(context->V)); - context->Fpcr = m_fpRegisters.__fpcr; - context->Fpsr = m_fpRegisters.__fpsr; + assert(sizeof(m_snapshot.m_fpRegisters.__v) == sizeof(context->V)); + memcpy(context->V, m_snapshot.m_fpRegisters.__v, sizeof(context->V)); + context->Fpcr = m_snapshot.m_fpRegisters.__fpcr; + context->Fpsr = m_snapshot.m_fpRegisters.__fpsr; } #else #error Platform not supported #endif -} +} \ No newline at end of file diff --git a/src/coreclr/debug/createdump/threadinfounix.cpp b/src/coreclr/debug/createdump/threadinfounix.cpp index 3fc9867c9ac79f..a279c03d8046ae 100644 --- a/src/coreclr/debug/createdump/threadinfounix.cpp +++ b/src/coreclr/debug/createdump/threadinfounix.cpp @@ -22,11 +22,9 @@ typedef int __ptrace_request; bool GetStatus(pid_t pid, pid_t* ppid, pid_t* tgid, std::string* name); -ThreadInfo::ThreadInfo(CrashInfo& crashInfo, pid_t tid) : +ThreadInfo::ThreadInfo(CrashInfo& crashInfo, const ThreadSnapshot& snapshot) : m_crashInfo(crashInfo), - m_tid(tid), - m_ppid(0), - m_tgid(0), + m_snapshot(snapshot), m_managed(false), m_exceptionObject(0), m_exceptionHResult(0), @@ -40,77 +38,6 @@ 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); -#elif defined(__loongarch64) - TRACE("Thread %04x PC %016llx SP %016llx\n", m_tid, (unsigned long long)m_gpRegisters.csr_era, (unsigned long long)m_gpRegisters.regs[3]); -#elif defined(__riscv) - TRACE("Thread %04x PC %016llx SP %016llx\n", m_tid, (unsigned long long)m_gpRegisters.pc, (unsigned long long)m_gpRegisters.sp); -#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) - { - printf_error("ptrace(PTRACE_GETREGSET, %d, NT_PRSTATUS) FAILED %s (%d)\n", m_tid, strerror(errno), 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 - printf_error("ptrace(PTRACE_GETREGSET, %d, NT_FPREGSET) FAILED %s (%d)\n", m_tid, strerror(errno), 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) - { - printf_error("ptrace(GETFPXREGS, %d) FAILED %s (%d)\n", m_tid, strerror(errno), 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) - { - printf_error("ptrace(PTRACE_GETVFPREGS, %d) FAILED %s (%d)\n", m_tid, strerror(errno), errno); - return false; - } -#endif - return true; -} - void ThreadInfo::GetThreadContext(uint32_t flags, CONTEXT* context) const { @@ -118,178 +45,178 @@ ThreadInfo::GetThreadContext(uint32_t flags, CONTEXT* context) const #if defined(__x86_64__) if ((flags & CONTEXT_CONTROL) == CONTEXT_CONTROL) { - context->Rbp = m_gpRegisters.rbp; - context->Rip = m_gpRegisters.rip; - context->SegCs = m_gpRegisters.cs; - context->EFlags = m_gpRegisters.eflags; - context->SegSs = m_gpRegisters.ss; - context->Rsp = m_gpRegisters.rsp; + context->Rbp = m_snapshot.m_gpRegisters.rbp; + context->Rip = m_snapshot.m_gpRegisters.rip; + context->SegCs = m_snapshot.m_gpRegisters.cs; + context->EFlags = m_snapshot.m_gpRegisters.eflags; + context->SegSs = m_snapshot.m_gpRegisters.ss; + context->Rsp = m_snapshot.m_gpRegisters.rsp; } if ((flags & CONTEXT_INTEGER) == CONTEXT_INTEGER) { - context->Rdi = m_gpRegisters.rdi; - context->Rsi = m_gpRegisters.rsi; - context->Rbx = m_gpRegisters.rbx; - context->Rdx = m_gpRegisters.rdx; - context->Rcx = m_gpRegisters.rcx; - context->Rax = m_gpRegisters.rax; - context->R8 = m_gpRegisters.r8; - context->R9 = m_gpRegisters.r9; - context->R10 = m_gpRegisters.r10; - context->R11 = m_gpRegisters.r11; - context->R12 = m_gpRegisters.r12; - context->R13 = m_gpRegisters.r13; - context->R14 = m_gpRegisters.r14; - context->R15 = m_gpRegisters.r15; + context->Rdi = m_snapshot.m_gpRegisters.rdi; + context->Rsi = m_snapshot.m_gpRegisters.rsi; + context->Rbx = m_snapshot.m_gpRegisters.rbx; + context->Rdx = m_snapshot.m_gpRegisters.rdx; + context->Rcx = m_snapshot.m_gpRegisters.rcx; + context->Rax = m_snapshot.m_gpRegisters.rax; + context->R8 = m_snapshot.m_gpRegisters.r8; + context->R9 = m_snapshot.m_gpRegisters.r9; + context->R10 = m_snapshot.m_gpRegisters.r10; + context->R11 = m_snapshot.m_gpRegisters.r11; + context->R12 = m_snapshot.m_gpRegisters.r12; + context->R13 = m_snapshot.m_gpRegisters.r13; + context->R14 = m_snapshot.m_gpRegisters.r14; + context->R15 = m_snapshot.m_gpRegisters.r15; } if ((flags & CONTEXT_SEGMENTS) == CONTEXT_SEGMENTS) { - context->SegDs = m_gpRegisters.ds; - context->SegEs = m_gpRegisters.es; - context->SegFs = m_gpRegisters.fs; - context->SegGs = m_gpRegisters.gs; + context->SegDs = m_snapshot.m_gpRegisters.ds; + context->SegEs = m_snapshot.m_gpRegisters.es; + context->SegFs = m_snapshot.m_gpRegisters.fs; + context->SegGs = m_snapshot.m_gpRegisters.gs; } if ((flags & CONTEXT_FLOATING_POINT) == CONTEXT_FLOATING_POINT) { - context->FltSave.ControlWord = m_fpRegisters.cwd; - context->FltSave.StatusWord = m_fpRegisters.swd; - context->FltSave.TagWord = m_fpRegisters.ftw; - context->FltSave.ErrorOpcode = m_fpRegisters.fop; + context->FltSave.ControlWord = m_snapshot.m_fpRegisters.cwd; + context->FltSave.StatusWord = m_snapshot.m_fpRegisters.swd; + context->FltSave.TagWord = m_snapshot.m_fpRegisters.ftw; + context->FltSave.ErrorOpcode = m_snapshot.m_fpRegisters.fop; - context->FltSave.ErrorOffset = FPREG_ErrorOffset(m_fpRegisters); - context->FltSave.ErrorSelector = FPREG_ErrorSelector(m_fpRegisters); - context->FltSave.DataOffset = FPREG_DataOffset(m_fpRegisters); - context->FltSave.DataSelector = FPREG_DataSelector(m_fpRegisters); + context->FltSave.ErrorOffset = FPREG_ErrorOffset(m_snapshot.m_fpRegisters); + context->FltSave.ErrorSelector = FPREG_ErrorSelector(m_snapshot.m_fpRegisters); + context->FltSave.DataOffset = FPREG_DataOffset(m_snapshot.m_fpRegisters); + context->FltSave.DataSelector = FPREG_DataSelector(m_snapshot.m_fpRegisters); - context->FltSave.MxCsr = m_fpRegisters.mxcsr; - context->FltSave.MxCsr_Mask = m_fpRegisters.mxcr_mask; + context->FltSave.MxCsr = m_snapshot.m_fpRegisters.mxcsr; + context->FltSave.MxCsr_Mask = m_snapshot.m_fpRegisters.mxcr_mask; - assert(sizeof(context->FltSave.FloatRegisters) == sizeof(m_fpRegisters.st_space)); - memcpy(context->FltSave.FloatRegisters, m_fpRegisters.st_space, sizeof(context->FltSave.FloatRegisters)); + assert(sizeof(context->FltSave.FloatRegisters) == sizeof(m_snapshot.m_fpRegisters.st_space)); + memcpy(context->FltSave.FloatRegisters, m_snapshot.m_fpRegisters.st_space, sizeof(context->FltSave.FloatRegisters)); - assert(sizeof(context->FltSave.XmmRegisters) == sizeof(m_fpRegisters.xmm_space)); - memcpy(context->FltSave.XmmRegisters, m_fpRegisters.xmm_space, sizeof(context->FltSave.XmmRegisters)); + assert(sizeof(context->FltSave.XmmRegisters) == sizeof(m_snapshot.m_fpRegisters.xmm_space)); + memcpy(context->FltSave.XmmRegisters, m_snapshot.m_fpRegisters.xmm_space, sizeof(context->FltSave.XmmRegisters)); } // TODO: debug registers? #elif defined(__aarch64__) if ((flags & CONTEXT_CONTROL) == CONTEXT_CONTROL) { - context->Fp = MCREG_Fp(m_gpRegisters); - context->Lr = MCREG_Lr(m_gpRegisters); - context->Sp = MCREG_Sp(m_gpRegisters); - context->Pc = MCREG_Pc(m_gpRegisters); - context->Cpsr = MCREG_Cpsr(m_gpRegisters); + context->Fp = MCREG_Fp(m_snapshot.m_gpRegisters); + context->Lr = MCREG_Lr(m_snapshot.m_gpRegisters); + context->Sp = MCREG_Sp(m_snapshot.m_gpRegisters); + context->Pc = MCREG_Pc(m_snapshot.m_gpRegisters); + context->Cpsr = MCREG_Cpsr(m_snapshot.m_gpRegisters); } if ((flags & CONTEXT_INTEGER) == CONTEXT_INTEGER) { - assert(sizeof(m_gpRegisters.regs) == (sizeof(context->X) + sizeof(context->Fp) + sizeof(context->Lr))); - memcpy(context->X, m_gpRegisters.regs, sizeof(context->X)); + assert(sizeof(m_snapshot.m_gpRegisters.regs) == (sizeof(context->X) + sizeof(context->Fp) + sizeof(context->Lr))); + memcpy(context->X, m_snapshot.m_gpRegisters.regs, sizeof(context->X)); } if ((flags & CONTEXT_FLOATING_POINT) == CONTEXT_FLOATING_POINT) { - assert(sizeof(m_fpRegisters.vregs) == sizeof(context->V)); - memcpy(context->V, m_fpRegisters.vregs, sizeof(context->V)); - context->Fpcr = m_fpRegisters.fpcr; - context->Fpsr = m_fpRegisters.fpsr; + assert(sizeof(m_snapshot.m_fpRegisters.vregs) == sizeof(context->V)); + memcpy(context->V, m_snapshot.m_fpRegisters.vregs, sizeof(context->V)); + context->Fpcr = m_snapshot.m_fpRegisters.fpcr; + context->Fpsr = m_snapshot.m_fpRegisters.fpsr; } #elif defined(__arm__) if ((flags & CONTEXT_CONTROL) == CONTEXT_CONTROL) { - context->Sp = m_gpRegisters.ARM_sp; - context->Lr = m_gpRegisters.ARM_lr; - context->Pc = m_gpRegisters.ARM_pc; - context->Cpsr = m_gpRegisters.ARM_cpsr; + context->Sp = m_snapshot.m_gpRegisters.ARM_sp; + context->Lr = m_snapshot.m_gpRegisters.ARM_lr; + context->Pc = m_snapshot.m_gpRegisters.ARM_pc; + context->Cpsr = m_snapshot.m_gpRegisters.ARM_cpsr; } if ((flags & CONTEXT_INTEGER) == CONTEXT_INTEGER) { - context->R0 = m_gpRegisters.ARM_r0; - context->R1 = m_gpRegisters.ARM_r1; - context->R2 = m_gpRegisters.ARM_r2; - context->R3 = m_gpRegisters.ARM_r3; - context->R4 = m_gpRegisters.ARM_r4; - context->R5 = m_gpRegisters.ARM_r5; - context->R6 = m_gpRegisters.ARM_r6; - context->R7 = m_gpRegisters.ARM_r7; - context->R8 = m_gpRegisters.ARM_r8; - context->R9 = m_gpRegisters.ARM_r9; - context->R10 = m_gpRegisters.ARM_r10; - context->R11 = m_gpRegisters.ARM_fp; - context->R12 = m_gpRegisters.ARM_ip; + context->R0 = m_snapshot.m_gpRegisters.ARM_r0; + context->R1 = m_snapshot.m_gpRegisters.ARM_r1; + context->R2 = m_snapshot.m_gpRegisters.ARM_r2; + context->R3 = m_snapshot.m_gpRegisters.ARM_r3; + context->R4 = m_snapshot.m_gpRegisters.ARM_r4; + context->R5 = m_snapshot.m_gpRegisters.ARM_r5; + context->R6 = m_snapshot.m_gpRegisters.ARM_r6; + context->R7 = m_snapshot.m_gpRegisters.ARM_r7; + context->R8 = m_snapshot.m_gpRegisters.ARM_r8; + context->R9 = m_snapshot.m_gpRegisters.ARM_r9; + context->R10 = m_snapshot.m_gpRegisters.ARM_r10; + context->R11 = m_snapshot.m_gpRegisters.ARM_fp; + context->R12 = m_snapshot.m_gpRegisters.ARM_ip; } if ((flags & CONTEXT_FLOATING_POINT) == CONTEXT_FLOATING_POINT) { #if defined(__VFP_FP__) && !defined(__SOFTFP__) - context->Fpscr = m_vfpRegisters.fpscr; + context->Fpscr = m_snapshot.m_vfpRegisters.fpscr; - assert(sizeof(context->D) == sizeof(m_vfpRegisters.fpregs)); - memcpy(context->D, m_vfpRegisters.fpregs, sizeof(context->D)); + assert(sizeof(context->D) == sizeof(m_snapshot.m_vfpRegisters.fpregs)); + memcpy(context->D, m_snapshot.m_vfpRegisters.fpregs, sizeof(context->D)); #endif } #elif defined(__loongarch64) if (flags & CONTEXT_CONTROL) { - context->Ra = MCREG_Ra(m_gpRegisters); - context->Sp = MCREG_Sp(m_gpRegisters); - context->Fp = MCREG_Fp(m_gpRegisters); - context->Pc = MCREG_Pc(m_gpRegisters); + context->Ra = MCREG_Ra(m_snapshot.m_gpRegisters); + context->Sp = MCREG_Sp(m_snapshot.m_gpRegisters); + context->Fp = MCREG_Fp(m_snapshot.m_gpRegisters); + context->Pc = MCREG_Pc(m_snapshot.m_gpRegisters); } if (flags & CONTEXT_INTEGER) { - memcpy(&context->A0, &m_gpRegisters.regs[4], sizeof(context->A0)*(21 - 4 + 1)); - memcpy(&context->S0, &m_gpRegisters.regs[23], sizeof(context->S0)*9); + memcpy(&context->A0, &m_snapshot.m_gpRegisters.regs[4], sizeof(context->A0)*(21 - 4 + 1)); + memcpy(&context->S0, &m_snapshot.m_gpRegisters.regs[23], sizeof(context->S0)*9); } if (flags & CONTEXT_FLOATING_POINT) { - assert(sizeof(context->F) == sizeof(m_fpRegisters.regs)); - memcpy(context->F, m_fpRegisters.regs, sizeof(context->F)); - context->Fcsr = m_fpRegisters.fcsr; - context->Fcc = m_fpRegisters.fcc; + assert(sizeof(context->F) == sizeof(m_snapshot.m_fpRegisters.regs)); + memcpy(context->F, m_snapshot.m_fpRegisters.regs, sizeof(context->F)); + context->Fcsr = m_snapshot.m_fpRegisters.fcsr; + context->Fcc = m_snapshot.m_fpRegisters.fcc; } #elif defined(__riscv) if ((flags & CONTEXT_CONTROL) == CONTEXT_CONTROL) { - context->Ra = MCREG_Ra(m_gpRegisters); - context->Sp = MCREG_Sp(m_gpRegisters); - context->Fp = MCREG_Fp(m_gpRegisters); - context->Pc = MCREG_Pc(m_gpRegisters); + context->Ra = MCREG_Ra(m_snapshot.m_gpRegisters); + context->Sp = MCREG_Sp(m_snapshot.m_gpRegisters); + context->Fp = MCREG_Fp(m_snapshot.m_gpRegisters); + context->Pc = MCREG_Pc(m_snapshot.m_gpRegisters); } if (flags & CONTEXT_INTEGER) { - context->Gp = m_gpRegisters.gp; - context->Tp = m_gpRegisters.tp; - context->T0 = m_gpRegisters.t0; - context->T1 = m_gpRegisters.t1; - context->T2 = m_gpRegisters.t2; - context->S1 = m_gpRegisters.s1; - context->A0 = m_gpRegisters.a0; - context->A1 = m_gpRegisters.a1; - context->A2 = m_gpRegisters.a2; - context->A3 = m_gpRegisters.a3; - context->A4 = m_gpRegisters.a4; - context->A5 = m_gpRegisters.a5; - context->A6 = m_gpRegisters.a6; - context->A7 = m_gpRegisters.a7; - context->S2 = m_gpRegisters.s2; - context->S3 = m_gpRegisters.s3; - context->S4 = m_gpRegisters.s4; - context->S5 = m_gpRegisters.s5; - context->S6 = m_gpRegisters.s6; - context->S7 = m_gpRegisters.s7; - context->S8 = m_gpRegisters.s8; - context->S9 = m_gpRegisters.s9; - context->S10 = m_gpRegisters.s10; - context->S11 = m_gpRegisters.s11; - context->T3 = m_gpRegisters.t3; - context->T4 = m_gpRegisters.t4; - context->T5 = m_gpRegisters.t5; - context->T6 = m_gpRegisters.t6; + context->Gp = m_snapshot.m_gpRegisters.gp; + context->Tp = m_snapshot.m_gpRegisters.tp; + context->T0 = m_snapshot.m_gpRegisters.t0; + context->T1 = m_snapshot.m_gpRegisters.t1; + context->T2 = m_snapshot.m_gpRegisters.t2; + context->S1 = m_snapshot.m_gpRegisters.s1; + context->A0 = m_snapshot.m_gpRegisters.a0; + context->A1 = m_snapshot.m_gpRegisters.a1; + context->A2 = m_snapshot.m_gpRegisters.a2; + context->A3 = m_snapshot.m_gpRegisters.a3; + context->A4 = m_snapshot.m_gpRegisters.a4; + context->A5 = m_snapshot.m_gpRegisters.a5; + context->A6 = m_snapshot.m_gpRegisters.a6; + context->A7 = m_snapshot.m_gpRegisters.a7; + context->S2 = m_snapshot.m_gpRegisters.s2; + context->S3 = m_snapshot.m_gpRegisters.s3; + context->S4 = m_snapshot.m_gpRegisters.s4; + context->S5 = m_snapshot.m_gpRegisters.s5; + context->S6 = m_snapshot.m_gpRegisters.s6; + context->S7 = m_snapshot.m_gpRegisters.s7; + context->S8 = m_snapshot.m_gpRegisters.s8; + context->S9 = m_snapshot.m_gpRegisters.s9; + context->S10 = m_snapshot.m_gpRegisters.s10; + context->S11 = m_snapshot.m_gpRegisters.s11; + context->T3 = m_snapshot.m_gpRegisters.t3; + context->T4 = m_snapshot.m_gpRegisters.t4; + context->T5 = m_snapshot.m_gpRegisters.t5; + context->T6 = m_snapshot.m_gpRegisters.t6; } if (flags & CONTEXT_FLOATING_POINT) { - assert(sizeof(context->F) == sizeof(m_fpRegisters.fpregs)); - memcpy(context->F, m_fpRegisters.fpregs, sizeof(context->F)); - context->Fcsr = m_fpRegisters.fcsr; + assert(sizeof(context->F) == sizeof(m_snapshot.m_fpRegisters.fpregs)); + memcpy(context->F, m_snapshot.m_fpRegisters.fpregs, sizeof(context->F)); + context->Fcsr = m_snapshot.m_fpRegisters.fcsr; } #else #error Platform not supported diff --git a/src/coreclr/nativeaot/Bootstrap/main.cpp b/src/coreclr/nativeaot/Bootstrap/main.cpp index eb3d7c1d525d3f..ccf7dce881954e 100644 --- a/src/coreclr/nativeaot/Bootstrap/main.cpp +++ b/src/coreclr/nativeaot/Bootstrap/main.cpp @@ -3,8 +3,28 @@ #include #include +#include #include +#if defined(TARGET_LINUX) +#include +#include "../Runtime/createdump/createdump_sentinel.h" + +// Linked-in createdump support for NativeAOT executables (Unix only). +// We create two variants of the createdump library: the "enabled" one actually contains +// the implementation while the "disabled" one contains stubs (for the linker) only. +// Supported Linux executables link the enabled variant. Other configurations +// link the disabled variant and retain the external createdump path. +extern bool g_createdumpLinked; +extern int nativeaot_createdump_main(int argc, const char* argv[]); + +#ifdef NATIVEAOT_DLL +bool g_createdumpDispatchSupported = false; +#else +bool g_createdumpDispatchSupported = true; +#endif +#endif // TARGET_LINUX + #if defined(DEBUG) && defined(_WIN32) #include #include @@ -228,6 +248,21 @@ int __cdecl wmain(int argc, wchar_t* argv[]) int main(int argc, char* argv[]) #endif { +#if defined(TARGET_LINUX) + // Check if we're being re-invoked as a dump generator. + // When a NativeAOT process crashes and createdump is linked in, it forks + // and re-executes itself with the GUID sentinel as argv[1]. + if (g_createdumpLinked && + getauxval(AT_SECURE) == 0 && + argc >= 2 && + strcmp(argv[1], CREATEDUMP_SENTINEL) == 0) + { + // Shift args: [self, sentinel, ...createdump args] -> [self, ...createdump args] + argv[1] = argv[0]; + return nativeaot_createdump_main(argc - 1, (const char**)&argv[1]); + } +#endif + int initval = InitializeRuntime(); if (initval != 0) return initval; diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets index 5b6862703de826..9b4073d00b7bcc 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets @@ -86,6 +86,9 @@ The .NET Foundation licenses this file to you under the MIT license. libeventpipe-disabled libeventpipe-enabled + libnativeaot-createdump-disabled + libnativeaot-createdump-enabled + true libRuntime.VxsortDisabled libRuntime.VxsortEnabled @@ -181,6 +184,7 @@ The .NET Foundation licenses this file to you under the MIT license. + diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets index 3124302b5dfe5e..cb8d4b25faa5ad 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets @@ -36,6 +36,7 @@ The .NET Foundation licenses this file to you under the MIT license. false + false true false true diff --git a/src/coreclr/nativeaot/Runtime/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/CMakeLists.txt index 06d92e2a4d70f5..dbe4a94d811a34 100644 --- a/src/coreclr/nativeaot/Runtime/CMakeLists.txt +++ b/src/coreclr/nativeaot/Runtime/CMakeLists.txt @@ -398,6 +398,10 @@ if(FEATURE_PERFTRACING) add_subdirectory(eventpipe) endif() +if(CLR_CMAKE_TARGET_UNIX) + add_subdirectory(createdump) +endif() + if(NOT CLR_CMAKE_TARGET_ARCH_WASM) # Create an interface library that captures the Runtime's include directories # for use by the datadescriptor intermediary compilation. diff --git a/src/coreclr/nativeaot/Runtime/createdump/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/createdump/CMakeLists.txt new file mode 100644 index 00000000000000..1d6079c6cb4da6 --- /dev/null +++ b/src/coreclr/nativeaot/Runtime/createdump/CMakeLists.txt @@ -0,0 +1,25 @@ +project(nativeaot-createdump) + +# Enabled variant: full ELF core dump writer (Linux only). +if(CLR_CMAKE_TARGET_LINUX AND (CLR_CMAKE_TARGET_ARCH_AMD64 OR CLR_CMAKE_TARGET_ARCH_ARM64)) + set(CREATEDUMP_SHARED_DIR ${CLR_DIR}/debug/createdump/shared) + set(NATIVEAOT_CREATEDUMP_SOURCES + nativeaot_createdump_main.cpp + ${CREATEDUMP_SHARED_DIR}/createdumpcore.cpp + ${CREATEDUMP_SHARED_DIR}/createdumpunixcore.cpp + ${CREATEDUMP_SHARED_DIR}/crashinfocore.cpp + ${CREATEDUMP_SHARED_DIR}/crashinfounixcore.cpp + ${CREATEDUMP_SHARED_DIR}/dumpname.cpp + ${CREATEDUMP_SHARED_DIR}/dumpwriter.cpp + ${CREATEDUMP_SHARED_DIR}/dumpwriterelf.cpp + ${CREATEDUMP_SHARED_DIR}/threadsnapshot.cpp + ) + + add_library(nativeaot-createdump-enabled STATIC ${NATIVEAOT_CREATEDUMP_SOURCES}) + target_include_directories(nativeaot-createdump-enabled PRIVATE ${CREATEDUMP_SHARED_DIR}) + install_static_library(nativeaot-createdump-enabled aotsdk nativeaot) +endif() + +# Disabled variant: no-op stubs (all platforms). +add_library(nativeaot-createdump-disabled STATIC createdump_disabled.cpp) +install_static_library(nativeaot-createdump-disabled aotsdk nativeaot) diff --git a/src/coreclr/nativeaot/Runtime/createdump/createdump_disabled.cpp b/src/coreclr/nativeaot/Runtime/createdump/createdump_disabled.cpp new file mode 100644 index 00000000000000..b77e2732d98f7e --- /dev/null +++ b/src/coreclr/nativeaot/Runtime/createdump/createdump_disabled.cpp @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Disabled stub for NativeAOT linked-in createdump. +// Linked when linked createdump is not enabled for the output configuration. + +bool g_createdumpLinked = false; + +int nativeaot_createdump_main(int, const char*[]) +{ + return 1; +} diff --git a/src/coreclr/nativeaot/Runtime/createdump/createdump_sentinel.h b/src/coreclr/nativeaot/Runtime/createdump/createdump_sentinel.h new file mode 100644 index 00000000000000..ec5ea7657bfd6e --- /dev/null +++ b/src/coreclr/nativeaot/Runtime/createdump/createdump_sentinel.h @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +// Well-known GUID sentinel argument used for self-restart createdump. +// When a NativeAOT process crashes and createdump support is linked in, +// the process forks and re-executes itself with this sentinel as argv[1]. +// The bootstrap checks for this sentinel before initializing the runtime +// and redirects into the dump-writing code path instead. +#define CREATEDUMP_SENTINEL "--{E89B44A7-9C62-4AB5-B292-FC7E1A25E949}" diff --git a/src/coreclr/nativeaot/Runtime/createdump/nativeaot_createdump_main.cpp b/src/coreclr/nativeaot/Runtime/createdump/nativeaot_createdump_main.cpp new file mode 100644 index 00000000000000..73a1c848eb1b9d --- /dev/null +++ b/src/coreclr/nativeaot/Runtime/createdump/nativeaot_createdump_main.cpp @@ -0,0 +1,239 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "createdumpcore.h" +#include "dumpwriter.h" +#include + +// Exported symbol so PalCreateDump.cpp can detect that this library is linked. +bool g_createdumpLinked = true; + +// Regions is a sorted array of non-overlapping memory regions +static size_t FindDumpRegionInsertionIndex(const DynamicArray* regions, uint64_t startAddress) +{ + size_t low = 0; + size_t high = regions->Count(); + + while (low < high) + { + size_t middle = low + ((high - low) / 2); + if ((*regions)[middle].StartAddress() < startAddress) + { + low = middle + 1; + } + else + { + high = middle; + } + } + + return low; +} + +static bool FindDumpRegionOverlap( + void* container, + uint64_t startAddress, + uint64_t endAddress, + MemoryRegion* result) +{ + DynamicArray* regions = static_cast*>(container); + size_t index = FindDumpRegionInsertionIndex(regions, startAddress); + + if (index > 0 && (*regions)[index - 1].EndAddress() > startAddress) + { + *result = (*regions)[index - 1]; + return true; + } + + if (index == regions->Count() || (*regions)[index].StartAddress() >= endAddress) + { + return false; + } + + *result = (*regions)[index]; + return true; +} + +static bool InsertDumpRegion(void* container, const MemoryRegion* region) +{ + DynamicArray* regions = static_cast*>(container); + size_t index = FindDumpRegionInsertionIndex(regions, region->StartAddress()); + + if ((index > 0 && (*regions)[index - 1].EndAddress() > region->StartAddress()) || + (index < regions->Count() && (*regions)[index].StartAddress() < region->EndAddress())) + { + return false; + } + + if (!regions->Add(*region)) + { + return false; + } + + for (size_t destination = regions->Count() - 1; destination > index; destination--) + { + (*regions)[destination] = (*regions)[destination - 1]; + } + (*regions)[index] = *region; + return true; +} + +void print_trace_timestamp() +{ + struct timespec timestamp; + if (clock_gettime(CLOCK_MONOTONIC, ×tamp) == 0) + { + uint64_t milliseconds = static_cast(timestamp.tv_sec) * 1000 + static_cast(timestamp.tv_nsec) / 1000000; + fprintf(g_stdout, "%08" PRIx64 " ", milliseconds); + } +} + +bool GetDefaultDumpPath(char* buffer, size_t bufferSize) +{ + strncpy(buffer, DEFAULT_DUMP_PATH DEFAULT_DUMP_TEMPLATE, bufferSize); + buffer[bufferSize - 1] = '\0'; + return true; +} + +// This method is a simplified version of the original CreateDump function in createdumpunix.cpp. +// If changes are made to this function, consider updating the other one. +bool LinkedCreateDump(const CreateDumpOptions* options) +{ + assert(options->CreateDump); + + if (!ValidateDumpOptions(options)) + { + return false; + } + + bool result = false; + bool processInitialized = false; + DynamicArray dumpRegions; + DynamicArray combinedRegions; + DumpRegionStore regionStore{ &dumpRegions, &FindDumpRegionOverlap, &InsertDumpRegion }; + ProcessInfo processInfo(*options); + + if (!processInfo.Initialize()) + { + return false; + } + + processInitialized = true; + + printf_status("Gathering state for process %d %s\n", options->Pid, processInfo.Name()); + + if (options->Signal != 0 || options->CrashThread != 0) + { + printf_status("Crashing thread %04x signal %d (%04x)\n", options->CrashThread, options->Signal, options->Signal); + } + + // Suspend all the threads in the target process and build the list of threads + if (!processInfo.EnumerateAndSuspendThreads()) + { + goto exit; + } + + // Gather all the info about the process, threads (registers, etc.) and memory regions + if (!processInfo.GatherCrashInfo(regionStore)) + { + goto exit; + } + + // Add the special (fake) memory region for the special diagnostics info. Use constructor that doesn't assert PAGE_SIZE alignment. + if (!AddSpecialDiagInfoRegion(regionStore)) + { + goto exit; + } + + // Determine which memory regions should be included in the dump based on the dump type + if (!processInfo.SelectDumpRegions(regionStore, options->DumpType)) + { + goto exit; + } + + char dumpPath[MAX_DUMP_PATH]; + // Format the dump pattern template + if (!FormatDumpName(dumpPath, sizeof(dumpPath), options->DumpPathTemplate, processInfo.Name(), options->Pid)) + { + goto exit; + } + + if (!CombineMemoryRegions( + dumpRegions, + combinedRegions, + [&combinedRegions](const MemoryRegion& region) + { + assert(combinedRegions.empty() || combinedRegions[combinedRegions.Count() - 1] < region); + return combinedRegions.Add(region); + })) + { + goto exit; + } + dumpRegions = Move(combinedRegions); + + printf_status("Writing %s to file %s\n", GetDumpTypeString(options->DumpType), dumpPath); + + processInfo.CalculateRuntimeBaseAddress(); + + { + DumpWriter dumpWriter(processInfo, processInfo.ModuleMappings(), dumpRegions); + + // Write the actual dump file + if (!dumpWriter.OpenAndWriteDump(dumpPath)) + { + goto exit; + } + } + + result = true; + +exit: + LogProcessStatus(options->Pid); + if (processInitialized) + { + processInfo.CleanupAndResumeProcess(); + } + + return result; +} + +int nativeaot_createdump_main(int argc, const char* argv[]) +{ + CreateDumpOptions options{}; + int exitCode = ParseCreateDumpOptions(argc, (char**)argv, &options); + if (exitCode != 0) + { + return exitCode; + } + + char defaultDumpPath[MAX_LONGPATH]; + if (options.DumpPathTemplate == nullptr) + { + if (!GetDefaultDumpPath(defaultDumpPath, MAX_LONGPATH)) + { + printf_error("Could not get default dump path\n"); + return -1; + } + options.DumpPathTemplate = defaultDumpPath; + } + + if (LinkedCreateDump(&options)) + { + printf_status("Dump successfully written\n"); + } + else + { + printf_error("Failure writing dump\n"); + exitCode = -1; + } + + fflush(stderr); + fflush(g_stdout); + + if (g_logfile != nullptr) + { + fflush(g_logfile); + fclose(g_logfile); + } + return exitCode; +} \ No newline at end of file diff --git a/src/coreclr/nativeaot/Runtime/unix/PalCreateDump.cpp b/src/coreclr/nativeaot/Runtime/unix/PalCreateDump.cpp index 203988bf6511b3..1d911ff2c34f31 100644 --- a/src/coreclr/nativeaot/Runtime/unix/PalCreateDump.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/PalCreateDump.cpp @@ -16,6 +16,9 @@ #include #include #include +#ifdef TARGET_LINUX +#include +#endif #if HAVE_PRCTL_H #include #include @@ -52,6 +55,20 @@ #include #include +#ifdef TARGET_LINUX +// Defined in the nativeaot-createdump-enabled/disabled library. +// True when the enabled variant is linked into a supported Linux executable. +extern bool g_createdumpLinked; +// True when the linked bootstrap handles the createdump sentinel. +extern bool g_createdumpDispatchSupported; + +#include "createdump/createdump_sentinel.h" + +// True when linked-in createdump can re-execute the current process. +bool g_selfRestartCreatedump = false; +const char* const SelfCreateDumpPath = "/proc/self/exe"; +#endif + #if !defined(HOST_MACCATALYST) && !defined(HOST_IOS) && !defined(HOST_TVOS) && !defined(HOST_WASM) // Crash dump generating program arguments. MAX_ARGV_ENTRIES is the max number @@ -120,6 +137,26 @@ Return true - succeeds, false - fails --*/ +static +bool +CanUseSelfRestartCreatedump( + int dumpType, + uint32_t flags, + const char* logFileName) +{ +#ifdef TARGET_LINUX + const uint32_t SupportedFlags = + GenerateDumpFlagsLoggingEnabled | + GenerateDumpFlagsVerboseLoggingEnabled; + + return g_selfRestartCreatedump && + (flags & ~SupportedFlags) == 0 && + dumpType == DumpTypeFull; +#else + return false; +#endif +} + static bool BuildCreateDumpCommandLine( @@ -127,15 +164,32 @@ BuildCreateDumpCommandLine( const char* dumpName, const char* logFileName, int dumpType, - uint32_t flags) + uint32_t flags, + bool selfRestart) { - if (g_szCreateDumpPath == nullptr) + const char* program = g_szCreateDumpPath; +#ifdef TARGET_LINUX + if (selfRestart) + { + program = SelfCreateDumpPath; + } +#endif + if (program == nullptr) { return false; } int argc = 0; - argv[argc++] = g_szCreateDumpPath; + argv[argc++] = program; + +#ifdef TARGET_LINUX + // In self-restart mode, insert the GUID sentinel so the re-executed + // process knows to enter createdump mode instead of normal startup. + if (selfRestart) + { + argv[argc++] = CREATEDUMP_SENTINEL; + } +#endif if (dumpName != nullptr) { @@ -522,7 +576,8 @@ PalGenerateCoreDump( { dumpName = nullptr; } - bool result = BuildCreateDumpCommandLine(argvCreateDump, dumpName, nullptr, dumpType, flags); + bool selfRestart = CanUseSelfRestartCreatedump(dumpType, flags, nullptr); + bool result = BuildCreateDumpCommandLine(argvCreateDump, dumpName, nullptr, dumpType, flags, selfRestart); if (result) { result = CreateCrashDump(argvCreateDump, errorMessageBuffer, cbErrorMessageBuffer); @@ -605,7 +660,8 @@ PalCreateDumpInitialize() char* program = nullptr; // Check if user provided a custom path to createdump tool directory - if (RhConfig::Environment::TryGetStringValue("DbgCreateDumpToolPath", &dumpToolPath)) + bool customDumpToolPath = RhConfig::Environment::TryGetStringValue("DbgCreateDumpToolPath", &dumpToolPath); + if (customDumpToolPath) { // Use the provided directory path and concatenate with "createdump" size_t dumpToolPathLen = strlen(dumpToolPath); @@ -654,7 +710,17 @@ PalCreateDumpInitialize() g_szCreateDumpPath = program; - if (!BuildCreateDumpCommandLine(g_argvCreateDump, dumpName, logFilePath, dumpType, flags)) +#ifdef TARGET_LINUX + g_selfRestartCreatedump = + g_createdumpLinked && + g_createdumpDispatchSupported && + !customDumpToolPath && + getauxval(AT_SECURE) == 0 && + access(SelfCreateDumpPath, X_OK) == 0; +#endif + + bool selfRestart = CanUseSelfRestartCreatedump(dumpType, flags, logFilePath); + if (!BuildCreateDumpCommandLine(g_argvCreateDump, dumpName, logFilePath, dumpType, flags, selfRestart)) { return false; } diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props index 5c36a463c5e5a6..45ed61bd1e12cb 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props @@ -152,6 +152,8 @@ + + diff --git a/src/tests/nativeaot/SmokeTests/CreatedumpValidation/CreatedumpValidation.cs b/src/tests/nativeaot/SmokeTests/CreatedumpValidation/CreatedumpValidation.cs new file mode 100644 index 00000000000000..2cae872a5c1890 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/CreatedumpValidation/CreatedumpValidation.cs @@ -0,0 +1,1186 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.IO.MemoryMappedFiles; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +internal static class CreatedumpValidation +{ + private const int Pass = 100; + private const int Fail = 1; + private const int TimeoutMilliseconds = 90_000; + + private const uint PtLoad = 1; + private const uint PtNote = 4; + private const uint PfRead = 4; + private const ushort EtCore = 4; + private const ushort EmX86_64 = 62; + private const ushort EmAArch64 = 183; + private const byte ElfClass64 = 2; + private const byte ElfDataLsb = 1; + private const int ElfHeaderSize = 64; + private const int ProgramHeaderSize = 56; + private const int PrPsInfoPidOffset = 24; + private const ulong ElfLoadAlignment = 4096; + + private const uint MhMagic64 = 0xFEEDFACF; + private const uint MhCore = 4; + private const uint CpuTypeX86_64 = 0x01000007; + private const uint CpuTypeArm64 = 0x0100000C; + private const uint LcThread = 0x4; + private const uint LcSegment64 = 0x19; + private const uint LcNote = 0x31; + private const uint VmProtRead = 1; + private const uint VmProtMask = 7; + private const int MachHeader64Size = 32; + private const int MachLoadCommandSize = 8; + private const int MachSegmentCommand64Size = 72; + private const int MachNoteCommandSize = 40; + private const int SpecialThreadInfoHeaderSize = 24; + private const int SpecialThreadInfoEntrySize = 16; + + private const uint NtPrStatus = 1; + private const uint NtFpRegSet = 2; + private const uint NtPrPsInfo = 3; + private const uint NtAuxV = 6; + private const uint NtFile = 0x46494c45; + private const uint NtSigInfo = 0x53494749; + + private const ulong AtNull = 0; + private const ulong AtPhdr = 3; + private const ulong AtPageSize = 6; + + private const ulong LinuxAndMacArm64SpecialDiagInfoAddress = 0x00007ffffff10000; + private const ulong MacX64SpecialDiagInfoAddress = 0x7fffffff10000000; + private const ulong MacArm64SpecialThreadInfoAddress = 0x00007ffffff00000; + private const ulong MacX64SpecialThreadInfoAddress = 0x7fffffff00000000; + private const ulong SpecialDiagInfoSize = 0x1000; + private const int SpecialDiagInfoVersion = 2; + private const int SpecialDiagExceptionRecordOffset = 24; + private const int SpecialDiagRuntimeBaseOffset = 32; + + private const uint StatusStackBufferOverrun = 0xC0000409; + private const uint ExceptionNoncontinuable = 1; + private const ulong FastFailExceptionDotNetAot = 0x48; + private const uint InvalidOperationHResult = 0x80131509; + private const ulong MaximumTriageBufferSize = 8192; + private const int ExceptionRecordSize = 152; + private const int ExceptionRecordParameterCountOffset = 24; + private const int ExceptionRecordInformationOffset = 32; + + private const int DeletedMappingProbeOffset = 128; + private const string ExceptionMessage = "CreatedumpValidation managed exception payload 8A41C43D"; + private const string ExternalMarkerEnvironmentVariable = "CREATEDUMP_VALIDATION_EXTERNAL_MARKER"; + private const string ExternalHelperEnvironmentVariable = "CREATEDUMP_VALIDATION_EXTERNAL_HELPER"; + + private static readonly byte[] ElfMagic = { 0x7f, 0x45, 0x4c, 0x46 }; + private static readonly byte[] MachMagic64 = { 0xcf, 0xfa, 0xed, 0xfe }; + private static readonly byte[] DeletedMappingPattern = { 0x43, 0x44, 0x55, 0x4d, 0x50, 0x8a, 0x41, 0xc4 }; + private static readonly byte[] SpecialDiagSignature = Encoding.ASCII.GetBytes("DIAGINFOHEADER"); + private static readonly byte[] SpecialThreadInfoSignature = Encoding.ASCII.GetBytes("THREADINFO"); + private static readonly byte[] ProcessMetadataOwner = Encoding.ASCII.GetBytes("process metadata"); + + private static MemoryMappedFile? s_deletedMappingFile; + private static MemoryMappedViewAccessor? s_deletedMappingView; + + private readonly struct LoadSegment + { + public LoadSegment(uint flags, ulong fileOffset, ulong virtualAddress, ulong fileSize, ulong memorySize) + { + Flags = flags; + FileOffset = fileOffset; + VirtualAddress = virtualAddress; + FileSize = fileSize; + MemorySize = memorySize; + } + + public uint Flags { get; } + public ulong FileOffset { get; } + public ulong VirtualAddress { get; } + public ulong FileSize { get; } + public ulong MemorySize { get; } + } + + private struct NoteSummary + { + public int PrStatus; + public int FpRegSet; + public int PrPsInfo; + public int AuxV; + public int File; + public int SigInfo; + public bool AuxVHasExpectedPageSize; + public bool FileHasExpectedPageSize; + public bool HasProgramHeaders; + public bool HasExecutableFileName; + public bool HasDeletedMappingFileName; + public int Signal; + } + + private sealed class ExternalCreatedumpScope : IDisposable + { + private const string WrapperContents = + "#!/bin/sh\n" + + ": > \"$" + ExternalMarkerEnvironmentVariable + "\"\n" + + "exec \"$" + ExternalHelperEnvironmentVariable + "\" \"$@\"\n"; + + private readonly string _defaultHelperPath; + private readonly string? _originalHelperBackup; + private readonly UnixFileMode? _originalHelperMode; + + public ExternalCreatedumpScope(string processPath, string testDirectory) + { + string applicationDirectory = Path.GetDirectoryName(processPath) + ?? throw new InvalidOperationException("The process path has no directory."); + + _defaultHelperPath = Path.Combine(applicationDirectory, "createdump"); + RealHelperPath = Path.Combine(testDirectory, "createdump-real"); + ForcedHelperDirectory = Path.Combine(testDirectory, "forced-external"); + Directory.CreateDirectory(ForcedHelperDirectory); + + string sourceHelper; + if (File.Exists(_defaultHelperPath)) + { + _originalHelperBackup = Path.Combine(testDirectory, "createdump-original"); + _originalHelperMode = File.GetUnixFileMode(_defaultHelperPath); + File.Copy(_defaultHelperPath, _originalHelperBackup, overwrite: true); + sourceHelper = _originalHelperBackup; + File.Delete(_defaultHelperPath); + } + else + { + string? coreRoot = Environment.GetEnvironmentVariable("CORE_ROOT"); + sourceHelper = coreRoot is null ? string.Empty : Path.Combine(coreRoot, "createdump"); + if (!File.Exists(sourceHelper)) + { + throw new FileNotFoundException( + $"External createdump was not found next to the test or under CORE_ROOT ('{coreRoot ?? ""}').", + sourceHelper); + } + } + + try + { + File.Copy(sourceHelper, RealHelperPath, overwrite: true); + File.SetUnixFileMode( + RealHelperPath, + UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); + + WriteWrapper(_defaultHelperPath); + WriteWrapper(Path.Combine(ForcedHelperDirectory, "createdump")); + } + catch + { + RestoreDefaultHelper(); + throw; + } + } + + public string RealHelperPath { get; } + public string ForcedHelperDirectory { get; } + + public void Dispose() => RestoreDefaultHelper(); + + private void RestoreDefaultHelper() + { + File.Delete(_defaultHelperPath); + if (_originalHelperBackup is not null) + { + Debug.Assert(_originalHelperMode.HasValue); + File.Copy(_originalHelperBackup, _defaultHelperPath, overwrite: true); + File.SetUnixFileMode(_defaultHelperPath, _originalHelperMode.Value); + } + } + + private static void WriteWrapper(string path) + { + File.WriteAllText(path, WrapperContents); + File.SetUnixFileMode(path, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); + } + } + + private static int Main(string[] args) + { + if (args.Length == 1 && args[0] == "--crash") + { + return CrashChild(); + } + + try + { + return RunParent(); + } + catch (Exception exception) + { + Console.WriteLine($"FAIL: {exception}"); + return Fail; + } + } + + private static int RunParent() + { + string processPath = Environment.ProcessPath + ?? throw new InvalidOperationException("Environment.ProcessPath is null."); + string testDirectory = Path.Combine(Path.GetTempPath(), "createdump_validation_" + Path.GetRandomFileName()); + Directory.CreateDirectory(testDirectory); + + ExternalCreatedumpScope? externalCreatedump = null; + try + { + externalCreatedump = new ExternalCreatedumpScope(processPath, testDirectory); + + bool automaticUsedExternal = RunScenario( + processPath, + testDirectory, + externalCreatedump, + scenarioName: "automatic", + forceExternal: false); + + if (OperatingSystem.IsMacOS()) + { + if (!automaticUsedExternal) + { + throw new InvalidDataException("The macOS scenario did not invoke the external createdump helper."); + } + + Console.WriteLine("PASS: the macOS external dump contains the expected process, memory, and managed exception data."); + return Pass; + } + + if (automaticUsedExternal) + { + throw new InvalidDataException("The automatic scenario did not use linked createdump."); + } + + bool forcedUsedExternal = RunScenario( + processPath, + testDirectory, + externalCreatedump, + scenarioName: "forced-external", + forceExternal: true); + + if (!forcedUsedExternal) + { + throw new InvalidDataException("The forced-external scenario did not invoke the external createdump helper."); + } + + Console.WriteLine("PASS: automatic and forced-external dumps contain the expected process, signal, memory, and managed exception data."); + return Pass; + } + finally + { + externalCreatedump?.Dispose(); + try + { + Directory.Delete(testDirectory, recursive: true); + } + catch (DirectoryNotFoundException) + { + } + } + } + + private static bool RunScenario( + string processPath, + string testDirectory, + ExternalCreatedumpScope externalCreatedump, + string scenarioName, + bool forceExternal) + { + string scenarioDirectory = Path.Combine(testDirectory, scenarioName); + Directory.CreateDirectory(scenarioDirectory); + string markerPath = Path.Combine(scenarioDirectory, "external.marker"); + string dumpPathTemplate = Path.Combine(scenarioDirectory, "coredump.%d.%%"); + + ProcessStartInfo startInfo = new ProcessStartInfo + { + FileName = processPath, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + }; + startInfo.ArgumentList.Add("--crash"); + startInfo.Environment["DOTNET_DbgEnableMiniDump"] = "1"; + startInfo.Environment["DOTNET_DbgMiniDumpName"] = dumpPathTemplate; + startInfo.Environment["DOTNET_DbgMiniDumpType"] = "4"; + startInfo.Environment["DOTNET_EnableCrashReport"] = "0"; + startInfo.Environment["DOTNET_EnableCrashReportOnly"] = "0"; + startInfo.Environment["DOTNET_CreateDumpDiagnostics"] = "0"; + startInfo.Environment["DOTNET_CreateDumpVerboseDiagnostics"] = "0"; + startInfo.Environment.Remove("DOTNET_CreateDumpLogToFile"); + startInfo.Environment[ExternalMarkerEnvironmentVariable] = markerPath; + startInfo.Environment[ExternalHelperEnvironmentVariable] = externalCreatedump.RealHelperPath; + + if (forceExternal) + { + startInfo.Environment["DOTNET_DbgCreateDumpToolPath"] = externalCreatedump.ForcedHelperDirectory; + } + else + { + startInfo.Environment.Remove("DOTNET_DbgCreateDumpToolPath"); + } + + Console.WriteLine($"Launching {scenarioName}: {processPath} --crash"); + Console.WriteLine($"Dump path template: {dumpPathTemplate}"); + + using Process child = Process.Start(startInfo) + ?? throw new InvalidOperationException("Failed to start the crash child."); + Task stdoutTask = child.StandardOutput.ReadToEndAsync(); + Task stderrTask = child.StandardError.ReadToEndAsync(); + + if (!child.WaitForExit(TimeoutMilliseconds)) + { + child.Kill(entireProcessTree: true); + child.WaitForExit(); + throw new TimeoutException($"The {scenarioName} crash child did not exit within {TimeoutMilliseconds} ms."); + } + + string stdout = stdoutTask.GetAwaiter().GetResult(); + string stderr = stderrTask.GetAwaiter().GetResult(); + Console.WriteLine($"{scenarioName} child exit code: {child.ExitCode}"); + if (stdout.Length != 0) + { + Console.WriteLine($"{scenarioName} stdout: {stdout}"); + } + if (stderr.Length != 0) + { + Console.WriteLine($"{scenarioName} stderr: {stderr}"); + } + + if (child.ExitCode == 0) + { + throw new InvalidOperationException($"The {scenarioName} crash child exited successfully."); + } + + bool usedExternal = File.Exists(markerPath); + if (forceExternal && !usedExternal) + { + throw new InvalidOperationException("The forced-external scenario did not execute the external createdump wrapper."); + } + + string combinedOutput = stdout + stderr; + if (!combinedOutput.Contains("[createdump]", StringComparison.Ordinal)) + { + throw new InvalidOperationException($"The {scenarioName} child output has no createdump status marker."); + } + if (!combinedOutput.Contains("Dump successfully written", StringComparison.Ordinal)) + { + throw new InvalidOperationException("Createdump did not report successful dump generation."); + } + + ulong deletedMappingProbe = ParseHexOutputValue(stdout, "DELETED_MAPPING_PROBE=0x"); + string deletedMappingFile = ParseOutputValue(stdout, "DELETED_MAPPING_FILE="); + string dumpFile = Path.Combine(scenarioDirectory, $"coredump.{child.Id}.%"); + string[] dumpFiles = Directory.GetFiles(scenarioDirectory, "coredump.*"); + if (dumpFiles.Length != 1 || !string.Equals(dumpFiles[0], dumpFile, StringComparison.Ordinal)) + { + throw new FileNotFoundException( + $"Expected exactly '{dumpFile}', but found: {string.Join(", ", dumpFiles)}"); + } + + if (OperatingSystem.IsLinux()) + { + ValidateElfCore(dumpFile, processPath, deletedMappingProbe, deletedMappingFile, child.Id); + } + else if (OperatingSystem.IsMacOS()) + { + ValidateMachOCore(dumpFile, deletedMappingProbe, child.Id); + } + else + { + throw new PlatformNotSupportedException(); + } + return usedExternal; + } + + private static void ValidateElfCore( + string dumpFile, + string processPath, + ulong deletedMappingProbe, + string deletedMappingFile, + int expectedPid) + { + using FileStream stream = File.OpenRead(dumpFile); + byte[] header = ReadBytes(stream, 0, ElfHeaderSize); + + if (!header.AsSpan(0, ElfMagic.Length).SequenceEqual(ElfMagic)) + { + throw new InvalidDataException("The dump does not have ELF magic."); + } + if (header[4] != ElfClass64 || header[5] != ElfDataLsb) + { + throw new InvalidDataException($"Expected little-endian ELF64, got class {header[4]} and data encoding {header[5]}."); + } + if (BinaryPrimitives.ReadUInt16LittleEndian(header.AsSpan(16)) != EtCore) + { + throw new InvalidDataException("The ELF file is not ET_CORE."); + } + + ushort machine = BinaryPrimitives.ReadUInt16LittleEndian(header.AsSpan(18)); + ushort expectedMachine = RuntimeInformation.ProcessArchitecture switch + { + Architecture.X64 => EmX86_64, + Architecture.Arm64 => EmAArch64, + _ => throw new PlatformNotSupportedException($"Unsupported architecture {RuntimeInformation.ProcessArchitecture}."), + }; + if (machine != expectedMachine) + { + throw new InvalidDataException($"ELF machine {machine} does not match expected machine {expectedMachine}."); + } + + // Only read the bytes we care about from the program header table. + ulong programHeaderOffset = BinaryPrimitives.ReadUInt64LittleEndian(header.AsSpan(32)); + ushort programHeaderEntrySize = BinaryPrimitives.ReadUInt16LittleEndian(header.AsSpan(54)); + ushort programHeaderCount = BinaryPrimitives.ReadUInt16LittleEndian(header.AsSpan(56)); + if (programHeaderEntrySize != ProgramHeaderSize || programHeaderCount == 0) + { + throw new InvalidDataException( + $"Invalid program-header table: offset {programHeaderOffset}, entry size {programHeaderEntrySize}, count {programHeaderCount}."); + } + + List loadSegments = new List(programHeaderCount); + NoteSummary noteSummary = default; + int noteSegmentCount = 0; + + for (int index = 0; index < programHeaderCount; index++) + { + ulong entryOffset = checked(programHeaderOffset + (ulong)index * programHeaderEntrySize); + byte[] programHeader = ReadBytes(stream, entryOffset, ProgramHeaderSize); + uint type = BinaryPrimitives.ReadUInt32LittleEndian(programHeader); + uint flags = BinaryPrimitives.ReadUInt32LittleEndian(programHeader.AsSpan(4)); + ulong fileOffset = BinaryPrimitives.ReadUInt64LittleEndian(programHeader.AsSpan(8)); + ulong virtualAddress = BinaryPrimitives.ReadUInt64LittleEndian(programHeader.AsSpan(16)); + ulong fileSize = BinaryPrimitives.ReadUInt64LittleEndian(programHeader.AsSpan(32)); + ulong memorySize = BinaryPrimitives.ReadUInt64LittleEndian(programHeader.AsSpan(40)); + ulong alignment = BinaryPrimitives.ReadUInt64LittleEndian(programHeader.AsSpan(48)); + + ValidateFileRange(stream, fileOffset, fileSize, $"program header {index}"); + + if (type == PtLoad) + { + if (fileSize == 0 || memorySize < fileSize) + { + throw new InvalidDataException($"Invalid PT_LOAD sizes at index {index}: file {fileSize}, memory {memorySize}."); + } + if (alignment != ElfLoadAlignment) + { + throw new InvalidDataException( + $"PT_LOAD alignment {alignment} does not match the createdump alignment {ElfLoadAlignment}."); + } + if (fileOffset % alignment != virtualAddress % alignment) + { + throw new InvalidDataException($"PT_LOAD {index} has incongruent file and virtual addresses."); + } + + loadSegments.Add(new LoadSegment(flags, fileOffset, virtualAddress, fileSize, memorySize)); + } + else if (type == PtNote) + { + noteSegmentCount++; + byte[] notes = ReadBytes(stream, fileOffset, checked((int)fileSize)); + ReadNotes(notes, processPath, deletedMappingFile, expectedPid, ref noteSummary); + } + else + { + throw new InvalidDataException($"Unexpected program-header type {type} at index {index}."); + } + } + + if (loadSegments.Count == 0 || noteSegmentCount != 1) + { + throw new InvalidDataException($"Expected PT_LOAD segments and one PT_NOTE segment; found {loadSegments.Count} and {noteSegmentCount}."); + } + + if (noteSummary.PrPsInfo != 1 || + noteSummary.AuxV != 1 || + noteSummary.File != 1 || + noteSummary.PrStatus == 0 || + noteSummary.FpRegSet == 0 || + noteSummary.FpRegSet > noteSummary.PrStatus || + noteSummary.SigInfo != 1 || + noteSummary.Signal != 6 || + !noteSummary.AuxVHasExpectedPageSize || + !noteSummary.FileHasExpectedPageSize || + !noteSummary.HasProgramHeaders || + !noteSummary.HasExecutableFileName || + noteSummary.HasDeletedMappingFileName) + { + throw new InvalidDataException( + $"Unexpected notes: PRSTATUS={noteSummary.PrStatus}, FPREGSET={noteSummary.FpRegSet}, " + + $"PRPSINFO={noteSummary.PrPsInfo}, AUXV={noteSummary.AuxV}, FILE={noteSummary.File}, " + + $"SIGINFO={noteSummary.SigInfo}, signal={noteSummary.Signal}, auxvPageSize={noteSummary.AuxVHasExpectedPageSize}, " + + $"filePageSize={noteSummary.FileHasExpectedPageSize}, " + + $"phdr={noteSummary.HasProgramHeaders}, executable={noteSummary.HasExecutableFileName}, " + + $"deletedName={noteSummary.HasDeletedMappingFileName}."); + } + + byte[] deletedPattern = ReadVirtualMemory(stream, loadSegments, deletedMappingProbe, DeletedMappingPattern.Length); + if (!deletedPattern.AsSpan().SequenceEqual(DeletedMappingPattern)) + { + throw new InvalidDataException("The deleted mapping contents do not match the expected pattern."); + } + + ValidateSpecialDiagnostics( + stream, + loadSegments, + LinuxAndMacArm64SpecialDiagInfoAddress, + PfRead, + ElfMagic); + Console.WriteLine( + $"Validated {Path.GetFileName(dumpFile)}: {loadSegments.Count} PT_LOAD segments, " + + $"{noteSummary.PrStatus} threads, managed exception record and crash JSON present."); + } + + private static void ValidateMachOCore(string dumpFile, ulong deletedMappingProbe, int expectedPid) + { + using FileStream stream = File.OpenRead(dumpFile); + byte[] header = ReadBytes(stream, 0, MachHeader64Size); + + if (BinaryPrimitives.ReadUInt32LittleEndian(header) != MhMagic64) + { + throw new InvalidDataException("The dump does not have 64-bit Mach-O magic."); + } + + uint expectedCpuType = RuntimeInformation.ProcessArchitecture switch + { + Architecture.X64 => CpuTypeX86_64, + Architecture.Arm64 => CpuTypeArm64, + _ => throw new PlatformNotSupportedException($"Unsupported architecture {RuntimeInformation.ProcessArchitecture}."), + }; + uint cpuType = BinaryPrimitives.ReadUInt32LittleEndian(header.AsSpan(4)); + uint fileType = BinaryPrimitives.ReadUInt32LittleEndian(header.AsSpan(12)); + uint loadCommandCount = BinaryPrimitives.ReadUInt32LittleEndian(header.AsSpan(16)); + uint loadCommandBytes = BinaryPrimitives.ReadUInt32LittleEndian(header.AsSpan(20)); + if (cpuType != expectedCpuType || fileType != MhCore || loadCommandCount == 0 || + (ulong)loadCommandCount * MachLoadCommandSize > loadCommandBytes) + { + throw new InvalidDataException( + $"Invalid Mach-O header: CPU=0x{cpuType:X}, fileType={fileType}, " + + $"commands={loadCommandCount}, commandBytes={loadCommandBytes}."); + } + + ulong commandOffset = MachHeader64Size; + ulong commandTableEnd = checked(commandOffset + loadCommandBytes); + ValidateFileRange(stream, commandOffset, loadCommandBytes, "Mach-O load-command table"); + + List loadSegments = new List(); + int threadCommandCount = 0; + int processMetadataNoteCount = 0; + ulong processMetadataOffset = 0; + ulong processMetadataSize = 0; + + for (uint index = 0; index < loadCommandCount; index++) + { + byte[] loadCommandHeader = ReadBytes(stream, commandOffset, MachLoadCommandSize); + uint command = BinaryPrimitives.ReadUInt32LittleEndian(loadCommandHeader); + uint commandSize = BinaryPrimitives.ReadUInt32LittleEndian(loadCommandHeader.AsSpan(4)); + ulong nextCommandOffset = checked(commandOffset + commandSize); + if (commandSize < MachLoadCommandSize || (commandSize & 7) != 0 || + commandSize > int.MaxValue || nextCommandOffset > commandTableEnd) + { + throw new InvalidDataException( + $"Invalid Mach-O load command {index}: type=0x{command:X}, size={commandSize}."); + } + + byte[] loadCommand = ReadBytes(stream, commandOffset, checked((int)commandSize)); + switch (command) + { + case LcSegment64: + if (commandSize != MachSegmentCommand64Size) + { + throw new InvalidDataException($"LC_SEGMENT_64 has unexpected size {commandSize}."); + } + + ulong virtualAddress = BinaryPrimitives.ReadUInt64LittleEndian(loadCommand.AsSpan(24)); + ulong memorySize = BinaryPrimitives.ReadUInt64LittleEndian(loadCommand.AsSpan(32)); + ulong fileOffset = BinaryPrimitives.ReadUInt64LittleEndian(loadCommand.AsSpan(40)); + ulong fileSize = BinaryPrimitives.ReadUInt64LittleEndian(loadCommand.AsSpan(48)); + uint maximumProtection = BinaryPrimitives.ReadUInt32LittleEndian(loadCommand.AsSpan(56)); + uint initialProtection = BinaryPrimitives.ReadUInt32LittleEndian(loadCommand.AsSpan(60)); + uint sectionCount = BinaryPrimitives.ReadUInt32LittleEndian(loadCommand.AsSpan(64)); + uint segmentFlags = BinaryPrimitives.ReadUInt32LittleEndian(loadCommand.AsSpan(68)); + if (fileSize == 0 || memorySize != fileSize || maximumProtection != initialProtection || + (initialProtection & ~VmProtMask) != 0 || sectionCount != 0 || segmentFlags != 0) + { + throw new InvalidDataException( + $"Invalid LC_SEGMENT_64: vm=0x{virtualAddress:X}+0x{memorySize:X}, " + + $"file=0x{fileOffset:X}+0x{fileSize:X}, protections={maximumProtection}/{initialProtection}, " + + $"sections={sectionCount}, flags=0x{segmentFlags:X}."); + } + + ValidateFileRange(stream, fileOffset, fileSize, $"Mach-O segment {loadSegments.Count}"); + loadSegments.Add(new LoadSegment(initialProtection, fileOffset, virtualAddress, fileSize, memorySize)); + break; + + case LcThread: + ValidateMachThreadCommand(loadCommand); + threadCommandCount++; + break; + + case LcNote: + processMetadataNoteCount++; + if (commandSize != MachNoteCommandSize || + !loadCommand.AsSpan(8, ProcessMetadataOwner.Length).SequenceEqual(ProcessMetadataOwner)) + { + throw new InvalidDataException("The process metadata LC_NOTE is invalid."); + } + + processMetadataOffset = BinaryPrimitives.ReadUInt64LittleEndian(loadCommand.AsSpan(24)); + processMetadataSize = BinaryPrimitives.ReadUInt64LittleEndian(loadCommand.AsSpan(32)); + if (processMetadataSize == 0 || processMetadataSize > int.MaxValue) + { + throw new InvalidDataException($"The process metadata LC_NOTE has invalid size {processMetadataSize}."); + } + ValidateFileRange(stream, processMetadataOffset, processMetadataSize, "process metadata"); + break; + + default: + throw new InvalidDataException($"Unexpected Mach-O load command 0x{command:X} at index {index}."); + } + + commandOffset = nextCommandOffset; + } + + if (commandOffset != commandTableEnd || loadSegments.Count == 0 || threadCommandCount == 0 || + processMetadataNoteCount != 1 || processMetadataOffset != commandTableEnd) + { + throw new InvalidDataException( + $"Unexpected Mach-O commands: segments={loadSegments.Count}, threads={threadCommandCount}, " + + $"processMetadata={processMetadataNoteCount}, commandEnd=0x{commandOffset:X}, " + + $"metadataOffset=0x{processMetadataOffset:X}."); + } + + List threadIds = ValidateSpecialThreadInfo(stream, loadSegments, expectedPid, threadCommandCount); + ValidateProcessMetadata(stream, processMetadataOffset, checked((int)processMetadataSize), threadIds); + + byte[] deletedPattern = ReadVirtualMemory(stream, loadSegments, deletedMappingProbe, DeletedMappingPattern.Length); + if (!deletedPattern.AsSpan().SequenceEqual(DeletedMappingPattern)) + { + throw new InvalidDataException("The deleted mapping contents do not match the expected pattern."); + } + + ValidateSpecialDiagnostics( + stream, + loadSegments, + GetMacSpecialDiagInfoAddress(), + VmProtRead, + MachMagic64); + Console.WriteLine( + $"Validated {Path.GetFileName(dumpFile)}: {loadSegments.Count} LC_SEGMENT_64 commands, " + + $"{threadCommandCount} threads, process metadata, managed exception record and crash JSON present."); + } + + private static void ValidateMachThreadCommand(byte[] command) + { + int offset = MachLoadCommandSize; + uint previousFlavor = 0; + for (int stateIndex = 0; stateIndex < 2; stateIndex++) + { + if (offset > command.Length - 8) + { + throw new InvalidDataException("LC_THREAD is missing a thread-state header."); + } + + uint flavor = BinaryPrimitives.ReadUInt32LittleEndian(command.AsSpan(offset)); + uint count = BinaryPrimitives.ReadUInt32LittleEndian(command.AsSpan(offset + 4)); + if (flavor == 0 || flavor == previousFlavor || count == 0 || count > int.MaxValue / sizeof(uint)) + { + throw new InvalidDataException($"LC_THREAD has invalid flavor {flavor} or count {count}."); + } + + previousFlavor = flavor; + offset = checked(offset + 8 + checked((int)count * sizeof(uint))); + if (offset > command.Length) + { + throw new InvalidDataException("LC_THREAD state extends past the load command."); + } + } + + int paddingSize = command.Length - offset; + if (paddingSize >= 8) + { + throw new InvalidDataException("LC_THREAD has trailing or missing state data."); + } + for (; offset < command.Length; offset++) + { + if (command[offset] != 0) + { + throw new InvalidDataException("LC_THREAD has nonzero padding data."); + } + } + } + + private static List ValidateSpecialThreadInfo( + FileStream stream, + List loadSegments, + int expectedPid, + int expectedThreadCount) + { + ulong specialThreadInfoAddress = GetMacSpecialThreadInfoAddress(); + LoadSegment? specialThreadInfoSegment = null; + foreach (LoadSegment segment in loadSegments) + { + if (segment.VirtualAddress == specialThreadInfoAddress) + { + if (specialThreadInfoSegment is not null) + { + throw new InvalidDataException("The dump has multiple special thread-info segments."); + } + specialThreadInfoSegment = segment; + } + } + + byte[] header = ReadVirtualMemory(stream, loadSegments, specialThreadInfoAddress, SpecialThreadInfoHeaderSize); + if (!header.AsSpan(0, SpecialThreadInfoSignature.Length).SequenceEqual(SpecialThreadInfoSignature) || + header[SpecialThreadInfoSignature.Length] != 0) + { + throw new InvalidDataException("The special thread-info signature is missing."); + } + + uint pid = BinaryPrimitives.ReadUInt32LittleEndian(header.AsSpan(16)); + uint threadCount = BinaryPrimitives.ReadUInt32LittleEndian(header.AsSpan(20)); + ulong expectedSize = checked((ulong)SpecialThreadInfoHeaderSize + (ulong)threadCount * SpecialThreadInfoEntrySize); + if (pid != (uint)expectedPid || threadCount == 0 || threadCount != (uint)expectedThreadCount || + threadCount > int.MaxValue || specialThreadInfoSegment is not LoadSegment threadInfoSegment || + threadInfoSegment.Flags != VmProtRead || threadInfoSegment.FileSize != expectedSize || threadInfoSegment.MemorySize != expectedSize) + { + throw new InvalidDataException( + $"Invalid special thread info: pid={pid}, threads={threadCount}, expectedSize={expectedSize}."); + } + + byte[] entries = ReadVirtualMemory( + stream, + loadSegments, + checked(specialThreadInfoAddress + SpecialThreadInfoHeaderSize), + checked((int)threadCount * SpecialThreadInfoEntrySize)); + List threadIds = new List(checked((int)threadCount)); + HashSet uniqueThreadIds = new HashSet(); + for (int index = 0; index < threadCount; index++) + { + int offset = index * SpecialThreadInfoEntrySize; + uint threadId = BinaryPrimitives.ReadUInt32LittleEndian(entries.AsSpan(offset)); + ulong stackPointer = BinaryPrimitives.ReadUInt64LittleEndian(entries.AsSpan(offset + 8)); + if (threadId == 0 || stackPointer == 0 || !uniqueThreadIds.Add(threadId)) + { + throw new InvalidDataException( + $"Invalid special thread-info entry {index}: tid={threadId}, sp=0x{stackPointer:X}."); + } + threadIds.Add(threadId); + } + + return threadIds; + } + + private static void ValidateProcessMetadata( + FileStream stream, + ulong processMetadataOffset, + int processMetadataSize, + List threadIds) + { + string actual = Encoding.UTF8.GetString(ReadBytes(stream, processMetadataOffset, processMetadataSize)); + StringBuilder expected = new StringBuilder("{\"threads\":["); + for (int index = 0; index < threadIds.Count; index++) + { + if (index != 0) + { + expected.Append(','); + } + expected.Append("{\"thread_id\":"); + expected.Append(threadIds[index].ToString(CultureInfo.InvariantCulture)); + expected.Append('}'); + } + expected.Append("]}"); + + if (!actual.Equals(expected.ToString(), StringComparison.Ordinal)) + { + throw new InvalidDataException($"Unexpected process metadata: {actual}"); + } + } + + private static ulong GetMacSpecialDiagInfoAddress() => RuntimeInformation.ProcessArchitecture switch + { + Architecture.X64 => MacX64SpecialDiagInfoAddress, + Architecture.Arm64 => LinuxAndMacArm64SpecialDiagInfoAddress, + _ => throw new PlatformNotSupportedException($"Unsupported architecture {RuntimeInformation.ProcessArchitecture}."), + }; + + private static ulong GetMacSpecialThreadInfoAddress() => RuntimeInformation.ProcessArchitecture switch + { + Architecture.X64 => MacX64SpecialThreadInfoAddress, + Architecture.Arm64 => MacArm64SpecialThreadInfoAddress, + _ => throw new PlatformNotSupportedException($"Unsupported architecture {RuntimeInformation.ProcessArchitecture}."), + }; + + private static void ReadNotes( + byte[] notes, + string processPath, + string deletedMappingFile, + int expectedPid, + ref NoteSummary summary) + { + int offset = 0; + while (offset <= notes.Length - 12) + { + uint nameSize = BinaryPrimitives.ReadUInt32LittleEndian(notes.AsSpan(offset)); + uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(notes.AsSpan(offset + 4)); + uint type = BinaryPrimitives.ReadUInt32LittleEndian(notes.AsSpan(offset + 8)); + ulong descriptionOffset = checked((ulong)offset + 12 + Align4(nameSize)); + ulong nextOffset = checked(descriptionOffset + Align4(dataSize)); + if (nextOffset > (ulong)notes.Length) + { + throw new InvalidDataException("An ELF note extends past the note segment."); + } + + if (nameSize != 5 || !notes.AsSpan(offset + 12, 5).SequenceEqual("CORE\0"u8)) + { + throw new InvalidDataException("Unexpected ELF note owner."); + } + if (dataSize == 0) + { + throw new InvalidDataException($"ELF note 0x{type:X} has an empty payload."); + } + + ReadOnlySpan description = notes.AsSpan(checked((int)descriptionOffset), checked((int)dataSize)); + switch (type) + { + case NtPrStatus: + summary.PrStatus++; + break; + + case NtFpRegSet: + summary.FpRegSet++; + break; + + case NtPrPsInfo: + summary.PrPsInfo++; + if (description.Length < PrPsInfoPidOffset + sizeof(int)) + { + throw new InvalidDataException("NT_PRPSINFO is too small."); + } + int pid = BinaryPrimitives.ReadInt32LittleEndian(description.Slice(PrPsInfoPidOffset)); + if (pid != expectedPid) + { + throw new InvalidDataException($"NT_PRPSINFO PID {pid} does not match crashed process {expectedPid}."); + } + break; + + case NtAuxV: + summary.AuxV++; + ReadAuxv(description, ref summary); + break; + + case NtFile: + summary.File++; + ReadNtFile(description, processPath, deletedMappingFile, ref summary); + break; + + case NtSigInfo: + summary.SigInfo++; + if (description.Length < 12) + { + throw new InvalidDataException("NT_SIGINFO is too small."); + } + summary.Signal = BinaryPrimitives.ReadInt32LittleEndian(description); + break; + + default: + throw new InvalidDataException($"Unexpected ELF note type 0x{type:X}."); + } + + offset = checked((int)nextOffset); + } + + if (offset != notes.Length) + { + throw new InvalidDataException("The ELF note segment has trailing bytes."); + } + } + + private static void ReadAuxv(ReadOnlySpan description, ref NoteSummary summary) + { + const int EntrySize = 16; + if (description.Length % EntrySize != 0) + { + throw new InvalidDataException("NT_AUXV does not contain complete ELF64 entries."); + } + + bool foundNull = false; + for (int offset = 0; offset < description.Length; offset += EntrySize) + { + ulong type = BinaryPrimitives.ReadUInt64LittleEndian(description.Slice(offset)); + ulong value = BinaryPrimitives.ReadUInt64LittleEndian(description.Slice(offset + 8)); + if (type == AtNull) + { + foundNull = true; + break; + } + if (type == AtPageSize) + { + summary.AuxVHasExpectedPageSize |= value == (ulong)Environment.SystemPageSize; + } + else if (type == AtPhdr) + { + summary.HasProgramHeaders |= value != 0; + } + } + + if (!foundNull) + { + throw new InvalidDataException("NT_AUXV has no AT_NULL terminator."); + } + } + + private static void ReadNtFile( + ReadOnlySpan description, + string processPath, + string deletedMappingFile, + ref NoteSummary summary) + { + const int HeaderSize = 16; + const int EntrySize = 24; + if (description.Length < HeaderSize) + { + throw new InvalidDataException("NT_FILE is too small."); + } + + ulong count = BinaryPrimitives.ReadUInt64LittleEndian(description); + ulong pageSize = BinaryPrimitives.ReadUInt64LittleEndian(description.Slice(8)); + summary.FileHasExpectedPageSize = pageSize == (ulong)Environment.SystemPageSize; + if (count == 0 || count > int.MaxValue) + { + throw new InvalidDataException($"NT_FILE has invalid entry count {count}."); + } + + int namesOffset = checked(HeaderSize + checked((int)count) * EntrySize); + if (namesOffset > description.Length) + { + throw new InvalidDataException("NT_FILE entries extend past the payload."); + } + + string executableName = Path.GetFileName(processPath); + string deletedName = Path.GetFileName(deletedMappingFile); + string deletedNameWithSuffix = deletedName + " (deleted)"; + int current = namesOffset; + for (ulong index = 0; index < count; index++) + { + int terminator = description.Slice(current).IndexOf((byte)0); + if (terminator < 0) + { + throw new InvalidDataException("NT_FILE contains an unterminated filename."); + } + + string fileName = Encoding.UTF8.GetString(description.Slice(current, terminator)); + if (Path.GetFileName(fileName).Equals(executableName, StringComparison.Ordinal)) + { + summary.HasExecutableFileName = true; + } + string fileNameWithoutPath = Path.GetFileName(fileName); + if (fileNameWithoutPath.Equals(deletedName, StringComparison.Ordinal) || + fileNameWithoutPath.Equals(deletedNameWithSuffix, StringComparison.Ordinal)) + { + summary.HasDeletedMappingFileName = true; + } + current += terminator + 1; + } + } + + private static void ValidateSpecialDiagnostics( + FileStream stream, + List loadSegments, + ulong specialDiagInfoAddress, + uint expectedReadPermission, + byte[] runtimeImageMagic) + { + LoadSegment? specialDiagnosticsSegment = null; + foreach (LoadSegment segment in loadSegments) + { + if (segment.VirtualAddress == specialDiagInfoAddress) + { + if (specialDiagnosticsSegment is not null) + { + throw new InvalidDataException("The dump has multiple special diagnostics PT_LOAD segments."); + } + specialDiagnosticsSegment = segment; + } + } + + if (specialDiagnosticsSegment is not LoadSegment diagnosticsSegment || + diagnosticsSegment.Flags != expectedReadPermission || + diagnosticsSegment.FileSize != SpecialDiagInfoSize || + diagnosticsSegment.MemorySize != SpecialDiagInfoSize) + { + throw new InvalidDataException("The special diagnostics PT_LOAD segment is missing or invalid."); + } + + byte[] header = ReadVirtualMemory(stream, loadSegments, specialDiagInfoAddress, 40); + if (!header.AsSpan(0, SpecialDiagSignature.Length).SequenceEqual(SpecialDiagSignature) || + header[SpecialDiagSignature.Length] != 0) + { + throw new InvalidDataException("The special diagnostics signature is missing."); + } + if (BinaryPrimitives.ReadInt32LittleEndian(header.AsSpan(16)) != SpecialDiagInfoVersion) + { + throw new InvalidDataException("The special diagnostics version is incorrect."); + } + + ulong exceptionRecordAddress = BinaryPrimitives.ReadUInt64LittleEndian(header.AsSpan(SpecialDiagExceptionRecordOffset)); + ulong runtimeBaseAddress = BinaryPrimitives.ReadUInt64LittleEndian(header.AsSpan(SpecialDiagRuntimeBaseOffset)); + if (exceptionRecordAddress == 0 || runtimeBaseAddress == 0) + { + throw new InvalidDataException( + $"Special diagnostics has invalid addresses: exception=0x{exceptionRecordAddress:X}, runtime=0x{runtimeBaseAddress:X}."); + } + + byte[] runtimeHeader = ReadVirtualMemory(stream, loadSegments, runtimeBaseAddress, runtimeImageMagic.Length); + if (!runtimeHeader.AsSpan().SequenceEqual(runtimeImageMagic)) + { + throw new InvalidDataException("RuntimeBaseAddress does not point to the expected runtime image."); + } + + byte[] exceptionRecord = ReadVirtualMemory(stream, loadSegments, exceptionRecordAddress, ExceptionRecordSize); + uint exceptionCode = BinaryPrimitives.ReadUInt32LittleEndian(exceptionRecord); + uint exceptionFlags = BinaryPrimitives.ReadUInt32LittleEndian(exceptionRecord.AsSpan(4)); + ulong nestedRecord = BinaryPrimitives.ReadUInt64LittleEndian(exceptionRecord.AsSpan(8)); + ulong exceptionAddress = BinaryPrimitives.ReadUInt64LittleEndian(exceptionRecord.AsSpan(16)); + uint parameterCount = BinaryPrimitives.ReadUInt32LittleEndian(exceptionRecord.AsSpan(ExceptionRecordParameterCountOffset)); + ulong fastFailCode = BinaryPrimitives.ReadUInt64LittleEndian(exceptionRecord.AsSpan(ExceptionRecordInformationOffset)); + ulong exceptionHResult = BinaryPrimitives.ReadUInt64LittleEndian(exceptionRecord.AsSpan(ExceptionRecordInformationOffset + 8)); + ulong triageBufferAddress = BinaryPrimitives.ReadUInt64LittleEndian(exceptionRecord.AsSpan(ExceptionRecordInformationOffset + 16)); + ulong triageBufferSize = BinaryPrimitives.ReadUInt64LittleEndian(exceptionRecord.AsSpan(ExceptionRecordInformationOffset + 24)); + + if (exceptionCode != StatusStackBufferOverrun || + exceptionFlags != ExceptionNoncontinuable || + nestedRecord != 0 || + exceptionAddress == 0 || + parameterCount != 4 || + fastFailCode != FastFailExceptionDotNetAot || + exceptionHResult != InvalidOperationHResult || + triageBufferAddress == 0 || + triageBufferSize == 0 || + triageBufferSize > MaximumTriageBufferSize) + { + throw new InvalidDataException( + $"Unexpected fail-fast record: code=0x{exceptionCode:X8}, flags={exceptionFlags}, nested=0x{nestedRecord:X}, " + + $"address=0x{exceptionAddress:X}, params={parameterCount}, fastFail=0x{fastFailCode:X}, " + + $"hr=0x{exceptionHResult:X}, triage=0x{triageBufferAddress:X}+{triageBufferSize}."); + } + + byte[] triageBytes = ReadVirtualMemory(stream, loadSegments, triageBufferAddress, checked((int)triageBufferSize)); + string triageJson = Encoding.UTF8.GetString(triageBytes); + if (!triageJson.StartsWith('{') || + !triageJson.EndsWith('}') || + !triageJson.Contains("\"reason\":\"1\"", StringComparison.Ordinal) || + !triageJson.Contains(ExceptionMessage, StringComparison.Ordinal) || + !triageJson.Contains("System.InvalidOperationException", StringComparison.Ordinal)) + { + throw new InvalidDataException($"Crash JSON does not contain the expected managed exception data: {triageJson}"); + } + } + + private static byte[] ReadVirtualMemory( + FileStream stream, + List loadSegments, + ulong virtualAddress, + int size) + { + ulong unsignedSize = checked((ulong)size); + foreach (LoadSegment segment in loadSegments) + { + if (virtualAddress >= segment.VirtualAddress && + virtualAddress - segment.VirtualAddress < segment.FileSize && + unsignedSize <= segment.FileSize - (virtualAddress - segment.VirtualAddress)) + { + ulong fileOffset = checked(segment.FileOffset + virtualAddress - segment.VirtualAddress); + return ReadBytes(stream, fileOffset, size); + } + } + + throw new InvalidDataException($"Virtual range 0x{virtualAddress:X}+0x{size:X} is absent from PT_LOAD data."); + } + + private static byte[] ReadBytes(FileStream stream, ulong offset, int size) + { + ValidateFileRange(stream, offset, checked((ulong)size), "read"); + byte[] bytes = new byte[size]; + stream.Position = checked((long)offset); + stream.ReadExactly(bytes); + return bytes; + } + + private static void ValidateFileRange(FileStream stream, ulong offset, ulong size, string description) + { + ulong fileLength = checked((ulong)stream.Length); + if (offset > fileLength || size > fileLength - offset) + { + throw new InvalidDataException($"The {description} range 0x{offset:X}+0x{size:X} extends past the dump."); + } + } + + private static ulong Align4(uint value) => ((ulong)value + 3) & ~3UL; + + private static string ParseOutputValue(string output, string prefix) + { + int start = output.IndexOf(prefix, StringComparison.Ordinal); + if (start < 0) + { + throw new InvalidDataException($"Child output does not contain '{prefix}'."); + } + + start += prefix.Length; + int end = output.IndexOfAny(new[] { '\r', '\n' }, start); + return (end < 0 ? output[start..] : output[start..end]).Trim(); + } + + private static ulong ParseHexOutputValue(string output, string prefix) + { + string value = ParseOutputValue(output, prefix); + if (!ulong.TryParse(value, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out ulong result)) + { + throw new InvalidDataException($"'{value}' is not a hexadecimal address."); + } + return result; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static unsafe int CrashChild() + { + string mappingPath = Path.Combine(Path.GetTempPath(), "createdump_deleted_" + Path.GetRandomFileName()); + int pageSize = Environment.SystemPageSize; + int probeOffset = checked(pageSize + DeletedMappingProbeOffset); + byte[] mappingContents = new byte[checked(pageSize * 2)]; + DeletedMappingPattern.CopyTo(mappingContents.AsSpan(probeOffset)); + File.WriteAllBytes(mappingPath, mappingContents); + + s_deletedMappingFile = MemoryMappedFile.CreateFromFile( + mappingPath, + FileMode.Open, + mapName: null, + capacity: 0, + MemoryMappedFileAccess.Read); + s_deletedMappingView = s_deletedMappingFile.CreateViewAccessor( + offset: 0, + size: 0, + MemoryMappedFileAccess.Read); + File.Delete(mappingPath); + + byte* mappingPointer = null; + s_deletedMappingView.SafeMemoryMappedViewHandle.AcquirePointer(ref mappingPointer); + ulong probeAddress = checked((ulong)(mappingPointer + probeOffset)); + Console.WriteLine($"DELETED_MAPPING_PROBE=0x{probeAddress:X}"); + Console.WriteLine($"DELETED_MAPPING_FILE={mappingPath}"); + Console.WriteLine("Throwing the unhandled managed exception."); + Console.Out.Flush(); + + throw new InvalidOperationException(ExceptionMessage); + } +} diff --git a/src/tests/nativeaot/SmokeTests/CreatedumpValidation/CreatedumpValidation.csproj b/src/tests/nativeaot/SmokeTests/CreatedumpValidation/CreatedumpValidation.csproj new file mode 100644 index 00000000000000..4866f8e3891ddc --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/CreatedumpValidation/CreatedumpValidation.csproj @@ -0,0 +1,20 @@ + + + Exe + 0 + true + + true + + true + true + false + true + + + + + + + +