Why x86 JIT is smarter than x64? - c#

I'm running a very simple program
static void Main(string[] args)
{
Console.WriteLine(Get4S());
Console.WriteLine(Get4());
}
private static int Get4S()
{
return 4;
}
private static int Get4()
{
int res = 0;
for (int i = 0; i < 4; i++)
{
res++;
}
return res;
}
when it works under x86 it inlines Get4S method and Get4 asm code is:
00000000 push ebp
00000001 mov ebp,esp
00000003 xor eax,eax
00000005 inc eax
00000006 inc eax
00000007 inc eax
00000008 inc eax
00000009 pop ebp
0000000a ret
BUT when running under x64 we get same asm for Get4S method, but Get4 asm is not optimized at all:
00000000 xor eax,eax
00000002 xor edx,edx
00000004 inc eax
00000006 inc edx
00000008 cmp edx,4
0000000b jl 0000000000000004
0000000d ret
I supposed that x64 JIT unroll the loop, then see that result can be computed in compile-time, and function with compile-time result will be inlined. But nothing from it happend.
Why x64 is so stupid in this case?..

I got the point. It's because RyuJIT is used when x64 build is selected, even if .Net 4.5.2 target platform is selected. So i fixed it by adding this section in App.Config file:
<configuration>
<runtime>
<useLegacyJit enabled="1" />
</runtime>
</configuration>
This markup enables "legacy" x64 JIT (in quotes, because I think he's much better than "shiny" RyuJIT), and result ASM in main method is:
00000000 sub rsp,28h
00000004 mov ecx,4
00000009 call 000000005EE75870
0000000e mov ecx,4
00000013 call 000000005EE75870
00000018 nop
00000019 add rsp,28h
0000001d ret
both methods was calculated in compile time and inlined by theirs values.
Conclusion: When .Net 4.6 is installed, old x64 jitter is replaced by RyuJIT for all solutions under CLR4.0. So only way to turn it off is useLegacyJit switch or COMPLUS_AltJit environment variable

Related

How do you track down winform ntGetContextThread + 0xc crash

What is my next step to track down my app crash?
I have a c# winforms app which seems to randomly close without warning. I have run a dump file though winDbg but it hasn't narrowed down my search. Here's what it has given me.
0:000> !analyze -v
*******************************************************************************
* *
* Exception Analysis *
* *
*******************************************************************************
Unable to load image C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\225759bb87c854c0fff27b1d84858c21\mscorlib.ni.dll, Win32 error 0n2
*** WARNING: Unable to verify checksum for mscorlib.ni.dll
*** WARNING: Unable to verify checksum for System.ni.dll
Failed to request MethodData, not in JIT code range
KEY_VALUES_STRING: 1
Key : Analysis.CPU.mSec
Value: 39186
Key : Analysis.DebugAnalysisProvider.CPP
Value: Create: 8007007e on IT21023
Key : Analysis.DebugData
Value: CreateObject
Key : Analysis.DebugModel
Value: CreateObject
Key : Analysis.Elapsed.mSec
Value: 61046
Key : Analysis.Memory.CommitPeak.Mb
Value: 162
Key : Analysis.System
Value: CreateObject
Key : CLR.BuiltBy
Value: NETFXREL2
Key : CLR.Engine
Value: CLR
Key : CLR.Version
Value: 4.6.1055.0
Key : Timeline.Process.Start.DeltaSec
Value: 230
Key : WER.OS.Branch
Value: win8_rtm
Key : WER.OS.Timestamp
Value: 2012-07-25T12:47:00Z
Key : WER.OS.Version
Value: 6.2.9200.16384
Key : WER.Process.Version
Value: 1.0.0.0
ADDITIONAL_XML: 1
OS_BUILD_LAYERS: 1
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 00000000
ExceptionCode: 80000003 (Break instruction exception)
ExceptionFlags: 00000000
NumberParameters: 0
FAULTING_THREAD: 00000970
PROCESS_NAME: app.exe
ERROR_CODE: (NTSTATUS) 0x80000003 - {EXCEPTION} Breakpoint A breakpoint has been reached.
EXCEPTION_CODE_STR: 80000003
MISSING_CLR_SYMBOL: 0
STACK_TEXT:
0074e4d4 772b28a2 772b9f60 0012020e 753bca8a ntdll!NtGetContextThread+0xc
0074e4e0 753bca8a 0074e5c4 0074e5b0 74a7bd90 ntdll!_SEH_epilog4_GS+0xa
0074e5a8 772b214f 0e990248 772b2128 fe9c0cf3 KERNELBASE!CheckSpecialLocales+0xd7
0074e628 772b24cc 00000000 0f27fc30 0e9905c0 ntdll!RtlpFreeHeap+0x667
0074e64c 746026cd 0e990000 00000000 0f27fc30 ntdll!RtlFreeHeap+0x206
0074e660 74600aa1 0f27fc30 0074ed58 0f250048 dbghelp!Win32LiveAllocationProvider::Free+0x19
0074e8a0 0f276660 0074e8c0 00000340 0074ed58 dbghelp!Win32LiveSystemProvider::QueryBuildString+0x1e4
WARNING: Frame IP not in any known module. Following frames may be wrong.
00000000 00000000 00000000 00000000 00000000 0xf276660
STACK_COMMAND: ~0s; .ecxr ; kb
SYMBOL_NAME: dbghelp!Win32LiveAllocationProvider::Free+19
MODULE_NAME: dbghelp
IMAGE_NAME: dbghelp.dll
FAILURE_BUCKET_ID: BREAKPOINT_80000003_dbghelp.dll!Win32LiveAllocationProvider::Free
OS_VERSION: 6.2.9200.16384
BUILDLAB_STR: win8_rtm
OSPLATFORM_TYPE: x86
OSNAME: Windows 8
IMAGE_VERSION: 6.2.9200.16384
FAILURE_ID_HASH: {0bbdb302-68ba-7226-fd2a-f9b8c4d5b299}
Followup: MachineOwner
---------
It doesn't seem to throw any clues which link it back to the app.
The dump file it self came from bugsplat would this be the cause? I tried it in WinDbg as bugsplat also couldn't offer any help, the active thread looks like this.
ntdll!NtGetContextThread
ntdll!_SEH_epilog4_GS
KERNELBASE!CheckSpecialLocales
ntdll!RtlpFreeHeap
ntdll!RtlFreeHeap
dbghelp!Win32LiveAllocationProvider::Free
dbghelp!Win32LiveSystemProvider::QueryBuildString
0xf276660
[InlinedCallFrame: 0074ee98]
08068e3e
08068bf1
08068bf1
080683d3
08067aef
6b8d9eda
System.Windows.Forms.Control.InvokeMarshaledCallbacks()
System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
[InlinedCallFrame: 0074f074]
System.Windows.Forms.DataGridView.WndProc(System.Windows.Forms.Message ByRef)
System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)
System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)
System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
[InlinedCallFrame: 0074f2b4]
DomainBoundILStubClass.IL_STUB_PInvoke(MSG ByRef)
[InlinedCallFrame: 0074f2b4] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr, Int32, Int32)
[InlinedCallFrame: 0074f2ec]
System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
System.Windows.Forms.Application.Run(System.Windows.Forms.Form)
app.Program.Main() [C:\Projects\App\App\Program.cs # 55]
Which manages to link back to Application.Run command but not to anywhere else.
Am I missing something which can help me find what the cause of the crash is, or have I been over looking some information?
# # Update # #
The error turned out to be from the line.
System.Windows.Forms.Control.InvokeMarshaledCallbacks()
Which was being produced from a BeginInvoke call.
If you use the BugSplat .NET SDK, you'll get a managed call stack as well as the native call stack. If you need help with that, feel free to reach out to support#bugsplat.com

How to dump .NET JIT-ed generic method with windbg?

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
[...]

Dotfuscator doesn't support Unity3d?

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.

Compilation omits code after fixed blocks in certain methods

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.

Retrieve JIT output

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.

Categories