Skip to content
Draft
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions eng/pipelines/cdac/prepare-cdac-helix-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ parameters:
buildDebuggees: true
skipDebuggeeCopy: false
runtimeConfiguration: 'Checked'
# When true, copy the cdac-lite native library into the testhost payload (next to coreclr) so
# createdump can select managed memory with cdac-lite instead of the legacy DAC. Paired with the
# DOTNET_DbgUseCdacLite pre-command in cdac-dump-helix.proj. Supported on Windows, Linux, and macOS.
useCdacLite: false
# Configuration the cDAC managed assemblies are built at during payload
# prep. The dump tests load the managed cDAC directly via ProjectReference,
# so passing /p:Configuration=Debug here makes MSBuild walk the
Expand Down Expand Up @@ -69,3 +73,23 @@ steps:
Write-Host "Helix queue: $queue"
Write-Host "##vso[task.setvariable variable=CdacHelixQueue]$queue"
displayName: 'Find TestHost Directory and Helix Queue'

- ${{ if parameters.useCdacLite }}:
# cdac-lite mode: place the cdac-lite native library next to the testhost's coreclr so createdump
# (which runs on the Helix machine when a debuggee crashes) loads it and selects the managed memory
# instead of the legacy DAC. Supported on Windows, Linux, and macOS.
- pwsh: |
$names = switch ("$(osGroup)") {
"windows" { @{ coreclr = "coreclr.dll"; cdaclite = "cdaclite.dll" } }
"osx" { @{ coreclr = "libcoreclr.dylib"; cdaclite = "libcdaclite.dylib" } }
default { @{ coreclr = "libcoreclr.so"; cdaclite = "libcdaclite.so" } }
}
$coreclr = Get-ChildItem -Recurse -ErrorAction SilentlyContinue `
-Path "$(Build.SourcesDirectory)/artifacts/bin/testhost/net*-$(osGroup)-*-$(archType)/shared/Microsoft.NETCore.App" `
-Filter $names.coreclr | Select-Object -First 1
if (-not $coreclr) { Write-Error "$($names.coreclr) not found in the testhost shared framework."; exit 1 }
$src = "$(Build.SourcesDirectory)/artifacts/bin/coreclr/$(osGroup).$(archType).${{ parameters.runtimeConfiguration }}/$($names.cdaclite)"
if (-not (Test-Path $src)) { Write-Error "$($names.cdaclite) not found at $src (was it built by the CdacBuild -s clr subset?)."; exit 1 }
Copy-Item $src $coreclr.DirectoryName -Force
Write-Host "Copied $($names.cdaclite) into $($coreclr.DirectoryName)"
displayName: 'Copy cdac-lite into TestHost payload'
7 changes: 6 additions & 1 deletion eng/pipelines/runtime-diagnostics.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ parameters:
values:
- single-leg
- xplat
- name: cdacUseCdacLite
displayName: Use cdac-lite for dump generation (Windows only)
type: boolean
default: false
Comment on lines +68 to +71
- name: cdacStressPlatforms
displayName: cDAC Stress Test Platforms
type: object
Expand Down Expand Up @@ -261,8 +265,9 @@ extends:
platforms: ${{ parameters.cdacDumpPlatforms }}
prepareParameters:
cdacTestConfig: Debug
useCdacLite: ${{ parameters.cdacUseCdacLite }}
sendDisplayName: 'Send cDAC Dump Tests to Helix'
sendParams: $(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/cdac-dump-helix.proj /t:Test /p:TargetOS=$(osGroup) /p:TargetArchitecture=$(archType) /p:HelixTargetQueues="$(CdacHelixQueue)" /p:TestHostPayload=$(TestHostPayloadDir) /p:DumpTestsPayload=$(Build.SourcesDirectory)/artifacts/helixPayload/cdac /bl:$(Build.SourcesDirectory)/artifacts/log/SendToHelix.binlog
sendParams: $(Build.SourcesDirectory)/src/native/managed/cdac/tests/DumpTests/cdac-dump-helix.proj /t:Test /p:TargetOS=$(osGroup) /p:TargetArchitecture=$(archType) /p:HelixTargetQueues="$(CdacHelixQueue)" /p:TestHostPayload=$(TestHostPayloadDir) /p:DumpTestsPayload=$(Build.SourcesDirectory)/artifacts/helixPayload/cdac /p:UseCdacLite=${{ parameters.cdacUseCdacLite }} /bl:$(Build.SourcesDirectory)/artifacts/log/SendToHelix.binlog
failErrorMessage: 'One or more cDAC dump test failures were detected. Failing the job.'
publishDumpsArtifactName: CdacDumps_$(osGroup)_$(archType)

Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/debug/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ endif()
if(CLR_CMAKE_HOST_WIN32)
add_subdirectory(createdump)
endif(CLR_CMAKE_HOST_WIN32)
if(NOT CLR_CROSS_COMPONENTS_BUILD)
add_subdirectory(cdaclite)
endif(NOT CLR_CROSS_COMPONENTS_BUILD)
if(FEATURE_SINGLE_FILE_DIAGNOSTICS)
add_subdirectory(runtimeinfo)
endif(FEATURE_SINGLE_FILE_DIAGNOSTICS)
83 changes: 83 additions & 0 deletions src/coreclr/debug/cdaclite/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
set(CMAKE_INCLUDE_CURRENT_DIR ON)

include_directories(BEFORE ${VM_DIR})
include_directories(${CLR_DIR}/debug/datadescriptor-shared/inc)

add_subdirectory(json)
add_subdirectory(data)
add_subdirectory(contracts)

set(CDACLITE_SOURCES
cdaclite.cpp
datatarget.cpp
enumerate.cpp
)

if(CLR_CMAKE_HOST_WIN32)
add_definitions(-DFX_VER_INTERNALNAME_STR=cdaclite.dll)

# Embed the FX version resource so the shared framework manifest validation
# (sharedfx.targets) finds a FileVersion, matching mscordaccore/mscordbi.
list(APPEND CDACLITE_SOURCES Native.rc)

set(DEF_SOURCES cdaclite.src)
set(CURRENT_BINARY_DIR_FOR_CONFIG ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR})

