Merge pull request #388 from DrChat/elf_modules
(Experimental) ELF module support
This commit is contained in:
commit
ae183f918f
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/cpu/elf_module.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "xenia/base/byte_order.h"
|
||||
#include "xenia/base/logging.h"
|
||||
|
||||
#include "xenia/cpu/processor.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
ElfModule::ElfModule(Processor* processor, kernel::KernelState* kernel_state)
|
||||
: Module(processor), kernel_state_(kernel_state) {}
|
||||
|
||||
ElfModule::~ElfModule() {}
|
||||
|
||||
// ELF structures
|
||||
struct elf32_ehdr {
|
||||
uint8_t e_ident[16];
|
||||
xe::be<uint16_t> e_type;
|
||||
xe::be<uint16_t> e_machine;
|
||||
xe::be<uint32_t> e_version;
|
||||
xe::be<uint32_t> e_entry;
|
||||
xe::be<uint32_t> e_phoff;
|
||||
xe::be<uint32_t> e_shoff;
|
||||
xe::be<uint32_t> e_flags;
|
||||
xe::be<uint16_t> e_ehsize;
|
||||
xe::be<uint16_t> e_phentsize;
|
||||
xe::be<uint16_t> e_phnum;
|
||||
xe::be<uint16_t> e_shentsize;
|
||||
xe::be<uint16_t> e_shnum;
|
||||
xe::be<uint16_t> e_shtrndx;
|
||||
};
|
||||
|
||||
struct elf32_phdr {
|
||||
xe::be<uint32_t> p_type;
|
||||
xe::be<uint32_t> p_offset;
|
||||
xe::be<uint32_t> p_vaddr;
|
||||
xe::be<uint32_t> p_paddr;
|
||||
xe::be<uint32_t> p_filesz;
|
||||
xe::be<uint32_t> p_memsz;
|
||||
xe::be<uint32_t> p_flags;
|
||||
xe::be<uint32_t> p_align;
|
||||
};
|
||||
|
||||
bool ElfModule::Load(const std::string& name, const std::string& path,
|
||||
const void* elf_addr, size_t elf_length) {
|
||||
name_ = name;
|
||||
path_ = path;
|
||||
|
||||
uint8_t* pelf = (uint8_t*)elf_addr;
|
||||
elf32_ehdr* hdr = (elf32_ehdr*)(pelf + 0x0);
|
||||
if (hdr->e_ident[0] != 0x7F || hdr->e_ident[1] != 'E' ||
|
||||
hdr->e_ident[2] != 'L' || hdr->e_ident[3] != 'F') {
|
||||
// Not an ELF file!
|
||||
return false;
|
||||
}
|
||||
|
||||
assert_true(hdr->e_ident[4] == 1); // 32bit
|
||||
|
||||
if (hdr->e_type != 2 /* ET_EXEC */) {
|
||||
// Not executable (shared objects not supported yet)
|
||||
XELOGE("ELF: Could not load ELF because it isn't executable!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hdr->e_machine != 20 /* EM_PPC */) {
|
||||
// Not a PPC ELF!
|
||||
XELOGE(
|
||||
"ELF: Could not load ELF because target machine is not PPC! (target: "
|
||||
"%d)",
|
||||
hdr->e_machine);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse LOAD program headers and load into memory.
|
||||
if (!hdr->e_phoff) {
|
||||
XELOGE("ELF: File doesn't have a program header!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hdr->e_entry) {
|
||||
XELOGE("ELF: Executable has no entry point!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Entry point virtual address
|
||||
entry_point_ = hdr->e_entry;
|
||||
|
||||
// Copy the ELF header
|
||||
elf_header_mem_.resize(hdr->e_ehsize);
|
||||
std::memcpy(elf_header_mem_.data(), hdr, hdr->e_ehsize);
|
||||
|
||||
assert_true(hdr->e_phentsize == sizeof(elf32_phdr));
|
||||
elf32_phdr* phdr = (elf32_phdr*)(pelf + hdr->e_phoff);
|
||||
for (uint32_t i = 0; i < hdr->e_phnum; i++) {
|
||||
if (phdr[i].p_type == 1 /* PT_LOAD */) {
|
||||
// Allocate and copy into memory.
|
||||
// Base address @ 0x80000000
|
||||
uint32_t virtual_addr = phdr[i].p_vaddr < 0x80000000
|
||||
? phdr[i].p_vaddr + 0x80000000
|
||||
: phdr[i].p_vaddr;
|
||||
if (!memory()
|
||||
->LookupHeap(virtual_addr)
|
||||
->AllocFixed(
|
||||
virtual_addr, phdr[i].p_memsz, phdr[i].p_align,
|
||||
xe::kMemoryAllocationReserve | xe::kMemoryAllocationCommit,
|
||||
xe::kMemoryProtectRead | xe::kMemoryProtectWrite)) {
|
||||
XELOGE("ELF: Could not allocate memory!");
|
||||
}
|
||||
|
||||
auto p = memory()->TranslateVirtual(virtual_addr);
|
||||
std::memset(p, 0, phdr[i].p_memsz);
|
||||
std::memcpy(p, pelf + phdr[i].p_offset,
|
||||
std::min(phdr[i].p_memsz, phdr[i].p_filesz));
|
||||
|
||||
// Notify backend about executable code.
|
||||
if (phdr[i].p_flags & 0x1 /* PF_X */) {
|
||||
processor_->backend()->CommitExecutableRange(
|
||||
virtual_addr, virtual_addr + phdr[i].p_memsz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<Function> ElfModule::CreateFunction(uint32_t address) {
|
||||
return std::unique_ptr<Function>(
|
||||
processor_->backend()->CreateGuestFunction(this, address));
|
||||
}
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_CPU_ELF_MODULE_H_
|
||||
#define XENIA_CPU_ELF_MODULE_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/cpu/module.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
class KernelState;
|
||||
} // namespace kernel
|
||||
|
||||
namespace cpu {
|
||||
|
||||
// ELF module: Used to load libxenon executables.
|
||||
class ElfModule : public xe::cpu::Module {
|
||||
public:
|
||||
ElfModule(Processor* processor, kernel::KernelState* kernel_state);
|
||||
virtual ~ElfModule();
|
||||
|
||||
bool loaded() const { return loaded_; }
|
||||
uint32_t entry_point() const { return entry_point_; }
|
||||
const std::string& name() const { return name_; }
|
||||
const std::string& path() const { return path_; }
|
||||
|
||||
bool Load(const std::string& name, const std::string& path,
|
||||
const void* elf_addr, size_t elf_length);
|
||||
bool Unload();
|
||||
|
||||
protected:
|
||||
std::unique_ptr<Function> CreateFunction(uint32_t address) override;
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::string path_;
|
||||
kernel::KernelState* kernel_state_;
|
||||
|
||||
bool loaded_;
|
||||
std::vector<uint8_t> elf_header_mem_; // Holds the ELF header
|
||||
uint32_t entry_point_;
|
||||
};
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_CPU_ELF_MODULE_H_
|
|
@ -187,7 +187,8 @@ X_STATUS Emulator::LaunchPath(std::wstring path) {
|
|||
if (last_dot == std::wstring::npos) {
|
||||
// Likely an STFS container.
|
||||
return LaunchStfsContainer(path);
|
||||
} else if (path.substr(last_dot) == L".xex") {
|
||||
} else if (path.substr(last_dot) == L".xex" ||
|
||||
path.substr(last_dot) == L".elf") {
|
||||
// Treat as a naked xex file.
|
||||
return LaunchXexFile(path);
|
||||
} else {
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/cpu/elf_module.h"
|
||||
#include "xenia/cpu/processor.h"
|
||||
#include "xenia/cpu/xex_module.h"
|
||||
#include "xenia/emulator.h"
|
||||
|
@ -45,8 +46,16 @@ X_STATUS XUserModule::LoadFromFile(std::string path) {
|
|||
return result;
|
||||
}
|
||||
|
||||
ModuleFormat fmt;
|
||||
auto magic = *(xe::be<uint32_t>*)mmap->data();
|
||||
if (magic == 'XEX2') {
|
||||
fmt = kModuleFormatXex;
|
||||
} else if (magic == 0x7F454C46 /* 0x7F 'ELF' */) {
|
||||
fmt = kModuleFormatElf;
|
||||
}
|
||||
|
||||
// Load the module.
|
||||
result = LoadFromMemory(mmap->data(), mmap->size());
|
||||
result = LoadFromMemory(mmap->data(), mmap->size(), fmt);
|
||||
} else {
|
||||
std::vector<uint8_t> buffer(fs_entry->size());
|
||||
|
||||
|
@ -66,45 +75,74 @@ X_STATUS XUserModule::LoadFromFile(std::string path) {
|
|||
return result;
|
||||
}
|
||||
|
||||
ModuleFormat fmt;
|
||||
uint32_t magic = *(uint32_t*)buffer.data();
|
||||
if (magic == 'XEX2') {
|
||||
fmt = kModuleFormatXex;
|
||||
} else if (magic == 0x7F454C46 /* 0x7F 'ELF' */) {
|
||||
fmt = kModuleFormatElf;
|
||||
}
|
||||
|
||||
// Load the module.
|
||||
result = LoadFromMemory(buffer.data(), bytes_read);
|
||||
result = LoadFromMemory(buffer.data(), bytes_read, fmt);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
X_STATUS XUserModule::LoadFromMemory(const void* addr, const size_t length) {
|
||||
X_STATUS XUserModule::LoadFromMemory(const void* addr, const size_t length,
|
||||
ModuleFormat module_format) {
|
||||
auto processor = kernel_state()->processor();
|
||||
module_format_ = module_format;
|
||||
|
||||
// Prepare the module for execution.
|
||||
// Runtime takes ownership.
|
||||
auto xex_module = std::make_unique<cpu::XexModule>(processor, kernel_state());
|
||||
if (!xex_module->Load(name_, path_, addr, length)) {
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
if (module_format == kModuleFormatXex) {
|
||||
// Prepare the module for execution.
|
||||
// Runtime takes ownership.
|
||||
auto xex_module =
|
||||
std::make_unique<cpu::XexModule>(processor, kernel_state());
|
||||
if (!xex_module->Load(name_, path_, addr, length)) {
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
processor_module_ = xex_module.get();
|
||||
if (!processor->AddModule(std::move(xex_module))) {
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
// Copy the xex2 header into guest memory.
|
||||
const xex2_header* header = this->xex_module()->xex_header();
|
||||
guest_xex_header_ = memory()->SystemHeapAlloc(header->header_size);
|
||||
|
||||
uint8_t* xex_header_ptr = memory()->TranslateVirtual(guest_xex_header_);
|
||||
std::memcpy(xex_header_ptr, header, header->header_size);
|
||||
|
||||
// Setup the loader data entry
|
||||
auto ldr_data =
|
||||
memory()->TranslateVirtual<X_LDR_DATA_TABLE_ENTRY*>(hmodule_ptr_);
|
||||
|
||||
ldr_data->dll_base = 0; // GetProcAddress will read this.
|
||||
ldr_data->xex_header_base = guest_xex_header_;
|
||||
|
||||
// Cache some commonly used headers...
|
||||
this->xex_module()->GetOptHeader(XEX_HEADER_ENTRY_POINT, &entry_point_);
|
||||
this->xex_module()->GetOptHeader(XEX_HEADER_DEFAULT_STACK_SIZE,
|
||||
&stack_size_);
|
||||
dll_module_ = !!(header->module_flags & XEX_MODULE_DLL_MODULE);
|
||||
} else if (module_format == kModuleFormatElf) {
|
||||
auto elf_module =
|
||||
std::make_unique<cpu::ElfModule>(processor, kernel_state());
|
||||
if (!elf_module->Load(name_, path_, addr, length)) {
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
entry_point_ = elf_module->entry_point();
|
||||
stack_size_ = 1024 * 1024; // 1 MB
|
||||
dll_module_ = false; // Hardcoded not a DLL (for now)
|
||||
|
||||
processor_module_ = elf_module.get();
|
||||
if (!processor->AddModule(std::move(elf_module))) {
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
}
|
||||
processor_module_ = xex_module.get();
|
||||
if (!processor->AddModule(std::move(xex_module))) {
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
// Copy the xex2 header into guest memory.
|
||||
const xex2_header* header = this->xex_module()->xex_header();
|
||||
guest_xex_header_ = memory()->SystemHeapAlloc(header->header_size);
|
||||
|
||||
uint8_t* xex_header_ptr = memory()->TranslateVirtual(guest_xex_header_);
|
||||
std::memcpy(xex_header_ptr, header, header->header_size);
|
||||
|
||||
// Setup the loader data entry
|
||||
auto ldr_data =
|
||||
memory()->TranslateVirtual<X_LDR_DATA_TABLE_ENTRY*>(hmodule_ptr_);
|
||||
|
||||
ldr_data->dll_base = 0; // GetProcAddress will read this.
|
||||
ldr_data->xex_header_base = guest_xex_header_;
|
||||
|
||||
// Cache some commonly used headers...
|
||||
this->xex_module()->GetOptHeader(XEX_HEADER_ENTRY_POINT, &entry_point_);
|
||||
this->xex_module()->GetOptHeader(XEX_HEADER_DEFAULT_STACK_SIZE, &stack_size_);
|
||||
dll_module_ = !!(header->module_flags & XEX_MODULE_DLL_MODULE);
|
||||
|
||||
OnLoad();
|
||||
|
||||
|
@ -160,6 +198,11 @@ X_STATUS XUserModule::GetSection(const char* name, uint32_t* out_section_data,
|
|||
X_STATUS XUserModule::GetOptHeader(xe_xex2_header_keys key, void** out_ptr) {
|
||||
assert_not_null(out_ptr);
|
||||
|
||||
if (module_format_ == kModuleFormatElf) {
|
||||
// Quick die.
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
bool ret = xex_module()->GetOptHeader(key, out_ptr);
|
||||
if (!ret) {
|
||||
return X_STATUS_NOT_FOUND;
|
||||
|
@ -170,6 +213,11 @@ X_STATUS XUserModule::GetOptHeader(xe_xex2_header_keys key, void** out_ptr) {
|
|||
|
||||
X_STATUS XUserModule::GetOptHeader(xe_xex2_header_keys key,
|
||||
uint32_t* out_header_guest_ptr) {
|
||||
if (module_format_ == kModuleFormatElf) {
|
||||
// Quick die.
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
auto header =
|
||||
memory()->TranslateVirtual<const xex2_header*>(guest_xex_header_);
|
||||
if (!header) {
|
||||
|
@ -255,6 +303,11 @@ X_STATUS XUserModule::Launch(uint32_t flags) {
|
|||
}
|
||||
|
||||
void XUserModule::Dump() {
|
||||
if (module_format_ == kModuleFormatElf) {
|
||||
// Quick die.
|
||||
return;
|
||||
}
|
||||
|
||||
xe::cpu::ExportResolver* export_resolver =
|
||||
kernel_state_->emulator()->export_resolver();
|
||||
auto header = xex_header();
|
||||
|
|
|
@ -19,6 +19,11 @@
|
|||
#include "xenia/xbox.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
class XexModule;
|
||||
class ElfModule;
|
||||
} // namespace cpu
|
||||
|
||||
namespace kernel {
|
||||
|
||||
class XUserModule : public XModule {
|
||||
|
@ -26,10 +31,18 @@ class XUserModule : public XModule {
|
|||
XUserModule(KernelState* kernel_state, const char* path);
|
||||
~XUserModule() override;
|
||||
|
||||
enum ModuleFormat {
|
||||
kModuleFormatUndefined = 0,
|
||||
kModuleFormatXex,
|
||||
kModuleFormatElf,
|
||||
};
|
||||
|
||||
const xe::cpu::XexModule* xex_module() const {
|
||||
assert_true(module_format_ == kModuleFormatXex);
|
||||
return reinterpret_cast<xe::cpu::XexModule*>(processor_module_);
|
||||
}
|
||||
xe::cpu::XexModule* xex_module() {
|
||||
assert_true(module_format_ == kModuleFormatXex);
|
||||
return reinterpret_cast<xe::cpu::XexModule*>(processor_module_);
|
||||
}
|
||||
|
||||
|
@ -41,7 +54,8 @@ class XUserModule : public XModule {
|
|||
uint32_t stack_size() const { return stack_size_; }
|
||||
|
||||
X_STATUS LoadFromFile(std::string path);
|
||||
X_STATUS LoadFromMemory(const void* addr, const size_t length);
|
||||
X_STATUS LoadFromMemory(const void* addr, const size_t length,
|
||||
ModuleFormat module_type);
|
||||
X_STATUS Unload();
|
||||
|
||||
uint32_t GetProcAddressByOrdinal(uint16_t ordinal) override;
|
||||
|
@ -70,7 +84,8 @@ class XUserModule : public XModule {
|
|||
void Dump();
|
||||
|
||||
private:
|
||||
uint32_t guest_xex_header_;
|
||||
uint32_t guest_xex_header_ = 0;
|
||||
ModuleFormat module_format_ = kModuleFormatUndefined;
|
||||
|
||||
bool dll_module_;
|
||||
uint32_t entry_point_;
|
||||
|
|
Loading…
Reference in New Issue