Skip to content

Commit

Permalink
NativeAOT createdump fork/exec for crash dump generation (#89203)
Browse files Browse the repository at this point in the history
* NativeAOT createdump fork/exec for crash dump generation

Port the .NET Core createdump fork/exec code to NativeAOT.

Add src/native/inc/generatedumpflags.h. Remove dup definitions of this enum.

Move and port clrconfignocache.h from src/coreclr/inc to src/native/inc/.

* Add building more command line arguments for signal number, etc.

Fix some build problems.

* Code review feedback - use RhConfig instead of clrconfignocache.h

* Fix string buffer length

* Fix nativeaot test failures. Added crash buffer address to debug header contract

* Generate a core dump on unhandled SIGSEGV/SIGFPE

Add PalCreateDump.h with all the public functions.

* Build and pass an EXCEPTION_RECORD for Linux like it was done for Windows. The next step is to pass the address of it to createdump.

* Pass exception record address to createdump

* Add special diagnostic info memory region

Contains the exception record address for Native AOT crashes.
  • Loading branch information
mikem8361 authored Jul 28, 2023
1 parent 5832fed commit 8ae79d2
Show file tree
Hide file tree
Showing 31 changed files with 896 additions and 117 deletions.
1 change: 1 addition & 0 deletions src/coreclr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ include(../../eng/native/configurepaths.cmake)
include(${CLR_ENG_NATIVE_DIR}/configurecompiler.cmake)

include_directories("${CLR_SRC_NATIVE_DIR}")
include_directories("${CLR_SRC_NATIVE_DIR}/inc")

if(MSVC)
set(CMAKE_CXX_STANDARD_LIBRARIES "") # do not link against standard win32 libs i.e. kernel32, uuid, user32, etc.
Expand Down
6 changes: 5 additions & 1 deletion src/coreclr/debug/createdump/crashinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ CrashInfo::CrashInfo(const CreateDumpOptions& options) :
m_gatherFrames(options.CrashReport),
m_crashThread(options.CrashThread),
m_signal(options.Signal),
m_exceptionRecord(options.ExceptionRecord),
m_moduleInfos(&ModuleInfoCompare),
m_mainModule(nullptr),
m_cbModuleMappings(0),
Expand All @@ -39,7 +40,7 @@ CrashInfo::CrashInfo(const CreateDumpOptions& options) :
m_siginfo.si_signo = options.Signal;
m_siginfo.si_code = options.SignalCode;
m_siginfo.si_errno = options.SignalErrno;
m_siginfo.si_addr = options.SignalAddress;
m_siginfo.si_addr = (void*)options.SignalAddress;
}

CrashInfo::~CrashInfo()
Expand Down Expand Up @@ -193,6 +194,9 @@ CrashInfo::GatherCrashInfo(DumpType dumpType)
{
return false;
}
// Add the special (fake) memory region for the special diagnostics info
MemoryRegion special(PF_R, SpecialDiagInfoAddress, SpecialDiagInfoAddress + PAGE_SIZE);
m_memoryRegions.insert(special);
#ifdef __APPLE__
InitializeOtherMappings();
#endif
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/debug/createdump/crashinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi
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
Expand Down Expand Up @@ -115,6 +116,7 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggi
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 const ModuleInfo* MainModule() const { return m_mainModule; }
inline const uint64_t RuntimeBaseAddress() const { return m_runtimeBaseAddress; }
Expand Down
4 changes: 3 additions & 1 deletion src/coreclr/debug/createdump/createdump.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ typedef struct
int Signal;
int SignalCode;
int SignalErrno;
void* SignalAddress;
uint64_t SignalAddress;
uint64_t ExceptionRecord;
} CreateDumpOptions;

#ifdef HOST_UNIX
Expand All @@ -136,6 +137,7 @@ typedef struct
#include "crashreportwriter.h"
#include "dumpwriter.h"
#include "runtimeinfo.h"
#include "specialdiaginfo.h"
#endif

#ifndef MAX_LONGPATH
Expand Down
11 changes: 7 additions & 4 deletions src/coreclr/debug/createdump/createdumpmain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,10 @@ int createdump_main(const int argc, const char* argv[])
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
options.SignalAddress = 0;
options.ExceptionRecord = 0;
bool help = false;
int exitCode = 0;

