MapViewOfFile引起的问题。。。

最近在fix bug的时候,遇到一个由于MapViewOfFile引起的问题。在此把分析的思路记下来。

先介绍一下背景。

项目里面有一个component叫做Message。功能分为两方面:message writer和message reader。项目有多个UI进程。需要支持每个进程写消息,同时也需要在每个进程里面有个mini message viewer显示消息。另外,在一个叫做console manager的进程里有一个Message Viewer。

现在这个message writer被实现为一个singleton COM EXE。如果哪个进程需要写消息,可以创建instance,然后通过Interface来写消息。message reader是一个普通的COM object。message viewer会create一个instance,然后定期来读消息,显示在UI上。

从上面分析我们可以看出,message是跨进程share的。该项目是通过MMF来达到该目的的。message writer会create一块memory mapping file,然后在该块file上面记录信息。message reader会打开该块MMF,然后从其中读取信息。

OK。回到正题。

现在是这样的。用户在其他一个进程操作的时候,突然,console manager 崩溃了。于是,捕捉dump,看一下出问题的call stack.

0:000> kbn
 # ChildEBP RetAddr  Args to Child             
00 0012e23c 7c90df4a 7c864742 00000002 0012e3a8 ntdll!KiFastSystemCallRet
01 0012e240 7c864742 00000002 0012e3a8 00000001 ntdll!ZwWaitForMultipleObjects+0xc
02 0012e578 7c843892 0012e5a0 7c839b21 0012e5a8 kernel32!UnhandledExceptionFilter+0x8b9
03 0012e580 7c839b21 0012e5a8 00000000 0012e5a8 kernel32!BaseProcessStart+0x39
04 0012e5a8 7c9032a8 0012e694 0012ffe0 0012e6b0 kernel32!_except_handler3+0x61
05 0012e5cc 7c90327a 0012e694 0012ffe0 0012e6b0 ntdll!ExecuteHandler2+0x26
06 0012e67c 7c90e48a 00000000 0012e6b0 0012e694 ntdll!ExecuteHandler+0x24
07 0012e67c 77135720 00000000 0012e6b0 0012e694 ntdll!KiUserExceptionDispatcher+0xe
08 0012e9a0 4d954ca4 00a90060 0012ea48 0012e9c8 oleaut32!SystemTimeToVariantTime+0xb
09 0012e9d0 4d954c64 00a90060 0012ea48 0012ea48 MsgMMFReader!ATL::AtlConvertSystemTimeToVariantTime+0x34
0a 0012e9e4 4d954c33 00a90060 0012ea48 0012ea04 MsgMMFReader!ATL::COleDateTime::ConvertSystemTimeToVariantTime+0x14
0b 0012e9f4 4d954c14 00a90060 0012ea48 0012eaac MsgMMFReader!ATL::COleDateTime::operator=+0x13
0c 0012ea04 4d94a41e 00a90060 49595134 0012ebbc MsgMMFReader!ATL::COleDateTime::COleDateTime
0d 0012eaac 4d949a34 04dbb6e8 00005112 05472ca3 MsgMMFReader!CMsgMMFReaderBase::PopulateSafeArrayWithLogRecordInfo+0x1be
0e 0012eacc 4d94afc2 000050dd 00005112 49595160 MsgMMFReader!CMsgMMFReaderBase::ReadMessageFromMMF+0xb4
0f 0012eaf8 4d94bcd5 000050dd 00005112 49595008 MsgMMFReader!MsgMMFReaderBase::LockAndReadMMFDetails+0x52
10 0012eb90 77520c7a 04dbb6e8 00000001 0651eae4 MsgMMFReader!CSlbMsgMMFReaderBase::GetMessages+0x475
11 0012ebc8 77587e9e 4d94b860 04dbb6e8 0012ecfc ole32!CallFrame::Invoke+0x54
12 0012ec10 775881c2 05efd2d8 0012ed4c 0012ecd8 ole32!CCtxChnl::SyncInvoke2+0x68
13 0012ecc4 775899d0 001736c0 001a1f48 0012edd0 ole32!CCtxChnl::SendReceive2+0x201

UnhandledExceptionFilter很眼熟。于是看一下exception context

