自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

記一次 .NET某工業(yè)設(shè)計(jì)軟件崩潰分析

開(kāi)發(fā) 前端
一般來(lái)說(shuō)崩潰在clr里都不是什么好事情,這預(yù)示著 clr 在執(zhí)行自身代碼的時(shí)候拋了異常,即災(zāi)難的 ExecutionEngineException,可以用 !t 驗(yàn)證下。

一、背景

1. 講故事

前些天有位朋友找到我,說(shuō)他的軟件在客戶那邊不知道什么原因崩掉了,從windows事件日志看崩潰在 clr 里,讓我能否幫忙定位下,dump 也抓到了,既然dump有了,接下來(lái)就上 windbg 分析吧。

二、WinDbg 分析

1. 為什么崩潰在 clr

一般來(lái)說(shuō)崩潰在clr里都不是什么好事情,這預(yù)示著 clr 在執(zhí)行自身代碼的時(shí)候拋了異常,即災(zāi)難的 ExecutionEngineException,可以用 !t 驗(yàn)證下。

0:000> !t
ThreadCount:      18
UnstartedThread:  0
BackgroundThread: 7
PendingThread:    0
DeadThread:       11
Hosted Runtime:   no
                                                                         Lock  
       ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception
   0    1 52e8 18998d50     24220 Preemptive  639B0D58:00000000 18c361f0 0     STA System.ExecutionEngineException 1f421120
   ...

既然是災(zāi)難性異常,那為什么會(huì)出現(xiàn)呢?可以用 !analyze -v 觀察下。

0:000> !analyze -v
CONTEXT:  0115a98c -- (.cxr 0x115a98c)
eax=00000000 ebx=00000000 ecx=00000000 edx=18c364a4 esi=00030000 edi=18998d50
eip=552bfff1 esp=0115ae6c ebp=0115af24 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
clr!VirtualCallStubManager::ResolveWorker+0x33:
552bfff1 8bb968020000    mov     edi,dword ptr [ecx+268h] ds:002b:00000268=????????
Resetting default scope

READ_ADDRESS:  00000268 

STACK_TEXT:  
0115af24 552c0698     0115afdc 1f4222c0 00030000 clr!VirtualCallStubManager::ResolveWorker+0x33
0115affc 552c070b     0115b010 1f4222c0 00030000 clr!VSD_ResolveWorker+0x1d2
0115b024 28a3a949     639b0d38 00000000 00000000 clr!ResolveWorkerAsmStub+0x1b
0115b0a4 28a3a8bd     00000000 00000000 00000000 xxxx!xxx
...

我去,真無(wú)語(yǔ)了,我卦中數(shù)據(jù)看,這是一個(gè)接口Stub調(diào)用的崩潰,在這里崩潰真的是少之又少,從匯編代碼 edi,dword ptr [ecx+268h] ds:002b:00000268=???????? 上看就是因?yàn)?ecx =0 導(dǎo)致的,接下來(lái)觀察下方法的匯編代碼。

圖片圖片

從匯編上看這個(gè) ecx 其實(shí)就是這個(gè)方法的 this 指針,那為什么 this =null 呢?這就很奇葩了。

2. 為什么 this =null

要想找到這個(gè)答案,只能看clr源代碼,簡(jiǎn)化后如下:

PCODE VSD_ResolveWorker(TransitionBlock* pTransitionBlock,
                        TADDR siteAddrForRegisterIndirect,
                        size_t token
                        )
{
    ...
    VirtualCallStubManager::StubKind stubKind = VirtualCallStubManager::SK_UNKNOWN;
    VirtualCallStubManager* pMgr = VirtualCallStubManager::FindStubManager(callSiteTarget, &stubKind);
    
    ...
    target = pMgr->ResolveWorker(&callSite, protectedObj, representativeToken, stubKind);
}

從卦中代碼看,問(wèn)題就是 pMgr=null 導(dǎo)致的,無(wú)語(yǔ)了,這個(gè) VirtualCallStubManager::FindStubManager 方法的本意就是根據(jù) callSite的stub的前綴找到對(duì)應(yīng)的 虛調(diào)用管理器,它的核心邏輯如下:

StubKind getStubKind(PCODE stubStartAddress, BOOL usePredictStubKind = TRUE)
{
    StubKind predictedKind = (usePredictStubKind) ? predictStubKind(stubStartAddress) : SK_UNKNOWN;
    ...
    if (predictedKind == SK_LOOKUP)
    {
        if (isLookupStub(stubStartAddress))
            return SK_LOOKUP;
    }
    ...
    return SK_UNKNOWN;
}

