mirror of https://github.com/stenzek/duckstation
System: Support loading ELF files
parent
65f3dcbe9b
commit
ead9e56c4d
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
};
|
||||
Loading…
Reference in New Issue