# Preprocess exports definition file
preprocess_file(${CMAKE_CURRENT_SOURCE_DIR}/${DEF_SOURCES} ${CURRENT_BINARY_DIR_FOR_CONFIG}/cdaclite.def)
add_custom_target(cdaclite_def DEPENDS ${CURRENT_BINARY_DIR_FOR_CONFIG}/cdaclite.def)
else(CLR_CMAKE_HOST_WIN32)
set(DEF_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cdaclite_unixexports.src)
set(EXPORTS_FILE ${CMAKE_CURRENT_BINARY_DIR}/cdaclite.exports)
generate_exports_file(${DEF_SOURCES} ${EXPORTS_FILE})
add_custom_target(cdaclite_exports DEPENDS ${EXPORTS_FILE})
set_exports_linker_option(${EXPORTS_FILE})
endif(CLR_CMAKE_HOST_WIN32)

add_library_clr(cdaclite SHARED ${CDACLITE_SOURCES})

if(CLR_CMAKE_HOST_WIN32)
add_dependencies(cdaclite cdaclite_def)
set_property(TARGET cdaclite APPEND_STRING PROPERTY LINK_FLAGS " /DEF:\"${CURRENT_BINARY_DIR_FOR_CONFIG}/cdaclite.def\"")
else(CLR_CMAKE_HOST_WIN32)
add_dependencies(cdaclite cdaclite_exports)
set_property(TARGET cdaclite APPEND_STRING PROPERTY LINK_FLAGS ${EXPORTS_LINKER_OPTION})
set_property(TARGET cdaclite APPEND_STRING PROPERTY LINK_DEPENDS ${EXPORTS_FILE})
endif(CLR_CMAKE_HOST_WIN32)

set(CDACLITE_LIBRARIES
cdacgc
cdacdata
corguids
dbgutil
coreclrminipal
)

if(CLR_CMAKE_HOST_WIN32)
list(APPEND CDACLITE_LIBRARIES
kernel32.lib
advapi32.lib
${STATIC_MT_CRT_LIB}
)
else(CLR_CMAKE_HOST_WIN32)
list(APPEND CDACLITE_LIBRARIES
coreclrpal
)
endif(CLR_CMAKE_HOST_WIN32)

target_link_libraries(cdaclite PRIVATE ${CDACLITE_LIBRARIES})

install_clr(TARGETS cdaclite DESTINATIONS . sharedFramework COMPONENT debug)

