提取共享内存的大小

Extracting shared memory's size

本文关键字:内存 共享 提取      更新时间:2023-10-16

我试图了解如何准确提取MapViewOfFile的返回缓冲区大小。我使用以下命令分配共享内存

hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, dwDataSize, strSharedMemoryName.c_str());

使用以下代码片段填充了它:

pBuffer = DynamicAPI::MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0, 0, dwDataSize);
if (nullptr == pBuffer || GetLastError() != 0)
{
    LOG_ERROR(L"Failed to MapViewOfFile: " << GetLastError());
    break;
}
// Copy buffer to the shared memory
::CopyMemory(pBuffer, pData, dwDataSize);
然后,在

其他地方,尝试重新打开该共享内存并读取整个缓冲区:

HANDLE hSharedMemory = OpenFileMapping(FILE_MAP_READ, FALSE, m_strSharedName.c_str());
    if (nullptr == hSharedMemory)
    {
        return false;
    }
    LPVOID pData = nullptr;
    if (nullptr == (pData = MapViewOfFile(hSharedMemory, FILE_MAP_READ, 0, 0, 0)))
    {
        LOG_ERROR(L"Failed to MapViewOfFile");
        return false;
    }

我的下一行将是

std::string strData = pData; // use std::string::assign

但是,我不知道pData有多大,一种选择是在缓冲区中发送整体大小,但是MSDN表示VirtualQueryEx能够做这样的事情。

我尝试执行以下代码片段:

MEMORY_BASIC_INFORMATION info;
SIZE_T szBufferSize = ::VirtualQueryEx(::GetCurrentProcess(), pData, &info, sizeof(info));

但是,如果我没记错的话,这给了我单个页面的大小,我如何利用它给我整体缓冲区的大小?

谢谢!

据我所知,没有办法检索现有文件映射或文件映射视图的大小。 您应该自己跟踪此信息。

MSDN表示VirtualQueryEx能够做这样的事情。

不,VirtualQueryEx 所能确定的只是为视图保留的页数。 这意味着结果始终向上舍入到页面大小。 此外,没有明确保证 MapViewOfFile 将仅保留映射文件所需的最小页数。 例如,它可能会选择将其四舍五入到分配粒度。

这实际上是可能的,但由于某种原因Microsoft没有记录它。NtQuerySection API从Windows NT的早期版本就存在,并且在Windows 10中仍然存在。

因此,以下是您的操作方法(显然需要您自己承担依赖未记录的内核 API 的风险):

HANDLE hFileMapping = ::OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, strSharedMemName);
_ASSERT(hFileMapping);
ULONGLONG uicbSharedMemSize = 0;
//Get the handle returned by the CreateFileMapping function
//Assuming the same process here...
HANDLE hDupH;
if((::DuplicateHandle(
    ::GetCurrentProcess(), hFileMapping, 
    ::GetCurrentProcess(), &hDupH,
    DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS, FALSE, 0)))
{
    hFileMapping = hDupH;
    enum SECTION_INFORMATION_CLASS{
        SectionBasicInformation,
        SectionImageInformation
    };
    typedef struct _SECTION_BASIC_INFORMATION {
      ULONG                   Unknown;
      ULONG                   SectionAttributes;
      LARGE_INTEGER           SectionSize;
    } SECTION_BASIC_INFORMATION, *PSECTION_BASIC_INFORMATION;
    static NTSTATUS
    (NTAPI
    *pfnNtQuerySection)(
      IN HANDLE               SectionHandle,
      IN SECTION_INFORMATION_CLASS InformationClass,
      OUT PVOID               InformationBuffer,
      IN ULONG                InformationBufferSize,
      OUT PULONG              ResultLength OPTIONAL ) = NULL;
    if(!pfnNtQuerySection)
        (FARPROC&)pfnNtQuerySection = ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), "NtQuerySection");
    if(pfnNtQuerySection)
    {
        SECTION_BASIC_INFORMATION sbi = {0};
        ULONG ucbRead = 0;
        NTSTATUS stat = pfnNtQuerySection(hFileMapping, SectionBasicInformation, &sbi, sizeof(sbi), &ucbRead);
        if(stat >= 0)
        {
            //The size returned will be rounded up to the page size (i.e. 4K in most cases)
            uicbSharedMemSize = sbi.SectionSize.QuadPart;
        }
    }
}

首先,SECTION_BASIC_INFORMATION的正确定义是:

typedef struct _SECTION_BASIC_INFORMATION
{
    PVOID BaseAddress;
    ULONG AllocationAttributes;
    LARGE_INTEGER MaximumSize;
} SECTION_BASIC_INFORMATION, *PSECTION_BASIC_INFORMATION;

所以旧的定义(第一个成员DWORD - 是错误的,不适用于 64 位代码)。

对于调用NtQuerySection,部分句柄必须具有SECTION_QUERY访问权限,否则将被STATUS_ACCESS_DENIED - so 行:

OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, strSharedMemName);

错的。一定是

OpenFileMapping(SECTION_QUERY|FILE_MAP_READ|FILE_MAP_WRITE, FALSE, strSharedMemName);

或任何访问,包括SECTION_QUERY

请注意,如果使用OpenFileMapping并将dwDesiredAccess设置为SECTION_QUERY未知原因OpenFileMapping,请将其更改为SECTION_MAP_READ - 是此 API 中的下一行代码。 if (dwDesiredAccess == SECTION_QUERY) dwDesiredAccess = SECTION_MAP_READ;所以需要添加一些对SECTION_QUERY的访问权限。当然,我们可以完全SECTION_QUERY一起使用ZwOpenSection

接下来 - 对于什么调用DuplicateHandle ?!?这在任务上下文调用中是绝对没有意义的。 我们通过OpenFileMappingWZwOpenSection得到了部分的句柄。

最后,对于ZwQuerySection来说GetModuleHandle+GetProcAddress是什么? 例如,我们如何在没有GetModuleHandle + GetProcAddress的情况下有趣地称呼OpenFileMappingWGetProcAddress如何调用? 以同样的方式,我们可以调用ZwQuerySection - 只需通过NTDLL.libntdll链接.dll NTDLL.lib存在于每个WDK Lib文件夹中。 所以最终代码必须是:

if (HANDLE hMap = OpenFileMappingW(SECTION_QUERY|SECTION_MAP_READ, FALSE, name))
{
    SECTION_BASIC_INFORMATION sbi;
    if (0 <= ZwQuerySection(hMap, SectionBasicInformation, &sbi, sizeof(sbi), 0))
    {
        DbgPrint("section size = %I64xn", sbi.Size.QuadPart);
    }
    CloseHandle(hMap);
}

HANDLE hMap;
if (0 <= ZwOpenSection(&hMap, SECTION_QUERY, &oa))
{
    SECTION_BASIC_INFORMATION sbi;
    if (0 <= ZwQuerySection(hMap, SectionBasicInformation, &sbi, sizeof(sbi), 0))
    {
        DbgPrint("section size = %I64xn", sbi.Size.QuadPart);
    }
    CloseHandle(hMap);
}