2013-01-13 07:25:41 +00:00
|
|
|
/**
|
|
|
|
******************************************************************************
|
|
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
|
|
******************************************************************************
|
|
|
|
* Copyright 2013 Ben Vanik. All rights reserved. *
|
|
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
|
|
******************************************************************************
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <xenia/core/memory.h>
|
|
|
|
|
2013-10-20 19:39:59 +00:00
|
|
|
#include <gflags/gflags.h>
|
2013-01-31 06:44:32 +00:00
|
|
|
#include <xenia/core/mutex.h>
|
|
|
|
|
2013-01-30 09:45:55 +00:00
|
|
|
#if !XE_PLATFORM(WIN32)
|
2013-01-13 07:25:41 +00:00
|
|
|
#include <sys/mman.h>
|
2013-01-30 09:45:55 +00:00
|
|
|
#endif // WIN32
|
2013-01-13 07:25:41 +00:00
|
|
|
|
2013-01-29 05:36:03 +00:00
|
|
|
#define MSPACES 1
|
2013-01-31 06:44:32 +00:00
|
|
|
#define USE_LOCKS 0
|
2013-01-29 05:36:03 +00:00
|
|
|
#define USE_DL_PREFIX 1
|
|
|
|
#define HAVE_MORECORE 0
|
|
|
|
#define HAVE_MREMAP 0
|
|
|
|
#define malloc_getpagesize 4096
|
|
|
|
#define DEFAULT_GRANULARITY 64 * 1024
|
|
|
|
#define DEFAULT_TRIM_THRESHOLD MAX_SIZE_T
|
2013-05-28 02:58:20 +00:00
|
|
|
#define MALLOC_ALIGNMENT 32
|
2013-10-20 19:39:59 +00:00
|
|
|
#if XE_DEBUG
|
|
|
|
#define FOOTERS 1
|
|
|
|
#define MALLOC_INSPECT_ALL 1
|
|
|
|
#endif // XE_DEBUG
|
2013-01-29 05:36:03 +00:00
|
|
|
#include <third_party/dlmalloc/malloc.c.h>
|
|
|
|
|
2013-10-20 19:39:59 +00:00
|
|
|
DEFINE_bool(log_heap, false,
|
|
|
|
"Log heap structure on alloc/free.");
|
|
|
|
|
2013-01-13 07:25:41 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Memory map:
|
2013-06-01 10:08:31 +00:00
|
|
|
* 0x00000000 - 0x3FFFFFFF (1024mb) - virtual 4k pages
|
|
|
|
* 0x40000000 - 0x7FFFFFFF (1024mb) - virtual 64k pages
|
|
|
|
* 0x80000000 - 0x8BFFFFFF ( 192mb) - xex 64k pages
|
|
|
|
* 0x8C000000 - 0x8FFFFFFF ( 64mb) - xex 64k pages (encrypted)
|
|
|
|
* 0x90000000 - 0x9FFFFFFF ( 256mb) - xex 4k pages
|
|
|
|
* 0xA0000000 - 0xBFFFFFFF ( 512mb) - physical 64k pages
|
|
|
|
* 0xC0000000 - 0xDFFFFFFF - physical 16mb pages
|
|
|
|
* 0xE0000000 - 0xFFFFFFFF - physical 4k pages
|
2013-01-13 07:25:41 +00:00
|
|
|
*
|
|
|
|
* We use the host OS to create an entire addressable range for this. That way
|
|
|
|
* we don't have to emulate a TLB. It'd be really cool to pass through page
|
|
|
|
* sizes or use madvice to let the OS know what to expect.
|
2013-05-30 04:00:55 +00:00
|
|
|
*
|
|
|
|
* We create our own heap of committed memory that lives at XE_MEMORY_HEAP_LOW
|
|
|
|
* to XE_MEMORY_HEAP_HIGH - all normal user allocations come from there. Since
|
|
|
|
* the Xbox has no paging, we know that the size of this heap will never need
|
|
|
|
* to be larger than ~512MB (realistically, smaller than that). We place it far
|
|
|
|
* away from the XEX data and keep the memory around it uncommitted so that we
|
|
|
|
* have some warning if things go astray.
|
|
|
|
*
|
|
|
|
* For XEX/GPU/etc data we allow placement allocations (base_address != 0) and
|
|
|
|
* commit the requested memory as needed. This bypasses the standard heap, but
|
|
|
|
* XEXs should never be overwriting anything so that's fine. We can also query
|
|
|
|
* for previous commits and assert that we really isn't committing twice.
|
2013-01-13 07:25:41 +00:00
|
|
|
*/
|
|
|
|
|
2013-05-30 04:00:55 +00:00
|
|
|
#define XE_MEMORY_HEAP_LOW 0x20000000
|
|
|
|
#define XE_MEMORY_HEAP_HIGH 0x40000000
|
|
|
|
|
2013-01-13 07:25:41 +00:00
|
|
|
|
|
|
|
struct xe_memory {
|
|
|
|
xe_ref_t ref;
|
|
|
|
|
2013-05-28 02:58:20 +00:00
|
|
|
size_t system_page_size;
|
|
|
|
|
2013-01-31 06:44:32 +00:00
|
|
|
size_t length;
|
|
|
|
void* ptr;
|
2013-01-29 05:36:03 +00:00
|
|
|
|
2013-01-31 06:44:32 +00:00
|
|
|
xe_mutex_t* heap_mutex;
|
|
|
|
mspace heap;
|
2013-01-13 07:25:41 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2013-03-29 12:07:32 +00:00
|
|
|
xe_memory_ref xe_memory_create(xe_memory_options_t options) {
|
2013-01-29 05:36:03 +00:00
|
|
|
|
2013-01-13 07:25:41 +00:00
|
|
|
xe_memory_ref memory = (xe_memory_ref)xe_calloc(sizeof(xe_memory));
|
|
|
|
xe_ref_init((xe_ref)memory);
|
|
|
|
|
2013-05-28 02:58:20 +00:00
|
|
|
#if XE_PLATFORM(WIN32)
|
2013-05-30 04:00:55 +00:00
|
|
|
SYSTEM_INFO si;
|
2013-05-28 02:58:20 +00:00
|
|
|
GetSystemInfo(&si);
|
|
|
|
memory->system_page_size = si.dwPageSize;
|
|
|
|
#else
|
|
|
|
#error need to implement page size retrieval
|
|
|
|
#endif // WIN32
|
|
|
|
|
2013-01-13 07:25:41 +00:00
|
|
|
memory->length = 0xC0000000;
|
2013-01-30 09:45:55 +00:00
|
|
|
|
|
|
|
#if XE_PLATFORM(WIN32)
|
2013-05-30 04:00:55 +00:00
|
|
|
// Reserve the entire usable address space.
|
|
|
|
// We're 64-bit, so this should be no problem.
|
2013-01-30 09:45:55 +00:00
|
|
|
memory->ptr = VirtualAlloc(0, memory->length,
|
2013-05-30 04:00:55 +00:00
|
|
|
MEM_RESERVE,
|
2013-01-30 09:45:55 +00:00
|
|
|
PAGE_READWRITE);
|
2013-05-30 04:00:55 +00:00
|
|
|
XEEXPECTNOTNULL(memory->ptr);
|
2013-01-30 09:45:55 +00:00
|
|
|
#else
|
2013-01-13 07:25:41 +00:00
|
|
|
memory->ptr = mmap(0, memory->length, PROT_READ | PROT_WRITE,
|
|
|
|
MAP_PRIVATE | MAP_ANON, -1, 0);
|
|
|
|
XEEXPECT(memory->ptr != MAP_FAILED);
|
2013-01-30 09:45:55 +00:00
|
|
|
XEEXPECTNOTNULL(memory->ptr);
|
2013-05-30 04:00:55 +00:00
|
|
|
#endif // WIN32
|
2013-01-13 07:25:41 +00:00
|
|
|
|
2013-05-30 04:00:55 +00:00
|
|
|
// Lock used around heap allocs/frees.
|
|
|
|
memory->heap_mutex = xe_mutex_alloc(10000);
|
2013-01-31 06:44:32 +00:00
|
|
|
XEEXPECTNOTNULL(memory->heap_mutex);
|
|
|
|
|
2013-05-30 04:00:55 +00:00
|
|
|
// Commit the memory where our heap will live.
|
|
|
|
// We don't allocate at 0 to make bad writes easier to find.
|
|
|
|
uint32_t heap_offset = XE_MEMORY_HEAP_LOW;
|
|
|
|
uint32_t heap_size = XE_MEMORY_HEAP_HIGH - XE_MEMORY_HEAP_LOW;
|
|
|
|
uint8_t* heap_ptr = (uint8_t*)memory->ptr + heap_offset;
|
|
|
|
#if XE_PLATFORM(WIN32)
|
|
|
|
void* heap_result = VirtualAlloc(heap_ptr, heap_size,
|
|
|
|
MEM_COMMIT, PAGE_READWRITE);
|
|
|
|
XEEXPECTNOTNULL(heap_result);
|
|
|
|
#else
|
|
|
|
#error ?
|
|
|
|
#endif // WIN32
|
|
|
|
|
2013-01-29 05:36:03 +00:00
|
|
|
// Allocate the mspace for our heap.
|
2013-05-30 04:00:55 +00:00
|
|
|
memory->heap = create_mspace_with_base(heap_ptr, heap_size, 0);
|
2013-01-29 05:36:03 +00:00
|
|
|
|
2013-01-13 07:25:41 +00:00
|
|
|
return memory;
|
|
|
|
|
|
|
|
XECLEANUP:
|
|
|
|
xe_memory_release(memory);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void xe_memory_dealloc(xe_memory_ref memory) {
|
2013-01-31 06:44:32 +00:00
|
|
|
if (memory->heap_mutex && memory->heap) {
|
|
|
|
xe_mutex_lock(memory->heap_mutex);
|
2013-01-30 09:45:55 +00:00
|
|
|
destroy_mspace(memory->heap);
|
2013-01-31 06:44:32 +00:00
|
|
|
memory->heap = NULL;
|
|
|
|
xe_mutex_unlock(memory->heap_mutex);
|
|
|
|
}
|
|
|
|
if (memory->heap_mutex) {
|
|
|
|
xe_mutex_free(memory->heap_mutex);
|
|
|
|
memory->heap_mutex = NULL;
|
2013-01-30 09:45:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#if XE_PLATFORM(WIN32)
|
2013-05-30 04:00:55 +00:00
|
|
|
// This decommits all pages and releases everything.
|
|
|
|
XEIGNORE(VirtualFree(memory->ptr, 0, MEM_RELEASE));
|
2013-01-30 09:45:55 +00:00
|
|
|
#else
|
2013-01-13 07:25:41 +00:00
|
|
|
munmap(memory->ptr, memory->length);
|
2013-01-30 09:45:55 +00:00
|
|
|
#endif // WIN32
|
2013-01-13 07:25:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
xe_memory_ref xe_memory_retain(xe_memory_ref memory) {
|
|
|
|
xe_ref_retain((xe_ref)memory);
|
|
|
|
return memory;
|
|
|
|
}
|
|
|
|
|
|
|
|
void xe_memory_release(xe_memory_ref memory) {
|
|
|
|
xe_ref_release((xe_ref)memory, (xe_ref_dealloc_t)xe_memory_dealloc);
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t xe_memory_get_length(xe_memory_ref memory) {
|
|
|
|
return memory->length;
|
|
|
|
}
|
|
|
|
|
2013-02-09 06:07:38 +00:00
|
|
|
uint8_t *xe_memory_addr(xe_memory_ref memory, size_t guest_addr) {
|
2013-01-13 07:25:41 +00:00
|
|
|
return (uint8_t*)memory->ptr + guest_addr;
|
|
|
|
}
|
2013-01-18 07:17:49 +00:00
|
|
|
|
2013-05-31 17:01:32 +00:00
|
|
|
void xe_memory_copy(xe_memory_ref memory,
|
|
|
|
uint32_t dest, uint32_t src, uint32_t size) {
|
|
|
|
uint8_t* pdest = (uint8_t*)memory->ptr + dest;
|
|
|
|
uint8_t* psrc = (uint8_t*)memory->ptr + src;
|
|
|
|
XEIGNORE(xe_copy_memory(pdest, size, psrc, size));
|
|
|
|
}
|
|
|
|
|
2013-02-09 06:07:38 +00:00
|
|
|
uint32_t xe_memory_search_aligned(xe_memory_ref memory, size_t start,
|
|
|
|
size_t end, const uint32_t *values,
|
2013-01-18 07:17:49 +00:00
|
|
|
const size_t value_count) {
|
|
|
|
XEASSERT(start <= end);
|
|
|
|
const uint32_t *p = (const uint32_t*)xe_memory_addr(memory, start);
|
|
|
|
const uint32_t *pe = (const uint32_t*)xe_memory_addr(memory, end);
|
|
|
|
while (p != pe) {
|
|
|
|
if (*p == values[0]) {
|
|
|
|
const uint32_t *pc = p + 1;
|
|
|
|
size_t matched = 1;
|
|
|
|
for (size_t n = 1; n < value_count; n++, pc++) {
|
|
|
|
if (*pc != values[n]) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
matched++;
|
|
|
|
}
|
|
|
|
if (matched == value_count) {
|
|
|
|
return (uint32_t)((uint8_t*)p - (uint8_t*)memory->ptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2013-01-29 05:36:03 +00:00
|
|
|
|
2013-10-20 19:39:59 +00:00
|
|
|
void xe_memory_heap_dump_handler(
|
|
|
|
void* start, void* end, size_t used_bytes, void* context) {
|
|
|
|
xe_memory_ref memory = (xe_memory_ref)context;
|
|
|
|
uint32_t guest_start = (uint32_t)((uintptr_t)start - (uintptr_t)memory->ptr);
|
|
|
|
uint32_t guest_end = (uint32_t)((uintptr_t)end - (uintptr_t)memory->ptr);
|
|
|
|
XELOGI(" - %.8X-%.8X (%9db) %.16llX-%.16llX - %9db used",
|
|
|
|
guest_start, guest_end, (guest_end - guest_start),
|
|
|
|
(uint64_t)start, (uint64_t)end,
|
|
|
|
used_bytes);
|
|
|
|
}
|
|
|
|
void xe_memory_heap_dump(xe_memory_ref memory) {
|
|
|
|
XELOGI("xe_memory_heap_dump:");
|
|
|
|
struct mallinfo info = mspace_mallinfo(memory->heap);
|
|
|
|
XELOGI(" arena: %lld", info.arena);
|
|
|
|
XELOGI(" ordblks: %lld", info.ordblks);
|
|
|
|
XELOGI(" hblks: %lld", info.hblks);
|
|
|
|
XELOGI(" hblkhd: %lld", info.hblkhd);
|
|
|
|
XELOGI(" usmblks: %lld", info.usmblks);
|
|
|
|
XELOGI(" uordblks: %lld", info.uordblks);
|
|
|
|
XELOGI(" fordblks: %lld", info.fordblks);
|
|
|
|
XELOGI(" keepcost: %lld", info.keepcost);
|
|
|
|
mspace_inspect_all(memory->heap, xe_memory_heap_dump_handler, memory);
|
|
|
|
}
|
|
|
|
|
2013-05-30 04:00:55 +00:00
|
|
|
uint32_t xe_memory_heap_alloc(
|
|
|
|
xe_memory_ref memory, uint32_t base_address, uint32_t size,
|
2013-10-20 01:18:19 +00:00
|
|
|
uint32_t flags, uint32_t alignment) {
|
2013-01-29 05:36:03 +00:00
|
|
|
XEASSERT(flags == 0);
|
|
|
|
|
2013-05-30 04:00:55 +00:00
|
|
|
// If we were given a base address we are outside of the normal heap and
|
|
|
|
// will place wherever asked (so long as it doesn't overlap the heap).
|
|
|
|
if (!base_address) {
|
|
|
|
// Normal allocation from the managed heap.
|
|
|
|
XEIGNORE(xe_mutex_lock(memory->heap_mutex));
|
2013-10-20 01:18:19 +00:00
|
|
|
uint8_t* p = (uint8_t*)mspace_memalign(memory->heap, alignment, size);
|
2013-10-20 19:39:59 +00:00
|
|
|
if (FLAGS_log_heap) {
|
|
|
|
xe_memory_heap_dump(memory);
|
|
|
|
}
|
2013-05-30 04:00:55 +00:00
|
|
|
XEIGNORE(xe_mutex_unlock(memory->heap_mutex));
|
|
|
|
if (!p) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return (uint32_t)((uintptr_t)p - (uintptr_t)memory->ptr);
|
|
|
|
} else {
|
|
|
|
if (base_address >= XE_MEMORY_HEAP_LOW &&
|
|
|
|
base_address < XE_MEMORY_HEAP_HIGH) {
|
|
|
|
// Overlapping managed heap.
|
|
|
|
XEASSERTALWAYS();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t* p = (uint8_t*)memory->ptr + base_address;
|
|
|
|
#if XE_PLATFORM(WIN32)
|
|
|
|
// TODO(benvanik): check if address range is in use with a query.
|
|
|
|
|
|
|
|
void* pv = VirtualAlloc(p, size, MEM_COMMIT, PAGE_READWRITE);
|
|
|
|
if (!pv) {
|
|
|
|
// Failed.
|
|
|
|
XEASSERTALWAYS();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#error ?
|
|
|
|
#endif // WIN32
|
2013-01-29 05:36:03 +00:00
|
|
|
|
2013-05-30 04:00:55 +00:00
|
|
|
return base_address;
|
|
|
|
}
|
2013-01-29 05:36:03 +00:00
|
|
|
}
|
|
|
|
|
2013-05-30 04:00:55 +00:00
|
|
|
int xe_memory_heap_free(
|
|
|
|
xe_memory_ref memory, uint32_t address, uint32_t size) {
|
|
|
|
uint8_t* p = (uint8_t*)memory->ptr + address;
|
|
|
|
if (address >= XE_MEMORY_HEAP_LOW && address < XE_MEMORY_HEAP_HIGH) {
|
|
|
|
// Heap allocated address.
|
|
|
|
size_t real_size = mspace_usable_size(p);
|
|
|
|
if (!real_size) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
XEIGNORE(xe_mutex_lock(memory->heap_mutex));
|
|
|
|
mspace_free(memory->heap, p);
|
2013-10-20 19:39:59 +00:00
|
|
|
if (FLAGS_log_heap) {
|
|
|
|
xe_memory_heap_dump(memory);
|
|
|
|
}
|
2013-05-30 04:00:55 +00:00
|
|
|
XEIGNORE(xe_mutex_unlock(memory->heap_mutex));
|
|
|
|
return (uint32_t)real_size;
|
|
|
|
} else {
|
|
|
|
// A placed address. Decommit.
|
|
|
|
#if XE_PLATFORM(WIN32)
|
|
|
|
return VirtualFree(p, size, MEM_DECOMMIT) ? 0 : 1;
|
|
|
|
#else
|
|
|
|
#error decommit
|
|
|
|
#endif // WIN32
|
|
|
|
}
|
|
|
|
}
|
2013-01-29 05:36:03 +00:00
|
|
|
|
2013-05-30 04:00:55 +00:00
|
|
|
bool xe_memory_is_valid(xe_memory_ref memory, uint32_t address) {
|
|
|
|
uint8_t* p = (uint8_t*)memory->ptr + address;
|
|
|
|
if (address >= XE_MEMORY_HEAP_LOW && address < XE_MEMORY_HEAP_HIGH) {
|
|
|
|
// Within heap range, ask dlmalloc.
|
|
|
|
return mspace_usable_size(p) > 0;
|
|
|
|
} else {
|
|
|
|
// Maybe -- could Query here (though that may be expensive).
|
|
|
|
return true;
|
2013-01-29 05:36:03 +00:00
|
|
|
}
|
2013-05-30 04:00:55 +00:00
|
|
|
}
|
2013-01-29 05:36:03 +00:00
|
|
|
|
2013-05-30 04:00:55 +00:00
|
|
|
int xe_memory_protect(
|
|
|
|
xe_memory_ref memory, uint32_t address, uint32_t size, uint32_t access) {
|
|
|
|
uint8_t* p = (uint8_t*)memory->ptr + address;
|
2013-01-29 05:36:03 +00:00
|
|
|
|
2013-05-30 04:00:55 +00:00
|
|
|
#if XE_PLATFORM(WIN32)
|
|
|
|
DWORD new_protect = 0;
|
|
|
|
if (access & XE_MEMORY_ACCESS_WRITE) {
|
|
|
|
new_protect = PAGE_READWRITE;
|
|
|
|
} else if (access & XE_MEMORY_ACCESS_READ) {
|
|
|
|
new_protect = PAGE_READONLY;
|
|
|
|
} else {
|
|
|
|
new_protect = PAGE_NOACCESS;
|
|
|
|
}
|
|
|
|
DWORD old_protect;
|
|
|
|
return VirtualProtect(p, size, new_protect, &old_protect) == TRUE ? 0 : 1;
|
|
|
|
#else
|
|
|
|
int prot = 0;
|
|
|
|
if (access & XE_MEMORY_ACCESS_READ) {
|
|
|
|
prot = PROT_READ;
|
|
|
|
}
|
|
|
|
if (access & XE_MEMORY_ACCESS_WRITE) {
|
|
|
|
prot = PROT_WRITE;
|
|
|
|
}
|
|
|
|
return mprotect(p, size, prot);
|
|
|
|
#endif // WIN32
|
2013-01-29 05:36:03 +00:00
|
|
|
}
|