SPU/PPU Loader: Implement linker/PS3 compiler executable files loading

This commit is contained in:
Eladash 2022-04-27 19:46:09 +03:00 committed by Ivan
parent 6cc1466baa
commit f9a62667cf
6 changed files with 274 additions and 27 deletions

View File

@ -164,12 +164,12 @@ public:
return r;
}
constexpr bool all_of(bs_t arg)
constexpr bool all_of(bs_t arg) const
{
return (m_data & arg.m_data) == arg.m_data;
}
constexpr bool none_of(bs_t arg)
constexpr bool none_of(bs_t arg) const
{
return (m_data & arg.m_data) == 0;
}

View File

@ -904,7 +904,7 @@ void try_spawn_ppu_if_exclusive_program(const ppu_module& m)
auto ppu = idm::make_ptr<named_thread<ppu_thread>>(p, "test_thread", 0);
ppu->cia = m.funcs[0].addr;
ppu->cia = m.funcs.empty() ? m.secs[0].addr : m.funcs[0].addr;
// For kernel explorer
g_fxo->init<lv2_memory_container>(4096);
@ -995,9 +995,9 @@ std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::stri
for (const auto& s : elf.shdrs)
{
ppu_loader.notice("** Section: sh_type=0x%x, addr=0x%llx, size=0x%llx, flags=0x%x", s.sh_type, s.sh_addr, s.sh_size, s.sh_flags);
ppu_loader.notice("** Section: sh_type=0x%x, addr=0x%llx, size=0x%llx, flags=0x%x", std::bit_cast<u32>(s.sh_type), s.sh_addr, s.sh_size, s._sh_flags);
if (s.sh_type != 1u) continue;
if (s.sh_type != sec_type::sht_progbits) continue;
const u32 addr = vm::cast(s.sh_addr);
const u32 size = vm::cast(s.sh_size);
@ -1013,8 +1013,8 @@ std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::stri
ppu_segment _sec;
_sec.addr = addr - saddr + prx->segs[i].addr;
_sec.size = size;
_sec.type = s.sh_type;
_sec.flags = static_cast<u32>(s.sh_flags & 7);
_sec.type = std::bit_cast<u32>(s.sh_type);
_sec.flags = static_cast<u32>(s._sh_flags & 7);
_sec.filesz = 0;
prx->secs.emplace_back(_sec);
@ -1421,16 +1421,16 @@ bool ppu_load_exec(const ppu_exec_object& elf)
// Load section list, used by the analyser
for (const auto& s : elf.shdrs)
{
ppu_loader.notice("** Section: sh_type=0x%x, addr=0x%llx, size=0x%llx, flags=0x%x", s.sh_type, s.sh_addr, s.sh_size, s.sh_flags);
ppu_loader.notice("** Section: sh_type=0x%x, addr=0x%llx, size=0x%llx, flags=0x%x", std::bit_cast<u32>(s.sh_type), s.sh_addr, s.sh_size, s._sh_flags);
if (s.sh_type != 1u) continue;
if (s.sh_type != sec_type::sht_progbits) continue;
ppu_segment _sec;
const u32 addr = _sec.addr = vm::cast(s.sh_addr);
const u32 size = _sec.size = vm::cast(s.sh_size);
_sec.type = s.sh_type;
_sec.flags = static_cast<u32>(s.sh_flags & 7);
_sec.type = std::bit_cast<u32>(s.sh_type);
_sec.flags = static_cast<u32>(s._sh_flags & 7);
_sec.filesz = 0;
if (addr && size)
@ -1997,16 +1997,16 @@ std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_ex
// Load section list, used by the analyser
for (const auto& s : elf.shdrs)
{
ppu_loader.notice("** Section: sh_type=0x%x, addr=0x%llx, size=0x%llx, flags=0x%x", s.sh_type, s.sh_addr, s.sh_size, s.sh_flags);
ppu_loader.notice("** Section: sh_type=0x%x, addr=0x%llx, size=0x%llx, flags=0x%x", std::bit_cast<u32>(s.sh_type), s.sh_addr, s.sh_size, s._sh_flags);
if (s.sh_type != 1u) continue;
if (s.sh_type != sec_type::sht_progbits) continue;
ppu_segment _sec;
const u32 addr = _sec.addr = vm::cast(s.sh_addr);
const u32 size = _sec.size = vm::cast(s.sh_size);
_sec.type = s.sh_type;
_sec.flags = static_cast<u32>(s.sh_flags & 7);
_sec.type = std::bit_cast<u32>(s.sh_type);
_sec.flags = static_cast<u32>(s._sh_flags & 7);
_sec.filesz = 0;
if (addr && size)
@ -2152,3 +2152,93 @@ std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_ex
return {std::move(ovlm), {}};
}
bool ppu_load_rel_exec(const ppu_rel_object& elf)
{
ppu_module relm{};
struct on_fatal_error
{
ppu_module& relm;
bool errored = true;
~on_fatal_error()
{
if (!errored)
{
return;
}
// Revert previous allocations on an error
for (const auto& seg : relm.secs)
{
vm::dealloc(seg.addr);
}
}
} error_handler{relm};
u32 memsize = 0;
for (const auto& s : elf.shdrs)
{
if (s.sh_type != sec_type::sht_progbits)
{
memsize = utils::align<u32>(memsize + vm::cast(s.sh_size), 128);
}
}
u32 addr = vm::alloc(memsize, vm::main);
if (!addr)
{
ppu_loader.fatal("ppu_load_rel_exec(): vm::alloc() failed (memsz=0x%x)", memsize);
return false;
}
ppu_register_range(addr, memsize);
// Copy references to sections for the purpose of sorting executable sections before non-executable ones
std::vector<const elf_shdata<elf_be, u64>*> shdrs(elf.shdrs.size());
for (auto& ref : shdrs)
{
ref = &elf.shdrs[&ref - shdrs.data()];
}
std::stable_sort(shdrs.begin(), shdrs.end(), [](auto& a, auto& b) -> bool
{
const bs_t<sh_flag> flags_a_has = a->sh_flags() - b->sh_flags();
return flags_a_has.all_of(sh_flag::shf_execinstr);
});
// Load sections
for (auto ptr : shdrs)
{
const auto& s = *ptr;
ppu_loader.notice("** Section: sh_type=0x%x, addr=0x%llx, size=0x%llx, flags=0x%x", std::bit_cast<u32>(s.sh_type), s.sh_addr, s.sh_size, s._sh_flags);
if (s.sh_type == sec_type::sht_progbits && s.sh_size && s.sh_flags().all_of(sh_flag::shf_alloc))
{
ppu_segment _sec;
const u32 size = _sec.size = vm::cast(s.sh_size);
_sec.type = std::bit_cast<u32>(s.sh_type);
_sec.flags = static_cast<u32>(s._sh_flags & 7);
_sec.filesz = size;
_sec.addr = addr;
relm.secs.emplace_back(_sec);
std::memcpy(vm::base(addr), s.bin.data(), size);
addr = utils::align<u32>(addr + size, 128);
}
}
try_spawn_ppu_if_exclusive_program(relm);
error_handler.errored = false;
return true;
}

View File

@ -1,6 +1,7 @@
#include "stdafx.h"
#include "Emu/IdManager.h"
#include "Loader/ELF.h"
#include "util/asm.hpp"
#include "Emu/Cell/RawSPUThread.h"
@ -336,3 +337,40 @@ void spu_load_exec(const spu_exec_object& elf)
spu->status_npc = {SPU_STATUS_RUNNING, elf.header.e_entry};
atomic_storage<u32>::release(spu->pc, elf.header.e_entry);
}
void spu_load_rel_exec(const spu_rel_object& elf)
{
spu_thread::g_raw_spu_ctr++;
auto spu = idm::make_ptr<named_thread<spu_thread>>(nullptr, 0, "test_spu", 0);
u64 total_memsize = 0;
// Compute executable data size
for (const auto& shdr : elf.shdrs)
{
if (shdr.sh_type == sec_type::sht_progbits && shdr.sh_flags().all_of(sh_flag::shf_alloc))
{
total_memsize = utils::align<u32>(total_memsize + shdr.sh_size, 4);
}
}
// Place executable data in SPU local memory
u32 offs = 0;
for (const auto& shdr : elf.shdrs)
{
if (shdr.sh_type == sec_type::sht_progbits && shdr.sh_flags().all_of(sh_flag::shf_alloc))
{
std::memcpy(spu->_ptr<void>(offs), shdr.bin.data(), shdr.sh_size);
offs = utils::align<u32>(offs + shdr.sh_size, 4);
}
}
spu_log.success("Loaded 0x%x of SPU relocatable executable data", total_memsize);
spu_thread::g_raw_spu_id[0] = spu->id;
spu->status_npc = {SPU_STATUS_RUNNING, elf.header.e_entry};
atomic_storage<u32>::release(spu->pc, elf.header.e_entry);
}

View File

@ -78,7 +78,7 @@ void sys_spu_image::load(const fs::file& stream)
for (const auto& shdr : obj.shdrs)
{
spu_log.notice("** Section: sh_type=0x%x, addr=0x%llx, size=0x%llx, flags=0x%x", shdr.sh_type, shdr.sh_addr, shdr.sh_size, shdr.sh_flags);
spu_log.notice("** Section: sh_type=0x%x, addr=0x%llx, size=0x%llx, flags=0x%x", std::bit_cast<u32>(shdr.sh_type), shdr.sh_addr, shdr.sh_size, shdr._sh_flags);
}
for (const auto& prog : obj.progs)

View File

@ -64,12 +64,14 @@ atomic_t<u64> g_watchdog_hold_ctr{0};
extern bool ppu_load_exec(const ppu_exec_object&);
extern void spu_load_exec(const spu_exec_object&);
extern void spu_load_rel_exec(const spu_rel_object&);
extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<lv2_prx*>* loaded_prx);
extern bool ppu_initialize(const ppu_module&, bool = false);
extern void ppu_finalize(const ppu_module&);
extern void ppu_unload_prx(const lv2_prx&);
extern std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object&, const std::string&, s64 = 0);
extern std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_exec_object&, const std::string& path, s64 = 0);
extern bool ppu_load_rel_exec(const ppu_rel_object&);
fs::file g_tty;
atomic_t<s64> g_tty_size{0};
@ -1340,7 +1342,9 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
ppu_exec_object ppu_exec;
ppu_prx_object ppu_prx;
ppu_rel_object ppu_rel;
spu_exec_object spu_exec;
spu_rel_object spu_rel;
if (ppu_exec.open(elf_file) == elf_error::ok)
{
@ -1449,7 +1453,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
}
else if (ppu_prx.open(elf_file) == elf_error::ok)
{
// PPU PRX (experimental)
// PPU PRX
GetCallbacks().on_ready();
g_fxo->init(false);
ppu_load_prx(ppu_prx, m_path);
@ -1457,12 +1461,28 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
}
else if (spu_exec.open(elf_file) == elf_error::ok)
{
// SPU executable (experimental)
// SPU executable
GetCallbacks().on_ready();
g_fxo->init(false);
spu_load_exec(spu_exec);
Pause(true);
}
else if (spu_rel.open(elf_file) == elf_error::ok)
{
// SPU linker file
GetCallbacks().on_ready();
g_fxo->init(false);
spu_load_rel_exec(spu_rel);
Pause(true);
}
else if (ppu_rel.open(elf_file) == elf_error::ok)
{
// PPU linker file
GetCallbacks().on_ready();
g_fxo->init(false);
ppu_load_rel_exec(ppu_rel);
Pause(true);
}
else
{
sys_log.error("Invalid or unsupported file format: %s", elf_path);
@ -1470,6 +1490,8 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
sys_log.warning("** ppu_exec -> %s", ppu_exec.get_error());
sys_log.warning("** ppu_prx -> %s", ppu_prx.get_error());
sys_log.warning("** spu_exec -> %s", spu_exec.get_error());
sys_log.warning("** spu_rel -> %s", spu_rel.get_error());
sys_log.warning("** ppu_rel -> %s", ppu_rel.get_error());
Kill(false);
return game_boot_result::invalid_file_or_folder;

View File

@ -33,6 +33,50 @@ enum class elf_machine : u16
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<typename T>
using elf_be = be_t<T>;
@ -123,8 +167,8 @@ template<template<typename T> class en_t, typename sz_t>
struct elf_shdr
{
en_t<u32> sh_name;
en_t<u32> sh_type;
en_t<sz_t> sh_flags;
en_t<sec_type> sh_type;
en_t<sz_t> _sh_flags;
en_t<sz_t> sh_addr;
en_t<sz_t> sh_offset;
en_t<sz_t> sh_size;
@ -132,6 +176,21 @@ struct elf_shdr
en_t<u32> sh_info;
en_t<sz_t> sh_addralign;
en_t<sz_t> sh_entsize;
bs_t<sh_flag> sh_flags() const
{
return std::bit_cast<bs_t<sh_flag>>(static_cast<u32>(+_sh_flags));
}
};
template<template<typename T> class en_t, typename sz_t>
struct elf_shdata final : elf_shdr<en_t, sz_t>
{
std::vector<uchar> bin{};
using base = elf_shdr<en_t, sz_t>;
elf_shdata() = default;
};
// ELF loading options
@ -177,11 +236,12 @@ public:
using phdr_t = elf_phdr<en_t, sz_t>;
using shdr_t = elf_shdr<en_t, sz_t>;
using prog_t = elf_prog<en_t, sz_t>;
using shdata_t = elf_shdata<en_t, sz_t>;
ehdr_t header{};
std::vector<prog_t> progs{};
std::vector<shdr_t> shdrs{};
std::vector<shdata_t> shdrs{};
public:
elf_object() = default;
@ -238,6 +298,7 @@ public:
// Load program headers
std::vector<phdr_t> _phdrs;
std::vector<shdr_t> _shdrs;
if (!(opts & elf_opt::no_programs))
{
@ -249,7 +310,7 @@ public:
if (!(opts & elf_opt::no_sections))
{
stream.seek(offset + header.e_shoff);
if (!stream.read(shdrs, header.e_shnum))
if (!stream.read(_shdrs, header.e_shnum))
return set_error(elf_error::stream_shdrs);
}
@ -257,9 +318,7 @@ public:
progs.reserve(_phdrs.size());
for (const auto& hdr : _phdrs)
{
progs.emplace_back();
static_cast<phdr_t&>(progs.back()) = hdr;
static_cast<phdr_t&>(progs.emplace_back()) = hdr;
if (!(opts & elf_opt::no_data))
{
@ -269,6 +328,20 @@ public:
}
}
shdrs.clear();
shdrs.reserve(_shdrs.size());
for (const auto& shdr : _shdrs)
{
static_cast<shdr_t&>(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();
@ -279,6 +352,8 @@ public:
{
fs::file stream = fs::make_stream<std::vector<u8>>(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;
@ -298,7 +373,7 @@ public:
header.e_phentsize = u32{sizeof(phdr_t)};
header.e_phnum = ::size32(progs);
header.e_shentsize = u32{sizeof(shdr_t)};
header.e_shnum = ::size32(shdrs);
header.e_shnum = ::size32(shdrs) + u32{fixup_shdrs};
header.e_shstrndx = this->header.e_shstrndx;
stream.write(header);
@ -310,9 +385,19 @@ public:
stream.write(phdr);
}
if (fixup_shdrs)
{
// Insert a must-have empty section at the start
stream.write(shdr_t{});
}
for (shdr_t shdr : shdrs)
{
// TODO?
if (is_memorizable_section(shdr.sh_type))
{
shdr.sh_offset = std::exchange(off, off + shdr.sh_size);
}
stream.write(shdr);
}
@ -322,6 +407,16 @@ public:
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<fs::container_stream<std::vector<u8>>*>(stream.release().get())->obj);
}
@ -364,5 +459,7 @@ public:
using ppu_exec_object = elf_object<elf_be, u64, elf_machine::ppc64, elf_os::none, elf_type::exec>;
using ppu_prx_object = elf_object<elf_be, u64, elf_machine::ppc64, elf_os::lv2, elf_type::prx>;
using ppu_rel_object = elf_object<elf_be, u64, elf_machine::ppc64, elf_os::lv2, elf_type::rel>;
using spu_exec_object = elf_object<elf_be, u32, elf_machine::spu, elf_os::none, elf_type::exec>;
using spu_rel_object = elf_object<elf_be, u32, elf_machine::spu, elf_os::none, elf_type::rel>;
using arm_exec_object = elf_object<elf_le, u32, elf_machine::arm, elf_os::none, elf_type::none>;