It's not specific to Main entry point method. Consider this code:
public class Program
{
static void Main(string[] args) {
MyClass.Test();
}
}
static class MyClass {
static MyClass() {
throw new Exception("here we are");
}
public static void Test() {
Console.WriteLine("test");
}
}
If you run it, exception stack trace will be:
Unhandled exception: System.TypeInitializationException: Type
initialized for "ConsoleApp2.MyClass" threw an exception. --->
System.Exception: here we are
at ConsoleApp2.MyClass..cctor()
--- End of inner exception stack trace ---
at ConsoleApp2.MyClass.Test()
at ConsoleApp2.Program.Main(String[] args)
So, the same situation as with exception in static constructor of entry point class.
If you run that application with WinDbg and run !clrstack when exception is thrown, you will see:
000000af568fdc98 00007ffd54659d98 [GCFrame: 000000af568fdc98]
000000af568fde58 00007ffd54659d98 [GCFrame: 000000af568fde58]
000000af568fea00 00007ffd54659d98 [PrestubMethodFrame:
000000af568fea00] ConsoleApp2.MyClass.Test()
000000af568febe0 00007ffce37704a2
ConsoleApp2.Program.Main(System.String[])
000000af568fee40 00007ffd42d86793 [GCFrame: 000000af568fee40]
And in stack window you can see:
clr!MethodTable::DoRunClassInitThrowing + 0x599
clr!MethodTable::CheckRunClassInitThrowing + 0xbb
clr!MethodDesc::DoPrestub + 0xd1d
When exactly to call static type constructor is determined by JIT compiler. When type explicitly defines static constructor, C# compiler will not mark type with BeforeFieldInit flag. Types with that flags are allowed to be initialized
in "relaxed" fashion, at some point before their members are accessed (or at least before their static fields are accessed). So for them, JIT might emit static constructor call at any point before you access them, even right on startup of your application. Types without that flag are initialized in "precise" fashion - JIT will emit call to static constructor for them right when some member is accessed for the first time.
JIT performs compilation of methods on the fly. If method is not yet compiled into native code - method will point to a "stub". This stub contains code to run JIT, inspect method, compile it into native code, and then change method pointer from stub to compiled native code, so that on next call to this method flow goes directly to compiled code, without stub.
As you can see from WinDbg output - when exception happens, we are in a stub of MyClass.Test() method. At this point, when performing compilation of MyClass.Test() into native code, JIT sees that static constructor has not been run, emits code to call static constructor and compiles MyClass.Test(). But all this happens after method is technically called (but before any code from that method has actually been executed), so it appears on call stack.
I've used different code to illustrate it's not related to Main, but situation with code from your question is the same:
0000007ba0b3dba8 00007ffbfbb89d98 [GCFrame: 0000007ba0b3dba8]
0000007ba0b3dd68 00007ffbfbb89d98 [GCFrame: 0000007ba0b3dd68]
0000007ba0b3e910 00007ffbfbb89d98 [PrestubMethodFrame:
0000007ba0b3e910] ConsoleApp2.Program.Main(System.String[])
0000007ba0b3ed20 00007ffbfbb89d98 [GCFrame: 0000007ba0b3ed20]