From ead9e56c4dd75bd7f8391372cef948129f14ce5b Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 5 Nov 2024 17:54:12 +1000 Subject: [PATCH] System: Support loading ELF files --- src/common/log_channels.h | 2 +- src/core/bus.cpp | 54 ++++++-- src/core/cpu_core.cpp | 41 ++++++ src/core/cpu_core.h | 1 + src/core/fullscreen_ui.cpp | 4 +- src/core/psf_loader.cpp | 2 +- src/core/system.cpp | 4 +- src/duckstation-qt/mainwindow.cpp | 11 +- src/util/CMakeLists.txt | 2 + src/util/elf_file.cpp | 204 ++++++++++++++++++++++++++++++ src/util/elf_file.h | 116 +++++++++++++++++ src/util/util.vcxproj | 2 + src/util/util.vcxproj.filters | 2 + 13 files changed, 424 insertions(+), 21 deletions(-) create mode 100644 src/util/elf_file.cpp create mode 100644 src/util/elf_file.h diff --git a/src/common/log_channels.h b/src/common/log_channels.h index 425ebb510..e5b6b74a2 100644 --- a/src/common/log_channels.h +++ b/src/common/log_channels.h @@ -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) \ diff --git a/src/core/bus.cpp b/src/core/bus.cpp index c1e870cd3..2c7bcb3ca 100644 --- a/src/core/bus.cpp +++ b/src/core/bus.cpp @@ -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 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 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 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 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(data.size()); + const u32 zero_bytes = dest_size - static_cast(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> exe_data = - FileSystem::ReadBinaryFile(System::GetExeOverride().c_str(), error); + std::optional> 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 diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index c78029e40..2d36a68ac 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -3212,6 +3212,47 @@ bool CPU::SafeWriteMemoryBytes(VirtualMemoryAddress addr, const std::span(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; diff --git a/src/core/cpu_core.h b/src/core/cpu_core.h index 8406839e9..04abbc048 100644 --- a/src/core/cpu_core.h +++ b/src/core/cpu_core.h @@ -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 data); +bool SafeZeroMemoryBytes(VirtualMemoryAddress addr, u32 length); // External IRQs void SetIRQRequest(bool state); diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index fdfb028d4..244682c2b 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -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 fast_boot) diff --git a/src/core/psf_loader.cpp b/src/core/psf_loader.cpp index fca857322..c0249d96e 100644 --- a/src/core/psf_loader.cpp +++ b/src/core/psf_loader.cpp @@ -15,7 +15,7 @@ #include -LOG_CHANNEL(PSFLoader); +LOG_CHANNEL(FileLoader); namespace PSFLoader { static bool LoadLibraryPSF(const std::string& path, bool use_pc_sp, Error* error, u32 depth = 0); diff --git a/src/core/system.cpp b/src/core/system.cpp index 12cc320c5..f0347c579 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -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 diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 31ea3c094..b9381c2be 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -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; diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 5ffe0b72f..ee8d4c230 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -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 diff --git a/src/util/elf_file.cpp b/src/util/elf_file.cpp new file mode 100644 index 000000000..0e92a8f33 --- /dev/null +++ b/src/util/elf_file.cpp @@ -0,0 +1,204 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// 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(&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(hdr.e_shentsize); + if ((offset + sizeof(Elf32_Shdr)) > m_data.size()) + return nullptr; + + return reinterpret_cast(&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(&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(hdr.e_phentsize); + if ((offset + sizeof(Elf32_Phdr)) > m_data.size()) + return nullptr; + + return reinterpret_cast(&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)); + 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 data; + if (phdr->p_filesz > 0) + { + if ((phdr->p_offset + static_cast(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; +} diff --git a/src/util/elf_file.h b/src/util/elf_file.h new file mode 100644 index 000000000..29d5205de --- /dev/null +++ b/src/util/elf_file.h @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#pragma once + +#include "common/heap_array.h" +#include "common/types.h" + +#include + +class Error; + +class ELFFile +{ +public: + using DataArray = DynamicHeapArray; + + // 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 data, u32 dest_vaddr, u32 dest_size, Error* error)>; + bool LoadExecutableSections(const LoadExecutableSectionCallback& callback, Error* error) const; + +private: + DataArray m_data; +}; diff --git a/src/util/util.vcxproj b/src/util/util.vcxproj index 66c9c7964..51371b21d 100644 --- a/src/util/util.vcxproj +++ b/src/util/util.vcxproj @@ -3,6 +3,7 @@ + @@ -131,6 +132,7 @@ + diff --git a/src/util/util.vcxproj.filters b/src/util/util.vcxproj.filters index fd5e6bb63..a93e198ef 100644 --- a/src/util/util.vcxproj.filters +++ b/src/util/util.vcxproj.filters @@ -72,6 +72,7 @@ + @@ -152,6 +153,7 @@ +