Why does localloc violate this CIL method?

I have the following part of the reduced CIL code.
When this CIL method is executed, the CLR throws an InvalidProgramException:

.method assembly hidebysig specialname rtspecialname instance void .ctor(class [mscorlib]System.Collections.Generic.IEnumerable`1<class System.Windows.Input.StylusDeviceBase> styluses) cil managed { .locals init (class [mscorlib]System.Collections.Generic.IEnumerator`1<class System.Windows.Input.StylusDeviceBase> V_0, class System.Windows.Input.StylusDeviceBase V_1) ldc.i4.8 // These instructions cause CIL to break conv.u // localloc // pop // ldarg.0 newobj instance void class [mscorlib]System.Collections.Generic.List`1<class System.Windows.Input.StylusDevice>::.ctor() call instance void class [mscorlib]System.Collections.ObjectModel.ReadOnlyCollection`1<class System.Windows.Input.StylusDevice>::.ctor(class [mscorlib]System.Collections.Generic.IList`1<!0>) ldarg.1 callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<class System.Windows.Input.StylusDeviceBase>::GetEnumerator() stloc.0 .try { leave.s IL_0040 } finally { endfinally } IL_0040: ret } // end of method StylusDeviceCollection::.ctor 

My question is: why is this CIL code invalid?

A few tombs:
- If localloc removed, the code works fine. As far as I know, localloc replaces the size of the parameter in the stack with an address, so the stack remains balanced, AFAICT.
- If the try and finally blocks are removed, the code works fine.
- If the first block of instructions containing localloc is moved after the try-finally block, the code works fine.

So this looks like something in a combination of localloc and try-finally.

A bit of background:

I got to this point after an InvalidProgramException was thrown for the original method due to some tools made at runtime. My approach for debugging this, to find out what is wrong with the device, is as follows:

  • Parsing a failed DLL using ildasm
  • Applying Toolkit Code to a Failure Method
  • Recreating a DLL from a Modified IL with ilasm
  • Starting the program again, and checking its failure
  • Continue to reduce the IL code of the failure method, gradually, up to the minimum scenario that causes the problem (and tries not to introduce errors along the way).

Unfortunately, peverify.exe /IL did not indicate any errors. I tried to console the ECMA specification and Serge Lidin Expert.NET IL, but I could not understand what was going wrong.

Is there anything basic that I am missing?

Edit:

I updated the corresponding IL code a bit to make it more complete (without changing instructions). The second block of instructions, including ldarg , newobj , etc., is taken as from the working code - the source code of the method.

Which is strange for me, deleting either localloc or .try - finally , the code works, but none of them, as far as I know, should change the stack balancing compared to the fact that re is present in the code.

Here, the IL code was decompiled in C # using ILSpy:

 internal unsafe StylusDeviceCollection(IEnumerable<StylusDeviceBase> styluses) { IntPtr arg_04_0 = stackalloc byte[(UIntPtr)8]; base..ctor(new List<StylusDevice>()); IEnumerator<StylusDeviceBase> enumerator = styluses.GetEnumerator(); try { } finally { } } 

Edit 2:

Other observations:
- Taking the localloc block of the localloc code and moving it to the end of the function, the code works fine - so it seems that the code itself is in order.
- The problem does not reproduce when I paste a similar IL code into the global test function hello.

I am very puzzled ...

I would like more information from an InvalidProgramException. The CLR does not seem to bind the exact cause of the failure to the exception object. I also thought about debugging using the CoreCLR debug build, but fortunately the program I am debugging is not compatible with it ...

+5
source share
1 answer

Unfortunately, it seems I hit a CLR error ...

Everything works when using the obsolete JIT compiler:

set COMPLUS_useLegacyJit=1

I was not able to isolate the specific RyuJit setting, which may be the reason for this. I followed the recommendations in this article:
https://github.com/Microsoft/dotnet/blob/master/Documentation/testing-with-ryujit.md

Thanks to everyone who helped!

Aftermath:

Sometime after I came across a workaround for JIT, I realized that the problem only manifests itself when using localloc (which is an localloc ) in a security-critical method called transparent security . Only in this case RyuJit through InvalidProgramException , and Legacy JIT will not.

In my replay, I parsed and reassembled the corresponding DLL and immediately changed the function code, preserving the security attributes - in particular, the assembly attribute AllowPartiallyTrustedCallers , which explains why the problem was not reproduced using an isolated example.

RyuJIT may have some security enhancement compared to the Legacy JIT, which gives a surface to this problem, but nevertheless the fact that localloc will cause the CLR to throw an InvalidProgramException depdending in the presence of try-catch and its relative location to localloc . seems like a subtle mistake.

Running SecAnnotate.exe ( .NET Security Annotator tool ) in the event of a DLL failure was useful to identify security problems between function calls.

More on Security-Transparent Code:
https://docs.microsoft.com/en-us/dotnet/framework/misc/security-transparent-code

+2
source

Source: https://habr.com/ru/post/1272895/


All Articles