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. 218
      src/runtime/runtime_nintendoswitch.go
  3. 59
      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 {
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))

218
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)
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
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
}
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
}
//export exit
func exit(code int) int
// 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

59
src/runtime/runtime_nintendoswitch.s

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

1
targets/nintendoswitch.ld

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

64
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

Loading…
Cancel
Save