Description
This is a more distilled design proposal that solves issues raised in #5429. The idea is to add a new profiler API such as:
ICorProfilerInfo10::AddAssemblyPath(AppDomainID appDomainId, const WCHAR* pAssemblyPath)
which will add a path to list of assemblies that the runtime probes to resolve assembly references. This allows profilers to instrument managed code and include assembly references to a new assembly that was deployed by the profiler rather than being deployed as part of the application.
Background info to assist implementors
Probably the hardest part of the problem is creating a testable repro scenario (assuming you don't already have your own IL instrumenting profiler handy). There is an example instrumenting profiler here: https://github.com/Microsoft/clr-samples/tree/master/ProfilingAPI/ReJITEnterLeaveHooks that has most of what is necessary. However this example instruments using a CALLI instruction:
https://github.com/Microsoft/clr-samples/blob/master/ProfilingAPI/ReJITEnterLeaveHooks/ILRewriter.cpp#L746
To reproduce the problem here the profiler instead needs to:
- Include with the profiler a new managed assembly that has the implementation of some simple callback within it. Lets call this ManagedInstrumentation.dll.
public static class ManagedCallbackFunctions
{
public static void OnMethodEnter(int functionId)
{
Console.WriteLine("Function " + functionId + " was called");
}
}
- Use IMetaDataAssemblyEmit::DefineAssemblyRef to create an AssemblyRef token that refers to the ManagedInstrumentation.dll assembly.
https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/imetadataassemblyemit-defineassemblyref-method - Use IMetaDataEmit::DefineTypeRefByName and DefineMemberRef
https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/imetadataemit-definetyperefbyname-method
https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/imetadataemit-definememberref-method
to create a MemberRef token that refers to ManagedCallbackFunctions.OnMethodEnter - Change the instrumentation to use an IL CALL opcode with the ManagedCallbackFunctions.OnMethodEnter token
Running an application with this profiler should fail because ManagedInstrumentation.dll can't be located.
- To test the new API you will need to handle the AppDomainCreationFinished callback. In the handler QI for ICorProfilerInfo10 and then invoke AddTrustedAssemblyPath(domainId, path_to_ManagedInstrumentation.dll). Initially the QI will fail, but once the feature is implemented this call should succeed at modifying the assembly list. Later the compilation of instrumented methods should also succeed because the runtime will find ManagedInstrumentation.dll and load it.
To implement the feature:
- Add the new API to https://github.com/dotnet/coreclr/blob/master/src/inc/corprof.idl
- Add an implementation to
https://github.com/dotnet/coreclr/blob/master/src/vm/proftoeeinterfaceimpl.h
https://github.com/dotnet/coreclr/blob/master/src/vm/proftoeeinterfaceimpl.cpp
The implementation of the method needs to change the TPA list which is stored here:
https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/binder/inc/applicationcontext.inl#L74
Navigating to that datastructure should be something like:
BaseDomain* pDomain = (BaseDomain *) appDomainId;
pDomain->GetTPABinderContext()->GetAppContext()->GetTpaList();
More background docs about CLR and profiling:
https://github.com/dotnet/coreclr/tree/master/Documentation/botr
https://github.com/dotnet/coreclr/blob/master/Documentation/botr/profiling.md
https://github.com/dotnet/coreclr/blob/master/Documentation/botr/profilability.md
Where the TPA list normally gets set from without using a profiler:
https://github.com/dotnet/coreclr/blob/ef88a92215a8f90fe0bd8b0327c16bb889902105/src/vm/corhost.cpp#L820
Inside the pProfilerNames/Values there is a property named TRUSTED_PLATFORM_ASSEMBLIES with a list of assembly paths. The runtime host constructs this list however they want (dotnet.exe assembles it from some .json configuration files like app.deps.json) and then the runtime stores it.