Last active
March 14, 2021 16:21
-
-
Save avdgrinten/0830a696f3484d85840ea6e02c9de0c8 to your computer and use it in GitHub Desktop.
Loading ELF shared libraries as kernel modules; includes relocation and symbol lookup
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void processElfDso(const char *buffer) { | |
auto base = reinterpret_cast<char *>(KernelVirtualMemory::global().allocate(0x10000)); | |
// Check the EHDR file header. | |
Elf64_Ehdr ehdr; | |
memcpy(&ehdr, buffer, sizeof(Elf64_Ehdr)); | |
assert(ehdr.e_ident[0] == 0x7F | |
&& ehdr.e_ident[1] == 'E' | |
&& ehdr.e_ident[2] == 'L' | |
&& ehdr.e_ident[3] == 'F'); | |
// Load all PHDRs. | |
Elf64_Dyn *dynamic = nullptr; | |
for(int i = 0; i < ehdr.e_phnum; i++) { | |
Elf64_Phdr phdr; | |
memcpy(&phdr, buffer + ehdr.e_phoff + i * ehdr.e_phentsize, sizeof(Elf64_Phdr)); | |
if(phdr.p_type == PT_LOAD) { | |
uintptr_t misalign = phdr.p_vaddr & (kPageSize - 1); | |
assert(phdr.p_memsz > 0); | |
// Map pages for the segment. | |
// TODO: We need write permission to fill the page. Get rid of it. | |
uint32_t pf = page_access::write; | |
if(phdr.p_flags & PF_X) | |
pf |= page_access::execute; | |
for(size_t pg = 0; pg < misalign + phdr.p_memsz; pg += kPageSize) { | |
auto va = reinterpret_cast<VirtualAddr>(base + phdr.p_vaddr + pg) | |
& ~(kPageSize - 1); | |
auto physical = physicalAllocator->allocate(kPageSize); | |
assert(physical != PhysicalAddr(-1)); | |
KernelPageSpace::global().mapSingle4k(va, physical, | |
pf, CachingMode::null); | |
} | |
// Fill the segment. | |
memset(base + phdr.p_vaddr, 0, phdr.p_memsz); | |
memcpy(base + phdr.p_vaddr, buffer + phdr.p_offset, phdr.p_filesz); | |
}else if(phdr.p_type == PT_DYNAMIC) { | |
dynamic = reinterpret_cast<Elf64_Dyn *>(base + phdr.p_vaddr); | |
}else if(phdr.p_type == PT_NOTE | |
|| phdr.p_type == PT_GNU_EH_FRAME | |
|| phdr.p_type == PT_GNU_STACK | |
|| phdr.p_type == PT_GNU_RELRO) { | |
// Ignore the PHDR. | |
}else{ | |
assert(!"Unexpected PHDR"); | |
} | |
} | |
// Extract symbol & relocation tables from DYNAMIC. | |
const char *str_tab = nullptr; | |
const Elf64_Sym *sym_tab = nullptr; | |
const Elf64_Word *hash_tab = nullptr; | |
const char *plt_rels = nullptr; | |
size_t plt_rel_sectionsize = 0; | |
for(size_t i = 0; dynamic[i].d_tag != DT_NULL; i++) { | |
auto ent = dynamic + i; | |
switch(ent->d_tag) { | |
// References to sections that we need to extract: | |
case DT_STRTAB: | |
str_tab = base + ent->d_ptr; | |
break; | |
case DT_SYMTAB: | |
sym_tab = reinterpret_cast<const Elf64_Sym *>(base + ent->d_ptr); | |
break; | |
case DT_HASH: | |
hash_tab = reinterpret_cast<const Elf64_Word *>(base + ent->d_ptr); | |
break; | |
case DT_JMPREL: | |
plt_rels = base + ent->d_ptr; | |
break; | |
// Data that we need to extract: | |
case DT_PLTRELSZ: | |
plt_rel_sectionsize = ent->d_val; | |
break; | |
// Make sure those entries match our expectation: | |
case DT_SYMENT: | |
assert(ent->d_val == sizeof(Elf64_Sym)); | |
break; | |
// Ignore the following entries: | |
case DT_STRSZ: | |
case DT_PLTGOT: | |
case DT_PLTREL: | |
case DT_GNU_HASH: | |
break; | |
default: | |
assert(!"Unexpected dynamic entry in kernlet"); | |
} | |
} | |
assert(str_tab); | |
assert(sym_tab); | |
assert(hash_tab); | |
// Perform relocations. | |
auto resolveExternal = [] (frg::string_view name) -> void * { | |
uint8_t (*abi_mmio_read8)(const char *, ptrdiff_t) = | |
[] (const char *base, ptrdiff_t offset) { | |
return *reinterpret_cast<volatile const uint8_t *>(base + offset); | |
}; | |
if(name == "__mmio_read8") | |
return reinterpret_cast<void *>(abi_mmio_read8); | |
frigg::panicLogger() << "Could not resolve external " << name.data() << frigg::endLog; | |
}; | |
for(size_t off = 0; off < plt_rel_sectionsize; off += sizeof(Elf64_Rela)) { | |
auto reloc = reinterpret_cast<const Elf64_Rela *>(plt_rels + off); | |
assert(ELF64_R_TYPE(reloc->r_info) == R_X86_64_JUMP_SLOT); | |
auto rp = reinterpret_cast<uint64_t *>(base + reloc->r_offset); | |
auto symbol = sym_tab + ELF64_R_SYM(reloc->r_info); | |
auto sym_name = frg::string_view{str_tab + symbol->st_name}; | |
*rp = reinterpret_cast<uint64_t>(resolveExternal(sym_name)); | |
} | |
// Look up symbols. | |
auto elf64Hash = [] (frg::string_view string) -> uint32_t { | |
uint32_t h = 0; | |
for(size_t i = 0; i < string.size(); ++i) { | |
h = (h << 4) + (uint8_t)string[i]; | |
uint32_t g = h & 0xF0000000; | |
if(g) | |
h ^= g >> 24; | |
h &= 0x0FFFFFFF; | |
} | |
return h; | |
}; | |
auto eligible = [&] (const Elf64_Sym *candidate) { | |
if(candidate->st_shndx == SHN_UNDEF) | |
return false; | |
auto bind = ELF64_ST_BIND(candidate->st_info); | |
if(bind != STB_GLOBAL && bind != STB_WEAK) | |
return false; | |
return true; | |
}; | |
auto lookup = [&] (frg::string_view name) -> void * { | |
auto n = hash_tab[0]; // Number of buckets. | |
auto b = elf64Hash(name) % n; // First bucket the symbol can appear in. | |
for(auto idx = hash_tab[2 + b]; idx; idx = hash_tab[2 + n + idx]) { | |
auto candidate = sym_tab + idx; | |
auto cand_name = frg::string_view{str_tab + candidate->st_name}; | |
if(!eligible(candidate) || cand_name != name) | |
continue; | |
return base + candidate->st_value; | |
} | |
assert(!"Unable to resolve symbol"); | |
}; | |
auto x = lookup("irq_filter"); | |
frigg::infoLogger() << "irq_filter is at " << x << frigg::endLog; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment