Skip to content

Commit

Permalink
Create a createdump static library for single-file dump generation (#…
Browse files Browse the repository at this point in the history
…84864)

* Create a createdump static library for single-file and native AOT dump generation

A "createdump_static" lib is now built that is linked into the single-file host and
in the future can be linked into a Native AOT host/app going forward. For single-file
apps most of the same unhandled exception or crash dump generation code is the same
(same env vars, most of all the PAL code is the same) except where createdump executable
was execve, a callback invokes (set by the host) the static createdump lib entry point.

File full dumps are generated (which currently are very large under MacOS) but the user
can put the DAC side-by-side with their app and the smaller heap or mini dumps can be
generated. The SDK could give an option to publish the DAC SXS for a single-file app.
  • Loading branch information
mikem8361 authored May 10, 2023
1 parent 3747edb commit bc11753
Show file tree
Hide file tree
Showing 19 changed files with 695 additions and 503 deletions.
16 changes: 13 additions & 3 deletions src/coreclr/debug/createdump/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ if(CLR_CMAKE_HOST_WIN32)

set(CREATEDUMP_SOURCES
main.cpp
createdumpmain.cpp
dumpname.cpp
createdumpwindows.cpp
createdump.rc
Expand Down Expand Up @@ -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
Expand All @@ -66,25 +67,34 @@ 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)
endif(CLR_CMAKE_HOST_OSX)

target_link_libraries(createdump
PRIVATE
createdump_static
corguids
dbgutil
# share the PAL in the dac module
Expand Down
133 changes: 81 additions & 52 deletions src/coreclr/debug/createdump/crashinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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),
Expand All @@ -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()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -178,7 +182,7 @@ CrashInfo::GatherCrashInfo(MINIDUMP_TYPE minidumpType)
}
#endif
// Load and initialize DAC interfaces
if (!InitializeDAC())
if (!InitializeDAC(dumpType))
{
return false;
}
Expand All @@ -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)
{
Expand All @@ -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)
{
Expand Down Expand Up @@ -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 "";
}
Expand All @@ -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<DumpDataTarget> 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:
Expand All @@ -327,17 +355,18 @@ 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);

// CLRDATA_ENUM_MEM_HEAP2 skips the expensive (in both time and memory usage) enumeration of the
// 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);
Expand Down
22 changes: 10 additions & 12 deletions src/coreclr/debug/createdump/crashinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<MemoryRegion> m_allMemoryRegions; // all memory regions on MacOS
#else
siginfo_t m_siginfo; // signal info (if any)
bool m_canUseProcVmReadSyscall;
int m_fdMem; // /proc/<pid>/mem handle
int m_fdPagemap; // /proc/<pid>/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<MemoryRegion> m_allMemoryRegions; // all memory regions on MacOS
#else
std::array<elf_aux_val_t, AT_MAX> m_auxvValues; // auxv values
std::vector<elf_aux_entry> m_auxvEntries; // full auxv entries
#endif
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -125,10 +123,10 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi
inline const std::set<MemoryRegion>& ModuleMappings() const { return m_moduleMappings; }
inline const std::set<MemoryRegion>& OtherMappings() const { return m_otherMappings; }
inline const std::set<MemoryRegion>& MemoryRegions() const { return m_memoryRegions; }
inline const siginfo_t* SigInfo() const { return &m_siginfo; }
#ifndef __APPLE__
inline const std::vector<elf_aux_entry>& 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
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/debug/createdump/crashinfomac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Loading

0 comments on commit bc11753

Please sign in to comment.