diff options
Diffstat (limited to 'src/pal/src/map/map.cpp')
-rw-r--r-- | src/pal/src/map/map.cpp | 2749 |
1 files changed, 2749 insertions, 0 deletions
diff --git a/src/pal/src/map/map.cpp b/src/pal/src/map/map.cpp new file mode 100644 index 0000000000..f3ec47b846 --- /dev/null +++ b/src/pal/src/map/map.cpp @@ -0,0 +1,2749 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more 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 "pal/environ.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; +LIST_ENTRY MappedViewList; + +#ifndef CORECLR +static PAL_ERROR MAPCreateTempFile(CPalThread *, PINT, PSZ); +#endif // !CORECLR +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( + otiFileMapping, + FileMappingCleanupRoutine, + FileMappingInitializationRoutine, + sizeof(CFileMappingImmutableData), + sizeof(CFileMappingProcessLocalData), + 0, + PAGE_READWRITE | PAGE_READONLY | PAGE_WRITECOPY, + CObjectType::SecuritySupported, + CObjectType::SecurityInfoNotPersisted, + CObjectType::UnnamedObject, + CObjectType::LocalDuplicationOnly, + CObjectType::UnwaitableObject, + CObjectType::SignalingNotApplicable, + CObjectType::ThreadReleaseNotApplicable, + CObjectType::OwnershipNotApplicable + ); + +CAllowedObjectTypes aotFileMapping(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 +#ifndef CORECLR + && NULL == lpName +#endif // !CORECLR + ) + { + // + // 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 + { +#ifndef CORECLR + TRACE( "INVALID_HANDLE_VALUE was the hFile, time to try to create a " + "temporary file" ); + + /* Create a temporary file on the filesystem in order to be + shared across processes. */ + palError = MAPCreateTempFile(pThread, &UnixFd, pImmutableData->szFileName); + if (NO_ERROR != palError) + { + ERROR("Unable to create the temporary file.\n"); + goto ExitInternalCreateFileMapping; + } + bPALCreatedTempFile = TRUE; +#else // !CORECLR + ASSERT("should not get here\n"); + palError = ERROR_INTERNAL_ERROR; + goto ExitInternalCreateFileMapping; +#endif // !CORECLR + } + + 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); + free(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); + free(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; + } + } + } + + if (NO_ERROR == palError) + { + 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; + free(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); + } + free (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; + char* envVar; +#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 + envVar = EnvironGetenv("PAL_ForceRelocs"); + if (envVar) + { + if (strlen(envVar) > 0) + { + forceRelocs = true; + TRACE_(LOADER)("Forcing rebase of image\n"); + } + + free(envVar); + } + + 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|MAP_PRIVATE, -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|MAP_PRIVATE, -1, 0); +#else // defined(_AMD64_) + // First try to reserve virtual memory using ExecutableAllcator. This allows all PE images to be + // near each other and close to the coreclr library which also allows the runtime to generate + // more efficient code (by avoiding usage of jump stubs). + loadedBase = ReserveMemoryFromExecutableAllocator(pThread, virtualSize); + if (loadedBase == NULL) + { + // 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 ¤tHeader = 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, + §ionData); + 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, ¤tHeader, 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); + } + free(pView); // this leaves pLink dangling + } + + TRACE_(LOADER)("MAPUnmapPEFile returning %d\n", retval); + return retval; +} |