Skip to content

mvenditto/ManagedCorProfiler

Repository files navigation

ManagedCorProfiler

A prototype CLR profiler written in C# for learning and fun.

Warning

🚧 WIP WIP WIP 🚧

Building blocks

Exposing a profiler callback

///
/// A sample profiler callback that prints the loaded modules
///
[ProfilerCallback("1E040027-162F-489B-B12F-F113E6AF40CF")]
internal unsafe class MyProfiler : CorProfilerCallback2
{
    private ICorProfilerInfo2* _corProfilerInfo;

    public override HRESULT Initialize(IUnknown* pICorProfilerInfoUnk)
    {
        // Query for a pointer to the ICorProfilerCallback2 interface
        var hr = pICorProfilerInfoUnk->QueryInterface(ICorProfilerInfo2.IID_Guid, out var pinfo);

        if (hr.Failed)
        {
            Console.WriteLine($"FAIL QueryInterface hr={hr}");
            return HRESULT.E_FAIL;
        }

        // Track our reference
        _corProfilerInfo = (ICorProfilerInfo2*)pinfo;
        _corProfilerInfo->AddRef();

        // Specify our profiler is interested in module load events (Module* callbacks)
        hr = _corProfilerInfo->SetEventMask((uint)COR_PRF_MONITOR.COR_PRF_MONITOR_MODULE_LOADS);

        if (hr.Failed)
        {
            Console.WriteLine($"FAIL SetEventMask hr={hr}");
            return HRESULT.E_FAIL;
        }

        return HRESULT.S_OK;
    }

    public override HRESULT ModuleLoadFinished(nuint moduleId, HRESULT hrStatus)
    {
        if (hrStatus.Failed)
        {
            return HRESULT.S_OK;
        }

        const int NameBufferLength = 256; // char

        // Allocate a buffer to hold the module name.
        using var szNameBuffer = NativeBuffer<char>.Alloc(NameBufferLength);

        // A pointer to a string of wide-characters to pass in input to GetModuleInfo.
        var szName = new PWSTR(szNameBuffer.Pointer);

        uint pcchName = 0;

        // Retrieve the file name of the module
        var hr = _corProfilerInfo->GetModuleInfo(
            moduleId,            // the target moduleId
            null,
            NameBufferLength,    // The length, in characters, of the szName return buffer
            &pcchName,           // A pointer to the total character length of the module's file name that is returned
            szName,              // A caller-provided wide character buffer
            null);

        if (hr.Failed)
        {
            Console.WriteLine($"FAIL GetModuleInfo hr={hr}");
            return hr;
        }

        var moduleName = szName.CopyToString(length: (int)pcchName);

        Console.WriteLine($"loaded module 0x{moduleId:x8} <{moduleName}>");

        return HRESULT.S_OK;
    }

    public override HRESULT Shutdown()
    {
        _corProfilerInfo->Release();
        return HRESULT.S_OK;
    }
}

Sample output

C:\ManagedCorProfiler\Samples\ModuleLoadsProfiler> .\run.cmd
[... OMITTED ...]
Loaded Module -> 0x7ffeac1b4000 C:\Users\dev\Source\Repos\runtime\artifacts\bin\coreclr\windows.x64.Debug\System.Private.CoreLib.dll
Loaded Module -> 0x7ffeac702148 C:\ManagedCorProfiler\Samples\SampleApp\bin\Debug\net8.0\SampleApp.dll
Loaded Module -> 0x7ffeac703e40 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\system.runtime.dll
Loaded Module -> 0x7ffeac8f9798 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\system.console.dll
Loaded Module -> 0x7ffeac8fc1f0 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\system.threading.dll
Loaded Module -> 0x7ffeac9317d0 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\system.text.encoding.extensions.dll
Loaded Module -> 0x7ffeac9389a0 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\system.runtime.interopservices.dll
Hello World!
C:\ManagedCorProfiler\Samples\ModuleLoadsProfiler> █

Compilation

Dumpbin of the profiler DLL

C:\ManagedCorProfiler\Samples\ModuleLoadsProfiler> dumpbin.exe /EXPORTS bin\Release\net8.0\publish\win-x64\ModuleLoadsProfiler.dll
[...OMITTED FOR BREVITY...]
    ordinal hint RVA      name
          1    0 00232660 DllCanUnloadNow = DllCanUnloadNow
          2    1 002323A0 DllGetClassObject = DllGetClassObject
          3    2 002326A0 DllMain = DllMain
[...OMITTED FOR BREVITY...]
C:\ManagedCorProfiler\Samples\ModuleLoadsProfiler> █

ELT Hooks

.

How to Build

.

Requirements

.

Contributing

Any contribution is welcome. I'm actually working on porting the tests at dotnet/runtime/tree/main/src/tests/profiler, this is a good place to start contributing.

Resources

Misc

COM / COM Interop

Profiling

Native AOT