VirtualCallStubManager::StubKind VirtualCallStubManager::predictStubKind(TADDR stubStartAddress)
{
    StubKind stubKind = SK_UNKNOWN;

    WORD firstWord = *((WORD*)stubStartAddress);

    if (firstWord == 0x05ff)
    {
        stubKind = SK_DISPATCH;
    }
    else if (firstWord == 0x6850)
    {
        stubKind = SK_LOOKUP;
    }
    else if (firstWord == 0x8b50)
    {
        stubKind = SK_RESOLVE;
    }

    return stubKind;
}

接下來(lái)需要找到 stubStartAddress 的地址是多少?這個(gè)只需要提取 ResolveWorker 方法的第一個(gè)參數(shù) callSite 即可。

0:000> dp poi(0115afdc) L1
0c740040  0c746012

0:000> u 0c746012
0c746012 50              push    eax
0c746013 6800000300      push    30000h
0c746018 e9d3a6b748      jmp     clr!ResolveWorkerAsmStub (552c06f0)
0c74601d 0000            add     byte ptr [eax],al
0c74601f 0000            add     byte ptr [eax],al
0c746021 005068          add     byte ptr [eax+68h],dl
0c746024 0000            add     byte ptr [eax],al
0c746026 46              inc     esi

0:000> dp 0c746012 L1
0c746012  00006850

對(duì)比剛才的代碼既然都返回來(lái)了 SK_LOOKUP 那為什么還是 SK_UNKNOWN 呢?這個(gè)也可以通過(guò)在線程棧上找到 &stubKind 變量得到驗(yàn)證。

0:000> uf 552c0698
...
clr!VSD_ResolveWorker+0x1ab:
552c065f 8b85e0ffffff    mov     eax,dword ptr [ebp-20h]
552c0665 83a5ecffffff00  and     dword ptr [ebp-14h],0
552c066c 8d95ecffffff    lea     edx,[ebp-14h]
552c0672 8b08            mov     ecx,dword ptr [eax]
552c0674 e858feffff      call    clr!VirtualCallStubManager::FindStubManager (552c04d1)
552c0679 ffb5ecffffff    push    dword ptr [ebp-14h]
552c067f 51              push    ecx
552c0680 8bcc            mov     ecx,esp
552c0682 8931            mov     dword ptr [ecx],esi
552c0684 ffb5e8ffffff    push    dword ptr [ebp-18h]
552c068a 8d8de0ffffff    lea     ecx,[ebp-20h]
552c0690 51              push    ecx
552c0691 8bc8            mov     ecx,eax
552c0693 e823f9ffff      call    clr!VirtualCallStubManager::ResolveWorker (552bffbb)
552c0698 8bf0            mov     esi,eax
...

0:000> dp 0115affc-0x14 L1
0115afe8  00000000

我感覺(jué)這邏輯也只有clr團(tuán)隊(duì)幫忙解釋,我已經(jīng)搞不清楚了,接下來(lái)我們回頭看托管方法,看能不能繼續(xù)下去。

3. 在托管層尋找突破口

高級(jí)調(diào)試就是這樣,一個(gè)方向走不通就需要在另一個(gè)方向上突破,接下來(lái)使用 !clrstack 觀察一下。

0:000> !clrstack
OS Thread Id: 0x52e8 (0)
Child SP       IP Call Site
0115af50 775c2aac [GCFrame: 0115af50] 
0115afac 775c2aac [StubDispatchFrame: 0115afac]xxx.GetListDrawerType(System.String)
0115b02c 28a3a949 xxx.PluginInvoker.InvokeMothod[[System.__Canon, mscorlib]](System.String, System.Object[])
0115b0b0 28a3a8bd xxx.xxx.OnFinishSizeCheck(Int64)
...

從調(diào)用棧來(lái)看,貌似是用反射來(lái)實(shí)現(xiàn)功能增強(qiáng),不管怎么說(shuō)先看下xxxCheck 方法干了什么?簡(jiǎn)化后的代碼如下:

public string OnFinishSizeCheck(long uuid)
{
    return PluginInvoker.InvokeMothod<string>("xxxCheck", new object[1] { uuid });
}

public static T InvokeMothod<T>(string methodName, params object[] args)
{
    IPluginInvoker pluginInvoker = GetPluginInvoker();

    return (T)pluginInvoker.InvokeMothod(methodName, args);
}

