I'm trying to improve my C# math operations by using IL code. One problem is currently that C# does not allow math operations on generics, but the IL does - at least for the primitive data types (interestingly not decimal). For that reason I created some test method in C# to check that the resulting IL code.
Here's the code of the C# method:
public static float Add(float A, float B)
{
return A + B;
}
Here's the result VS2015SP2 / Release + Optimizations turned on.
Just to make sure: Here`s the csc command line from the build:
C:\Program Files (x86)\MSBuild\14.0\bin\csc.exe /noconfig
/nowarn:1701,1702,2008 /nostdlib+ /platform:anycpu32bitpreferred
/errorreport:prompt /warn:4 /define:TRACE /errorendlocation
/preferreduilang:en-US /highentropyva+ /reference:"C:\Program Files
(x86)\Reference
Assemblies\Microsoft\Framework.NETFramework\v4.5.2\Microsoft.CSharp.dll"
/reference:"C:\Program Files (x86)\Reference
Assemblies\Microsoft\Framework.NETFramework\v4.5.2\mscorlib.dll"
/reference:"C:\Program Files (x86)\Reference
Assemblies\Microsoft\Framework.NETFramework\v4.5.2\System.Core.dll"
/reference:"C:\Program Files (x86)\Reference
Assemblies\Microsoft\Framework.NETFramework\v4.5.2\System.Data.DataSetExtensions.dll"
/reference:"C:\Program Files (x86)\Reference
Assemblies\Microsoft\Framework.NETFramework\v4.5.2\System.Data.dll"
/reference:"C:\Program Files (x86)\Reference
Assemblies\Microsoft\Framework.NETFramework\v4.5.2\System.dll"
/reference:"C:\Program Files (x86)\Reference
Assemblies\Microsoft\Framework.NETFramework\v4.5.2\System.Net.Http.dll"
/reference:"C:\Program Files (x86)\Reference
Assemblies\Microsoft\Framework.NETFramework\v4.5.2\System.Xml.dll"
/reference:"C:\Program Files (x86)\Reference
Assemblies\Microsoft\Framework.NETFramework\v4.5.2\System.Xml.Linq.dll"
/debug- /filealign:512 /optimize+
/out:obj\Release\ConsoleApplication1.exe /ruleset:"C:\Program Files
(x86)\Microsoft Visual Studio 14.0\Team Tools\Static Analysis
Tools\Rule Sets\MinimumRecommendedRules.ruleset"
/subsystemversion:6.00 /target:exe /utf8output Program.cs
Properties\AssemblyInfo.cs
"C:\Users\Martin\AppData\Local\Temp.NETFramework,Version=v4.5.2.AssemblyAttributes.cs"
obj\Release\TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs
obj\Release\TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs
obj\Release\TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs
(TaskId:27) 1>
.method public hidebysig static
float32 Add (
float32 A,
float32 B
) cil managed
{
// Method begins at RVA 0x2054
// Code size 9 (0x9)
.maxstack 2
.locals init (
[0] float32
)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.1
IL_0003: add
IL_0004: stloc.0
IL_0005: br.s IL_0007
IL_0007: ldloc.0
IL_0008: ret
} // end of method Program::Add
Do you know any reason why there is still nop in it, why there's a local variable and why there's a jump at the end that does nothing?
It's clear to me that the final jitter might solve this, but if I see this I don't know, if I can trust the jitter.
Thanks
Martin
I'm afraid I cannot repeat your results. Compiling with csc /debug- /optimize+ and then using ildasm, I get:
.method public hidebysig static float32 Add(float32 A,
float32 B) cil managed
{
// Code size 4 (0x4)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: add
IL_0003: ret
} // end of method Program::Add
which is what I'd expect from optimized code. Indeed if I change to optimize-, I get the code you posted. Are you checking a Debug vs. Release subdirectory perhaps?
Related
I have no idea how to do compile IL code at runtime.
I use .NET core 3.1 and I can generate a string which contains
.assembly extern mscorlib {}
.assembly Hello {}
.module Hello.exe
.class Hello.Program
extends [mscorlib]System.Object
{
.method static void Main(string[] args)
cil managed
{
.entrypoint
ldstr "H"
call void[mscorlib]
System.Console::
Write(string)
ldstr "i"
call void[mscorlib]
System.Console::
Write(string)
ret
}
}
But how do I then compile the file? I want to do it at runtime.
(ignore the bad IL code)
So, i found out how to do it. I copied the ilasm.exe and fusion.dll from my .net framework folder and then i saved the string to a file and then i used ProcessInfo to run ilasm file. That then outputted the exe i wanted.
For anybody who is wondering, ilasm.exe is located in
C:\Windows\Microsoft.NET\Framework\Verison
if you replace verison with the framework verison, for example v4.0.30319.
Thanks to anybody who tried to help!
I need to inspect code, that JIT emits for generic method with different struct parameters. I read articles about WinDbg and SOS.dll and it's possible to inspect non-generic methods. But for generic methods, which should be JIT-ed for each struct type there is no JIT-ed code in method table, where can i find it?
namespace ConsoleApp1
{
class Program
{
public void Foo<T>(T arg)
{
//some code here
}
static void Main(string[] args)
{
var program = new Program();
program.Foo("test");
program.Foo(1.0);
program.Foo(new Guid());
program.Foo((byte)1);
program.Foo((char)1);
program.Foo(1);
}
}
}
WinDbg:
0:006> .loadby sos clr
0:006> !Name2EE ConsoleApp1!ConsoleApp1.Program
Module: 00007ffa3abf4118
Assembly: ConsoleApp1.exe
Token: 0000000002000002
MethodTable: 00007ffa3abf5a18
EEClass: 00007ffa3abf24b8
Name: ConsoleApp1.Program
Dump method table
0:006> !DumpMT -md 00007ffa3abf5a18
EEClass: 00007ffa3abf24b8
Module: 00007ffa3abf4118
Name: ConsoleApp1.Program
mdToken: 0000000002000002
File: D:\repos\ConsoleApp1\ConsoleApp1\bin\Debug\net462\ConsoleApp1.exe
BaseSize: 0x18
ComponentSize: 0x0
Slots in VTable: 7
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
00007ffa9759c080 00007ffa970f7fb8 PreJIT System.Object.ToString()
00007ffa97604360 00007ffa970f7fc0 PreJIT System.Object.Equals(System.Object)
00007ffa97674770 00007ffa970f7fe8 PreJIT System.Object.GetHashCode()
00007ffa975cf570 00007ffa970f8000 PreJIT System.Object.Finalize()
00007ffa3ad005c0 00007ffa3abf5a10 JIT ConsoleApp1.Program..ctor()
00007ffa3ad00078 00007ffa3abf59e0 NONE ConsoleApp1.Program.Foo(!!0) // "base" IL code
00007ffa3ad00480 00007ffa3abf5a00 JIT ConsoleApp1.Program.Main(System.String[])
And ofc I got no code for Foo
0:006> !U 00007ffa3abf59e0
Not jitted yet
Is it any ideas?
I took your code and compiled it as x86 debug build. I then attached to the process and waited until it terminated.
I find the same result as you:
Entry MethodDe JIT Name
[...]
00250038 00204d34 NONE JitExample2.Program.Foo(!!0)
Here it says NONE, which has the meaning "not jitted".
0:000> !dumpmd 00204d34
Method Name: JitExample2.Program.Foo(!!0)
[...]
IsJitted: no
So the answer seems to be confirmed: it's not jitted.
I don't know any official method of getting a list of jitted generic methods. But with the help of !bpmd it's possible:
0:000> !bpmd JitExample2 JitExample2.Program.Foo
Found 1 methods in module 00204024...
MethodDesc = 00204d34
Setting breakpoint: bp 0025074A [JitExample2.Program.Foo[[System.Char, mscorlib]](Char)]
Setting breakpoint: bp 002506EA [JitExample2.Program.Foo[[System.Byte, mscorlib]](Byte)]
Setting breakpoint: bp 00250687 [JitExample2.Program.Foo[[System.Guid, mscorlib]](System.Guid)]
Setting breakpoint: bp 00250627 [JitExample2.Program.Foo[[System.Double, mscorlib]](Double)]
Setting breakpoint: bp 002505B0 [JitExample2.Program.Foo[[System.__Canon, mscorlib]](System.__Canon)]
Setting breakpoint: bp 002507AA [JitExample2.Program.Foo[[System.Int32, mscorlib]](Int32)]
You can then use the addresses after bp to show the JIT code:
0:000> !u 0025074A
Normal JIT generated code
JitExample2.Program.Foo[[System.Char, mscorlib]](Char)
Begin 00250728, size 4b
C:\Users\For example John\JitExample2\Program.cs # 8:
00250728 55 push ebp
00250729 8bec mov ebp,esp
0025072b 83ec10 sub esp,10h
0025072e 33c0 xor eax,eax
[...]
I tested Dotfuscator with some of my DLLs written in C#.
It seemed it works really well on .NET environment, so this time I tried them with Unity3D. Those simple DLLs were just coded only with simple standard C# classes and methods, no reflection, no LINQ, no other custom or 3rd party APIs including Unity's. But failed to make them work in my Unity3d project, while original pure DLLs which aren't obfuscated were working fine.
After struggling for a few hours, I decided to test with new simple projects. Below is all I got in C#, there are no other code, no other references at all.
[System.Reflection.Obfuscation(Feature = "renaming", Exclude = true)]
public class Class1
{
[System.Reflection.Obfuscation(Feature = "renaming", Exclude = true)]
public static int Get()
{
return 123;
}
}
After compiling this to DLL, I put it in an EMPTY project on Unity3D Editor and played, and it worked well. But when I gave another try after obfuscating the DLL with Dotfuscator, it stopped saying "InvalidProgramException: Invalid IL code in Class1:Get (): IL_000c: pop".
As you can see above, I added attributes which prevent the code from being renamed, and of course, I could see in Reflector, the name of both class and method weren't actually renamed.
Yesterday I newly registered and downloaded the evaluation version of the latest Dotfuscator Professional Edition 4.21.0 from PreEmptive's site. I compiled both on Visual Studio 2008 and 2015 with .NET 3.5, also tried with all different Solution Platforms. The version of Unity is 5.3.4f1.
My guess now is Dotfuscator only works with MSIL, not Mono 2.X which Unity3D internally uses.
Am I right? Is there anyone using Dotfuscator with Unity3D or Mono well?
[EDIT]
I checked IL code using ILDasm, don't understand what's going on, but interesting to see a bunch of code is modified.
Before applying Dotfuscator
.method public hidebysig static int32 Get() cil managed
{
.custom instance void [mscorlib]System.Reflection.ObfuscationAttribute::.ctor() = ( 01 00 02 00 54 0E 07 46 65 61 74 75 72 65 08 72 // ....T..Feature.r
65 6E 61 6D 69 6E 67 54 02 07 45 78 63 6C 75 64 // enamingT..Exclud
65 01 ) // e.
// Code Size 6 (0x6)
.maxstack 8
IL_0000: ldc.i4 0x75bcd15
IL_0005: ret
} // end of method Class1::Get
After applying Dotfuscator
.method public hidebysig static int32 Get() cil managed
{
// Code Size 127 (0x7f)
.maxstack 2
.locals init (int32 V_0)
IL_0000: ldc.i4 0x993
IL_0005: stloc V_0
IL_0009: ldloca V_0
IL_000d: ldind.i4
IL_000e: ldc.i4 0x993
IL_0013: stloc V_0
IL_0017: ldloca V_0
IL_001b: ldind.i4
IL_001c: ceq
IL_001e: switch (
IL_002f,
IL_004c,
IL_002f)
IL_002f: br.s IL_003e
IL_0031: ldc.i4.0
IL_0032: stloc V_0
IL_0036: ldloca V_0
IL_003a: ldind.i4
IL_003b: pop
IL_003c: br.s IL_004a
IL_003e: ldc.i4.0
IL_003f: stloc V_0
IL_0043: ldloca V_0
IL_0047: ldind.i4
IL_0048: br.s IL_003b
IL_004a: nop
IL_004b: nop
IL_004c: ldc.i4 0x1
IL_0051: stloc V_0
IL_0055: ldloca V_0
IL_0059: ldind.i4
IL_005a: br.s IL_0068
IL_005c: ldc.i4.0
IL_005d: stloc V_0
IL_0061: ldloca V_0
IL_0065: ldind.i4
IL_0066: br.s IL_0068
IL_0068: brfalse.s IL_006a
IL_006a: ldc.i4.0
IL_006b: stloc V_0
IL_006f: ldloca V_0
IL_0073: ldind.i4
IL_0074: brfalse IL_0079
IL_0079: ldc.i4 0x75bcd15
IL_007e: ret
} // end of method Class1::Get
UPDATE: As of version 6.0, Dotfuscator Professional no longer supports Unity. (See changelog for 6.0.0-beta). It continues to support Mono. The original answer follows.
Yes, Dotfuscator Professional Edition supports Mono and Unity. The reason you're seeing differences in the IL is due to the Control Flow obfuscation that Professional Edition provides. This is a distinct from the Renaming feature from which you have excluded your identifiers.
By default, Dotfuscator uses as many Control Flow transforms as it can get away with on the .NET Framework (i.e., Microsoft's implementation). However, some of these transforms are not compatible with Mono. Dotfuscator provides an option to disable such transforms.
After loading your Dotfuscator project in the GUI, this option can be found on the Settings tab, under "Advanced", named "Use only Mono-compatible transforms". Set this option to "Yes", then save and rebuild the project.
If that is still giving you problems, you can disable Control Flow entirely to see if that fixes the problem. This is also on the Settings tab, under "Features", "Disable Control Flow". Set this option to "Yes", then save and rebuild the project.
Full disclosure: I work for the Dotfuscator team at PreEmptive Solutions. If you have more questions, keep in mind that evaluation users like yourself also have full access to PreEmptive support.
We have the following method in a class in one of our projects:
private unsafe void SomeMethod()
{
// Beginning of the method omitted for brevity
var nv = new Vector4[x];
fixed (Vector4* vp = nv)
{
fixed (float* tp = /* Source float ptr */)
{
fixed (double* ap = /* Source double ptr */)
{
for (var i = atlArray.Length - 1; i >= 0; --i)
{
vp[((i + 1) << 3) - 2] = new Vector4(tp[i], btt, 0.0f, 1.0f);
// Additional Vector4 construction omitted for brevity
nttp[i] = new Vector2(tp[i], this.ttvp);
nts[i] = string.Format(ap[i], /* etc. */);
}
}
}
}
this.ts = nts;
this.ttp = nttp;
this.V = nv; // <- This is a property setter
}
I've had to obfuscate this, but hopefully it's still clear enough to get an idea of what's going on.
On one of our developers' machines, in debug builds, the C# compiler removes the three assignments that occur after the fixed block is closed. If we attempt to put a breakpoint on these lines, the breakpoint skips to the ending brace of the method when the application starts. Code that appears between a fixed block and the end of a method is removed in other methods too, but puzzlingly, not in all of them.
After some experimentation, we found that turning on optimization for the affected project caused the missing code to be included. However, this work-around fails for our unit testing - the code is missing, and changing the optimization of the affected project and its test project does not help. We also found that moving the three assignments inside the inner-most fixed statement worked - it becomes clear why when examining the IL.
In the debug DLL built on the affected machine (with optimization turned off), a return op appears directly after ap is popped off the stack:
IL_03a1: nop
IL_03a2: ldc.i4.0
IL_03a3: conv.u
IL_03a4: stloc.s ap
IL_03a6: ret
This explains why moving the three assignments before the stloc instruction works. In the debug DLL built on my machine, the return op occurs in the expected place, after the three assignments:
IL_03a5: nop
IL_03a6: ldc.i4.0
IL_03a7: conv.u
IL_03a8: stloc.s ap
IL_03aa: nop
IL_03ab: ldc.i4.0
IL_03ac: conv.u
IL_03ad: stloc.s tp
IL_03af: nop
IL_03b0: ldc.i4.0
IL_03b1: conv.u
IL_03b2: stloc.s vp
IL_03b4: ldarg.0
IL_03b5: ldloc.s nts
IL_03b7: stfld string[] N.B.E.B::ts
IL_03bc: ldarg.0
IL_03bd: ldloc.s nttp
IL_03bf: stfld valuetype [SharpDX]SharpDX.Vector2[] N.B.E.B::ttp
IL_03c4: ldarg.0
IL_03c5: ldloc.s nv
IL_03c7: call instance void N.B.E.B::set_V(valuetype [SharpDX]SharpDX.Vector4[])
IL_03cc: nop
IL_03cd: ret
We've so far failed to produce an SSCCE - this seems to manifest only in very specific circumstances, and only in one of our projects. We've checked that the same versions of Visual Studio, the .NET framework, the C# compiler and MSBuild are being used on both machines. We've checked other potential differences like OS version and updates. Things appear to be the same on both machines (they are the same model of laptop). We're a bit puzzled, frankly. Any help would be much appreciated.
My colleague, the developer whose machine this was affecting, found a difference in the diagnostic output from the build - it was the C# compiler. We thought that MSBuild was using the csc.exe in the expected location (c:\Program Files (x86)\MSBuild\12.0\Bin), but bizarrely, it was actually executing a csc.exe in C:\Users\[user]\AppData\Local\Microsoft\VisualStudio\12.0\Extensions, which had a completely different version. We don't know how that compiler executable came to be there, or how it was being referenced by MSBuild, but once it was removed, MSBuild reverted to using the version of csc.exe in its home Bin directory, and the code is being built correctly now.
I'm interested in viewing the actual x86 assembly output by a C# program (not the CLR bytecode instructions). Is there a good way to do this?
While debugging your application in Visual Studio, you can right-click on a code where you have stopped (using breakpoint) and click "Go to Disassembly". You can debug through native instructions.
As for doing that with *.exe files on disk, maybe you could use NGen to generate native output and then disassemble it (although I never tried that, so I can't guarantee that it will work).
Here are some sample opcodes from simple arithmetic operation that was written in c#:
int x = 5;
mov dword ptr [ebp-40h],5
int y = 6;
mov dword ptr [ebp-44h],6
int z = x + y;
mov eax,dword ptr [ebp-40h]
add eax,dword ptr [ebp-44h]
mov dword ptr [ebp-48h],eax
As #IvanDanilov answered, you can use WinDbg and SOS. I am answering separately to provide a walk-through.
In this example, I want to view the disassembly of the AreEqual() method from:
using System;
namespace TestArrayCompare
{
class Program
{
static bool AreEqual(byte[] a1, byte[] a2)
{
bool result = true;
for (int i = 0; i < a1.Length; ++i)
{
if (a1[i] != a2[i])
result = false;
}
return result;
}
static void Main(string[] args)
{
byte[] a1 = new byte[100];
byte[] a2 = new byte[100];
if (AreEqual(a1, a2))
{
Console.WriteLine("`a1' equals `a2'.");
}
else
{
Console.WriteLine("`a1' does not equal `a2'.");
}
}
}
}
Steps:
Open WinDbg. From the File menu, select "Open Executable...". Browse to the location of the EXE (in my case, C:\Users\Daniel\Documents\Visual Studio 2013\Projects\TestArrayCompare\TestArrayCompare\bin\Release\TestArrayCompare.exe).
Add the directory containing the PDB file to the symbol path. For example:
.sympath "C:\Users\Daniel\Documents\Visual Studio 2013\Projects\TestArrayCompare\TestArrayCompare\bin\Release"
In WinDbg's Command window, set a breakpoint when clr.dll is loaded via:
sxe ld:clr
Continue by running the 'Go' command: g
At the clr.dll ModLoad, load SOS: .loadby sos clr
Run BPMD to break on the method for which you wish to see the disassembly. For example:
0:000> !BPMD TestArrayCompare.exe TestArrayCompare.Program.AreEqual
Adding pending breakpoints...
Continue again by running the 'Go' command: g
Run Name2EE to see the method descriptor. For example:
0:000> !Name2EE TestArrayCompare.exe TestArrayCompare.Program.AreEqual
Module: 00a62edc
Assembly: TestArrayCompare.exe
Token: 06000001
MethodDesc: 00a637a4
Name: TestArrayCompare.Program.AreEqual(Byte[], Byte[])
Not JITTED yet. Use !bpmd -md 00a637a4 to break on run.
Run the BPMD command in the "Not JITTED yet" line. For example:
0:000> !bpmd -md 00a637a4
MethodDesc = 00a637a4
Adding pending breakpoints...
Continue again: g
You should see "JITTED ..." in the Command window. Re-run the Name2EE command to see the address of the JIT code. For example:
0:000> !Name2EE TestArrayCompare.exe TestArrayCompare.Program.AreEqual
Module: 00a62edc
Assembly: TestArrayCompare.exe
Token: 06000001
MethodDesc: 00a637a4
Name: TestArrayCompare.Program.AreEqual(Byte[], Byte[])
JITTED Code Address: 00b500c8
Use the u command to disassemble, starting at the listed code address. For example:
0:000> u 00b500c8 L20
00b500c8 55 push ebp
00b500c9 8bec mov ebp,esp
00b500cb 57 push edi
00b500cc 56 push esi
...
(For the above, I was using WinDbg 6.3.9600.17200 X86 from the Windows 8.1 SDK.)
One handy reference is the SOS.dll (SOS Debugging Extension) reference page on MSDN.
You should use WinDbg with SOS/SOSEX, ensure that method you want to see x86 code for is JITted in method tables and then see actual unassembly with u command. Thus you would see actual code.
As others mentioned here, with ngen you could see code that is not exactly matches actual JIT compilation result. With Visual Studio it is also possible because JIT's compilation depends heavily on the fact if debugger is present or not.
UPD: Some clarification. WinDbg is a debugger also, but it is native one.
Here you can read about the technique in detail.
You could use Visual Studio Debugger by placing a breakpoint and then viewing the Dissassembly window (Alt+Ctrl+D) or try the Native Image Generator Tool (ngen.exe).
You can do a memory dump. However, note that the in-memory code does not necessarily contain every method.
ngen does AOT, or Ahead-of-time code generation, which can be different from JIT code.