Browse Source
Merge pull request #8199 from liamwhite/jit-service
Merge pull request #8199 from liamwhite/jit-service
service: jit: Implement the JIT servicence_cpp
committed by
GitHub
5 changed files with 784 additions and 9 deletions
-
2src/core/CMakeLists.txt
-
11src/core/hle/kernel/k_code_memory.cpp
-
291src/core/hle/service/jit/jit.cpp
-
424src/core/hle/service/jit/jit_context.cpp
-
65src/core/hle/service/jit/jit_context.h
@ -0,0 +1,424 @@ |
|||||
|
// Copyright 2022 yuzu Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <array>
|
||||
|
#include <map>
|
||||
|
#include <span>
|
||||
|
#include <boost/icl/interval_set.hpp>
|
||||
|
#include <dynarmic/interface/A64/a64.h>
|
||||
|
#include <dynarmic/interface/A64/config.h>
|
||||
|
|
||||
|
#include "common/alignment.h"
|
||||
|
#include "common/common_funcs.h"
|
||||
|
#include "common/div_ceil.h"
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "core/hle/service/jit/jit_context.h"
|
||||
|
#include "core/memory.h"
|
||||
|
|
||||
|
namespace Service::JIT { |
||||
|
|
||||
|
constexpr std::array<u8, 4> STOP_ARM64 = { |
||||
|
0x01, 0x00, 0x00, 0xd4, // svc #0
|
||||
|
}; |
||||
|
|
||||
|
constexpr std::array<u8, 8> RESOLVE_ARM64 = { |
||||
|
0x21, 0x00, 0x00, 0xd4, // svc #1
|
||||
|
0xc0, 0x03, 0x5f, 0xd6, // ret
|
||||
|
}; |
||||
|
|
||||
|
constexpr std::array<u8, 4> PANIC_ARM64 = { |
||||
|
0x41, 0x00, 0x00, 0xd4, // svc #2
|
||||
|
}; |
||||
|
|
||||
|
constexpr std::array<u8, 60> MEMMOVE_ARM64 = { |
||||
|
0x1f, 0x00, 0x01, 0xeb, // cmp x0, x1
|
||||
|
0x83, 0x01, 0x00, 0x54, // b.lo #+34
|
||||
|
0x42, 0x04, 0x00, 0xd1, // sub x2, x2, 1
|
||||
|
0x22, 0x01, 0xf8, 0xb7, // tbnz x2, #63, #+36
|
||||
|
0x23, 0x68, 0x62, 0x38, // ldrb w3, [x1, x2]
|
||||
|
0x03, 0x68, 0x22, 0x38, // strb w3, [x0, x2]
|
||||
|
0xfc, 0xff, 0xff, 0x17, // b #-16
|
||||
|
0x24, 0x68, 0x63, 0x38, // ldrb w4, [x1, x3]
|
||||
|
0x04, 0x68, 0x23, 0x38, // strb w4, [x0, x3]
|
||||
|
0x63, 0x04, 0x00, 0x91, // add x3, x3, 1
|
||||
|
0x7f, 0x00, 0x02, 0xeb, // cmp x3, x2
|
||||
|
0x8b, 0xff, 0xff, 0x54, // b.lt #-16
|
||||
|
0xc0, 0x03, 0x5f, 0xd6, // ret
|
||||
|
0x03, 0x00, 0x80, 0xd2, // mov x3, 0
|
||||
|
0xfc, 0xff, 0xff, 0x17, // b #-16
|
||||
|
}; |
||||
|
|
||||
|
constexpr std::array<u8, 28> MEMSET_ARM64 = { |
||||
|
0x03, 0x00, 0x80, 0xd2, // mov x3, 0
|
||||
|
0x7f, 0x00, 0x02, 0xeb, // cmp x3, x2
|
||||
|
0x4b, 0x00, 0x00, 0x54, // b.lt #+8
|
||||
|
0xc0, 0x03, 0x5f, 0xd6, // ret
|
||||
|
0x01, 0x68, 0x23, 0x38, // strb w1, [x0, x3]
|
||||
|
0x63, 0x04, 0x00, 0x91, // add x3, x3, 1
|
||||
|
0xfb, 0xff, 0xff, 0x17, // b #-20
|
||||
|
}; |
||||
|
|
||||
|
struct HelperFunction { |
||||
|
const char* name; |
||||
|
const std::span<const u8> data; |
||||
|
}; |
||||
|
|
||||
|
constexpr std::array<HelperFunction, 6> HELPER_FUNCTIONS{{ |
||||
|
{"_stop", STOP_ARM64}, |
||||
|
{"_resolve", RESOLVE_ARM64}, |
||||
|
{"_panic", PANIC_ARM64}, |
||||
|
{"memcpy", MEMMOVE_ARM64}, |
||||
|
{"memmove", MEMMOVE_ARM64}, |
||||
|
{"memset", MEMSET_ARM64}, |
||||
|
}}; |
||||
|
|
||||
|
struct Elf64_Dyn { |
||||
|
u64 d_tag; |
||||
|
u64 d_un; |
||||
|
}; |
||||
|
|
||||
|
struct Elf64_Rela { |
||||
|
u64 r_offset; |
||||
|
u64 r_info; |
||||
|
s64 r_addend; |
||||
|
}; |
||||
|
|
||||
|
static constexpr u32 Elf64_RelaType(const Elf64_Rela* rela) { |
||||
|
return static_cast<u32>(rela->r_info); |
||||
|
} |
||||
|
|
||||
|
constexpr int DT_RELA = 7; /* Address of Rela relocs */ |
||||
|
constexpr int DT_RELASZ = 8; /* Total size of Rela relocs */ |
||||
|
constexpr int R_AARCH64_RELATIVE = 1027; /* Adjust by program base. */ |
||||
|
|
||||
|
constexpr size_t STACK_ALIGN = 16; |
||||
|
|
||||
|
class JITContextImpl; |
||||
|
|
||||
|
using IntervalSet = boost::icl::interval_set<VAddr>::type; |
||||
|
using IntervalType = boost::icl::interval_set<VAddr>::interval_type; |
||||
|
|
||||
|
class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks { |
||||
|
public: |
||||
|
explicit DynarmicCallbacks64(Core::Memory::Memory& memory_, std::vector<u8>& local_memory_, |
||||
|
IntervalSet& mapped_ranges_, JITContextImpl& parent_) |
||||
|
: memory{memory_}, local_memory{local_memory_}, |
||||
|
mapped_ranges{mapped_ranges_}, parent{parent_} {} |
||||
|
|
||||
|
u8 MemoryRead8(u64 vaddr) override { |
||||
|
return ReadMemory<u8>(vaddr); |
||||
|
} |
||||
|
u16 MemoryRead16(u64 vaddr) override { |
||||
|
return ReadMemory<u16>(vaddr); |
||||
|
} |
||||
|
u32 MemoryRead32(u64 vaddr) override { |
||||
|
return ReadMemory<u32>(vaddr); |
||||
|
} |
||||
|
u64 MemoryRead64(u64 vaddr) override { |
||||
|
return ReadMemory<u64>(vaddr); |
||||
|
} |
||||
|
u128 MemoryRead128(u64 vaddr) override { |
||||
|
return ReadMemory<u128>(vaddr); |
||||
|
} |
||||
|
std::string MemoryReadCString(u64 vaddr) { |
||||
|
std::string result; |
||||
|
u8 next; |
||||
|
|
||||
|
while ((next = MemoryRead8(vaddr++)) != 0) { |
||||
|
result += next; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
void MemoryWrite8(u64 vaddr, u8 value) override { |
||||
|
WriteMemory<u8>(vaddr, value); |
||||
|
} |
||||
|
void MemoryWrite16(u64 vaddr, u16 value) override { |
||||
|
WriteMemory<u16>(vaddr, value); |
||||
|
} |
||||
|
void MemoryWrite32(u64 vaddr, u32 value) override { |
||||
|
WriteMemory<u32>(vaddr, value); |
||||
|
} |
||||
|
void MemoryWrite64(u64 vaddr, u64 value) override { |
||||
|
WriteMemory<u64>(vaddr, value); |
||||
|
} |
||||
|
void MemoryWrite128(u64 vaddr, u128 value) override { |
||||
|
WriteMemory<u128>(vaddr, value); |
||||
|
} |
||||
|
|
||||
|
bool MemoryWriteExclusive8(u64 vaddr, u8 value, u8) override { |
||||
|
return WriteMemory<u8>(vaddr, value); |
||||
|
} |
||||
|
bool MemoryWriteExclusive16(u64 vaddr, u16 value, u16) override { |
||||
|
return WriteMemory<u16>(vaddr, value); |
||||
|
} |
||||
|
bool MemoryWriteExclusive32(u64 vaddr, u32 value, u32) override { |
||||
|
return WriteMemory<u32>(vaddr, value); |
||||
|
} |
||||
|
bool MemoryWriteExclusive64(u64 vaddr, u64 value, u64) override { |
||||
|
return WriteMemory<u64>(vaddr, value); |
||||
|
} |
||||
|
bool MemoryWriteExclusive128(u64 vaddr, u128 value, u128) override { |
||||
|
return WriteMemory<u128>(vaddr, value); |
||||
|
} |
||||
|
|
||||
|
void CallSVC(u32 swi) override; |
||||
|
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override; |
||||
|
void InterpreterFallback(u64 pc, size_t num_instructions) override; |
||||
|
|
||||
|
void AddTicks(u64 ticks) override {} |
||||
|
u64 GetTicksRemaining() override { |
||||
|
return std::numeric_limits<u32>::max(); |
||||
|
} |
||||
|
u64 GetCNTPCT() override { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
template <class T> |
||||
|
T ReadMemory(u64 vaddr) { |
||||
|
T ret{}; |
||||
|
if (boost::icl::contains(mapped_ranges, vaddr)) { |
||||
|
memory.ReadBlock(vaddr, &ret, sizeof(T)); |
||||
|
} else if (vaddr + sizeof(T) > local_memory.size()) { |
||||
|
LOG_CRITICAL(Service_JIT, "plugin: unmapped read @ 0x{:016x}", vaddr); |
||||
|
} else { |
||||
|
std::memcpy(&ret, local_memory.data() + vaddr, sizeof(T)); |
||||
|
} |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
template <class T> |
||||
|
bool WriteMemory(u64 vaddr, const T value) { |
||||
|
if (boost::icl::contains(mapped_ranges, vaddr)) { |
||||
|
memory.WriteBlock(vaddr, &value, sizeof(T)); |
||||
|
} else if (vaddr + sizeof(T) > local_memory.size()) { |
||||
|
LOG_CRITICAL(Service_JIT, "plugin: unmapped write @ 0x{:016x}", vaddr); |
||||
|
} else { |
||||
|
std::memcpy(local_memory.data() + vaddr, &value, sizeof(T)); |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
Core::Memory::Memory& memory; |
||||
|
std::vector<u8>& local_memory; |
||||
|
IntervalSet& mapped_ranges; |
||||
|
JITContextImpl& parent; |
||||
|
}; |
||||
|
|
||||
|
class JITContextImpl { |
||||
|
public: |
||||
|
explicit JITContextImpl(Core::Memory::Memory& memory_) : memory{memory_} { |
||||
|
callbacks = |
||||
|
std::make_unique<DynarmicCallbacks64>(memory, local_memory, mapped_ranges, *this); |
||||
|
user_config.callbacks = callbacks.get(); |
||||
|
jit = std::make_unique<Dynarmic::A64::Jit>(user_config); |
||||
|
} |
||||
|
|
||||
|
bool LoadNRO(std::span<const u8> data) { |
||||
|
local_memory.clear(); |
||||
|
local_memory.insert(local_memory.end(), data.begin(), data.end()); |
||||
|
|
||||
|
if (FixupRelocations()) { |
||||
|
InsertHelperFunctions(); |
||||
|
InsertStack(); |
||||
|
return true; |
||||
|
} else { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool FixupRelocations() { |
||||
|
const VAddr mod_offset{callbacks->MemoryRead32(4)}; |
||||
|
if (callbacks->MemoryRead32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)}; |
||||
|
VAddr rela_dyn = 0; |
||||
|
size_t num_rela = 0; |
||||
|
while (true) { |
||||
|
const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)}; |
||||
|
dynamic_offset += sizeof(Elf64_Dyn); |
||||
|
|
||||
|
if (!dyn.d_tag) { |
||||
|
break; |
||||
|
} |
||||
|
if (dyn.d_tag == DT_RELA) { |
||||
|
rela_dyn = dyn.d_un; |
||||
|
} |
||||
|
if (dyn.d_tag == DT_RELASZ) { |
||||
|
num_rela = dyn.d_un / sizeof(Elf64_Rela); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (size_t i = 0; i < num_rela; i++) { |
||||
|
const auto rela{callbacks->ReadMemory<Elf64_Rela>(rela_dyn + i * sizeof(Elf64_Rela))}; |
||||
|
if (Elf64_RelaType(&rela) != R_AARCH64_RELATIVE) { |
||||
|
continue; |
||||
|
} |
||||
|
const VAddr contents{callbacks->MemoryRead64(rela.r_offset)}; |
||||
|
callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend); |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void InsertHelperFunctions() { |
||||
|
for (const auto& [name, contents] : HELPER_FUNCTIONS) { |
||||
|
helpers[name] = local_memory.size(); |
||||
|
local_memory.insert(local_memory.end(), contents.begin(), contents.end()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void InsertStack() { |
||||
|
const u64 pad_amount{Common::AlignUp(local_memory.size(), STACK_ALIGN) - |
||||
|
local_memory.size()}; |
||||
|
local_memory.insert(local_memory.end(), 0x10000 + pad_amount, 0); |
||||
|
top_of_stack = local_memory.size(); |
||||
|
heap_pointer = top_of_stack; |
||||
|
} |
||||
|
|
||||
|
void MapProcessMemory(VAddr dest_address, std::size_t size) { |
||||
|
mapped_ranges.add(IntervalType{dest_address, dest_address + size}); |
||||
|
} |
||||
|
|
||||
|
void PushArgument(const void* data, size_t size) { |
||||
|
const size_t num_words = Common::DivCeil(size, sizeof(u64)); |
||||
|
const size_t current_pos = argument_stack.size(); |
||||
|
argument_stack.insert(argument_stack.end(), num_words, 0); |
||||
|
std::memcpy(argument_stack.data() + current_pos, data, size); |
||||
|
} |
||||
|
|
||||
|
void SetupArguments() { |
||||
|
for (size_t i = 0; i < 8 && i < argument_stack.size(); i++) { |
||||
|
jit->SetRegister(i, argument_stack[i]); |
||||
|
} |
||||
|
if (argument_stack.size() > 8) { |
||||
|
const VAddr new_sp = Common::AlignDown( |
||||
|
top_of_stack - (argument_stack.size() - 8) * sizeof(u64), STACK_ALIGN); |
||||
|
for (size_t i = 8; i < argument_stack.size(); i++) { |
||||
|
callbacks->MemoryWrite64(new_sp + (i - 8) * sizeof(u64), argument_stack[i]); |
||||
|
} |
||||
|
jit->SetSP(new_sp); |
||||
|
} |
||||
|
argument_stack.clear(); |
||||
|
heap_pointer = top_of_stack; |
||||
|
} |
||||
|
|
||||
|
u64 CallFunction(VAddr func) { |
||||
|
jit->SetRegister(30, helpers["_stop"]); |
||||
|
jit->SetSP(top_of_stack); |
||||
|
SetupArguments(); |
||||
|
|
||||
|
jit->SetPC(func); |
||||
|
jit->Run(); |
||||
|
return jit->GetRegister(0); |
||||
|
} |
||||
|
|
||||
|
VAddr GetHelper(const std::string& name) { |
||||
|
return helpers[name]; |
||||
|
} |
||||
|
|
||||
|
VAddr AddHeap(const void* data, size_t size) { |
||||
|
const size_t num_bytes{Common::AlignUp(size, STACK_ALIGN)}; |
||||
|
if (heap_pointer + num_bytes > local_memory.size()) { |
||||
|
local_memory.insert(local_memory.end(), |
||||
|
(heap_pointer + num_bytes) - local_memory.size(), 0); |
||||
|
} |
||||
|
const VAddr location{heap_pointer}; |
||||
|
std::memcpy(local_memory.data() + location, data, size); |
||||
|
heap_pointer += num_bytes; |
||||
|
return location; |
||||
|
} |
||||
|
|
||||
|
void GetHeap(VAddr location, void* data, size_t size) { |
||||
|
std::memcpy(data, local_memory.data() + location, size); |
||||
|
} |
||||
|
|
||||
|
std::unique_ptr<DynarmicCallbacks64> callbacks; |
||||
|
std::vector<u8> local_memory; |
||||
|
std::vector<u64> argument_stack; |
||||
|
IntervalSet mapped_ranges; |
||||
|
Dynarmic::A64::UserConfig user_config; |
||||
|
std::unique_ptr<Dynarmic::A64::Jit> jit; |
||||
|
std::map<std::string, VAddr, std::less<>> helpers; |
||||
|
Core::Memory::Memory& memory; |
||||
|
VAddr top_of_stack; |
||||
|
VAddr heap_pointer; |
||||
|
}; |
||||
|
|
||||
|
void DynarmicCallbacks64::CallSVC(u32 swi) { |
||||
|
switch (swi) { |
||||
|
case 0: |
||||
|
parent.jit->HaltExecution(); |
||||
|
break; |
||||
|
|
||||
|
case 1: { |
||||
|
// X0 contains a char* for a symbol to resolve
|
||||
|
std::string name{MemoryReadCString(parent.jit->GetRegister(0))}; |
||||
|
const auto helper{parent.helpers[name]}; |
||||
|
|
||||
|
if (helper != 0) { |
||||
|
parent.jit->SetRegister(0, helper); |
||||
|
} else { |
||||
|
LOG_WARNING(Service_JIT, "plugin requested unknown function {}", name); |
||||
|
parent.jit->SetRegister(0, parent.helpers["_panic"]); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case 2: |
||||
|
default: |
||||
|
LOG_CRITICAL(Service_JIT, "plugin panicked!"); |
||||
|
parent.jit->HaltExecution(); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void DynarmicCallbacks64::ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) { |
||||
|
LOG_CRITICAL(Service_JIT, "Illegal operation PC @ {:08x}", pc); |
||||
|
parent.jit->HaltExecution(); |
||||
|
} |
||||
|
|
||||
|
void DynarmicCallbacks64::InterpreterFallback(u64 pc, size_t num_instructions) { |
||||
|
LOG_CRITICAL(Service_JIT, "Unimplemented instruction PC @ {:08x}", pc); |
||||
|
parent.jit->HaltExecution(); |
||||
|
} |
||||
|
|
||||
|
JITContext::JITContext(Core::Memory::Memory& memory) |
||||
|
: impl{std::make_unique<JITContextImpl>(memory)} {} |
||||
|
|
||||
|
JITContext::~JITContext() {} |
||||
|
|
||||
|
bool JITContext::LoadNRO(std::span<const u8> data) { |
||||
|
return impl->LoadNRO(data); |
||||
|
} |
||||
|
|
||||
|
void JITContext::MapProcessMemory(VAddr dest_address, std::size_t size) { |
||||
|
impl->MapProcessMemory(dest_address, size); |
||||
|
} |
||||
|
|
||||
|
u64 JITContext::CallFunction(VAddr func) { |
||||
|
return impl->CallFunction(func); |
||||
|
} |
||||
|
|
||||
|
void JITContext::PushArgument(const void* data, size_t size) { |
||||
|
impl->PushArgument(data, size); |
||||
|
} |
||||
|
|
||||
|
VAddr JITContext::GetHelper(const std::string& name) { |
||||
|
return impl->GetHelper(name); |
||||
|
} |
||||
|
|
||||
|
VAddr JITContext::AddHeap(const void* data, size_t size) { |
||||
|
return impl->AddHeap(data, size); |
||||
|
} |
||||
|
|
||||
|
void JITContext::GetHeap(VAddr location, void* data, size_t size) { |
||||
|
impl->GetHeap(location, data, size); |
||||
|
} |
||||
|
|
||||
|
} // namespace Service::JIT
|
||||
@ -0,0 +1,65 @@ |
|||||
|
// Copyright 2022 yuzu Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <memory> |
||||
|
#include <span> |
||||
|
#include <string> |
||||
|
|
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace Core::Memory { |
||||
|
class Memory; |
||||
|
} |
||||
|
|
||||
|
namespace Service::JIT { |
||||
|
|
||||
|
class JITContextImpl; |
||||
|
|
||||
|
class JITContext { |
||||
|
public: |
||||
|
explicit JITContext(Core::Memory::Memory& memory); |
||||
|
~JITContext(); |
||||
|
|
||||
|
[[nodiscard]] bool LoadNRO(std::span<const u8> data); |
||||
|
void MapProcessMemory(VAddr dest_address, std::size_t size); |
||||
|
|
||||
|
template <typename T, typename... Ts> |
||||
|
u64 CallFunction(VAddr func, T argument, Ts... rest) { |
||||
|
static_assert(std::is_trivially_copyable_v<T>); |
||||
|
PushArgument(&argument, sizeof(argument)); |
||||
|
|
||||
|
if constexpr (sizeof...(rest) > 0) { |
||||
|
return CallFunction(func, rest...); |
||||
|
} else { |
||||
|
return CallFunction(func); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
u64 CallFunction(VAddr func); |
||||
|
VAddr GetHelper(const std::string& name); |
||||
|
|
||||
|
template <typename T> |
||||
|
VAddr AddHeap(T argument) { |
||||
|
return AddHeap(&argument, sizeof(argument)); |
||||
|
} |
||||
|
VAddr AddHeap(const void* data, size_t size); |
||||
|
|
||||
|
template <typename T> |
||||
|
T GetHeap(VAddr location) { |
||||
|
static_assert(std::is_trivially_copyable_v<T>); |
||||
|
T result; |
||||
|
GetHeap(location, &result, sizeof(result)); |
||||
|
return result; |
||||
|
} |
||||
|
void GetHeap(VAddr location, void* data, size_t size); |
||||
|
|
||||
|
private: |
||||
|
std::unique_ptr<JITContextImpl> impl; |
||||
|
|
||||
|
void PushArgument(const void* data, size_t size); |
||||
|
}; |
||||
|
|
||||
|
} // namespace Service::JIT |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue