共享内存操作函数使用及错误调试

  • Post author:
  • Post category:其他




共享内存操作


1、为指定的文件创建或者打开一个命名的或者非命名的文件映射对象。

HANDLE CreateFileMapping(

HANDLE hFile, // handle to file

LPSECURITY_ATTRIBUTESlpAttributes, // security

DWORD flProtect, // protection

DWORD dwMaximumSizeHigh, // high-order DWORD of size

DWORD dwMaximumSizeLow, // low-order DWORD of size

LPCTSTR lpName // object name

);



hFile:

指向创建映射对象的文件句柄。如果需要和物理文件关联,要确保物理文件创建的时候的访问模式和由

flProtect

参数指定的”保护标识”匹配,比如:物理文件只读, 内存映射需要读写就会发生错误。推荐使用独占方式创建。

如果

hFile

为INVALID_HANDLE_VALUE,需要在

dwMaximumSizeHigh和dwMaximumSizeLow

中指定映射对象的大小。在这种情况下,CreateFileMapping由操作系统分页文件而不是由一个文件系统中的一个命名的文件创建一个指定大小的文件对象。文件映射对象可以同过复制,继承或者命名得到。



lpAttributes:

指向一个SECURITY_ATTRIBUTES结构体,决定是否被子进程继承。如果为NULL,句柄不能被继承,一般设置成NULL。



flProtect:

当文件映射的时候,需要此参数设置保护标识。



dwMaximumSizeHigh:



文件映射对象的最大值的高位(DWORD)



dwMaximumSizeLow



文件映射对象的最大值的低位(DWORD),如果

dwMaximumSizeHigh

为0,文件映射对象的最大值为由

hFile

标识的文件的大小。



lpName



指定文件映射对象的名称。

如果这个参数匹配一个存在的命名映射对象,这个函数用flProtect的访问权限请求访问这个存在的映射对象。

如果参数为NULL,创建一个未命名的映射对象。

如果参数名称匹配存在的事件、信号量、互斥、等待时间或者工作对象,函数返回失败,GetLastError函数会返回ERROR_INVALID_HANDLE。因为这些对象共用相同的命名空间。


返回值

如果函数返回成功,,返回值为文件对象句柄;如果这个对象在调用此函数的时候已经存在,函数返回这个存在的句柄(大小为现在的大小),GetLastError函数会返回ERROR_ALREADY_EXISTS。


说明

在文件映射对象创建之后,文件大小不要超过文件映射对象的大小,并不是所有的文件内容适合共享。

如果应用指定的文件映射对象的大小超过实际的文件大小,硬盘上的文件增大去匹配指定的文件映射对象大小。如果文件不能增大,则会创建文件映射对象失败。GetLastError函数会返回ERROR_DISK_FULL。


CreateFileMapping返回句柄有所有对文件对象句柄访问权限。

hFileMap= CreateFileMapping(...); 

if (hFileMap != NULL&& GetLastError() == ERROR_ALREADY_EXISTS) 
{ 
   CloseHandle(hFileMap); 
   hFileMap = NULL; 
} 
return hFileMap; 



2、映射文件视图到调用进程的地址空间。

LPVOID MapViewOfFile(
 
HANDLE hFileMappingObject, // handle to file-mapping object
 
DWORD dwDesiredAccess, // access mode
 
DWORD dwFileOffsetHigh, // high-order DWORD of offset
 
DWORD dwFileOffsetLow, // low-order DWORD of offset
 
SIZE_T dwNumberOfBytesToMap // number of bytes to map
 
);



hFileMappingObject

一个打开的映射文件对象的句柄,这个句柄可以由CreateFileMapping和OpenFileMapping函数返回。



dwDesiredAccess

指定访问文件视图的类型:

FILE_MAP_WRITE

FILE_MAP_READ

FILE_MAP_ALL_ACCESS

FILE_MAP_COPY



dwFileOffsetHigh

指定开始映射文件偏移量的高位。



dwFileOffsetLow

指定开始映射文件偏移量的低位。



dwNumberOfBytesToMap

指定需要映射的文件的字节数量,如果dwNumberOfBytesToMap为0,映射整个的文件。


返回值

如果函数成功,返回值是映射视图的开始位置。

如果函数失败,返回值为NULL,可以通过调用GetLastError函数获得详细的错误信息。

映射一个文件,让其指定的文件部分在调用进程的地址空间可见。






3  将内存复制到所映射的物理文件上面


FlushViewOfFile 函数可以将内存里面的内容DUMP到物理磁盘上面.把文件映射视图中的修改的内容或全部写回到磁盘文件中

BOOL FlushViewOfFile(
  LPCVOID lpBaseAddress,       // 修改内容的起始地址
  DWORD dwNumberOfBytesToFlush // 修改的字节数目
);
函数执行成功返回非零。


4 卸载内存映射文件地址指针


UnmapViewOfFile函数就是卸载,删除文件的映射视图

