Skip to content

Commit fb1fc26

Browse files
racerxdldeadprogram
authored andcommitted
nintendoswitch: Add dynamic loader for runtime loading PIE sections
1 parent 490e377 commit fb1fc26

File tree

6 files changed

+147
-34
lines changed

6 files changed

+147
-34
lines changed

builder/objcopy.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import (
99
"github.com/marcinbor85/gohex"
1010
)
1111

12+
// maxPadBytes is the maximum allowed bytes to be padded in a rom extraction
13+
// this value is currently defined by Nintendo Switch Page Alignment (4096 bytes)
14+
const maxPadBytes = 4095
15+
1216
// objcopyError is an error returned by functions that act like objcopy.
1317
type objcopyError struct {
1418
Op string
@@ -70,7 +74,12 @@ func extractROM(path string) (uint64, []byte, error) {
7074
var rom []byte
7175
for _, prog := range progs {
7276
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
73-
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
77+
diff := prog.Paddr - (progs[0].Paddr + uint64(len(rom)))
78+
if diff > maxPadBytes {
79+
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
80+
}
81+
// Pad the difference
82+
rom = append(rom, make([]byte, diff)...)
7483
}
7584
data, err := ioutil.ReadAll(prog.Open())
7685
if err != nil {

src/runtime/dynamic_arm64.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package runtime
2+
3+
import (
4+
"debug/elf"
5+
"unsafe"
6+
)
7+
8+
const debugLoader = false
9+
10+
//export __dynamic_loader
11+
func dynamicLoader(base uintptr, dyn *elf.Dyn64) {
12+
var rela *elf.Rela64
13+
relasz := uint64(0)
14+
15+
if debugLoader {
16+
println("ASLR Base: ", base)
17+
}
18+
19+
for dyn.Tag != int64(elf.DT_NULL) {
20+
switch elf.DynTag(dyn.Tag) {
21+
case elf.DT_RELA:
22+
rela = (*elf.Rela64)(unsafe.Pointer(base + uintptr(dyn.Val)))
23+
case elf.DT_RELASZ:
24+
relasz = uint64(dyn.Val) / uint64(unsafe.Sizeof(elf.Rela64{}))
25+
}
26+
27+
ptr := uintptr(unsafe.Pointer(dyn))
28+
ptr += unsafe.Sizeof(elf.Dyn64{})
29+
dyn = (*elf.Dyn64)(unsafe.Pointer(ptr))
30+
}
31+
32+
if rela == nil {
33+
runtimePanic("bad reloc")
34+
}
35+
if rela == nil {
36+
runtimePanic("bad reloc")
37+
}
38+
39+
if debugLoader {
40+
println("Sections to load: ", relasz)
41+
}
42+
43+
for relasz > 0 && rela != nil {
44+
switch elf.R_AARCH64(rela.Info) {
45+
case elf.R_AARCH64_RELATIVE:
46+
ptr := (*uint64)(unsafe.Pointer(base + uintptr(rela.Off)))
47+
*ptr = uint64(base + uintptr(rela.Addend))
48+
}
49+
50+
rptr := uintptr(unsafe.Pointer(rela))
51+
rptr += unsafe.Sizeof(elf.Rela64{})
52+
rela = (*elf.Rela64)(unsafe.Pointer(rptr))
53+
relasz--
54+
}
55+
}

src/runtime/runtime_nintendoswitch.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
package runtime
44

5+
import "unsafe"
6+
57
type timeUnit int64
68

79
const asyncScheduler = false
@@ -60,6 +62,16 @@ func abort() {
6062
}
6163
}
6264

65+
//export write
66+
func write(fd int32, buf *byte, count int) int {
67+
// TODO: Proper handling write
68+
for i := 0; i < count; i++ {
69+
putchar(*buf)
70+
buf = (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(buf)) + 1))
71+
}
72+
return count
73+
}
74+
6375
//export sleepThread
6476
func sleepThread(nanos uint64)
6577

