From d599f38f0d983e0c99550b959238c30bcae570b6 Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Sun, 1 Jul 2018 21:17:43 +0900 Subject: [PATCH 1/3] Rewrite the memory management code for Nintendo Switch The first implementation was naive and did not account for multiple memory allocations. However, this implementation may still be incomplete. Currently, when running applications, the code runs fine. When the application is exited via code (the end of the program is reached or quit() is called), the Switch will crash. Not sure why this happens, but I suspect it is from Nim memory allocations. I suspect the memory allocations because when I compile the helloworld application without any Nim allocations (just C function calls) and use `--gc:none` as a compile option, the application exits fine. --- lib/system/osalloc.nim | 84 ++++++++++++++++++++++++++++++------------ 1 file changed, 61 insertions(+), 23 deletions(-) diff --git a/lib/system/osalloc.nim b/lib/system/osalloc.nim index 048077b50..091274eec 100644 --- a/lib/system/osalloc.nim +++ b/lib/system/osalloc.nim @@ -83,39 +83,77 @@ elif defined(genode): elif defined(nintendoswitch): import nintendoswitch/switch_memory - var - stack: pointer + type + PSwitchBlock = ptr NSwitchBlock + NSwitchBlock {.pure, inheritable.} = object + realSize: int + heap: pointer # pointer to main heap alloc + heapMirror: pointer # pointer to virtmem mapped heap proc alignSize(size: int): int = - (size + 0x00000FFF) and not 0x00000FFF + ## Align a size integer to be in multiples of PageSize + ## The nintendo switch will not allocate memory that is not + ## aligned to 0x1000 bytes and will just crash. + (size + (PageSize - 1)) and not (PageSize - 1) - proc freeMem(p: pointer, msize: int) = - let size = alignSize(msize) - discard svcUnmapMemory(p, stack, size.uint64) - virtmemFreeMap(p, size.csize) - free(stack) + proc freeMem(p: pointer) = + # Retrieve the switch block data from the pointer we set before + let nswitchDescrPos = cast[ByteAddress](p) -% sizeof(NSwitchBlock) + let nswitchBlock = cast[PSwitchBlock](nswitchDescrPos) - proc osAllocPages(msize: int): pointer {.inline.} = - let size = alignSize(msize) - stack = memalign(0x1000, size) - result = virtmemReserveMap(size.csize) - let rc = svcMapMemory(result, stack, size.uint64) + discard svcUnmapMemory( + nswitchBlock.heapMirror, nswitchBlock.heap, nswitchBlock.realSize.uint64 + ) + virtmemFreeMap(nswitchBlock.heapMirror, nswitchBlock.realSize.csize) + free(nswitchBlock.heap) + + template allocPages(size: int, outOfMemoryStmt: untyped): untyped = + let realSize = alignSize(size + sizeof(NSwitchBlock)) + + let heap = memalign(PageSize, realSize) + let heapMirror = virtmemReserveMap(realSize.csize) + result = heapMirror + + let rc = svcMapMemory(heapMirror, heap, realSize.uint64) if rc.uint32 != 0: - freeMem(result, size) + discard svcUnmapMemory(heapMirror, heap, realSize.uint64) + virtmemFreeMap(heapMirror, realSize.csize) + free(heap) + outOfMemoryStmt + + # What we are doing here in the following section is + # allocating memory for ourselves so that we can store + # extra data in the mapped memory (our switch pointers) + + let pos = cast[int](result) + + var new_pos = cast[ByteAddress](pos) +% (PageSize - (pos %% PageSize)) + if (new_pos-pos) < sizeof(NSwitchBlock): + new_pos = new_pos +% PageSize + # set result to be the original size requirement + result = cast[pointer](new_pos) + + let nswitchDescrPos = cast[ByteAddress](result) -% sizeof(NSwitchBlock) + + # We need to store this in a pointer obj so that the data sticks + # at the address + var nswitchDescr = cast[PSwitchBlock](nswitchDescrPos) + nswitchDescr.realSize = realSize + nswitchDescr.heap = heap + nswitchDescr.heapMirror = heapMirror + + + proc osAllocPages(size: int): pointer {.inline.} = + allocPages(size): raiseOutOfMem() - proc osTryAllocPages(msize: int): pointer {.inline.} = - let size = alignSize(msize) - stack = memalign(0x1000, size) - result = virtmemReserveMap(size.csize) - let rc = svcMapMemory(result, stack, size.uint64) - if rc.uint32 != 0: - freeMem(result, size) - result = nil + proc osTryAllocPages(size: int): pointer {.inline.} = + allocPages(size): + return nil proc osDeallocPages(p: pointer, size: int) {.inline.} = when reallyOsDealloc: - freeMem(p, size) + freeMem(p) elif defined(posix): const From 5428e37ae82ef82d1e6d7d02234f38759bf0184b Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Thu, 5 Jul 2018 09:15:18 +0900 Subject: [PATCH 2/3] Add documentation and fix switch crashing on exit --- lib/nintendoswitch/switch_memory.nim | 25 ++++++-- lib/system/osalloc.nim | 91 +++++++++++++++++++--------- 2 files changed, 83 insertions(+), 33 deletions(-) diff --git a/lib/nintendoswitch/switch_memory.nim b/lib/nintendoswitch/switch_memory.nim index 09b34c5d0..f34bd363a 100644 --- a/lib/nintendoswitch/switch_memory.nim +++ b/lib/nintendoswitch/switch_memory.nim @@ -1,21 +1,36 @@ +## All of these library headers and source can be found in the github repo +## https://github.com/switchbrew/libnx. + const virtMemHeader = "" -const svcHeader = "" +const svcHeader = "" const mallocHeader = "" +## Aligns a block of memory with request `size` to `bytes` size. For +## example, a request of memalign(0x1000, 0x1001) == 0x2000 bytes allocated proc memalign*(bytes: csize, size: csize): pointer {.importc: "memalign", header: mallocHeader.} -proc free*(address: pointer) {.importc: "free", - header: mallocHeader.} +# Should be required, but not needed now because of how +# svcUnmapMemory frees all memory +#proc free*(address: pointer) {.importc: "free", +# header: mallocHeader.} +## Maps a memaligned block of memory from `src_addr` to `dst_addr`. The +## Nintendo Switch requires this call in order to make use of memory, otherwise +## an invalid memory access occurs. proc svcMapMemory*(dst_addr: pointer; src_addr: pointer; size: uint64): uint32 {. importc: "svcMapMemory", header: svcHeader.} +## Unmaps (frees) all memory from both `dst_addr` and `src_addr`. **Must** be called +## whenever svcMapMemory is used. The Switch will expect all memory to be allocated +## before gfxExit() calls () proc svcUnmapMemory*(dst_addr: pointer; src_addr: pointer; size: uint64): uint32 {. importc: "svcUnmapMemory", header: svcHeader.} proc virtmemReserveMap*(size: csize): pointer {.importc: "virtmemReserveMap", header: virtMemHeader.} -proc virtmemFreeMap*(address: pointer; size: csize) {.importc: "virtmemFreeMap", - header: virtMemHeader.} +# Should be required, but not needed now because of how +# svcUnmapMemory frees all memory +#proc virtmemFreeMap*(address: pointer; size: csize) {.importc: "virtmemFreeMap", +# header: virtMemHeader.} diff --git a/lib/system/osalloc.nim b/lib/system/osalloc.nim index 091274eec..c215f2b76 100644 --- a/lib/system/osalloc.nim +++ b/lib/system/osalloc.nim @@ -85,6 +85,10 @@ elif defined(nintendoswitch): type PSwitchBlock = ptr NSwitchBlock + ## This will hold the heap pointer data in a separate + ## block of memory that is PageSize bytes above + ## the requested memory. It's the only good way + ## to pass around data with heap allocations NSwitchBlock {.pure, inheritable.} = object realSize: int heap: pointer # pointer to main heap alloc @@ -96,52 +100,76 @@ elif defined(nintendoswitch): ## aligned to 0x1000 bytes and will just crash. (size + (PageSize - 1)) and not (PageSize - 1) + proc deallocate(heapMirror: pointer, heap: pointer, size: int) = + # Unmap the allocated memory + discard svcUnmapMemory(heapMirror, heap, size.uint64) + # These should be called (theoretically), but referencing them crashes the switch. + # The above call seems to free all heap memory, so these are not needed. + # virtmemFreeMap(nswitchBlock.heapMirror, nswitchBlock.realSize.csize) + # free(nswitchBlock.heap) + proc freeMem(p: pointer) = # Retrieve the switch block data from the pointer we set before - let nswitchDescrPos = cast[ByteAddress](p) -% sizeof(NSwitchBlock) - let nswitchBlock = cast[PSwitchBlock](nswitchDescrPos) + # The data is located just sizeof(NSwitchBlock) bytes below + # the top of the pointer to the heap + let + nswitchDescrPos = cast[ByteAddress](p) -% sizeof(NSwitchBlock) + nswitchBlock = cast[PSwitchBlock](nswitchDescrPos) - discard svcUnmapMemory( - nswitchBlock.heapMirror, nswitchBlock.heap, nswitchBlock.realSize.uint64 + deallocate( + nswitchBlock.heapMirror, nswitchBlock.heap, nswitchBlock.realSize ) - virtmemFreeMap(nswitchBlock.heapMirror, nswitchBlock.realSize.csize) - free(nswitchBlock.heap) + + proc storeHeapData(address, heapMirror, heap: pointer, size: int) = + ## Store data in the heap for deallocation purposes later + + # the position of our heap pointer data. Since we allocated PageSize extra + # bytes, we should have a buffer on top of the requested size of at least + # PageSize bytes, which is much larger than sizeof(NSwitchBlock). So we + # decrement the address by sizeof(NSwitchBlock) and use that address + # to store our pointer data + let nswitchBlockPos = cast[ByteAddress](address) -% sizeof(NSwitchBlock) + + # We need to store this in a pointer obj (PSwitchBlock) so that the data sticks + # at the address we've chosen. If NSwitchBlock is used here, the data will + # be all 0 when we try to retrieve it later. + var nswitchBlock = cast[PSwitchBlock](nswitchBlockPos) + nswitchBlock.realSize = size + nswitchBlock.heap = heap + nswitchBlock.heapMirror = heapMirror + + proc getOriginalHeapPosition(address: pointer, difference: int): pointer = + ## This function sets the heap back to the originally requested + ## size + let + pos = cast[int](address) + newPos = cast[ByteAddress](pos) +% difference + + return cast[pointer](newPos) template allocPages(size: int, outOfMemoryStmt: untyped): untyped = + # This is to ensure we get a block of memory the requested + # size, as well as space to store our structure let realSize = alignSize(size + sizeof(NSwitchBlock)) let heap = memalign(PageSize, realSize) + + if heap.isNil: + outOfMemoryStmt + let heapMirror = virtmemReserveMap(realSize.csize) result = heapMirror let rc = svcMapMemory(heapMirror, heap, realSize.uint64) + # Any return code not equal 0 means an error in libnx if rc.uint32 != 0: - discard svcUnmapMemory(heapMirror, heap, realSize.uint64) - virtmemFreeMap(heapMirror, realSize.csize) - free(heap) + deallocate(heapMirror, heap, realSize) outOfMemoryStmt - # What we are doing here in the following section is - # allocating memory for ourselves so that we can store - # extra data in the mapped memory (our switch pointers) - - let pos = cast[int](result) - - var new_pos = cast[ByteAddress](pos) +% (PageSize - (pos %% PageSize)) - if (new_pos-pos) < sizeof(NSwitchBlock): - new_pos = new_pos +% PageSize # set result to be the original size requirement - result = cast[pointer](new_pos) - - let nswitchDescrPos = cast[ByteAddress](result) -% sizeof(NSwitchBlock) - - # We need to store this in a pointer obj so that the data sticks - # at the address - var nswitchDescr = cast[PSwitchBlock](nswitchDescrPos) - nswitchDescr.realSize = realSize - nswitchDescr.heap = heap - nswitchDescr.heapMirror = heapMirror + result = getOriginalHeapPosition(result, realSize - size) + storeHeapData(result, heapMirror, heap, realSize) proc osAllocPages(size: int): pointer {.inline.} = allocPages(size): @@ -152,6 +180,13 @@ elif defined(nintendoswitch): return nil proc osDeallocPages(p: pointer, size: int) {.inline.} = + # Note that in order for the Switch not to crash, a call to + # deallocHeap(runFinalizers = true, allowGcAfterwards = false) + # must be run before gfxExit(). The Switch requires all memory + # to be deallocated before the graphics application has exited. + # + # gfxExit() can be found in in the github + # repo https://github.com/switchbrew/libnx when reallyOsDealloc: freeMem(p) From f9f2facc0832fca1a736ac17bf889c8e778143b3 Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Thu, 5 Jul 2018 20:05:37 +0900 Subject: [PATCH 3/3] Fix inline procs --- lib/system/osalloc.nim | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/system/osalloc.nim b/lib/system/osalloc.nim index c215f2b76..85c796ba0 100644 --- a/lib/system/osalloc.nim +++ b/lib/system/osalloc.nim @@ -81,6 +81,7 @@ elif defined(genode): include genode/alloc # osAllocPages, osTryAllocPages, osDeallocPages elif defined(nintendoswitch): + import nintendoswitch/switch_memory type @@ -94,7 +95,7 @@ elif defined(nintendoswitch): heap: pointer # pointer to main heap alloc heapMirror: pointer # pointer to virtmem mapped heap - proc alignSize(size: int): int = + proc alignSize(size: int): int {.inline.} = ## Align a size integer to be in multiples of PageSize ## The nintendo switch will not allocate memory that is not ## aligned to 0x1000 bytes and will just crash. @@ -120,7 +121,7 @@ elif defined(nintendoswitch): nswitchBlock.heapMirror, nswitchBlock.heap, nswitchBlock.realSize ) - proc storeHeapData(address, heapMirror, heap: pointer, size: int) = + proc storeHeapData(address, heapMirror, heap: pointer, size: int) {.inline.} = ## Store data in the heap for deallocation purposes later # the position of our heap pointer data. Since we allocated PageSize extra @@ -138,7 +139,7 @@ elif defined(nintendoswitch): nswitchBlock.heap = heap nswitchBlock.heapMirror = heapMirror - proc getOriginalHeapPosition(address: pointer, difference: int): pointer = + proc getOriginalHeapPosition(address: pointer, difference: int): pointer {.inline.} = ## This function sets the heap back to the originally requested ## size let @@ -175,11 +176,11 @@ elif defined(nintendoswitch): allocPages(size): raiseOutOfMem() - proc osTryAllocPages(size: int): pointer {.inline.} = + proc osTryAllocPages(size: int): pointer = allocPages(size): return nil - proc osDeallocPages(p: pointer, size: int) {.inline.} = + proc osDeallocPages(p: pointer, size: int) = # Note that in order for the Switch not to crash, a call to # deallocHeap(runFinalizers = true, allowGcAfterwards = false) # must be run before gfxExit(). The Switch requires all memory