共享內存操作函數使用及錯誤調試

共享內存操作

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 0012e5a8kernel32!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 0012e6b00012e5cc 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_FileMap10

最後的數字,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! 應該用“!=”來判斷!

把這個地方改爲"!="後,這個問題再也沒有重現過。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章