Convert IDacDbiInterface to COM interface#125074
Convert IDacDbiInterface to COM interface#125074max-charlamb wants to merge 1 commit intodotnet:mainfrom
Conversation
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
There was a problem hiding this comment.
Pull request overview
This PR converts IDacDbiInterface (DAC↔DBI contract) from a plain C++ abstract class to a COM-style interface (IUnknown + GUID), with the goal of enabling a future managed (C#) cDAC implementation and standard COM lifetime management.
Changes:
- Make
IDacDbiInterfacea COM interface (MIDL_INTERFACE,IUnknownbase) and remove the customDestroy()lifecycle method in favor ofRelease(). - Implement
QueryInterface/AddRef/ReleaseonDacDbiInterfaceImpland update DBI call sites for the new pointer-based out params. - Add
dacdbi.idldescribing the intended COM contract (including callback interfaces).
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/debug/inc/dacdbiinterface.h | Converts IDacDbiInterface to MIDL_INTERFACE + IUnknown, removes Destroy(), adjusts several signatures for COM compatibility. |
| src/coreclr/debug/inc/dacdbi.idl | Adds an IDL definition for the full COM contract and callback interfaces. |
| src/coreclr/debug/di/rstype.cpp | Updates GetExactTypeHandle call to pass an out pointer. |
| src/coreclr/debug/di/rspriv.h | Updates forward-declaration to struct to match MIDL_INTERFACE expansion. |
| src/coreclr/debug/di/process.cpp | Updates metadata query call sites to use pointer out-params; switches DAC teardown from Destroy() to Release(). |
| src/coreclr/debug/di/module.cpp | Updates metadata query call sites to use pointer out-params. |
| src/coreclr/debug/di/divalue.cpp | Updates exception stack frame query to pass DacDbiArrayList by pointer. |
| src/coreclr/debug/daccess/dacdbiimpl.h | Declares IUnknown methods and updates signatures to pointer out-params. |
| src/coreclr/debug/daccess/dacdbiimpl.cpp | Implements IUnknown methods; updates implementations for pointer out-params. |
Comments suppressed due to low confidence (2)
src/coreclr/debug/daccess/dacdbiimpl.cpp:1201
- GetMetaDataFileInfoFromPEFile now takes pTimeStamp/pImageSize as pointers, but the implementation unconditionally dereferences them on the success path and also leaves them uninitialized when vmPEAssembly resolves to NULL. For COM-style APIs (and consistent with other methods in this file that return E_POINTER), please validate pTimeStamp/pImageSize (and other required out params like pResult) up-front, return E_POINTER when null, and ensure out values are always initialized on all return paths.
HRESULT DacDbiInterfaceImpl::GetMetaDataFileInfoFromPEFile(VMPTR_PEAssembly vmPEAssembly, DWORD * pTimeStamp, DWORD * pImageSize, IStringHolder* pStrFilename, OUT bool * pResult)
{
DD_ENTER_MAY_THROW;
HRESULT hr = S_OK;
EX_TRY
{
DWORD dwDataSize;
DWORD dwRvaHint;
PEAssembly * pPEAssembly = vmPEAssembly.GetDacPtr();
_ASSERTE(pPEAssembly != NULL);
if (pPEAssembly == NULL)
{
*pResult = false;
}
src/coreclr/debug/daccess/dacdbiimpl.cpp:3756
- GetStackFramesFromException now takes the DacDbiArrayList as a pointer, but the method doesn’t validate pDacStackFrames before dereferencing it (Alloc / operator[]). Please add an E_POINTER check at the top of the method (and consider initializing it to an empty list on success paths where there are 0 frames).
HRESULT DacDbiInterfaceImpl::GetStackFramesFromException(VMPTR_Object vmObject, DacDbiArrayList<DacExceptionCallStackData>* pDacStackFrames)
{
DD_ENTER_MAY_THROW;
HRESULT hr = S_OK;
EX_TRY
{
PTR_Object objPtr = vmObject.GetDacPtr();
#ifdef _DEBUG
3d9e111 to
f3b3cb8
Compare
f3b3cb8 to
1c619d9
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
src/coreclr/debug/daccess/dacdbiimpl.cpp:1190
- GetMetaDataFileInfoFromPEFile now takes multiple pointer parameters (pTimeStamp, pImageSize, pStrFilename, pResult) but does not validate them before dereferencing later in the method. This was previously safe with reference parameters; now a null argument will crash. Add E_POINTER checks (and consider setting outputs to safe defaults on failure paths).
HRESULT DacDbiInterfaceImpl::GetMetaDataFileInfoFromPEFile(VMPTR_PEAssembly vmPEAssembly, DWORD * pTimeStamp, DWORD * pImageSize, IStringHolder* pStrFilename, OUT BOOL * pResult)
{
DD_ENTER_MAY_THROW;
HRESULT hr = S_OK;
1c619d9 to
3f45dad
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
src/coreclr/debug/daccess/dacdbiimpl.cpp:1219
- GetMetaDataFileInfoFromPEFile returns S_OK even when
vmPEAssembly.GetDacPtr()is null, but in that path it only sets*pResult = FALSEand leaves*pTimeStamp,*pImageSize, andpStrFilenameunchanged. Because these are out parameters, callers could observe stale values if they reuse variables/holders. Recommend explicitly zeroing timestamp/size and clearing the filename holder on the failure path.
HRESULT DacDbiInterfaceImpl::GetMetaDataFileInfoFromPEFile(VMPTR_PEAssembly vmPEAssembly, DWORD * pTimeStamp, DWORD * pImageSize, IStringHolder* pStrFilename, OUT BOOL * pResult)
{
if (pTimeStamp == NULL || pImageSize == NULL || pStrFilename == NULL || pResult == NULL)
return E_POINTER;
DD_ENTER_MAY_THROW;
HRESULT hr = S_OK;
EX_TRY
{
DWORD dwDataSize;
DWORD dwRvaHint;
PEAssembly * pPEAssembly = vmPEAssembly.GetDacPtr();
_ASSERTE(pPEAssembly != NULL);
if (pPEAssembly == NULL)
{
*pResult = FALSE;
}
else
{
WCHAR wszFilePath[MAX_LONGPATH] = {0};
DWORD cchFilePath = MAX_LONGPATH;
bool ret = ClrDataAccess::GetMetaDataFileInfoFromPEFile(pPEAssembly,
*pTimeStamp,
*pImageSize,
dwDataSize,
dwRvaHint,
wszFilePath,
cchFilePath);
pStrFilename->AssignCopy(wszFilePath);
*pResult = ret;
}
| STDMETHODIMP | ||
| DacDbiInterfaceImpl::QueryInterface(THIS_ IN REFIID interfaceId, OUT PVOID* iface) | ||
| { | ||
| HRESULT hr = S_OK; | ||
| EX_TRY | ||
| if (IsEqualIID(interfaceId, __uuidof(IDacDbiInterface))) | ||
| { | ||
| m_pAllocator = NULL; | ||
|
|
||
| this->Release(); | ||
| // Memory is deleted, don't access this object any more | ||
| AddRef(); | ||
| *iface = static_cast<IDacDbiInterface*>(this); | ||
| return S_OK; | ||
| } | ||
| EX_CATCH_HRESULT(hr); | ||
| return hr; | ||
| return ClrDataAccess::QueryInterface(interfaceId, iface); |
There was a problem hiding this comment.
DacDbiInterfaceImpl::QueryInterface dereferences iface without a null check. If a caller passes a null out-pointer (or uses it incorrectly), this will AV before you can return an HRESULT. Consider matching COM expectations here by returning E_POINTER when iface is null and (optionally) setting *iface = nullptr before any IID checks.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs
Outdated
Show resolved
Hide resolved
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IDacDbiInterface.cs
Outdated
Show resolved
Hide resolved
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs
Outdated
Show resolved
Hide resolved
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/DacDbiImpl.cs
Outdated
Show resolved
Hide resolved
55e1ca0 to
cfaad28
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 33 out of 33 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (1)
src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs:1
DacDbiImpl.EnumerateThreadsexplicitly filters out threads inDeadorUnstartedstate (“Match native: skip dead and unstarted threads”). Comparing the callback count toThreadStoreData.ThreadCountis likely incorrect ifThreadCountincludes those threads. To avoid brittle/flaky validation, compute the expected count by walking the thread list and applying the same state filter (or use a contract API that enumerates with matching semantics, if available).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs
Show resolved
Hide resolved
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs
Outdated
Show resolved
Hide resolved
| DacDbiInterfaceImpl::QueryInterface(THIS_ IN REFIID interfaceId, OUT PVOID* iface) | ||
| { | ||
| HRESULT hr = S_OK; | ||
| EX_TRY | ||
| if (IsEqualIID(interfaceId, __uuidof(IDacDbiInterface))) | ||
| { | ||
| m_pAllocator = NULL; | ||
|
|
||
| this->Release(); | ||
| // Memory is deleted, don't access this object any more | ||
| AddRef(); | ||
| *iface = static_cast<IDacDbiInterface*>(this); | ||
| return S_OK; | ||
| } | ||
| EX_CATCH_HRESULT(hr); | ||
| return hr; | ||
| return ClrDataAccess::QueryInterface(interfaceId, iface); | ||
| } |
| bool _isDead; | ||
| BOOL _isDead; | ||
| IfFailThrow(GetProcess()->GetDAC()->IsThreadMarkedDead(m_vmThreadToken, &_isDead)); | ||
| return _isDead; |
2ee7c87 to
693593c
Compare
ec7f213 to
9bc05d7
Compare
9bc05d7 to
874452f
Compare
874452f to
6e98da8
Compare
6e98da8 to
c150f00
Compare
| STDMETHODIMP | ||
| DacDbiInterfaceImpl::QueryInterface(THIS_ IN REFIID interfaceId, OUT PVOID* iface) | ||
| { | ||
| HRESULT hr = S_OK; | ||
| EX_TRY | ||
| if (IsEqualIID(interfaceId, __uuidof(IDacDbiInterface))) | ||
| { | ||
| m_pAllocator = NULL; | ||
|
|
||
| this->Release(); | ||
| // Memory is deleted, don't access this object any more | ||
| AddRef(); | ||
| *iface = static_cast<IDacDbiInterface*>(this); | ||
| return S_OK; | ||
| } | ||
| EX_CATCH_HRESULT(hr); | ||
| return hr; | ||
| return ClrDataAccess::QueryInterface(interfaceId, iface); | ||
| } |
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs
Show resolved
Hide resolved
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs
Show resolved
Hide resolved
c150f00 to
4a6ec2b
Compare
4a6ec2b to
b0b4646
Compare
| STDMETHODIMP | ||
| DacDbiInterfaceImpl::QueryInterface(THIS_ IN REFIID interfaceId, OUT PVOID* iface) | ||
| { | ||
| HRESULT hr = S_OK; | ||
| EX_TRY | ||
| if (IsEqualIID(interfaceId, __uuidof(IDacDbiInterface))) | ||
| { | ||
| m_pAllocator = NULL; | ||
|
|
||
| this->Release(); | ||
| // Memory is deleted, don't access this object any more | ||
| AddRef(); | ||
| *iface = static_cast<IDacDbiInterface*>(this); | ||
| return S_OK; | ||
| } | ||
| EX_CATCH_HRESULT(hr); | ||
| return hr; | ||
| return ClrDataAccess::QueryInterface(interfaceId, iface); | ||
| } |
There was a problem hiding this comment.
QueryInterface dereferences iface without null-checking. If a caller passes nullptr (legal to detect and return E_POINTER for COM APIs), this will AV. Fix by returning E_POINTER when iface == nullptr, and (per COM convention) initializing *iface = nullptr before any IID checks/delegation.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs
Show resolved
Hide resolved
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs
Show resolved
Hide resolved
| //----------------------------------------------------------------------------- | ||
| class IAllocator | ||
| { | ||
| class IAllocator : public IUnknown { |
There was a problem hiding this comment.
The extra spacing before { is inconsistent with surrounding formatting in this header and makes future diffs noisier. Consider normalizing these declarations to the file’s prevailing brace/spacing style.
| //----------------------------------------------------------------------------- | ||
| class IMetaDataLookup | ||
| { | ||
| class IMetaDataLookup : public IUnknown { |
There was a problem hiding this comment.
The extra spacing before { is inconsistent with surrounding formatting in this header and makes future diffs noisier. Consider normalizing these declarations to the file’s prevailing brace/spacing style.
b0b4646 to
7e5576d
Compare
Convert the native IDacDbiInterface from a C++ abstract class to a proper COM interface defined in IDL. This includes: - Add dacdbi.idl with full COM interface definition (159 methods) - Update dacdbiinterface.h to use MIDL_INTERFACE with proper GUID - Convert all C++ reference parameters to pointers for COM compatibility - Update all callers in the debugger DI layer (process.cpp, rsthread.cpp, rstype.cpp, module.cpp, divalue.cpp) - Add managed IDacDbiInterface.cs with [GeneratedComInterface] - Add prebuilt GUID definitions for cross-platform builds - Use Interop.BOOL for all BOOL parameters matching native IDL Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
7e5576d to
3a0737e
Compare
| PEAssembly * pPEAssembly = vmPEAssembly.GetDacPtr(); | ||
| _ASSERTE(pPEAssembly != NULL); | ||
| if (pPEAssembly == NULL) | ||
| { | ||
| *pResult = false; | ||
| *pResult = FALSE; | ||
| } | ||
| else |
There was a problem hiding this comment.
In GetMetaDataFileInfoFromPEFile, the pPEAssembly == NULL path returns S_OK with *pResult = FALSE but leaves *pTimeStamp, *pImageSize, and the string holder potentially untouched. Please initialize these outputs to deterministic defaults (e.g., 0 / empty string) so callers don’t observe stale/uninitialized values when the lookup fails early.
Summary
Converts
IDacDbiInterfacefrom a plain C++ abstract class into a proper COM interface, enabling future implementation in managed C# via the cDAC. This follows the same pattern used byISOSDacInterface→SOSDacImpl.Motivation
This is Phase 1 of enabling the cDAC (contract-based Data Access Component) to implement
IDacDbiInterfacein managed C#. The prior PR #124836 converted all methods to return HRESULT; this PR completes the COM conversion by makingIDacDbiInterfacea standard COM interface that can be consumed via[GeneratedComInterface]in C#.Types of Changes
Each change below was required to convert from a C++ abstract class to a valid COM interface:
1. IUnknown Inheritance
IDacDbiInterfacenow inherits fromIUnknownviaMIDL_INTERFACE. This gives the interface a stable GUID identity, enablingQueryInterfacediscovery and standard COM ref-counting. Required because COM interfaces must derive fromIUnknown— it's the root of all COM.2. Lifecycle: Destroy() → Release()
Removed the custom
Destroy()method. COM objects manage their lifetime throughAddRef()/Release()ref-counting — when the last reference is released, the destructor runs automatically. Having a separateDestroy()would conflict with this model.3. QueryInterface/AddRef/Release Implementation
DacDbiInterfaceImplinheritsIUnknownfrom two paths (viaClrDataAccessand viaIDacDbiInterface), creating diamond inheritance. The explicit QI/AddRef/Release implementations resolve this by delegating toClrDataAccesswhile also handling the newIDacDbiInterfaceIID.4. STDMETHODCALLTYPE Calling Convention
All interface methods now use
STDMETHODCALLTYPE(__stdcallon Windows) to match the COM binary standard. This follows the same pattern used byClrDataAccessin dacimpl.h. Required because COM defines a fixed calling convention for vtable dispatch.5. bool → BOOL
All
boolparameters converted toBOOL(a 32-bitint). C++boolis 1 byte with platform-dependent behavior, which can't be reliably marshaled across COM boundaries.BOOLis the standard COM boolean type with well-defined size and marshaling.6. References → Pointers
C++ reference parameters (
T&) converted to pointers (T*) withE_POINTERnull checks. References are a C++ language feature with no representation in IDL or other languages. COM uses pointers for all indirection, and the null checks follow COM convention for validating output parameters.7. IAllocator & IMetaDataLookup → IUnknown
These inner callback interfaces now inherit from
IUnknownwith their own GUIDs. They're implemented by heap-allocatedCordbProcessand passed across the DAC/DBI boundary, so they need proper COM identity.CordbProcess::QueryInterfaceupdated to expose them.8. IStringHolder — NOT Converted
IStringHolderintentionally remains a plain C++ abstract class. It's implemented by stack-allocatedStringCopyHolderobjects, which violates COM's ref-counting contract (you can'tdelete thison a stack object). It's represented as an opaquevoid*in the IDL.9. Forward Declaration: class → struct
MIDL_INTERFACE(...)expands tostruct __declspec(uuid(...)), so forward declarations updated fromclass IDacDbiInterfacetostruct IDacDbiInterfaceto avoid type mismatch.10. Overload Rename: GetReJitInfoByAddress
COM interfaces cannot have overloaded methods (IDL doesn't support them, and vtable slot ordering would be ambiguous). The second
GetReJitInfooverload renamed toGetReJitInfoByAddressto give each method a unique name.11. Caller-Side Updates
All call sites updated to pass
&variablewhere references became pointers, and to useBOOL/TRUE/FALSEwhereboolchanged.12. New Files
[local]since these interfaces are in-process only (no cross-process marshaling).[GeneratedComInterface]and all supporting struct definitions.Testing