Browse Source

nintendoswitch: Add env parser and removed unused stuff

*	Heap allocation based on available ram
*	Added homebrew launcher parser (for overriden heap)
*	Removed unused stuff (moved to gonx)
*	Kept require code at minimum to work in a real device
*	Moved everything to a single file
pull/1480/head
Lucas Teske 4 years ago
committed by Ayke
parent
commit
387bca8e32
  1. 7
      src/runtime/dynamic_arm64.go
  2. 220
      src/runtime/runtime_nintendoswitch.go
  3. 71
      src/runtime/runtime_nintendoswitch.s
  4. 25
      src/runtime/runtime_nintendoswitch_heap.go
  5. 7
      src/runtime/runtime_nintendoswitch_noheap.go
  6. 22
      src/runtime/runtime_nintendoswitch_svc.go
  7. 5
      targets/nintendoswitch.json
  8. 1
      targets/nintendoswitch.ld
  9. 64
      targets/nintendoswitch.s

7
src/runtime/dynamic_arm64.go

@ -59,8 +59,15 @@ func dynamicLoader(base uintptr, dyn *dyn64) {
for relasz > 0 && rela != nil { for relasz > 0 && rela != nil {
switch rela.Info { switch rela.Info {
case rAARCH64_RELATIVE: 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)(unsafe.Pointer(base + uintptr(rela.Off)))
*ptr = uint64(base + uintptr(rela.Addend)) *ptr = uint64(base + uintptr(rela.Addend))
default:
if debugLoader {
println("unknown section to load:", rela.Info)
}
} }
rptr := uintptr(unsafe.Pointer(rela)) rptr := uintptr(unsafe.Pointer(rela))

220
src/runtime/runtime_nintendoswitch.go

@ -8,35 +8,67 @@ type timeUnit int64
const asyncScheduler = false 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 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 postinit() {}
func preinit() {
// Unsafe to use heap here
setupEnv()
setupHeap()
}
// Entry point for Go. Initialize all packages and call main.main(). // Entry point for Go. Initialize all packages and call main.main().
//export main //export main
func main() int { func main() {
preinit() preinit()
run()
// 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()
// Call exit to correctly finish the program // Call exit to correctly finish the program
// Without this, the application crashes at start, not sure why // Without this, the application crashes at start, not sure why
return exit(0) for {
} exit(0)
}
// Must be a separate function to get the correct stack pointer.
//go:noinline
func runMain() {
run()
} }
// sleepTicks sleeps for the specified system ticks // sleepTicks sleeps for the specified system ticks
func sleepTicks(d timeUnit) { func sleepTicks(d timeUnit) {
sleepThread(uint64(ticksToNanoseconds(d))) svcSleepThread(uint64(ticksToNanoseconds(d)))
} }
// armTicksToNs converts cpu ticks to nanoseconds // armTicksToNs converts cpu ticks to nanoseconds
@ -60,7 +92,7 @@ var position = 0
func putchar(c byte) { func putchar(c byte) {
if c == '\n' || position >= len(stdoutBuffer) { if c == '\n' || position >= len(stdoutBuffer) {
nxOutputString(&stdoutBuffer[0], uint64(position)) svcOutputDebugString(&stdoutBuffer[0], uint64(position))
position = 0 position = 0
return return
} }
@ -85,11 +117,159 @@ func write(fd int32, buf *byte, count int) int {
return count return count
} }
//export sleepThread // exit checks if a savedReturnAddress were provided by the launcher
func sleepThread(nanos uint64) // 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
}
nxExit(code, stackTop, savedReturnAddress)
}
type configEntry struct {
Key uint32
Flags uint32
Value [2]uint64
}
//export exit func setupEnv() {
func exit(code int) int 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 //export armGetSystemTick
func getArmSystemTick() int64 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

71
src/runtime/runtime_nintendoswitch.s

@ -1,45 +1,40 @@
.section .text.armGetSystemTick, "ax", %progbits // Macro for writing less code
.global armGetSystemTick .macro FUNC name
.type armGetSystemTick, %function .section .text.\name, "ax", %progbits
.align 2 .global \name
armGetSystemTick: .type \name, %function
.align 2
\name:
.endm
FUNC armGetSystemTick
mrs x0, cntpct_el0 mrs x0, cntpct_el0
ret ret
.section .text.nxOutputString, "ax", %progbits // Horizon System Calls
.global nxOutputString // https://switchbrew.org/wiki/SVC
.type nxOutputString, %function FUNC svcSetHeapSize
.align 2 str x0, [sp, #-16]!
.cfi_startproc svc 0x1
nxOutputString: ldr x2, [sp], #16
svc 0x27 str x1, [x2]
ret ret
.cfi_endproc
.section .text.exit, "ax", %progbits FUNC svcExitProcess
.global exit svc 0x7
.type exit, %function ret
.align 2
exit:
svc 0x7
ret
.section .text.setHeapSize, "ax", %progbits FUNC svcSleepThread
.global setHeapSize svc 0xB
.type setHeapSize, %function ret
.align 2
setHeapSize:
str x0, [sp, #-16]!
svc 0x1
ldr x2, [sp], #16
str x1, [x2]
ret
FUNC svcOutputDebugString
svc 0x27
ret
.section .text.sleepThread, "ax", %progbits FUNC svcGetInfo
.global sleepThread str x0, [sp, #-16]!
.type sleepThread, %function svc 0x29
.align 2 ldr x2, [sp], #16
sleepThread: str x1, [x2]
svc 0xB ret
ret

25
src/runtime/runtime_nintendoswitch_heap.go

@ -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
}

7
src/runtime/runtime_nintendoswitch_noheap.go

@ -1,7 +0,0 @@
// +build nintendoswitch
// +build gc.none gc.extalloc
package runtime
func preinit() {}

22
src/runtime/runtime_nintendoswitch_svc.go

@ -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)

5
targets/nintendoswitch.json

@ -9,9 +9,10 @@
"libc": "picolibc", "libc": "picolibc",
"gc": "conservative", "gc": "conservative",
"relocation-model": "pic", "relocation-model": "pic",
"cpu": "cortex-a57",
"cflags": [ "cflags": [
"-target", "aarch64-none-linux-gnu", "-target", "aarch64-unknown-none",
"-mtune=cortex-a57", "-mcpu=cortex-a57",
"-fPIE", "-fPIE",
"-Werror", "-Werror",
"-Qunused-arguments", "-Qunused-arguments",

1
targets/nintendoswitch.ld

@ -17,6 +17,7 @@ SECTIONS
KEEP(*(.text.jmp)) KEEP(*(.text.jmp))
. = 0x80; . = 0x80;
KEEP(*(.text.start))
*(.text .text.*) *(.text .text.*)
*(.plt .plt.*) *(.plt .plt.*)

64
targets/nintendoswitch.s

@ -14,11 +14,11 @@ _start:
.word 0 // flags (unused) .word 0 // flags (unused)
// segment headers // segment headers
.word 0 // __text_start .word __text_start - _start
.word __text_size .word __text_size
.word 0 //__rodata_start .word __rodata_start - _start
.word __rodata_size .word __rodata_size
.word 0 //__data_start .word __data_start - _start
.word __data_size .word __data_size
.word __bss_size .word __bss_size
.word 0 .word 0
@ -41,19 +41,37 @@ _mod_header:
.word 0, 0 // eh_frame_hdr start/end .word 0, 0 // eh_frame_hdr start/end
.word 0 // runtime-generated module object offset .word 0 // runtime-generated module object offset
.section .text, "x" .section .text.start, "x"
.global start .global start
start: start:
// Get ASLR Base // save lr
adrp x6, _start mov x7, x30
// get aslr base
bl +4
sub x6, x30, #0x88
// context ptr and main thread handle // context ptr and main thread handle
mov x5, x0 mov x25, x0
mov x4, x1 mov x26, x1
// Save ASLR Base to use later // Save ASLR Base to use later
mov x0, x6 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 // clear .bss
adrp x5, __bss_start adrp x5, __bss_start
add x5, x5, #:lo12:__bss_start add x5, x5, #:lo12:__bss_start
@ -76,3 +94,33 @@ run:
// call entrypoint // call entrypoint
b main 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

Loading…
Cancel
Save