Skip to content

Conversation

rcj1
Copy link
Contributor

@rcj1 rcj1 commented Sep 25, 2025

  • Adding GetComWrappersCCWData, IsComWrappersCCW, and IsComWrappersRCW cDAC APIs
  • Fixing DAC bug on ARM. Previously DACGetComWrappersCCWVTableQIAddress had cleared the thumb code on the qiAddress, only for it to be compared with an entrypoint with the thumb code. I just removed the clearing of the thumb code.
  • Renaming GetPrimitiveType to GetBinderType to be used more broadly to get method tables stored in the CoreLibBinder

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request implements cDAC (Contract-based Data Access Component) APIs for simple ComWrappers functionality. The main purpose is to add three new cDAC APIs: GetComWrappersCCWData, IsComWrappersCCW, and IsComWrappersRCW, while also fixing a DAC bug on ARM architecture and renaming GetPrimitiveType to GetBinderType for broader use.

Key changes include:

  • Implementation of three new ComWrappers cDAC APIs with full functionality
  • ARM DAC bug fix by removing incorrect thumb code clearing
  • Renaming and broadening the scope of type lookup functionality

Reviewed Changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated no comments.

Show a summary per file
File Description
SOSDacImpl.cs Implements the three new ComWrappers cDAC APIs and updates method name usage
ManagedObjectWrapperLayout.cs New data contract for managed object wrapper reference count
ManagedObjectWrapperHolderObject.cs New data contract for managed object wrapper holder
SignatureTypeProvider.cs Updates method call to use new GetBinderType name
RuntimeTypeSystem_1.cs Renames GetPrimitiveType to GetBinderType for broader usage
ComWrappers_1.cs Implements core ComWrappers contract functionality
Constants.cs Adds global constants for ComWrappers functionality
DataType.cs Adds new data types for managed object wrapper contracts
IRuntimeTypeSystem.cs Updates interface to use GetBinderType instead of GetPrimitiveType
IComWrappers.cs Adds new method signatures to ComWrappers interface
interoplibinterface_comwrappers.h Adds cdac_data template specialization for ManagedObjectWrapperHolderObject
datadescriptor.inc Adds new data type definitions and global variables
datadescriptor.h Includes ComWrappers header for feature support
appdomain.cpp Caches NativeObjectWrapper method table for cDAC usage
interoplibabi.h Adds cdac_data template specialization for ManagedObjectWrapperLayout
comwrappers.hpp Adds forward declarations for QueryInterface methods
request.cpp Fixes ARM DAC bug by removing thumb code clearing
SignatureDecoder.md Updates documentation to reflect GetBinderType rename
ComWrappers.md Updates documentation with new API definitions and implementation details

Copy link
Contributor

Tagging subscribers to this area: @steveisok, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.


// cache the method table here for cDAC IsComWrappersRCW
#ifdef FEATURE_COMWRAPPERS
CoreLibBinder::GetClass(CLASS__NATIVE_OBJECT_WRAPPER);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would it take to avoid this? Can we check for the type by name instead?

ComWrappers are only used by a fraction of applications. It would be unfortunate to bring them ComWrappers in during startup of every apps.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on how much input validation we want to do for DumpRCW in SOS.

If we want to do the minimum validation (only validate which type of RCW to try to dump: built-in COM vs ComWrappers), then we can drop this type check.

I tried to make the checks more in-depth to prevent giving out bad info if an invalid pointer is passed to DumpRCW, but we can make this more lax if desired.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkotas in the DAC we do look for the type by name, which boils down into FindClassModuleThrowing.
Basically, it iterates through all of the assembly's modules until a name match is found in a module's AvailableClassHashTable.

Figured this wouldn't be worth the inefficiency and code complexity when we can just store it in the array that's already in CoreLibBinder.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkoritzinsky IsComWrappersRCW is directly exposed to the user via DumpRCW, who could then input any value, so I believe we should have the validation.

Copy link
Member

@jkotas jkotas Sep 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkotas in the DAC we do look for the type by name, which boils down into FindClassModuleThrowing.
Basically, it iterates through all of the assembly's modules until a name match is found in a module's AvailableClassHashTable.

Checking whether the type is of a specific name and getting a type of a specific name are very common operations needed by higher-level debugger functionality. It is going to important for them to be fast. We do not want to make them fast by hardcoding every type that may be interesting for higher-level debugger functionality.

Here is an example from diagnostic repo where higher level debugger functionality checks type name: https://github.com/dotnet/diagnostics/blob/46eddddffcb272342504d143b46cba130783e75b/src/Microsoft.Diagnostics.ExtensionCommands/DumpAsyncCommand.cs#L884-L894 . There is a lot of code like that in the closed source Visual Studio debugger.

Historically, COM interop has been intertwined with the core runtime, and it is why a lot of it is in the legacy DAC APIs. We have been pushing COM interop out of core runtime where possible. We should keep doing the same here.

I think we should:

  • Do not force initialization of NativeObjectWrapper during runtime startup just to make cDAC work. Startup is our number one perf problem. We should not be making it worse. (It is death by thousand paper cuts problem. As you can see, there are many types initialized already for various reasons to do anything. I would rather see us working on deleting from the list of types that must be initialized.)
  • Keep CoreLibBinder in cDac as is, just for core primitive types
  • Use name-based checks and lookups for NativeObjectWrapper
  • Open an issue to look into perf of make name-based checks and lookups if we do not have one already