BOOL UnmapViewOfFile(
  LPCVOID lpBaseAddress   // 映射视图起始地址
);
注意:
lpBaseAddress:映射视图起始地址,由 MapViewOfFile 函数 MapViewOfFileEx产生。 
返回值:
如果调用成功返回非零,并且所有指定地址内的脏页面会被写入硬盘。调用失败返回零。

7) 关闭内存映射文件

CloseHand



案例分析



转载自:



http://www.cnblogs.com/xiaxi/archive/2011/04/25/2027067.html






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的。下面是代码。


m_baseLogMMF = MapViewOfFile(m_hLogMMFMapping,
            FILE_MAP_READ,
            0,
            m_offsetToStartMapView,
            m_numberOfBytesToMap);
        if(m_baseLogMMF == NULL
            && ERROR_ACCESS_DENIED == ::GetLastError())
        {
            //it's because the file size is smaller than what we want. so we only map what the file has.
            m_baseLogMMF = ::MapViewOfFile(
                m_hLogMMFMapping, // Handle to File Mapping object from CreateFileMapping()
                FILE_MAP_READ, // FILE_MAP_ALL_ACCESS
                0,   // High order offset (32 bytes) in to start View.
                m_offsetToStartMapView,    // Low order offset (32 bytes) in to start View.
                0);// read to file end
        }

大概猜到了root cause。第一次map的时候失败了,然后第二次map的时候直接map到文件末尾。但是,


m_numberOfBytesToMap并没有更新。所以后面会越界访问。

问题似乎清楚了。但这并不是真正的root cause。

以下内容来自:

http://www.cnblogs.com/xiaxi/archive/2011/04/25/2027219.html

在知道为什么有越界访问之后,我们下一个问题是:为什么第一次调用mapviewoffile会失败?为什么第二次调用mapviewoffile会成功了?

查阅了msdn一下。如果在调用MapViewOfFile()的时候,dwNumberOfBytesToMap如果大于文件的size,那么该call就会失败,并且error code是access denied。

这个解释貌似和我们发生的很贴切。因为第一次size太大了,第一次就会失败。但是如果第二次我们指明map到文件末尾,那么就会成功。

但是,我们审阅了我们的代码后,觉得不会发生这样的情况。size是根据文件信息读出来的,因而不会有错。

那会是什么引起的了?

因为message会不断的增加,所以message writer会check当前的buffer有没有写完。如果快写完了,那么就flush当前的view,close当前的handle。然后调用createmappingfile(),创建一个更大的MMF。同时为给这块shared memory取一个新的名字。

会不会是message reader这边用错了handle了?

看一下handle的information。

0:000> !handle 0x00000cb0  f

Handle 00000cb0

Type             Section

Attributes       0

GrantedAccess    0x4:

None

MapRead

HandleCount      2

PointerCount     4

Name             \BaseNamedObjects\

3835699D-D3CE-4847-BFCA-A50791DF408D_Log_FileMap


10


最后的数字,10,是writer新取的instance number。这个数字很蹊跷,因为应该不会这么大。

看一下实际上应该是多少。

0:000> dt _HeaderLogMessageInfoStruct 0x04dbb6e8+0x05c

MsgMMFReader!_HeaderLogMessageInfoStruct

+0x000 NumberOfRecords  : 0x5112

+0x004 WriteNewLogRecordOffset : 0x260070

+0x008 WriteNewRecordIntoBlobOffset : 0x284719

+0x00c SizeOfLogMMFFile : 0x2a0000

+0x010 SizeOfDataMMFFile : 0x2a0000



+0x014 CurrentLogMappingNumberInWriter : 2



+0x018 CurrentBlobMappingNumberInWriter : 1

+0x01c UnreadInfoMsgCnt : 0xfffffe1d

+0x020 UnreadWarningMsgCnt : 0x122b

+0x024 UnreadErrorMsgCnt : 0x21f

+0x028 TotalInfoMsgsCount : 5469

+0x02c TotalWarningMsgsCount : 4651

+0x030 TotalErrorMsgsCount : 853

+0x034 InformationMsgsViewed : 0

+0x035 WarningMsgsViewed : 0

+0x036 ErrorMsgsViewed  : 0

+0x038 FirstRecIn24HourWindow : 0x2636

+0x03c FileCreationTimeStamp : _SYSTEMTIME

+0x04c LastTouchTimeStamp : _SYSTEMTIME

我们看到,Writer那边的instance number才是2。而reader那边居然用的是10。这才是导致MapViewOfFIle失败的真正原因。

又重新看了遍code。发现代码那边有check number change scenario。但是,代码是这么写的:

if (m_currentLogMappingNumber < m_msgLogHeaderStruct.CurrentLogMappingNumberInWriter){            // remap the mmf}

这个地方用的是比较大小来决定需不需要重新mapviewoffile。这个才是真真正正的root cause! 应该用“!=”来判断!

把这个地方改为”!=”后,这个问题再也没有重现过。



版权声明:本文为bluewind23原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。