有個程式總是在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)]