Skip to content

Commit

Permalink
Update readme + small fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
kevingosse committed Dec 28, 2024
1 parent b7a6738 commit db21204
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 5 deletions.
86 changes: 86 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,88 @@
Silhouette - A library to build .NET profilers in .NET
=======================

# Quick start

Create a new C# NativeAOT project. Reference the Silhouette nuget package and add a class inheriting from `Silhouette.CorProfilerCallback11Base` (you can use a different version of `CorProfilerCallbackBase` depending on the version of .NET you're targeting). Override the `Initialize` method. It will be called with the highest version number of `ICorProfilerInfo` supported by the target runtime.

```csharp

using Silhouette;

internal partial class CorProfiler : CorProfilerCallback11Base
{
protected override HResult Initialize(int iCorProfilerInfoVersion)
{
if (iCorProfilerInfoVersion < 11)
{
return HResult.E_FAIL;
}

var result = ICorProfilerInfo11.SetEventMask(COR_PRF_MONITOR.COR_PRF_ENABLE_STACK_SNAPSHOT | COR_PRF_MONITOR.COR_PRF_MONITOR_THREADS);

return result;
}
}
```

You also need to expose a `DllGetClassObject` method that will be called by the .NET runtime when initializing the profiler. Use the built-in `ClassFactory` implementation and give it an instance of your `CorProfiler` class.

```csharp
using Silhouette;
using System.Runtime.InteropServices;

internal class DllMain
{
private static ClassFactory Instance;

[UnmanagedCallersOnly(EntryPoint = "DllGetClassObject")]
public static unsafe HResult DllGetClassObject(Guid* rclsid, Guid* riid, nint* ppv)
{
// Use your own profiler GUID here
if (*rclsid != new Guid("0A96F866-D763-4099-8E4E-ED1801BE9FBC"))
{
return HResult.E_NOINTERFACE;
}

Instance = new ClassFactory(new CorProfiler());
*ppv = Instance.IClassFactory;

return 0;
}
}
```

`CorProfilerXxBase` offers base virtual methods for all `ICorProfilerCallback` methods, so override the ones you're interested in:

```csharp
protected override HResult ThreadCreated(ThreadId threadId)
{
Console.WriteLine($"Thread created: {threadId.Value}");
return HResult.S_OK;
}
```

Use the `ICorProfilerInfoXx` fields to access the `ICorProfilerInfo` APIs:

```csharp
private unsafe string ResolveMethodName(nint ip)
{
try
{
var functionId = ICorProfilerInfo11.GetFunctionFromIP(ip).ThrowIfFailed();
var functionInfo = ICorProfilerInfo2.GetFunctionInfo(functionId).ThrowIfFailed();
using var metaDataImport = ICorProfilerInfo2.GetModuleMetaData(functionInfo.ModuleId, CorOpenFlags.ofRead, KnownGuids.IMetaDataImport).ThrowIfFailed().Wrap();
var methodProperties = metaDataImport.Value.GetMethodProps(new MdMethodDef(functionInfo.Token)).ThrowIfFailed();
var typeDefProps = metaDataImport.Value.GetTypeDefProps(methodProperties.Class).ThrowIfFailed();

return $"{typeDefProps.TypeName}.{methodProperties.Name}";
}
catch (Win32Exception)
{
return "<unknown>";
}
}
```

Most methods return an instance of `HResult<T>`. You can deconstruct it into a `(HResult error, T result)` and manually check the error code. You can also use the `ThrowIfFailed()` method that will return only the result and throw a `Win32Exception` if the error code is not `S_OK`.

8 changes: 7 additions & 1 deletion src/ManagedDotnetProfiler/CorProfiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,13 @@ private string GetTypeNameFromClassId(ClassId classId)

private string GetFunctionFullName(FunctionId functionId)
{
var functionInfo = ICorProfilerInfo2.GetFunctionInfo(functionId).ThrowIfFailed();
var (result, functionInfo) = ICorProfilerInfo2.GetFunctionInfo(functionId);

if (!result)
{
return $"Failed ({result})";
}

var metaDataImport = ICorProfilerInfo2.GetModuleMetaData(functionInfo.ModuleId, CorOpenFlags.ofRead, KnownGuids.IMetaDataImport).ThrowIfFailed();
var methodProperties = metaDataImport.GetMethodProps(new MdMethodDef(functionInfo.Token)).ThrowIfFailed();
var typeDefProps = metaDataImport.GetTypeDefProps(methodProperties.Class).ThrowIfFailed();
Expand Down
10 changes: 8 additions & 2 deletions src/ManagedDotnetProfiler/DllMain.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using System;
using System.Runtime.InteropServices;
using Silhouette;

namespace ManagedDotnetProfiler;
Expand All @@ -8,8 +9,13 @@ public class DllMain
private static ClassFactory Instance;

[UnmanagedCallersOnly(EntryPoint = "DllGetClassObject")]
public static unsafe int DllGetClassObject(void* rclsid, void* riid, nint* ppv)
public static unsafe HResult DllGetClassObject(Guid* rclsid, Guid* riid, nint* ppv)
{
if (*rclsid != new Guid("0A96F866-D763-4099-8E4E-ED1801BE9FBC"))
{
return HResult.E_NOINTERFACE;
}

Instance = new ClassFactory(new CorProfiler());
*ppv = Instance.IClassFactory;

Expand Down
4 changes: 2 additions & 2 deletions src/TestApp/launch.cmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@set CORECLR_ENABLE_PROFILING=1
@set CORECLR_PROFILER={846F5F1C-F9AE-4B07-969E-05C26BC060D8}
@set CORECLR_PROFILER_PATH=E:\git\ManagedDotnetProfiler\ManagedDotnetProfiler\bin\Release\net9.0\win-x64\publish\ManagedDotnetProfiler.dll
@set CORECLR_PROFILER={0A96F866-D763-4099-8E4E-ED1801BE9FBC}
@set CORECLR_PROFILER_PATH=..\..\..\..\ManagedDotnetProfiler\bin\Release\net9.0\win-x64\publish\ManagedDotnetProfiler.dll
@TestApp.exe

0 comments on commit db21204

Please sign in to comment.