blob: eef2704e462d6117abbbaac21322d5c1d0bf85b8 [file]
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
/*++
Module Name:
map.cpp
Abstract:
Implementation of file mapping API.
--*/
#include "pal/palinternal.h"
#include "pal/dbgmsg.h"
#include "pal/init.h"
#include "pal/critsect.h"
#include "pal/virtual.h"
#include "common.h"
#include "pal/map.hpp"
#include "pal/thread.hpp"
#include "pal/file.hpp"
#include "pal/malloc.hpp"
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>
#include "rt/ntimage.h"
#include <pal_endian.h>
using namespace CorUnix;
SET_DEFAULT_DEBUG_CHANNEL(VIRTUAL);
//
// The mapping critical section guards access to the list
// of currently mapped views. If a thread needs to access
// both this critical section and the data for an object
// it must acquire the object data first. That is, a thread
// cannot acquire any other locks after taking hold of
// this critical section.
//
CRITICAL_SECTION mapping_critsec PAL_GLOBAL;
LIST_ENTRY MappedViewList PAL_GLOBAL;
static PAL_ERROR MAPGrowLocalFile(INT, UINT);
static PMAPPED_VIEW_LIST MAPGetViewForAddress( LPCVOID );
static PAL_ERROR MAPDesiredAccessAllowed( DWORD, DWORD, DWORD );
static INT MAPProtectionToFileOpenFlags( DWORD );
static BOOL MAPIsRequestPermissible( DWORD, CFileProcessLocalData * );
static BOOL MAPContainsInvalidFlags( DWORD );
static DWORD MAPConvertProtectToAccess( DWORD );
static INT MAPFileMapToMmapFlags( DWORD );
static DWORD MAPMmapProtToAccessFlags( int prot );
#if ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS
static NativeMapHolder * NewNativeMapHolder(CPalThread *pThread, LPVOID address, SIZE_T size,
SIZE_T offset, long init_ref_count);
static LONG NativeMapHolderAddRef(NativeMapHolder * thisPMH);
static LONG NativeMapHolderRelease(CPalThread *pThread, NativeMapHolder * thisPMH);
static PMAPPED_VIEW_LIST FindSharedMappingReplacement(CPalThread *pThread, dev_t deviceNum, ino_t inodeNum,
SIZE_T size, SIZE_T offset);
#endif
static PAL_ERROR
MAPRecordMapping(
IPalObject *pMappingObject,
void *pPEBaseAddress,
void *addr,
size_t len,
int prot
);
static PAL_ERROR
MAPmmapAndRecord(
IPalObject *pMappingObject,
void *pPEBaseAddress,
void *addr,
size_t len,
int prot,
int flags,
int fd,
off_t offset,
LPVOID *ppvBaseAddress
);
#if !HAVE_MMAP_DEV_ZERO
/* We need MAP_ANON. However on some platforms like HP-UX, it is defined as MAP_ANONYMOUS */
#if !defined(MAP_ANON) && defined(MAP_ANONYMOUS)
#define MAP_ANON MAP_ANONYMOUS
#endif
#endif
void
FileMappingCleanupRoutine(
CPalThread *pThread,
IPalObject *pObjectToCleanup,
bool fShutdown,
bool fCleanupSharedState
);
PAL_ERROR
FileMappingInitializationRoutine(
CPalThread *pThread,
CObjectType *pObjectType,
void *pImmutableData,
void *pSharedData,
void *pProcessLocalData
);
CObjectType CorUnix::otFileMapping PAL_GLOBAL(
otiFileMapping,
FileMappingCleanupRoutine,
FileMappingInitializationRoutine,
sizeof(CFileMappingImmutableData),
sizeof(CFileMappingProcessLocalData),
0,
PAGE_READWRITE | PAGE_READONLY | PAGE_WRITECOPY,
CObjectType::SecuritySupported,
CObjectType::SecurityInfoNotPersisted,
CObjectType::ObjectCanHaveName,
CObjectType::LocalDuplicationOnly,
CObjectType::UnwaitableObject,
CObjectType::SignalingNotApplicable,
CObjectType::ThreadReleaseNotApplicable,
CObjectType::OwnershipNotApplicable
);
CAllowedObjectTypes aotFileMapping PAL_GLOBAL (otiFileMapping);
void
FileMappingCleanupRoutine(
CPalThread *pThread,
IPalObject *pObjectToCleanup,
bool fShutdown,
bool fCleanupSharedState
)
{
PAL_ERROR palError = NO_ERROR;
CFileMappingImmutableData *pImmutableData = NULL;
CFileMappingProcessLocalData *pLocalData = NULL;
IDataLock *pLocalDataLock = NULL;
bool fDataChanged = FALSE;
if (TRUE == fCleanupSharedState)
{
//
// If we created a temporary file to back this mapping we need
// to unlink it now
//
palError = pObjectToCleanup->GetImmutableData(
reinterpret_cast<void**>(&pImmutableData)
);
if (NO_ERROR != palError)
{
ASSERT("Unable to obtain immutable data for object to be reclaimed");
return;
}
if (pImmutableData->bPALCreatedTempFile)
{
unlink(pImmutableData->szFileName);
}
}
if (FALSE == fShutdown)
{
//
// We only need to close the object's descriptor if we're not
// shutting down
//
palError = pObjectToCleanup->GetProcessLocalData(
pThread,
WriteLock,
&pLocalDataLock,
reinterpret_cast<void**>(&pLocalData)
);
if (NO_ERROR != palError)
{
ASSERT("Unable to obtain process local data for object to be reclaimed");
return;
}
if (-1 != pLocalData->UnixFd)
{
close(pLocalData->UnixFd);
pLocalData->UnixFd = -1;
fDataChanged = TRUE;
}
pLocalDataLock->ReleaseLock(pThread, fDataChanged);
}
//
// Why don't we need to deal with any views that may have been created
// from this mapping? If the process is shutting down then there's nothing
// that we need to take care of, as the OS will remove the underlying
// mappings when the process goes away. If we're not shutting down then
// there's no way for a view to exist against this mapping, since each
// view holds a reference against the mapping object.
//
}
PAL_ERROR
FileMappingInitializationRoutine(
CPalThread *pThread,
CObjectType *pObjectType,
void *pvImmutableData,
void *pvSharedData,
void *pvProcessLocalData
)
{
PAL_ERROR palError = NO_ERROR;
CFileMappingImmutableData *pImmutableData =
reinterpret_cast<CFileMappingImmutableData *>(pvImmutableData);
CFileMappingProcessLocalData *pProcessLocalData =
reinterpret_cast<CFileMappingProcessLocalData *>(pvProcessLocalData);
pProcessLocalData->UnixFd = InternalOpen(
pImmutableData->szFileName,
MAPProtectionToFileOpenFlags(pImmutableData->flProtect)
);
if (-1 == pProcessLocalData->UnixFd)
{
palError = ERROR_INTERNAL_ERROR;
goto ExitFileMappingInitializationRoutine;
}
#if ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS
struct stat st;
if (0 == fstat(pProcessLocalData->UnixFd, &st))
{
pProcessLocalData->MappedFileDevNum = st.st_dev;
pProcessLocalData->MappedFileInodeNum = st.st_ino;
}
else
{
ERROR("Couldn't get inode info for fd=%d to be stored in mapping object\n", pProcessLocalData->UnixFd);
}
#endif
ExitFileMappingInitializationRoutine:
return palError;
}
/*++
Function:
CreateFileMappingA
Note:
File mapping are used to do inter-process communication.
See MSDN doc.
--*/
HANDLE
PALAPI
CreateFileMappingA(
IN HANDLE hFile,
IN LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
IN DWORD flProtect,
IN DWORD dwMaximumSizeHigh,
IN DWORD dwMaximumSizeLow,
IN LPCSTR lpName)
{
HANDLE hFileMapping = NULL;
CPalThread *pThread = NULL;
PAL_ERROR palError = NO_ERROR;
PERF_ENTRY(CreateFileMappingA);
ENTRY("CreateFileMappingA(hFile=%p, lpAttributes=%p, flProtect=%#x, "
"dwMaxSizeH=%d, dwMaxSizeL=%d, lpName=%p (%s))\n",
hFile, lpFileMappingAttributes, flProtect,
dwMaximumSizeHigh, dwMaximumSizeLow,
lpName?lpName:"NULL",
lpName?lpName:"NULL");
pThread = InternalGetCurrentThread();
if (lpName != nullptr)
{
ASSERT("lpName: Cross-process named objects are not supported in PAL");
palError = ERROR_NOT_SUPPORTED;
}
else
{
palError = InternalCreateFileMapping(
pThread,
hFile,
lpFileMappingAttributes,
flProtect,
dwMaximumSizeHigh,
dwMaximumSizeLow,
NULL,
&hFileMapping
);
}
//
// We always need to set last error, even on success:
// we need to protect ourselves from the situation
// where last error is set to ERROR_ALREADY_EXISTS on
// entry to the function
//
pThread->SetLastError(palError);
LOGEXIT( "CreateFileMappingA returns HANDLE %p. \n", hFileMapping );
PERF_EXIT(CreateFileMappingA);
return hFileMapping;
}
/*++
Function:
CreateFileMappingW
Note:
File mapping are used to do inter-process communication.
See MSDN doc.
--*/
HANDLE
PALAPI
CreateFileMappingW(
IN HANDLE hFile,
IN LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
IN DWORD flProtect,
IN DWORD dwMaximumSizeHigh,
IN DWORD dwMaximumSizeLow,
IN LPCWSTR lpName)
{
HANDLE hFileMapping = NULL;
CPalThread *pThread = NULL;
PAL_ERROR palError = NO_ERROR;
PERF_ENTRY(CreateFileMappingW);
ENTRY("CreateFileMappingW(hFile=%p, lpAttributes=%p, flProtect=%#x, "
"dwMaxSizeH=%u, dwMaxSizeL=%u, lpName=%p (%S))\n",
hFile, lpFileMappingAttributes, flProtect, dwMaximumSizeHigh,
dwMaximumSizeLow, lpName?lpName:W16_NULLSTRING, lpName?lpName:W16_NULLSTRING);
pThread = InternalGetCurrentThread();
palError = InternalCreateFileMapping(
pThread,
hFile,
lpFileMappingAttributes,
flProtect,
dwMaximumSizeHigh,
dwMaximumSizeLow,
lpName,
&hFileMapping
);
//
// We always need to set last error, even on success:
// we need to protect ourselves from the situation
// where last error is set to ERROR_ALREADY_EXISTS on
// entry to the function
//
pThread->SetLastError(palError);
LOGEXIT( "CreateFileMappingW returning %p .\n", hFileMapping );
PERF_EXIT(CreateFileMappingW);
return hFileMapping;
}
PAL_ERROR
CorUnix::InternalCreateFileMapping(
CPalThread *pThread,
HANDLE hFile,
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
LPCWSTR lpName,
HANDLE *phMapping
)
{
CObjectAttributes objectAttributes(lpName, lpFileMappingAttributes);
PAL_ERROR palError = NO_ERROR;
IPalObject *pMapping = NULL;
IPalObject *pRegisteredMapping = NULL;
CFileMappingProcessLocalData *pLocalData = NULL;
IDataLock *pLocalDataLock = NULL;
CFileMappingImmutableData *pImmutableData = NULL;
IPalObject *pFileObject = NULL;
CFileProcessLocalData *pFileLocalData = NULL;
IDataLock *pFileLocalDataLock = NULL;
struct stat UnixFileInformation;
INT UnixFd = -1;
BOOL bPALCreatedTempFile = FALSE;
UINT nFileSize = 0;
//
// Validate parameters
//
if (lpName != nullptr)
{
ASSERT("lpName: Cross-process named objects are not supported in PAL");
palError = ERROR_NOT_SUPPORTED;
goto ExitInternalCreateFileMapping;
}
if (0 != dwMaximumSizeHigh)
{
ASSERT("dwMaximumSizeHigh is always 0.\n");
palError = ERROR_INVALID_PARAMETER;
goto ExitInternalCreateFileMapping;
}
if (PAGE_READWRITE != flProtect
&& PAGE_READONLY != flProtect
&& PAGE_WRITECOPY != flProtect)
{
ASSERT( "invalid flProtect %#x, acceptable values are PAGE_READONLY "
"(%#x), PAGE_READWRITE (%#x) and PAGE_WRITECOPY (%#x).\n",
flProtect, PAGE_READONLY, PAGE_READWRITE, PAGE_WRITECOPY );
palError = ERROR_INVALID_PARAMETER;
goto ExitInternalCreateFileMapping;
}
if (hFile == INVALID_HANDLE_VALUE && 0 == dwMaximumSizeLow)
{
ERROR( "If hFile is INVALID_HANDLE_VALUE, then you must specify a size.\n" );
palError = ERROR_INVALID_PARAMETER;
goto ExitInternalCreateFileMapping;
}
if (hFile != INVALID_HANDLE_VALUE && NULL != lpName)
{
ASSERT( "If hFile is not -1, then lpName must be NULL.\n" );
palError = ERROR_INVALID_PARAMETER;
goto ExitInternalCreateFileMapping;
}
palError = g_pObjectManager->AllocateObject(
pThread,
&otFileMapping,
&objectAttributes,
&pMapping
);
if (NO_ERROR != palError)
{
goto ExitInternalCreateFileMapping;
}
palError = pMapping->GetImmutableData(reinterpret_cast<void**>(&pImmutableData));
if (NO_ERROR != palError)
{
goto ExitInternalCreateFileMapping;
}
if (hFile == INVALID_HANDLE_VALUE)
{
//
// Note: this path is what prevents us supporting the
// duplication of file mapping objects across processes, since
// there is no backing file that the other process can open. We can
// avoid this restriction by always using a temp backing file for
// anonymous mappings.
//
/* Anonymous mapped files. */
if (strcpy_s(pImmutableData->szFileName, sizeof(pImmutableData->szFileName), "/dev/zero") != SAFECRT_SUCCESS)
{
ERROR( "strcpy_s failed!\n" );
palError = ERROR_INTERNAL_ERROR;
goto ExitInternalCreateFileMapping;
}
#if HAVE_MMAP_DEV_ZERO
UnixFd = InternalOpen(pImmutableData->szFileName, O_RDWR);
if ( -1 == UnixFd )
{
ERROR( "Unable to open the file.\n");
palError = ERROR_INTERNAL_ERROR;
goto ExitInternalCreateFileMapping;
}
#else //!HAVE_MMAP_DEV_ZERO
UnixFd = -1; /* will pass MAP_ANON to mmap() instead */
#endif //!HAVE_MMAP_DEV_ZERO
}
else
{
if ( hFile != INVALID_HANDLE_VALUE )
{
palError = g_pObjectManager->ReferenceObjectByHandle(
pThread,
hFile,
&aotFile,
GENERIC_READ,
&pFileObject
);
if (NO_ERROR != palError)
{
ERROR("Unable to obtain file data.\n");
palError = ERROR_INVALID_PARAMETER;
goto ExitInternalCreateFileMapping;
}
palError = pFileObject->GetProcessLocalData(
pThread,
ReadLock,
&pFileLocalDataLock,
reinterpret_cast<void**>(&pFileLocalData)
);
if (NO_ERROR != palError)
{
goto ExitInternalCreateFileMapping;
}
/* We need to check to ensure flProtect jives with
the permission on the file handle */
if (!MAPIsRequestPermissible(flProtect, pFileLocalData))
{
ERROR("File handle does not have the correct "
"permissions to create mapping\n" );
palError = ERROR_ACCESS_DENIED;
if (NULL != pFileLocalDataLock)
{
pFileLocalDataLock->ReleaseLock(pThread, FALSE);
}
goto ExitInternalCreateFileMapping;
}
//
// TODO: technically, the file mapping object should hold
// a reference to the passed in file object. This implementation
// only keeps the underlying native file structure (i.e., what
// the duplicated descriptors point to) open. There may be a risk
// here pertaining to the file lock information that the PAL must
// maintain (e.g,. if the passed in handle is closed immediately
// after the file mapping is opened then the lock information will
// be released, since we're not doing anything to keep it alive
// here).
//
// Having a direct reference to the underlying file object adds
// some complication, especially in cross-process cases. We may
// want to consider adding a reference to the PAL's file lock
// information, though...
//
UnixFd = dup(pFileLocalData->unix_fd);
if (-1 == UnixFd)
{
ERROR( "Unable to duplicate the Unix file descriptor!\n" );
palError = ERROR_INTERNAL_ERROR;
if (NULL != pFileLocalDataLock)
{
pFileLocalDataLock->ReleaseLock(pThread, FALSE);
}
goto ExitInternalCreateFileMapping;
}
if (strcpy_s(pImmutableData->szFileName, sizeof(pImmutableData->szFileName), pFileLocalData->unix_filename) != SAFECRT_SUCCESS)
{
ERROR( "strcpy_s failed!\n" );
palError = ERROR_INTERNAL_ERROR;
if (NULL != pFileLocalDataLock)
{
pFileLocalDataLock->ReleaseLock(pThread, FALSE);
}
goto ExitInternalCreateFileMapping;
}
if (NULL != pFileLocalDataLock)
{
pFileLocalDataLock->ReleaseLock(pThread, FALSE);
}
}
else
{
ASSERT("should not get here\n");
palError = ERROR_INTERNAL_ERROR;
goto ExitInternalCreateFileMapping;
}
if (-1 == fstat(UnixFd, &UnixFileInformation))
{
ASSERT("fstat() failed for this reason %s.\n", strerror(errno));
palError = ERROR_INTERNAL_ERROR;
goto ExitInternalCreateFileMapping;
}
if ( 0 == UnixFileInformation.st_size &&
0 == dwMaximumSizeHigh && 0 == dwMaximumSizeLow )
{
ERROR( "The file cannot be a zero length file.\n" );
palError = ERROR_FILE_INVALID;
goto ExitInternalCreateFileMapping;
}
if ( INVALID_HANDLE_VALUE != hFile &&
dwMaximumSizeLow > (DWORD) UnixFileInformation.st_size &&
( PAGE_READONLY == flProtect || PAGE_WRITECOPY == flProtect ) )
{
/* In this situation, Windows returns an error, because the
permissions requested do not allow growing the file */
ERROR( "The file cannot be grown do to the map's permissions.\n" );
palError = ERROR_NOT_ENOUGH_MEMORY;
goto ExitInternalCreateFileMapping;
}
if ( (DWORD) UnixFileInformation.st_size < dwMaximumSizeLow )
{
TRACE( "Growing the size of file on disk to match requested size.\n" );
/* Need to grow the file on disk to match size. */
palError = MAPGrowLocalFile(UnixFd, dwMaximumSizeLow);
if (NO_ERROR != palError)
{
ERROR( "Unable to grow the file on disk.\n" );
goto ExitInternalCreateFileMapping;
}
}
}
nFileSize = ( 0 == dwMaximumSizeLow && 0 == dwMaximumSizeHigh ) ?
UnixFileInformation.st_size : dwMaximumSizeLow;
pImmutableData->MaxSize = nFileSize;
pImmutableData->flProtect = flProtect;
pImmutableData->bPALCreatedTempFile = bPALCreatedTempFile;
pImmutableData->dwDesiredAccessWhenOpened = MAPConvertProtectToAccess(flProtect);
//
// The local data isn't grabbed / modified until here so that we don't
// need to worry ourselves with locking issues with the passed in
// file handle -- all operations concerning the file handle are completed
// before we deal with the lock for the new object.
//
palError = pMapping->GetProcessLocalData(
pThread,
WriteLock,
&pLocalDataLock,
reinterpret_cast<void**>(&pLocalData)
);
if (NO_ERROR != palError)
{
goto ExitInternalCreateFileMapping;
}
pLocalData->UnixFd = UnixFd;
#if ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS
if (-1 == UnixFd)
{
pLocalData->MappedFileDevNum = (dev_t)-1; /* there is no standard NO_DEV */
pLocalData->MappedFileInodeNum = NO_INO;
}
else
{
struct stat st;
if (0 == fstat(UnixFd, &st))
{
pLocalData->MappedFileDevNum = st.st_dev;
pLocalData->MappedFileInodeNum = st.st_ino;
}
else
{
ERROR("Couldn't get inode info for fd=%d to be stored in mapping object\n", UnixFd);
palError = ERROR_INTERNAL_ERROR;
goto ExitInternalCreateFileMapping;
}
}
#endif
pLocalDataLock->ReleaseLock(pThread, TRUE);
pLocalDataLock = NULL;
palError = g_pObjectManager->RegisterObject(
pThread,
pMapping,
&aotFileMapping,
flProtect, // TODO: is flProtect really an access right?
phMapping,
&pRegisteredMapping
);
//
// pMapping is invalidated by the call to RegisterObject, so NULL it
// out here to ensure that we don't try to release a reference on
// it down the line. This also ensures that we won't attempt to release
// any data associated with the mapping object here, as if any cleanup is
// necessary due to a failure in RegisterObject (which includes another
// object by the same name already existing) the cleanup will take place
// when that routine releases the reference to pMapping.
//
pMapping = NULL;
ExitInternalCreateFileMapping:
if (NULL != pLocalDataLock)
{
pLocalDataLock->ReleaseLock(
pThread,
TRUE
);
}
if (NULL != pMapping)
{
pMapping->ReleaseReference(pThread);
if (bPALCreatedTempFile)
{
unlink(pImmutableData->szFileName);
}
if (-1 != UnixFd)
{
close(UnixFd);
}
}
if (NULL != pRegisteredMapping)
{
pRegisteredMapping->ReleaseReference(pThread);
}
if (NULL != pFileObject)
{
pFileObject->ReleaseReference(pThread);
}
return palError;
}
/*++
Function:
OpenFileMappingA
See MSDN doc.
--*/
HANDLE
PALAPI
OpenFileMappingA(
IN DWORD dwDesiredAccess,
IN BOOL bInheritHandle,
IN LPCSTR lpName)
{
HANDLE hFileMapping = NULL;
CPalThread *pThread = NULL;
PAL_ERROR palError = NO_ERROR;
PERF_ENTRY(OpenFileMappingA);
ENTRY("OpenFileMappingA(dwDesiredAccess=%u, bInheritHandle=%d, lpName=%p (%s)\n",
dwDesiredAccess, bInheritHandle, lpName?lpName:"NULL", lpName?lpName:"NULL");
pThread = InternalGetCurrentThread();
if (lpName == nullptr)
{
ERROR("name is NULL\n");
palError = ERROR_INVALID_PARAMETER;
}
else
{
ASSERT("lpName: Cross-process named objects are not supported in PAL");
palError = ERROR_NOT_SUPPORTED;
}
if (NO_ERROR != palError)
{
pThread->SetLastError(palError);
}
LOGEXIT( "OpenFileMappingA returning %p\n", hFileMapping );
PERF_EXIT(OpenFileMappingA);
return hFileMapping;
}
/*++
Function:
OpenFileMappingW
See MSDN doc.
--*/
HANDLE
PALAPI
OpenFileMappingW(
IN DWORD dwDesiredAccess,
IN BOOL bInheritHandle,
IN LPCWSTR lpName)
{
HANDLE hFileMapping = NULL;
PAL_ERROR palError = NO_ERROR;
CPalThread *pThread = NULL;
PERF_ENTRY(OpenFileMappingW);
ENTRY("OpenFileMappingW(dwDesiredAccess=%#x, bInheritHandle=%d, lpName=%p (%S)\n",
dwDesiredAccess, bInheritHandle, lpName?lpName:W16_NULLSTRING, lpName?lpName:W16_NULLSTRING);
pThread = InternalGetCurrentThread();
/* validate parameters */
if (lpName == nullptr)
{
ERROR("name is NULL\n");
palError = ERROR_INVALID_PARAMETER;
}
else
{
ASSERT("lpName: Cross-process named objects are not supported in PAL");
palError = ERROR_NOT_SUPPORTED;
}
if (NO_ERROR != palError)
{
pThread->SetLastError(palError);
}
LOGEXIT("OpenFileMappingW returning %p.\n", hFileMapping);
PERF_EXIT(OpenFileMappingW);
return hFileMapping;
}
PAL_ERROR
CorUnix::InternalOpenFileMapping(
CPalThread *pThread,
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCWSTR lpName,
HANDLE *phMapping
)
{
PAL_ERROR palError = NO_ERROR;
IPalObject *pFileMapping = NULL;
CPalString sObjectName(lpName);
if ( MAPContainsInvalidFlags( dwDesiredAccess ) )
{
ASSERT( "dwDesiredAccess can be one or more of FILE_MAP_READ, "
"FILE_MAP_WRITE, FILE_MAP_COPY or FILE_MAP_ALL_ACCESS.\n" );
palError = ERROR_INVALID_PARAMETER;
goto ExitInternalOpenFileMapping;
}
palError = g_pObjectManager->LocateObject(
pThread,
&sObjectName,
&aotFileMapping,
&pFileMapping
);
if (NO_ERROR != palError)
{
goto ExitInternalOpenFileMapping;
}
palError = g_pObjectManager->ObtainHandleForObject(
pThread,
pFileMapping,
dwDesiredAccess,
bInheritHandle,
NULL,
phMapping
);
if (NO_ERROR != palError)
{
goto ExitInternalOpenFileMapping;
}
ExitInternalOpenFileMapping:
if (NULL != pFileMapping)
{
pFileMapping->ReleaseReference(pThread);
}
return palError;
}
/*++
Function:
MapViewOfFile
Limitations: 1) Currently file mappings are supported only at file
offset 0.
2) Some platforms (specifically HP-UX) do not support
multiple simultaneous shared mapping of the same file
region in the same process. On these platforms, in case
we are asked for a new view completely contained in an
existing one, we return an address within the existing
mapping. In case the new requested view is overlapping
with the existing one, but not contained in it, the
mapping is impossible, and MapViewOfFile will fail.
Since currently the mappings are supported only at file
offset 0, MapViewOfFile will succeed if the new view
is equal or smaller of the existing one, and the address
returned will be the same address of the existing
mapping.
Since the underlying mapping is always the same, all
the shared views of the same file region will share the
same protection, i.e. they will have the largest
protection requested. If any mapping asked for a
read-write access, all the read-only mappings of the
same region will silently get a read-write access to
it.
See MSDN doc.
--*/
LPVOID
PALAPI
MapViewOfFile(
IN HANDLE hFileMappingObject,
IN DWORD dwDesiredAccess,
IN DWORD dwFileOffsetHigh,
IN DWORD dwFileOffsetLow,
IN SIZE_T dwNumberOfBytesToMap)
{
PAL_ERROR palError = NO_ERROR;
CPalThread *pThread = NULL;
LPVOID pvMappedBaseAddress = NULL;
PERF_ENTRY(MapViewOfFile);
ENTRY("MapViewOfFile(hFileMapping=%p, dwDesiredAccess=%u, "
"dwFileOffsetH=%u, dwFileOffsetL=%u, dwNumberOfBytes=%u)\n",
hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh,
dwFileOffsetLow, dwNumberOfBytesToMap);
pThread = InternalGetCurrentThread();
palError = InternalMapViewOfFile(
pThread,
hFileMappingObject,
dwDesiredAccess,
dwFileOffsetHigh,
dwFileOffsetLow,
dwNumberOfBytesToMap,
&pvMappedBaseAddress
);
if (NO_ERROR != palError)
{
pThread->SetLastError(palError);
}
LOGEXIT( "MapViewOfFile returning %p.\n", pvMappedBaseAddress );
PERF_EXIT(MapViewOfFile);
return pvMappedBaseAddress;
}
LPVOID
PALAPI
MapViewOfFileEx(
IN HANDLE hFileMappingObject,
IN DWORD dwDesiredAccess,
IN DWORD dwFileOffsetHigh,
IN DWORD dwFileOffsetLow,
IN SIZE_T dwNumberOfBytesToMap,
IN LPVOID lpBaseAddress)
{
PAL_ERROR palError = NO_ERROR;
CPalThread *pThread = NULL;
LPVOID pvMappedBaseAddress = NULL;
PERF_ENTRY(MapViewOfFileEx);
ENTRY("MapViewOfFileEx(hFileMapping=%p, dwDesiredAccess=%u, "
"dwFileOffsetH=%u, dwFileOffsetL=%u, dwNumberOfBytes=%u, lpBaseAddress=%p)\n",
hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh,
dwFileOffsetLow, dwNumberOfBytesToMap, lpBaseAddress);
pThread = InternalGetCurrentThread();
if (lpBaseAddress == NULL)
{
palError = InternalMapViewOfFile(
pThread,
hFileMappingObject,
dwDesiredAccess,
dwFileOffsetHigh,
dwFileOffsetLow,
dwNumberOfBytesToMap,
&pvMappedBaseAddress
);
if (NO_ERROR != palError)
{
pThread->SetLastError(palError);
}
}
else
{
// TODO: Figure out if we can support mapping at a specific address on Linux.
pThread->SetLastError(ERROR_INVALID_PARAMETER);
}
LOGEXIT( "MapViewOfFileEx returning %p.\n", pvMappedBaseAddress );
PERF_EXIT(MapViewOfFileEx);
return pvMappedBaseAddress;
}
/*++
Function:
FlushViewOfFile
See MSDN doc.
--*/
BOOL
PALAPI
FlushViewOfFile(
IN LPVOID lpBaseAddress,
IN SIZE_T dwNumberOfBytesToFlush)
{
PAL_ERROR palError = NO_ERROR;
CPalThread *pThread = NULL;
PMAPPED_VIEW_LIST pView = NULL;
BOOL fResult = TRUE;
PERF_ENTRY(FlushViewOfFile);
ENTRY("FlushViewOfFile(lpBaseAddress=%p, dwNumberOfBytesToFlush=%u)\n",
lpBaseAddress, dwNumberOfBytesToFlush);
pThread = InternalGetCurrentThread();
InternalEnterCriticalSection(pThread, &mapping_critsec);
pView = MAPGetViewForAddress(lpBaseAddress);
if (NULL == pView)
{
ERROR("lpBaseAddress has to be the address returned by MapViewOfFile[Ex]");
palError = ERROR_INVALID_HANDLE;
goto Exit;
}
if (dwNumberOfBytesToFlush == 0)
{
dwNumberOfBytesToFlush = pView->NumberOfBytesToMap;
}
// <ROTORTODO>we should only use MS_SYNC if the file has been opened
// with FILE_FLAG_WRITE_THROUGH
if (msync(lpBaseAddress, dwNumberOfBytesToFlush, MS_SYNC) == -1)
{
if (errno == EINVAL)
{
WARN("msync failed; %s\n", strerror(errno));
palError = ERROR_INVALID_PARAMETER;
}
else if (errno == EIO)
{
WARN("msync failed; %s\n", strerror(errno));
palError = ERROR_WRITE_FAULT;
}
else
{
ERROR("msync failed; %s\n", strerror(errno));
palError = ERROR_INTERNAL_ERROR;
}
}
Exit:
InternalLeaveCriticalSection(pThread, &mapping_critsec);
if (NO_ERROR != palError)
{
fResult = FALSE;
pThread->SetLastError(palError);
}
LOGEXIT("FlushViewOfFile returning %d.\n", fResult);
PERF_EXIT(FlushViewOfFile);
return fResult;
}
/*++
Function:
UnmapViewOfFile
See MSDN doc.
--*/
BOOL
PALAPI
UnmapViewOfFile(
IN LPCVOID lpBaseAddress)
{
PAL_ERROR palError;
CPalThread *pThread;
PERF_ENTRY(UnmapViewOfFile);
ENTRY("UnmapViewOfFile(lpBaseAddress=%p)\n", lpBaseAddress);
pThread = InternalGetCurrentThread();
palError = InternalUnmapViewOfFile(pThread, lpBaseAddress);
if (NO_ERROR != palError)
{
pThread->SetLastError(palError);
}
LOGEXIT( "UnmapViewOfFile returning %s.\n", (NO_ERROR == palError) ? "TRUE" : "FALSE" );
PERF_EXIT(UnmapViewOfFile);
return (NO_ERROR == palError);
}
PAL_ERROR
CorUnix::InternalMapViewOfFile(
CPalThread *pThread,
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap,
LPVOID *ppvBaseAddress
)
{
PAL_ERROR palError = NO_ERROR;
IPalObject *pMappingObject = NULL;
CFileMappingImmutableData *pImmutableData = NULL;
CFileMappingProcessLocalData *pProcessLocalData = NULL;
IDataLock *pProcessLocalDataLock = NULL;
#if ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS
PMAPPED_VIEW_LIST pReusedMapping = NULL;
#endif
LPVOID pvBaseAddress = NULL;
/* Sanity checks */
if ( MAPContainsInvalidFlags( dwDesiredAccess ) )
{
ASSERT( "dwDesiredAccess can be one of FILE_MAP_WRITE, FILE_MAP_READ,"
" FILE_MAP_COPY or FILE_MAP_ALL_ACCESS.\n" );
palError = ERROR_INVALID_PARAMETER;
goto InternalMapViewOfFileExit;
}
if ( 0 != dwFileOffsetHigh || 0 != dwFileOffsetLow )
{
ASSERT( "dwFileOffsetHigh and dwFileOffsetLow are always 0.\n" );
palError = ERROR_INVALID_PARAMETER;
goto InternalMapViewOfFileExit;
}
palError = g_pObjectManager->ReferenceObjectByHandle(
pThread,
hFileMappingObject,
&aotFileMapping,
dwDesiredAccess,
&pMappingObject
);
if (NO_ERROR != palError)
{
ERROR( "Unable to reference handle %p.\n",hFileMappingObject );
goto InternalMapViewOfFileExit;
}
palError = pMappingObject->GetImmutableData(
reinterpret_cast<void**>(&pImmutableData)
);
if (NO_ERROR != palError)
{
ERROR( "Unable to obtain object immutable data");
goto InternalMapViewOfFileExit;
}
palError = pMappingObject->GetProcessLocalData(
pThread,
ReadLock,
&pProcessLocalDataLock,
reinterpret_cast<void**>(&pProcessLocalData)
);
if (NO_ERROR != palError)
{
ERROR( "Unable to obtain object process local data");
goto InternalMapViewOfFileExit;
}
/* If dwNumberOfBytesToMap is 0, we need to map the entire file.
* mmap doesn't do the same thing as Windows in that case, though,
* so we use the file size instead. */
if (0 == dwNumberOfBytesToMap)
{
dwNumberOfBytesToMap = pImmutableData->MaxSize;
}
palError = MAPDesiredAccessAllowed(
pImmutableData->flProtect,
dwDesiredAccess,
pImmutableData->dwDesiredAccessWhenOpened
);
if (NO_ERROR != palError)
{
goto InternalMapViewOfFileExit;
}
InternalEnterCriticalSection(pThread, &mapping_critsec);
if (FILE_MAP_COPY == dwDesiredAccess)
{
int flags = MAP_PRIVATE;
#if !HAVE_MMAP_DEV_ZERO
if (pProcessLocalData->UnixFd == -1)
{
flags |= MAP_ANON;
}
#endif
pvBaseAddress = mmap(
NULL,
dwNumberOfBytesToMap,
PROT_READ|PROT_WRITE,
flags,
pProcessLocalData->UnixFd,
0
);
}
else
{
INT prot = MAPFileMapToMmapFlags(dwDesiredAccess);
if (prot != -1)
{
int flags = MAP_SHARED;
#if !HAVE_MMAP_DEV_ZERO
if (pProcessLocalData->UnixFd == -1)
{
flags |= MAP_ANON;
}
#endif
pvBaseAddress = mmap(
NULL,
dwNumberOfBytesToMap,
prot,
flags,
pProcessLocalData->UnixFd,
0
);
#if ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS
if ((MAP_FAILED == pvBaseAddress) && (ENOMEM == errno))
{
/* Search in list of MAPPED_MEMORY_INFO for a shared mapping
with the same inode number
*/
TRACE("Mmap() failed with errno=ENOMEM probably for multiple mapping "
"limitation. Searching for a replacement among existing mappings\n");
pReusedMapping = FindSharedMappingReplacement(
pThread,
pProcessLocalData->MappedFileDevNum,
pProcessLocalData->MappedFileInodeNum,
dwNumberOfBytesToMap,
0
);
if (pReusedMapping)
{
int ret;
TRACE("Mapping @ %p {sz=%d offs=%d} fully "
"contains the requested one {sz=%d offs=%d}: reusing it\n",
pReusedMapping->pNMHolder->address,
(int)pReusedMapping->pNMHolder->size,
(int)pReusedMapping->pNMHolder->offset,
dwNumberOfBytesToMap, 0);
/* Let's check the mapping's current protection */
ret = mprotect(pReusedMapping->pNMHolder->address,
pReusedMapping->pNMHolder->size,
prot | PROT_CHECK);
if (0 != ret)
{
/* We need to raise the protection to the desired
one. That will give write access to any read-only
mapping sharing this native mapping, but there is
no way around this problem on systems that do not
allow more than one mapping per file region, per
process */
TRACE("Raising protections on mapping @ %p to 0x%x\n",
pReusedMapping->pNMHolder->address, prot);
ret = mprotect(pReusedMapping->pNMHolder->address,
pReusedMapping->pNMHolder->size,
prot);
}
if (ret != 0)
{
ERROR( "Failed setting protections on reused mapping\n");
NativeMapHolderRelease(pThread, pReusedMapping->pNMHolder);
InternalFree(pReusedMapping);
pReusedMapping = NULL;
}
}
}
#endif // ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS
}
else
{
ASSERT( "MapFileMapToMmapFlags failed!\n" );
palError = ERROR_INTERNAL_ERROR;
goto InternalMapViewOfFileLeaveCriticalSection;
}
}
if (MAP_FAILED == pvBaseAddress
#if ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS
&& (pReusedMapping == NULL)
#endif // ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS
)
{
ERROR( "mmap failed with code %s.\n", strerror( errno ) );
palError = ERROR_NOT_ENOUGH_MEMORY;
goto InternalMapViewOfFileLeaveCriticalSection;
}
#if ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS
if (pReusedMapping != NULL)
{
//
// Add a reference to the file mapping object the reused mapping
// points to (note that it may be different than the object this
// call was actually made against) and add the view to the global
// list. All other initialization took place in
// FindSharedMappingReplacement
//
pvBaseAddress = pReusedMapping->lpAddress;
pReusedMapping->pFileMapping->AddReference();
InsertTailList(&MappedViewList, &pReusedMapping->Link);
}
else
#endif // ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS
{
//
// Allocate and fill out a new view structure, and add it to
// the global list.
//
PMAPPED_VIEW_LIST pNewView = (PMAPPED_VIEW_LIST)InternalMalloc(sizeof(*pNewView));
if (NULL != pNewView)
{
pNewView->lpAddress = pvBaseAddress;
pNewView->NumberOfBytesToMap = dwNumberOfBytesToMap;
pNewView->dwDesiredAccess = dwDesiredAccess;
pNewView->pFileMapping = pMappingObject;
pNewView->pFileMapping->AddReference();
pNewView->lpPEBaseAddress = 0;
InsertTailList(&MappedViewList, &pNewView->Link);
#if ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS
pNewView->MappedFileDevNum = pProcessLocalData->MappedFileDevNum;
pNewView->MappedFileInodeNum = pProcessLocalData->MappedFileInodeNum;
pNewView->pNMHolder = NewNativeMapHolder(
pThread,
pvBaseAddress,
dwNumberOfBytesToMap,
0,
1
);
if (NULL == pNewView->pNMHolder)
{
pNewView->pFileMapping->ReleaseReference(pThread);
RemoveEntryList(&pNewView->Link);
InternalFree(pNewView);
palError = ERROR_INTERNAL_ERROR;
}
#endif // ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS
}
else
{
palError = ERROR_INTERNAL_ERROR;
}
if (NO_ERROR != palError)
{
if (-1 == munmap(pvBaseAddress, dwNumberOfBytesToMap))
{
ERROR("Unable to unmap the file. Expect trouble.\n");
goto InternalMapViewOfFileLeaveCriticalSection;
}
}
}
TRACE( "Added %p to the list.\n", pvBaseAddress );
*ppvBaseAddress = pvBaseAddress;
InternalMapViewOfFileLeaveCriticalSection:
InternalLeaveCriticalSection(pThread, &mapping_critsec);
InternalMapViewOfFileExit:
if (NULL != pProcessLocalDataLock)
{
pProcessLocalDataLock->ReleaseLock(pThread, FALSE);
}
if (NULL != pMappingObject)
{
pMappingObject->ReleaseReference(pThread);
}
return palError;
}
PAL_ERROR
CorUnix::InternalUnmapViewOfFile(
CPalThread *pThread,
LPCVOID lpBaseAddress
)
{
PAL_ERROR palError = NO_ERROR;
PMAPPED_VIEW_LIST pView = NULL;
IPalObject *pMappingObject = NULL;
InternalEnterCriticalSection(pThread, &mapping_critsec);
pView = MAPGetViewForAddress(lpBaseAddress);
if (NULL == pView)
{
ERROR("lpBaseAddress has to be the address returned by MapViewOfFile[Ex]");
palError = ERROR_INVALID_HANDLE;
goto InternalUnmapViewOfFileExit;
}
#if ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS
NativeMapHolderRelease(pThread, pView->pNMHolder);
pView->pNMHolder = NULL;
#else
if (-1 == munmap((LPVOID)lpBaseAddress, pView->NumberOfBytesToMap))
{
ASSERT( "Unable to unmap the memory. Error=%s.\n",
strerror( errno ) );
palError = ERROR_INTERNAL_ERROR;
//
// Even if the unmap fails we want to continue removing the
// info for this view
//
}
#endif
RemoveEntryList(&pView->Link);
pMappingObject = pView->pFileMapping;
InternalFree(pView);
InternalUnmapViewOfFileExit:
InternalLeaveCriticalSection(pThread, &mapping_critsec);
//
// We can't dereference the file mapping object until after
// we've released the mapping critical section, since it may
// start going down its cleanup path and we don't want to make
// any assumptions as to what locks that might grab...
//
if (NULL != pMappingObject)
{
pMappingObject->ReleaseReference(pThread);
}
return palError;
}
/*++
Function :
MAPInitialize
Initialize the critical sections.
Return value:
TRUE if initialization succeeded
FALSE otherwise
--*/
BOOL
MAPInitialize( void )
{
TRACE( "Initialising the critical section.\n" );
InternalInitializeCriticalSection(&mapping_critsec);
InitializeListHead(&MappedViewList);
return TRUE;
}
/*++
Function :
MAPCleanup
Deletes the critical sections. And all other necessary cleanup.
Note:
This function is called after the handle manager is stopped. So
there shouldn't be any call that will cause an access to the handle
manager.
--*/
void MAPCleanup( void )
{
TRACE( "Deleting the critical section.\n" );
InternalDeleteCriticalSection(&mapping_critsec);
}
/*++
Function :
MAPGetViewForAddress
Returns the mapped view (if any) that is based at the passed in address.
Callers to this function must hold mapping_critsec
--*/
static PMAPPED_VIEW_LIST MAPGetViewForAddress( LPCVOID lpAddress )
{
if ( NULL == lpAddress )
{
ERROR( "lpAddress cannot be NULL\n" );
return NULL;
}
for(LIST_ENTRY *pLink = MappedViewList.Flink;
pLink != &MappedViewList;
pLink = pLink->Flink)
{
PMAPPED_VIEW_LIST pView = CONTAINING_RECORD(pLink, MAPPED_VIEW_LIST, Link);
if (pView->lpAddress == lpAddress)
{
return pView;
}
}
WARN( "No match found.\n" );
return NULL;
}
/*++
Function :
MAPDesiredAccessAllowed
Determines if desired access is allowed based on the protection state.
if dwDesiredAccess conflicts with flProtect then the error is
ERROR_INVALID_PARAMETER, if the dwDesiredAccess conflicts with
dwDesiredAccessWhenOpened, then the error code is ERROR_ACCESS_DENIED
--*/
static PAL_ERROR MAPDesiredAccessAllowed( DWORD flProtect,
DWORD dwUserDesiredAccess,
DWORD dwDesiredAccessWhenOpened )
{
TRACE( "flProtect=%d, dwUserDesiredAccess=%d, dwDesiredAccessWhenOpened=%d\n",
flProtect, dwUserDesiredAccess, dwDesiredAccessWhenOpened );
/* check flProtect parameters*/
if ( FILE_MAP_READ!= dwUserDesiredAccess && PAGE_READONLY == flProtect )
{
ERROR( "map object is read-only, can't map a view with write access\n");
return ERROR_INVALID_PARAMETER;
}
if ( FILE_MAP_WRITE == dwUserDesiredAccess && PAGE_READWRITE != flProtect )
{
ERROR( "map object not open read-write, can't map a view with write "
"access.\n" );
return ERROR_INVALID_PARAMETER;
}
if ( FILE_MAP_COPY == dwUserDesiredAccess && PAGE_WRITECOPY != flProtect )
{
ERROR( "map object not open for copy-on-write, can't map copy-on-write "
"view.\n" );
return ERROR_INVALID_PARAMETER;
}
/* Check to see we don't confict with the desired access we
opened the mapping object with. */
if ( ( dwUserDesiredAccess == FILE_MAP_READ ) &&
!( ( dwDesiredAccessWhenOpened == FILE_MAP_READ ) ||
( dwDesiredAccessWhenOpened == FILE_MAP_ALL_ACCESS ) ) )
{
ERROR( "dwDesiredAccess conflict : read access requested, object not "
"opened with read access.\n" );
return ERROR_ACCESS_DENIED;
}
if ( ( dwUserDesiredAccess & FILE_MAP_WRITE ) &&
!( ( dwDesiredAccessWhenOpened == FILE_MAP_WRITE ) ||
( dwDesiredAccessWhenOpened == FILE_MAP_ALL_ACCESS ) ) )
{
ERROR( "dwDesiredAccess conflict : write access requested, object not "
"opened with write access.\n" );
return ERROR_ACCESS_DENIED;
}
if ( ( dwUserDesiredAccess == FILE_MAP_COPY ) &&
!( dwDesiredAccessWhenOpened == FILE_MAP_COPY ) )
{
ERROR( "dwDesiredAccess conflict : copy-on-write access requested, "
"object not opened with copy-on-write access.\n" );
return ERROR_ACCESS_DENIED;
}
return NO_ERROR;
}
/*++
Function :
MAPConvertProtectToAccess
Converts the PAGE_READONLY type flags to FILE_MAP_READ flags.
--*/
static DWORD MAPConvertProtectToAccess( DWORD flProtect )
{
if ( PAGE_READONLY == flProtect )
{
return FILE_MAP_READ;
}
if ( PAGE_READWRITE == flProtect )
{
return FILE_MAP_ALL_ACCESS;
}
if ( PAGE_WRITECOPY == flProtect )
{
return FILE_MAP_COPY;
}
ASSERT( "Unknown flag for flProtect. This line "
"should not have been executed.\n " );
return (DWORD) -1;
}
/*++
Function :
MAPConvertAccessToProtect
Converts the FILE_MAP_READ type flags to PAGE_READONLY flags.
Currently, this function only deals with the access flags recognized as valid
by MAPContainsInvalidFlags().
--*/
static DWORD MAPConvertAccessToProtect(DWORD flAccess)
{
if (flAccess == FILE_MAP_ALL_ACCESS)
{
return PAGE_READWRITE;
}
else if ((flAccess == FILE_MAP_COPY) || (flAccess == FILE_MAP_WRITE))
{
return PAGE_WRITECOPY;
}
else if (flAccess == FILE_MAP_READ)
{
return PAGE_READONLY;
}
else if (flAccess == 0)
{
return PAGE_NOACCESS;
}
ASSERT("Unknown flag for flAccess.\n");
return (DWORD) -1;
}
/*++
Function :
MAPFileMapToMmapFlags
Converts the mapping flags to unix protection flags.
--*/
static INT MAPFileMapToMmapFlags( DWORD flags )
{
if ( FILE_MAP_READ == flags )
{
TRACE( "FILE_MAP_READ\n" );
return PROT_READ;
}
else if ( FILE_MAP_WRITE == flags )
{
TRACE( "FILE_MAP_WRITE\n" );
/* The limitation of x86 archetecture
means you cant have writable but not readable
page. In Windows maps of FILE_MAP_WRITE can still be
read from. */
return PROT_WRITE | PROT_READ;
}
else if ( (FILE_MAP_READ|FILE_MAP_WRITE) == flags )
{
TRACE( "FILE_MAP_READ|FILE_MAP_WRITE\n" );
return PROT_READ | PROT_WRITE;
}
else if( FILE_MAP_COPY == flags)
{
TRACE( "FILE_MAP_COPY\n");
return PROT_READ | PROT_WRITE;
}
ASSERT( "Unknown flag. This line should not have been executed.\n" );
return -1;
}
/*++
Function :
MAPMmapProtToAccessFlags
Converts unix protection flags to file access flags.
We ignore PROT_EXEC.
--*/
static DWORD MAPMmapProtToAccessFlags( int prot )
{
DWORD flAccess = 0; // default: no access
if (PROT_NONE == prot)
{
flAccess = 0;
}
else if ( ((PROT_READ | PROT_WRITE) & prot) == (PROT_READ | PROT_WRITE) )
{
flAccess = FILE_MAP_ALL_ACCESS;
}
else if ( (PROT_WRITE & prot) == PROT_WRITE )
{
flAccess = FILE_MAP_WRITE;
}
else if ( (PROT_READ & prot) == PROT_READ )
{
flAccess = FILE_MAP_READ;
}
else
{
ASSERT( "Unknown Unix protection flag\n" );
}
return flAccess;
}
/*++
Function :
MAPGrowLocalFile
Grows the file on disk to match the specified size.
--*/
static PAL_ERROR MAPGrowLocalFile( INT UnixFD, UINT NewSize )
{
PAL_ERROR palError = NO_ERROR;
INT TruncateRetVal = -1;
struct stat FileInfo;
TRACE( "Entered MapGrowLocalFile (UnixFD=%d,NewSize%d)\n", UnixFD, NewSize );
//
// TODO: can we add configure flags to model the behavior of ftruncate
// among our various target platforms? How much would that actually gain
// us?
//
/* ftruncate is a standard function, but the behavior of enlarging files is
non-standard. So I will try to enlarge a file, and if that fails try the
less efficent way.*/
TruncateRetVal = ftruncate( UnixFD, NewSize );
fstat( UnixFD, &FileInfo );
if ( TruncateRetVal != 0 || FileInfo.st_size != (int) NewSize )
{
INT OrigSize;
CONST UINT BUFFER_SIZE = 128;
BYTE buf[BUFFER_SIZE];
UINT x = 0;
UINT CurrentPosition = 0;
TRACE( "Trying the less efficent way.\n" );
CurrentPosition = lseek( UnixFD, 0, SEEK_CUR );
OrigSize = lseek( UnixFD, 0, SEEK_END );
if ( OrigSize == -1 )
{
ERROR( "Unable to locate the EOF marker. Reason=%s\n",
strerror( errno ) );
palError = ERROR_INTERNAL_ERROR;
goto done;
}
if (NewSize <= (UINT) OrigSize)
{
return TRUE;
}
memset( buf, 0, BUFFER_SIZE );
for ( x = 0; x < NewSize - OrigSize - BUFFER_SIZE; x += BUFFER_SIZE )
{
if ( write( UnixFD, (LPVOID)buf, BUFFER_SIZE ) == -1 )
{
ERROR( "Unable to grow the file. Reason=%s\n", strerror( errno ) );
if((errno == ENOSPC) || (errno == EDQUOT))
{
palError = ERROR_DISK_FULL;
}
else
{
palError = ERROR_INTERNAL_ERROR;
}
goto done;
}
}
/* Catch any left overs. */
if ( x != NewSize )
{
if ( write( UnixFD, (LPVOID)buf, NewSize - OrigSize - x) == -1 )
{
ERROR( "Unable to grow the file. Reason=%s\n", strerror( errno ) );
if((errno == ENOSPC) || (errno == EDQUOT))
{
palError = ERROR_DISK_FULL;
}
else
{
palError = ERROR_INTERNAL_ERROR;
}
goto done;
}
}
/* restore the file pointer position */
lseek( UnixFD, CurrentPosition, SEEK_SET );
}
done:
return palError;
}
/*++
Function :
MAPContainsInvalidFlags
Checks that only valid flags are in the parameter.
--*/
static BOOL MAPContainsInvalidFlags( DWORD flags )
{
if ( (flags == FILE_MAP_READ) ||
(flags == FILE_MAP_WRITE) ||
(flags == FILE_MAP_ALL_ACCESS) ||
(flags == FILE_MAP_COPY) )
{
return FALSE;
}
else
{
return TRUE;
}
}
/*++
Function :
MAPProtectionToFileOpenFlags
Converts the PAGE_* flags to the O_* flags.
Returns the file open flags.
--*/
static INT MAPProtectionToFileOpenFlags( DWORD flProtect )
{
INT retVal = 0;
switch(flProtect)
{
case PAGE_READONLY:
retVal = O_RDONLY;
break;
case PAGE_READWRITE:
retVal = O_RDWR;
break;
case PAGE_WRITECOPY:
retVal = O_RDONLY;
break;
default:
ASSERT("unexpected flProtect value %#x\n", flProtect);
retVal = 0;
break;
}
return retVal;
}
/*++
Function :
MAPIsRequestPermissible
DWORD flProtect - The requested file mapping protection .
file * pFileStruct - The file structure containing all the information.
--*/
static BOOL MAPIsRequestPermissible( DWORD flProtect, CFileProcessLocalData * pFileLocalData )
{
if ( ( (flProtect == PAGE_READONLY || flProtect == PAGE_WRITECOPY) &&
(pFileLocalData->open_flags_deviceaccessonly == TRUE ||
pFileLocalData->open_flags & O_WRONLY) )
)
{
/*
* PAGE_READONLY or PAGE_WRITECOPY access to a file must at least be
* readable. Contrary to what MSDN says, PAGE_WRITECOPY
* only needs to be readable.
*/
return FALSE;
}
else if ( flProtect == PAGE_READWRITE && !(pFileLocalData->open_flags & O_RDWR) )
{
/*
* PAGE_READWRITE access to a file needs to be readable and writable
*/
return FALSE;
}
else
{
/* Action is permissible */
return TRUE;
}
}
// returns TRUE if we have information about the specified address
BOOL MAPGetRegionInfo(LPVOID lpAddress,
PMEMORY_BASIC_INFORMATION lpBuffer)
{
BOOL fFound = FALSE;
CPalThread * pThread = InternalGetCurrentThread();
InternalEnterCriticalSection(pThread, &mapping_critsec);
for(LIST_ENTRY *pLink = MappedViewList.Flink;
pLink != &MappedViewList;
pLink = pLink->Flink)
{
UINT MappedSize;
VOID * real_map_addr;
SIZE_T real_map_sz;
PMAPPED_VIEW_LIST pView = CONTAINING_RECORD(pLink, MAPPED_VIEW_LIST, Link);
#if ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS
real_map_addr = pView->pNMHolder->address;
real_map_sz = pView->pNMHolder->size;
#else
real_map_addr = pView->lpAddress;
real_map_sz = pView->NumberOfBytesToMap;
#endif
MappedSize = ((real_map_sz-1) & ~VIRTUAL_PAGE_MASK) + VIRTUAL_PAGE_SIZE;
if ( real_map_addr <= lpAddress &&
(VOID *)((UINT_PTR)real_map_addr+MappedSize) > lpAddress )
{
if (lpBuffer)
{
SIZE_T regionSize = MappedSize + (UINT_PTR) real_map_addr -
((UINT_PTR) lpAddress & ~VIRTUAL_PAGE_MASK);
lpBuffer->BaseAddress = lpAddress;
lpBuffer->AllocationProtect = 0;
lpBuffer->RegionSize = regionSize;
lpBuffer->State = MEM_COMMIT;
lpBuffer->Protect = MAPConvertAccessToProtect(pView->dwDesiredAccess);
lpBuffer->Type = MEM_MAPPED;
}
fFound = TRUE;
break;
}
}
InternalLeaveCriticalSection(pThread, &mapping_critsec);
return fFound;
}
#if ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS
//
// Callers of FindSharedMappingReplacement must hold mapping_critsec
//
static PMAPPED_VIEW_LIST FindSharedMappingReplacement(
CPalThread *pThread,
dev_t deviceNum,
ino_t inodeNum,
SIZE_T size,
SIZE_T offset)
{
PMAPPED_VIEW_LIST pNewView = NULL;
if (size == 0)
{
ERROR("Mapping size cannot be NULL\n");
return NULL;
}
for (LIST_ENTRY *pLink = MappedViewList.Flink;
pLink != &MappedViewList;
pLink = pLink->Flink)
{
PMAPPED_VIEW_LIST pView = CONTAINING_RECORD(pLink, MAPPED_VIEW_LIST, Link);
if (pView->MappedFileDevNum != deviceNum
|| pView->MappedFileInodeNum != inodeNum
|| pView->dwDesiredAccess == FILE_MAP_COPY)
{
continue;
}
//
// This is a shared mapping for the same indoe / device. Now, check
// to see if it overlaps with the range for the new view
//
SIZE_T real_map_offs = pView->pNMHolder->offset;
SIZE_T real_map_sz = pView->pNMHolder->size;
if (real_map_offs <= offset
&& real_map_offs+real_map_sz >= offset)
{
//
// The views overlap. Even if this view is not reusable for the
// new once the search is over, as on
// ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS systems there
// cannot be shared mappings of two overlapping regions of the
// same file, in the same process. Therefore, whether this view
// is reusable or not we cannot mmap the requested region of
// the specified file.
//
if (real_map_offs+real_map_sz >= offset+size)
{
/* The new desired mapping is fully contained in the
one just found: we can reuse this one */
pNewView = (PMAPPED_VIEW_LIST)InternalMalloc(sizeof(MAPPED_VIEW_LIST));
if (pNewView)
{
memcpy(pNewView, pView, sizeof(*pNewView));
NativeMapHolderAddRef(pNewView->pNMHolder);
pNewView->lpAddress = (VOID*)((CHAR*)pNewView->pNMHolder->address +
offset - pNewView->pNMHolder->offset);
pNewView->NumberOfBytesToMap = size;
}
else
{
ERROR("No memory for new MAPPED_VIEW_LIST node\n");
}
}
break;
}
}
TRACE ("FindSharedMappingReplacement returning %p\n", pNewView);
return pNewView;
}
static NativeMapHolder * NewNativeMapHolder(CPalThread *pThread, LPVOID address, SIZE_T size,
SIZE_T offset, long init_ref_count)
{
NativeMapHolder * pThisMapHolder;
if (init_ref_count < 0)
{
ASSERT("Negative initial reference count for new map holder\n");
return NULL;
}
pThisMapHolder =
(NativeMapHolder *)InternalMalloc(sizeof(NativeMapHolder));
if (pThisMapHolder)
{
pThisMapHolder->ref_count = init_ref_count;
pThisMapHolder->address = address;
pThisMapHolder->size = size;
pThisMapHolder->offset = offset;
}
return pThisMapHolder;
}
static LONG NativeMapHolderAddRef(NativeMapHolder * thisNMH)
{
LONG ret = InterlockedIncrement(&thisNMH->ref_count);
return ret;
}
static LONG NativeMapHolderRelease(CPalThread *pThread, NativeMapHolder * thisNMH)
{
LONG ret = InterlockedDecrement(&thisNMH->ref_count);
if (ret == 0)
{
if (-1 == munmap(thisNMH->address, thisNMH->size))
{
ASSERT( "Unable to unmap memory. Error=%s.\n",
strerror( errno ) );
}
else
{
TRACE( "Successfully unmapped %p (size=%lu)\n",
thisNMH->address, (unsigned long)thisNMH->size);
}
InternalFree (thisNMH);
}
else if (ret < 0)
{
ASSERT( "Negative reference count for map holder %p"
" {address=%p, size=%lu}\n", thisNMH->address,
(unsigned long)thisNMH->size);
}
return ret;
}
#endif // ONE_SHARED_MAPPING_PER_FILEREGION_PER_PROCESS
// Record a mapping in the MappedViewList list.
// This call assumes the mapping_critsec has already been taken.
static PAL_ERROR
MAPRecordMapping(
IPalObject *pMappingObject,
void *pPEBaseAddress,
void *addr,
size_t len,
int prot
)
{
if (pPEBaseAddress == NULL)
{
return ERROR_INTERNAL_ERROR;
}
PAL_ERROR palError = NO_ERROR;
PMAPPED_VIEW_LIST pNewView;
pNewView = (PMAPPED_VIEW_LIST)InternalMalloc(sizeof(*pNewView));
if (NULL != pNewView)
{
pNewView->lpAddress = addr;
pNewView->NumberOfBytesToMap = len;
pNewView->dwDesiredAccess = MAPMmapProtToAccessFlags(prot);
pMappingObject->AddReference();
pNewView->pFileMapping = pMappingObject;
pNewView->lpPEBaseAddress = pPEBaseAddress;
InsertTailList(&MappedViewList, &pNewView->Link);
TRACE_(LOADER)("Added address %p, size 0x%x, to the mapped file list.\n", addr, len);
}
else
{
palError = ERROR_INTERNAL_ERROR;
}
return palError;
}
// Do the actual mmap() call, and record the mapping in the MappedViewList list.
// This call assumes the mapping_critsec has already been taken.
static PAL_ERROR
MAPmmapAndRecord(
IPalObject *pMappingObject,
void *pPEBaseAddress,
void *addr,
size_t len,
int prot,
int flags,
int fd,
off_t offset,
LPVOID *ppvBaseAddress
)
{
_ASSERTE(pPEBaseAddress != NULL);
PAL_ERROR palError = NO_ERROR;
LPVOID pvBaseAddress = NULL;
pvBaseAddress = mmap(addr, len, prot, flags, fd, offset);
if (MAP_FAILED == pvBaseAddress)
{
ERROR_(LOADER)( "mmap failed with code %d: %s.\n", errno, strerror( errno ) );
palError = FILEGetLastErrorFromErrno();
}
else
{
palError = MAPRecordMapping(pMappingObject, pPEBaseAddress, pvBaseAddress, len, prot);
if (NO_ERROR != palError)
{
if (-1 == munmap(pvBaseAddress, len))
{
ERROR_(LOADER)("Unable to unmap the file. Expect trouble.\n");
}
}
else
{
*ppvBaseAddress = pvBaseAddress;
}
}
return palError;
}
/*++
MAPMapPEFile -
Map a PE format file into memory like Windows LoadLibrary() would do.
Doesn't apply base relocations if the function is relocated.
Parameters:
IN hFile - file to map
Return value:
non-NULL - the base address of the mapped image
NULL - error, with last error set.
--*/
void * MAPMapPEFile(HANDLE hFile)
{
PAL_ERROR palError = 0;
IPalObject *pFileObject = NULL;
IDataLock *pLocalDataLock = NULL;
CFileProcessLocalData *pLocalData = NULL;
CPalThread *pThread = InternalGetCurrentThread();
void * loadedBase = NULL;
IMAGE_DOS_HEADER * loadedHeader = NULL;
void * retval;
#if _DEBUG
bool forceRelocs = false;
#endif
ENTRY("MAPMapPEFile (hFile=%p)\n", hFile);
//Step 0: Verify values, find internal pal data structures.
if (INVALID_HANDLE_VALUE == hFile)
{
ERROR_(LOADER)( "Invalid file handle\n" );
palError = ERROR_INVALID_HANDLE;
goto done;
}
palError = g_pObjectManager->ReferenceObjectByHandle(
pThread,
hFile,
&aotFile,
GENERIC_READ,
&pFileObject
);
if (NO_ERROR != palError)
{
ERROR_(LOADER)( "ReferenceObjectByHandle failed\n" );
goto done;
}
palError = pFileObject->GetProcessLocalData(
pThread,
ReadLock,
&pLocalDataLock,
reinterpret_cast<void**>(&pLocalData)
);
if (NO_ERROR != palError)
{
ERROR_(LOADER)( "GetProcessLocalData failed\n" );
goto done;
}
int fd;
fd = pLocalData->unix_fd;
//Step 1: Read the PE headers and reserve enough space for the whole image somewhere.
IMAGE_DOS_HEADER dosHeader;
IMAGE_NT_HEADERS ntHeader;
errno = 0;
if (0 != lseek(fd, 0, SEEK_SET))
{
palError = FILEGetLastErrorFromErrno();
ERROR_(LOADER)( "lseek failed\n" );
goto done;
}
if (sizeof(dosHeader) != read(fd, &dosHeader, sizeof(dosHeader)))
{
palError = FILEGetLastErrorFromErrno();
ERROR_(LOADER)( "reading dos header failed\n" );
goto done;
}
if (dosHeader.e_lfanew != lseek(fd, dosHeader.e_lfanew, SEEK_SET))
{
palError = FILEGetLastErrorFromErrno();
goto done;
}
if (sizeof(ntHeader) != read(fd, &ntHeader, sizeof(ntHeader)))
{
palError = FILEGetLastErrorFromErrno();
goto done;
}
if ((VAL16(IMAGE_DOS_SIGNATURE) != VAL16(dosHeader.e_magic))
|| (VAL32(IMAGE_NT_SIGNATURE) != VAL32(ntHeader.Signature))
|| (VAL16(IMAGE_NT_OPTIONAL_HDR_MAGIC) != VAL16(ntHeader.OptionalHeader.Magic) ) )
{
ERROR_(LOADER)( "Magic number mismatch\n" );
palError = ERROR_INVALID_PARAMETER;
goto done;
}
//this code requires that the file alignment be the same as the page alignment
if (ntHeader.OptionalHeader.FileAlignment < VIRTUAL_PAGE_SIZE)
{
ERROR_(LOADER)( "Optional header file alignment is bad\n" );
palError = ERROR_INVALID_PARAMETER;
goto done;
}
//This doesn't read the entire NT header (the optional header technically has a variable length. But I
//don't need more directories.
//I now know how big the file is. Reserve enough address space for the whole thing. Try to get the
//preferred base. Create the intial mapping as "no access". We'll use that for the guard pages in the
//"holes" between sections.
SIZE_T preferredBase, virtualSize;
preferredBase = ntHeader.OptionalHeader.ImageBase;
virtualSize = ntHeader.OptionalHeader.SizeOfImage;
// Validate the image header
if ( (preferredBase == 0)
|| (virtualSize == 0)
|| (preferredBase + virtualSize < preferredBase) // Does the image overflow?
)
{
ERROR_(LOADER)( "image is corrupt\n" );
palError = ERROR_INVALID_PARAMETER;
goto done;
}
#if _DEBUG
char * envVar;
envVar = getenv("PAL_ForceRelocs");
if (envVar && strlen(envVar) > 0)
{
forceRelocs = true;
TRACE_(LOADER)("Forcing rebase of image\n");
}
void * pForceRelocBase;
pForceRelocBase = NULL;
if (forceRelocs)
{
//if we're forcing relocs, create an anonymous mapping at the preferred base. Only create the
//mapping if we can create it at the specified address.
pForceRelocBase = mmap( (void*)preferredBase, VIRTUAL_PAGE_SIZE, PROT_NONE, MAP_ANON|MAP_FIXED, -1, 0 );
if (pForceRelocBase == MAP_FAILED)
{
TRACE_(LOADER)("Attempt to take preferred base of %p to force relocation failed\n", (void*)preferredBase);
forceRelocs = false;
}
else if ((void*)preferredBase != pForceRelocBase)
{
TRACE_(LOADER)("Attempt to take preferred base of %p to force relocation failed; actually got %p\n", (void*)preferredBase, pForceRelocBase);
}
}
#endif // _DEBUG
// The first mmap mapping covers the entire file but just reserves space. Subsequent mappings cover
// individual parts of the file, and actually map pages in. Note that according to the mmap() man page, "A
// successful mmap deletes any previous mapping in the allocated address range." Also, "If a MAP_FIXED
// request is successful, the mapping established by mmap() replaces any previous mappings for the process' pages
// in the range from addr to addr + len." Thus, we will record a series of mappings here, one for the header
// and each of the sections, as well as all the space between them that we give PROT_NONE protections.
// We're going to start adding mappings to the mapping list, so take the critical section
InternalEnterCriticalSection(pThread, &mapping_critsec);
#if !defined(_AMD64_)
loadedBase = mmap((void*)preferredBase, virtualSize, PROT_NONE, MAP_ANON, -1, 0);
#else // defined(_AMD64_)
// MAC64 requires we pass MAP_SHARED (or MAP_PRIVATE) flags - otherwise, the call is failed.
// Refer to mmap documentation at http://www.manpagez.com/man/2/mmap/ for details.
loadedBase = mmap((void*)preferredBase, virtualSize, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);
#endif // !defined(_AMD64_)
if (MAP_FAILED == loadedBase)
{
ERROR_(LOADER)( "mmap failed with code %d: %s.\n", errno, strerror( errno ) );
palError = FILEGetLastErrorFromErrno();
loadedBase = NULL; // clear it so we don't try to use it during clean-up
goto doneReleaseMappingCriticalSection;
}
// All subsequent mappings of the PE file will be in the range [loadedBase, loadedBase + virtualSize)
#if _DEBUG
if (forceRelocs)
{
_ASSERTE(((SIZE_T)loadedBase) != preferredBase);
munmap(pForceRelocBase, VIRTUAL_PAGE_SIZE); // now that we've forced relocation, let the original address mapping go
}
if (((SIZE_T)loadedBase) != preferredBase)
{
TRACE_(LOADER)("Image rebased from preferredBase of %p to loadedBase of %p\n", preferredBase, loadedBase);
}
else
{
TRACE_(LOADER)("Image loaded at preferred base %p\n", loadedBase);
}
#endif // _DEBUG
//we have now reserved memory (potentially we got rebased). Walk the PE sections and map each part
//separately.
size_t headerSize;
headerSize = VIRTUAL_PAGE_SIZE; // if there are lots of sections, this could be wrong
//first, map the PE header to the first page in the image. Get pointers to the section headers
palError = MAPmmapAndRecord(pFileObject, loadedBase,
loadedBase, headerSize, PROT_READ, MAP_FILE|MAP_PRIVATE|MAP_FIXED, fd, 0,
(void**)&loadedHeader);
if (NO_ERROR != palError)
{
ERROR_(LOADER)( "mmap of PE header failed\n" );
goto doneReleaseMappingCriticalSection;
}
TRACE_(LOADER)("PE header loaded @ %p\n", loadedHeader);
_ASSERTE(loadedHeader == loadedBase); // we already preallocated the space, and we used MAP_FIXED, so we should have gotten this address
IMAGE_SECTION_HEADER * firstSection;
firstSection = (IMAGE_SECTION_HEADER*)(((char *)loadedHeader)
+ loadedHeader->e_lfanew
+ offsetof(IMAGE_NT_HEADERS, OptionalHeader)
+ VAL16(ntHeader.FileHeader.SizeOfOptionalHeader));
unsigned numSections;
numSections = ntHeader.FileHeader.NumberOfSections;
// Validation
char* sectionHeaderEnd;
sectionHeaderEnd = (char*)firstSection + numSections * sizeof(IMAGE_SECTION_HEADER);
if ( ((void*)firstSection < loadedBase)
|| ((char*)firstSection > sectionHeaderEnd)
|| (sectionHeaderEnd > (char*)loadedBase + virtualSize)
)
{
ERROR_(LOADER)( "image is corrupt\n" );
palError = ERROR_INVALID_PARAMETER;
goto doneReleaseMappingCriticalSection;
}
void* prevSectionBase;
prevSectionBase = loadedBase; // the first "section" for our purposes is the header
size_t prevSectionSizeInMemory;
prevSectionSizeInMemory = headerSize;
for (unsigned i = 0; i < numSections; ++i)
{
//for each section, map the section of the file to the correct virtual offset. Gather the
//protection bits from the PE file and convert them to the correct mmap PROT_* flags.
void * sectionData;
int prot = 0;
IMAGE_SECTION_HEADER &currentHeader = firstSection[i];
void* sectionBase = (char*)loadedBase + currentHeader.VirtualAddress;
// Validate the section header
if ( (sectionBase < loadedBase) // Did computing the section base overflow?
|| ((char*)sectionBase + currentHeader.SizeOfRawData < (char*)sectionBase) // Does the section overflow?
|| ((char*)sectionBase + currentHeader.SizeOfRawData > (char*)loadedBase + virtualSize) // Does the section extend past the end of the image as the header stated?
|| ((char*)prevSectionBase + prevSectionSizeInMemory > sectionBase) // Does this section overlap the previous one?
)
{
ERROR_(LOADER)( "section %d is corrupt\n", i );
palError = ERROR_INVALID_PARAMETER;
goto doneReleaseMappingCriticalSection;
}
if (currentHeader.Misc.VirtualSize > currentHeader.SizeOfRawData)
{
ERROR_(LOADER)( "no support for zero-padded sections, section %d\n", i );
palError = ERROR_INVALID_PARAMETER;
goto doneReleaseMappingCriticalSection;
}
// Is there space between the previous section and this one? If so, add a PROT_NONE mapping to cover it.
if ((char*)prevSectionBase + prevSectionSizeInMemory < sectionBase)
{
char* gapBase = (char*)prevSectionBase + prevSectionSizeInMemory;
palError = MAPRecordMapping(pFileObject,
loadedBase,
(void*)gapBase,
(char*)sectionBase - gapBase,
PROT_NONE);
if (NO_ERROR != palError)
{
ERROR_(LOADER)( "recording gap section before section %d failed\n", i );
goto doneReleaseMappingCriticalSection;
}
}
//Don't discard these sections. We need them to verify PE files
//if (currentHeader.Characteristics & IMAGE_SCN_MEM_DISCARDABLE)
// continue;
if (currentHeader.Characteristics & IMAGE_SCN_MEM_EXECUTE)
prot |= PROT_EXEC;
if (currentHeader.Characteristics & IMAGE_SCN_MEM_READ)
prot |= PROT_READ;
if (currentHeader.Characteristics & IMAGE_SCN_MEM_WRITE)
prot |= PROT_WRITE;
palError = MAPmmapAndRecord(pFileObject, loadedBase,
sectionBase,
currentHeader.SizeOfRawData,
prot,
MAP_FILE|MAP_PRIVATE|MAP_FIXED,
fd,
currentHeader.PointerToRawData,
&sectionData);
if (NO_ERROR != palError)
{
ERROR_(LOADER)( "mmap of section %d failed\n", i );
goto doneReleaseMappingCriticalSection;
}
#if _DEBUG
{
// Ensure null termination of section name (which is allowed to not be null terminated if exactly 8 characters long)
char sectionName[9];
sectionName[8] = '\0';
memcpy(sectionName, currentHeader.Name, sizeof(currentHeader.Name));
TRACE_(LOADER)("Section %d '%s' (header @ %p) loaded @ %p (RVA: 0x%x, SizeOfRawData: 0x%x, PointerToRawData: 0x%x)\n",
i, sectionName, &currentHeader, sectionData, currentHeader.VirtualAddress, currentHeader.SizeOfRawData, currentHeader.PointerToRawData);
}
#endif // _DEBUG
prevSectionBase = sectionBase;
prevSectionSizeInMemory = (currentHeader.SizeOfRawData + VIRTUAL_PAGE_MASK) & ~VIRTUAL_PAGE_MASK; // round up to page boundary
}
// Is there space after the last section and before the end of the mapped image? If so, add a PROT_NONE mapping to cover it.
char* imageEnd;
imageEnd = (char*)loadedBase + virtualSize; // actually, points just after the mapped end
if ((char*)prevSectionBase + prevSectionSizeInMemory < imageEnd)
{
char* gapBase = (char*)prevSectionBase + prevSectionSizeInMemory;
palError = MAPRecordMapping(pFileObject,
loadedBase,
(void*)gapBase,
imageEnd - gapBase,
PROT_NONE);
if (NO_ERROR != palError)
{
ERROR_(LOADER)( "recording end of image gap section failed\n" );
goto doneReleaseMappingCriticalSection;
}
}
palError = ERROR_SUCCESS;
doneReleaseMappingCriticalSection:
InternalLeaveCriticalSection(pThread, &mapping_critsec);
done:
if (NULL != pLocalDataLock)
{
pLocalDataLock->ReleaseLock(pThread, FALSE);
}
if (NULL != pFileObject)
{
pFileObject->ReleaseReference(pThread);
}
if (palError == ERROR_SUCCESS)
{
retval = loadedBase;
LOGEXIT("MAPMapPEFile returns %p\n", retval);
}
else
{
retval = NULL;
LOGEXIT("MAPMapPEFile error: %d\n", palError);
// If we had an error, and had mapped anything, we need to unmap it
if (loadedBase != NULL)
{
MAPUnmapPEFile(loadedBase);
}
}
return retval;
}
/*++
Function :
MAPUnmapPEFile - unmap a PE file, and remove it from the recorded list of PE files mapped
returns TRUE if successful, FALSE otherwise
--*/
BOOL MAPUnmapPEFile(LPCVOID lpAddress)
{
TRACE_(LOADER)("MAPUnmapPEFile(lpAddress=%p)\n", lpAddress);
if ( NULL == lpAddress )
{
ERROR_(LOADER)( "lpAddress cannot be NULL\n" );
return FALSE;
}
BOOL retval = TRUE;
CPalThread * pThread = InternalGetCurrentThread();
InternalEnterCriticalSection(pThread, &mapping_critsec);
PLIST_ENTRY pLink, pLinkNext, pLinkLocal = NULL;
unsigned nPESections = 0;
// Look through the entire MappedViewList for all mappings associated with the
// PE file with base address 'lpAddress'. We want to unmap all the memory
// and then release the file mapping object. Unfortunately, based on the comment
// in CorUnix::InternalUnmapViewOfFile(), we can't release the file mapping object
// while within the mapping critical section. So, we unlink all the elements from the
// main list while in the critical section, and then run through this local list
// doing the real work after releasing the main list critical section. The order
// of the unmapping doesn't matter, so we don't fully set all the list link pointers,
// only a minimal set.
for(pLink = MappedViewList.Flink;
pLink != &MappedViewList;
pLink = pLinkNext)
{
pLinkNext = pLink->Flink;
PMAPPED_VIEW_LIST pView = CONTAINING_RECORD(pLink, MAPPED_VIEW_LIST, Link);
if (pView->lpPEBaseAddress == lpAddress) // this entry is associated with the PE file
{
++nPESections; // for debugging, check that we see at least one
RemoveEntryList(&pView->Link);
pView->Link.Flink = pLinkLocal; // the local list is singly-linked, NULL terminated
pLinkLocal = &pView->Link;
}
}
#if _DEBUG
if (nPESections == 0)
{
ERROR_(LOADER)( "MAPUnmapPEFile called to unmap a file that was not in the PE file mapping list\n" );
}
#endif // _DEBUG
InternalLeaveCriticalSection(pThread, &mapping_critsec);
// Now, outside the critical section, do the actual unmapping work
for(pLink = pLinkLocal;
pLink != NULL;
pLink = pLinkNext)
{
pLinkNext = pLink->Flink;
PMAPPED_VIEW_LIST pView = CONTAINING_RECORD(pLink, MAPPED_VIEW_LIST, Link);
// remove pView mapping from the list
if (-1 == munmap(pView->lpAddress, pView->NumberOfBytesToMap))
{
// Emit an error message in a trace, but continue trying to do the rest
ERROR_(LOADER)("Unable to unmap the file. Expect trouble.\n");
retval = FALSE;
}
IPalObject* pFileObject = pView->pFileMapping;
if (NULL != pFileObject)
{
pFileObject->ReleaseReference(pThread);
}
InternalFree(pView); // this leaves pLink dangling
}
TRACE_(LOADER)("MAPUnmapPEFile returning %d\n", retval);
return retval;
}