diff options
author | Lucas Teske <[email protected]> | 2020-09-13 21:10:27 -0300 |
---|---|---|
committer | Ron Evans <[email protected]> | 2020-09-17 07:46:29 +0200 |
commit | fb1fc267ab8beca0c3b7f11a6441e0b403017d7c (patch) | |
tree | 621c9630c567dbceab00edea650862a620b26459 | |
parent | 490e377bba012afb6fd606b156276b76c24a3312 (diff) | |
download | tinygo-fb1fc267ab8beca0c3b7f11a6441e0b403017d7c.tar.gz tinygo-fb1fc267ab8beca0c3b7f11a6441e0b403017d7c.zip |
nintendoswitch: Add dynamic loader for runtime loading PIE sections
-rw-r--r-- | builder/objcopy.go | 11 | ||||
-rw-r--r-- | src/runtime/dynamic_arm64.go | 55 | ||||
-rw-r--r-- | src/runtime/runtime_nintendoswitch.go | 12 | ||||
-rw-r--r-- | targets/nintendoswitch.json | 4 | ||||
-rw-r--r-- | targets/nintendoswitch.ld | 53 | ||||
-rw-r--r-- | targets/nintendoswitch.s | 46 |
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 |