Expand Down Expand Up @@ -141,7 +140,11 @@ int createdump_main(const int argc, const char* argv[])
}
else if (strcmp(*argv, "--address") == 0)
{
options.SignalAddress = (void*)atoll(*++argv);
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))
Expand Down
21 changes: 21 additions & 0 deletions src/coreclr/debug/createdump/dumpwriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,27 @@ DumpWriter::OpenDump(const char* dumpFileName)
return true;
}

bool
DumpWriter::WriteDiagInfo(size_t size)
{
// Write the diagnostics info header
SpecialDiagInfoHeader header = {
{SPECIAL_DIAGINFO_SIGNATURE},
SPECIAL_DIAGINFO_VERSION,
m_crashInfo.ExceptionRecord()
};
if (!WriteData(&header, sizeof(header))) {
return false;
}
size_t alignment = size - sizeof(header);
assert(alignment < sizeof(m_tempBuffer));
memset(m_tempBuffer, 0, alignment);
if (!WriteData(m_tempBuffer, alignment)) {
return false;
}
return true;
}

// Write all of the given buffer, handling short writes and EINTR. Return true iff successful.
bool
DumpWriter::WriteData(int fd, const void* buffer, size_t length)
Expand Down
45 changes: 27 additions & 18 deletions src/coreclr/debug/createdump/dumpwriterelf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,28 +176,37 @@ DumpWriter::WriteDump()
size_t size = memoryRegion.Size();
total += size;

