mirror of
https://github.com/switchbrew/libnx.git
synced 2025-06-21 20:42:44 +02:00
139 lines
3.3 KiB
C
139 lines
3.3 KiB
C
#include "result.h"
|
|
#include "kernel/svc.h"
|
|
#include "runtime/diag.h"
|
|
#include <elf.h>
|
|
#include <string.h>
|
|
|
|
typedef struct Mod0Header {
|
|
u32 magic_mod0;
|
|
s32 dyn_offset;
|
|
s32 bss_start_offset;
|
|
s32 bss_end_offset;
|
|
s32 eh_frame_hdr_start_offset;
|
|
s32 eh_frame_hdr_end_offset;
|
|
s32 unused;
|
|
|
|
u32 magic_lny0;
|
|
s32 got_start_offset;
|
|
s32 got_end_offset;
|
|
|
|
u32 magic_lny1;
|
|
s32 relro_start_offset;
|
|
s32 relro_end_offset;
|
|
} Mod0Header;
|
|
|
|
NX_INLINE void* _dynResolveOffset(const Mod0Header* mod0, s32 offset)
|
|
{
|
|
return (void*)((uintptr_t)mod0 + offset);
|
|
}
|
|
|
|
static void _dynProcessRela(uintptr_t base, const Elf64_Rela* rela, size_t relasz)
|
|
{
|
|
for (; relasz--; rela++) {
|
|
switch (ELF64_R_TYPE(rela->r_info)) {
|
|
default: {
|
|
diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_BadReloc));
|
|
break;
|
|
}
|
|
|
|
case R_AARCH64_NONE: {
|
|
break;
|
|
}
|
|
|
|
case R_AARCH64_RELATIVE: {
|
|
u64* ptr = (u64*)(base + rela->r_offset);
|
|
*ptr = base + rela->r_addend;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void _dynProcessRelr(uintptr_t base, const Elf64_Relr* relr, size_t relrsz)
|
|
{
|
|
u64* ptr = NULL;
|
|
for (; relrsz--; relr++) {
|
|
if ((*relr & 1) == 0) {
|
|
ptr = (u64*)(base + *relr);
|
|
*ptr++ += base;
|
|
} else {
|
|
u64 bitmap = *relr >> 1;
|
|
while (bitmap) {
|
|
unsigned id = __builtin_ffsl(bitmap)-1;
|
|
bitmap &= ~(1UL << id);
|
|
ptr[id] += base;
|
|
}
|
|
ptr += 63;
|
|
}
|
|
}
|
|
}
|
|
|
|
void __nx_dynamic(uintptr_t base, const Mod0Header* mod0)
|
|
{
|
|
// Return early if MOD0 header has been invalidated
|
|
if (mod0->magic_mod0 != 0x30444f4d) { // MOD0
|
|
return;
|
|
}
|
|
|
|
// Clear the BSS area
|
|
u8* bss_start = _dynResolveOffset(mod0, mod0->bss_start_offset);
|
|
u8* bss_end = _dynResolveOffset(mod0, mod0->bss_end_offset);
|
|
if (bss_start != bss_end) {
|
|
memset(bss_start, 0, bss_end - bss_start);
|
|
}
|
|
|
|
// Retrieve pointer to the ELF dynamic section
|
|
const Elf64_Dyn* dyn = _dynResolveOffset(mod0, mod0->dyn_offset);
|
|
|
|
// Extract relevant information from the ELF dynamic section
|
|
const Elf64_Rela* rela = NULL;
|
|
size_t relasz = 0;
|
|
const Elf64_Relr* relr = NULL;
|
|
size_t relrsz = 0;
|
|
for (; dyn->d_tag != DT_NULL; dyn++) {
|
|
switch (dyn->d_tag) {
|
|
case DT_RELA:
|
|
rela = (const Elf64_Rela*)(base + dyn->d_un.d_ptr);
|
|
break;
|
|
|
|
case DT_RELASZ:
|
|
relasz = dyn->d_un.d_val / sizeof(Elf64_Rela);
|
|
break;
|
|
|
|
case DT_RELR:
|
|
relr = (const Elf64_Relr*)(base + dyn->d_un.d_ptr);
|
|
break;
|
|
|
|
case DT_RELRSZ:
|
|
relrsz = dyn->d_un.d_val / sizeof(Elf64_Relr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Apply RELA relocations if present
|
|
if (rela && relasz) {
|
|
_dynProcessRela(base, rela, relasz);
|
|
}
|
|
|
|
// Apply RELR relocations if present
|
|
if (relr && relrsz) {
|
|
_dynProcessRelr(base, relr, relrsz);
|
|
}
|
|
|
|
// Return early if LNY0/LNY1 extensions are not present
|
|
if (mod0->magic_lny0 != 0x30594e4c || mod0->magic_lny1 != 0x31594e4c) { // LNY0, LNY1
|
|
return;
|
|
}
|
|
|
|
// Reprotect relro segment as read-only now that we're done processing relocations
|
|
u8* relro_start = _dynResolveOffset(mod0, mod0->relro_start_offset);
|
|
size_t relro_sz = (u8*)_dynResolveOffset(mod0, mod0->relro_end_offset) - relro_start;
|
|
Result rc = svcSetMemoryPermission(relro_start, relro_sz, Perm_R);
|
|
if (R_FAILED(rc)) {
|
|
diagAbortWithResult(rc);
|
|
}
|
|
|
|
// Lock the relro segment's permissions
|
|
svcSetMemoryAttribute(relro_start, relro_sz, MemAttr_IsPermissionLocked, MemAttr_IsPermissionLocked);
|
|
}
|