diff --git a/src/coreclr/debug/createdump/CMakeLists.txt b/src/coreclr/debug/createdump/CMakeLists.txt index 2489b8d135e1c..282412f354122 100644 --- a/src/coreclr/debug/createdump/CMakeLists.txt +++ b/src/coreclr/debug/createdump/CMakeLists.txt @@ -14,6 +14,7 @@ if(CLR_CMAKE_HOST_WIN32) set(CREATEDUMP_SOURCES main.cpp + createdumpmain.cpp dumpname.cpp createdumpwindows.cpp createdump.rc @@ -55,7 +56,7 @@ else(CLR_CMAKE_HOST_WIN32) include_directories(${CMAKE_BINARY_DIR}) set(CREATEDUMP_SOURCES - main.cpp + createdumpmain.cpp dumpname.cpp createdumpunix.cpp crashinfo.cpp @@ -66,18 +67,26 @@ else(CLR_CMAKE_HOST_WIN32) ) if(CLR_CMAKE_HOST_OSX) - add_executable_clr(createdump + add_library_clr(createdump_static + STATIC crashinfomac.cpp threadinfomac.cpp dumpwritermacho.cpp ${CREATEDUMP_SOURCES} ) -else() add_executable_clr(createdump + main.cpp + ) +else() + add_library_clr(createdump_static + STATIC crashinfounix.cpp threadinfounix.cpp dumpwriterelf.cpp ${CREATEDUMP_SOURCES} + ) + add_executable_clr(createdump + main.cpp ${PAL_REDEFINES_FILE} ) add_dependencies(createdump pal_redefines_file) @@ -85,6 +94,7 @@ endif(CLR_CMAKE_HOST_OSX) target_link_libraries(createdump PRIVATE + createdump_static corguids dbgutil # share the PAL in the dac module diff --git a/src/coreclr/debug/createdump/crashinfo.cpp b/src/coreclr/debug/createdump/crashinfo.cpp index 5addb7903ccc9..44294dc439bda 100644 --- a/src/coreclr/debug/createdump/crashinfo.cpp +++ b/src/coreclr/debug/createdump/crashinfo.cpp @@ -3,6 +3,9 @@ #include "createdump.h" +typedef BOOL (PALAPI_NOEXPORT *PFN_DLLMAIN)(HINSTANCE, DWORD, LPVOID); /* entry point of module */ +typedef HINSTANCE (PALAPI_NOEXPORT *PFN_REGISTER_MODULE)(LPCSTR); /* used to create the HINSTANCE for above DLLMain entry point */ + // This is for the PAL_VirtualUnwindOutOfProc read memory adapter. CrashInfo* g_crashInfo; @@ -12,9 +15,10 @@ CrashInfo::CrashInfo(const CreateDumpOptions& options) : m_ref(1), m_pid(options.Pid), m_ppid(-1), - m_hdac(nullptr), + 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), @@ -30,12 +34,12 @@ CrashInfo::CrashInfo(const CreateDumpOptions& options) : #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 = options.SignalAddress; -#endif } CrashInfo::~CrashInfo() @@ -64,10 +68,10 @@ CrashInfo::~CrashInfo() m_pClrDataProcess->Release(); } // Unload DAC module - if (m_hdac != nullptr) + if (m_dacModule != nullptr) { - FreeLibrary(m_hdac); - m_hdac = nullptr; + dlclose(m_dacModule); + m_dacModule = nullptr; } #ifdef __APPLE__ if (m_task != 0) @@ -145,7 +149,7 @@ CrashInfo::LogMessage( // Gather all the necessary crash dump info. // bool -CrashInfo::GatherCrashInfo(MINIDUMP_TYPE minidumpType) +CrashInfo::GatherCrashInfo(DumpType dumpType) { // Get the info about the threads (registers, etc.) for (ThreadInfo* thread : m_threads) @@ -178,7 +182,7 @@ CrashInfo::GatherCrashInfo(MINIDUMP_TYPE minidumpType) } #endif // Load and initialize DAC interfaces - if (!InitializeDAC()) + if (!InitializeDAC(dumpType)) { return false; } @@ -205,7 +209,7 @@ CrashInfo::GatherCrashInfo(MINIDUMP_TYPE minidumpType) } } // If full memory dump, include everything regardless of permissions - if (minidumpType & MiniDumpWithFullMemory) + if (dumpType == DumpType::Full) { for (const MemoryRegion& region : m_moduleMappings) { @@ -224,7 +228,7 @@ CrashInfo::GatherCrashInfo(MINIDUMP_TYPE minidumpType) { // Add all the heap read/write memory regions (m_otherMappings contains the heaps). On Alpine // the heap regions are marked RWX instead of just RW. - if (minidumpType & MiniDumpWithPrivateReadWriteMemory) + if (dumpType == DumpType::Heap) { for (const MemoryRegion& region : m_otherMappings) { @@ -254,18 +258,18 @@ GetHResultString(HRESULT hr) { switch (hr) { - case E_FAIL: - return "The operation has failed"; - case E_INVALIDARG: - return "Invalid argument"; - case E_OUTOFMEMORY: - return "Out of memory"; - case CORDBG_E_INCOMPATIBLE_PLATFORMS: - return "The operation failed because debuggee and debugger are on incompatible platforms"; - case CORDBG_E_MISSING_DEBUGGER_EXPORTS: - return "The debuggee memory space does not have the expected debugging export table"; - case CORDBG_E_UNSUPPORTED: - return "The specified action is unsupported by this version of the runtime"; + case E_FAIL: + return "The operation has failed"; + case E_INVALIDARG: + return "Invalid argument"; + case E_OUTOFMEMORY: + return "Out of memory"; + case CORDBG_E_INCOMPATIBLE_PLATFORMS: + return "The operation failed because debuggee and debugger are on incompatible platforms"; + case CORDBG_E_MISSING_DEBUGGER_EXPORTS: + return "The debuggee memory space does not have the expected debugging export table"; + case CORDBG_E_UNSUPPORTED: + return "The specified action is unsupported by this version of the runtime"; } return ""; } @@ -274,49 +278,73 @@ GetHResultString(HRESULT hr) // Enumerate all the memory regions using the DAC memory region support given a minidump type // bool -CrashInfo::InitializeDAC() +CrashInfo::InitializeDAC(DumpType dumpType) { + // Don't attempt to load the DAC if the app model doesn't support it by default. The default for single-file is a + // full dump, but if the dump type requested is a mini, triage or heap and the DAC is side-by-side to the single-file + // application the core dump will be generated. + if (dumpType == DumpType::Full && (m_appModel == AppModelType::SingleFile || m_appModel == AppModelType::NativeAOT)) + { + return true; + } + // Can't load the DAC if the runtime wasn't found + if (m_coreclrPath.empty()) + { + printf_error("InitializeDAC: coreclr not found; not using DAC\n"); + return true; + } ReleaseHolder dataTarget = new DumpDataTarget(*this); PFN_CLRDataCreateInstance pfnCLRDataCreateInstance = nullptr; + PFN_DLLMAIN pfnDllMain = nullptr; bool result = false; HRESULT hr = S_OK; - if (!m_coreclrPath.empty()) - { - // We assume that the DAC is in the same location as the libcoreclr.so module - std::string dacPath; - dacPath.append(m_coreclrPath); - dacPath.append(MAKEDLLNAME_A("mscordaccore")); + // We assume that the DAC is in the same location as the libcoreclr.so module + std::string dacPath; + dacPath.append(m_coreclrPath); + dacPath.append(MAKEDLLNAME_A("mscordaccore")); - // Load and initialize the DAC - m_hdac = LoadLibraryA(dacPath.c_str()); - if (m_hdac == nullptr) - { - printf_error("InitializeDAC: LoadLibraryA(%s) FAILED %s\n", dacPath.c_str(), GetLastErrorString().c_str()); - goto exit; - } - pfnCLRDataCreateInstance = (PFN_CLRDataCreateInstance)GetProcAddress(m_hdac, "CLRDataCreateInstance"); - if (pfnCLRDataCreateInstance == nullptr) - { - printf_error("InitializeDAC: GetProcAddress(CLRDataCreateInstance) FAILED %s\n", GetLastErrorString().c_str()); - goto exit; - } - hr = pfnCLRDataCreateInstance(__uuidof(ICLRDataEnumMemoryRegions), dataTarget, (void**)&m_pClrDataEnumRegions); - if (FAILED(hr)) + // Load and initialize the DAC. We don't use the LoadLibraryA here because the PAL may not be + // initialized properly in the forked process for the statically linked single-file scenario. + m_dacModule = dlopen(dacPath.c_str(), RTLD_LAZY); + if (m_dacModule == nullptr) + { + printf_error("InitializeDAC: dlopen(%s) FAILED %s\n", dacPath.c_str(), dlerror()); + goto exit; + } + pfnDllMain = (PFN_DLLMAIN)dlsym(m_dacModule, "DllMain"); + if (pfnDllMain != nullptr) + { + PFN_REGISTER_MODULE registerModule = (PFN_REGISTER_MODULE)dlsym(m_dacModule, "PAL_RegisterModule"); + if (registerModule == nullptr) { - printf_error("InitializeDAC: CLRDataCreateInstance(ICLRDataEnumMemoryRegions) FAILED %s (%08x)\n", GetHResultString(hr), hr); + printf_error("InitializeDAC: PAL_RegisterModule FAILED\n"); goto exit; } - hr = pfnCLRDataCreateInstance(__uuidof(IXCLRDataProcess), dataTarget, (void**)&m_pClrDataProcess); - if (FAILED(hr)) + HINSTANCE hModule = registerModule(dacPath.c_str()); + if (!pfnDllMain(hModule, DLL_PROCESS_ATTACH, nullptr)) { - printf_error("InitializeDAC: CLRDataCreateInstance(IXCLRDataProcess) FAILED %s (%08x)\n", GetHResultString(hr), hr); + printf_error("InitializeDAC: DllMain(DLL_PROCESS_ATTACH) FAILED\n"); goto exit; } } - else + pfnCLRDataCreateInstance = (PFN_CLRDataCreateInstance)dlsym(m_dacModule, "CLRDataCreateInstance"); + if (pfnCLRDataCreateInstance == nullptr) { - printf_error("InitializeDAC: coreclr not found; not using DAC\n"); + printf_error("InitializeDAC: GetProcAddress(CLRDataCreateInstance) FAILED %s\n", dlerror()); + goto exit; + } + hr = pfnCLRDataCreateInstance(__uuidof(ICLRDataEnumMemoryRegions), dataTarget, (void**)&m_pClrDataEnumRegions); + if (FAILED(hr)) + { + printf_error("InitializeDAC: CLRDataCreateInstance(ICLRDataEnumMemoryRegions) FAILED %s (%08x)\n", GetHResultString(hr), hr); + goto exit; + } + hr = pfnCLRDataCreateInstance(__uuidof(IXCLRDataProcess), dataTarget, (void**)&m_pClrDataProcess); + if (FAILED(hr)) + { + printf_error("InitializeDAC: CLRDataCreateInstance(IXCLRDataProcess) FAILED %s (%08x)\n", GetHResultString(hr), hr); + goto exit; } result = true; exit: @@ -327,9 +355,9 @@ CrashInfo::InitializeDAC() // Enumerate all the memory regions using the DAC memory region support given a minidump type // bool -CrashInfo::EnumerateMemoryRegionsWithDAC(MINIDUMP_TYPE minidumpType) +CrashInfo::EnumerateMemoryRegionsWithDAC(DumpType dumpType) { - if (m_pClrDataEnumRegions != nullptr && (minidumpType & MiniDumpWithFullMemory) == 0) + if (m_pClrDataEnumRegions != nullptr && dumpType != DumpType::Full) { TRACE("EnumerateMemoryRegionsWithDAC: Memory enumeration STARTED (%d %d)\n", m_enumMemoryPagesAdded, m_dataTargetPagesAdded); @@ -337,7 +365,8 @@ CrashInfo::EnumerateMemoryRegionsWithDAC(MINIDUMP_TYPE minidumpType) // low level data structures and adds all the loader allocator heaps instead. The older 'DbgEnableFastHeapDumps' // env var didn't generate a complete enough heap dump on Linux and this new path does. CLRDataEnumMemoryFlags flags = CLRDATA_ENUM_MEM_HEAP2; - if (minidumpType & MiniDumpWithPrivateReadWriteMemory) + MINIDUMP_TYPE minidumpType = GetMiniDumpType(dumpType); + if (dumpType == DumpType::Heap) { // This is the old fast heap env var for backwards compatibility for VS4Mac. CLRConfigNoCache fastHeapDumps = CLRConfigNoCache::Get("DbgEnableFastHeapDumps", /*noprefix*/ false, &getenv); diff --git a/src/coreclr/debug/createdump/crashinfo.h b/src/coreclr/debug/createdump/crashinfo.h index de00dee4bbac3..0e3459b671b68 100644 --- a/src/coreclr/debug/createdump/crashinfo.h +++ b/src/coreclr/debug/createdump/crashinfo.h @@ -48,26 +48,24 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi pid_t m_pid; // pid pid_t m_ppid; // parent pid pid_t m_tgid; // process group - HMODULE m_hdac; // dac module handle when loaded + 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 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 - siginfo_t m_siginfo; // signal info (if any) bool m_canUseProcVmReadSyscall; int m_fdMem; // /proc//mem handle int m_fdPagemap; // /proc//pagemap handle -#endif - std::string m_coreclrPath; // the path of the coreclr module or empty if none - uint64_t m_runtimeBaseAddress; -#ifdef __APPLE__ - std::set m_allMemoryRegions; // all memory regions on MacOS -#else std::array m_auxvValues; // auxv values std::vector m_auxvEntries; // full auxv entries #endif @@ -95,9 +93,9 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi bool Initialize(); void CleanupAndResumeProcess(); bool EnumerateAndSuspendThreads(); - bool GatherCrashInfo(MINIDUMP_TYPE minidumpType); + bool GatherCrashInfo(DumpType dumpType); void CombineMemoryRegions(); - bool EnumerateMemoryRegionsWithDAC(MINIDUMP_TYPE minidumpType); + bool EnumerateMemoryRegionsWithDAC(DumpType dumpType); bool ReadMemory(void* address, void* buffer, size_t size); // read memory and add to dump bool ReadProcessMemory(void* address, void* buffer, size_t size, size_t* read); // read raw memory uint64_t GetBaseAddressFromAddress(uint64_t address); @@ -125,10 +123,10 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi 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; } #ifndef __APPLE__ inline const std::vector& AuxvEntries() const { return m_auxvEntries; } inline size_t GetAuxvSize() const { return m_auxvEntries.size() * sizeof(elf_aux_entry); } - inline const siginfo_t* SigInfo() const { return &m_siginfo; } #endif // IUnknown @@ -156,7 +154,7 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi void VisitProgramHeader(uint64_t loadbias, uint64_t baseAddress, ElfW(Phdr)* phdr); bool EnumerateMemoryRegions(); #endif - bool InitializeDAC(); + bool InitializeDAC(DumpType dumpType); bool EnumerateManagedModules(); bool UnwindAllThreads(); void AddOrReplaceModuleMapping(CLRDATA_ADDRESS baseAddress, ULONG64 size, const std::string& pszName); diff --git a/src/coreclr/debug/createdump/crashinfomac.cpp b/src/coreclr/debug/createdump/crashinfomac.cpp index 21ec72ff23417..97477f2918b63 100644 --- a/src/coreclr/debug/createdump/crashinfomac.cpp +++ b/src/coreclr/debug/createdump/crashinfomac.cpp @@ -245,7 +245,7 @@ void CrashInfo::VisitModule(MachOModule& module) TRACE("TryLookupSymbol(" DACCESS_TABLE_SYMBOL ") FAILED\n"); } } - else if (g_checkForSingleFile) + else if (m_appModel == AppModelType::SingleFile) { uint64_t symbolOffset; if (module.TryLookupSymbol("DotNetRuntimeInfo", &symbolOffset)) diff --git a/src/coreclr/debug/createdump/crashinfounix.cpp b/src/coreclr/debug/createdump/crashinfounix.cpp index 20b2494f329db..d4b003a20ae8b 100644 --- a/src/coreclr/debug/createdump/crashinfounix.cpp +++ b/src/coreclr/debug/createdump/crashinfounix.cpp @@ -17,7 +17,7 @@ bool CrashInfo::Initialize() { char memPath[128]; - _snprintf_s(memPath, sizeof(memPath), sizeof(memPath), "/proc/%lu/mem", m_pid); + _snprintf_s(memPath, sizeof(memPath), sizeof(memPath), "/proc/%u/mem", m_pid); m_fdMem = open(memPath, O_RDONLY); if (m_fdMem == -1) @@ -42,7 +42,7 @@ CrashInfo::Initialize() { TRACE("DbgDisablePagemapUse detected - pagemap file checking is enabled\n"); char pagemapPath[128]; - _snprintf_s(pagemapPath, sizeof(pagemapPath), sizeof(pagemapPath), "/proc/%lu/pagemap", m_pid); + _snprintf_s(pagemapPath, sizeof(pagemapPath), sizeof(pagemapPath), "/proc/%u/pagemap", m_pid); m_fdPagemap = open(pagemapPath, O_RDONLY); if (m_fdPagemap == -1) { @@ -95,7 +95,7 @@ bool CrashInfo::EnumerateAndSuspendThreads() { char taskPath[128]; - snprintf(taskPath, sizeof(taskPath), "/proc/%d/task", m_pid); + _snprintf_s(taskPath, sizeof(taskPath), sizeof(taskPath), "/proc/%u/task", m_pid); DIR* taskDir = opendir(taskPath); if (taskDir == nullptr) @@ -139,7 +139,7 @@ bool CrashInfo::GetAuxvEntries() { char auxvPath[128]; - snprintf(auxvPath, sizeof(auxvPath), "/proc/%d/auxv", m_pid); + _snprintf_s(auxvPath, sizeof(auxvPath), sizeof(auxvPath), "/proc/%u/auxv", m_pid); int fd = open(auxvPath, O_RDONLY, 0); if (fd == -1) @@ -195,7 +195,7 @@ CrashInfo::EnumerateMemoryRegions() // Making something like: /proc/123/maps char mapPath[128]; - int chars = snprintf(mapPath, sizeof(mapPath), "/proc/%d/maps", m_pid); + int chars = _snprintf_s(mapPath, sizeof(mapPath), sizeof(mapPath), "/proc/%u/maps", m_pid); assert(chars > 0 && (size_t)chars <= sizeof(mapPath)); FILE* mapsFile = fopen(mapPath, "r"); @@ -339,7 +339,7 @@ CrashInfo::VisitModule(uint64_t baseAddress, std::string& moduleName) } } } - else if (g_checkForSingleFile) + else if (m_appModel == AppModelType::SingleFile) { if (PopulateForSymbolLookup(baseAddress)) { @@ -489,7 +489,7 @@ bool GetStatus(pid_t pid, pid_t* ppid, pid_t* tgid, std::string* name) { char statusPath[128]; - snprintf(statusPath, sizeof(statusPath), "/proc/%d/status", pid); + _snprintf_s(statusPath, sizeof(statusPath), sizeof(statusPath), "/proc/%d/status", pid); FILE *statusFile = fopen(statusPath, "r"); if (statusFile == nullptr) @@ -538,6 +538,13 @@ ModuleInfo::LoadModule() if (m_module == nullptr) { m_module = dlopen(m_moduleName.c_str(), RTLD_LAZY); - m_localBaseAddress = ((struct link_map*)m_module)->l_addr; + if (m_module != nullptr) + { + m_localBaseAddress = ((struct link_map*)m_module)->l_addr; + } + else + { + TRACE("LoadModule: dlopen(%s) FAILED %s\n", m_moduleName.c_str(), dlerror()); + } } } diff --git a/src/coreclr/debug/createdump/crashreportwriter.cpp b/src/coreclr/debug/createdump/crashreportwriter.cpp index a092e93b42428..77081fad39236 100644 --- a/src/coreclr/debug/createdump/crashreportwriter.cpp +++ b/src/coreclr/debug/createdump/crashreportwriter.cpp @@ -376,7 +376,7 @@ void CrashReportWriter::WriteValue32(const char* key, uint32_t value) { char buffer[16]; - snprintf(buffer, sizeof(buffer), "0x%x", value); + _snprintf_s(buffer, sizeof(buffer), sizeof(buffer), "0x%x", value); WriteValue(key, buffer); } @@ -384,7 +384,7 @@ void CrashReportWriter::WriteValue64(const char* key, uint64_t value) { char buffer[32]; - snprintf(buffer, sizeof(buffer), "0x%" PRIx64, value); + _snprintf_s(buffer, sizeof(buffer), sizeof(buffer), "0x%" PRIx64, value); WriteValue(key, buffer); } diff --git a/src/coreclr/debug/createdump/createdump.h b/src/coreclr/debug/createdump/createdump.h index 0bc6ea73bcfa6..3d796d5446ad1 100644 --- a/src/coreclr/debug/createdump/createdump.h +++ b/src/coreclr/debug/createdump/createdump.h @@ -10,7 +10,6 @@ extern bool g_diagnostics; extern bool g_diagnosticsVerbose; #ifdef HOST_UNIX -extern bool g_checkForSingleFile; extern void trace_printf(const char* format, ...); extern void trace_verbose_printf(const char* format, ...); #define TRACE(args...) trace_printf(args) @@ -91,21 +90,34 @@ typedef int T_CONTEXT; #include #include +enum class DumpType +{ + Mini, + Heap, + Triage, + Full +}; + +enum class AppModelType +{ + Normal, + SingleFile, + NativeAOT +}; + typedef struct { const char* DumpPathTemplate; - const char* DumpType; - MINIDUMP_TYPE MinidumpType; + enum DumpType DumpType; + enum AppModelType AppModel; bool CreateDump; bool CrashReport; int Pid; int CrashThread; int Signal; -#if defined(HOST_UNIX) int SignalCode; int SignalErrno; void* SignalAddress; -#endif } CreateDumpOptions; #ifdef HOST_UNIX @@ -130,7 +142,11 @@ typedef struct 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 extern std::string GetLastErrorString(); +#endif extern void printf_status(const char* format, ...); extern void printf_error(const char* format, ...); diff --git a/src/coreclr/debug/createdump/createdumpmain.cpp b/src/coreclr/debug/createdump/createdumpmain.cpp new file mode 100644 index 0000000000000..503bfecc4ce07 --- /dev/null +++ b/src/coreclr/debug/createdump/createdumpmain.cpp @@ -0,0 +1,419 @@ +// 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" + +#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 + +#ifdef HOST_UNIX +const char* g_help = "createdump [options] pid\n" +#else +const char* g_help = "createdump [options]\n" +#endif +"-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 - enable single-file app check.\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; +uint64_t GetTickFrequency(); +uint64_t GetTimeStamp(); + +// +// Common entry point +// +int createdump_main(const int argc, const char* argv[]) +{ +#ifdef HOST_UNIX + CLRConfigNoCache waitForAttach = CLRConfigNoCache::Get("CreateDumpWaitForAttach", /*noprefix*/ false, &getenv); + DWORD value = 0; + if (waitForAttach.IsSet() && waitForAttach.TryAsInteger(10, value) && value == 1) + { + fprintf(stderr, "[createdump] waiting for attach %u: ", getpid()); + 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; + options.Pid = 0; +#if defined(HOST_UNIX) && !defined(HOST_OSX) + options.SignalCode = 0; + options.SignalErrno = 0; + options.SignalAddress = nullptr; +#endif + bool help = false; + int exitCode = 0; + + // Parse the command line options and target pid + 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, "--code") == 0) + { + options.SignalCode = atoi(*++argv); + } + else if (strcmp(*argv, "--errno") == 0) + { + options.SignalErrno = atoi(*++argv); + } + else if (strcmp(*argv, "--address") == 0) + { + options.SignalAddress = (void*)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 + { +#ifdef HOST_UNIX + options.Pid = atoi(*argv); +#else + printf_error("The pid argument is no longer supported\n"); + return -1; +#endif + } + argv++; + } + } + +#ifdef HOST_UNIX + if (options.Pid == 0) + { + help = true; + } +#endif + + if (help) + { + // if no pid or invalid command line option + printf_error("%s", g_help); + return -1; + } + + g_ticksPerMS = GetTickFrequency() / 1000UL; + g_startTime = GetTimeStamp(); + TRACE("TickFrequency: %d ticks per ms\n", g_ticksPerMS); + + ArrayHolder tmpPath = new char[MAX_LONGPATH]; + if (options.DumpPathTemplate == nullptr) + { + if (::GetTempPathA(MAX_LONGPATH, tmpPath) == 0) + { + //printf_error("GetTempPath failed %s", GetLastErrorString().c_str()); + printf_error("GetTempPath failed\n"); + return ::GetLastError(); + } + exitCode = strcat_s(tmpPath, MAX_LONGPATH, DEFAULT_DUMP_TEMPLATE); + if (exitCode != 0) + { + printf_error("strcat_s failed (%d)", exitCode); + return exitCode; + } + options.DumpPathTemplate = tmpPath; + } + + if (CreateDump(options)) + { + printf_status("Dump successfully written in %llums\n", GetTimeStamp() - g_startTime); + } + else + { + printf_error("Failure took %llums\n", GetTimeStamp() - g_startTime); + exitCode = -1; + } + + fflush(g_stdout); + + if (g_logfile != nullptr) + { + fflush(g_logfile); + fclose(g_logfile); + } + 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) +{ + switch (dumpType) + { + case DumpType::Mini: + return (MINIDUMP_TYPE)(MiniDumpNormal | + MiniDumpWithDataSegs | + MiniDumpWithHandleData | + MiniDumpWithThreadInfo); + case DumpType::Heap: + return (MINIDUMP_TYPE)(MiniDumpWithPrivateReadWriteMemory | + MiniDumpWithDataSegs | + MiniDumpWithHandleData | + MiniDumpWithUnloadedModules | + MiniDumpWithFullMemoryInfo | + MiniDumpWithThreadInfo | + MiniDumpWithTokenInformation); + case DumpType::Triage: + return (MINIDUMP_TYPE)(MiniDumpFilterTriage | + MiniDumpIgnoreInaccessibleMemory | + MiniDumpWithoutOptionalData | + MiniDumpWithProcessThreadData | + MiniDumpFilterModulePaths | + MiniDumpWithUnloadedModules | + MiniDumpFilterMemory | + MiniDumpWithHandleData); + case DumpType::Full: + default: + return (MINIDUMP_TYPE)(MiniDumpWithFullMemory | + MiniDumpWithDataSegs | + MiniDumpWithHandleData | + MiniDumpWithUnloadedModules | + MiniDumpWithFullMemoryInfo | + MiniDumpWithThreadInfo | + MiniDumpWithTokenInformation); + } +} + +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); +} + +uint64_t +GetTickFrequency() +{ + LARGE_INTEGER ret; + ZeroMemory(&ret, sizeof(LARGE_INTEGER)); + QueryPerformanceFrequency(&ret); + return ret.QuadPart; +} + +uint64_t +GetTimeStamp() +{ + LARGE_INTEGER ret; + ZeroMemory(&ret, sizeof(LARGE_INTEGER)); + QueryPerformanceCounter(&ret); + return ret.QuadPart / g_ticksPerMS; +} + +#ifdef HOST_UNIX + +static void +trace_prefix() +{ + // Only add this prefix if logging to the console + if (g_logfile == nullptr) + { + fprintf(g_stdout, "[createdump] "); + } + fprintf(g_stdout, "%08" PRIx64 " ", GetTimeStamp()); +} + +void +trace_printf(const char* format, ...) +{ + if (g_diagnostics) + { + va_list args; + va_start(args, format); + trace_prefix(); + vfprintf(g_stdout, format, args); + fflush(g_stdout); + va_end(args); + } +} + +void +trace_verbose_printf(const char* format, ...) +{ + if (g_diagnosticsVerbose) + { + va_list args; + va_start(args, format); + trace_prefix(); + vfprintf(g_stdout, format, args); + fflush(g_stdout); + va_end(args); + } +} + +void +CrashInfo::Trace(const char* format, ...) +{ + if (g_diagnostics) + { + va_list args; + va_start(args, format); + trace_prefix(); + vfprintf(g_stdout, format, args); + fflush(g_stdout); + va_end(args); + } +} + +void +CrashInfo::TraceVerbose(const char* format, ...) +{ + if (g_diagnosticsVerbose) + { + va_list args; + va_start(args, format); + trace_prefix(); + vfprintf(g_stdout, format, args); + fflush(g_stdout); + va_end(args); + } +} + +void initialize_static_createdump() +{ + PAL_SetCreateDumpCallback(createdump_main); +} + +#endif // HOST_UNIX diff --git a/src/coreclr/debug/createdump/createdumpunix.cpp b/src/coreclr/debug/createdump/createdumpunix.cpp index eec103af880f1..f39ab9be4b932 100644 --- a/src/coreclr/debug/createdump/createdumpunix.cpp +++ b/src/coreclr/debug/createdump/createdumpunix.cpp @@ -24,6 +24,12 @@ CreateDump(const CreateDumpOptions& options) #endif TRACE("PAGE_SIZE %d\n", PAGE_SIZE); + if (options.CrashReport && (options.AppModel == AppModelType::SingleFile || options.AppModel != AppModelType::NativeAOT)) + { + printf_error("The app model does not support crash report generation\n"); + goto exit; + } + // Initialize the crash info if (!crashInfo->Initialize()) { @@ -42,7 +48,7 @@ CreateDump(const CreateDumpOptions& options) goto exit; } // Gather all the info about the process, threads (registers, etc.) and memory regions - if (!crashInfo->GatherCrashInfo(options.MinidumpType)) + if (!crashInfo->GatherCrashInfo(options.DumpType)) { goto exit; } @@ -60,14 +66,14 @@ CreateDump(const CreateDumpOptions& options) if (options.CreateDump) { // Gather all the useful memory regions from the DAC - if (!crashInfo->EnumerateMemoryRegionsWithDAC(options.MinidumpType)) + if (!crashInfo->EnumerateMemoryRegionsWithDAC(options.DumpType)) { goto exit; } // Join all adjacent memory regions crashInfo->CombineMemoryRegions(); - printf_status("Writing %s to file %s\n", options.DumpType, dumpPath.c_str()); + 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())) diff --git a/src/coreclr/debug/createdump/createdumpwindows.cpp b/src/coreclr/debug/createdump/createdumpwindows.cpp index 4e8429255a343..759ec3c52f678 100644 --- a/src/coreclr/debug/createdump/createdumpwindows.cpp +++ b/src/coreclr/debug/createdump/createdumpwindows.cpp @@ -55,7 +55,7 @@ CreateDump(const CreateDumpOptions& options) { goto exit; } - printf_status("Writing %s for process %d to file %s\n", options.DumpType, pid, dumpPath.c_str()); + 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); if (hFile == INVALID_HANDLE_VALUE) @@ -67,7 +67,7 @@ CreateDump(const CreateDumpOptions& options) // Retry the write dump on ERROR_PARTIAL_COPY for (int i = 0; i < 5; i++) { - if (MiniDumpWriteDump(hProcess, pid, hFile, options.MinidumpType, NULL, NULL, NULL)) + if (MiniDumpWriteDump(hProcess, pid, hFile, GetMiniDumpType(options.DumpType), NULL, NULL, NULL)) { result = true; break; @@ -96,3 +96,37 @@ CreateDump(const CreateDumpOptions& options) return result; } + +std::string +GetLastErrorString() +{ + DWORD error = GetLastError(); + std::string result; + LPSTR messageBuffer; + DWORD length = FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&messageBuffer, + 0, + NULL); + if (length > 0) + { + result.append(messageBuffer, length); + LocalFree(messageBuffer); + + // Remove the \r\n at the end of the system message. Assumes that the \r is first. + size_t found = result.find_last_of('\r'); + if (found != std::string::npos) + { + result.erase(found); + } + result.append(" "); + } + char buffer[64]; + _snprintf_s(buffer, sizeof(buffer), sizeof(buffer), "(%d)", error); + result.append(buffer); + return result; +} + diff --git a/src/coreclr/debug/createdump/datatarget.cpp b/src/coreclr/debug/createdump/datatarget.cpp index 6f6a68ef70eed..38675532aeb78 100644 --- a/src/coreclr/debug/createdump/datatarget.cpp +++ b/src/coreclr/debug/createdump/datatarget.cpp @@ -4,12 +4,6 @@ #include "createdump.h" #include -#if defined(HOST_ARM64) -// Flag to check if atomics feature is available on -// the machine -bool g_arm64_atomics_present = false; -#endif - DumpDataTarget::DumpDataTarget(CrashInfo& crashInfo) : m_ref(1), m_crashInfo(crashInfo) diff --git a/src/coreclr/debug/createdump/main.cpp b/src/coreclr/debug/createdump/main.cpp index 75a616dc2e47b..822947271aff1 100644 --- a/src/coreclr/debug/createdump/main.cpp +++ b/src/coreclr/debug/createdump/main.cpp @@ -3,51 +3,12 @@ #include "createdump.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 +extern int createdump_main(const int argc, const char* argv[]); -#ifdef HOST_UNIX -const char* g_help = "createdump [options] pid\n" -#else -const char* g_help = "createdump [options]\n" -#endif -"-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 - enable single-file app check.\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; -uint64_t GetTickFrequency(); -uint64_t GetTimeStamp(); - -#ifdef HOST_UNIX -bool g_checkForSingleFile = false; +#if defined(HOST_ARM64) +// Flag to check if atomics feature is available on +// the machine +bool g_arm64_atomics_present = false; #endif // @@ -55,29 +16,7 @@ bool g_checkForSingleFile = false; // int __cdecl main(const int argc, const char* argv[]) { - CreateDumpOptions options; - options.MinidumpType = (MINIDUMP_TYPE)(MiniDumpWithPrivateReadWriteMemory | - MiniDumpWithDataSegs | - MiniDumpWithHandleData | - MiniDumpWithUnloadedModules | - MiniDumpWithFullMemoryInfo | - MiniDumpWithThreadInfo | - MiniDumpWithTokenInformation); - options.DumpType = "minidump with heap"; - options.DumpPathTemplate = nullptr; - options.CrashReport = false; - options.CreateDump = true; - options.Signal = 0; - options.CrashThread = 0; - options.Pid = 0; -#if defined(HOST_UNIX) - options.SignalCode = 0; - options.SignalErrno = 0; - options.SignalAddress = nullptr; -#endif - bool help = false; int exitCode = 0; - #ifdef HOST_UNIX exitCode = PAL_InitializeDLL(); if (exitCode != 0) @@ -86,344 +25,21 @@ int __cdecl main(const int argc, const char* argv[]) return exitCode; } #endif - - // Parse the command line options and target pid - 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 = "minidump"; - options.MinidumpType = (MINIDUMP_TYPE)(MiniDumpNormal | - MiniDumpWithDataSegs | - MiniDumpWithHandleData | - MiniDumpWithThreadInfo); - } - else if ((strcmp(*argv, "-h") == 0) || (strcmp(*argv, "--withheap") == 0)) - { - options.DumpType = "minidump with heap"; - options.MinidumpType = (MINIDUMP_TYPE)(MiniDumpWithPrivateReadWriteMemory | - MiniDumpWithDataSegs | - MiniDumpWithHandleData | - MiniDumpWithUnloadedModules | - MiniDumpWithFullMemoryInfo | - MiniDumpWithThreadInfo | - MiniDumpWithTokenInformation); - } - else if ((strcmp(*argv, "-t") == 0) || (strcmp(*argv, "--triage") == 0)) - { - options.DumpType = "triage minidump"; - options.MinidumpType = (MINIDUMP_TYPE)(MiniDumpFilterTriage | - MiniDumpIgnoreInaccessibleMemory | - MiniDumpWithoutOptionalData | - MiniDumpWithProcessThreadData | - MiniDumpFilterModulePaths | - MiniDumpWithUnloadedModules | - MiniDumpFilterMemory | - MiniDumpWithHandleData); - } - else if ((strcmp(*argv, "-u") == 0) || (strcmp(*argv, "--full") == 0)) - { - options.DumpType = "full dump"; - options.MinidumpType = (MINIDUMP_TYPE)(MiniDumpWithFullMemory | - MiniDumpWithDataSegs | - MiniDumpWithHandleData | - MiniDumpWithUnloadedModules | - MiniDumpWithFullMemoryInfo | - MiniDumpWithThreadInfo | - MiniDumpWithTokenInformation); - } -#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) - { - g_checkForSingleFile = true; - } - 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 = (void*)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 - { -#ifdef HOST_UNIX - options.Pid = atoi(*argv); -#else - printf_error("The pid argument is no longer supported\n"); - return -1; -#endif - } - argv++; - } - } - -#ifdef HOST_UNIX - if (options.Pid == 0) - { - help = true; - } -#endif - - if (help) - { - // if no pid or invalid command line option - printf_error("%s", g_help); - return -1; - } - - g_ticksPerMS = GetTickFrequency() / 1000UL; - g_startTime = GetTimeStamp(); - TRACE("TickFrequency: %d ticks per ms\n", g_ticksPerMS); - - ArrayHolder tmpPath = new char[MAX_LONGPATH]; - if (options.DumpPathTemplate == nullptr) - { - if (::GetTempPathA(MAX_LONGPATH, tmpPath) == 0) - { - printf_error("GetTempPath failed %s", GetLastErrorString().c_str()); - return ::GetLastError(); - } - exitCode = strcat_s(tmpPath, MAX_LONGPATH, DEFAULT_DUMP_TEMPLATE); - if (exitCode != 0) - { - printf_error("strcat_s failed (%d)", exitCode); - return exitCode; - } - options.DumpPathTemplate = tmpPath; - } - - if (CreateDump(options)) - { - printf_status("Dump successfully written in %llums\n", GetTimeStamp() - g_startTime); - } - else - { - printf_error("Failure took %llums\n", GetTimeStamp() - g_startTime); - exitCode = -1; - } - - fflush(g_stdout); - - if (g_logfile != nullptr) - { - fflush(g_logfile); - fclose(g_logfile); - } + exitCode = createdump_main(argc, argv); #ifdef HOST_UNIX PAL_TerminateEx(exitCode); #endif return exitCode; } -std::string -GetLastErrorString() -{ - DWORD error = GetLastError(); - std::string result; -#ifdef HOST_WINDOWS - LPSTR messageBuffer; - DWORD length = FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR)&messageBuffer, - 0, - NULL); - if (length > 0) - { - result.append(messageBuffer, length); - LocalFree(messageBuffer); - - // Remove the \r\n at the end of the system message. Assumes that the \r is first. - size_t found = result.find_last_of('\r'); - if (found != std::string::npos) - { - result.erase(found); - } - result.append(" "); - } -#endif - char buffer[64]; - snprintf(buffer, sizeof(buffer), "(%d)", error); - result.append(buffer); - return result; -} - -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); -} - -uint64_t -GetTickFrequency() -{ - LARGE_INTEGER ret; - ZeroMemory(&ret, sizeof(LARGE_INTEGER)); - QueryPerformanceFrequency(&ret); - return ret.QuadPart; -} - -uint64_t -GetTimeStamp() -{ - LARGE_INTEGER ret; - ZeroMemory(&ret, sizeof(LARGE_INTEGER)); - QueryPerformanceCounter(&ret); - return ret.QuadPart / g_ticksPerMS; -} - #ifdef HOST_UNIX -static void -trace_prefix() -{ - // Only add this prefix if logging to the console - if (g_logfile == nullptr) - { - fprintf(g_stdout, "[createdump] "); - } - fprintf(g_stdout, "%08" PRIx64 " ", GetTimeStamp()); -} - -void -trace_printf(const char* format, ...) +PALIMPORT +VOID +PALAPI +PAL_SetCreateDumpCallback( + IN PCREATEDUMP_CALLBACK callback) { - if (g_diagnostics) - { - va_list args; - va_start(args, format); - trace_prefix(); - vfprintf(g_stdout, format, args); - fflush(g_stdout); - va_end(args); - } } -void -trace_verbose_printf(const char* format, ...) -{ - if (g_diagnosticsVerbose) - { - va_list args; - va_start(args, format); - trace_prefix(); - vfprintf(g_stdout, format, args); - fflush(g_stdout); - va_end(args); - } -} - -void -CrashInfo::Trace(const char* format, ...) -{ - if (g_diagnostics) - { - va_list args; - va_start(args, format); - trace_prefix(); - vfprintf(g_stdout, format, args); - fflush(g_stdout); - va_end(args); - } -} - -void -CrashInfo::TraceVerbose(const char* format, ...) -{ - if (g_diagnosticsVerbose) - { - va_list args; - va_start(args, format); - trace_prefix(); - vfprintf(g_stdout, format, args); - fflush(g_stdout); - va_end(args); - } -} - -#endif // HOST_UNIX +#endif diff --git a/src/coreclr/pal/inc/pal.h b/src/coreclr/pal/inc/pal.h index 8e085e3cc15ab..866f76a6a3060 100644 --- a/src/coreclr/pal/inc/pal.h +++ b/src/coreclr/pal/inc/pal.h @@ -343,12 +343,6 @@ PAL_Initialize( int argc, char * const argv[]); -PALIMPORT -void -PALAPI -PAL_InitializeWithFlags( - DWORD flags); - PALIMPORT int PALAPI @@ -402,6 +396,17 @@ PALAPI PAL_SetShutdownCallback( IN PSHUTDOWN_CALLBACK callback); +/// +/// Used by the single-file and native AOT hosts to connect the linked in version of createdump +/// +typedef int (*PCREATEDUMP_CALLBACK)(const int argc, const char* argv[]); + +PALIMPORT +VOID +PALAPI +PAL_SetCreateDumpCallback( + IN PCREATEDUMP_CALLBACK callback); + // Must be the same as the copy in excep.h and the WriteDumpFlags enum in the diagnostics repo enum { diff --git a/src/coreclr/pal/src/thread/process.cpp b/src/coreclr/pal/src/thread/process.cpp index 32da7beef37e6..a3cb53270b1fc 100644 --- a/src/coreclr/pal/src/thread/process.cpp +++ b/src/coreclr/pal/src/thread/process.cpp @@ -236,6 +236,9 @@ static_assert_no_msg(CLR_SEM_MAX_NAMELEN <= MAX_PATH); // Function to call during PAL/process shutdown/abort Volatile g_shutdownCallback = nullptr; +// Function to call instead of exec'ing the createdump binary. Used by single-file and native AOT hosts. +Volatile g_createdumpCallback = nullptr; + // Crash dump generating program arguments. Initialized in PROCAbortInitialize(). std::vector g_argvCreateDump; @@ -1372,6 +1375,25 @@ PAL_SetShutdownCallback( g_shutdownCallback = callback; } +/*++ +Function: + PAL_SetCreateDumpCallback + +Abstract: + Sets a callback that is executed when create dump is launched to create a crash dump. + + NOTE: Currently only one callback can be set at a time. +--*/ +PALIMPORT +VOID +PALAPI +PAL_SetCreateDumpCallback( + IN PCREATEDUMP_CALLBACK callback) +{ + _ASSERTE(g_createdumpCallback == nullptr); + g_createdumpCallback = callback; +} + // Build the semaphore names using the PID and a value that can be used for distinguishing // between processes with the same PID (which ran at different times). This is to avoid // cases where a prior process with the same PID exited abnormally without having a chance @@ -2202,11 +2224,22 @@ PROCCreateCrashDump( { dup2(child_pipe, STDERR_FILENO); } - // Execute the createdump program - if (execve(argv[0], (char**)argv.data(), palEnvironment) == -1) + if (g_createdumpCallback != nullptr) + { + // Remove the signal handlers inherited from the runtime process + SEHCleanupSignals(); + + // Call the statically linked createdump code + g_createdumpCallback(argv.size(), argv.data()); + } + else { - fprintf(stderr, "Problem launching createdump (may not have execute permissions): execve(%s) FAILED %s (%d)\n", argv[0], strerror(errno), errno); - exit(-1); + // Execute the createdump program + if (execve(argv[0], (char**)argv.data(), palEnvironment) == -1) + { + fprintf(stderr, "Problem launching createdump (may not have execute permissions): execve(%s) FAILED %s (%d)\n", argv[0], strerror(errno), errno); + exit(-1); + } } } else @@ -2252,7 +2285,7 @@ PROCCreateCrashDump( else { #ifdef _DEBUG - fprintf(stderr, "[createdump] waitpid() returned successfully (wstatus %08x)\n", wstatus); + fprintf(stderr, "waitpid() returned successfully (wstatus %08x) WEXITSTATUS %x WTERMSIG %x\n", wstatus, WEXITSTATUS(wstatus), WTERMSIG(wstatus)); #endif return !WIFEXITED(wstatus) || WEXITSTATUS(wstatus) == 0; } diff --git a/src/native/corehost/apphost/static/CMakeLists.txt b/src/native/corehost/apphost/static/CMakeLists.txt index db5d751a3fc6f..adc5dfeaefac5 100644 --- a/src/native/corehost/apphost/static/CMakeLists.txt +++ b/src/native/corehost/apphost/static/CMakeLists.txt @@ -132,7 +132,11 @@ if(CLR_CMAKE_TARGET_WIN32) set(RUNTIMEINFO_LIB runtimeinfo) else() - set(NATIVE_LIBS + if(CLR_CMAKE_HOST_OSX OR (CLR_CMAKE_HOST_LINUX AND NOT CLR_CMAKE_HOST_UNIX_X86 AND NOT CLR_CMAKE_HOST_ANDROID)) + LIST(APPEND NATIVE_LIBS createdump_static) + endif() + + LIST(APPEND NATIVE_LIBS coreclr_static System.Globalization.Native-Static @@ -142,7 +146,9 @@ else() System.Security.Cryptography.Native.OpenSsl-Static palrt - coreclrpal + coreclrpal_dac + corguids + dbgutil eventprovider nativeresourcestring ) diff --git a/src/native/corehost/corehost.cpp b/src/native/corehost/corehost.cpp index be7b76a519964..7a85b6acbb216 100644 --- a/src/native/corehost/corehost.cpp +++ b/src/native/corehost/corehost.cpp @@ -101,6 +101,8 @@ void need_newer_framework_error(const pal::string_t& dotnet_root, const pal::str int exe_start(const int argc, const pal::char_t* argv[]) { + pal::initialize_createdump(); + pal::string_t host_path; if (!pal::get_own_executable_path(&host_path) || !pal::realpath(&host_path)) { diff --git a/src/native/corehost/hostmisc/pal.h b/src/native/corehost/hostmisc/pal.h index 72aa756a93399..f7a07c6a01eca 100644 --- a/src/native/corehost/hostmisc/pal.h +++ b/src/native/corehost/hostmisc/pal.h @@ -335,6 +335,8 @@ namespace pal bool is_emulating_x64(); bool are_paths_equal_with_normalized_casing(const string_t& path1, const string_t& path2); + + void initialize_createdump(); } #endif // PAL_H diff --git a/src/native/corehost/hostmisc/pal.unix.cpp b/src/native/corehost/hostmisc/pal.unix.cpp index fafcb90d679f0..46ffaf951adfb 100644 --- a/src/native/corehost/hostmisc/pal.unix.cpp +++ b/src/native/corehost/hostmisc/pal.unix.cpp @@ -1106,3 +1106,14 @@ bool pal::are_paths_equal_with_normalized_casing(const string_t& path1, const st return path1 == path2; #endif } + +#if defined(FEATURE_STATIC_HOST) && (defined(TARGET_OSX) || defined(TARGET_LINUX)) && !defined(TARGET_X86) +extern void initialize_static_createdump(); +#endif + +void pal::initialize_createdump() +{ +#if defined(FEATURE_STATIC_HOST) && (defined(TARGET_OSX) || defined(TARGET_LINUX)) && !defined(TARGET_X86) + initialize_static_createdump(); +#endif +} diff --git a/src/native/corehost/hostmisc/pal.windows.cpp b/src/native/corehost/hostmisc/pal.windows.cpp index 6afca1fe41de6..98e4efbed72f0 100644 --- a/src/native/corehost/hostmisc/pal.windows.cpp +++ b/src/native/corehost/hostmisc/pal.windows.cpp @@ -942,3 +942,7 @@ void pal::mutex_t::unlock() { ::LeaveCriticalSection(&_impl); } + +void pal::initialize_createdump() +{ +}