What the hell is cmp byte ptr[rax],0 in a managed mini dump?

来源:互联网 发布:世界大数据公司排名 编辑:程序博客网 时间:2024/05/16 18:40

Today I get a managed minidump which contains a NULL REFERENCE exception.

There are very limited information within the minidump – no full image information, no full heap. Debugging such kind of minidump
especially managed one is always scary. Fortunately I can use !pe to find the exception. The top frame in the exception gives me the
name of the function, as well as the fact that we are crashing at instruction 000007ff`03d1e530. And I can see a minimum set of
instructions around the crashing point:

000007ff`03d1e528 488bd0 mov rdx,rax
000007ff`03d1e52b e820be2bf6 call mscorwks!JIT_ChkCastClassSpecial (000007fe`f9fda350)
000007ff`03d1e530 803800
cmp byte ptr [rax],0 <-- we are crashing here
000007ff`03d1e533 488bc8 mov rcx,rax
000007ff`03d1e536 e8254094fe call 000007ff`02662560

It would be nice if I can resolve the address in the last call to some function. But I cannot because there is no full image info within the dump. So what does this code fraction mean?

mscorwks!JIT_ChkCastClassSpecial is a function used by CLR to walk up the inheritance link to cast the object to the target type, and
stores the address to the casted object into rax. This tells us that we are crashing right after a “cast” operation. This is not hard to
understand. However I am confused by the instruction at the crashing point – why do we compare the first byte rax pointing to with 0? And even stranger, there is *no conditional jmp instruction* after the comparison.

After researching and trying with test projects I finally found out the answer. It turns out this instruction is a trick used by the JIT
compiler to ensure that an object is not null before calling a non-static property. If this is a valid object instance, [rax] shall be the
pointer to the MethodTable and this instruction is just a nop. Otherwise it will trigger a null reference exception and that’s why I got the crash.

Here is the test source I used:

class Base {…}
class Test : Base
{
    public int Val{…}
}
class Program
{
    …
    static int Main(string[] args)
   {
        Base o = GetObject();
        var a = (Test)o;
        int b = a.Val;
        return b;
    }
}

And part of the jitted “Main” function is here:

48 000007ff`0018013d 488bd0 mov rdx,rax
48 000007ff`00180140 e80ba200ed call mscorwks!JIT_ChkCastClassSpecial (000007fe`ed18a350)
49 000007ff`00180145 803800 cmp byte ptr [rax],0
49 000007ff`00180148 33c9 xor ecx,ecx
49 000007ff`0018014a 8bc1 mov eax,ecx
49 000007ff`0018014c 0f1f4000 nop dword ptr [rax]
49 000007ff`00180150 83c002 add eax,2
49 000007ff`00180153 83c101 add ecx,1
49 000007ff`00180156 81f9e8030000 cmp ecx,3E8h
49 000007ff`0018015c 7cf2 jl CSharpTest!CSharpTest.Program.Main(System.String[])+0x30 (000007ff`00180150)
49 000007ff`0018015e 4883c428 add rsp,28h
49 000007ff`00180162 f3c3 rep ret

I set a break point on 000007ff`00180145. When it hits the breakpoint I did following to prove that [rax] shall point to the
MethodTable.

0:000> !dumpmt -md poi(@rax)
EEClass: 000007ff00172470
Module: 000007ff00042e30
Name: CSharpTest.Test
mdToken: 02000003 (D:/Work/TestProject/CSharpTest/CSharpTest/bin/Release/CSharpTest.exe)
BaseSize: 0x18
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 6
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
000007feec2cabe0 000007feec04b2e0 PreJIT System.Object.ToString()
000007feec2d2560 000007feec04b2e8 PreJIT System.Object.Equals(System.Object)
000007feec2cbc70 000007feec04b328 PreJIT System.Object.GetHashCode()
000007feec37e5f0 000007feec04b358 PreJIT System.Object.Finalize()
000007ff001801d0 000007ff00043568 JIT CSharpTest.Test..ctor()
000007ff0004c070 000007ff00043558 NONE CSharpTest.Test.get_Val()

With this in mind, I searched through the source code and find the issue quickly. Hope this helps if you hit similar issue too.

原创粉丝点击