#pragma once #include "util/types.hpp" #include "../../Utilities/File.h" #include "../../Utilities/bit_set.h" enum class elf_os : u8 { none = 0, lv2 = 0x66, }; enum class elf_type : u16 { none = 0, rel = 1, exec = 2, dyn = 3, core = 4, prx = 0xffa4, psv1 = 0xfe00, // ET_SCE_EXEC psv2 = 0xfe04, // ET_SCE_RELEXEC (vitasdk) }; enum class elf_machine : u16 { ppc64 = 0x15, spu = 0x17, arm = 0x28, mips = 0x08, }; enum class sec_type : u32 { sht_null = 0, sht_progbits = 1, sht_symtab = 2, sht_strtab = 3, sht_rela = 4, sht_hash = 5, sht_dynamic = 6, sht_note = 7, sht_nobits = 8, sht_rel = 9, }; enum class sh_flag : u32 { shf_write = 1, shf_alloc = 2, shf_execinstr = 4, __bitset_enum_max }; constexpr bool is_memorizable_section(sec_type type) { switch (type) { case sec_type::sht_null: case sec_type::sht_nobits: { return false; } default: { if (type > sec_type::sht_rel) { return false; } return true; } } } template using elf_be = be_t; template using elf_le = le_t; template class en_t, typename sz_t> struct elf_ehdr { nse_t e_magic; u8 e_class; u8 e_data; u8 e_curver; elf_os e_os_abi; u8 e_abi_ver; u8 e_pad[7]; en_t e_type; en_t e_machine; en_t e_version; en_t e_entry; en_t e_phoff; en_t e_shoff; en_t e_flags; en_t e_ehsize; en_t e_phentsize; en_t e_phnum; en_t e_shentsize; en_t e_shnum; en_t e_shstrndx; }; template class en_t, typename sz_t> struct elf_phdr { static_assert(!sizeof(sz_t), "Invalid elf size type (must be u32 or u64)"); }; template class en_t> struct elf_phdr { en_t p_type; en_t p_flags; en_t p_offset; en_t p_vaddr; en_t p_paddr; en_t p_filesz; en_t p_memsz; en_t p_align; }; template class en_t> struct elf_phdr { en_t p_type; en_t p_offset; en_t p_vaddr; en_t p_paddr; en_t p_filesz; en_t p_memsz; en_t p_flags; en_t p_align; }; template class en_t, typename sz_t> struct elf_prog final : elf_phdr { std::vector bin{}; using base = elf_phdr; elf_prog() = default; elf_prog(u32 type, u32 flags, sz_t vaddr, sz_t memsz, sz_t align, std::vector&& bin) : bin(std::move(bin)) { base::p_type = type; base::p_flags = flags; base::p_vaddr = vaddr; base::p_memsz = memsz; base::p_align = align; base::p_filesz = static_cast(this->bin.size()); base::p_paddr = 0; base::p_offset = -1; } }; template class en_t, typename sz_t> struct elf_shdr { en_t sh_name; en_t sh_type; en_t _sh_flags; en_t sh_addr; en_t sh_offset; en_t sh_size; en_t sh_link; en_t sh_info; en_t sh_addralign; en_t sh_entsize; bs_t sh_flags() const { return std::bit_cast>(static_cast(+_sh_flags)); } }; template class en_t, typename sz_t> struct elf_shdata final : elf_shdr { std::vector bin{}; using base = elf_shdr; elf_shdata() = default; }; // ELF loading options enum class elf_opt : u32 { no_programs, // Don't load phdrs, implies no_data no_sections, // Don't load shdrs no_data, // Load phdrs without data __bitset_enum_max }; // ELF loading error enum class elf_error { ok = 0, stream, stream_header, stream_phdrs, stream_shdrs, stream_data, header_magic, header_version, header_class, header_machine, header_endianness, header_type, header_os, }; // ELF object with specified parameters. // en_t: endianness (elf_le or elf_be) // sz_t: size (u32 for ELF32, u64 for ELF64) template class en_t, typename sz_t, elf_machine Machine, elf_os OS, elf_type Type> class elf_object { elf_error m_error = elf_error::stream; // Set initial error to "file not found" error public: using ehdr_t = elf_ehdr; using phdr_t = elf_phdr; using shdr_t = elf_shdr; using prog_t = elf_prog; using shdata_t = elf_shdata; ehdr_t header{}; std::vector progs{}; std::vector shdrs{}; public: elf_object() = default; elf_object(const fs::file& stream, u64 offset = 0, bs_t opts = {}) { open(stream, offset, opts); } elf_error open(const fs::file& stream, u64 offset = 0, bs_t opts = {}) { // Check stream if (!stream) return set_error(elf_error::stream); // Read ELF header stream.seek(offset); if (!stream.read(header)) return set_error(elf_error::stream_header); // Check magic if (header.e_magic != "\177ELF"_u32) return set_error(elf_error::header_magic); // Check class if (header.e_class != (std::is_same::value ? 1 : 2)) return set_error(elf_error::header_class); // Check endianness if (header.e_data != (std::is_same, le_t>::value ? 1 : 2)) return set_error(elf_error::header_endianness); // Check machine if (header.e_machine != Machine) return set_error(elf_error::header_machine); // Check OS only if specified (hack) if (OS != elf_os::none && header.e_os_abi != OS) return set_error(elf_error::header_os); // Check type only if specified (hack) if (Type != elf_type::none && header.e_type != Type) return set_error(elf_error::header_type); // Check version and other params if (header.e_curver != 1 || header.e_version != 1u || header.e_ehsize != u16{sizeof(ehdr_t)}) return set_error(elf_error::header_version); if (header.e_phnum && header.e_phentsize != u16{sizeof(phdr_t)}) return set_error(elf_error::header_version); if (header.e_shnum && header.e_shentsize != u16{sizeof(shdr_t)}) return set_error(elf_error::header_version); // Load program headers std::vector _phdrs; std::vector _shdrs; if (!(opts & elf_opt::no_programs)) { stream.seek(offset + header.e_phoff); if (!stream.read(_phdrs, header.e_phnum)) return set_error(elf_error::stream_phdrs); } if (!(opts & elf_opt::no_sections)) { stream.seek(offset + header.e_shoff); if (!stream.read(_shdrs, header.e_shnum)) return set_error(elf_error::stream_shdrs); } progs.clear(); progs.reserve(_phdrs.size()); for (const auto& hdr : _phdrs) { static_cast(progs.emplace_back()) = hdr; if (!(opts & elf_opt::no_data)) { stream.seek(offset + hdr.p_offset); if (!stream.read(progs.back().bin, hdr.p_filesz)) return set_error(elf_error::stream_data); } } shdrs.clear(); shdrs.reserve(_shdrs.size()); for (const auto& shdr : _shdrs) { static_cast(shdrs.emplace_back()) = shdr; if (!(opts & elf_opt::no_data) && is_memorizable_section(shdr.sh_type)) { stream.seek(offset + shdr.sh_offset); if (!stream.read(shdrs.back().bin, shdr.sh_size)) return set_error(elf_error::stream_data); } } shdrs.shrink_to_fit(); progs.shrink_to_fit(); return m_error = elf_error::ok; } std::vector save(std::vector&& init = std::vector{}) const { fs::file stream = fs::make_stream>(std::move(init)); const bool fixup_shdrs = shdrs.empty() || shdrs[0].sh_type != sec_type::sht_null; // Write header ehdr_t header{}; header.e_magic = "\177ELF"_u32; header.e_class = std::is_same::value ? 1 : 2; header.e_data = std::is_same, le_t>::value ? 1 : 2; header.e_curver = 1; header.e_os_abi = OS != elf_os::none ? OS : this->header.e_os_abi; header.e_abi_ver = this->header.e_abi_ver; header.e_type = Type != elf_type::none ? Type : static_cast(this->header.e_type); header.e_machine = Machine; header.e_version = 1; header.e_entry = this->header.e_entry; header.e_phoff = u32{sizeof(ehdr_t)}; header.e_shoff = u32{sizeof(ehdr_t)} + u32{sizeof(phdr_t)} * ::size32(progs); header.e_flags = this->header.e_flags; header.e_ehsize = u32{sizeof(ehdr_t)}; header.e_phentsize = u32{sizeof(phdr_t)}; header.e_phnum = ::size32(progs); header.e_shentsize = u32{sizeof(shdr_t)}; header.e_shnum = ::size32(shdrs) + u32{fixup_shdrs}; header.e_shstrndx = this->header.e_shstrndx; stream.write(header); sz_t off = header.e_shoff + u32{sizeof(shdr_t)} * ::size32(shdrs); for (phdr_t phdr : progs) { phdr.p_offset = std::exchange(off, off + phdr.p_filesz); stream.write(phdr); } if (fixup_shdrs) { // Insert a must-have empty section at the start stream.write(shdr_t{}); } for (shdr_t shdr : shdrs) { if (is_memorizable_section(shdr.sh_type)) { shdr.sh_offset = std::exchange(off, off + shdr.sh_size); } stream.write(shdr); } // Write data for (const auto& prog : progs) { stream.write(prog.bin); } for (const auto& shdr : shdrs) { if (!is_memorizable_section(shdr.sh_type)) { continue; } stream.write(shdr.bin); } return std::move(static_cast>*>(stream.release().get())->obj); } elf_object& clear() { // Do not use clear() in order to dealloc memory progs = {}; shdrs = {}; header.e_magic = 0; m_error = elf_error::stream; return *this; } elf_object& set_error(elf_error error) { // Setting an error causes the state to clear if there was no error before // Trying to set elf_error::ok is ignored if (error != elf_error::ok) { if (m_error == elf_error::ok) clear(); m_error = error; } return *this; } // Return error code operator elf_error() const { return m_error; } elf_error get_error() const { return m_error; } }; using ppu_exec_object = elf_object; using ppu_prx_object = elf_object; using ppu_rel_object = elf_object; using spu_exec_object = elf_object; using spu_rel_object = elf_object; using arm_exec_object = elf_object;