0:000> dd 0012e5a0
0012e5a0  0012e694 0012e6b0 0012e5cc 7c9032a8
0012e5b0  0012e694 0012ffe0 0012e6b0 0012e668
0012e5c0  0012eaa0 7c9032bc 0012ffe0 0012e67c
0012e5d0  7c90327a 0012e694 0012ffe0 0012e6b0
0012e5e0  0012e668 7c839ac0 00000001 0012e694
0012e5f0  0012ffe0 7c92aa0f 0012e694 0012ffe0
0012e600  0012e6b0 0012e668 7c839ac0 0012ebbc
0012e610  0012e694 05d7dc0c 00000001 7c9100b8
0:000> .cxr 0012e6b0
eax=00a90060 ebx=05d7db80 ecx=00000000 edx=0012ea48 esi=05d7dc0c edi=0012ebbc
eip=77135720 esp=0012e97c ebp=0012e9a0 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010212
oleaut32!SystemTimeToVariantTime+0xb:
77135720 668b08          mov     cx,word ptr [eax]        ds:0023:00a90060=01b2
0:000> .exr 0012e694
ExceptionAddress: 77135720 (oleaut32!SystemTimeToVariantTime+0x0000000b)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000000
   Parameter[1]: 00a90060
Attempt to read from address 00a90060
从上面来看,system在试图访问 00a90060的时候出现了AV。ok,看一下00a90060的属性。

0:000> !address 00a90060
    00a90000 : 00a90000 - 00001000
                    Type     00040000 MEM_MAPPED
                    Protect  00000002 PAGE_READONLY
                    State    00001000 MEM_COMMIT
                    Usage    RegionUsageIsVAD

应该没有问题啊。从上面我们可以看出,该处内存属于memory mapping file,并且是read only属性。

奇怪。

思考一段时间之后,我们决定看一下该内存块存储的是不是我们想要的东西。

0:000> dt SYSTEMTIME 00a90060
MsgMMFReader!SYSTEMTIME
   +0x000 wYear            : 0x1b2
   +0x002 wMonth           : 0
   +0x004 wDayOfWeek       : 0
   +0x006 wDay             : 0
   +0x008 wHour            : 0x2fdc
   +0x00a wMinute          : 0xb44f
   +0x00c wSecond          : 0x350
   +0x00e wMilliseconds    : 0

这个地方肯定有问题。wYear 0x1b2 = 434, wMonth = 0。这个时间肯定是不对的。

于是,我们分析是如何获得该内存地址的。经过分析源代码后,我们发现该处地址是通过offset取得的。难道该offset算的不对?有没有可能越界访问?于是,我们取到了基地址,并看一下该基地址的相关信息。

0:000> !address 0x00a8fff8
    00a80000 : 00a80000 - 00010000
                    Type     00040000 MEM_MAPPED
                    Protect  00000002 PAGE_READONLY
                    State    00001000 MEM_COMMIT
                    Usage    RegionUsageIsVAD

该基地址是0x00a8fff8。同样也是memory mapping file。

但是,我们注意到一个严重的问题:该基地址所属的内存块从0x00a80000开始,size是0x10000。所以,它的最后一块地址是0x00a8ffff。而我们现在试图访问0x00a90060。

典型的访问越界。只不过00a80000后面紧跟着另外一块commit过的内存块,所以才到了后期才出的av。如果是一块reserved的内存快,估计一开始就av了。

接下来,我们就要分析,访问为什么会越界?看了看控制访问的地方,逻辑都很正确。于是,我们决定看看memory mapping file是如何map的。下面是代码。

1m_baseLogMMF = MapViewOfFile(m_hLogMMFMapping,
2 FILE_MAP_READ,
3 0,
4 m_offsetToStartMapView,
5 m_numberOfBytesToMap);
6 if(m_baseLogMMF == NULL
7 && ERROR_ACCESS_DENIED == ::GetLastError())
8 {
9 //it's because the file size is smaller than what we want. so we only map what the file has.
10 m_baseLogMMF = ::MapViewOfFile(
11 m_hLogMMFMapping, // Handle to File Mapping object from CreateFileMapping()
12 FILE_MAP_READ, // FILE_MAP_ALL_ACCESS
13 0, // High order offset (32 bytes) in to start View.
14 m_offsetToStartMapView, // Low order offset (32 bytes) in to start View.
15 0);// read to file end
16 }
17
大概猜到了root cause。第一次map的时候失败了,然后第二次map的时候直接map到文件末尾。但是,
m_numberOfBytesToMap并没有更新。所以后面会越界访问。

问题似乎清楚了。但这并不是真正的root cause。
posted @ 2011-04-25 12:05  relianceslee  阅读(5031)  评论(0编辑  收藏  举报