runtime: Add Profiling-API callback for AssemblyBindFailure's

Here is my use-case:

  • I’m trying to write a Profiler for the CoreCLR (which is hooked into the process via Profiling API (CORECLR_ENABLE_PROFILING, …)
  • It’s intercepting ModuleLoad and JitCompilationStarted callbacks to modify Module Metadata on the fly and inject IL code into certain methods.
  • The injected IL instructions call a managed helper method, which is defined in a separate assembly (MyProfiler.dll, MyProfiler.MethodEnter())
  • In order to inject that IL instruction, I need to add a TypeRef/MemberRef for MyProfiler.MethodEnter() and an AssemblyRef to MyProfiler.dll as resolution scope. The AssemblyRef would point to MyProfiler with a certain version, hashcode, ect.
  • Since the AssemblyRef to MyProfiler.dll is injected at runtime, that assembly is not referenced by the application at build-time. So, there is nothing about it in [AssemblyName].deps

My question is: How can I make the CoreCLR resolve that AssemblyRef? I already figured, Assemblies are searched in the application directory and in the “Trusted Platform Assemblies”. If I understood that right, these are passed in by the corehost, and usually they come from the global nuget package cache.

So, I see three possible solutions for this: (1) add MyProfiler.dll to the nuget package cache on the machine (2) somehow add MyProfiler.dll to the TPA list (is that possible from within a profiler?) (3) Add a Profiling-API callback like AssemblyBindFailure and allow the profiler to specify a path to the dll. The CLR would then try to bind that assembly once more with the given path.

I’m not very much in favor of (1), because it would involve some “installer-step” for my profiler to get it to work. I thought (3) is a neat idea that would solve my problem elegantly. What do you think about it?

Or is there a much simpler solution to my problem?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 39 (15 by maintainers)

Most upvoted comments

@discostu105 @noahfalk it can hook il in premian ( add assemblyloadfrom(“”)) , there is sample https://github.com/caozhiyuan/clr-samples/blob/master/ProfilingAPI/ClrProfiler/CorProfiler.cpp#L396 , so now it’s no other deps

https://github.com/caozhiyuan/ClrProfiler.Trace/blob/master/src/ClrProfiler/CorProfiler.cpp#L483 this line only support IMAGE_CEE_CS_CALLCONV_HASTHIS , you can change code to support static method

@david-sackstein

If you are around I’m happy to meet up. Just let me know which days/times are good. My email is noahfalk at Microsoft.com if you want to coordinate there.

If you just want to chat a bit on a high level approach and leave it for someone else to build at a later time that’s fine. If you do decide you’d like to push further here are some resources to start learning more:

Docs: 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

Code: https://github.com/dotnet/coreclr/blob/master/src/vm/proftoeeinterfaceimpl.h - the implementation of the ICorProfilerInfo* interfaces. https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/binder/inc/applicationcontext.inl#L74 - where the TPA list is stored https://github.com/dotnet/coreclr/blob/00e30c5f2627e5683e030144eb1806b30c70dd1e/src/binder/clrprivbindercoreclr.cpp#L152 - example code path showing use of the TPA list to resolve an assembly In general code in the src/binder subdirectory is responsible for ‘binding’ - the process of resolving a reference to an assembly into a particular file on disk that should be loaded.

A good way to quickly get your hands dirty is to build coreclr and then run it under a native debugger with breakpoints set at some of the locations I mentioned. This will allow you step through and explore the code working in action. I myself haven’t spent much time in the binder portion of the runtime code so I’d have to shore up my knowledge as well if we forge ahead.

I would be willing to work with you on finding a reliable way for the runtime to provide support for this assembly loading scenario.

I’m happy to collaborate for whatever time you are willing to put in : ) On my side 2.1 issues are pressing so my most immediate priorities are elsewhere, but if you were interested in doing some design and/or implemention I’m sure I could carve out some time to provide design feedback and/or code review. I don’t know if that’s what you had in mind. If you just wanted to discuss that’s fine too.

The first step would be to put together a little spec for what would get built and how a profiler would use it. My initial thoughts would be adding a new profiler API that adds to the TPA list that was provided by the host to the runtime: https://github.com/dotnet/coreclr/blob/ef88a92215a8f90fe0bd8b0327c16bb889902105/src/vm/corhost.cpp#L820 (the tpa list is one of the properties) I think that list ultimately gets stored in the binder and then profiler could modify it there. The profiler would place its assemblies anywhere on disk it wants, then during startup invoke the API to add the paths to TPA. Later when assembly resolution occurred the profiler’s assembly would be found because it is on the TPA list. If we went ahead with that approach we’d need to flesh out the details of the API. I’m also open to entirely different suggestions about how to fill this gap.

cc @jkotas @swaroop-sridhar - just a heads up in case you see any conflicts between this and other plans in the binder space

Hi noahfalk, thanks for update.

For some profilers I believe the priority of this issue diminished as the 2.0 framework started including more of the traditional .NET SDK surface area by default.

I am not sure how this will help here?

I’d suspect hooking the Resolving event for AssemblyLoadContext.Default may also be a viable way to get dynamically injected dependencies to resolve.

I’m not sure if I understand correctly. Do you mean to do this in C# ? But this would require having my c# assembly already loaded, and we are going back to the problem with how to load an assembly.

Or do you mean to hook the Resolving event from C++ profiling code somehow?