blob: 3e2e199ef2bccd91b144cfef7eb2b219dc6591f5 [file] [edit]
/*
* Copyright (c) 2016 Advanced Micro Devices, Inc. All rights reserved.
* Copyright (C) 2015-2017 LunarG, Inc.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "vktrace_common.h"
#include "vktrace_pageguard_memorycopy.h"
#include "vktrace_lib_pagestatusarray.h"
#include "vktrace_lib_pageguardmappedmemory.h"
#include "vktrace_lib_pageguardcapture.h"
#include "vktrace_lib_pageguard.h"
#include "vktrace_lib_trim.h"
#if !defined(ANDROID)
static const bool PAGEGUARD_PAGEGUARD_ENABLE_DEFAULT = true;
#else
static const bool PAGEGUARD_PAGEGUARD_ENABLE_DEFAULT = false;
#endif
static const VkDeviceSize PAGEGUARD_TARGET_RANGE_SIZE_DEFAULT = 2; // cover all reasonal mapped memory size, the mapped memory size
// may be less than 1 page, so processing for mapped memory
// size<1 page is already added,
// other value: 32 * 1024 * 1024 (32M), 64M, this is the size which cause DOOM4 capture very slow.
static const VkDeviceSize PAGEGUARD_PAGEGUARD_TARGET_RANGE_SIZE_MIN = 1; // already tested: 2,2M,4M,32M,64M, because commonly page
// size is 4k, so only range size=2 can cover small size
// mapped memory.
#if defined(WIN32)
static vktrace_sem_id ref_amount_sem_id; // TODO if vktrace implement cross platform lib or dll load or unload function, this sem
// can be putted in those functions, but now we leave it to process quit.
static bool ref_amount_sem_id_create_success = vktrace_sem_create(&ref_amount_sem_id, 1);
#endif
static vktrace_sem_id map_lock_sem_id;
static bool map_lock_sem_id_create_success = vktrace_sem_create(&map_lock_sem_id, 1);
void pageguardEnter() { vktrace_sem_wait(map_lock_sem_id); }
void pageguardExit() { vktrace_sem_post(map_lock_sem_id); }
VkDeviceSize& ref_target_range_size() {
static VkDeviceSize OPTTargetRangeSize = PAGEGUARD_TARGET_RANGE_SIZE_DEFAULT;
return OPTTargetRangeSize;
}
void set_pageguard_target_range_size(VkDeviceSize newrangesize) {
VkDeviceSize& refTargetRangeSize = ref_target_range_size();
refTargetRangeSize = newrangesize;
}
#if defined(WIN32)
LONG WINAPI PageGuardExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo);
#endif
PVOID OPTHandler = nullptr; // use to remove page guard handler
uint32_t OPTHandlerRefAmount = 0; // for persistent map and multi-threading environment, map and unmap maybe overlap, we need to
// make sure remove handler after all persistent map has been unmapped.
// Function to verify pageguard support on current platform.
// Generates warning if platform does not support pageguard.
// Returns true if platform can support pageguard, false otherwise.
// This function is needed because some Linux kernels may be
// configured without /proc/self/pagemap support.
static bool verifyPlatformPageGuardSupport(void) {
#if defined(PLATFORM_LINUX)
int pmFd = -1, crFd = -1;
void* p = nullptr;
size_t pageSize = pageguardGetSystemPageSize();
off_t fileOffset;
uint64_t ptEntry;
char four = '4';
pmFd = open("/proc/self/pagemap", O_RDONLY);
if (pmFd <= 0) goto error;
crFd = open("/proc/self/clear_refs", O_WRONLY);
if (crFd <= 0) goto error;
p = (uint32_t*)pageguardAllocateMemory(sizeof(uint32_t));
if (p == nullptr) goto error;
fileOffset = ((off_t)((((size_t)p & ~(pageSize - 1)) / pageSize))) * 8;
// Use clear_refs to clear dirty bit and verify it is cleared
if (write(crFd, &four, 1) <= 0) goto error;
if (fileOffset != lseek(pmFd, fileOffset, SEEK_SET)) goto error;
if (sizeof(ptEntry) != read(pmFd, &ptEntry, sizeof(ptEntry))) goto error;
if ((ptEntry & PTE_DIRTY_BIT) != 0) goto error;
// Make sure a write to *p results in dirty bit being set
*((uint64_t*)p) = 1;
if (fileOffset != lseek(pmFd, fileOffset, SEEK_SET)) goto error;
if (sizeof(ptEntry) != read(pmFd, &ptEntry, sizeof(ptEntry))) goto error;
if ((ptEntry & PTE_DIRTY_BIT) == 0) goto error;
// Use clear_refs to clear dirty bit and verify it is cleared
if (write(crFd, &four, 1) <= 0) goto error;
if (fileOffset != lseek(pmFd, fileOffset, SEEK_SET)) goto error;
if (sizeof(ptEntry) != read(pmFd, &ptEntry, sizeof(ptEntry))) goto error;
if ((ptEntry & PTE_DIRTY_BIT) != 0) goto error;
// Clean up
pageguardFreeMemory(p);
close(pmFd);
close(crFd);
return true;
error:
vktrace_LogAlways("Cannot enable pmb tracing, using --PMB false.");
vktrace_LogAlways("Linux kernel seems to not be configured with CONFIG_MEM_SOFT_DIRTY.");
if (p != nullptr) pageguardFreeMemory(p);
if (pmFd != -1) close(pmFd);
if (crFd != -1) close(crFd);
return false;
#else
// Windows always supports pageguard
return true;
#endif
}
// return if enable pageguard;
// if enable page guard, then check if need to update target range size, page guard only work for those persistent mapped memory
// which >= target range size.
bool getPageGuardEnableFlag() {
static bool EnablePageGuard = PAGEGUARD_PAGEGUARD_ENABLE_DEFAULT;
static bool FirstTimeRun = true;
if (FirstTimeRun) {
FirstTimeRun = false;
const char* env_pageguard = vktrace_get_global_var(VKTRACE_PMB_ENABLE_ENV);
if (env_pageguard) {
int envvalue;
if (sscanf(env_pageguard, "%d", &envvalue) == 1) {
if (envvalue) {
EnablePageGuard = true;
const char* env_target_range_size = vktrace_get_global_var(_VKTRACE_PMB_TARGET_RANGE_SIZE_ENV);
if (env_target_range_size) {
VkDeviceSize rangesize;
if (sscanf(env_target_range_size, "%" PRIx64, &rangesize) == 1) {
if (rangesize > PAGEGUARD_PAGEGUARD_TARGET_RANGE_SIZE_MIN) {
set_pageguard_target_range_size(rangesize);
}
}
}
} else {
EnablePageGuard = false;
}
}
}
// Make sure current platform can support pageguard
if (EnablePageGuard) EnablePageGuard = verifyPlatformPageGuardSupport();
}
return EnablePageGuard;
}
bool getEnableReadProcessFlag(const char* name) {
static bool EnableReadPMB;
static bool FirstTimeRun = true;
if (FirstTimeRun) {
EnableReadPMB = (vktrace_get_global_var(name) != NULL);
FirstTimeRun = false;
}
return EnableReadPMB;
}
bool getEnableReadPMBFlag() { return getEnableReadProcessFlag(VKTRACE_PAGEGUARD_ENABLE_READ_PMB_ENV); }
bool getEnableReadPMBPostProcessFlag() { return getEnableReadProcessFlag(VKTRACE_PAGEGUARD_ENABLE_READ_POST_PROCESS_ENV); }
#if defined(WIN32)
void setPageGuardExceptionHandler() {
vktrace_sem_wait(ref_amount_sem_id);
if (!OPTHandler) {
OPTHandler = AddVectoredExceptionHandler(1, PageGuardExceptionHandler);
OPTHandlerRefAmount = 1;
} else {
OPTHandlerRefAmount++;
}
vktrace_sem_post(ref_amount_sem_id);
}
void removePageGuardExceptionHandler() {
vktrace_sem_wait(ref_amount_sem_id);
if (OPTHandler) {
if (OPTHandlerRefAmount) {
OPTHandlerRefAmount--;
}
if (!OPTHandlerRefAmount) {
RemoveVectoredExceptionHandler(OPTHandler);
OPTHandler = nullptr;
}
}
vktrace_sem_post(ref_amount_sem_id);
}
#endif
size_t pageguardGetAdjustedSize(size_t size) {
size_t pagesize = pageguardGetSystemPageSize();
if (size % pagesize) {
size = size - (size % pagesize) + pagesize;
}
return size;
}
#if defined(PLATFORM_LINUX)
// Keep a map of memory allocations and sizes.
// We need the size when we want to free the memory on Linux.
static std::unordered_map<void*, size_t> allocateMemoryMap;
#endif
// Page guard only works for virtual memory. Real device memory
// sometimes doesn't have a page concept, so we can't use page guard
// to track it (or check dirty bits in /proc/<pid>/pagemap).
// So we allocate virtual memory to return to the app and we
// keep it sync'ed it with real device memory.
void* pageguardAllocateMemory(size_t size) {
void* pMemory = nullptr;
if (size != 0) {
#if defined(WIN32)
pMemory = (PBYTE)VirtualAlloc(nullptr, pageguardGetAdjustedSize(size), MEM_WRITE_WATCH | MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE);
#else
pMemory = mmap(NULL, pageguardGetAdjustedSize(size), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (pMemory != nullptr) allocateMemoryMap[pMemory] = pageguardGetAdjustedSize(size);
#endif
}
if (pMemory == nullptr) vktrace_LogError("pageguardAllocateMemory(%d) memory allocation failed", size);
return pMemory;
}
void pageguardFreeMemory(void* pMemory) {
if (pMemory) {
#if defined(WIN32)
VirtualFree(pMemory, 0, MEM_RELEASE);
#else
munmap(pMemory, allocateMemoryMap[pMemory]);
allocateMemoryMap.erase(pMemory);
#endif
}
}
DWORD pageguardGetSystemPageSize() {
#if defined(PLATFORM_LINUX)
return getpagesize();
#elif defined(WIN32)
SYSTEM_INFO sSysInfo;
GetSystemInfo(&sSysInfo);
return sSysInfo.dwPageSize;
#endif
}
void setFlagTovkFlushMappedMemoryRangesSpecial(PBYTE pOPTPackageData) {
PageGuardChangedBlockInfo* pChangedInfoArray = (PageGuardChangedBlockInfo*)pOPTPackageData;
pChangedInfoArray[0].reserve0 = pChangedInfoArray[0].reserve0 | PAGEGUARD_SPECIAL_FORMAT_PACKET_FOR_VKFLUSHMAPPEDMEMORYRANGES;
}
PageGuardCapture& getPageGuardControlInstance() {
static PageGuardCapture OPTControl;
return OPTControl;
}
void flushTargetChangedMappedMemory(LPPageGuardMappedMemory TargetMappedMemory, vkFlushMappedMemoryRangesFunc pFunc,
VkMappedMemoryRange* pMemoryRanges) {
bool newMemoryRangesInside = (pMemoryRanges == nullptr);
if (newMemoryRangesInside) {
pMemoryRanges = new VkMappedMemoryRange[1];
assert(pMemoryRanges);
}
pMemoryRanges[0].memory = TargetMappedMemory->getMappedMemory();
pMemoryRanges[0].offset = TargetMappedMemory->getMappedOffset();
pMemoryRanges[0].pNext = nullptr;
pMemoryRanges[0].size = TargetMappedMemory->getMappedSize();
pMemoryRanges[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
(*pFunc)(TargetMappedMemory->getMappedDevice(), 1, pMemoryRanges);
if (newMemoryRangesInside) {
delete[] pMemoryRanges;
}
}
void flushAllChangedMappedMemory(vkFlushMappedMemoryRangesFunc pFunc) {
LPPageGuardMappedMemory pMappedMemoryTemp;
uint64_t amount = getPageGuardControlInstance().getMapMemory().size();
if (amount) {
int i = 0;
VkMappedMemoryRange* pMemoryRanges = new VkMappedMemoryRange[1]; // amount
for (std::unordered_map<VkDeviceMemory, PageGuardMappedMemory>::iterator it =
getPageGuardControlInstance().getMapMemory().begin();
it != getPageGuardControlInstance().getMapMemory().end(); it++) {
pMappedMemoryTemp = &(it->second);
flushTargetChangedMappedMemory(pMappedMemoryTemp, pFunc, pMemoryRanges);
i++;
}
delete[] pMemoryRanges;
}
}
void resetAllReadFlagAndPageGuard() {
LPPageGuardMappedMemory pMappedMemoryTemp;
uint64_t amount = getPageGuardControlInstance().getMapMemory().size();
if (amount) {
int i = 0;
for (std::unordered_map<VkDeviceMemory, PageGuardMappedMemory>::iterator it =
getPageGuardControlInstance().getMapMemory().begin();
it != getPageGuardControlInstance().getMapMemory().end(); it++) {
pMappedMemoryTemp = &(it->second);
pMappedMemoryTemp->resetMemoryObjectAllReadFlagAndPageGuard();
i++;
}
}
}
#ifdef WIN32
LONG WINAPI PageGuardExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo) {
LONG resultCode = EXCEPTION_CONTINUE_SEARCH;
pageguardEnter();
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) {
VkDeviceSize OffsetOfAddr;
PBYTE pBlock;
VkDeviceSize BlockSize;
PBYTE addr = reinterpret_cast<PBYTE>(ExceptionInfo->ExceptionRecord->ExceptionInformation[1]);
bool bWrite = ExceptionInfo->ExceptionRecord->ExceptionInformation[0];
LPPageGuardMappedMemory pMappedMem =
getPageGuardControlInstance().findMappedMemoryObject(addr, &OffsetOfAddr, &pBlock, &BlockSize);
if (pMappedMem) {
int64_t index = pMappedMem->getIndexOfChangedBlockByAddr(addr);
if (index >= 0) {
if (!getEnableReadPMBFlag() || bWrite) {
// We don't attempt to use checksums on Windows to determine
// if a page is being written with the same data.
// We can't compute and save a checksum here because
// PAGEGUARD is a one-shot, and the saved checksum
// would be incorrect once the next word in the same block
// is written, and we don't have a way to find out about the
// change.
if (!pMappedMem->isMappedBlockLoaded(index)) {
// the page never get accessed since the time of the shadow
// memory creation in map process. so here we copy the page
// from real mapped memory to shadow memory. after the memcpy,
// we set the loaded flag, next time we don't do the memcopy
// again in this page guard write process.
vktrace_pageguard_memcpy(pBlock,
pMappedMem->getRealMappedDataPointer() + OffsetOfAddr - OffsetOfAddr % BlockSize,
pMappedMem->getMappedBlockSize(index));
pMappedMem->setMappedBlockLoaded(index, true);
}
pMappedMem->setMappedBlockChanged(index, true, BLOCK_FLAG_ARRAY_CHANGED);
} else {
#ifndef PAGEGUARD_ADD_PAGEGUARD_ON_REAL_MAPPED_MEMORY
if (!pMappedMem->isMappedBlockLoaded(index)) {
// Target app read the page which is never accessed since the
// shadow memory creation in map process.
// here we only set the loaded flag, we still need to do memcpy
// in the following reading acess to the page,that is different
// with page guard process when target app write to the page.
// the loaded flag is setted here only to make sure the following
// write access not to do the memcpy again. because the memcpy
// in read process is to capture GPU side change which is needed
// by CPU side, it is not to replace initial sync from real
// mapped memory to shadow memory in map process.
pMappedMem->setMappedBlockLoaded(index, true);
}
vktrace_pageguard_memcpy(pBlock,
pMappedMem->getRealMappedDataPointer() + OffsetOfAddr - OffsetOfAddr % BlockSize,
pMappedMem->getMappedBlockSize(index));
pMappedMem->setMappedBlockChanged(index, true, BLOCK_FLAG_ARRAY_READ);
if (getEnableReadPMBPostProcessFlag()) {
pMappedMem->setMappedBlockChanged(index, true, BLOCK_FLAG_ARRAY_CHANGED);
}
#else
pMappedMem->setMappedBlockChanged(index, true);
#endif
}
resultCode = EXCEPTION_CONTINUE_EXECUTION;
}
}
}
pageguardExit();
return resultCode;
}
#endif
// The function source code is modified from __HOOKED_vkFlushMappedMemoryRanges
// for coherent map, need this function to dump data so simulate target application write data when playback.
VkResult vkFlushMappedMemoryRangesWithoutAPICall(VkDevice device, uint32_t memoryRangeCount,
const VkMappedMemoryRange* pMemoryRanges) {
VkResult result = VK_SUCCESS;
vktrace_trace_packet_header* pHeader;
size_t rangesSize = 0;
size_t dataSize = 0;
uint32_t iter;
packet_vkFlushMappedMemoryRanges* pPacket = nullptr;
#ifdef USE_PAGEGUARD_SPEEDUP
PBYTE* ppPackageData = new PBYTE[memoryRangeCount];
getPageGuardControlInstance().vkFlushMappedMemoryRangesPageGuardHandle(
device, memoryRangeCount, pMemoryRanges, ppPackageData); // the packet is not needed if no any change on data of all ranges
#endif
// find out how much memory is in the ranges
for (iter = 0; iter < memoryRangeCount; iter++) {
VkMappedMemoryRange* pRange = (VkMappedMemoryRange*)&pMemoryRanges[iter];
rangesSize += vk_size_vkmappedmemoryrange(pRange);
dataSize += (size_t)pRange->size;
}
#ifdef USE_PAGEGUARD_SPEEDUP
dataSize = getPageGuardControlInstance().getALLChangedPackageSizeInMappedMemory(device, memoryRangeCount, pMemoryRanges,
ppPackageData);
#endif
CREATE_TRACE_PACKET(vkFlushMappedMemoryRanges, rangesSize + sizeof(void*) * memoryRangeCount + dataSize);
pPacket = interpret_body_as_vkFlushMappedMemoryRanges(pHeader);
vktrace_add_buffer_to_trace_packet(pHeader, (void**)&(pPacket->pMemoryRanges), rangesSize, pMemoryRanges);
vktrace_finalize_buffer_address(pHeader, (void**)&(pPacket->pMemoryRanges));
// insert into packet the data that was written by CPU between the vkMapMemory call and here
// create a temporary local ppData array and add it to the packet (to reserve the space for the array)
void** ppTmpData = (void**)malloc(memoryRangeCount * sizeof(void*));
vktrace_add_buffer_to_trace_packet(pHeader, (void**)&(pPacket->ppData), sizeof(void*) * memoryRangeCount, ppTmpData);
free(ppTmpData);
// now the actual memory
vktrace_enter_critical_section(&g_memInfoLock);
for (iter = 0; iter < memoryRangeCount; iter++) {
VkMappedMemoryRange* pRange = (VkMappedMemoryRange*)&pMemoryRanges[iter];
VKAllocInfo* pEntry = find_mem_info_entry(pRange->memory);
if (pEntry != nullptr) {
assert(pEntry->handle == pRange->memory);
assert(pEntry->totalSize >= (pRange->size + pRange->offset));
assert(pEntry->totalSize >= pRange->size);
#ifdef USE_PAGEGUARD_SPEEDUP
LPPageGuardMappedMemory pOPTMemoryTemp = getPageGuardControlInstance().findMappedMemoryObject(device, pRange);
VkDeviceSize OPTPackageSizeTemp = 0;
if (pOPTMemoryTemp) {
PBYTE pOPTDataTemp = pOPTMemoryTemp->getChangedDataPackage(&OPTPackageSizeTemp);
setFlagTovkFlushMappedMemoryRangesSpecial(pOPTDataTemp);
vktrace_add_buffer_to_trace_packet(pHeader, (void**)&(pPacket->ppData[iter]), OPTPackageSizeTemp, pOPTDataTemp);
pOPTMemoryTemp->clearChangedDataPackage();
pOPTMemoryTemp->resetMemoryObjectAllChangedFlagAndPageGuard();
} else {
PBYTE pOPTDataTemp =
getPageGuardControlInstance().getChangedDataPackageOutOfMap(ppPackageData, iter, &OPTPackageSizeTemp);
setFlagTovkFlushMappedMemoryRangesSpecial(pOPTDataTemp);
vktrace_add_buffer_to_trace_packet(pHeader, (void**)&(pPacket->ppData[iter]), OPTPackageSizeTemp, pOPTDataTemp);
getPageGuardControlInstance().clearChangedDataPackageOutOfMap(ppPackageData, iter);
}
#else
vktrace_add_buffer_to_trace_packet(pHeader, (void**)&(pPacket->ppData[iter]), pRange->size,
pEntry->pData + pRange->offset);
#endif
vktrace_finalize_buffer_address(pHeader, (void**)&(pPacket->ppData[iter]));
pEntry->didFlush = true;
} else {
vktrace_LogError("Failed to copy app memory into trace packet (idx = %u) on vkFlushedMappedMemoryRanges",
pHeader->global_packet_index);
}
}
#ifdef USE_PAGEGUARD_SPEEDUP
delete[] ppPackageData;
#endif
vktrace_leave_critical_section(&g_memInfoLock);
// now finalize the ppData array since it is done being updated
vktrace_finalize_buffer_address(pHeader, (void**)&(pPacket->ppData));
// result = mdd(device)->devTable.FlushMappedMemoryRanges(device, memoryRangeCount, pMemoryRanges);
vktrace_set_packet_entrypoint_end_time(pHeader);
pPacket->device = device;
pPacket->memoryRangeCount = memoryRangeCount;
pPacket->result = result;
if (!g_trimEnabled) {
// trim not enabled, send packet as usual
FINISH_TRACE_PACKET();
} else if (g_trimIsPreTrim) {
vktrace_finalize_trace_packet(pHeader);
} else if (g_trimIsInTrim) {
// Currently tracing the frame, so need to track references & store packet to write post-tracing.
vktrace_finalize_trace_packet(pHeader);
trim::write_packet(pHeader);
} else // g_trimIsPostTrim
{
vktrace_delete_trace_packet(&pHeader);
}
return result;
}