I: Background
1. Storytelling
Was watching the AOT the other day and followed itsource generator
, quite an interesting one to write a post today to share briefly.
II: Source Generator Exploration Tour
1. What is a source generator
In short, the source generator isRoslyn compiler
An opening for programmers where some custom cs code can be stuffed in to make theRoslyn compiler
It's given along for the ride when the code is compiled, simply putsmuggle personal goods (e.g. between individuals) But as the old saying goes.If the teacher doesn't go by the way, the doctor doesn't knock on the door.
, so it's still awkward, take a look at the official picture given, the orange area in the picture is the entrapped bootlegs.
Some of you must be wondering, what's the point of this thing? Actually, in the AOT world, the JsonSerializer uses the SourceGeneration
to generate metadata for the serialized type (WeatherForecast), the reference code is as follows:
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(WeatherForecast))]
internal partial class SourceGenerationContext : JsonSerializerContext
{
}
2. A simple example
The above example does not go into too much depth, let's first look at how to implement 0 to 1, here using the official example with hooksDistribution method
The methodological body.
- New SourceGenerator Class Library Project
The source in this is the hook code, but for now it can only be the.NET Standard 2.0
project, which is supposed to achieve maximum compatibility, the reference code is as follows:
namespace SourceGenerator
{
[Generator]
public class HelloSourceGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
// Find the main method
var mainMethod = ();
// Build up the source code
string source = $@"
// <auto-generated/>
using System;
namespace {()}
{{
public static partial class {}
{{
static partial void HelloFrom(string name) =>
($""Generator says: Hi from '{{name}}'"");
}}
}}
";
var typeName = ;
// Add the source code to the compilation
($"{typeName}.", source);
}
public void Initialize(GeneratorInitializationContext context)
{
// No initialization required for this one
}
}
}
- New Example_21_15 project
This is a console program that references the project just described and declares some of the methodsHelloFrom
, the reference code is as follows:
namespace Example_21_15
{
partial class Program
{
static void Main(string[] args)
{
HelloFrom("Generated Code");
();
}
static partial void HelloFrom(string name);
}
}
Keep in mind to add two extra parameters when Include in Example_21_15.csproj, refer to the following:
<ItemGroup>
<ProjectReference Include="..\SourceGenerator\"
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
Once configured you can run the program up and see that the method body is indeed the code in the hook.
II: How Roslyn entrapped her private goods
1. windbg debugging
Exactly how to entrap private goods, essentially Roslyn's internal logic, now the question is how to give him dug out? This requires the use of powerful windbg, using exe startup hijacking insight, the process steps are as follows:
- windbg exe hijacking
I'm sure veteran WinDbg players know this trick, I wrote a simple bat script to startup blocking, and I'd like to remind you that if you have security software, you can uninstall it first to avoid the problem of no permissions.
SET ApplicationName=
SET WinDbgPath=C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\
REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\%ApplicationName%" /v debugger /t REG_SZ /d "%WinDbgPath%" /f
ECHO Successfully set
PAUSE
- Modify the dog code
The easiest and crudest way to do this is to addso that he can be automatically interrupted in windbg.
public class HelloSourceGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
();
//...
}
}
- Publishing programs with dotnet publish
With all the ambushes in place, the final step is to lure Roslyn out of her hole with dotnet publish, using the following commandsdotnet publish -r win-x64 -c Debug -o D:\testdump
If the command is executed, it is intercepted, and the screenshot is as follows:
Since call stacks are hard to come by, get another copy of the text version.
0:029> !clrstack
OS Thread Id: 0x443c (29)
Child SP IP Call Site
00000068E603DD18 00007ffe0135d962 [HelperMethodFrame: 00000068e603dd18] ()
00000068E603DE20 00007ffd6dd357da () [/_/src/coreclr//src/System/Diagnostics/ @ 18]
00000068E603DE50 00007ffd13920522 ()
00000068E603DF10 00007ffd6a4ad4ac .b__5_5(, GeneratorContextBuilder) [/_/src/Compilers/Core/Portable/SourceGeneration/ @ 55]
00000068E603E020 00007ffd6a5bfdd8 +c__DisplayClass3_0`2[[, ],[System.__Canon, ]].b__0(, System.__Canon, ) [/_/src/Compilers/Core/Portable/SourceGeneration/ @ 101]
00000068E603E070 00007ffd6a624e9c `1[[System.__Canon, ]].UpdateStateTable(Builder, `1,`1>>, ) [/_/src/Compilers/Core/Portable/SourceGeneration/Nodes/ @ 70]
00000068E603E2B0 00007ffd13fd1ed8 +[[`2[[System.__Canon, ],[System.__Canon, ]], ]](`1>) [/_/src/Compilers/Core/Portable/SourceGeneration/Nodes/ @ 60]
00000068E603E320 00007ffd6a625349 `1[[System.__Canon, ]].AppendOutputs(, ) [/_/src/Compilers/Core/Portable/SourceGeneration/Nodes/ @ 102]
00000068E603E460 00007ffd6a4b0837 (`1, , Builder, , Builder) [/_/src/Compilers/Core/Portable/SourceGeneration/ @ 340]
00000068E603E520 00007ffd6a4afc9b (, , ) [/_/src/Compilers/Core/Portable/SourceGeneration/ @ 303]
00000068E603ECB0 00007ffd6a4adfc1 (, ByRef, `1 ByRef, ) [/_/src/Compilers/Core/Portable/SourceGeneration/ @ 54]
00000068E603EE50 00007ffd6a468763 (, , , `1, , `1, ) [/_/src/Compilers/Core/Portable/CommandLine/ @ 838]
00000068E603EF20 00007ffd6a469520 (, ByRef, `1, `1, `1, , `1, `1, , , , ByRef, ByRef, `1 ByRef) [/_/src/Compilers/Core/Portable/CommandLine/ @ 1145]
00000068E603F280 00007ffd6a468c52 (, , ) [/_/src/Compilers/Core/Portable/CommandLine/ @ 956]
00000068E603F450 00007ffd6a4684b6 (, ) [/_/src/Compilers/Core/Portable/CommandLine/ @ 781]
00000068E603F4C0 00007ffd6aaf9def ( ByRef, ) [/_/src/Compilers/Server/VBCSCompiler/ @ 152]
00000068E603F5E0 00007ffd6aaff745 +c__DisplayClass8_0.b__1() [/_/src/Compilers/Server/VBCSCompiler/ @ 168]
00000068E603F640 00007ffd6de7b78f `1[[System.__Canon, ]].InnerInvoke() [/_/src/libraries//src/System/Threading/Tasks/ @ 501]
00000068E603F680 00007ffd6dc364bd (, , ) [/_/src/libraries//src/System/Threading/ @ 179]
00000068E603F6F0 00007ffd6dc50914 ( ByRef, ) [/_/src/libraries//src/System/Threading/Tasks/ @ 2345]
00000068E603F9C0 00007ffd72d1c663 [DebuggerU2MCatchHandlerFrame: 00000068e603f9c0]
The last question is if you can find the source code on this call stack, on github of course:/dotnet/roslyn If you want to analyze the methods on the call stack, you can do so by pulling them down, as follows:
III: Summary
When it comes to studying the underlying layers, windbg can be a handy weapon, and this example is kind of a living argument, otherwise you really don't know where to argue the official flowchart from Roslyn, do you?