Skip to content

Instantly share code, notes, and snippets.

@avdgrinten
Last active March 14, 2021 16:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save avdgrinten/0830a696f3484d85840ea6e02c9de0c8 to your computer and use it in GitHub Desktop.
Save avdgrinten/0830a696f3484d85840ea6e02c9de0c8 to your computer and use it in GitHub Desktop.
Loading ELF shared libraries as kernel modules; includes relocation and symbol lookup
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