System: Support loading ELF files
Some checks failed
Create rolling release / Windows x64 Build (push) Has been cancelled
Create rolling release / Windows x64 SSE2 Build (push) Has been cancelled
Create rolling release / Windows ARM64 Build (push) Has been cancelled
Create rolling release / Linux x64 AppImage (push) Has been cancelled
Create rolling release / Linux x64 SSE2 AppImage (push) Has been cancelled
Create rolling release / Linux Flatpak Build (push) Has been cancelled
Create rolling release / MacOS Universal Build (push) Has been cancelled
Create rolling release / Create Release (push) Has been cancelled

This commit is contained in:
Stenzek 2024-11-05 17:54:12 +10:00
parent 65f3dcbe9b
commit ead9e56c4d
No known key found for this signature in database
13 changed files with 424 additions and 21 deletions

View File

@ -22,6 +22,7 @@
X(DInputSource) \
X(DMA) \
X(DynamicLibrary) \
X(FileLoader) \
X(FileSystem) \
X(FullscreenUI) \
X(GDBProtocol) \
@ -55,7 +56,6 @@
X(Multitap) \
X(NeGconRumble) \
X(PCDrv) \
X(PSFLoader) \
X(Pad) \
X(PerfMon) \
X(PlatformMisc) \

View File

@ -23,6 +23,7 @@
#include "timing_event.h"
#include "util/cd_image.h"
#include "util/elf_file.h"
#include "util/state_wrapper.h"
#include "common/align.h"
@ -170,6 +171,7 @@ static void SetRAMPageWritable(u32 page_index, bool writable);
static void KernelInitializedHook();
static bool SideloadEXE(const std::string& path, Error* error);
static bool InjectCPE(std::span<const u8> buffer, bool set_pc, Error* error);
static bool InjectELF(const ELFFile& elf, bool set_pc, Error* error);
static void SetHandlers();
static void UpdateMappedRAMSize();
@ -972,15 +974,12 @@ bool Bus::InjectExecutable(std::span<const u8> buffer, bool set_pc, Error* error
return false;
}
if (header.memfill_size > 0)
if (header.memfill_size > 0 &&
!CPU::SafeZeroMemoryBytes(header.memfill_start & ~UINT32_C(3), Common::AlignDownPow2(header.memfill_size, 4)))
{
const u32 words_to_write = header.memfill_size / 4;
u32 address = header.memfill_start & ~UINT32_C(3);
for (u32 i = 0; i < words_to_write; i++)
{
CPU::SafeWriteMemoryWord(address, 0);
address += sizeof(u32);
}
Error::SetStringFmt(error, "Failed to zero {} bytes of memory at address 0x{:08X}.", header.memfill_start,
header.memfill_size);
return false;
}
const u32 data_load_size =
@ -1145,6 +1144,34 @@ bool Bus::InjectCPE(std::span<const u8> buffer, bool set_pc, Error* error)
return true;
}
bool Bus::InjectELF(const ELFFile& elf, bool set_pc, Error* error)
{
const bool okay = elf.LoadExecutableSections(
[](std::span<const u8> data, u32 dest_addr, u32 dest_size, Error* error) {
if (!data.empty() && !CPU::SafeWriteMemoryBytes(dest_addr, data))
{
Error::SetStringFmt(error, "Failed to load {} bytes to 0x{:08X}", data.size(), dest_addr);
return false;
}
const u32 zero_addr = dest_addr + static_cast<u32>(data.size());
const u32 zero_bytes = dest_size - static_cast<u32>(data.size());
if (zero_bytes > 0 && !CPU::SafeZeroMemoryBytes(zero_addr, zero_bytes))
{
Error::SetStringFmt(error, "Failed to zero {} bytes at 0x{:08X}", zero_bytes, zero_addr);
return false;
}
return true;
},
error);
if (okay && set_pc)
CPU::SetPC(elf.GetEntryPoint());
return okay;
}
void Bus::KernelInitializedHook()
{
if (s_kernel_initialize_hook_run)
@ -1179,8 +1206,7 @@ void Bus::KernelInitializedHook()
bool Bus::SideloadEXE(const std::string& path, Error* error)
{
const std::optional<DynamicHeapArray<u8>> exe_data =
FileSystem::ReadBinaryFile(System::GetExeOverride().c_str(), error);
std::optional<DynamicHeapArray<u8>> exe_data = FileSystem::ReadBinaryFile(path.c_str(), error);
if (!exe_data.has_value())
{
Error::AddPrefixFmt(error, "Failed to read {}: ", Path::GetFileName(path));
@ -1195,6 +1221,14 @@ bool Bus::SideloadEXE(const std::string& path, Error* error)
{
okay = InjectCPE(exe_data->cspan(), true, error);
}
else if (StringUtil::EndsWithNoCase(filename, ".elf"))
{
ELFFile elf;
if (!elf.Open(std::move(exe_data.value()), error))
return false;
okay = InjectELF(elf, true, error);
}
else
{
// look for a libps.exe next to the exe, if it exists, load it

View File

@ -3212,6 +3212,47 @@ bool CPU::SafeWriteMemoryBytes(VirtualMemoryAddress addr, const std::span<const
return SafeWriteMemoryBytes(addr, data.data(), static_cast<u32>(data.size()));
}
bool CPU::SafeZeroMemoryBytes(VirtualMemoryAddress addr, u32 length)
{
using namespace Bus;
const u32 seg = (addr >> 29);
if ((seg != 0 && seg != 4 && seg != 5) || (((addr + length) & PHYSICAL_MEMORY_ADDRESS_MASK) >= RAM_MIRROR_END) ||
(((addr & g_ram_mask) + length) > g_ram_size))
{
while ((addr & 3u) != 0 && length > 0)
{
if (!CPU::SafeWriteMemoryByte(addr, 0)) [[unlikely]]
return false;
addr++;
length--;
}
while (length >= 4)
{
if (!CPU::SafeWriteMemoryWord(addr, 0)) [[unlikely]]
return false;
addr += 4;
length -= 4;
}
while (length > 0)
{
if (!CPU::SafeWriteMemoryByte(addr, 0)) [[unlikely]]
return false;
addr++;
length--;
}
return true;
}
// Fast path: all in RAM, no wraparound.
std::memset(&g_ram[addr & g_ram_mask], 0, length);
return true;
}
void* CPU::GetDirectReadMemoryPointer(VirtualMemoryAddress address, MemoryAccessSize size, TickCount* read_ticks)
{
using namespace Bus;

View File

@ -185,6 +185,7 @@ bool SafeWriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value);
bool SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value);
bool SafeWriteMemoryBytes(VirtualMemoryAddress addr, const void* data, u32 length);
bool SafeWriteMemoryBytes(VirtualMemoryAddress addr, const std::span<const u8> data);
bool SafeZeroMemoryBytes(VirtualMemoryAddress addr, u32 length);
// External IRQs
void SetIRQRequest(bool state);

View File

@ -949,8 +949,8 @@ void FullscreenUI::DestroyResources()
ImGuiFullscreen::FileSelectorFilters FullscreenUI::GetDiscImageFilters()
{
return {"*.bin", "*.cue", "*.iso", "*.img", "*.chd", "*.ecm", "*.mds", "*.psexe",
"*.ps-exe", "*.exe", "*.psx", "*.psf", "*.minipsf", "*.m3u", "*.pbp"};
return {"*.bin", "*.cue", "*.iso", "*.img", "*.chd", "*.ecm", "*.mds", "*.cpe", "*.elf",
"*.psexe", "*.ps-exe", "*.exe", "*.psx", "*.psf", "*.minipsf", "*.m3u", "*.pbp"};
}
void FullscreenUI::DoStartPath(std::string path, std::string state, std::optional<bool> fast_boot)

View File

@ -15,7 +15,7 @@
#include <cstring>
LOG_CHANNEL(PSFLoader);
LOG_CHANNEL(FileLoader);
namespace PSFLoader {
static bool LoadLibraryPSF(const std::string& path, bool use_pc_sp, Error* error, u32 depth = 0);

View File

@ -793,7 +793,7 @@ bool System::IsExePath(std::string_view path)
{
return (StringUtil::EndsWithNoCase(path, ".exe") || StringUtil::EndsWithNoCase(path, ".psexe") ||
StringUtil::EndsWithNoCase(path, ".ps-exe") || StringUtil::EndsWithNoCase(path, ".psx") ||
StringUtil::EndsWithNoCase(path, ".cpe"));
StringUtil::EndsWithNoCase(path, ".cpe") || StringUtil::EndsWithNoCase(path, ".elf"));
}
bool System::IsPsfPath(std::string_view path)
@ -811,7 +811,7 @@ bool System::IsLoadablePath(std::string_view path)
{
static constexpr const std::array extensions = {
".bin", ".cue", ".img", ".iso", ".chd", ".ecm", ".mds", // discs
".exe", ".psexe", ".ps-exe", ".psx", ".cpe", // exes
".exe", ".psexe", ".ps-exe", ".psx", ".cpe", ".elf", // exes
".psf", ".minipsf", // psf
".psxgpu", ".psxgpu.zst", ".psxgpu.xz", // gpu dump
".m3u", // playlists

View File

@ -61,11 +61,12 @@
LOG_CHANNEL(Host);
static constexpr char DISC_IMAGE_FILTER[] = QT_TRANSLATE_NOOP(
"MainWindow", "All File Types (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.pbp *.exe *.psexe *.ps-exe *.psx *.psf "
"*.minipsf *.m3u *.psxgpu);;Single-Track Raw Images (*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD "
"Images (*.chd);;Error Code Modeler Images (*.ecm);;Media Descriptor Sidecar Images "
"(*.mds);;PlayStation EBOOTs (*.pbp *.PBP);;PlayStation Executables (*.exe *.psexe *.ps-exe, "
"*.psx);;Portable Sound Format Files (*.psf *.minipsf);;Playlists (*.m3u);;PSX GPU Dumps (*.psxgpu)");
"MainWindow",
"All File Types (*.bin *.img *.iso *.cue *.chd *.cpe *.ecm *.mds *.pbp *.elf *.exe *.psexe *.ps-exe *.psx *.psf "
"*.minipsf *.m3u *.psxgpu);;Single-Track Raw Images (*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD Images "
"(*.chd);;Error Code Modeler Images (*.ecm);;Media Descriptor Sidecar Images (*.mds);;PlayStation EBOOTs (*.pbp "
"*.PBP);;PlayStation Executables (*.cpe *.elf *.exe *.psexe *.ps-exe, *.psx);;Portable Sound Format Files (*.psf "
"*.minipsf);;Playlists (*.m3u);;PSX GPU Dumps (*.psxgpu)");
MainWindow* g_main_window = nullptr;

View File

@ -19,6 +19,8 @@ add_library(util
compress_helpers.h
cue_parser.cpp
cue_parser.h
elf_file.cpp
elf_file.h
gpu_device.cpp
gpu_device.h
gpu_framebuffer_manager.h

204
src/util/elf_file.cpp Normal file
View File

@ -0,0 +1,204 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "elf_file.h"
#include "common/error.h"
#include "common/file_system.h"
#include "common/log.h"
LOG_CHANNEL(FileLoader);
static constexpr s64 MAX_ELF_FILE_SIZE = 32 * 1024 * 1024;
ELFFile::ELFFile() = default;
ELFFile::~ELFFile() = default;
const ELFFile::Elf32_Ehdr& ELFFile::GetELFHeader() const
{
return *reinterpret_cast<const Elf32_Ehdr*>(&m_data[0]);
}
u32 ELFFile::GetEntryPoint() const
{
return GetELFHeader().e_entry;
}
const ELFFile::Elf32_Shdr* ELFFile::GetSectionHeader(u32 index) const
{
const Elf32_Ehdr& hdr = GetELFHeader();
if (index == SHN_UNDEF || index >= hdr.e_shnum || hdr.e_shentsize < sizeof(Elf32_Shdr))
return nullptr;
const size_t offset = hdr.e_shoff + index * static_cast<size_t>(hdr.e_shentsize);
if ((offset + sizeof(Elf32_Shdr)) > m_data.size())
return nullptr;
return reinterpret_cast<const Elf32_Shdr*>(&m_data[offset]);
}
std::string_view ELFFile::GetSectionName(const Elf32_Shdr& section) const
{
const Elf32_Shdr* strhdr = GetSectionHeader(GetELFHeader().e_shstrndx);
if (!strhdr || section.sh_name >= strhdr->sh_size)
return std::string_view();
const size_t file_offset = strhdr->sh_offset;
const u32 start_offset = section.sh_name;
u32 current_offset = start_offset;
while (current_offset < strhdr->sh_size && (current_offset + file_offset) < m_data.size())
{
if (m_data[file_offset + current_offset] == '\0')
break;
current_offset++;
}
if (current_offset == start_offset)
return std::string_view();
return std::string_view(reinterpret_cast<const char*>(&m_data[file_offset + start_offset]),
current_offset - start_offset);
}
u32 ELFFile::GetSectionCount() const
{
return GetELFHeader().e_shnum;
}
const ELFFile::Elf32_Phdr* ELFFile::GetProgramHeader(u32 index) const
{
const Elf32_Ehdr& hdr = GetELFHeader();
if (index >= hdr.e_phnum || hdr.e_phentsize < sizeof(Elf32_Phdr))
return nullptr;
const size_t offset = hdr.e_phoff + index * static_cast<size_t>(hdr.e_phentsize);
if ((offset + sizeof(Elf32_Phdr)) > m_data.size())
return nullptr;
return reinterpret_cast<const Elf32_Phdr*>(&m_data[offset]);
}
u32 ELFFile::GetProgramHeaderCount() const
{
return GetELFHeader().e_phnum;
}
bool ELFFile::Open(const char* path, Error* error)
{
auto fp = FileSystem::OpenManagedCFile(path, "rb", error);
if (!fp)
return false;
const s64 size = FileSystem::FSize64(fp.get(), error);
if (size < 0)
return false;
if (size >= MAX_ELF_FILE_SIZE)
{
Error::SetStringView(error, "File is too large.");
return false;
}
DataArray data(static_cast<size_t>(size));
if (std::fread(data.data(), data.size(), 1, fp.get()) != 1)
{
Error::SetErrno(error, "fread() failed: ", errno);
return false;
}
return Open(std::move(data), error);
}
bool ELFFile::Open(DataArray data, Error* error)
{
m_data = std::move(data);
static constexpr const u8 EXPECTED_HEADER[4] = {'\177', 'E', 'L', 'F'};
if (m_data.size() < sizeof(Elf32_Ehdr) || std::memcmp(m_data.data(), EXPECTED_HEADER, sizeof(EXPECTED_HEADER)) != 0)
{
Error::SetStringView(error, "Invalid header.");
return false;
}
const Elf32_Ehdr& hdr = GetELFHeader();
if (hdr.e_machine != EM_MIPS)
{
Error::SetStringFmt(error, "Unsupported machine type {}.", hdr.e_machine);
return false;
}
const u32 section_count = GetSectionCount();
const u32 proghdr_count = GetProgramHeaderCount();
DEV_LOG("ELF Sections={} ProgramHeaders={} Entry=0x{:08X}", section_count, proghdr_count, hdr.e_entry);
for (u32 i = 0; i < section_count; i++)
{
const Elf32_Shdr* shdr = GetSectionHeader(i);
if (!shdr)
continue;
DEV_LOG("Section {}: Name={} Size={}", i, GetSectionName(*shdr), shdr->sh_size);
}
for (u32 i = 0; i < proghdr_count; i++)
{
const Elf32_Phdr* phdr = GetProgramHeader(i);
if (!phdr || phdr->p_type != PT_LOAD)
continue;
DEV_LOG("Program Header {}: Load {} at 0x{:08X}", i, phdr->p_filesz, phdr->p_vaddr);
}
return true;
}
bool ELFFile::LoadExecutableSections(const LoadExecutableSectionCallback& callback, Error* error) const
{
const u32 entry = GetELFHeader().e_entry;
bool loaded_entry = false;
const u32 ph_count = GetProgramHeaderCount();
for (u32 i = 0; i < ph_count; i++)
{
const Elf32_Phdr* phdr = GetProgramHeader(i);
if (!phdr)
{
Error::SetStringFmt(error, "Failed to find program header {}", i);
return false;
}
if (phdr->p_type != PT_LOAD)
{
// ignore section
continue;
}
std::span<const u8> data;
if (phdr->p_filesz > 0)
{
if ((phdr->p_offset + static_cast<size_t>(phdr->p_filesz)) > m_data.size())
{
Error::SetStringFmt(error, "Program header {} is out of file range {} {} {}", i, phdr->p_offset, phdr->p_filesz,
m_data.size());
return false;
}
data = m_data.cspan(phdr->p_offset, phdr->p_filesz);
}
if (!callback(data, phdr->p_vaddr, std::max(phdr->p_memsz, phdr->p_filesz), error))
return false;
loaded_entry |= (entry >= phdr->p_vaddr && entry < (phdr->p_vaddr + phdr->p_memsz));
}
if (!loaded_entry)
{
Error::SetStringFmt(error, "Entry point 0x{:08X} not loaded.", entry);
return false;
}
return true;
}

116
src/util/elf_file.h Normal file
View File

@ -0,0 +1,116 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "common/heap_array.h"
#include "common/types.h"
#include <functional>
class Error;
class ELFFile
{
public:
using DataArray = DynamicHeapArray<u8>;
// ELF header constants
static constexpr u8 EI_NIDENT = 16;
static constexpr u16 ET_EXEC = 2;
static constexpr u16 ET_DYN = 3;
static constexpr u16 EM_MIPS = 8;
static constexpr u16 SHN_UNDEF = 0;
static constexpr u32 SHT_NULL = 0;
static constexpr u32 SHT_PROGBITS = 1;
static constexpr u32 SHT_SYMTAB = 2;
static constexpr u32 SHT_STRTAB = 3;
static constexpr u32 SHT_RELA = 4;
static constexpr u32 SHT_HASH = 5;
static constexpr u32 SHT_DYNAMIC = 6;
static constexpr u32 SHT_NOTE = 7;
static constexpr u32 SHT_NOBITS = 8;
static constexpr u32 SHT_REL = 9;
static constexpr u32 SHT_SHLIB = 10;
static constexpr u32 SHT_DYNSYM = 11;
static constexpr u32 SHT_NUM = 12;
static constexpr u32 PT_NULL = 0;
static constexpr u32 PT_LOAD = 1;
static constexpr u32 PT_DYNAMIC = 2;
static constexpr u32 PT_INTERP = 3;
static constexpr u32 PT_NOTE = 4;
static constexpr u32 PT_SHLIB = 5;
static constexpr u32 PT_PHDR = 6;
static constexpr u32 PT_TLS = 7;
// ELF Header structure
struct Elf32_Ehdr
{
u8 e_ident[EI_NIDENT]; // Magic number and other information
u16 e_type; // Object file type
u16 e_machine; // Architecture
u32 e_version; // Object file version
u32 e_entry; // Entry point virtual address
u32 e_phoff; // Program header table file offset
u32 e_shoff; // Section header table file offset
u32 e_flags; // Processor-specific flags
u16 e_ehsize; // ELF header size in bytes
u16 e_phentsize; // Program header table entry size
u16 e_phnum; // Program header table entry count
u16 e_shentsize; // Section header table entry size
u16 e_shnum; // Section header table entry count
u16 e_shstrndx; // Section header string table index
};
// Section header structure
struct Elf32_Shdr
{
u32 sh_name; // Section name (string tbl index)
u32 sh_type; // Section type
u32 sh_flags; // Section flags
u32 sh_addr; // Section virtual addr at execution
u32 sh_offset; // Section file offset
u32 sh_size; // Section size in bytes
u32 sh_link; // Link to another section
u32 sh_info; // Additional section information
u32 sh_addralign; // Section alignment
u32 sh_entsize; // Entry size if section holds table
};
// Program header structure
struct Elf32_Phdr
{
u32 p_type;
u32 p_offset;
u32 p_vaddr;
u32 p_paddr;
u32 p_filesz;
u32 p_memsz;
u32 p_flags;
u32 p_align;
};
public:
ELFFile();
~ELFFile();
const Elf32_Ehdr& GetELFHeader() const;
u32 GetEntryPoint() const;
const Elf32_Shdr* GetSectionHeader(u32 index) const;
std::string_view GetSectionName(const Elf32_Shdr& section) const;
u32 GetSectionCount() const;
const Elf32_Phdr* GetProgramHeader(u32 index) const;
u32 GetProgramHeaderCount() const;
bool Open(const char* path, Error* error);
bool Open(DataArray data, Error* error);
using LoadExecutableSectionCallback =
std::function<bool(std::span<const u8> data, u32 dest_vaddr, u32 dest_size, Error* error)>;
bool LoadExecutableSections(const LoadExecutableSectionCallback& callback, Error* error) const;
private:
DataArray m_data;
};

View File

@ -3,6 +3,7 @@
<Import Project="..\..\dep\msvc\vsprops\Configurations.props" />
<ItemGroup>
<ClInclude Include="compress_helpers.h" />
<ClInclude Include="elf_file.h" />
<ClInclude Include="image.h" />
<ClInclude Include="imgui_animated.h" />
<ClInclude Include="audio_stream.h" />
@ -131,6 +132,7 @@
<ClCompile Include="d3d12_texture.cpp" />
<ClCompile Include="d3d_common.cpp" />
<ClCompile Include="dinput_source.cpp" />
<ClCompile Include="elf_file.cpp" />
<ClCompile Include="gpu_device.cpp" />
<ClCompile Include="gpu_shader_cache.cpp" />
<ClCompile Include="gpu_texture.cpp" />

View File

@ -72,6 +72,7 @@
<ClInclude Include="sockets.h" />
<ClInclude Include="media_capture.h" />
<ClInclude Include="compress_helpers.h" />
<ClInclude Include="elf_file.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="state_wrapper.cpp" />
@ -152,6 +153,7 @@
<ClCompile Include="sockets.cpp" />
<ClCompile Include="media_capture.cpp" />
<ClCompile Include="compress_helpers.cpp" />
<ClCompile Include="elf_file.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="metal_shaders.metal" />