一:背景 1. 講故事 前些天在看 AOT的時候關註了下 源生成器,挺有意思的一個東西,今天寫一篇文章簡單的分享下。 二:源生成器探究之旅 1. 源生成器是什麼 簡單來說,源生成器是Roslyn編譯器給程式員開的一道口子,在這個口子里可以塞入一些自定義的cs代碼,讓Roslyn編譯器在編譯代碼的時候 ...
一:背景
1. 講故事
前些天在看 AOT的時候關註了下 源生成器
,挺有意思的一個東西,今天寫一篇文章簡單的分享下。
二:源生成器探究之旅
1. 源生成器是什麼
簡單來說,源生成器是Roslyn編譯器
給程式員開的一道口子,在這個口子里可以塞入一些自定義的cs代碼,讓Roslyn編譯器
在編譯代碼的時候順帶給一起處理了,簡單的說就是 夾帶私貨 ,但古話又說 師不順路, 醫不叩門
,所以還是比較尷尬的,看一下官方給的圖,圖中的橙色區域就是夾帶的私貨。
有些朋友肯定好奇,這玩意有什麼用?其實在AOT領域中,JsonSerializer 就使用了 SourceGeneration
來給序列化的類型(WeatherForecast)生成元數據,參考代碼如下:
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(WeatherForecast))]
internal partial class SourceGenerationContext : JsonSerializerContext
{
}
2. 一個簡單的例子
上面的例子不過多深入,先看看怎麼實現0到1的問題,這裡使用官方例子,用鉤子來實現 分佈方法
的方法體。
- 新建 SourceGenerator 類庫項目
這裡面的source就是鉤子代碼,不過目前只能是.NET Standard 2.0
項目,應該是要達到最大的相容性,參考代碼如下:
namespace SourceGenerator
{
[Generator]
public class HelloSourceGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
// Find the main method
var mainMethod = context.Compilation.GetEntryPoint(context.CancellationToken);
// Build up the source code
string source = $@"
// <auto-generated/>
using System;
namespace {mainMethod.ContainingNamespace.ToDisplayString()}
{{
public static partial class {mainMethod.ContainingType.Name}
{{
static partial void HelloFrom(string name) =>
Console.WriteLine($""Generator says: Hi from '{{name}}'"");
}}
}}
";
var typeName = mainMethod.ContainingType.Name;
// Add the source code to the compilation
context.AddSource($"{typeName}.g.cs", source);
}
public void Initialize(GeneratorInitializationContext context)
{
// No initialization required for this one
}
}
}
- 新建 Example_21_15 項目
這是一個控制台程式,引用剛纔的項目,並聲明部分方法 HelloFrom
,參考代碼如下:
namespace Example_21_15
{
partial class Program
{
static void Main(string[] args)
{
HelloFrom("Generated Code");
Console.ReadLine();
}
static partial void HelloFrom(string name);
}
}
要記住在 Example_21_15.csproj 中 Include 時要額外增加兩個參數,參考如下:
<ItemGroup>
<ProjectReference Include="..\SourceGenerator\SourceGenerator.csproj"
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
配置好之後就可以把程式跑起來了,可以看到方法體確實是鉤子中的代碼。
二:Roslyn如何夾帶私貨
1. windbg調試
究竟是如何夾帶私貨,本質上是 Roslyn 內部的邏輯,現在的問題是如何給他挖出來了呢?這就需要使用強大的 windbg,採用exe啟動劫持的方式洞察,流程步驟如下:
- windbg 的exe劫持
資深的 WinDbg 玩家我相信都知道這個招數,我寫了一個簡單的 bat 腳本,對 dotnet.exe 進行啟動攔截,要提醒的是有安全軟體的話可以先卸載掉,以免出現無許可權的問題。
SET ApplicationName=dotnet.exe
SET WinDbgPath=C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe
REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\%ApplicationName%" /v debugger /t REG_SZ /d "%WinDbgPath%" /f
ECHO 已成功設置
PAUSE
- 修改狗子代碼
最簡單粗暴的方式就是加 Debugger.Break
,好讓他在 windbg 中自動中斷。
public class HelloSourceGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
Debugger.Break();
//...
}
}
- 使用 dotnet publish 發佈程式
所有的埋伏做好之後,最後就是用 dotnet publish 來引誘 Roslyn 出洞,參考命令如下 dotnet publish -r win-x64 -c Debug -o D:\testdump
, 命令執行之後果然給攔截到了,截圖如下:
由於調用棧難得,再弄一份文字版。
0:029> !clrstack
OS Thread Id: 0x443c (29)
Child SP IP Call Site
00000068E603DD18 00007ffe0135d962 [HelperMethodFrame: 00000068e603dd18] System.Diagnostics.Debugger.BreakInternal()
00000068E603DE20 00007ffd6dd357da System.Diagnostics.Debugger.Break() [/_/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/Debugger.cs @ 18]
00000068E603DE50 00007ffd13920522 SourceGenerator.HelloSourceGenerator.Execute(Microsoft.CodeAnalysis.GeneratorExecutionContext)
00000068E603DF10 00007ffd6a4ad4ac Microsoft.CodeAnalysis.SourceGeneratorAdaptor.b__5_5(Microsoft.CodeAnalysis.SourceProductionContext, GeneratorContextBuilder) [/_/src/Compilers/Core/Portable/SourceGeneration/GeneratorAdaptor.cs @ 55]
00000068E603E020 00007ffd6a5bfdd8 Microsoft.CodeAnalysis.UserFunctionExtensions+c__DisplayClass3_0`2[[Microsoft.CodeAnalysis.SourceProductionContext, Microsoft.CodeAnalysis],[System.__Canon, System.Private.CoreLib]].b__0(Microsoft.CodeAnalysis.SourceProductionContext, System.__Canon, System.Threading.CancellationToken) [/_/src/Compilers/Core/Portable/SourceGeneration/UserFunction.cs @ 101]
00000068E603E070 00007ffd6a624e9c Microsoft.CodeAnalysis.SourceOutputNode`1[[System.__Canon, System.Private.CoreLib]].UpdateStateTable(Builder, Microsoft.CodeAnalysis.NodeStateTable`1,System.Collections.Generic.IEnumerable`1>>, System.Threading.CancellationToken) [/_/src/Compilers/Core/Portable/SourceGeneration/Nodes/SourceOutputNode.cs @ 70]
00000068E603E2B0 00007ffd13fd1ed8 Microsoft.CodeAnalysis.DriverStateTable+Builder.GetLatestStateTableForNode[[System.ValueTuple`2[[System.__Canon, System.Private.CoreLib],[System.__Canon, System.Private.CoreLib]], System.Private.CoreLib]](Microsoft.CodeAnalysis.IIncrementalGeneratorNode`1>) [/_/src/Compilers/Core/Portable/SourceGeneration/Nodes/DriverStateTable.cs @ 60]
00000068E603E320 00007ffd6a625349 Microsoft.CodeAnalysis.SourceOutputNode`1[[System.__Canon, System.Private.CoreLib]].AppendOutputs(Microsoft.CodeAnalysis.IncrementalExecutionContext, System.Threading.CancellationToken) [/_/src/Compilers/Core/Portable/SourceGeneration/Nodes/SourceOutputNode.cs @ 102]
00000068E603E460 00007ffd6a4b0837 Microsoft.CodeAnalysis.GeneratorDriver.UpdateOutputs(System.Collections.Immutable.ImmutableArray`1, Microsoft.CodeAnalysis.IncrementalGeneratorOutputKind, Builder, System.Threading.CancellationToken, Builder) [/_/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs @ 340]
00000068E603E520 00007ffd6a4afc9b Microsoft.CodeAnalysis.GeneratorDriver.RunGeneratorsCore(Microsoft.CodeAnalysis.Compilation, Microsoft.CodeAnalysis.DiagnosticBag, System.Threading.CancellationToken) [/_/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs @ 303]
00000068E603ECB0 00007ffd6a4adfc1 Microsoft.CodeAnalysis.GeneratorDriver.RunGeneratorsAndUpdateCompilation(Microsoft.CodeAnalysis.Compilation, Microsoft.CodeAnalysis.Compilation ByRef, System.Collections.Immutable.ImmutableArray`1 ByRef, System.Threading.CancellationToken) [/_/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs @ 54]
00000068E603EE50 00007ffd6a468763 Microsoft.CodeAnalysis.CommonCompiler.RunGenerators(Microsoft.CodeAnalysis.Compilation, System.String, Microsoft.CodeAnalysis.ParseOptions, System.Collections.Immutable.ImmutableArray`1, Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider, System.Collections.Immutable.ImmutableArray`1, Microsoft.CodeAnalysis.DiagnosticBag) [/_/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @ 838]
00000068E603EF20 00007ffd6a469520 Microsoft.CodeAnalysis.CommonCompiler.CompileAndEmit(Microsoft.CodeAnalysis.TouchedFileLogger, Microsoft.CodeAnalysis.Compilation ByRef, System.Collections.Immutable.ImmutableArray`1, System.Collections.Immutable.ImmutableArray`1, System.Collections.Immutable.ImmutableArray`1, Microsoft.CodeAnalysis.AnalyzerConfigSet, System.Collections.Immutable.ImmutableArray`1, System.Collections.Immutable.ImmutableArray`1, Microsoft.CodeAnalysis.DiagnosticBag, Microsoft.CodeAnalysis.ErrorLogger, System.Threading.CancellationToken, System.Threading.CancellationTokenSource ByRef, Microsoft.CodeAnalysis.Diagnostics.AnalyzerDriver ByRef, System.Nullable`1 ByRef) [/_/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @ 1145]
00000068E603F280 00007ffd6a468c52 Microsoft.CodeAnalysis.CommonCompiler.RunCore(System.IO.TextWriter, Microsoft.CodeAnalysis.ErrorLogger, System.Threading.CancellationToken) [/_/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @ 956]
00000068E603F450 00007ffd6a4684b6 Microsoft.CodeAnalysis.CommonCompiler.Run(System.IO.TextWriter, System.Threading.CancellationToken) [/_/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @ 781]
00000068E603F4C0 00007ffd6aaf9def Microsoft.CodeAnalysis.CompilerServer.CompilerServerHost.RunCompilation(Microsoft.CodeAnalysis.CompilerServer.RunRequest ByRef, System.Threading.CancellationToken) [/_/src/Compilers/Server/VBCSCompiler/CompilerRequestHandler.cs @ 152]
00000068E603F5E0 00007ffd6aaff745 Microsoft.CodeAnalysis.CompilerServer.ClientConnectionHandler+c__DisplayClass8_0.b__1() [/_/src/Compilers/Server/VBCSCompiler/ClientConnectionHandler.cs @ 168]
00000068E603F640 00007ffd6de7b78f System.Threading.Tasks.Task`1[[System.__Canon, System.Private.CoreLib]].InnerInvoke() [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs @ 501]
00000068E603F680 00007ffd6dc364bd System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs @ 179]
00000068E603F6F0 00007ffd6dc50914 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef, System.Threading.Thread) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2345]
00000068E603F9C0 00007ffd72d1c663 [DebuggerU2MCatchHandlerFrame: 00000068e603f9c0]
最後一個問題就是如果找到這個調用棧上的源碼,當然是在 github 上找啦: https://github.com/dotnet/roslyn ,拉下來後就可以根據調用棧上的方法來分析啦,參考如下:
三:總結
在研究底層方面,windbg可謂是一把趁手的兵器,這個例子也算是活生生的論證了一把,否則真的不知道從 Roslyn 何處來論證官方給出的流程圖,對吧。