# Standalone test harness (Windows-only): drives cdaclite against a live process.
if(CLR_CMAKE_HOST_WIN32)
add_executable_clr(cdaclitetest cdaclitetest.cpp)
target_link_libraries(cdaclitetest
PRIVATE
corguids
kernel32.lib
advapi32.lib
dbghelp.lib
${STATIC_MT_CRT_LIB}
)
endif(CLR_CMAKE_HOST_WIN32)
7 changes: 7 additions & 0 deletions src/coreclr/debug/cdaclite/Native.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#define FX_VER_FILEDESCRIPTION_STR ".NET Runtime cDAC Lite Memory Enumeration"

#include <fxver.h>
#include <fxver.rc>
165 changes: 165 additions & 0 deletions src/coreclr/debug/cdaclite/cdaclite.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//*****************************************************************************
// cdaclite.cpp
//
// Creation: the classic DAC factory entry point (CLRDataCreateInstance) and the
// CDacLite COM object lifetime (construction, IUnknown, logging). The data-target
// source lives in datatarget.{h,cpp} and the memory enumeration in enumerate.cpp.
//*****************************************************************************

#include "cdaclite.h"
#include "datatarget.h"

#include <dbgutil.h>
#include "corerror.h"
#include <palclr.h>
#include <crosscomp.h>
#include <xclrdata.h>

#include <stdio.h>
#include <memory>

// Implemented per-platform in dbgutil (dbgutil.cpp / elfreader.cpp / machoreader.cpp).
// Resolves an exported symbol in the target image using only the data target.
extern "C" bool TryGetSymbol(ICorDebugDataTarget* dataTarget, uint64_t baseAddress, const char* symbolName, uint64_t* symbolAddress);

namespace cdac
{
CDacLite::CDacLite(ICLRDataTarget* target, uint64_t contractDescriptorAddr, uint64_t clrBase)
: m_ref(1), m_target(target), m_contractDescriptorAddr(contractDescriptorAddr), m_clrBase(clrBase)
{
m_target->AddRef();
}

CDacLite::~CDacLite()
{
m_target->Release();
}

HRESULT STDMETHODCALLTYPE CDacLite::QueryInterface(REFIID riid, void** ppvObject)
{
if (ppvObject == nullptr)
{
return E_POINTER;
}
if (riid == IID_IUnknown || riid == __uuidof(ICLRDataEnumMemoryRegions))
{
*ppvObject = static_cast<ICLRDataEnumMemoryRegions*>(this);
AddRef();
return S_OK;
}
*ppvObject = nullptr;
return E_NOINTERFACE;
}

ULONG STDMETHODCALLTYPE CDacLite::AddRef()
{
return InterlockedIncrement(&m_ref);
}

ULONG STDMETHODCALLTYPE CDacLite::Release()
{
LONG ref = InterlockedDecrement(&m_ref);
if (ref == 0)
{
delete this;
}
return ref;
}

void CDacLite::Log(ICLRDataEnumMemoryRegionsCallback* callback, const char* format, ...)
{
char buffer[1024];
va_list args;
va_start(args, format);
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
buffer[sizeof(buffer) - 1] = '\0';

ICLRDataLoggingCallback* logger = nullptr;
if (callback != nullptr &&
SUCCEEDED(callback->QueryInterface(__uuidof(ICLRDataLoggingCallback), (void**)&logger)) &&
logger != nullptr)
{
logger->LogMessage(buffer);
logger->Release();
}
else
{
fprintf(stderr, "cdaclite: %s\n", buffer);
#ifdef HOST_WINDOWS
OutputDebugStringA("cdaclite: ");
OutputDebugStringA(buffer);
OutputDebugStringA("\n");
#endif
}
}
}

//
// The classic DAC factory entry point. dbghelp (Windows) and createdump
// (Unix/macOS) call this to obtain an ICLRDataEnumMemoryRegions for a target.
//
// On Unix the symbol needs explicit default visibility so it survives
// -fvisibility=hidden and is exported for createdump's dlsym; the version script
// alone cannot un-hide a hidden symbol. On Windows the export comes from
// cdaclite.def, so no attribute is needed. Matches the DAC's CLRDataCreateInstance.
//
#ifdef HOST_WINDOWS
#define CDACLITE_EXPORT
#else
#define CDACLITE_EXPORT __attribute__((visibility("default")))
#endif

