Location>code7788 >text

NET AOT Compiler: How to Hijack the AOT Compiler for Source-Level Debugging

Popularity:756 ℃/2024-10-16 16:52:36

I: Background

1. Storytelling

The previous post talked aboutC# program compiled to Native code macro process, a fan friend raised a question about whether it's possible to make a change in the dotnet publish release process for theAOT compilerIntercepting for source-level debugging is a good problem to have and a must for deep research, so let's share this one.

II: Managed and Unmanaged Debuggers

1. Introduction to the debugger

I'm sure we all know by nowAOT Compiler () It is written in C# code, which means it is a managed program, and there are two debuggers for debugging managed programs:

  • Visual Studio Hosted Debugger

Debugging C# code it's a no-brainer, the downside is the lack of means to view unmanaged parts.

  • WinDbg Unmanaged Debugger

Debugging C/C++ is a great tool, but using it to debug managed C# code, while it works, is not very intuitive in all aspects of variable display.

A screenshot is below, so all in all there are pros and cons to each:

2. Test code

For the sake of demonstration, let's start with a test code, which is very simple.


    internal class Program
    {
        static void Main(string[] args)
        {
            ("Hello, World!");
            ();
        }
    }

Once you have the code, you can watch how to implement the respective interceptions with Visual Studio and Windbg.

III: Debugger Interception in Action

1. WinDbg Intercept

An unmanaged debugger that is king of the Windows platform, use it to hijack theIt's very convenient to have the registry'sHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\ The screenshot below shows how to configure a Deubgger key:

next usedotnet publish Publish the program, and after a few moments you will see that windbg immediately starts to intercept it, and thenctrl+o Open the cs file where we need to place the breakpoints, such as the kernel'sCompilation method, and then g is executed directly after the breakpoint, as shown in the following screenshot:

From the trigrams I got a hit, and with dv I can also see the memory address of each local variable, isn't that interesting.

Overall this approach is simple and brutal to use, but debugging C# with an unmanaged debugger like windbg is always a bit of a challenge.identity or status is unverified, or inconsistent in detailThe better way would still be to use a professional-grade homebrew like visual studio, wouldn't it?

2. Visual Studio Intercept

Previous articleTold you about executiondotnet publish The call is made from the catalog.nuget\packages\\8.0.8\tools Under the, the screenshot is below:

In order to use the hosted debugger, we will manually compile the project to replace the one here, as shown in the following screenshot:

In order to allow VS to be attached to processes, ilc provides a--waitfordebugger parameters, refer to the following:


PS D:\csharpapplication\21.20240910\src\Example\Example_21_1> ilc -h
Description:
  .NET Native IL Compiler

Usage:
  ilc <input-file-path>... [options]

Arguments:
  <input-file-path>  Input file(s)

Options:
  -?, -h, --help                  Show help and usage information
  ...
  --waitfordebugger               Pause to give opportunity to attach debugger

The effect of this parameter is to pass the Let the program pause so you can use VS to Attach, that's how it's written in the source code:


        public Program(ILCompilerRootCommand command)
        {
            _command = command;

            if (Get())
            {
                ("Waiting for debugger to attach. Press ENTER to continue");
                ();
            }
        }

But on my hand-compiled one I use I can't seem to stop it, so I'll change it slightly here, with the following reference:


        public Program(ILCompilerRootCommand command)
        {
            _command = command;

            while (!)
            {
                ("Waiting for debugger to attach. Press ENTER to continue");
                //();
                (1000);
            }
        }

Next, recompile the project, which will generate the directoryruntime\artifacts\bin\coreclr\windows.\ilc\net8.0 Copy all files under the nugut directory to the.nuget\packages\\8.0.8\tools Down, the screenshot is below:

Once everything is ready, the next step is to use thedotnet publish Redistribute the program and you can see from the cmd output that it is waiting for attach to attach.


PS D:\csharpapplication\21.20240910\src\Example\Example_21_1> dotnet publish -r win-x64 -c Debug -o D:\testdump
  The project to be restored is being identified…
  All items are up to date,unretrievable。
  Example_21_1 -> D:\csharpapplication\21.20240910\src\Example\Example_21_1\bin\Debug\net8.0\win-x64\Example_21_1.d
  ll
  Generating native code
  Waiting for debugger to attach. Press ENTER to continue
  Waiting for debugger to attach. Press ENTER to continue
  Waiting for debugger to attach. Press ENTER to continue
  Waiting for debugger to attach. Press ENTER to continue
  Waiting for debugger to attach. Press ENTER to continue

In the VS menu Debug -> Attach to Process to our process, you can see that really hit, we look at this debugging experience is not much higher, the screenshot is as follows:

Those who have experienced this approach I believe there are some new questions, that is, it is too much trouble to repeat the debugging, can we just start with thetriggering program way to debug? That's what we're going to talk about next.

3. VS startup debugging for ilc

For those of you who have read the previous post, you know that before every AOT compilation there is a file in the native directory that is the source of input for the AOT Compiler, the screenshot is as follows:

So it's perfectly fine to use it as a startup parameter for the project, and next we'll set the@D:\csharpapplication\21.20240910\src\Example\Example_21_1\obj\Debug\net8.0\win-x64\native\Example_21_1. Put it in the command line of the ILCompiler project, as shown in the following screenshot:

Once the configuration is done, next put theExample_21_1.dll,Example_21_1.obj,Example_21_1.def All three blocks are changed to full paths, referenced below:


D:\csharpapplication\21.20240910\src\Example\Example_21_1\obj\Debug\net8.0\win-x64\Example_21_1.dll
-o:D:\csharpapplication\21.20240910\src\Example\Example_21_1\obj\Debug\net8.0\win-x64\native\Example_21_1.obj
-r:C:\Users\Administrator\.nuget\packages\-x64\8.0.8\runtimes\win-x64\lib\net8.0\
-r:C:\Users\Administrator\.nuget\packages\\8.0.8\sdk\
...
--targetarch:x64
--dehydrate
-g
--exportsfile:D:\csharpapplication\21.20240910\src\Example\Example_21_1\obj\Debug\net8.0\win-x64\native\Example_21_1.def
...

Directly F5 start ILCompiler project, you can see the success of debugging easily, this way is a good solution to the problem of repeated debugging, screenshots are as follows:

III: Summary

Hijacking the AOT Compiler itself for source-level debugging is an interesting topic in its own right, and constantly intervening in the various stages of the Compiler's compilation is believed to provide some unusual means of learning AOT in depth.
图片名称