targets/nintendoswitch.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
"-fno-exceptions", "-fno-unwind-tables",
2121
"-ffunction-sections", "-fdata-sections"
2222
],
23+
"ldflags": [
24+
"-pie",
25+
"-z", "notext"
26+
],
2327
"linkerscript": "targets/nintendoswitch.ld",
2428
"extra-files": [
2529
"targets/nintendoswitch.s",

targets/nintendoswitch.ld

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,84 @@
1-
SECTIONS
1+
PHDRS
22
{
3-
. = 0;
3+
text PT_LOAD FLAGS(5) /* Read | Execute */;
4+
rodata PT_LOAD FLAGS(4) /* Read */;
5+
data PT_LOAD FLAGS(6) /* Read | Write */;
6+
bss PT_LOAD FLAGS(6) /* Read | Write */;
7+
dynamic PT_DYNAMIC;
8+
}
49

10+
SECTIONS
11+
{
512
/* Code and file header */
13+
. = 0;
614

7-
.text : {
15+
.text : ALIGN(0x1000) {
816
HIDDEN(__text_start = .);
917
KEEP(*(.text.jmp))
1018

1119
. = 0x80;
1220

1321
*(.text .text.*)
22+
*(.plt .plt.*)
1423

15-
. = ALIGN(0x1000);
1624
HIDDEN(__text_end = .);
1725
HIDDEN(__text_size = . - __text_start);
1826
}
1927

2028
/* Read-only sections */
29+
. = ALIGN(0x1000);
2130

22-
.rodata : {
23-
HIDDEN(__rodata_start = .);
24-
25-
*(.rodata .rodata.*)
26-
27-
*(.got)
31+
HIDDEN(__rodata_start = .);
32+
.rodata : { *(.rodata .rodata.*) } :rodata
2833

34+
.mod0 : {
2935
KEEP(crt0.nso.o(.data.mod0))
3036
KEEP(crt0.nro.o(.data.mod0))
3137
KEEP(crt0.lib.nro.o(.data.mod0))
32-
KEEP(*(.data.mod0))
38+
}
3339

34-
HIDDEN(__dynamic_start = .);
35-
*(.dynamic)
40+
.dynsym : { *(.dynsym) } :rodata
41+
.dynstr : { *(.dynstr) } :rodata
42+
.rela.dyn : { *(.rela.*) } :rodata
3643

37-
. = ALIGN(0x1000);
38-
HIDDEN(__rodata_end = .);
39-
HIDDEN(__rodata_size = . - __rodata_start);
40-
}
44+
HIDDEN(__rodata_end = .);
45+
HIDDEN(__rodata_size = . - __rodata_start);
4146

4247
/* Read-write sections */
48+
. = ALIGN(0x1000);
4349

4450
.data : {
4551
HIDDEN(__data_start = .);
4652

4753
*(.data .data.*)
54+
*(.got .got.*)
55+
*(.got.plt .got.plt.*)
4856

4957
HIDDEN(__data_end = .);
5058
HIDDEN(__data_size = . - __data_start);
59+
} :data
60+
61+
.dynamic : {
62+
HIDDEN(__dynamic_start = .);
63+
*(.dynamic)
5164
}
5265

5366
/* BSS section */
54-
67+
. = ALIGN(0x1000);
5568
.bss : {
5669
HIDDEN(__bss_start = .);
5770

5871
*(.bss .bss.*)
5972
*(COMMON)
73+
. = ALIGN(8);
6074

6175
HIDDEN(__bss_end = .);
6276
HIDDEN(__bss_size = . - __bss_start);
63-
}
77+
} : bss
6478

6579
/DISCARD/ :
6680
{
6781
*(.eh_frame) /* This is probably unnecessary and bloats the binary. */
82+
*(.eh_frame_hdr)
6883
}
6984
}

targets/nintendoswitch.s

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,19 @@
66
_start:
77
b start
88
.word _mod_header - _start
9-
.word 0
10-
.word 0
9+
.ascii "HOMEBREW"
1110

1211
.ascii "NRO0" // magic
1312
.word 0 // version (always 0)
1413
.word __bss_start - _start // total NRO file size
1514
.word 0 // flags (unused)
1615

1716
// segment headers
18-
.word __text_start
17+
.word 0 // __text_start
1918
.word __text_size
20-
.word __rodata_start
19+
.word 0 //__rodata_start
2120
.word __rodata_size
22-
.word __data_start
21+
.word 0 //__data_start
2322
.word __data_size
2423
.word __bss_size
2524
.word 0
@@ -45,18 +44,17 @@ _mod_header:
4544
.section .text, "x"
4645
.global start
4746
start:
48-
49-
// save lr
50-
mov x7, x30
51-
52-
// get aslr base
53-
bl +4
54-
sub x6, x30, #0x88
47+
// Get ASLR Base
48+
adrp x6, _start
5549

5650
// context ptr and main thread handle
5751
mov x5, x0
5852
mov x4, x1
5953

54+
// Save lr, context pointer, main thread handler
55+
adrp x0, _aslr_base
56+
str x6, [x0, #:lo12:_aslr_base]
57+
6058
// clear .bss
6159
adrp x5, __bss_start
6260
add x5, x5, #:lo12:__bss_start
@@ -71,7 +69,27 @@ bssloop:
7169
b bssloop
7270

7371
run:
72+
// process .dynamic section
73+
adrp x0, _aslr_base
74+
ldr x0, [x0, #:lo12:_aslr_base]
75+
adrp x1, _DYNAMIC
76+
add x1, x1, #:lo12:_DYNAMIC
77+
bl __dynamic_loader
78+
79+
// set LR to svcExitProcess if it's null
80+
adrp x3, exit
81+
add x3, x3, #:lo12:exit
82+
cmp x30, xzr
83+
csel x30, x3, x30, eq
84+
7485
// call entrypoint
75-
adrp x30, exit
76-
add x30, x30, #:lo12:exit
86+
mov x3, sp
87+
sub sp, sp, 0x10
88+
stp x29, x30, [sp]
7789
b main
90+
91+
.section .data.horizon
92+
.align 8
93+
.global _aslr_base // Placeholder for ASLR Base Address
94+
_aslr_base:
95+
.dword 0

0 commit comments

Comments
 (0)