STDAPI CDACLITE_EXPORT CLRDataCreateInstance(REFIID iid, ICLRDataTarget* pLegacyTarget, void** iface)
{
if (pLegacyTarget == nullptr || iface == nullptr)
{
return E_INVALIDARG;
}

*iface = nullptr;

// Determine the runtime module base, preferring ICLRRuntimeLocator and
// falling back to the well-known CLR module name.
CLRDATA_ADDRESS base = 0;
ICLRRuntimeLocator* locator = nullptr;
if (SUCCEEDED(pLegacyTarget->QueryInterface(__uuidof(ICLRRuntimeLocator), (void**)&locator)) &&
locator != nullptr &&
locator->GetRuntimeBase(&base) == S_OK)
{
locator->Release();
}
else
{
if (locator != nullptr)
{
locator->Release();
}
HRESULT hr = pLegacyTarget->GetImageBase(TARGET_MAIN_CLR_DLL_NAME_W, &base);
if (FAILED(hr))
{
return hr;
}
}

// Locate the exported contract descriptor in the target.
cdac::DataTargetAdapter adapter(pLegacyTarget);
uint64_t contractDescriptorAddr = 0;
if (!TryGetSymbol(&adapter, (uint64_t)base, "DotNetRuntimeContractDescriptor", &contractDescriptorAddr) ||
contractDescriptorAddr == 0)
{
return E_FAIL;
}

cdac::CDacLite* instance = new (std::nothrow) cdac::CDacLite(pLegacyTarget, contractDescriptorAddr, (uint64_t)base);
if (instance == nullptr)
{
return E_OUTOFMEMORY;
}

HRESULT hr = instance->QueryInterface(iid, iface);
instance->Release();
return hr;
}
67 changes: 67 additions & 0 deletions src/coreclr/debug/cdaclite/cdaclite.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//*****************************************************************************
// cdaclite.h
//
// cdac-lite: a minimal data access component implementing just enough of the
// classic DAC entry point (CLRDataCreateInstance + ICLRDataEnumMemoryRegions)
// to drive crash-dump memory enumeration without a version-matched mscordaccore.
//
// The implementation is split by concern:
// * datatarget.{h,cpp} -- the data-target source (memory reads / symbol lookup)
// * cdaclite.cpp -- creation (CLRDataCreateInstance) + COM object lifetime
// * enumerate.cpp -- the memory enumeration (EnumMemoryRegions + contracts)
//*****************************************************************************

#ifndef CDACLITE_CDACLITE_H
#define CDACLITE_CDACLITE_H

#include <windows.h>
#include <clrdata.h>
#include <stdint.h>
#include <stdarg.h>

namespace cdac
{
// Minimal ICLRDataEnumMemoryRegions implementation backed by the runtime's
// contract descriptor. Created by CLRDataCreateInstance (cdaclite.cpp); the
// enumeration itself lives in enumerate.cpp.
class CDacLite : public ICLRDataEnumMemoryRegions
{
public:
CDacLite(ICLRDataTarget* target, uint64_t contractDescriptorAddr, uint64_t clrBase);

// IUnknown
STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) override;
STDMETHOD_(ULONG, AddRef)() override;
STDMETHOD_(ULONG, Release)() override;

// ICLRDataEnumMemoryRegions (implemented in enumerate.cpp)
STDMETHOD(EnumMemoryRegions)(ICLRDataEnumMemoryRegionsCallback* callback, ULONG32 miniDumpFlags, CLRDataEnumMemoryFlags clrFlags) override;

private:
virtual ~CDacLite();

// Logs to the callback's ICLRDataLoggingCallback if present, else to stderr.
void Log(ICLRDataEnumMemoryRegionsCallback* callback, const char* format, ...);

// State shared with the region sinks while an enumeration is in progress.
struct RegionSinkState
{
CDacLite* owner;
ICLRDataEnumMemoryRegionsCallback* callback;
uint32_t count;
};

// Region sink (contract-reported regions) + EnumMem sink (implicitly-read structs).
static void RegionSinkThunk(void* context, const char* kind, uint64_t start, uint64_t size);
static void EnumMemThunk(void* context, uint64_t address, uint32_t size);

LONG m_ref;
ICLRDataTarget* m_target;
uint64_t m_contractDescriptorAddr;
uint64_t m_clrBase;
};
}

#endif // CDACLITE_CDACLITE_H
5 changes: 5 additions & 0 deletions src/coreclr/debug/cdaclite/cdaclite.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
; Licensed to the .NET Foundation under one or more agreements.
; The .NET Foundation licenses this file to you under the MIT license.

EXPORTS
CLRDataCreateInstance
Loading
Loading