aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLucas Teske <[email protected]>2020-09-13 21:10:27 -0300
committerRon Evans <[email protected]>2020-09-17 07:46:29 +0200
commitfb1fc267ab8beca0c3b7f11a6441e0b403017d7c (patch)
tree621c9630c567dbceab00edea650862a620b26459
parent490e377bba012afb6fd606b156276b76c24a3312 (diff)
downloadtinygo-fb1fc267ab8beca0c3b7f11a6441e0b403017d7c.tar.gz
tinygo-fb1fc267ab8beca0c3b7f11a6441e0b403017d7c.zip
nintendoswitch: Add dynamic loader for runtime loading PIE sections
-rw-r--r--builder/objcopy.go11
-rw-r--r--src/runtime/dynamic_arm64.go55
-rw-r--r--src/runtime/runtime_nintendoswitch.go12
-rw-r--r--targets/nintendoswitch.json4
-rw-r--r--targets/nintendoswitch.ld53
-rw-r--r--targets/nintendoswitch.s46
6 files changed, 147 insertions, 34 deletions
diff --git a/builder/objcopy.go b/builder/objcopy.go
index c2fefa047..a8a76afa9 100644
--- a/builder/objcopy.go
+++ b/builder/objcopy.go
@@ -9,6 +9,10 @@ import (
"github.com/marcinbor85/gohex"
)
+// maxPadBytes is the maximum allowed bytes to be padded in a rom extraction
+// this value is currently defined by Nintendo Switch Page Alignment (4096 bytes)
+const maxPadBytes = 4095
+
// objcopyError is an error returned by functions that act like objcopy.
type objcopyError struct {
Op string
@@ -70,7 +74,12 @@ func extractROM(path string) (uint64, []byte, error) {
var rom []byte
for _, prog := range progs {
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
- return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
+ diff := prog.Paddr - (progs[0].Paddr + uint64(len(rom)))
+ if diff > maxPadBytes {
+ return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
+ }
+ // Pad the difference
+ rom = append(rom, make([]byte, diff)...)
}
data, err := ioutil.ReadAll(prog.Open())
if err != nil {
diff --git a/src/runtime/dynamic_arm64.go b/src/runtime/dynamic_arm64.go
new file mode 100644
index 000000000..3290adb52
--- /dev/null
+++ b/src/runtime/dynamic_arm64.go
@@ -0,0 +1,55 @@
+package runtime
+
+import (
+ "debug/elf"
+ "unsafe"
+)
+
+const debugLoader = false
+
+//export __dynamic_loader
+func dynamicLoader(base uintptr, dyn *elf.Dyn64) {
+ var rela *elf.Rela64
+ relasz := uint64(0)
+
+ if debugLoader {
+ println("ASLR Base: ", base)
+ }
+
+ for dyn.Tag != int64(elf.DT_NULL) {
+ switch elf.DynTag(dyn.Tag) {
+ case elf.DT_RELA:
+ rela = (*elf.Rela64)(unsafe.Pointer(base + uintptr(dyn.Val)))
+ case elf.DT_RELASZ:
+ relasz = uint64(dyn.Val) / uint64(unsafe.Sizeof(elf.Rela64{}))
+ }
+
+ ptr := uintptr(unsafe.Pointer(dyn))
+ ptr += unsafe.Sizeof(elf.Dyn64{})
+ dyn = (*elf.Dyn64)(unsafe.Pointer(ptr))
+ }
+
+ if rela == nil {
+ runtimePanic("bad reloc")
+ }
+ if rela == nil {
+ runtimePanic("bad reloc")
+ }
+
+ if debugLoader {
+ println("Sections to load: ", relasz)
+ }
+
+ for relasz > 0 && rela != nil {
+ switch elf.R_AARCH64(rela.Info) {
+ case elf.R_AARCH64_RELATIVE:
+ ptr := (*uint64)(unsafe.Pointer(base + uintptr(rela.Off)))
+ *ptr = uint64(base + uintptr(rela.Addend))
+ }
+
+ rptr := uintptr(unsafe.Pointer(rela))
+ rptr += unsafe.Sizeof(elf.Rela64{})
+ rela = (*elf.Rela64)(unsafe.Pointer(rptr))
+ relasz--
+ }
+}
diff --git a/src/runtime/runtime_nintendoswitch.go b/src/runtime/runtime_nintendoswitch.go
index 26712ddab..745f7ffc3 100644
--- a/src/runtime/runtime_nintendoswitch.go
+++ b/src/runtime/runtime_nintendoswitch.go
@@ -2,6 +2,8 @@
package runtime
+import "unsafe"
+
type timeUnit int64
const asyncScheduler = false
@@ -60,6 +62,16 @@ func abort() {
}
}
+//export write
+func write(fd int32, buf *byte, count int) int {
+ // TODO: Proper handling write
+ for i := 0; i < count; i++ {
+ putchar(*buf)
+ buf = (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(buf)) + 1))
+ }
+ return count
+}
+
//export sleepThread
func sleepThread(nanos uint64)
diff --git a/targets/nintendoswitch.json b/targets/nintendoswitch.json
index 583d68f06..3a0e40bd8 100644
--- a/targets/nintendoswitch.json
+++ b/targets/nintendoswitch.json
@@ -20,6 +20,10 @@
"-fno-exceptions", "-fno-unwind-tables",
"-ffunction-sections", "-fdata-sections"
],
+ "ldflags": [
+ "-pie",
+ "-z", "notext"
+ ],
"linkerscript": "targets/nintendoswitch.ld",
"extra-files": [
"targets/nintendoswitch.s",
diff --git a/targets/nintendoswitch.ld b/targets/nintendoswitch.ld
index cc26a4208..401798668 100644
--- a/targets/nintendoswitch.ld
+++ b/targets/nintendoswitch.ld
@@ -1,69 +1,84 @@
-SECTIONS
+PHDRS
{
- . = 0;
+ text PT_LOAD FLAGS(5) /* Read | Execute */;
+ rodata PT_LOAD FLAGS(4) /* Read */;
+ data PT_LOAD FLAGS(6) /* Read | Write */;
+ bss PT_LOAD FLAGS(6) /* Read | Write */;
+ dynamic PT_DYNAMIC;
+}
+SECTIONS
+{
/* Code and file header */
+ . = 0;
- .text : {
+ .text : ALIGN(0x1000) {
HIDDEN(__text_start = .);
KEEP(*(.text.jmp))
. = 0x80;
*(.text .text.*)
+ *(.plt .plt.*)
- . = ALIGN(0x1000);
HIDDEN(__text_end = .);
HIDDEN(__text_size = . - __text_start);
}
/* Read-only sections */
+ . = ALIGN(0x1000);
- .rodata : {
- HIDDEN(__rodata_start = .);
-
- *(.rodata .rodata.*)
-
- *(.got)
+ HIDDEN(__rodata_start = .);
+ .rodata : { *(.rodata .rodata.*) } :rodata
+ .mod0 : {
KEEP(crt0.nso.o(.data.mod0))
KEEP(crt0.nro.o(.data.mod0))
KEEP(crt0.lib.nro.o(.data.mod0))
- KEEP(*(.data.mod0))
+ }
- HIDDEN(__dynamic_start = .);
- *(.dynamic)
+ .dynsym : { *(.dynsym) } :rodata
+ .dynstr : { *(.dynstr) } :rodata
+ .rela.dyn : { *(.rela.*) } :rodata
- . = ALIGN(0x1000);
- HIDDEN(__rodata_end = .);
- HIDDEN(__rodata_size = . - __rodata_start);
- }
+ HIDDEN(__rodata_end = .);
+ HIDDEN(__rodata_size = . - __rodata_start);
/* Read-write sections */
+ . = ALIGN(0x1000);
.data : {
HIDDEN(__data_start = .);
*(.data .data.*)
+ *(.got .got.*)
+ *(.got.plt .got.plt.*)
HIDDEN(__data_end = .);
HIDDEN(__data_size = . - __data_start);
+ } :data
+
+ .dynamic : {
+ HIDDEN(__dynamic_start = .);
+ *(.dynamic)
}
/* BSS section */
-
+ . = ALIGN(0x1000);
.bss : {
HIDDEN(__bss_start = .);
*(.bss .bss.*)
*(COMMON)
+ . = ALIGN(8);
HIDDEN(__bss_end = .);
HIDDEN(__bss_size = . - __bss_start);
- }
+ } : bss
/DISCARD/ :
{
*(.eh_frame) /* This is probably unnecessary and bloats the binary. */
+ *(.eh_frame_hdr)
}
}
diff --git a/targets/nintendoswitch.s b/targets/nintendoswitch.s
index e4af2d6dd..139c2266e 100644
--- a/targets/nintendoswitch.s
+++ b/targets/nintendoswitch.s
@@ -6,8 +6,7 @@
_start:
b start
.word _mod_header - _start
- .word 0
- .word 0
+ .ascii "HOMEBREW"
.ascii "NRO0" // magic
.word 0 // version (always 0)
@@ -15,11 +14,11 @@ _start:
.word 0 // flags (unused)
// segment headers
- .word __text_start
+ .word 0 // __text_start
.word __text_size
- .word __rodata_start
+ .word 0 //__rodata_start
.word __rodata_size
- .word __data_start
+ .word 0 //__data_start
.word __data_size
.word __bss_size
.word 0
@@ -45,18 +44,17 @@ _mod_header:
.section .text, "x"
.global start
start:
-
- // save lr
- mov x7, x30
-
- // get aslr base
- bl +4
- sub x6, x30, #0x88
+ // Get ASLR Base
+ adrp x6, _start
// context ptr and main thread handle
mov x5, x0
mov x4, x1
+ // Save lr, context pointer, main thread handler
+ adrp x0, _aslr_base
+ str x6, [x0, #:lo12:_aslr_base]
+
// clear .bss
adrp x5, __bss_start
add x5, x5, #:lo12:__bss_start
@@ -71,7 +69,27 @@ bssloop:
b bssloop
run:
+ // process .dynamic section
+ adrp x0, _aslr_base
+ ldr x0, [x0, #:lo12:_aslr_base]
+ adrp x1, _DYNAMIC
+ add x1, x1, #:lo12:_DYNAMIC
+ bl __dynamic_loader
+
+ // set LR to svcExitProcess if it's null
+ adrp x3, exit
+ add x3, x3, #:lo12:exit
+ cmp x30, xzr
+ csel x30, x3, x30, eq
+
// call entrypoint
- adrp x30, exit
- add x30, x30, #:lo12:exit
+ mov x3, sp
+ sub sp, sp, 0x10
+ stp x29, x30, [sp]
b main
+
+.section .data.horizon
+.align 8
+.global _aslr_base // Placeholder for ASLR Base Address
+_aslr_base:
+ .dword 0