mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-06-01 06:12:23 -06:00
2059 lines
73 KiB
C++
2059 lines
73 KiB
C++
/**********************************************************************
|
|
* $Id: cpl_virtualmem.cpp 29330 2015-06-14 12:11:11Z rouault $
|
|
*
|
|
* Name: cpl_virtualmem.cpp
|
|
* Project: CPL - Common Portability Library
|
|
* Purpose: Virtual memory
|
|
* Author: Even Rouault, <even dot rouault at mines dash paris dot org>
|
|
*
|
|
**********************************************************************
|
|
* Copyright (c) 2014, Even Rouault <even dot rouault at mines-paris dot org>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
****************************************************************************/
|
|
|
|
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
|
|
#include "cpl_virtualmem.h"
|
|
#include "cpl_error.h"
|
|
#include "cpl_multiproc.h"
|
|
#include "cpl_atomic_ops.h"
|
|
#include "cpl_conv.h"
|
|
|
|
#if defined(__linux) && defined(CPL_MULTIPROC_PTHREAD)
|
|
|
|
#include <sys/mman.h> /* mmap, munmap, mremap */
|
|
#include <sys/select.h> /* select */
|
|
#include <sys/stat.h> /* open() */
|
|
#include <sys/types.h> /* open() */
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h> /* open() */
|
|
#include <signal.h> /* sigaction */
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h> /* read, write, close, pipe */
|
|
#include <pthread.h>
|
|
|
|
#ifndef HAVE_5ARGS_MREMAP
|
|
#include "cpl_atomic_ops.h"
|
|
#endif
|
|
|
|
/* Linux specific (i.e. non POSIX compliant) features used :
|
|
- returning from a SIGSEGV handler is clearly a POSIX violation, but in
|
|
practice most POSIX systems should be happy.
|
|
- mremap() with 5 args is Linux specific. It is used when the user callback is invited
|
|
to fill a page, we currently mmap() a writable page, let it filled it,
|
|
and afterwards mremap() that temporary page onto the location where the
|
|
fault occured.
|
|
If we have no mremap(), the workaround is to pause other threads that
|
|
consume the current view while we are updating the faulted page, otherwise
|
|
a non-paused thread could access a page that is in the middle of being
|
|
filled... The way we pause those threads is quite original : we send them
|
|
a SIGUSR1 and wait that they are stuck in the temporary SIGUSR1 handler...
|
|
- MAP_ANONYMOUS isn't documented in Posix, but very commonly found (sometimes called MAP_ANON)
|
|
- dealing with the limitation of number of memory mapping regions, and the 65536 limit.
|
|
- other things I've not identified
|
|
*/
|
|
|
|
|
|
#define ALIGN_DOWN(p,pagesize) (void*)(((size_t)(p)) / (pagesize) * (pagesize))
|
|
#define ALIGN_UP(p,pagesize) (void*)(((size_t)(p) + (pagesize) - 1) / (pagesize) * (pagesize))
|
|
|
|
#define DEFAULT_PAGE_SIZE (256*256)
|
|
#define MAXIMUM_PAGE_SIZE (32*1024*1024)
|
|
|
|
/* Linux Kernel limit */
|
|
#define MAXIMUM_COUNT_OF_MAPPINGS 65536
|
|
|
|
#define BYEBYE_ADDR ((void*)(~(size_t)0))
|
|
|
|
#define MAPPING_FOUND "yeah"
|
|
#define MAPPING_NOT_FOUND "doh!"
|
|
|
|
#define SET_BIT(ar,bitnumber) ar[(bitnumber)/8] |= 1 << ((bitnumber) % 8)
|
|
#define UNSET_BIT(ar,bitnumber) ar[(bitnumber)/8] &= ~(1 << ((bitnumber) % 8))
|
|
#define TEST_BIT(ar,bitnumber) (ar[(bitnumber)/8] & (1 << ((bitnumber) % 8)))
|
|
|
|
typedef enum
|
|
{
|
|
OP_LOAD,
|
|
OP_STORE,
|
|
OP_MOVS_RSI_RDI,
|
|
OP_UNKNOWN
|
|
} OpType;
|
|
|
|
struct CPLVirtualMem
|
|
{
|
|
struct CPLVirtualMem *pVMemBase;
|
|
int nRefCount;
|
|
|
|
int bFileMemoryMapped; /* if TRUE, only eAccessMode, pData, pDataToFree, nSize and nPageSize are valid */
|
|
CPLVirtualMemAccessMode eAccessMode;
|
|
|
|
size_t nPageSize;
|
|
void *pData; /* aligned on nPageSize */
|
|
void *pDataToFree; /* returned by mmap(), potentially lower than pData */
|
|
size_t nSize; /* requested size (unrounded) */
|
|
|
|
GByte *pabitMappedPages;
|
|
GByte *pabitRWMappedPages;
|
|
|
|
int nCacheMaxSizeInPages; /* maximum size of page array */
|
|
int *panLRUPageIndices; /* array with indices of cached pages */
|
|
int iLRUStart; /* index in array where to write next page index */
|
|
int nLRUSize; /* current isze of the array */
|
|
|
|
int iLastPage; /* last page accessed */
|
|
int nRetry; /* number of consecutive retries to that last page */
|
|
|
|
int bSingleThreadUsage;
|
|
|
|
CPLVirtualMemCachePageCbk pfnCachePage; /* called when a page is mapped */
|
|
CPLVirtualMemUnCachePageCbk pfnUnCachePage; /* called when a (writable) page is unmapped */
|
|
void *pCbkUserData;
|
|
CPLVirtualMemFreeUserData pfnFreeUserData;
|
|
#ifndef HAVE_5ARGS_MREMAP
|
|
CPLMutex *hMutexThreadArray;
|
|
int nThreads;
|
|
pthread_t *pahThreads;
|
|
#endif
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
/* hVirtualMemManagerMutex protects the 2 following variables */
|
|
CPLVirtualMem **pasVirtualMem;
|
|
int nVirtualMemCount;
|
|
|
|
int pipefd_to_thread[2];
|
|
int pipefd_from_thread[2];
|
|
int pipefd_wait_thread[2];
|
|
CPLJoinableThread *hHelperThread;
|
|
|
|
struct sigaction oldact;
|
|
} CPLVirtualMemManager;
|
|
|
|
typedef struct
|
|
{
|
|
void *pFaultAddr;
|
|
OpType opType;
|
|
pthread_t hRequesterThread;
|
|
} CPLVirtualMemMsgToWorkerThread;
|
|
|
|
static CPLVirtualMemManager* pVirtualMemManager = NULL;
|
|
static CPLMutex* hVirtualMemManagerMutex = NULL;
|
|
|
|
static void CPLVirtualMemManagerInit();
|
|
|
|
#ifdef DEBUG_VIRTUALMEM
|
|
|
|
/************************************************************************/
|
|
/* fprintfstderr() */
|
|
/************************************************************************/
|
|
|
|
static void fprintfstderr(const char* fmt, ...)
|
|
{
|
|
char buffer[80];
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vsnprintf(buffer, sizeof(buffer), fmt, ap);
|
|
va_end(ap);
|
|
int offset = 0;
|
|
while(TRUE)
|
|
{
|
|
int ret = write(2, buffer + offset, strlen(buffer + offset));
|
|
if( ret < 0 && errno == EINTR )
|
|
;
|
|
else
|
|
{
|
|
if( ret == (int)strlen(buffer + offset) )
|
|
break;
|
|
offset += ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/************************************************************************/
|
|
/* CPLGetPageSize() */
|
|
/************************************************************************/
|
|
|
|
size_t CPLGetPageSize(void)
|
|
{
|
|
return (size_t) sysconf(_SC_PAGESIZE);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemManagerRegisterVirtualMem() */
|
|
/************************************************************************/
|
|
|
|
static void CPLVirtualMemManagerRegisterVirtualMem(CPLVirtualMem* ctxt)
|
|
{
|
|
CPLVirtualMemManagerInit();
|
|
|
|
assert(ctxt);
|
|
CPLAcquireMutex(hVirtualMemManagerMutex, 1000.0);
|
|
pVirtualMemManager->pasVirtualMem = (CPLVirtualMem**) CPLRealloc(
|
|
pVirtualMemManager->pasVirtualMem, sizeof(CPLVirtualMem*) * (pVirtualMemManager->nVirtualMemCount + 1) );
|
|
pVirtualMemManager->pasVirtualMem[pVirtualMemManager->nVirtualMemCount] = ctxt;
|
|
pVirtualMemManager->nVirtualMemCount ++;
|
|
CPLReleaseMutex(hVirtualMemManagerMutex);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemManagerUnregisterVirtualMem() */
|
|
/************************************************************************/
|
|
|
|
static void CPLVirtualMemManagerUnregisterVirtualMem(CPLVirtualMem* ctxt)
|
|
{
|
|
int i;
|
|
CPLAcquireMutex(hVirtualMemManagerMutex, 1000.0);
|
|
for(i=0;i<pVirtualMemManager->nVirtualMemCount;i++)
|
|
{
|
|
if( pVirtualMemManager->pasVirtualMem[i] == ctxt )
|
|
{
|
|
if( i < pVirtualMemManager->nVirtualMemCount - 1 )
|
|
{
|
|
memmove( pVirtualMemManager->pasVirtualMem + i,
|
|
pVirtualMemManager->pasVirtualMem + i + 1,
|
|
sizeof(CPLVirtualMem*) * (pVirtualMemManager->nVirtualMemCount - i - 1) );
|
|
}
|
|
pVirtualMemManager->nVirtualMemCount --;
|
|
break;
|
|
}
|
|
}
|
|
CPLReleaseMutex(hVirtualMemManagerMutex);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemNew() */
|
|
/************************************************************************/
|
|
|
|
CPLVirtualMem* CPLVirtualMemNew(size_t nSize,
|
|
size_t nCacheSize,
|
|
size_t nPageSizeHint,
|
|
int bSingleThreadUsage,
|
|
CPLVirtualMemAccessMode eAccessMode,
|
|
CPLVirtualMemCachePageCbk pfnCachePage,
|
|
CPLVirtualMemUnCachePageCbk pfnUnCachePage,
|
|
CPLVirtualMemFreeUserData pfnFreeUserData,
|
|
void *pCbkUserData)
|
|
{
|
|
CPLVirtualMem* ctxt;
|
|
void* pData;
|
|
size_t nMinPageSize = CPLGetPageSize();
|
|
size_t nPageSize = DEFAULT_PAGE_SIZE;
|
|
size_t nCacheMaxSizeInPages;
|
|
size_t nRoundedMappingSize;
|
|
FILE* f;
|
|
int nMappings = 0;
|
|
|
|
assert(nSize > 0);
|
|
assert(pfnCachePage != NULL);
|
|
|
|
if( nPageSizeHint >= nMinPageSize && nPageSizeHint <= MAXIMUM_PAGE_SIZE )
|
|
{
|
|
if( (nPageSizeHint % nMinPageSize) == 0 )
|
|
nPageSize = nPageSizeHint;
|
|
else
|
|
{
|
|
int nbits = 0;
|
|
nPageSize = (size_t)nPageSizeHint;
|
|
while(nPageSize > 0)
|
|
{
|
|
nPageSize >>= 1;
|
|
nbits ++;
|
|
}
|
|
nPageSize = (size_t)1 << (nbits - 1);
|
|
if( nPageSize < (size_t)nPageSizeHint )
|
|
nPageSize <<= 1;
|
|
}
|
|
}
|
|
|
|
if( (nPageSize % nMinPageSize) != 0 )
|
|
nPageSize = nMinPageSize;
|
|
|
|
if( nCacheSize > nSize )
|
|
nCacheSize = nSize;
|
|
else if( nCacheSize == 0 )
|
|
nCacheSize = 1;
|
|
|
|
/* Linux specific */
|
|
/* Count the number of existing memory mappings */
|
|
f = fopen("/proc/self/maps", "rb");
|
|
if( f != NULL )
|
|
{
|
|
char buffer[80];
|
|
while( fgets(buffer, sizeof(buffer), f) != NULL )
|
|
nMappings ++;
|
|
fclose(f);
|
|
}
|
|
|
|
while(TRUE)
|
|
{
|
|
/* /proc/self/maps must not have more than 65K lines */
|
|
nCacheMaxSizeInPages = (nCacheSize + 2 * nPageSize - 1) / nPageSize;
|
|
if( nCacheMaxSizeInPages >
|
|
(size_t)((MAXIMUM_COUNT_OF_MAPPINGS * 9 / 10) - nMappings) )
|
|
nPageSize <<= 1;
|
|
else
|
|
break;
|
|
}
|
|
nRoundedMappingSize = ((nSize + 2 * nPageSize - 1) / nPageSize) * nPageSize;
|
|
pData = mmap(NULL, nRoundedMappingSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
if( pData == MAP_FAILED )
|
|
{
|
|
perror("mmap");
|
|
return NULL;
|
|
}
|
|
ctxt = (CPLVirtualMem* )CPLCalloc(1, sizeof(CPLVirtualMem));
|
|
ctxt->nRefCount = 1;
|
|
ctxt->bFileMemoryMapped = FALSE;
|
|
ctxt->eAccessMode = eAccessMode;
|
|
ctxt->pDataToFree = pData;
|
|
ctxt->pData = ALIGN_UP(pData, nPageSize);
|
|
ctxt->nPageSize = nPageSize;
|
|
ctxt->pabitMappedPages = (GByte*)CPLCalloc(1, (nRoundedMappingSize / nPageSize + 7) / 8);
|
|
assert(ctxt->pabitMappedPages);
|
|
ctxt->pabitRWMappedPages = (GByte*)CPLCalloc(1, (nRoundedMappingSize / nPageSize + 7) / 8);
|
|
assert(ctxt->pabitRWMappedPages);
|
|
/* we need at least 2 pages in case for a rep movs instruction */
|
|
/* that operate in the view */
|
|
ctxt->nCacheMaxSizeInPages = nCacheMaxSizeInPages;
|
|
ctxt->panLRUPageIndices = (int*)CPLMalloc(ctxt->nCacheMaxSizeInPages * sizeof(int));
|
|
assert(ctxt->panLRUPageIndices);
|
|
ctxt->nSize = nSize;
|
|
ctxt->iLRUStart = 0;
|
|
ctxt->nLRUSize = 0;
|
|
ctxt->iLastPage = -1;
|
|
ctxt->nRetry = 0;
|
|
ctxt->bSingleThreadUsage = bSingleThreadUsage;
|
|
ctxt->pfnCachePage = pfnCachePage;
|
|
ctxt->pfnUnCachePage = pfnUnCachePage;
|
|
ctxt->pfnFreeUserData = pfnFreeUserData;
|
|
ctxt->pCbkUserData = pCbkUserData;
|
|
#ifndef HAVE_5ARGS_MREMAP
|
|
if( !ctxt->bSingleThreadUsage )
|
|
{
|
|
ctxt->hMutexThreadArray = CPLCreateMutex();
|
|
assert(ctxt->hMutexThreadArray != NULL);
|
|
CPLReleaseMutex(ctxt->hMutexThreadArray);
|
|
ctxt->nThreads = 0;
|
|
ctxt->pahThreads = NULL;
|
|
}
|
|
#endif
|
|
|
|
CPLVirtualMemManagerRegisterVirtualMem(ctxt);
|
|
|
|
return ctxt;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLIsVirtualMemFileMapAvailable() */
|
|
/************************************************************************/
|
|
|
|
int CPLIsVirtualMemFileMapAvailable(void)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemFileMapNew() */
|
|
/************************************************************************/
|
|
|
|
CPLVirtualMem *CPLVirtualMemFileMapNew( VSILFILE* fp,
|
|
vsi_l_offset nOffset,
|
|
vsi_l_offset nLength,
|
|
CPLVirtualMemAccessMode eAccessMode,
|
|
CPLVirtualMemFreeUserData pfnFreeUserData,
|
|
void *pCbkUserData )
|
|
{
|
|
#if SIZEOF_VOIDP == 4
|
|
if( nLength != (size_t)nLength )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"nLength = " CPL_FRMT_GUIB " incompatible with 32 bit architecture",
|
|
nLength);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
int fd = (int) (size_t) VSIFGetNativeFileDescriptorL(fp);
|
|
if( fd == 0 )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Cannot operate on a virtual file");
|
|
return NULL;
|
|
}
|
|
|
|
off_t nAlignedOffset = (nOffset / CPLGetPageSize()) * CPLGetPageSize();
|
|
size_t nAligment = nOffset - nAlignedOffset;
|
|
size_t nMappingSize = nLength + nAligment;
|
|
|
|
/* We need to ensure that the requested extent fits into the file size */
|
|
/* otherwise SIGBUS errors will occur when using the mapping */
|
|
vsi_l_offset nCurPos = VSIFTellL(fp);
|
|
VSIFSeekL(fp, 0, SEEK_END);
|
|
vsi_l_offset nFileSize = VSIFTellL(fp);
|
|
if( nFileSize < nOffset + nLength )
|
|
{
|
|
if( eAccessMode != VIRTUALMEM_READWRITE )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Trying to map an extent outside of the file");
|
|
VSIFSeekL(fp, nCurPos, SEEK_SET);
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
char ch = 0;
|
|
if( VSIFSeekL(fp, nOffset + nLength - 1, SEEK_SET) != 0 ||
|
|
VSIFWriteL(&ch, 1, 1, fp) != 1 )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Cannot extend file to mapping size");
|
|
VSIFSeekL(fp, nCurPos, SEEK_SET);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
VSIFSeekL(fp, nCurPos, SEEK_SET);
|
|
|
|
void* addr = mmap(NULL, nMappingSize,
|
|
(eAccessMode == VIRTUALMEM_READWRITE) ? PROT_READ | PROT_WRITE : PROT_READ,
|
|
MAP_SHARED, fd, nAlignedOffset);
|
|
if( addr == MAP_FAILED )
|
|
{
|
|
int myerrno = errno;
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"mmap() failed : %s", strerror(myerrno));
|
|
return NULL;
|
|
}
|
|
|
|
CPLVirtualMem* ctxt = (CPLVirtualMem* )CPLCalloc(1, sizeof(CPLVirtualMem));
|
|
ctxt->nRefCount = 1;
|
|
ctxt->eAccessMode = eAccessMode;
|
|
ctxt->bFileMemoryMapped = TRUE;
|
|
ctxt->pData = (GByte*) addr + nAligment;
|
|
ctxt->pDataToFree = addr;
|
|
ctxt->nSize = nLength;
|
|
ctxt->nPageSize = CPLGetPageSize();
|
|
ctxt->bSingleThreadUsage = FALSE;
|
|
ctxt->pfnFreeUserData = pfnFreeUserData;
|
|
ctxt->pCbkUserData = pCbkUserData;
|
|
|
|
return ctxt;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemDerivedNew() */
|
|
/************************************************************************/
|
|
|
|
CPLVirtualMem *CPLVirtualMemDerivedNew(CPLVirtualMem* pVMemBase,
|
|
vsi_l_offset nOffset,
|
|
vsi_l_offset nSize,
|
|
CPLVirtualMemFreeUserData pfnFreeUserData,
|
|
void *pCbkUserData)
|
|
{
|
|
if( nOffset + nSize > pVMemBase->nSize )
|
|
return NULL;
|
|
|
|
CPLVirtualMem* ctxt = (CPLVirtualMem* )CPLCalloc(1, sizeof(CPLVirtualMem));
|
|
ctxt->nRefCount = 1;
|
|
ctxt->pVMemBase = pVMemBase;
|
|
pVMemBase->nRefCount ++;
|
|
ctxt->eAccessMode = pVMemBase->eAccessMode;
|
|
ctxt->bFileMemoryMapped = pVMemBase->bFileMemoryMapped;
|
|
ctxt->pData = (GByte*) pVMemBase->pData + nOffset;
|
|
ctxt->pDataToFree = NULL;
|
|
ctxt->nSize = nSize;
|
|
ctxt->nPageSize = pVMemBase->nPageSize;
|
|
ctxt->bSingleThreadUsage = pVMemBase->bSingleThreadUsage;
|
|
ctxt->pfnFreeUserData = pfnFreeUserData;
|
|
ctxt->pCbkUserData = pCbkUserData;
|
|
|
|
return ctxt;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemFree() */
|
|
/************************************************************************/
|
|
|
|
void CPLVirtualMemFree(CPLVirtualMem* ctxt)
|
|
{
|
|
size_t nRoundedMappingSize;
|
|
|
|
if( ctxt == NULL || --(ctxt->nRefCount) > 0 )
|
|
return;
|
|
|
|
if( ctxt->pVMemBase != NULL )
|
|
{
|
|
CPLVirtualMemFree(ctxt->pVMemBase);
|
|
if( ctxt->pfnFreeUserData != NULL )
|
|
ctxt->pfnFreeUserData(ctxt->pCbkUserData);
|
|
CPLFree(ctxt);
|
|
return;
|
|
}
|
|
|
|
if( ctxt->bFileMemoryMapped )
|
|
{
|
|
size_t nMappingSize = ctxt->nSize + (GByte*)ctxt->pData - (GByte*)ctxt->pDataToFree;
|
|
assert(munmap(ctxt->pDataToFree, nMappingSize) == 0);
|
|
if( ctxt->pfnFreeUserData != NULL )
|
|
ctxt->pfnFreeUserData(ctxt->pCbkUserData);
|
|
CPLFree(ctxt);
|
|
return;
|
|
}
|
|
|
|
CPLVirtualMemManagerUnregisterVirtualMem(ctxt);
|
|
|
|
nRoundedMappingSize = ((ctxt->nSize + 2 * ctxt->nPageSize - 1) /
|
|
ctxt->nPageSize) * ctxt->nPageSize;
|
|
if( ctxt->eAccessMode == VIRTUALMEM_READWRITE &&
|
|
ctxt->pfnUnCachePage != NULL )
|
|
{
|
|
size_t i;
|
|
for(i = 0; i < nRoundedMappingSize / ctxt->nPageSize; i++)
|
|
{
|
|
if( TEST_BIT(ctxt->pabitRWMappedPages, i) )
|
|
{
|
|
void* addr = (char*)ctxt->pData + i * ctxt->nPageSize;
|
|
ctxt->pfnUnCachePage(ctxt,
|
|
i * ctxt->nPageSize,
|
|
addr,
|
|
ctxt->nPageSize,
|
|
ctxt->pCbkUserData);
|
|
}
|
|
}
|
|
}
|
|
assert(munmap(ctxt->pDataToFree, nRoundedMappingSize) == 0);
|
|
CPLFree(ctxt->pabitMappedPages);
|
|
CPLFree(ctxt->pabitRWMappedPages);
|
|
CPLFree(ctxt->panLRUPageIndices);
|
|
#ifndef HAVE_5ARGS_MREMAP
|
|
if( !ctxt->bSingleThreadUsage )
|
|
{
|
|
CPLFree(ctxt->pahThreads);
|
|
CPLDestroyMutex(ctxt->hMutexThreadArray);
|
|
}
|
|
#endif
|
|
if( ctxt->pfnFreeUserData != NULL )
|
|
ctxt->pfnFreeUserData(ctxt->pCbkUserData);
|
|
|
|
CPLFree(ctxt);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemGetAddr() */
|
|
/************************************************************************/
|
|
|
|
void* CPLVirtualMemGetAddr(CPLVirtualMem* ctxt)
|
|
{
|
|
return ctxt->pData;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemIsFileMapping() */
|
|
/************************************************************************/
|
|
|
|
int CPLVirtualMemIsFileMapping(CPLVirtualMem* ctxt)
|
|
{
|
|
return ctxt->bFileMemoryMapped;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemGetAccessMode() */
|
|
/************************************************************************/
|
|
|
|
CPLVirtualMemAccessMode CPLVirtualMemGetAccessMode(CPLVirtualMem* ctxt)
|
|
{
|
|
return ctxt->eAccessMode;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemGetPageSize() */
|
|
/************************************************************************/
|
|
|
|
size_t CPLVirtualMemGetPageSize(CPLVirtualMem* ctxt)
|
|
{
|
|
return ctxt->nPageSize;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemGetSize() */
|
|
/************************************************************************/
|
|
|
|
size_t CPLVirtualMemGetSize(CPLVirtualMem* ctxt)
|
|
{
|
|
return ctxt->nSize;
|
|
}
|
|
|
|
#ifndef HAVE_5ARGS_MREMAP
|
|
|
|
static volatile int nCountThreadsInSigUSR1 = 0;
|
|
static volatile int nWaitHelperThread = 0;
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemSIGUSR1Handler() */
|
|
/************************************************************************/
|
|
|
|
static void CPLVirtualMemSIGUSR1Handler(int signum_unused,
|
|
siginfo_t* the_info_unused,
|
|
void* the_ctxt_unused)
|
|
{
|
|
/* fprintfstderr("entering CPLVirtualMemSIGUSR1Handler %X\n", pthread_self()); */
|
|
(void)signum_unused;
|
|
(void)the_info_unused;
|
|
(void)the_ctxt_unused;
|
|
/* I guess this is only POSIX correct if it is implemented by an intrinsic */
|
|
CPLAtomicInc(&nCountThreadsInSigUSR1);
|
|
while( nWaitHelperThread )
|
|
usleep(1); /* not explicitly indicated as signal-async-safe, but hopefully ok */
|
|
CPLAtomicDec(&nCountThreadsInSigUSR1);
|
|
/* fprintfstderr("leaving CPLVirtualMemSIGUSR1Handler %X\n", pthread_self()); */
|
|
}
|
|
#endif
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemIsAccessThreadSafe() */
|
|
/************************************************************************/
|
|
|
|
int CPLVirtualMemIsAccessThreadSafe(CPLVirtualMem* ctxt)
|
|
{
|
|
return !ctxt->bSingleThreadUsage;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemDeclareThread() */
|
|
/************************************************************************/
|
|
|
|
void CPLVirtualMemDeclareThread(CPLVirtualMem* ctxt)
|
|
{
|
|
if( ctxt->bFileMemoryMapped )
|
|
return;
|
|
#ifndef HAVE_5ARGS_MREMAP
|
|
assert( !ctxt->bSingleThreadUsage );
|
|
CPLAcquireMutex(ctxt->hMutexThreadArray, 1000.0);
|
|
ctxt->pahThreads = (pthread_t*) CPLRealloc(ctxt->pahThreads,
|
|
(ctxt->nThreads + 1) * sizeof(pthread_t));
|
|
ctxt->pahThreads[ctxt->nThreads] = pthread_self();
|
|
ctxt->nThreads ++;
|
|
|
|
CPLReleaseMutex(ctxt->hMutexThreadArray);
|
|
#endif
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemUnDeclareThread() */
|
|
/************************************************************************/
|
|
|
|
void CPLVirtualMemUnDeclareThread(CPLVirtualMem* ctxt)
|
|
{
|
|
if( ctxt->bFileMemoryMapped )
|
|
return;
|
|
#ifndef HAVE_5ARGS_MREMAP
|
|
int i;
|
|
pthread_t self = pthread_self();
|
|
assert( !ctxt->bSingleThreadUsage );
|
|
CPLAcquireMutex(ctxt->hMutexThreadArray, 1000.0);
|
|
for(i = 0; i < ctxt->nThreads; i++)
|
|
{
|
|
if( ctxt->pahThreads[i] == self )
|
|
{
|
|
if( i < ctxt->nThreads - 1 )
|
|
memmove(ctxt->pahThreads + i + 1,
|
|
ctxt->pahThreads + i,
|
|
(ctxt->nThreads - 1 - i) * sizeof(pthread_t));
|
|
ctxt->nThreads --;
|
|
break;
|
|
}
|
|
}
|
|
|
|
CPLReleaseMutex(ctxt->hMutexThreadArray);
|
|
#endif
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemGetPageToFill() */
|
|
/************************************************************************/
|
|
|
|
/* Must be paired with CPLVirtualMemAddPage */
|
|
static
|
|
void* CPLVirtualMemGetPageToFill(CPLVirtualMem* ctxt, void* start_page_addr)
|
|
{
|
|
void* pPageToFill;
|
|
|
|
if( ctxt->bSingleThreadUsage )
|
|
{
|
|
pPageToFill = start_page_addr;
|
|
assert(mprotect(pPageToFill, ctxt->nPageSize, PROT_READ | PROT_WRITE) == 0);
|
|
}
|
|
else
|
|
{
|
|
#ifndef HAVE_5ARGS_MREMAP
|
|
CPLAcquireMutex(ctxt->hMutexThreadArray, 1000.0);
|
|
if( ctxt->nThreads == 1 )
|
|
{
|
|
pPageToFill = start_page_addr;
|
|
assert(mprotect(pPageToFill, ctxt->nPageSize, PROT_READ | PROT_WRITE) == 0);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
/* Allocate a temporary writable page that the user */
|
|
/* callback can fill */
|
|
pPageToFill = mmap(NULL, ctxt->nPageSize,
|
|
PROT_READ | PROT_WRITE,
|
|
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
assert(pPageToFill != MAP_FAILED);
|
|
}
|
|
}
|
|
return pPageToFill;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemAddPage() */
|
|
/************************************************************************/
|
|
|
|
static
|
|
void CPLVirtualMemAddPage(CPLVirtualMem* ctxt, void* target_addr, void* pPageToFill,
|
|
OpType opType, pthread_t hRequesterThread)
|
|
{
|
|
size_t iPage = (int)(((char*)target_addr - (char*)ctxt->pData) / ctxt->nPageSize);
|
|
if( ctxt->nLRUSize == ctxt->nCacheMaxSizeInPages )
|
|
{
|
|
/* fprintfstderr("uncaching page %d\n", iPage); */
|
|
int nOldPage = ctxt->panLRUPageIndices[ctxt->iLRUStart];
|
|
void* addr = (char*)ctxt->pData + nOldPage * ctxt->nPageSize;
|
|
if( ctxt->eAccessMode == VIRTUALMEM_READWRITE &&
|
|
ctxt->pfnUnCachePage != NULL &&
|
|
TEST_BIT(ctxt->pabitRWMappedPages, nOldPage) )
|
|
{
|
|
size_t nToBeEvicted = ctxt->nPageSize;
|
|
if( (char*)addr + nToBeEvicted >= (char*) ctxt->pData + ctxt->nSize )
|
|
nToBeEvicted = (char*) ctxt->pData + ctxt->nSize - (char*)addr;
|
|
|
|
ctxt->pfnUnCachePage(ctxt,
|
|
nOldPage * ctxt->nPageSize,
|
|
addr,
|
|
nToBeEvicted,
|
|
ctxt->pCbkUserData);
|
|
}
|
|
/* "Free" the least recently used page */
|
|
UNSET_BIT(ctxt->pabitMappedPages, nOldPage);
|
|
UNSET_BIT(ctxt->pabitRWMappedPages, nOldPage);
|
|
/* Free the old page */
|
|
/* Not sure how portable it is to do that that way... */
|
|
assert(mmap(addr, ctxt->nPageSize, PROT_NONE,
|
|
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) == addr);
|
|
}
|
|
ctxt->panLRUPageIndices[ctxt->iLRUStart] = iPage;
|
|
ctxt->iLRUStart = (ctxt->iLRUStart + 1) % ctxt->nCacheMaxSizeInPages;
|
|
if( ctxt->nLRUSize < ctxt->nCacheMaxSizeInPages )
|
|
{
|
|
ctxt->nLRUSize ++;
|
|
}
|
|
SET_BIT(ctxt->pabitMappedPages, iPage);
|
|
|
|
if( ctxt->bSingleThreadUsage )
|
|
{
|
|
if( opType == OP_STORE && ctxt->eAccessMode == VIRTUALMEM_READWRITE )
|
|
{
|
|
/* let (and mark) the page writable since the instruction that triggered */
|
|
/* the fault is a store */
|
|
SET_BIT(ctxt->pabitRWMappedPages, iPage);
|
|
}
|
|
else if( ctxt->eAccessMode != VIRTUALMEM_READONLY )
|
|
{
|
|
assert(mprotect(target_addr, ctxt->nPageSize, PROT_READ) == 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef HAVE_5ARGS_MREMAP
|
|
(void)hRequesterThread;
|
|
|
|
if( opType == OP_STORE && ctxt->eAccessMode == VIRTUALMEM_READWRITE )
|
|
{
|
|
/* let (and mark) the page writable since the instruction that triggered */
|
|
/* the fault is a store */
|
|
SET_BIT(ctxt->pabitRWMappedPages, iPage);
|
|
}
|
|
else if( ctxt->eAccessMode != VIRTUALMEM_READONLY )
|
|
{
|
|
/* Turn the temporary page read-only before remapping it. We will only turn it */
|
|
/* writtable when a new fault occurs (and that the mapping is writable) */
|
|
assert(mprotect(pPageToFill, ctxt->nPageSize, PROT_READ) == 0);
|
|
}
|
|
/* Can now remap the pPageToFill onto the target page */
|
|
assert(mremap(pPageToFill, ctxt->nPageSize, ctxt->nPageSize,
|
|
MREMAP_MAYMOVE | MREMAP_FIXED, target_addr) == target_addr);
|
|
|
|
#else
|
|
if (ctxt->nThreads > 1 )
|
|
{
|
|
int i;
|
|
|
|
/* Pause threads that share this mem view */
|
|
CPLAtomicInc(&nWaitHelperThread);
|
|
|
|
/* Install temporary SIGUSR1 signal handler */
|
|
struct sigaction act, oldact;
|
|
act.sa_sigaction = CPLVirtualMemSIGUSR1Handler;
|
|
sigemptyset (&act.sa_mask);
|
|
/* We don't want the sigsegv handler to be called when we are */
|
|
/* running the sigusr1 handler */
|
|
assert(sigaddset(&act.sa_mask, SIGSEGV) == 0);
|
|
act.sa_flags = 0;
|
|
assert(sigaction(SIGUSR1, &act, &oldact) == 0);
|
|
|
|
for(i = 0; i < ctxt->nThreads; i++)
|
|
{
|
|
if( ctxt->pahThreads[i] != hRequesterThread )
|
|
{
|
|
/* fprintfstderr("stopping thread %X\n", ctxt->pahThreads[i]); */
|
|
assert(pthread_kill( ctxt->pahThreads[i], SIGUSR1 ) == 0);
|
|
}
|
|
}
|
|
|
|
/* Wait that they are all paused */
|
|
while( nCountThreadsInSigUSR1 != ctxt->nThreads-1 )
|
|
usleep(1);
|
|
|
|
/* Restore old SIGUSR1 signal handler */
|
|
assert(sigaction(SIGUSR1, &oldact, NULL) == 0);
|
|
|
|
assert(mprotect(target_addr, ctxt->nPageSize, PROT_READ | PROT_WRITE) == 0);
|
|
/*fprintfstderr("memcpying page %d\n", iPage);*/
|
|
memcpy(target_addr, pPageToFill, ctxt->nPageSize);
|
|
|
|
if( opType == OP_STORE && ctxt->eAccessMode == VIRTUALMEM_READWRITE )
|
|
{
|
|
/* let (and mark) the page writable since the instruction that triggered */
|
|
/* the fault is a store */
|
|
SET_BIT(ctxt->pabitRWMappedPages, iPage);
|
|
}
|
|
else
|
|
{
|
|
assert(mprotect(target_addr, ctxt->nPageSize, PROT_READ) == 0);
|
|
}
|
|
|
|
/* Wake up sleeping threads */
|
|
CPLAtomicDec(&nWaitHelperThread);
|
|
while( nCountThreadsInSigUSR1 != 0 )
|
|
usleep(1);
|
|
|
|
assert(munmap(pPageToFill, ctxt->nPageSize) == 0);
|
|
}
|
|
else
|
|
{
|
|
if( opType == OP_STORE && ctxt->eAccessMode == VIRTUALMEM_READWRITE )
|
|
{
|
|
/* let (and mark) the page writable since the instruction that triggered */
|
|
/* the fault is a store */
|
|
SET_BIT(ctxt->pabitRWMappedPages, iPage);
|
|
}
|
|
else if( ctxt->eAccessMode != VIRTUALMEM_READONLY )
|
|
{
|
|
assert(mprotect(target_addr, ctxt->nPageSize, PROT_READ) == 0);
|
|
}
|
|
}
|
|
|
|
CPLReleaseMutex(ctxt->hMutexThreadArray);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemGetOpTypeImm() */
|
|
/************************************************************************/
|
|
|
|
#if defined(__x86_64__) || defined(__i386__)
|
|
static OpType CPLVirtualMemGetOpTypeImm(GByte val_rip)
|
|
{
|
|
OpType opType = OP_UNKNOWN;
|
|
if( (/*val_rip >= 0x00 &&*/ val_rip <= 0x07) ||
|
|
(val_rip >= 0x40 && val_rip <= 0x47) ) /* add $,(X) */
|
|
opType = OP_STORE;
|
|
if( (val_rip >= 0x08 && val_rip <= 0x0f) ||
|
|
(val_rip >= 0x48 && val_rip <= 0x4f) ) /* or $,(X) */
|
|
opType = OP_STORE;
|
|
if( (val_rip >= 0x20 && val_rip <= 0x27) ||
|
|
(val_rip >= 0x60 && val_rip <= 0x67) ) /* and $,(X) */
|
|
opType = OP_STORE;
|
|
if( (val_rip >= 0x28 && val_rip <= 0x2f) ||
|
|
(val_rip >= 0x68 && val_rip <= 0x6f) ) /* sub $,(X) */
|
|
opType = OP_STORE;
|
|
if( (val_rip >= 0x30 && val_rip <= 0x37) ||
|
|
(val_rip >= 0x70 && val_rip <= 0x77) ) /* xor $,(X) */
|
|
opType = OP_STORE;
|
|
if( (val_rip >= 0x38 && val_rip <= 0x3f) ||
|
|
(val_rip >= 0x78 && val_rip <= 0x7f) ) /* cmp $,(X) */
|
|
opType = OP_LOAD;
|
|
return opType;
|
|
}
|
|
#endif
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemGetOpType() */
|
|
/************************************************************************/
|
|
|
|
/* We don't need exhaustivity. It is just a hint for an optimization : */
|
|
/* if the fault occurs on a store operation, then we can directly put */
|
|
/* the page in writable mode if the mapping allows it */
|
|
|
|
static OpType CPLVirtualMemGetOpType(const GByte* rip)
|
|
{
|
|
OpType opType = OP_UNKNOWN;
|
|
|
|
#if defined(__x86_64__) || defined(__i386__)
|
|
switch(rip[0])
|
|
{
|
|
case 0x00: /* add %al,(%rax) */
|
|
case 0x01: /* add %eax,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x02: /* add (%rax),%al */
|
|
case 0x03: /* add (%rax),%eax */
|
|
opType = OP_LOAD;
|
|
break;
|
|
|
|
case 0x08: /* or %al,(%rax) */
|
|
case 0x09: /* or %eax,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x0a: /* or (%rax),%al */
|
|
case 0x0b: /* or (%rax),%eax */
|
|
opType = OP_LOAD;
|
|
break;
|
|
|
|
case 0x0f:
|
|
{
|
|
switch(rip[1])
|
|
{
|
|
case 0xb6: /* movzbl (%rax),%eax */
|
|
case 0xb7: /* movzwl (%rax),%eax */
|
|
case 0xbe: /* movsbl (%rax),%eax */
|
|
case 0xbf: /* movswl (%rax),%eax */
|
|
opType = OP_LOAD;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 0xc6: /* movb $,(%rax) */
|
|
case 0xc7: /* movl $,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
|
|
case 0x20: /* and %al,(%rax) */
|
|
case 0x21: /* and %eax,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x22: /* and (%rax),%al */
|
|
case 0x23: /* and (%rax),%eax */
|
|
opType = OP_LOAD;
|
|
break;
|
|
|
|
case 0x28: /* sub %al,(%rax) */
|
|
case 0x29: /* sub %eax,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x2a: /* sub (%rax),%al */
|
|
case 0x2b: /* sub (%rax),%eax */
|
|
opType = OP_LOAD;
|
|
break;
|
|
|
|
case 0x30: /* xor %al,(%rax) */
|
|
case 0x31: /* xor %eax,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x32: /* xor (%rax),%al */
|
|
case 0x33: /* xor (%rax),%eax */
|
|
opType = OP_LOAD;
|
|
break;
|
|
|
|
case 0x38: /* cmp %al,(%rax) */
|
|
case 0x39: /* cmp %eax,(%rax) */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x40:
|
|
{
|
|
switch(rip[1])
|
|
{
|
|
case 0x00: /* add %spl,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x02: /* add (%rax),%spl */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x28: /* sub %spl,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x2a: /* sub (%rax),%spl */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x3a: /* cmp (%rax),%spl */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x8a: /* mov (%rax),%spl */
|
|
opType = OP_LOAD;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
#if defined(__x86_64__)
|
|
case 0x41: /* reg=%al/%eax, X=%r8 */
|
|
case 0x42: /* reg=%al/%eax, X=%rax,%r8,1 */
|
|
case 0x43: /* reg=%al/%eax, X=%r8,%r8,1 */
|
|
case 0x44: /* reg=%r8b/%r8w, X = %rax */
|
|
case 0x45: /* reg=%r8b/%r8w, X = %r8 */
|
|
case 0x46: /* reg=%r8b/%r8w, X = %rax,%r8,1 */
|
|
case 0x47: /* reg=%r8b/%r8w, X = %r8,%r8,1 */
|
|
{
|
|
switch(rip[1])
|
|
{
|
|
case 0x00: /* add regb,(X) */
|
|
case 0x01: /* add regl,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x02: /* add (X),regb */
|
|
case 0x03: /* add (X),regl */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x0f:
|
|
{
|
|
switch(rip[2])
|
|
{
|
|
case 0xb6: /* movzbl (X),regl */
|
|
case 0xb7: /* movzwl (X),regl */
|
|
case 0xbe: /* movsbl (X),regl */
|
|
case 0xbf: /* movswl (X),regl */
|
|
opType = OP_LOAD;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 0x28: /* sub regb,(X) */
|
|
case 0x29: /* sub regl,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x2a: /* sub (X),regb */
|
|
case 0x2b: /* sub (X),regl */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x38: /* cmp regb,(X) */
|
|
case 0x39: /* cmp regl,(X) */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x80: /* cmpb,... $,(X) */
|
|
case 0x81: /* cmpl,... $,(X) */
|
|
case 0x83: /* cmpl,... $,(X) */
|
|
opType = CPLVirtualMemGetOpTypeImm(rip[2]);
|
|
break;
|
|
case 0x88: /* mov regb,(X) */
|
|
case 0x89: /* mov regl,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x8a: /* mov (X),regb */
|
|
case 0x8b: /* mov (X),regl */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0xc6: /* movb $,(X) */
|
|
case 0xc7: /* movl $,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x84: /* test %al,(X) */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0xf6: /* testb $,(X) or notb (X) */
|
|
case 0xf7: /* testl $,(X) or notl (X)*/
|
|
{
|
|
if( rip[2] < 0x10 ) /* test (X) */
|
|
opType = OP_LOAD;
|
|
else /* not (X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 0x48: /* reg=%rax, X=%rax or %rax,%rax,1 */
|
|
case 0x49: /* reg=%rax, X=%r8 or %r8,%rax,1 */
|
|
case 0x4a: /* reg=%rax, X=%rax,%r8,1 */
|
|
case 0x4b: /* reg=%rax, X=%r8,%r8,1 */
|
|
case 0x4c: /* reg=%r8, X=%rax or %rax,%rax,1 */
|
|
case 0x4d: /* reg=%r8, X=%r8 or %r8,%rax,1 */
|
|
case 0x4e: /* reg=%r8, X=%rax,%r8,1 */
|
|
case 0x4f: /* reg=%r8, X=%r8,%r8,1 */
|
|
{
|
|
switch(rip[1])
|
|
{
|
|
case 0x01: /* add reg,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x03: /* add (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
|
|
case 0x09: /* or reg,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x0b: /* or (%rax),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x0f:
|
|
{
|
|
switch(rip[2])
|
|
{
|
|
case 0xc3: /* movnti reg,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 0x21: /* and reg,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x23: /* and (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
|
|
case 0x29: /* sub reg,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x2b: /* sub (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
|
|
case 0x31: /* xor reg,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x33: /* xor (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
|
|
case 0x39: /* cmp reg,(X) */
|
|
opType = OP_LOAD;
|
|
break;
|
|
|
|
case 0x81:
|
|
case 0x83:
|
|
opType = CPLVirtualMemGetOpTypeImm(rip[2]);
|
|
break;
|
|
|
|
case 0x85: /* test reg,(X) */
|
|
opType = OP_LOAD;
|
|
break;
|
|
|
|
case 0x89: /* mov reg,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x8b: /* mov (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
|
|
case 0xc7: /* movq $,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
|
|
case 0xf7:
|
|
{
|
|
if( rip[2] < 0x10 ) /* testq $,(X) */
|
|
opType = OP_LOAD;
|
|
else /* notq (X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
case 0x66:
|
|
{
|
|
switch(rip[1])
|
|
{
|
|
case 0x01: /* add %ax,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x03: /* add (%rax),%ax */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x0f:
|
|
{
|
|
switch(rip[2])
|
|
{
|
|
case 0x2e: /* ucomisd (%rax),%xmm0 */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x6f: /* movdqa (%rax),%xmm0 */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x7f: /* movdqa %xmm0,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0xb6: /* movzbw (%rax),%ax */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0xe7: /* movntdq %xmm0,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 0x29: /* sub %ax,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x2b: /* sub (%rax),%ax */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x39: /* cmp %ax,(%rax) */
|
|
opType = OP_LOAD;
|
|
break;
|
|
#if defined(__x86_64__)
|
|
case 0x41: /* reg = %ax (or %xmm0), X = %r8 */
|
|
case 0x42: /* reg = %ax (or %xmm0), X = %rax,%r8,1 */
|
|
case 0x43: /* reg = %ax (or %xmm0), X = %r8,%r8,1 */
|
|
case 0x44: /* reg = %r8w (or %xmm8), X = %rax */
|
|
case 0x45: /* reg = %r8w (or %xmm8), X = %r8 */
|
|
case 0x46: /* reg = %r8w (or %xmm8), X = %rax,%r8,1 */
|
|
case 0x47: /* reg = %r8w (or %xmm8), X = %r8,%r8,1 */
|
|
{
|
|
switch(rip[2])
|
|
{
|
|
case 0x01: /* add reg,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x03: /* add (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x0f:
|
|
{
|
|
switch(rip[3])
|
|
{
|
|
case 0x2e: /* ucomisd (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x6f: /* movdqa (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x7f: /* movdqa reg,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0xb6: /* movzbw (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0xe7: /* movntdq reg,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 0x29: /* sub reg,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x2b: /* sub (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x39: /* cmp reg,(X) */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x81: /* cmpw,... $,(X) */
|
|
case 0x83: /* cmpw,... $,(X) */
|
|
opType = CPLVirtualMemGetOpTypeImm(rip[3]);
|
|
break;
|
|
case 0x85: /* test reg,(X) */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x89: /* mov reg,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x8b: /* mov (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0xc7: /* movw $,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0xf7:
|
|
{
|
|
if( rip[3] < 0x10 ) /* testw $,(X) */
|
|
opType = OP_LOAD;
|
|
else /* notw (X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
case 0x81: /* cmpw,... $,(%rax) */
|
|
case 0x83: /* cmpw,... $,(%rax) */
|
|
opType = CPLVirtualMemGetOpTypeImm(rip[2]);
|
|
break;
|
|
|
|
case 0x85: /* test %ax,(%rax) */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x89: /* mov %ax,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x8b: /* mov (%rax),%ax */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0xc7: /* movw $,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0xf3:
|
|
{
|
|
switch( rip[2] )
|
|
{
|
|
case 0xa5: /* rep movsw %ds:(%rsi),%es:(%rdi) */
|
|
opType = OP_MOVS_RSI_RDI;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 0xf7: /* testw $,(%rax) or notw (%rax) */
|
|
{
|
|
if( rip[2] < 0x10 ) /* test */
|
|
opType = OP_LOAD;
|
|
else /* not */
|
|
opType = OP_STORE;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 0x80: /* cmpb,... $,(%rax) */
|
|
case 0x81: /* cmpl,... $,(%rax) */
|
|
case 0x83: /* cmpl,... $,(%rax) */
|
|
opType = CPLVirtualMemGetOpTypeImm(rip[1]);
|
|
break;
|
|
case 0x84: /* test %al,(%rax) */
|
|
case 0x85: /* test %eax,(%rax) */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x88: /* mov %al,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x89: /* mov %eax,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x8a: /* mov (%rax),%al */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x8b: /* mov (%rax),%eax */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0xd9: /* 387 float */
|
|
{
|
|
if( rip[1] < 0x08 ) /* flds (%eax) */
|
|
opType = OP_LOAD;
|
|
else if( rip[1] >= 0x18 && rip[1] <= 0x20 ) /* fstps (%eax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
}
|
|
case 0xf2: /* SSE 2 */
|
|
{
|
|
switch(rip[1])
|
|
{
|
|
case 0x0f:
|
|
{
|
|
switch(rip[2])
|
|
{
|
|
case 0x10: /* movsd (%rax),%xmm0 */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x11: /* movsd %xmm0,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x58: /* addsd (%rax),%xmm0 */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x59: /* mulsd (%rax),%xmm0 */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x5c: /* subsd (%rax),%xmm0 */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x5e: /* divsd (%rax),%xmm0 */
|
|
opType = OP_LOAD;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
#if defined(__x86_64__)
|
|
case 0x41: /* reg=%xmm0, X=%r8 or %r8,%rax,1 */
|
|
case 0x42: /* reg=%xmm0, X=%rax,%r8,1 */
|
|
case 0x43: /* reg=%xmm0, X=%r8,%r8,1 */
|
|
case 0x44: /* reg=%xmm8, X=%rax or %rax,%rax,1*/
|
|
case 0x45: /* reg=%xmm8, X=%r8 or %r8,%rax,1 */
|
|
case 0x46: /* reg=%xmm8, X=%rax,%r8,1 */
|
|
case 0x47: /* reg=%xmm8, X=%r8,%r8,1 */
|
|
{
|
|
switch(rip[2])
|
|
{
|
|
case 0x0f:
|
|
{
|
|
switch(rip[3])
|
|
{
|
|
case 0x10: /* movsd (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x11: /* movsd reg,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x58: /* addsd (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x59: /* mulsd (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x5c: /* subsd (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x5e: /* divsd (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 0xf3:
|
|
{
|
|
switch(rip[1])
|
|
{
|
|
case 0x0f: /* SSE 2 */
|
|
{
|
|
switch(rip[2])
|
|
{
|
|
case 0x10: /* movss (%rax),%xmm0 */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x11: /* movss %xmm0,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x6f: /* movdqu (%rax),%xmm0 */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x7f: /* movdqu %xmm0,(%rax) */
|
|
opType = OP_STORE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
#if defined(__x86_64__)
|
|
case 0x41: /* reg=%xmm0, X=%r8 */
|
|
case 0x42: /* reg=%xmm0, X=%rax,%r8,1 */
|
|
case 0x43: /* reg=%xmm0, X=%r8,%r8,1 */
|
|
case 0x44: /* reg=%xmm8, X = %rax */
|
|
case 0x45: /* reg=%xmm8, X = %r8 */
|
|
case 0x46: /* reg=%xmm8, X = %rax,%r8,1 */
|
|
case 0x47: /* reg=%xmm8, X = %r8,%r8,1 */
|
|
{
|
|
switch(rip[2])
|
|
{
|
|
case 0x0f: /* SSE 2 */
|
|
{
|
|
switch(rip[3])
|
|
{
|
|
case 0x10: /* movss (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x11: /* movss reg,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
case 0x6f: /* movdqu (X),reg */
|
|
opType = OP_LOAD;
|
|
break;
|
|
case 0x7f: /* movdqu reg,(X) */
|
|
opType = OP_STORE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 0x48:
|
|
{
|
|
switch(rip[2])
|
|
{
|
|
case 0xa5: /* rep movsq %ds:(%rsi),%es:(%rdi) */
|
|
opType = OP_MOVS_RSI_RDI;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
case 0xa4: /* rep movsb %ds:(%rsi),%es:(%rdi) */
|
|
case 0xa5: /* rep movsl %ds:(%rsi),%es:(%rdi) */
|
|
opType = OP_MOVS_RSI_RDI;
|
|
break;
|
|
case 0xa6: /* repz cmpsb %es:(%rdi),%ds:(%rsi) */
|
|
opType = OP_LOAD;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 0xf6: /* testb $,(%rax) or notb (%rax) */
|
|
case 0xf7: /* testl $,(%rax) or notl (%rax) */
|
|
{
|
|
if( rip[1] < 0x10 ) /* test */
|
|
opType = OP_LOAD;
|
|
else /* not */
|
|
opType = OP_STORE;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
return opType;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemManagerPinAddrInternal() */
|
|
/************************************************************************/
|
|
|
|
static int CPLVirtualMemManagerPinAddrInternal(CPLVirtualMemMsgToWorkerThread* msg)
|
|
{
|
|
char wait_ready;
|
|
char response_buf[4];
|
|
|
|
/* Wait for the helper thread to be ready to process another request */
|
|
while(TRUE)
|
|
{
|
|
int ret = read(pVirtualMemManager->pipefd_wait_thread[0], &wait_ready, 1);
|
|
if( ret < 0 && errno == EINTR )
|
|
;
|
|
else
|
|
{
|
|
assert(ret == 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Pass the address that caused the fault to the helper thread */
|
|
assert(write(pVirtualMemManager->pipefd_to_thread[1], msg, sizeof(*msg))
|
|
== sizeof(*msg));
|
|
|
|
/* Wait that the helper thread has fixed the fault */
|
|
while(TRUE)
|
|
{
|
|
int ret = read(pVirtualMemManager->pipefd_from_thread[0], response_buf, 4);
|
|
if( ret < 0 && errno == EINTR )
|
|
;
|
|
else
|
|
{
|
|
assert(ret == 4);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* In case the helper thread did not recognize the address as being */
|
|
/* one that it should take care of, just rely on the previous SIGSEGV */
|
|
/* handler (with might abort the process) */
|
|
return( memcmp(response_buf, MAPPING_FOUND, 4) == 0 );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemPin() */
|
|
/************************************************************************/
|
|
|
|
void CPLVirtualMemPin(CPLVirtualMem* ctxt,
|
|
void* pAddr, size_t nSize, int bWriteOp)
|
|
{
|
|
if( ctxt->bFileMemoryMapped )
|
|
return;
|
|
|
|
CPLVirtualMemMsgToWorkerThread msg;
|
|
size_t i = 0, n;
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.hRequesterThread = pthread_self();
|
|
msg.opType = (bWriteOp) ? OP_STORE : OP_LOAD;
|
|
|
|
char* pBase = (char*)ALIGN_DOWN(pAddr, ctxt->nPageSize);
|
|
n = ((char*)pAddr - pBase + nSize + ctxt->nPageSize - 1) / ctxt->nPageSize;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
msg.pFaultAddr = (char*) pBase + i * ctxt->nPageSize;
|
|
CPLVirtualMemManagerPinAddrInternal(&msg);
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemManagerSIGSEGVHandler() */
|
|
/************************************************************************/
|
|
|
|
#if defined(__x86_64__)
|
|
#define REG_IP REG_RIP
|
|
#define REG_SI REG_RSI
|
|
#define REG_DI REG_RDI
|
|
#elif defined(__i386__)
|
|
#define REG_IP REG_EIP
|
|
#define REG_SI REG_ESI
|
|
#define REG_DI REG_EDI
|
|
#endif
|
|
|
|
/* We must take care of only using "asynchronous-signal-safe" functions in a signal handler */
|
|
/* pthread_self(), read() and write() are such. See */
|
|
/* https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers */
|
|
static void CPLVirtualMemManagerSIGSEGVHandler(int the_signal,
|
|
siginfo_t* the_info,
|
|
void* the_ctxt)
|
|
{
|
|
CPLVirtualMemMsgToWorkerThread msg;
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.pFaultAddr = the_info->si_addr;
|
|
msg.hRequesterThread = pthread_self();
|
|
msg.opType = OP_UNKNOWN;
|
|
|
|
#if defined(__x86_64__) || defined(__i386__)
|
|
ucontext_t* the_ucontext = (ucontext_t* )the_ctxt;
|
|
const GByte* rip = (const GByte*)the_ucontext->uc_mcontext.gregs[REG_IP];
|
|
msg.opType = CPLVirtualMemGetOpType(rip);
|
|
/*fprintfstderr("at rip %p, bytes: %02x %02x %02x %02x\n",
|
|
rip, rip[0], rip[1], rip[2], rip[3]);*/
|
|
if( msg.opType == OP_MOVS_RSI_RDI )
|
|
{
|
|
void* rsi = (void*)the_ucontext->uc_mcontext.gregs[REG_SI];
|
|
void* rdi = (void*)the_ucontext->uc_mcontext.gregs[REG_DI];
|
|
|
|
/*fprintfstderr("fault=%p rsi=%p rsi=%p\n", msg.pFaultAddr, rsi, rdi);*/
|
|
if( msg.pFaultAddr == rsi )
|
|
{
|
|
/*fprintfstderr("load\n");*/
|
|
msg.opType = OP_LOAD;
|
|
}
|
|
else if( msg.pFaultAddr == rdi )
|
|
{
|
|
/*fprintfstderr("store\n");*/
|
|
msg.opType = OP_STORE;
|
|
}
|
|
}
|
|
#ifdef DEBUG_VIRTUALMEM
|
|
else if( msg.opType == OP_UNKNOWN )
|
|
{
|
|
static int bHasWarned = FALSE;
|
|
if( !bHasWarned )
|
|
{
|
|
bHasWarned = TRUE;
|
|
fprintfstderr("at rip %p, unknown bytes: %02x %02x %02x %02x\n",
|
|
rip, rip[0], rip[1], rip[2], rip[3]);
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
/*fprintfstderr("entering handler for %X (addr=%p)\n", pthread_self(), the_info->si_addr); */
|
|
|
|
if( the_info->si_code != SEGV_ACCERR )
|
|
{
|
|
pVirtualMemManager->oldact.sa_sigaction(the_signal, the_info, the_ctxt);
|
|
return;
|
|
}
|
|
|
|
if( !CPLVirtualMemManagerPinAddrInternal(&msg) )
|
|
{
|
|
/* In case the helper thread did not recognize the address as being */
|
|
/* one that it should take care of, just rely on the previous SIGSEGV */
|
|
/* handler (with might abort the process) */
|
|
pVirtualMemManager->oldact.sa_sigaction(the_signal, the_info, the_ctxt);
|
|
}
|
|
|
|
/*fprintfstderr("leaving handler for %X (addr=%p)\n", pthread_self(), the_info->si_addr);*/
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemManagerThread() */
|
|
/************************************************************************/
|
|
|
|
static void CPLVirtualMemManagerThread(void* unused_param)
|
|
{
|
|
(void)unused_param;
|
|
|
|
while(TRUE)
|
|
{
|
|
char i_m_ready = 1;
|
|
int i;
|
|
CPLVirtualMem* ctxt = NULL;
|
|
int bMappingFound = FALSE;
|
|
CPLVirtualMemMsgToWorkerThread msg;
|
|
|
|
/* Signal that we are ready to process a new request */
|
|
assert(write(pVirtualMemManager->pipefd_wait_thread[1], &i_m_ready, 1) == 1);
|
|
|
|
/* Fetch the address to process */
|
|
assert(read(pVirtualMemManager->pipefd_to_thread[0], &msg,
|
|
sizeof(msg)) == sizeof(msg));
|
|
|
|
/* If CPLVirtualMemManagerTerminate() is called, it will use BYEBYE_ADDR as a */
|
|
/* means to ask for our termination */
|
|
if( msg.pFaultAddr == BYEBYE_ADDR )
|
|
break;
|
|
|
|
/* Lookup for a mapping that contains addr */
|
|
CPLAcquireMutex(hVirtualMemManagerMutex, 1000.0);
|
|
for(i=0;i<pVirtualMemManager->nVirtualMemCount;i++)
|
|
{
|
|
ctxt = pVirtualMemManager->pasVirtualMem[i];
|
|
if( (char*)msg.pFaultAddr >= (char*) ctxt->pData &&
|
|
(char*)msg.pFaultAddr < (char*) ctxt->pData + ctxt->nSize )
|
|
{
|
|
bMappingFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
CPLReleaseMutex(hVirtualMemManagerMutex);
|
|
|
|
if( bMappingFound )
|
|
{
|
|
char* start_page_addr = (char*)ALIGN_DOWN(msg.pFaultAddr, ctxt->nPageSize);
|
|
int iPage = (int)
|
|
(((char*)start_page_addr - (char*)ctxt->pData) / ctxt->nPageSize);
|
|
|
|
if( iPage == ctxt->iLastPage )
|
|
{
|
|
/* In case 2 threads try to access the same page */
|
|
/* concurrently it is possible that we are asked to mapped */
|
|
/* the page again whereas it is always mapped. However */
|
|
/* if that number of successive retries is too high, this */
|
|
/* is certainly a sign that something else happen, like */
|
|
/* trying to write-access a read-only page */
|
|
/* 100 is a bit of magic number. I believe it must be */
|
|
/* at least the number of concurrent threads. 100 seems */
|
|
/* to be really safe ! */
|
|
ctxt->nRetry ++;
|
|
/* fprintfstderr("retry on page %d : %d\n", (int)iPage, ctxt->nRetry); */
|
|
if( ctxt->nRetry >= 100 )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"CPLVirtualMemManagerThread: trying to "
|
|
"write into read-only mapping");
|
|
assert(write(pVirtualMemManager->pipefd_from_thread[1],
|
|
MAPPING_NOT_FOUND, 4) == 4);
|
|
break;
|
|
}
|
|
else if( msg.opType != OP_LOAD &&
|
|
ctxt->eAccessMode == VIRTUALMEM_READWRITE &&
|
|
!TEST_BIT(ctxt->pabitRWMappedPages, iPage) )
|
|
{
|
|
/* fprintfstderr("switching page %d to write mode\n", (int)iPage); */
|
|
SET_BIT(ctxt->pabitRWMappedPages, iPage);
|
|
assert(mprotect(start_page_addr, ctxt->nPageSize,
|
|
PROT_READ | PROT_WRITE) == 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ctxt->iLastPage = iPage;
|
|
ctxt->nRetry = 0;
|
|
|
|
if( TEST_BIT(ctxt->pabitMappedPages, iPage) )
|
|
{
|
|
if( msg.opType != OP_LOAD &&
|
|
ctxt->eAccessMode == VIRTUALMEM_READWRITE &&
|
|
!TEST_BIT(ctxt->pabitRWMappedPages, iPage) )
|
|
{
|
|
/*fprintfstderr("switching page %d to write mode\n",
|
|
iPage);*/
|
|
SET_BIT(ctxt->pabitRWMappedPages, iPage);
|
|
assert(mprotect(start_page_addr, ctxt->nPageSize,
|
|
PROT_READ | PROT_WRITE) == 0);
|
|
}
|
|
else
|
|
{
|
|
/*fprintfstderr("unexpected case for page %d\n",
|
|
iPage);*/
|
|
}
|
|
}
|
|
else
|
|
{
|
|
void* pPageToFill;
|
|
size_t nToFill;
|
|
pPageToFill = CPLVirtualMemGetPageToFill(ctxt, start_page_addr);
|
|
|
|
nToFill = ctxt->nPageSize;
|
|
if( start_page_addr + nToFill >= (char*) ctxt->pData + ctxt->nSize )
|
|
nToFill = (char*) ctxt->pData + ctxt->nSize - start_page_addr;
|
|
|
|
ctxt->pfnCachePage(
|
|
ctxt,
|
|
start_page_addr - (char*) ctxt->pData,
|
|
pPageToFill,
|
|
nToFill,
|
|
ctxt->pCbkUserData);
|
|
|
|
/* Now remap this page to its target address and */
|
|
/* register it in the LRU */
|
|
CPLVirtualMemAddPage(ctxt, start_page_addr, pPageToFill,
|
|
msg.opType, msg.hRequesterThread);
|
|
}
|
|
}
|
|
|
|
/* Warn the segfault handler that we have finished our job */
|
|
assert(write(pVirtualMemManager->pipefd_from_thread[1],
|
|
MAPPING_FOUND, 4) == 4);
|
|
}
|
|
else
|
|
{
|
|
/* Warn the segfault handler that we have finished our job */
|
|
/* but that the fault didn't occur in a memory range that is under */
|
|
/* our responsability */
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"CPLVirtualMemManagerThread: no mapping found");
|
|
assert(write(pVirtualMemManager->pipefd_from_thread[1],
|
|
MAPPING_NOT_FOUND, 4) == 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemManagerInit() */
|
|
/************************************************************************/
|
|
|
|
static void CPLVirtualMemManagerInit(void)
|
|
{
|
|
CPLMutexHolderD(&hVirtualMemManagerMutex);
|
|
if( pVirtualMemManager != NULL )
|
|
return;
|
|
|
|
struct sigaction act;
|
|
pVirtualMemManager = (CPLVirtualMemManager*) CPLMalloc(sizeof(CPLVirtualMemManager));
|
|
pVirtualMemManager->pasVirtualMem = NULL;
|
|
pVirtualMemManager->nVirtualMemCount = 0;
|
|
assert(pipe(pVirtualMemManager->pipefd_to_thread) == 0);
|
|
assert(pipe(pVirtualMemManager->pipefd_from_thread) == 0);
|
|
assert(pipe(pVirtualMemManager->pipefd_wait_thread) == 0);
|
|
|
|
/* Install our custom SIGSEGV handler */
|
|
act.sa_sigaction = CPLVirtualMemManagerSIGSEGVHandler;
|
|
sigemptyset (&act.sa_mask);
|
|
act.sa_flags = SA_SIGINFO;
|
|
assert(sigaction(SIGSEGV, &act, &pVirtualMemManager->oldact) == 0);
|
|
|
|
/* Starts the helper thread */
|
|
pVirtualMemManager->hHelperThread =
|
|
CPLCreateJoinableThread(CPLVirtualMemManagerThread, NULL);
|
|
assert(pVirtualMemManager->hHelperThread != NULL);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLVirtualMemManagerTerminate() */
|
|
/************************************************************************/
|
|
|
|
void CPLVirtualMemManagerTerminate(void)
|
|
{
|
|
char wait_ready;
|
|
CPLVirtualMemMsgToWorkerThread msg;
|
|
|
|
if( pVirtualMemManager == NULL )
|
|
return;
|
|
|
|
msg.pFaultAddr = BYEBYE_ADDR;
|
|
msg.opType = OP_UNKNOWN;
|
|
memset(&msg.hRequesterThread, 0, sizeof(msg.hRequesterThread));
|
|
|
|
/* Wait for the helper thread to be ready */
|
|
assert(read(pVirtualMemManager->pipefd_wait_thread[0],
|
|
&wait_ready, 1) == 1);
|
|
|
|
/* Ask it to terminate */
|
|
assert(write(pVirtualMemManager->pipefd_to_thread[1], &msg, sizeof(msg)) == sizeof(msg));
|
|
|
|
/* Wait for its termination */
|
|
CPLJoinThread(pVirtualMemManager->hHelperThread);
|
|
|
|
/* Cleanup everything */
|
|
while(pVirtualMemManager->nVirtualMemCount > 0)
|
|
CPLVirtualMemFree(pVirtualMemManager->pasVirtualMem[pVirtualMemManager->nVirtualMemCount - 1]);
|
|
CPLFree(pVirtualMemManager->pasVirtualMem);
|
|
|
|
close(pVirtualMemManager->pipefd_to_thread[0]);
|
|
close(pVirtualMemManager->pipefd_to_thread[1]);
|
|
close(pVirtualMemManager->pipefd_from_thread[0]);
|
|
close(pVirtualMemManager->pipefd_from_thread[1]);
|
|
close(pVirtualMemManager->pipefd_wait_thread[0]);
|
|
close(pVirtualMemManager->pipefd_wait_thread[1]);
|
|
|
|
/* Restore previous handler */
|
|
sigaction(SIGSEGV, &pVirtualMemManager->oldact, NULL);
|
|
|
|
CPLFree(pVirtualMemManager);
|
|
pVirtualMemManager = NULL;
|
|
|
|
CPLDestroyMutex(hVirtualMemManagerMutex);
|
|
hVirtualMemManagerMutex = NULL;
|
|
}
|
|
|
|
#else /* defined(__linux) && defined(CPL_MULTIPROC_PTHREAD) */
|
|
|
|
size_t CPLGetPageSize(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
CPLVirtualMem *CPLVirtualMemNew(CPL_UNUSED size_t nSize,
|
|
CPL_UNUSED size_t nCacheSize,
|
|
CPL_UNUSED size_t nPageSizeHint,
|
|
CPL_UNUSED int bSingleThreadUsage,
|
|
CPL_UNUSED CPLVirtualMemAccessMode eAccessMode,
|
|
CPL_UNUSED CPLVirtualMemCachePageCbk pfnCachePage,
|
|
CPL_UNUSED CPLVirtualMemUnCachePageCbk pfnUnCachePage,
|
|
CPL_UNUSED CPLVirtualMemFreeUserData pfnFreeUserData,
|
|
CPL_UNUSED void *pCbkUserData)
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"CPLVirtualMemNew() unsupported on this operating system / configuration");
|
|
return NULL;
|
|
}
|
|
|
|
int CPLIsVirtualMemFileMapAvailable(void)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
CPLVirtualMem *CPLVirtualMemFileMapNew(CPL_UNUSED VSILFILE* fp,
|
|
CPL_UNUSED vsi_l_offset nOffset,
|
|
CPL_UNUSED vsi_l_offset nLength,
|
|
CPL_UNUSED CPLVirtualMemAccessMode eAccessMode,
|
|
CPL_UNUSED CPLVirtualMemFreeUserData pfnFreeUserData,
|
|
CPL_UNUSED void *pCbkUserData)
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"CPLVirtualMemFileMapNew() unsupported on this operating system / configuration");
|
|
return NULL;
|
|
}
|
|
|
|
CPLVirtualMem *CPLVirtualMemDerivedNew(CPL_UNUSED CPLVirtualMem* pVMemBase,
|
|
CPL_UNUSED vsi_l_offset nOffset,
|
|
CPL_UNUSED vsi_l_offset nSize,
|
|
CPL_UNUSED CPLVirtualMemFreeUserData pfnFreeUserData,
|
|
CPL_UNUSED void *pCbkUserData)
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"CPLVirtualMemDerivedNew() unsupported on this operating system / configuration");
|
|
return NULL;
|
|
}
|
|
|
|
void CPLVirtualMemFree(CPL_UNUSED CPLVirtualMem* ctxt)
|
|
{
|
|
}
|
|
|
|
void* CPLVirtualMemGetAddr(CPL_UNUSED CPLVirtualMem* ctxt)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
size_t CPLVirtualMemGetSize(CPL_UNUSED CPLVirtualMem* ctxt)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int CPLVirtualMemIsFileMapping(CPL_UNUSED CPLVirtualMem* ctxt)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
CPLVirtualMemAccessMode CPLVirtualMemGetAccessMode(CPL_UNUSED CPLVirtualMem* ctxt)
|
|
{
|
|
return VIRTUALMEM_READONLY;
|
|
}
|
|
|
|
size_t CPLVirtualMemGetPageSize(CPL_UNUSED CPLVirtualMem* ctxt)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int CPLVirtualMemIsAccessThreadSafe(CPL_UNUSED CPLVirtualMem* ctxt)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
void CPLVirtualMemDeclareThread(CPL_UNUSED CPLVirtualMem* ctxt)
|
|
{
|
|
}
|
|
|
|
void CPLVirtualMemUnDeclareThread(CPL_UNUSED CPLVirtualMem* ctxt)
|
|
{
|
|
}
|
|
|
|
void CPLVirtualMemPin(CPL_UNUSED CPLVirtualMem* ctxt,
|
|
CPL_UNUSED void* pAddr,
|
|
CPL_UNUSED size_t nSize,
|
|
CPL_UNUSED int bWriteOp)
|
|
{
|
|
}
|
|
|
|
void CPLVirtualMemManagerTerminate(void)
|
|
{
|
|
}
|
|
|
|
#endif /* defined(__linux) && defined(CPL_MULTIPROC_PTHREAD) */
|