有个程序总是在windows 2003 server 异常退出. 并且, 查看调用栈也肯奇怪, 应该是很正常的调用. 怀疑是堆溢出.
开启heap trace :
C:\Program Files\Debugging Tools for Windows (x86)>gflags -i app.exe +ust +hpa
发现在Font.ToLogFont函数遇到: {"Attempted to read or write protected memory. This is often an indication that other memory is corrupt."}
如下图:
用Windbg打开exe后发现:
(12e8.13e4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=07362e9c ebx=00000001 ecx=00000003 edx=4dd77680 esi=07362f4c edi=09b03000
eip=4ddccc1c esp=0012e894 ebp=0012e8e8 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
gdiplus!GpFont::GetLogFontW+0x12d:
4ddccc1c f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
汇编代码:
4ddccc11 8b7004 mov esi,dword ptr [eax+4]
4ddccc14 6a10 push 10h
4ddccc16 03f0 add esi,eax
4ddccc18 83c71c add edi,1Ch
4ddccc1b 59 pop ecx
4ddccc1c f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
重新运行程序,在4ddccc1b处打断点,
0:007> bp 4ddccc1b
0:007> g
Breakpoint 0 hit
eax=07362e9c ebx=00000001 ecx=5d5be675 edx=4dd77680 esi=07362f18 edi=080b2fdc
eip=4ddccc1b esp=0012e890 ebp=0012e8e8 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
gdiplus!GpFont::GetLogFontW+0x12c:
4ddccc1b 59 pop ecx
查看edi所在堆还有多少可写空间,
0:000> !heap -p -a edi
address 080b2fdc found in
_DPH_HEAP_ROOT @ 151000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
9bf6380: 80b2fc0 3c - 80b2000 2000
7c83d7f0 ntdll!RtlAllocateHeap+0x00000e9f
776bcfde ole32!CRetailMalloc_Alloc+0x00000016
776bcf4b ole32!CoTaskMemAlloc+0x00000013
79ac4920 mscorlib_ni+0x00244920
79aca07b mscorlib_ni+0x0024a07b
79ab516a mscorlib_ni+0x0023516a
7b1effc4 System_Drawing_ni+0x0004ffc4
7b1e46c0 System_Drawing_ni+0x000446c0
7b1e452d System_Drawing_ni+0x0004452d
0:000> ? 80b2fc0+ 0x3c - edi
Evaluate expression: 32 = 00000020
剩余32字节.
而ecx指示需要写入0x10 * 4=0x40字节:
0:000> p
eax=07362e9c ebx=00000001 ecx=00000010 edx=4dd77680 esi=07362f18 edi=080b2fdc
eip=4ddccc1c esp=0012e894 ebp=0012e8e8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
gdiplus!GpFont::GetLogFontW+0x12d:
4ddccc1c f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
显然不够用, 执行后抛出AV:
(6d8.1310): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=07362e9c ebx=00000001 ecx=00000007 edx=4dd77680 esi=07362f3c edi=080b3000
eip=4ddccc1c esp=0012e894 ebp=0012e8e8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
gdiplus!GpFont::GetLogFontW+0x12d:
4ddccc1c f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
0:000> ?(3c - 20)/4
Evaluate expression: 7 = 00000007
ecx 还剩7个DWORD写不下.
原因是什么??
LOGFONT在C#中定义是:
[StructLayout(LayoutKind.Sequential)] private class LOGFONT { public int lfHeight = 0; public int lfWidth = 0; public int lfEscapement = 0; public int lfOrientation = 0; public int lfWeight = 0; public byte lfItalic = 0; public byte lfUnderline = 0; public byte lfStrikeOut = 0; public byte lfCharSet = 0; public byte lfOutPrecision = 0; public byte lfClipPrecision = 0; public byte lfQuality = 0; public byte lfPitchAndFamily = 0; [MarshalAs(UnmanagedType.ByValTStr, SizeConst=32) ] public string lfFaceName = ""; }
WinGDI.h定义:
/* Logical Font */ #define LF_FACESIZE 32 typedef struct tagLOGFONTA { LONG lfHeight; LONG lfWidth; LONG lfEscapement; LONG lfOrientation; LONG lfWeight; BYTE lfItalic; BYTE lfUnderline; BYTE lfStrikeOut; BYTE lfCharSet; BYTE lfOutPrecision; BYTE lfClipPrecision; BYTE lfQuality; BYTE lfPitchAndFamily; CHAR lfFaceName[LF_FACESIZE]; } LOGFONTA, *PLOGFONTA, NEAR *NPLOGFONTA, FAR *LPLOGFONTA;
看上去没有任何问题啊!!
但是 gdiplus并没有按照CHAR来处理传入进去的字符串, 相反, 是按照WCHAR来处理的.
WinGDI.h对于宽字符的LOGFONT定义是:
typedef struct tagLOGFONTW { LONG lfHeight; LONG lfWidth; LONG lfEscapement; LONG lfOrientation; LONG lfWeight; BYTE lfItalic; BYTE lfUnderline; BYTE lfStrikeOut; BYTE lfCharSet; BYTE lfOutPrecision; BYTE lfClipPrecision; BYTE lfQuality; BYTE lfPitchAndFamily; WCHAR lfFaceName[LF_FACESIZE]; } LOGFONTW, *PLOGFONTW, NEAR *NPLOGFONTW, FAR *LPLOGFONTW;
因此是C#中的定义lfFaceName长度只有一半!
解决方法就是C#中定义 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]