Comment on lines 4349 to 4354

#ifdef TARGET_ARM
// clear the THUMB bit on qiAddress before comparing with known vtable entry
*qiAddress &= ~THUMB_CODE;
#endif

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we handle this outside the function?
In theory this would cause problems with the other usage of DACGetComWrappersCCWVTableQIAddress in ClrDataAccess::DumpStowedExceptionObject. While it shouldn't matter because we don't build Windows Arm32 the other code path won't be exercised, I still think it is better practice as that would be an annoying bug to track down if we ever do run into that path.

Additionally, my understanding is that we should (but don't always) use PCODE to represent a value with masking bits rather than a TADDR.

// A PCODE is a valid PC/IP value -- a pointer to an instruction, possibly including some processor mode bits.
// (On ARM, for example, a PCODE value should have the low-order THUMB_CODE bit set if the code should
// be executed in that mode.)

Copy link
Contributor Author

@rcj1 rcj1 Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, we can keep this code then and replace GetEEFuncEntryPoint with GFN_TADDR as this does not set the thumb code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move the changes in this file into interoplibabi.h

Comment on lines +25 to +27
#ifdef FEATURE_COMWRAPPERS
#include "../interop/comwrappers.hpp"
#endif // FEATURE_COMWRAPPERS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be removed once the definitions in comwrappers.cpp are moved to interoplibabi.h.

Suggested change
#ifdef FEATURE_COMWRAPPERS
#include "../interop/comwrappers.hpp"
#endif // FEATURE_COMWRAPPERS

Comment on lines +190 to +199
struct ComWrappersVtablePtrs
{
decltype(&ManagedObjectWrapper_QueryInterface) MowQueryInterface;
decltype(&TrackerTarget_QueryInterface) TtQueryInterface;
};

const ComWrappersVtablePtrs g_ComWrappersVtablePtrs = {
&ManagedObjectWrapper_QueryInterface,
&TrackerTarget_QueryInterface
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we instead do this as an array of interesting function pointers (and initialize the value in comwrappers.cpp)?

That way we don't need to hard-code these names across the interoplib/VM/DAC boundary.

Something like the following:

// in interoplibabi.h
namespace InteropLib::ABI
{
    using QueryInterfaceMethod = HRESULT(STDMETHODCALLTYPE *)(_In_ ComInterfaceDispatch* disp, REFIID riid, _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject);
    constexpr size_t KnownQueryInterfaceImplementationsCount = 2;

    extern QueryInterfaceMethod KnownQueryInterfaceImplementations[KnownQueryInterfaceImplementationsCount];
}

// in comwrappers.cpp

QueryInterfaceMethod InteropLib::ABI:KnownQueryInterfaceImplementations[InteropLib::ABI::KnownQueryInterfaceImplementationsCount] = {
    &ManagedObjectWrapper_QueryInterface,
    &TrackerTarget_QueryInterface
}

Then (if the DAC is moved to use this table like the CDAC), we could even move these methods back into the anonymous namespace and not expose the symbols (which we'd prefer if possible).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do this, the downside that I see is it would be slightly less readable.. Instead of GFUNC(ManagedObjectWrapper_QueryInterface) we would be reading g_interestingPtrs[0]. If you are ok with this then I'd be happy to work on this and just add some comments about which pointer is where.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with that (as long as the same is something like the name I gave above).

Comment on lines +947 to +950
CDAC_TYPE_BEGIN(ComWrappersVtablePtrs)
CDAC_TYPE_FIELD(ComWrappersVtablePtrs, /*pointer*/, MowQueryInterface, offsetof(ComWrappersVtablePtrs, MowQueryInterface))
CDAC_TYPE_FIELD(ComWrappersVtablePtrs, /*pointer*/, TtQueryInterface, offsetof(ComWrappersVtablePtrs, TtQueryInterface))
CDAC_TYPE_END(ComWrappersVtablePtrs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could instead structure this as a well-known size struct instead of with individual members (using the idea in the comment in comwrappers.hpp):

Suggested change
CDAC_TYPE_BEGIN(ComWrappersVtablePtrs)
CDAC_TYPE_FIELD(ComWrappersVtablePtrs, /*pointer*/, MowQueryInterface, offsetof(ComWrappersVtablePtrs, MowQueryInterface))
CDAC_TYPE_FIELD(ComWrappersVtablePtrs, /*pointer*/, TtQueryInterface, offsetof(ComWrappersVtablePtrs, TtQueryInterface))
CDAC_TYPE_END(ComWrappersVtablePtrs)
CDAC_TYPE_BEGIN(ComWrappersVtablePtrs)
CDAC_TYPE_SIZE(sizeof(InteropLib::ABI:KnownQueryInterfaceImplementations))
CDAC_TYPE_END(ComWrappersVtablePtrs)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, slight decrease in readability

@AaronRobinsonMSFT AaronRobinsonMSFT added this to the 11.0.0 milestone Oct 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants