从卦中可以清晰的看到错误类型: Error type: HEAP_FAILURE_BLOCK_NOT_BUSY
,这是经典的 Double Free
,也就是上面的 原因1,接下来我们就要寻找代码源头了 。。。
2. 是谁的代码引发的从线程栈上看,底层的方法区都是十六进制,这表示当前是托管方法,这就好办了,我们用 !clrstack
看看托管代码是什么?
0:120> !clrstackOS Thread Id: 0x4d54 (120)Child SPIP Call Site000000003103cb88 00007ffbad9b0544 [InlinedCallFrame: 000000003103cb88] Microsoft.Win32.Win32Native.LocalFree(IntPtr)000000003103cb88 00007ffb66fac78f [InlinedCallFrame: 000000003103cb88] Microsoft.Win32.Win32Native.LocalFree(IntPtr)000000003103cb60 00007ffb66fac78f DomainNeutralILStubClass.IL_STUB_PInvoke(IntPtr)000000003103cc10 00007ffb66f273a4 System.Runtime.InteropServices.Marshal.FreeHGlobal(IntPtr) [f:\dd\ndp\clr\src\BCL\system\runtime\interopservices\marshal.cs @ 1212]000000003103cc50 00007ffb185c4fde xxxx.StructToBytes(System.Object)000000003103ced0 00007ffb185ec6b1 xxx.SendDoseProject(System.String)...
从卦中可以清晰的看到是托管方法 StructToBytes()
引发的,接下来导出这个方法的源码,截图如下:
文章插图
从方法逻辑看,这位朋友用了
Marshal
做了互操作,为了能够进一步分析 , 需要找到 localResource
堆块句柄,使用 !clrstack -l
显示方法栈参数 。0:120> !clrstack -lOS Thread Id: 0x4d54 (120)...000000003103cca0 00007ffb185c4fa1 xxx.StructToBytes(System.Object)LOCALS:0x000000003103cd0c = 0x000000000000018f0x000000003103ccf8 = 0x00000000030844200x000000003103ccf0 = 0x00000000030844200x000000003103cce8 = 0x00000000000000000x000000003103cce0 = 0x0000000000000000...
经过对比,发现并没有显示 localResource
值,这就很尴尬了 。。。一般在 dump 中 IntPtr
类型是显示不出来的,遇到好几次了 , 比较闹心 。。。既然显示不出来堆块句柄值 。。。那怎么办呢? 天要绝人之路吗?3. 绝处逢生既然托管层找不到堆块句柄,那就到非托管层去找,比如这里的
KERNELBASE!LocalFree+0x2f
函数 , msdn 上的定义如下:HLOCAL LocalFree([in] _Frees_ptr_opt_ HLOCAL hMem);
那如何找到这个 hMem 值呢? 在 x86 程序中可以直接用 kb
就能提取出来,但在 x64
下是无效的,因为它是用寄存器来传递方法参数 , 此时的寄存器值已经刷新到了 ntdll!NtWaitForMultipleObjects+0x14
上,比如下面的 rcx 肯定不是 hMem
值 。0:120> rrax=000000000000005b rbx=0000000000005b08 rcx=0000000000000002rdx=000000003103b690 rsi=0000000000000002 rdi=0000000000000000rip=00007ffbad9b0544 rsp=000000003103b658 rbp=0000000000001da4 r8=0000000000001000r9=0101010101010101 r10=0000000000000000r11=0000000000000246 r12=0000000000000000 r13=000000003103c930r14=0000000000001f98 r15=0000000000000000iopl=0nv up ei pl zr na po nccs=0033ss=002bds=002bes=002bfs=0053gs=002befl=00000246ntdll!NtWaitForMultipleObjects+0x14:00007ffb`ad9b0544 c3ret
怎么办呢?其实还有一条路,就是观察 KERNELBASE!LocalFree+0x2f
方法的汇编代码 , 看看它有没有将 rcx 临时性的存到 线程栈 上 。0:120> u KERNELBASE!LocalFreeKERNELBASE!LocalFree:00007ffb`aa388290 48895c2410movqword ptr [rsp+10h],rbx00007ffb`aa388295 4889742418movqword ptr [rsp+18h],rsi00007ffb`aa38829a 48894c2408movqword ptr [rsp+8],rcx00007ffb`aa38829f 57pushrdi00007ffb`aa3882a0 4883ec30subrsp,30h00007ffb`aa3882a4 488bd9movrbx,rcx00007ffb`aa3882a7 f6c308testbl,800007ffb`aa3882aa 753fjneKERNELBASE!LocalFree+0x5b (00007ffb`aa3882eb)
很开心的看到,当前的 rcx 存到了 rsp+8
位置上,那如何拿到 rsp 呢? 可以用 k 提取父函数 mscorlib_ni+0x63c78f
中的 Child-SP
值 。0:120> k # Child-SPRetAddrCall Site ...0e 00000000`3103ca80 00007ffb`aa3882bfntdll!RtlFreeHeap+0x966e00f 00000000`3103cb20 00007ffb`66fac78fKERNELBASE!LocalFree+0x2f10 00000000`3103cb60 00007ffb`66f273a4mscorlib_ni+0x63c78f...
因为这个 Child-SP
是 call 之前的 sp, 汇编中的 sp 是 call 之后的 , 所以相差一个 retaddr
指针单元,所以计算方法是: ChildSp- 0x8 + 0x8
就是 堆块句柄 。0:120> dp 00000000`3103cb60-0x8+0x8 L100000000`3103cb6000000000`2c873720
上面的 000000002c873720
就是堆块句柄 , 接下来用命令 !heap -x 000000002c873720
观察堆块情况 。0:120> !heap -x 000000002c873720EntryUserHeapSegmentSizePrevSizeUnusedFlags-------------------------------------------------------------------------------------------------------------000000002c873710000000002c8737200000000000c40000000000002c8703c030-0LFH;free
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 一次 Redis 事务使用不当引发的生产事故
- .NET Core C#系列之XiaoFeng.Data.IQueryableX ORM框架
- wifi万能钥匙电脑版怎么用(联想笔记本连wifi操作)
- 原神清籁逐雷记纸垂怎么玩
- 哈利波特:魔法觉醒记忆碎片七巧板怎么解
- .NET性能系列文章一:.NET7的性能改进
- 微信我的收藏怎么删除(公安局能调取已删微信记录吗)
- 2 HTML躬行记——WebRTC基础实践
- 记一次 .NET 某娱乐聊天流平台 CPU 爆高分析
- MassTransit | .NET 分布式应用框架