Need some help deciphering a line of assembler code, from .NET JITted code
-
13-09-2019 - |
Question
In a C# constructor, that ends up with a call to this(...)
, the actual call gets translated to this:
0000003d call dword ptr ds:[199B88E8h]
What is the DS register contents here? I know it's the data-segment, but is this call through a VMT-table or similar? I doubt it though, since this(...)
wouldn't be a call to a virtual method, just another constructor.
I ask because the value at that location seems to be bad in some way, if I hit F11, trace into (Visual Studio 2008), on that call-instruction, the program crashes with an access violation.
The code is deep inside a 3rd party control library, where, though I have the source code, I don't have the assemblies compiled with enough debug information that I can trace it through C# code, only through the disassembler, and then I have to match that back to the actual code.
The C# code in question is this:
public AxisRangeData(AxisRange range) : this(range, range.Axis) {
}
Reflector shows me this IL code:
.maxstack 8
L_0000: ldarg.0
L_0001: ldarg.1
L_0002: ldarg.1
L_0003: callvirt instance class DevExpress.XtraCharts.AxisBase DevExpress.XtraCharts.AxisRange::get_Axis()
L_0008: call instance void DevExpress.XtraCharts.Native.AxisRangeData::.ctor(class DevExpress.XtraCharts.ChartElement, class DevExpress.XtraCharts.AxisBase)
L_000d: ret
It's that last call there, to the other constructor of the same class, that fails. The debugger never surfaces inside the other method, it just crashes.
The disassembly for the method after JITting is this:
00000000 push ebp
00000001 mov ebp,esp
00000003 sub esp,14h
00000006 mov dword ptr [ebp-4],ecx
00000009 mov dword ptr [ebp-8],edx
0000000c cmp dword ptr ds:[18890E24h],0
00000013 je 0000001A
00000015 call 61843511
0000001a mov eax,dword ptr [ebp-4]
0000001d mov dword ptr [ebp-0Ch],eax
00000020 mov eax,dword ptr [ebp-8]
00000023 mov dword ptr [ebp-10h],eax
00000026 mov ecx,dword ptr [ebp-8]
00000029 cmp dword ptr [ecx],ecx
0000002b call dword ptr ds:[1889D0DCh] // range.Axis
00000031 mov dword ptr [ebp-14h],eax
00000034 push dword ptr [ebp-14h]
00000037 mov edx,dword ptr [ebp-10h]
0000003a mov ecx,dword ptr [ebp-0Ch]
0000003d call dword ptr ds:[199B88E8h] // this(range, range.Axis)?
00000043 nop
00000044 mov esp,ebp
00000046 pop ebp
00000047 ret
Basically what I'm asking is this:
- What the purpose of the
ds:[ADDR]
indirection here? VMT-table is only for virtual isn't it? and this is constructor - Could the constructor have yet to be JITted, which could mean that the call would actually call through a JIT shim? I'm afraid I'm in deep water here, so anything might and could help.
Edit: Well, the problem just got worse, or better, or whatever.
We are developing the .NET feature in a C# project in a Visual Studio 2008 solution, and debugging and developing through Visual Studio.
However, in the end, this code will be loaded into a .NET runtime hosted by a Win32 Delphi application.
In order to facilitate easy experimentation of such features, we can also configure the Visual Studio project/solution/debugger to copy the produced dll's to the Delphi app's directory, and then execute the Delphi app, through the Visual Studio debugger.
Turns out, the problem goes away if I run the program outside of the debugger, but during debugging, it crops up, every time.
Not sure that helps, but since the code isn't slated for production release for another 6 months or so, then it takes some of the pressure off of it for the test release that we have soon.
I'll dive into the memory parts later, but probably not until over the weekend, and post a followup.
Solution
Data segment is where compilers usually put global variables in and where the Import Address Table resides.
00000029 cmp dword ptr [ecx],ecx
0000002b call dword ptr ds:[1889D0DCh]
The first line is actually a null-check, which eventually raises a NullReferenceException
if the pointer located in the ECX register is invalid.
The callvirt
MSIL instruction must do the null-check before invoking the actual method. That being said, we can safely assume that these two lines of assembly code have the following MSIL code representation:
class DevExpress.XtraCharts.AxisBase DevExpress.XtraCharts.AxisRange::get_Axis()
And the commented assembly code:
00000026 mov ecx,dword ptr [ebp-8] // store the pointer to the 'range' in ECX
00000029 cmp dword ptr [ecx],ecx // null-check
0000002b call dword ptr ds:[1889D0DCh] // range.get_Axis()
00000031 mov dword ptr [ebp-14h],eax // store the result in a local variable
00000034 push dword ptr [ebp-14h] // push the result onto a stack
00000037 mov edx,dword ptr [ebp-10h] // this variable was previously loaded with the 'range' pointer
0000003a mov ecx,dword ptr [ebp-0Ch] // here seems to be stored the actual 'this' pointer
0000003d call dword ptr ds:[199B88E8h] // call the this(...) ctor
It's unclear to me why it's crashing, have you tried looking up the contents of the memory location (DS:[199B88E8h]
)?