xboxkrnl: NtAllocateVirtualMemory - Align the base address to a page boundary rather than returning an error code.

Return ACCESS_DENIED if the user requests e.g. a 64k page in a 4k region.
This commit is contained in:
Dr. Chat 2016-07-28 16:56:05 -05:00
parent 6af8546c49
commit 54ba3480e4
1 changed files with 62 additions and 78 deletions

View File

@ -56,29 +56,22 @@ uint32_t FromXdkProtectFlags(uint32_t protect) {
return result; return result;
} }
SHIM_CALL NtAllocateVirtualMemory_shim(PPCContext* ppc_context, dword_result_t NtAllocateVirtualMemory(lpdword_t base_addr_ptr,
KernelState* kernel_state) { lpdword_t region_size_ptr,
uint32_t base_addr_ptr = SHIM_GET_ARG_32(0); dword_t alloc_type, dword_t protect_bits,
uint32_t base_addr_value = SHIM_MEM_32(base_addr_ptr); dword_t debug_memory) {
uint32_t region_size_ptr = SHIM_GET_ARG_32(1);
uint32_t region_size_value = SHIM_MEM_32(region_size_ptr);
uint32_t alloc_type = SHIM_GET_ARG_32(2); // X_MEM_* bitmask
uint32_t protect_bits = SHIM_GET_ARG_32(3); // X_PAGE_* bitmask
uint32_t unknown = SHIM_GET_ARG_32(4);
XELOGD("NtAllocateVirtualMemory(%.8X(%.8X), %.8X(%.8X), %.8X, %.8X, %.8X)",
base_addr_ptr, base_addr_value, region_size_ptr, region_size_value,
alloc_type, protect_bits, unknown);
// NTSTATUS // NTSTATUS
// _Inout_ PVOID *BaseAddress, // _Inout_ PVOID *BaseAddress,
// _Inout_ PSIZE_T RegionSize, // _Inout_ PSIZE_T RegionSize,
// _In_ ULONG AllocationType, // _In_ ULONG AllocationType,
// _In_ ULONG Protect // _In_ ULONG Protect
// ? handle? // _In_ BOOLEAN DebugMemory
// I've only seen zero. assert_not_null(base_addr_ptr);
assert_true(unknown == 0); assert_not_null(region_size_ptr);
// Set to TRUE when allocation is from devkit memory area.
assert_true(debug_memory == 0);
// This allocates memory from the kernel heap, which is initialized on startup // This allocates memory from the kernel heap, which is initialized on startup
// and shared by both the kernel implementation and user code. // and shared by both the kernel implementation and user code.
@ -86,19 +79,16 @@ SHIM_CALL NtAllocateVirtualMemory_shim(PPCContext* ppc_context,
// it's simple today we could extend it to do better things in the future. // it's simple today we could extend it to do better things in the future.
// Must request a size. // Must request a size.
if (!region_size_value) { if (!base_addr_ptr) {
SHIM_SET_RETURN_32(X_STATUS_INVALID_PARAMETER); return X_STATUS_INVALID_PARAMETER;
return;
} }
// Check allocation type. // Check allocation type.
if (!(alloc_type & (X_MEM_COMMIT | X_MEM_RESET | X_MEM_RESERVE))) { if (!(alloc_type & (X_MEM_COMMIT | X_MEM_RESET | X_MEM_RESERVE))) {
SHIM_SET_RETURN_32(X_STATUS_INVALID_PARAMETER); return X_STATUS_INVALID_PARAMETER;
return;
} }
// If MEM_RESET is set only MEM_RESET can be set. // If MEM_RESET is set only MEM_RESET can be set.
if (alloc_type & X_MEM_RESET && (alloc_type & ~X_MEM_RESET)) { if (alloc_type & X_MEM_RESET && (alloc_type & ~X_MEM_RESET)) {
SHIM_SET_RETURN_32(X_STATUS_INVALID_PARAMETER); return X_STATUS_INVALID_PARAMETER;
return;
} }
// Don't allow games to set execute bits. // Don't allow games to set execute bits.
if (protect_bits & (X_PAGE_EXECUTE | X_PAGE_EXECUTE_READ | if (protect_bits & (X_PAGE_EXECUTE | X_PAGE_EXECUTE_READ |
@ -111,18 +101,14 @@ SHIM_CALL NtAllocateVirtualMemory_shim(PPCContext* ppc_context,
if (alloc_type & X_MEM_LARGE_PAGES) { if (alloc_type & X_MEM_LARGE_PAGES) {
page_size = 64 * 1024; page_size = 64 * 1024;
} }
if (int32_t(region_size_value) < 0) {
// Some games pass in negative sizes.
region_size_value = -int32_t(region_size_value);
}
uint32_t adjusted_size = xe::round_up(region_size_value, page_size);
// Some games (BF1943) do this, but then if we return an error code it'll // Round the base address down to the nearest page boundary.
// allocate with a smaller page size. uint32_t adjusted_base = *base_addr_ptr - (*base_addr_ptr % page_size);
if (base_addr_value % page_size != 0) { // For some reason, some games pass in negative sizes.
SHIM_SET_RETURN_32(X_STATUS_MAPPED_ALIGNMENT); uint32_t adjusted_size = int32_t(*region_size_ptr) < 0
return; ? -int32_t(*region_size_ptr)
} : *region_size_ptr;
adjusted_size = xe::round_up(adjusted_size, page_size);
// Allocate. // Allocate.
uint32_t allocation_type = 0; uint32_t allocation_type = 0;
@ -138,28 +124,32 @@ SHIM_CALL NtAllocateVirtualMemory_shim(PPCContext* ppc_context,
} }
uint32_t protect = FromXdkProtectFlags(protect_bits); uint32_t protect = FromXdkProtectFlags(protect_bits);
uint32_t address = 0; uint32_t address = 0;
if (base_addr_value) { if (adjusted_base != 0) {
auto heap = kernel_state->memory()->LookupHeap(base_addr_value); auto heap = kernel_memory()->LookupHeap(adjusted_base);
if (heap->AllocFixed(base_addr_value, adjusted_size, page_size, if (heap->page_size() != page_size) {
// Specified the wrong page size for the wrong heap.
return X_STATUS_ACCESS_DENIED;
}
if (heap->AllocFixed(adjusted_base, adjusted_size, page_size,
allocation_type, protect)) { allocation_type, protect)) {
address = base_addr_value; address = adjusted_base;
} }
} else { } else {
bool top_down = !!(alloc_type & X_MEM_TOP_DOWN); bool top_down = !!(alloc_type & X_MEM_TOP_DOWN);
auto heap = kernel_state->memory()->LookupHeapByType(false, page_size); auto heap = kernel_memory()->LookupHeapByType(false, page_size);
heap->Alloc(adjusted_size, page_size, allocation_type, protect, top_down, heap->Alloc(adjusted_size, page_size, allocation_type, protect, top_down,
&address); &address);
} }
if (!address) { if (!address) {
// Failed - assume no memory available. // Failed - assume no memory available.
SHIM_SET_RETURN_32(X_STATUS_NO_MEMORY); return X_STATUS_NO_MEMORY;
return;
} }
// Zero memory, if needed. // Zero memory, if needed.
if (address && !(alloc_type & X_MEM_NOZERO)) { if (address && !(alloc_type & X_MEM_NOZERO)) {
if (alloc_type & X_MEM_COMMIT) { if (alloc_type & X_MEM_COMMIT) {
kernel_state->memory()->Zero(address, adjusted_size); kernel_memory()->Zero(address, adjusted_size);
} }
} }
@ -167,10 +157,12 @@ SHIM_CALL NtAllocateVirtualMemory_shim(PPCContext* ppc_context,
// Stash back. // Stash back.
// Maybe set X_STATUS_ALREADY_COMMITTED if MEM_COMMIT? // Maybe set X_STATUS_ALREADY_COMMITTED if MEM_COMMIT?
SHIM_SET_MEM_32(base_addr_ptr, address); *base_addr_ptr = address;
SHIM_SET_MEM_32(region_size_ptr, adjusted_size); *region_size_ptr = adjusted_size;
SHIM_SET_RETURN_32(X_STATUS_SUCCESS); return X_STATUS_SUCCESS;
} }
DECLARE_XBOXKRNL_EXPORT(NtAllocateVirtualMemory,
ExportTag::kImplemented | ExportTag::kMemory);
SHIM_CALL NtFreeVirtualMemory_shim(PPCContext* ppc_context, SHIM_CALL NtFreeVirtualMemory_shim(PPCContext* ppc_context,
KernelState* kernel_state) { KernelState* kernel_state) {
@ -180,20 +172,20 @@ SHIM_CALL NtFreeVirtualMemory_shim(PPCContext* ppc_context,
uint32_t region_size_value = SHIM_MEM_32(region_size_ptr); uint32_t region_size_value = SHIM_MEM_32(region_size_ptr);
// X_MEM_DECOMMIT | X_MEM_RELEASE // X_MEM_DECOMMIT | X_MEM_RELEASE
uint32_t free_type = SHIM_GET_ARG_32(2); uint32_t free_type = SHIM_GET_ARG_32(2);
uint32_t unknown = SHIM_GET_ARG_32(3); uint32_t debug_memory = SHIM_GET_ARG_32(3);
XELOGD("NtFreeVirtualMemory(%.8X(%.8X), %.8X(%.8X), %.8X, %.8X)", XELOGD("NtFreeVirtualMemory(%.8X(%.8X), %.8X(%.8X), %.8X, %.8X)",
base_addr_ptr, base_addr_value, region_size_ptr, region_size_value, base_addr_ptr, base_addr_value, region_size_ptr, region_size_value,
free_type, unknown); free_type, debug_memory);
// NTSTATUS // NTSTATUS
// _Inout_ PVOID *BaseAddress, // _Inout_ PVOID *BaseAddress,
// _Inout_ PSIZE_T RegionSize, // _Inout_ PSIZE_T RegionSize,
// _In_ ULONG FreeType // _In_ ULONG FreeType
// ? handle? // _In_ BOOLEAN DebugMemory
// I've only seen zero. // Set to TRUE when freeing external devkit memory.
assert_true(unknown == 0); assert_true(debug_memory == 0);
if (!base_addr_value) { if (!base_addr_value) {
SHIM_SET_RETURN_32(X_STATUS_MEMORY_NOT_ALLOCATED); SHIM_SET_RETURN_32(X_STATUS_MEMORY_NOT_ALLOCATED);
@ -270,26 +262,18 @@ SHIM_CALL NtQueryVirtualMemory_shim(PPCContext* ppc_context,
SHIM_SET_RETURN_32(X_STATUS_SUCCESS); SHIM_SET_RETURN_32(X_STATUS_SUCCESS);
} }
SHIM_CALL MmAllocatePhysicalMemoryEx_shim(PPCContext* ppc_context, dword_result_t MmAllocatePhysicalMemoryEx(dword_t flags, dword_t region_size,
KernelState* kernel_state) { dword_t protect_bits,
uint32_t type = SHIM_GET_ARG_32(0); dword_t min_addr_range,
uint32_t region_size = SHIM_GET_ARG_32(1); dword_t max_addr_range,
uint32_t protect_bits = SHIM_GET_ARG_32(2); dword_t alignment) {
uint32_t min_addr_range = SHIM_GET_ARG_32(3);
uint32_t max_addr_range = SHIM_GET_ARG_32(4);
uint32_t alignment = SHIM_GET_ARG_32(5);
XELOGD("MmAllocatePhysicalMemoryEx(%d, %.8X, %.8X, %.8X, %.8X, %.8X)", type,
region_size, protect_bits, min_addr_range, max_addr_range, alignment);
// Type will usually be 0 (user request?), where 1 and 2 are sometimes made // Type will usually be 0 (user request?), where 1 and 2 are sometimes made
// by D3D/etc. // by D3D/etc.
// Check protection bits. // Check protection bits.
if (!(protect_bits & (X_PAGE_READONLY | X_PAGE_READWRITE))) { if (!(protect_bits & (X_PAGE_READONLY | X_PAGE_READWRITE))) {
XELOGE("MmAllocatePhysicalMemoryEx: bad protection bits"); XELOGE("MmAllocatePhysicalMemoryEx: bad protection bits");
SHIM_SET_RETURN_32(0); return 0;
return;
} }
// Either may be OR'ed into protect_bits: // Either may be OR'ed into protect_bits:
@ -314,27 +298,23 @@ SHIM_CALL MmAllocatePhysicalMemoryEx_shim(PPCContext* ppc_context,
uint32_t adjusted_size = xe::round_up(region_size, page_size); uint32_t adjusted_size = xe::round_up(region_size, page_size);
uint32_t adjusted_alignment = xe::round_up(alignment, page_size); uint32_t adjusted_alignment = xe::round_up(alignment, page_size);
// Callers can pick an address to allocate with min_addr_range/max_addr_range
// and the memory must be allocated there. I haven't seen a game do this,
// and instead they all do min=0 / max=-1 to indicate the system should pick.
// If we have to suport arbitrary placement things will get nasty.
uint32_t allocation_type = kMemoryAllocationReserve | kMemoryAllocationCommit; uint32_t allocation_type = kMemoryAllocationReserve | kMemoryAllocationCommit;
uint32_t protect = FromXdkProtectFlags(protect_bits); uint32_t protect = FromXdkProtectFlags(protect_bits);
bool top_down = true; bool top_down = true;
auto heap = kernel_state->memory()->LookupHeapByType(true, page_size); auto heap = kernel_memory()->LookupHeapByType(true, page_size);
uint32_t base_address; uint32_t base_address;
if (!heap->AllocRange(min_addr_range, max_addr_range, adjusted_size, if (!heap->AllocRange(min_addr_range, max_addr_range, adjusted_size,
adjusted_alignment, allocation_type, protect, top_down, adjusted_alignment, allocation_type, protect, top_down,
&base_address)) { &base_address)) {
// Failed - assume no memory available. // Failed - assume no memory available.
SHIM_SET_RETURN_32(0); return 0;
return;
} }
XELOGD("MmAllocatePhysicalMemoryEx = %.8X", base_address); XELOGD("MmAllocatePhysicalMemoryEx = %.8X", base_address);
SHIM_SET_RETURN_32(base_address); return base_address;
} }
DECLARE_XBOXKRNL_EXPORT(MmAllocatePhysicalMemoryEx,
ExportTag::kImplemented | ExportTag::kMemory);
SHIM_CALL MmFreePhysicalMemory_shim(PPCContext* ppc_context, SHIM_CALL MmFreePhysicalMemory_shim(PPCContext* ppc_context,
KernelState* kernel_state) { KernelState* kernel_state) {
@ -373,7 +353,8 @@ void MmSetAddressProtect(lpvoid_t base_address, dword_t region_size,
auto heap = kernel_memory()->LookupHeap(base_address); auto heap = kernel_memory()->LookupHeap(base_address);
heap->Protect(base_address.guest_address(), region_size, protect); heap->Protect(base_address.guest_address(), region_size, protect);
} }
DECLARE_XBOXKRNL_EXPORT(MmSetAddressProtect, ExportTag::kMemory); DECLARE_XBOXKRNL_EXPORT(MmSetAddressProtect,
ExportTag::kImplemented | ExportTag::kMemory);
SHIM_CALL MmQueryAllocationSize_shim(PPCContext* ppc_context, SHIM_CALL MmQueryAllocationSize_shim(PPCContext* ppc_context,
KernelState* kernel_state) { KernelState* kernel_state) {
@ -568,6 +549,12 @@ SHIM_CALL ExFreePool_shim(PPCContext* ppc_context, KernelState* kernel_state) {
kernel_state->memory()->SystemHeapFree(base_address); kernel_state->memory()->SystemHeapFree(base_address);
} }
dword_result_t KeGetImagePageTableEntry(lpvoid_t address) {
// Unknown
return 1;
}
DECLARE_XBOXKRNL_EXPORT(KeGetImagePageTableEntry, ExportTag::kStub);
SHIM_CALL KeLockL2_shim(PPCContext* ppc_context, KernelState* kernel_state) { SHIM_CALL KeLockL2_shim(PPCContext* ppc_context, KernelState* kernel_state) {
// Ignored for now. This is just a perf optimization, I think. // Ignored for now. This is just a perf optimization, I think.
// It may be useful as a hint for CPU-GPU transfer. // It may be useful as a hint for CPU-GPU transfer.
@ -610,11 +597,8 @@ DECLARE_XBOXKRNL_EXPORT(MmDeleteKernelStack, ExportTag::kImplemented);
void RegisterMemoryExports(xe::cpu::ExportResolver* export_resolver, void RegisterMemoryExports(xe::cpu::ExportResolver* export_resolver,
KernelState* kernel_state) { KernelState* kernel_state) {
SHIM_SET_MAPPING("xboxkrnl.exe", NtAllocateVirtualMemory, state);
SHIM_SET_MAPPING("xboxkrnl.exe", NtFreeVirtualMemory, state); SHIM_SET_MAPPING("xboxkrnl.exe", NtFreeVirtualMemory, state);
SHIM_SET_MAPPING("xboxkrnl.exe", NtQueryVirtualMemory, state); SHIM_SET_MAPPING("xboxkrnl.exe", NtQueryVirtualMemory, state);
// SHIM_SET_MAPPING("xboxkrnl.exe", MmAllocatePhysicalMemory, state);
SHIM_SET_MAPPING("xboxkrnl.exe", MmAllocatePhysicalMemoryEx, state);
SHIM_SET_MAPPING("xboxkrnl.exe", MmFreePhysicalMemory, state); SHIM_SET_MAPPING("xboxkrnl.exe", MmFreePhysicalMemory, state);
SHIM_SET_MAPPING("xboxkrnl.exe", MmQueryAddressProtect, state); SHIM_SET_MAPPING("xboxkrnl.exe", MmQueryAddressProtect, state);
SHIM_SET_MAPPING("xboxkrnl.exe", MmQueryAllocationSize, state); SHIM_SET_MAPPING("xboxkrnl.exe", MmQueryAllocationSize, state);