diff --git a/src/runtime/dynamic_arm64.go b/src/runtime/dynamic_arm64.go index 6fa6ef3c..645c7975 100644 --- a/src/runtime/dynamic_arm64.go +++ b/src/runtime/dynamic_arm64.go @@ -59,8 +59,15 @@ func dynamicLoader(base uintptr, dyn *dyn64) { for relasz > 0 && rela != nil { switch rela.Info { case rAARCH64_RELATIVE: + if debugLoader { + println("relocating ", uintptr(rela.Addend), " to ", base+uintptr(rela.Addend)) + } ptr := (*uint64)(unsafe.Pointer(base + uintptr(rela.Off))) *ptr = uint64(base + uintptr(rela.Addend)) + default: + if debugLoader { + println("unknown section to load:", rela.Info) + } } rptr := uintptr(unsafe.Pointer(rela)) diff --git a/src/runtime/runtime_nintendoswitch.go b/src/runtime/runtime_nintendoswitch.go index f5107fa8..6930e371 100644 --- a/src/runtime/runtime_nintendoswitch.go +++ b/src/runtime/runtime_nintendoswitch.go @@ -8,35 +8,67 @@ type timeUnit int64 const asyncScheduler = false +const ( + // Handles + infoTypeTotalMemorySize = 6 // Total amount of memory available for process. + infoTypeUsedMemorySize = 7 // Amount of memory currently used by process. + currentProcessHandle = 0xFFFF8001 // Pseudo handle for the current process. + + // Types of config Entry + envEntryTypeEndOfList = 0 // Entry list terminator. + envEntryTypeMainThreadHandle = 1 // Provides the handle to the main thread. + envEntryTypeOverrideHeap = 3 // Provides heap override information. + + // Default heap size allocated by libnx + defaultHeapSize = 0x2000000 * 16 + + debugInit = false +) + +//go:extern _saved_return_address +var savedReturnAddress uintptr + +//export __stack_top var stackTop uintptr +//go:extern _context +var context uintptr + +//go:extern _main_thread +var mainThread uintptr + +var ( + heapStart = uintptr(0) + heapEnd = uintptr(0) + usedRam = uint64(0) + totalRam = uint64(0) + totalHeap = uint64(0) +) + func postinit() {} +func preinit() { + // Unsafe to use heap here + setupEnv() + setupHeap() +} + // Entry point for Go. Initialize all packages and call main.main(). //export main -func main() int { +func main() { preinit() - - // Obtain the initial stack pointer right before calling the run() function. - // The run function has been moved to a separate (non-inlined) function so - // that the correct stack pointer is read. - stackTop = getCurrentStackPointer() - runMain() + run() // Call exit to correctly finish the program // Without this, the application crashes at start, not sure why - return exit(0) -} - -// Must be a separate function to get the correct stack pointer. -//go:noinline -func runMain() { - run() + for { + exit(0) + } } // sleepTicks sleeps for the specified system ticks func sleepTicks(d timeUnit) { - sleepThread(uint64(ticksToNanoseconds(d))) + svcSleepThread(uint64(ticksToNanoseconds(d))) } // armTicksToNs converts cpu ticks to nanoseconds @@ -60,7 +92,7 @@ var position = 0 func putchar(c byte) { if c == '\n' || position >= len(stdoutBuffer) { - nxOutputString(&stdoutBuffer[0], uint64(position)) + svcOutputDebugString(&stdoutBuffer[0], uint64(position)) position = 0 return } @@ -85,11 +117,159 @@ func write(fd int32, buf *byte, count int) int { return count } -//export sleepThread -func sleepThread(nanos uint64) +// exit checks if a savedReturnAddress were provided by the launcher +// if so, calls the nxExit which restores the stack and returns to launcher +// otherwise just calls systemcall exit +func exit(code int) { + if savedReturnAddress == 0 { + svcExitProcess(code) + return + } -//export exit -func exit(code int) int + nxExit(code, stackTop, savedReturnAddress) +} + +type configEntry struct { + Key uint32 + Flags uint32 + Value [2]uint64 +} + +func setupEnv() { + if debugInit { + println("Saved Return Address:", savedReturnAddress) + println("Context:", context) + println("Main Thread Handle:", mainThread) + } + + // See https://switchbrew.org/w/index.php?title=Homebrew_ABI + // Here we parse only the required configs for initializing + if context != 0 { + ptr := context + entry := (*configEntry)(unsafe.Pointer(ptr)) + for entry.Key != envEntryTypeEndOfList { + switch entry.Key { + case envEntryTypeOverrideHeap: + if debugInit { + println("Got heap override") + } + heapStart = uintptr(entry.Value[0]) + heapEnd = heapStart + uintptr(entry.Value[1]) + case envEntryTypeMainThreadHandle: + mainThread = uintptr(entry.Value[0]) + default: + if entry.Flags&1 > 0 { + // Mandatory but not parsed + runtimePanic("mandatory config entry not parsed") + } + } + ptr += unsafe.Sizeof(configEntry{}) + entry = (*configEntry)(unsafe.Pointer(ptr)) + } + } + // Fetch used / total RAM for allocating HEAP + svcGetInfo(&totalRam, infoTypeTotalMemorySize, currentProcessHandle, 0) + svcGetInfo(&usedRam, infoTypeUsedMemorySize, currentProcessHandle, 0) +} + +func setupHeap() { + if heapStart != 0 { + if debugInit { + print("Heap already overrided by hblauncher") + } + // Already overrided + return + } + + if debugInit { + print("No heap override. Using normal initialization") + } + + size := uint32(defaultHeapSize) + + if totalRam > usedRam+0x200000 { + // Get maximum possible heap + size = uint32(totalRam-usedRam-0x200000) & ^uint32(0x1FFFFF) + } + + if size < defaultHeapSize { + size = defaultHeapSize + } + + if debugInit { + println("Trying to allocate", size, "bytes of heap") + } + + svcSetHeapSize(&heapStart, uint64(size)) + + if heapStart == 0 { + runtimePanic("failed to allocate heap") + } + + totalHeap = uint64(size) + + heapEnd = heapStart + uintptr(size) + + if debugInit { + println("Heap Start", heapStart) + println("Heap End ", heapEnd) + println("Total Heap", totalHeap) + } +} + +// getHeapBase returns the start address of the heap +// this is externally linked by gonx +func getHeapBase() uintptr { + return heapStart +} + +// getHeapEnd returns the end address of the heap +// this is externally linked by gonx +func getHeapEnd() uintptr { + return heapEnd +} + +// getContextPtr returns the hblauncher context +// this is externally linked by gonx +func getContextPtr() uintptr { + return context +} + +// getMainThreadHandle returns the main thread handler if any +// this is externally linked by gonx +func getMainThreadHandle() uintptr { + return mainThread +} //export armGetSystemTick func getArmSystemTick() int64 + +// nxExit exits the program to homebrew launcher +//export __nx_exit +func nxExit(code int, stackTop uintptr, exitFunction uintptr) + +// Horizon System Calls +// svcSetHeapSize Set the process heap to a given size. It can both extend and shrink the heap. +// svc 0x01 +//export svcSetHeapSize +func svcSetHeapSize(addr *uintptr, length uint64) uint64 + +// svcExitProcess Exits the current process. +// svc 0x07 +//export svcExitProcess +func svcExitProcess(code int) + +// svcSleepThread Sleeps the current thread for the specified amount of time. +// svc 0x0B +//export svcSleepThread +func svcSleepThread(nanos uint64) + +// svcOutputDebugString Outputs debug text, if used during debugging. +// svc 0x27 +//export svcOutputDebugString +func svcOutputDebugString(str *uint8, size uint64) uint64 + +// svcGetInfo Retrieves information about the system, or a certain kernel object. +// svc 0x29 +//export svcGetInfo +func svcGetInfo(output *uint64, id0 uint32, handle uint32, id1 uint64) uint64 diff --git a/src/runtime/runtime_nintendoswitch.s b/src/runtime/runtime_nintendoswitch.s index 58ffe273..a4cac23e 100644 --- a/src/runtime/runtime_nintendoswitch.s +++ b/src/runtime/runtime_nintendoswitch.s @@ -1,45 +1,40 @@ -.section .text.armGetSystemTick, "ax", %progbits -.global armGetSystemTick -.type armGetSystemTick, %function -.align 2 -armGetSystemTick: +// Macro for writing less code +.macro FUNC name + .section .text.\name, "ax", %progbits + .global \name + .type \name, %function + .align 2 +\name: +.endm + +FUNC armGetSystemTick mrs x0, cntpct_el0 ret -.section .text.nxOutputString, "ax", %progbits -.global nxOutputString -.type nxOutputString, %function -.align 2 -.cfi_startproc -nxOutputString: - svc 0x27 - ret -.cfi_endproc +// Horizon System Calls +// https://switchbrew.org/wiki/SVC +FUNC svcSetHeapSize + str x0, [sp, #-16]! + svc 0x1 + ldr x2, [sp], #16 + str x1, [x2] + ret -.section .text.exit, "ax", %progbits -.global exit -.type exit, %function -.align 2 -exit: - svc 0x7 - ret +FUNC svcExitProcess + svc 0x7 + ret -.section .text.setHeapSize, "ax", %progbits -.global setHeapSize -.type setHeapSize, %function -.align 2 -setHeapSize: - str x0, [sp, #-16]! - svc 0x1 - ldr x2, [sp], #16 - str x1, [x2] - ret +FUNC svcSleepThread + svc 0xB + ret +FUNC svcOutputDebugString + svc 0x27 + ret -.section .text.sleepThread, "ax", %progbits -.global sleepThread -.type sleepThread, %function -.align 2 -sleepThread: - svc 0xB - ret +FUNC svcGetInfo + str x0, [sp, #-16]! + svc 0x29 + ldr x2, [sp], #16 + str x1, [x2] + ret diff --git a/src/runtime/runtime_nintendoswitch_heap.go b/src/runtime/runtime_nintendoswitch_heap.go deleted file mode 100644 index e8df7e4b..00000000 --- a/src/runtime/runtime_nintendoswitch_heap.go +++ /dev/null @@ -1,25 +0,0 @@ -// +build nintendoswitch - -// +build gc.conservative gc.leaking - -package runtime - -const heapSize = 0x2000000 * 16 // Default by libnx - -var ( - heapStart = uintptr(0) - heapEnd = uintptr(0) -) - -//export setHeapSize -func setHeapSize(addr *uintptr, length uint64) uint64 - -func preinit() { - setHeapSize(&heapStart, heapSize) - - if heapStart == 0 { - runtimePanic("failed to allocate heap") - } - - heapEnd = heapStart + heapSize -} diff --git a/src/runtime/runtime_nintendoswitch_noheap.go b/src/runtime/runtime_nintendoswitch_noheap.go deleted file mode 100644 index 41e0b368..00000000 --- a/src/runtime/runtime_nintendoswitch_noheap.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build nintendoswitch - -// +build gc.none gc.extalloc - -package runtime - -func preinit() {} diff --git a/src/runtime/runtime_nintendoswitch_svc.go b/src/runtime/runtime_nintendoswitch_svc.go deleted file mode 100644 index 2bc2003f..00000000 --- a/src/runtime/runtime_nintendoswitch_svc.go +++ /dev/null @@ -1,22 +0,0 @@ -// +build nintendoswitch - -package runtime - -import ( - "unsafe" -) - -// Result nxOutputString(const char *str, u64 size) -//export nxOutputString -func nxOutputString(str *uint8, size uint64) uint64 - -func NxOutputString(str string) uint64 { - strData := (*_string)(unsafe.Pointer(&str)) - return nxOutputString((*uint8)(unsafe.Pointer(strData.ptr)), uint64(strData.length)) -} - -//export malloc -func extalloc(size uintptr) unsafe.Pointer - -//export free -func extfree(ptr unsafe.Pointer) diff --git a/targets/nintendoswitch.json b/targets/nintendoswitch.json index aa8567b0..b7bc5bcd 100644 --- a/targets/nintendoswitch.json +++ b/targets/nintendoswitch.json @@ -9,9 +9,10 @@ "libc": "picolibc", "gc": "conservative", "relocation-model": "pic", + "cpu": "cortex-a57", "cflags": [ - "-target", "aarch64-none-linux-gnu", - "-mtune=cortex-a57", + "-target", "aarch64-unknown-none", + "-mcpu=cortex-a57", "-fPIE", "-Werror", "-Qunused-arguments", diff --git a/targets/nintendoswitch.ld b/targets/nintendoswitch.ld index 40179866..b8e9f5d9 100644 --- a/targets/nintendoswitch.ld +++ b/targets/nintendoswitch.ld @@ -17,6 +17,7 @@ SECTIONS KEEP(*(.text.jmp)) . = 0x80; + KEEP(*(.text.start)) *(.text .text.*) *(.plt .plt.*) diff --git a/targets/nintendoswitch.s b/targets/nintendoswitch.s index 38e0b39e..48edc857 100644 --- a/targets/nintendoswitch.s +++ b/targets/nintendoswitch.s @@ -14,11 +14,11 @@ _start: .word 0 // flags (unused) // segment headers - .word 0 // __text_start + .word __text_start - _start .word __text_size - .word 0 //__rodata_start + .word __rodata_start - _start .word __rodata_size - .word 0 //__data_start + .word __data_start - _start .word __data_size .word __bss_size .word 0 @@ -41,19 +41,37 @@ _mod_header: .word 0, 0 // eh_frame_hdr start/end .word 0 // runtime-generated module object offset -.section .text, "x" +.section .text.start, "x" .global start start: - // Get ASLR Base - adrp x6, _start + // save lr + mov x7, x30 + + // get aslr base + bl +4 + sub x6, x30, #0x88 // context ptr and main thread handle - mov x5, x0 - mov x4, x1 + mov x25, x0 + mov x26, x1 // Save ASLR Base to use later mov x0, x6 + adrp x4, _saved_return_address + str x7, [x4, #:lo12:_saved_return_address] + + adrp x4, _context + str x25, [x4, #:lo12:_context] + + adrp x4, _main_thread + str x26, [x4, #:lo12:_main_thread] + + // store stack pointer + mov x26, sp + adrp x4, _stack_top + str x26, [x4, #:lo12:_stack_top] + // clear .bss adrp x5, __bss_start add x5, x5, #:lo12:__bss_start @@ -76,3 +94,33 @@ run: // call entrypoint b main + +.global __nx_exit +.type __nx_exit, %function +__nx_exit: + // Exit code in x0 + + // restore stack pointer + mov sp, x1 + + // jump back to loader + br x2 + +.section .data.horizon +.align 8 +.global _saved_return_address // Saved return address. + // This might be different than null when coming from launcher +_saved_return_address: + .dword 0 +.global _context // Homebrew Launcher Context + // This might be different than null when not coming from launcher +_context: + .dword 0 + +.global _main_thread +_main_thread: + .dword 0 + +.global _stack_top +_stack_top: + .dword 0