while (size > 0)
if (address == SpecialDiagInfoAddress)
{
size_t bytesToRead = std::min(size, sizeof(m_tempBuffer));
size_t read = 0;

if (!m_crashInfo.ReadProcessMemory((void*)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);
if (!WriteDiagInfo(size)) {
return false;
}

// This can happen if the target process dies before createdump is finished
if (read == 0) {
printf_error("Error reading memory at %" PRIA PRIx64 " size %08zx returned 0 bytes read: %s (%d)\n", address, bytesToRead, strerror(g_readProcessMemoryErrno), g_readProcessMemoryErrno);
return false;
}

if (!WriteData(m_tempBuffer, read)) {
return false;
}
else
{
while (size > 0)
{
size_t bytesToRead = std::min(size, sizeof(m_tempBuffer));
size_t read = 0;

if (!m_crashInfo.ReadProcessMemory((void*)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;
}

// This can happen if the target process dies before createdump is finished
if (read == 0) {
printf_error("Error reading memory at %" PRIA PRIx64 " size %08zx returned 0 bytes read: %s (%d)\n", address, bytesToRead, strerror(g_readProcessMemoryErrno), g_readProcessMemoryErrno);
return false;
}

if (!WriteData(m_tempBuffer, read)) {
return false;
}

address += read;
size -= read;
}

address += read;
size -= read;
}
}

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/debug/createdump/dumpwriterelf.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class DumpWriter
static bool WriteData(int fd, const void* buffer, size_t length);

private:
bool WriteDiagInfo(size_t size);
bool WriteProcessInfo();
bool WriteAuxv();
size_t GetNTFileInfoSize(size_t* alignmentBytes = nullptr);
Expand Down
8 changes: 7 additions & 1 deletion src/coreclr/debug/createdump/dumpwritermacho.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,13 @@ DumpWriter::WriteSegments()
(segment.initprot & VM_PROT_EXECUTE) ? 'x' : '-',
segment.initprot);

if (address == SpecialThreadInfoAddress)
if (address == SpecialDiagInfoAddress)
{
if (!WriteDiagInfo(size)) {
return false;
}
}
else if (address == SpecialThreadInfoAddress)
{
// Write the header
SpecialThreadInfoHeader header = {
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/debug/createdump/dumpwritermacho.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class DumpWriter
static bool WriteData(int fd, const void* buffer, size_t length);

private:
bool WriteDiagInfo(size_t size);
void BuildSegmentLoadCommands();
void BuildThreadLoadCommands();
bool WriteHeader(uint64_t* pFileOffset);
Expand Down
32 changes: 32 additions & 0 deletions src/coreclr/debug/createdump/specialdiaginfo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// ******************************************************************************
// WARNING!!!: This code is also used by SOS in the diagnostics repo. Should be
// updated in a backwards and forwards compatible way.
// See: https://github.com/dotnet/diagnostics/blob/main/src/SOS/inc/specialdiaginfo.h
// ******************************************************************************

// This is a special memory region added to ELF and MachO dumps that contains extra diagnostics
// information like the exception record for a crash for a NativeAOT app. The exception record
// contains the pointer to the JSON formatted crash info.

#define SPECIAL_DIAGINFO_SIGNATURE "DIAGINFOHEADER"
#define SPECIAL_DIAGINFO_VERSION 1

#ifdef __APPLE__
const uint64_t SpecialDiagInfoAddress = 0x7fffffff10000000;
#else
#if TARGET_64BIT
const uint64_t SpecialDiagInfoAddress = 0x00007ffffff10000;
#else
const uint64_t SpecialDiagInfoAddress = 0x7fff1000;
#endif
#endif

struct SpecialDiagInfoHeader
{
char Signature[16];
int32_t Version;
uint64_t ExceptionRecordAddress;
};
1 change: 1 addition & 0 deletions src/coreclr/nativeaot/Runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ else()

list(APPEND COMMON_RUNTIME_SOURCES
unix/PalRedhawkUnix.cpp
unix/PalCreateDump.cpp
${GC_DIR}/unix/gcenv.unix.cpp
${GC_DIR}/unix/numasupport.cpp
${GC_DIR}/unix/events.cpp
Expand Down
13 changes: 13 additions & 0 deletions src/coreclr/nativeaot/Runtime/DebugHeader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "thread.h"
#include "threadstore.h"

extern uint8_t g_CrashInfoBuffer[];
GPTR_DECL(MethodTable, g_pFreeObjectEEType);

struct DebugTypeEntry
Expand Down Expand Up @@ -195,6 +196,11 @@ extern "C" void PopulateDebugHeaders()
MAKE_DEBUG_FIELD_ENTRY(ThreadBuffer, m_rgbAllocContextBuffer);
MAKE_DEBUG_FIELD_ENTRY(ThreadBuffer, m_threadId);
MAKE_DEBUG_FIELD_ENTRY(ThreadBuffer, m_pThreadStressLog);
MAKE_DEBUG_FIELD_ENTRY(ThreadBuffer, m_pExInfoStackHead);

MAKE_SIZE_ENTRY(ExInfo);
MAKE_DEBUG_FIELD_ENTRY(ExInfo, m_pPrevExInfo);
MAKE_DEBUG_FIELD_ENTRY(ExInfo, m_exception);

MAKE_SIZE_ENTRY(MethodTable);
MAKE_DEBUG_FIELD_ENTRY(MethodTable, m_uBaseSize);
Expand Down Expand Up @@ -244,6 +250,8 @@ extern "C" void PopulateDebugHeaders()
MAKE_SIZE_ENTRY(RuntimeInstance);
MAKE_DEBUG_FIELD_ENTRY(RuntimeInstance, m_pThreadStore);

MAKE_GLOBAL_ENTRY(g_CrashInfoBuffer);

RuntimeInstance *g_pTheRuntimeInstance = GetRuntimeInstance();
MAKE_GLOBAL_ENTRY(g_pTheRuntimeInstance);

Expand All @@ -269,4 +277,9 @@ extern "C" void PopulateDebugHeaders()
static_assert(MethodTable::Flags::IsGenericFlag == 0x02000000, "The debugging data contract has a hard coded dependency on this value of MethodTable::Flags. If you change this value you must bump major_version_number.");
static_assert(MethodTable::Flags::ElementTypeMask == 0x7C000000, "The debugging data contract has a hard coded dependency on this value of MethodTable::Flags. If you change this value you must bump major_version_number.");
static_assert(MethodTable::Flags::ElementTypeShift == 26, "The debugging data contract has a hard coded dependency on this value of MethodTable::Flags. If you change this value you must bump major_version_number.");
static_assert(MethodTable::Flags::HasComponentSizeFlag == 0x80000000, "The debugging data contract has a hard coded dependency on this value of MethodTable::Flags. If you change this value you must bump major_version_number.");

static_assert(MethodTable::Kinds::CanonicalEEType == 0x00000000, "The debugging data contract has a hard coded dependency on this value of MethodTable::Kinds. If you change this value you must bump major_version_number.");
static_assert(MethodTable::Kinds::ParameterizedEEType == 0x00020000, "The debugging data contract has a hard coded dependency on this value of MethodTable::Kinds. If you change this value you must bump major_version_number.");
static_assert(MethodTable::Kinds::GenericTypeDefEEType == 0x00030000, "The debugging data contract has a hard coded dependency on this value of MethodTable::Kinds. If you change this value you must bump major_version_number.");
}
10 changes: 9 additions & 1 deletion src/coreclr/nativeaot/Runtime/RuntimeInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ bool ShouldHijackForGcStress(uintptr_t CallsiteIP, HijackType ht);
#include "shash.inl"

#define MAX_CRASHINFOBUFFER_SIZE 8192
uint8_t g_CrashInfoBuffer[MAX_CRASHINFOBUFFER_SIZE];
uint8_t g_CrashInfoBuffer[MAX_CRASHINFOBUFFER_SIZE] = { 0 };

ThreadStore * RuntimeInstance::GetThreadStore()
{
Expand All @@ -50,6 +50,14 @@ COOP_PINVOKE_HELPER(uint8_t *, RhGetCrashInfoBuffer, (int32_t* pcbMaxSize))
return g_CrashInfoBuffer;
}

#if TARGET_UNIX
#include "PalCreateDump.h"
COOP_PINVOKE_HELPER(void, RhCreateCrashDumpIfEnabled, (PEXCEPTION_RECORD pExceptionRecord, PCONTEXT pExContext))
{
PalCreateCrashDumpIfEnabled(pExceptionRecord, pExContext);
}
#endif

COOP_PINVOKE_HELPER(uint8_t *, RhGetRuntimeVersion, (int32_t* pcbLength))
{
*pcbLength = sizeof(CLR_PRODUCT_VERSION) - 1; // don't include the terminating null
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/nativeaot/Runtime/eventpipe/ds-rt-aot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,5 @@ ds_rt_aot_set_environment_variable (const ep_char16_t *name, const ep_char16_t *
return SetEnvironmentVariableW(reinterpret_cast<LPCWSTR>(name), reinterpret_cast<LPCWSTR>(value)) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
#endif
}

#endif /* ENABLE_PERFTRACING */
21 changes: 19 additions & 2 deletions src/coreclr/nativeaot/Runtime/eventpipe/ds-rt-aot.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define DIAGNOSTICS_RT_AOT_H

#include <eventpipe/ds-rt-config.h>
#include <generatedumpflags.h>

#ifdef ENABLE_PERFTRACING
#include "ep-rt-aot.h"
Expand Down Expand Up @@ -181,8 +182,24 @@ ds_rt_generate_core_dump (
{
STATIC_CONTRACT_NOTHROW;

// Eventpipe driven core_dump is not currently supported in NativeAOT
return DS_IPC_E_NOTSUPPORTED;
ds_ipc_result_t result = DS_IPC_E_FAIL;
#ifdef TARGET_UNIX
uint32_t flags = ds_generate_core_dump_command_payload_get_flags(payload);
if (commandId == DS_DUMP_COMMANDID_GENERATE_CORE_DUMP)
{
// For the old commmand, this payload field is a bool of whether to enable logging
flags = flags != 0 ? GenerateDumpFlagsLoggingEnabled : 0;
}
const ep_char16_t *dumpName = ds_generate_core_dump_command_payload_get_dump_name (payload);
int32_t dumpType = static_cast<int32_t>(ds_generate_core_dump_command_payload_get_dump_type (payload));
ep_char8_t *dumpNameUtf8 = ep_rt_utf16le_to_utf8_string (dumpName, ep_rt_utf16_string_len (dumpName));
extern bool PalGenerateCoreDump(const char* dumpName, int dumpType, uint32_t flags, char* errorMessageBuffer, int cbErrorMessageBuffer);
if (PalGenerateCoreDump(dumpNameUtf8, dumpType, flags, errorMessageBuffer, cbErrorMessageBuffer))
{
result = DS_IPC_S_OK;
}
#endif
return result;
}

/*
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "UnixContext.h"
#include "HardwareExceptions.h"
#include "UnixSignals.h"
#include "PalCreateDump.h"

#if defined(HOST_APPLE)
#include <mach/mach.h>
Expand Down Expand Up @@ -559,6 +560,8 @@ void SIGSEGVHandler(int code, siginfo_t *siginfo, void *context)
// Restore the original or default handler and restart h/w exception
RestoreSignalHandler(code, &g_previousSIGSEGV);
}

PalCreateCrashDumpIfEnabled(code, siginfo);
}

// Handler for the SIGFPE signal
Expand All @@ -579,6 +582,8 @@ void SIGFPEHandler(int code, siginfo_t *siginfo, void *context)
// Restore the original or default handler and restart h/w exception
RestoreSignalHandler(code, &g_previousSIGFPE);
}

PalCreateCrashDumpIfEnabled(code, siginfo);
}

// Initialize hardware exception handling
Expand Down
Loading

0 comments on commit 8ae79d2

Please sign in to comment.