從代碼上可以看到原來(lái)是使用 (T)pluginInvoker.InvokeMothod(methodName, args); 實(shí)現(xiàn)的接口調(diào)用,在coreclr層面也能觀察得到,找到對(duì)象 1f4222c0 之后按圖索驥即可。

0:000> !do 1f4222c0
Name:        xxx.xxx.BusinessAppDomainInvoker
MethodTable: 0c73a144
EEClass:     0c6d6f0c
Size:        12(0xc) bytes
File:        E:\xxx\xxx.dll
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
0c73a4e8  400000a        4 ....AppDomainManager  0 instance 1f42236c appDomainManager
0c73a2dc  4000009       18 ..., xxx]]  0   static 1f422214 lazy

0:000> !dumpmt -md 0c73a144
EEClass:         0c6d6f0c
Module:          0c7383dc
Name:            xxx.xxx.BusinessAppDomainInvoker
mdToken:         02000006
File:            E:\xxx\xxx.dll
BaseSize:        0xc
ComponentSize:   0x0
Slots in VTable: 10
Number of IFaces in IFaceMap: 1
--------------------------------------
MethodDesc Table
   Entry MethodDe    JIT Name
   ...
0c6c3400 0c73a110    JIT xxx.xxx.InvokeMothod(System.String, System.Object[])

0:000> !do  poi(0c73a144+0x24)
Name:        xxx.IPluginInvoker
MethodTable: 0c739f30
EEClass:     0c6d6d34
Size:        0(0x0) bytes
File:        E:\xxx\xxx.dll
Fields:
None
ThinLock owner 1 (18998d50), Recursive 0

對(duì)比那個(gè) token=30000h 發(fā)現(xiàn)什么地方都沒(méi)有問(wèn)題,奇葩的就是一個(gè)簡(jiǎn)單接口調(diào)用就出現(xiàn)了問(wèn)題,仔細(xì)觀察代碼之后發(fā)現(xiàn)了兩個(gè)和別人不一樣的地方。

4. 與眾不同的地方在哪里

第一個(gè)是他的程序是多 AppDomain 的,可以用 !dumpdomain 觀察。

0:000> !dumpdomain
--------------------------------------
System Domain:      55a6caa0
...
--------------------------------------
Shared Domain:      55a6c750
LowFrequencyHeap:   55a6cdc4
Stage:              OPEN
--------------------------------------
Domain 1:           18b04690
LowFrequencyHeap:   18b04afc
Name:               DefaultDomain
--------------------------------------
Domain 2:           18c361f0
LowFrequencyHeap:   18c3665c
...

第二個(gè)是我發(fā)現(xiàn)托管調(diào)用棧上還有很多 托管C++,這種混合編程真的是無(wú)語(yǔ)了。

到這里我想到了三個(gè)辦法:

1)如果可以先把接口方法預(yù)熱,clr會(huì)直接把方法入口塞到匯編里,就不會(huì)再走clr底層邏輯了。

2)能否將 托管C++ 和 C# 隔離,不要混合編程。

3)重點(diǎn)觀察下多Domain下這個(gè)托管調(diào)用是不是有什么問(wèn)題。

三、總結(jié)

這種 多domain + 托管C++混合C# 編程,真出問(wèn)題了基本上就是無(wú)解,一般人hold不住,無(wú)語(yǔ)了。

責(zé)任編輯:武曉燕 來(lái)源: 一線碼農(nóng)聊技術(shù)
相關(guān)推薦

2024-12-27 13:31:18

.NETdump調(diào)試

2023-06-26 00:12:46

2024-03-28 12:56:36

2023-03-26 20:24:50

ERP網(wǎng)站系統(tǒng)

2024-03-26 00:44:53

.NETCIM系統(tǒng)

2024-07-12 11:20:34

.NET崩潰視覺(jué)程序

2022-10-25 14:17:01

.NET代碼程序

2023-06-29 17:55:00

.NET日志WinDbg

2024-07-09 11:51:20

Windows線程池源碼

2024-06-13 17:09:55

2024-06-04 10:54:34

.NET代碼程序

2023-09-27 07:23:10

.NET監(jiān)控軟件

2023-05-15 11:15:50

.NET門診語(yǔ)句

2023-10-07 13:28:53

.NET軟件賬本

2022-10-09 10:47:37

NET視覺(jué)軟件

2023-04-06 10:52:18

2024-08-27 13:08:50

2022-10-13 18:40:05

.NETOA后端

2023-07-06 10:11:38

.NET模式dump

2024-05-20 09:39:02

.NETurl線程池
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)