mmio dcc and pybind

This commit is contained in:
wrapper 2026-03-29 12:25:55 +07:00
parent b53587f80d
commit d76b978242
10 changed files with 965 additions and 258 deletions

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10.0)
project(Dynemu VERSION 0.1.0 LANGUAGES C CXX)
project(Dynemu VERSION 0.2.0 LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20)
@ -18,6 +18,8 @@ if (DEFINED Dynemu_SHARED_LIBS)
set(BUILD_SHARED_LIBS "${Dynemu_SHARED_LIBS}")
endif ()
option(DYNEMU_PYTHON "Python support" ON)
# 01 - Boost first
set(BOOST_INCLUDEDIR "${PROJECT_SOURCE_DIR}/externals/dynarmic/externals/ext-boost")
find_package(Boost 1.57 REQUIRED)

View file

@ -0,0 +1,81 @@
"""
Dynemu
"""
from __future__ import annotations
import collections.abc
import enum
import typing
__all__: list[str] = ['Dynemu']
class Dynemu:
class DCCType(enum.Enum):
ARM11_CORTEX: typing.ClassVar[Dynemu.DCCType] # value = <DCCType.ARM11_CORTEX: 1>
ARM7_ARM9: typing.ClassVar[Dynemu.DCCType] # value = <DCCType.ARM7_ARM9: 0>
ARM11_CORTEX: typing.ClassVar[Dynemu.DCCType] # value = <DCCType.ARM11_CORTEX: 1>
ARM7_ARM9: typing.ClassVar[Dynemu.DCCType] # value = <DCCType.ARM7_ARM9: 0>
def __init__(self, dcc_type: Dynemu.DCCType = DCCType.ARM7_ARM9, no_opts: bool = False, big_endian: bool = False) -> None:
...
def dcc_read(self) -> int:
...
def dcc_write(self, value: typing.SupportsInt | typing.SupportsIndex) -> None:
...
def execute(self, pc: typing.SupportsInt | typing.SupportsIndex, stub_mode: bool = True) -> int:
...
def get_reg(self, reg_num: typing.SupportsInt | typing.SupportsIndex) -> int:
...
def memory_map(self, vaddr: typing.SupportsInt | typing.SupportsIndex, size: typing.SupportsInt | typing.SupportsIndex) -> None:
...
@typing.overload
def mmio(self, vaddr: typing.SupportsInt | typing.SupportsIndex, size: typing.SupportsInt | typing.SupportsIndex) -> None:
...
@typing.overload
def mmio(self, vaddr: typing.SupportsInt | typing.SupportsIndex, size: typing.SupportsInt | typing.SupportsIndex, read: collections.abc.Callable[[typing.SupportsInt | typing.SupportsIndex, typing.SupportsInt | typing.SupportsIndex], int]) -> None:
...
@typing.overload
def mmio(self, vaddr: typing.SupportsInt | typing.SupportsIndex, size: typing.SupportsInt | typing.SupportsIndex, write: collections.abc.Callable[[typing.SupportsInt | typing.SupportsIndex, typing.SupportsInt | typing.SupportsIndex, typing.SupportsInt | typing.SupportsIndex], None]) -> None:
...
@typing.overload
def mmio(self, vaddr: typing.SupportsInt | typing.SupportsIndex, size: typing.SupportsInt | typing.SupportsIndex, read: collections.abc.Callable[[typing.SupportsInt | typing.SupportsIndex, typing.SupportsInt | typing.SupportsIndex], int], write: collections.abc.Callable[[typing.SupportsInt | typing.SupportsIndex, typing.SupportsInt | typing.SupportsIndex, typing.SupportsInt | typing.SupportsIndex], None]) -> None:
...
@typing.overload
def mmio(self, vaddr: typing.SupportsInt | typing.SupportsIndex, size: typing.SupportsInt | typing.SupportsIndex, write: collections.abc.Callable[[typing.SupportsInt | typing.SupportsIndex, typing.SupportsInt | typing.SupportsIndex, typing.SupportsInt | typing.SupportsIndex], None], read: collections.abc.Callable[[typing.SupportsInt | typing.SupportsIndex, typing.SupportsInt | typing.SupportsIndex], int]) -> None:
...
def read_bytes(self, vaddr: typing.SupportsInt | typing.SupportsIndex, size: typing.SupportsInt | typing.SupportsIndex) -> bytes:
...
def read_u16(self, vaddr: typing.SupportsInt | typing.SupportsIndex) -> int:
...
def read_u32(self, vaddr: typing.SupportsInt | typing.SupportsIndex) -> int:
...
def read_u64(self, vaddr: typing.SupportsInt | typing.SupportsIndex) -> int:
...
def read_u8(self, vaddr: typing.SupportsInt | typing.SupportsIndex) -> int:
...
def set_reg(self, reg_num: typing.SupportsInt | typing.SupportsIndex, value: typing.SupportsInt | typing.SupportsIndex) -> None:
...
def upload_file(self, filename: str, vaddr: typing.SupportsInt | typing.SupportsIndex, size: typing.SupportsInt | typing.SupportsIndex) -> None:
...
@typing.overload
def upload_fls_memory(self, data: bytes) -> None:
...
@typing.overload
def upload_fls_memory(self, data: bytearray) -> None:
...
@typing.overload
def upload_memstub(self, vaddr: typing.SupportsInt | typing.SupportsIndex, mem_start: typing.SupportsInt | typing.SupportsIndex = 2684354560, mem_size: typing.SupportsInt | typing.SupportsIndex = 33554432) -> None:
...
@typing.overload
def upload_memstub(self, mem_start: typing.SupportsInt | typing.SupportsIndex = 2684354560, mem_size: typing.SupportsInt | typing.SupportsIndex = 33554432) -> None:
...
@typing.overload
def write_bytes(self, vaddr: typing.SupportsInt | typing.SupportsIndex, data: bytes) -> None:
...
@typing.overload
def write_bytes(self, vaddr: typing.SupportsInt | typing.SupportsIndex, data: bytearray) -> None:
...
def write_u16(self, vaddr: typing.SupportsInt | typing.SupportsIndex, value: typing.SupportsInt | typing.SupportsIndex) -> None:
...
def write_u32(self, vaddr: typing.SupportsInt | typing.SupportsIndex, value: typing.SupportsInt | typing.SupportsIndex) -> None:
...
def write_u64(self, vaddr: typing.SupportsInt | typing.SupportsIndex, value: typing.SupportsInt | typing.SupportsIndex) -> None:
...
def write_u8(self, vaddr: typing.SupportsInt | typing.SupportsIndex, value: typing.SupportsInt | typing.SupportsIndex) -> None:
...

View file

@ -15,9 +15,17 @@ set(BUILD_TESTING OFF)
# simply add the directory to that file as a subdirectory
# to have CMake automatically recognize them.
set(DYNARMIC_TESTS OFF CACHE BOOL "")
set(DYNARMIC_FRONTENDS "A32;A64" CACHE STRING "")
set(DYNARMIC_USE_PRECOMPILED_HEADERS ON CACHE BOOL "")
set(DYNARMIC_USE_BUNDLED_EXTERNALS ON CACHE BOOL "")
set(DYNARMIC_WARNINGS_AS_ERRORS OFF CACHE BOOL "")
add_subdirectory(dynarmic)
# Dynarmic
if (NOT TARGET dynarmic::dynarmic)
set(DYNARMIC_TESTS OFF CACHE BOOL "")
set(DYNARMIC_FRONTENDS "A32;A64" CACHE STRING "")
set(DYNARMIC_USE_PRECOMPILED_HEADERS ON CACHE BOOL "")
set(DYNARMIC_USE_BUNDLED_EXTERNALS ON CACHE BOOL "")
set(DYNARMIC_WARNINGS_AS_ERRORS OFF CACHE BOOL "")
add_subdirectory(dynarmic)
endif()
if (DYNEMU_PYTHON)
add_subdirectory(pybind11)
endif()

View file

@ -14,6 +14,11 @@ extern "C" {
typedef uint32_t u32;
typedef uint64_t u64;
enum DY_DCCType {
ARM7_ARM9,
ARM11_CORTEX
};
#ifndef __cplusplus
typedef uint8_t bool;
#endif
@ -21,25 +26,29 @@ extern "C" {
#if !defined(dynemu_EXPORTS) && !defined(DYNEMU_STATIC_DEFINE)
typedef struct Emulator Emulator;
#endif
typedef u64 (*DY_MMIO_Read)(u64 address, u8 access);
typedef void (*DY_MMIO_Write)(u64 address, u64 value, u8 access);
DYNEMU_EXPORT Emulator *dynemu_open();
DYNEMU_EXPORT Emulator *dynemu_open(enum DY_DCCType dcc_type, bool no_opts, bool big_endian);
DYNEMU_EXPORT void dynemu_delete(Emulator *e);
DYNEMU_EXPORT void dynemu_mmap(Emulator *e, u32 vaddr, u32 size);
DYNEMU_EXPORT size_t dynemu_upload_file(Emulator *e, const char *filename, int64_t dest, u32 size);
DYNEMU_EXPORT bool dynemu_upload_fls_memory(Emulator *e, const u8 *fls);
DYNEMU_EXPORT void dynemu_upload_memstub(Emulator *e, u32 dest, u32 mem_start, u32 mem_size, bool is_big);
DYNEMU_EXPORT void dynemu_upload_memstub_nooffset(Emulator *e, u32 mem_start, u32 mem_size, bool is_big);
DYNEMU_EXPORT void dynemu_upload_memstub(Emulator *e, u32 dest, u32 mem_start, u32 mem_size);
DYNEMU_EXPORT void dynemu_upload_memstub_nooffset(Emulator *e, u32 mem_start, u32 mem_size);
DYNEMU_EXPORT void dynemu_mmio(Emulator *e, u32 vaddr, u32 size, DY_MMIO_Read read, DY_MMIO_Write write);
DYNEMU_EXPORT u8 dynemu_read_u8(Emulator *e, u32 vaddr);
DYNEMU_EXPORT u16 dynemu_read_u16(Emulator *e, u32 vaddr, bool is_big);
DYNEMU_EXPORT u32 dynemu_read_u32(Emulator *e, u32 vaddr, bool is_big);
DYNEMU_EXPORT u64 dynemu_read_u64(Emulator *e, u32 vaddr, bool is_big);
DYNEMU_EXPORT u16 dynemu_read_u16(Emulator *e, u32 vaddr);
DYNEMU_EXPORT u32 dynemu_read_u32(Emulator *e, u32 vaddr);
DYNEMU_EXPORT u64 dynemu_read_u64(Emulator *e, u32 vaddr);
DYNEMU_EXPORT void dynemu_write_u8(Emulator *e, u32 vaddr, u8 value);
DYNEMU_EXPORT void dynemu_write_u16(Emulator *e, u32 vaddr, u16 value, bool is_big);
DYNEMU_EXPORT void dynemu_write_u32(Emulator *e, u32 vaddr, u32 value, bool is_big);
DYNEMU_EXPORT void dynemu_write_u64(Emulator *e, u32 vaddr, u64 value, bool is_big);
DYNEMU_EXPORT void dynemu_write_u16(Emulator *e, u32 vaddr, u16 value);
DYNEMU_EXPORT void dynemu_write_u32(Emulator *e, u32 vaddr, u32 value);
DYNEMU_EXPORT void dynemu_write_u64(Emulator *e, u32 vaddr, u64 value);
DYNEMU_EXPORT void dynemu_read_bytes(Emulator *e, u32 vaddr, u8 *output, u32 size);
DYNEMU_EXPORT void dynemu_write_bytes(Emulator *e, u32 vaddr, const u8 *input, u32 size);
DYNEMU_EXPORT u32 dynemu_execute(Emulator *e, u32 pc, bool is_big);
DYNEMU_EXPORT u32 dynemu_execute(Emulator *e, u32 pc, bool stub_mode);
DYNEMU_EXPORT u32 dynemu_read_reg(Emulator *e, u8 reg);
DYNEMU_EXPORT void dynemu_write_reg(Emulator *e, u8 reg, u32 value);
#ifdef __cplusplus

View file

@ -1,66 +1,104 @@
/* (c) 2025 Wrapper Inc. - The New Emulation Library */
#pragma once
#include "dynarmic/interface/A32/config.h"
#include "dynarmic/interface/halt_reason.h"
#include "fmt/format.h"
#include "dynarmic/interface/A32/a32.h"
#include "dynarmic/interface/A32/coprocessor.h"
#include "dynarmic/interface/exclusive_monitor.h"
#include "dynemu/export.h"
#include <functional>
#include <future>
#include <iostream>
#include <memory>
#include <optional>
using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;
// typedef u64 (*MMIO_Read)(u64 address, u8 access);
// typedef void (*MMIO_Write)(u64 address, u64 value, u8 access);
using MMIO_Read = std::function<u64(u64, u8)>;
using MMIO_Write = std::function<void(u64, u64, u8)>;
enum class DCCType {
ARM7_ARM9,
ARM11_CORTEX
};
namespace Dynemu {
using Callback = Dynarmic::A32::Coprocessor::Callback;
using CoprocReg = Dynarmic::A32::CoprocReg;
using CallbackOrAccessOneWord = Dynarmic::A32::Coprocessor::CallbackOrAccessOneWord;
using CallbackOrAccessTwoWords = Dynarmic::A32::Coprocessor::CallbackOrAccessTwoWords;
struct MemMap final {
u64 start;
u64 size;
u32 start;
u32 size;
std::unique_ptr<u8[]> data;
};
struct MMIO final {
u32 start;
u32 size;
MMIO_Read read;
MMIO_Write write;
};
class DYNEMU_EXPORT MyEmulator final : public Dynarmic::A32::UserCallbacks {
private:
u8 find_memory_map_id(u32 vaddr);
bool is_mmap_exists(u32 vaddr);
template <std::size_t N>
bool CheckExecOverridePC(const std::array<u32, N> &list, u32 pc);
/* Memory map */
std::optional<u8> find_memory_map_id(u32 vaddr);
/* MMIO */
std::optional<u8> find_mmio_id(u32 vaddr);
std::optional<u32> handle_mmio_read(u32 vaddr, u8 access);
bool handle_mmio_write(u32 vaddr, u32 value, u8 access);
/* Misc */
bool check_memory_access(u32 vaddr);
bool big_endian;
bool thumb_mode;
bool stub_mode;
public:
/* Memory map */
std::array<MemMap, 64> mem_map;
u32 map_index;
u32 os_offset;
u8 mem_map_index;
/* MMIO */
std::array<MMIO, 64> mmio_map;
u8 mmio_index;
/* state */
Dynarmic::A32::Jit *cpu;
u32 os_offset;
/*
MyEmulator()
Constructor
*/
MyEmulator() : map_index(0), os_offset(0xa8000000), mem_map() {};
MyEmulator(bool big_endian = false) : mem_map_index(0), mmio_index(0), os_offset(0xa8000000), mem_map(), mmio_map(), big_endian(big_endian) {};
/*
void CreateMemoryMap(u32 vaddr, u32 size)
void CreateMemoryMap(u32 vaddr, u32 size)
This function creates a memory map for this specific vaddr (virtual address) with specific size
*/
void CreateMemoryMap(u32 vaddr, u32 size);
/*
void RoutineReplaceWithBXLR(const std::array<u32, N> &list, bool is_thumb, bool is_big)
This function replaces each instruction on the list with "bx lr" instruction
Currently, this has no use outside for hooking functions
*/
template <std::size_t N>
void RoutineReplaceWithBXLR(const std::array<u32, N> &list, bool is_thumb, bool is_big);
/*
size_t UploadToMemory(std::string filename, std::int64_t dest, u32 size)
size_t UploadToMemory(const std::string &filename, std::int64_t dest, u32 size)
This function uploads a file into memory, automatically creating a memory map if not exists.
Setting dest to -1 will use a multi-map parser
Returns:
0: NOK
1 or FileSize: OK
*/
size_t UploadToMemory(std::string filename, std::int64_t dest, u32 size);
size_t UploadToMemory(const std::string &filename, std::int64_t dest, u32 size);
/*
bool UploadFLSFromBytes(const u8 *fls)
@ -84,27 +122,51 @@ namespace Dynemu {
}
/*
void UploadOSStub(u32 dest, u32 mem_start, u32 mem_size, bool is_big)
This function uploads a stub libc to a specific dest virtual address, along with setting up the regions assosiated with the dynamic memory functions.
Endianness were determined by is_big parameter.
bool UploadFLSFromBytes(const std::vector<u8> &fls)
This function uploads a multi-part code file from memory.
Returns:
0: NOK
1: OK
*/
void UploadOSStub(u32 dest, u32 mem_start, u32 mem_size, bool is_big);
template <std::size_t N>
bool UploadFLSFromBytes(const std::vector<u8> &fls) {
return UploadFLSFromBytes(fls.data());
}
/*
void UploadOSStub(u32 mem_start, u32 mem_size, bool is_big)
This function uploads a stub libc to the configured os_offset parameter virtual address, along with setting up the regions assosiated with the dynamic memory functions.
Endianness were determined by is_big parameter.
void UploadOSStub(u32 dest, u32 mem_start, u32 mem_size)
This function uploads a stub libc to a specific dest virtual address, along with setting up the regions assosiated with the dynamic memory functions.
Endianness were determined by big_endian parameter.
*/
void UploadOSStub(u32 mem_start, u32 mem_size, bool is_big) {
UploadOSStub(os_offset, mem_start, mem_size, is_big);
void UploadOSStub(u32 dest, u32 mem_start, u32 mem_size);
/*
void UploadOSStub(u32 mem_start, u32 mem_size)
This function uploads a stub libc to the configured os_offset parameter virtual address, along with setting up the regions assosiated with the dynamic memory functions.
Endianness were determined by big_endian parameter.
*/
void UploadOSStub(u32 mem_start, u32 mem_size) {
UploadOSStub(os_offset, mem_start, mem_size);
}
/*
void CreateMMIO(u32 addr_start, u32 addr_size, const MMIO_Read &read, const MMIO_Write &write)
This function maps the virtual address to specific functions.
*/
void CreateMMIO(u32 addr_start, u32 addr_size, const MMIO_Read &read = [](u64, u8) -> u64 {return -1;}, const MMIO_Write &write = [](u64, u32, u8) {});
/*
bool PreCodeReadHook(bool is_thumb, u32 pc, Dynarmic::A32::IREmitter& ir)
Custom function used for end of code detection.
*/
bool PreCodeReadHook(bool is_thumb, u32 pc, Dynarmic::A32::IREmitter& ir) override;
/*
std::optional<u32> MemoryReadCode(u32 vaddr)
Custom function used in code read routines.
*/
std::optional<u32> MemoryReadCode(u32 vaddr) override;
/*
u8 MemoryRead8(u32 vaddr)
Reads an 8-bit value from vaddr
@ -118,6 +180,23 @@ namespace Dynemu {
*/
void MemoryReadBytes(u32 vaddr, u8 *output, u32 size);
/*
void MemoryReadBytes(u32 vaddr, const std::array<u8, N> &data)
Copy data from memory map to a single u8 pointer.
*/
template <std::size_t N>
void MemoryReadBytes(u32 vaddr, const std::array<u8, N> &data) {
MemoryReadBytes(vaddr, data.data(), N);
};
/*
void MemoryReadBytes(u32 vaddr, std::vector<u8> data)
Copy data from memory map to a single u8 pointer.
*/
void MemoryReadBytes(u32 vaddr, std::vector<u8> data) {
MemoryReadBytes(vaddr, data.data(), data.size());
};
/*
u16 MemoryRead16(u32 vaddr)
Reads a 16-bit value from vaddr
@ -139,26 +218,6 @@ namespace Dynemu {
*/
u64 MemoryRead64(u32 vaddr) override;
/*
u16 MemoryRead16_Big(u32 vaddr)
Reads a 16-bit value from vaddr
Returns: short[vaddr]
*/
u16 MemoryRead16_Big(u32 vaddr);
/*
u32 MemoryRead32_Big(u32 vaddr)
Reads a 32-bit value from vaddr
Returns: word[vaddr]
*/
u32 MemoryRead32_Big(u32 vaddr);
/*
u64 MemoryRead64_Big(u32 vaddr)
Reads a 64-bit value from vaddr
Returns: dword[vaddr]
*/
u64 MemoryRead64_Big(u32 vaddr);
/*
void MemoryWrite8(u32 vaddr, u8 value)
@ -181,6 +240,14 @@ namespace Dynemu {
MemoryWriteBytes(vaddr, data.data(), N);
};
/*
void MemoryWriteBytes(u32 vaddr, const std::vector<u8> &data)
Copy data from a single u8 pointer into memory map.
*/
void MemoryWriteBytes(u32 vaddr, const std::vector<u8> &data) {
MemoryWriteBytes(vaddr, data.data(), data.size());
};
/*
void MemoryWrite16(u32 vaddr, u16 value)
Writes an 16-bit value to vaddr
@ -198,24 +265,6 @@ namespace Dynemu {
Writes an 64-bit value to vaddr
*/
void MemoryWrite64(u32 vaddr, u64 value) override;
/*
void MemoryWrite16_Big(u32 vaddr, u16 value)
Writes an 16-bit value to vaddr
*/
void MemoryWrite16_Big(u32 vaddr, u16 value);
/*
void MemoryWrite32_Big(u32 vaddr, u32 value)
Writes an 32-bit value to vaddr
*/
void MemoryWrite32_Big(u32 vaddr, u32 value);
/*
void MemoryWrite64_Big(u32 vaddr, u64 value)
Writes an 64-bit value to vaddr
*/
void MemoryWrite64_Big(u32 vaddr, u64 value);
/*
void InterpreterFallback(u32 pc, size_t num_instructions)
@ -233,29 +282,28 @@ namespace Dynemu {
}
/*
u32 Execute(u32 pc, bool is_big)
u32 Execute(u32 pc, bool stub_mode)
Executes from specific PC
Returns: R0 register
*/
u32 Execute(u32 pc, bool is_big);
u32 Execute(u32 pc, bool stub_mode=true);
/*
u32 Execute(u32 pc)
Executes from specific PC
Returns: R0 register
std::future<u32> Spawn(u32 pc, bool stub_mode)
Async version of Execute
Returns: Future(R0 register)
*/
u32 Execute(u32 pc) {
return Execute(pc, false);
}
std::future<u32> Spawn(u32 pc, bool stub_mode=true);
/*
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception)
*/
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
std::cerr << fmt::format("decoder: exception at {:#08x}: ", pc);
std::cerr << fmt::format("dynemu: exception at {:#08x}: ", pc);
std::cerr << fmt::format("{}", (int)exception) << std::endl;
std::terminate();
// Do something.
if (exception == Dynarmic::A32::Exception::NoExecuteFault)
cpu->HaltExecution(Dynarmic::HaltReason::MemoryAbort);
}
/*
@ -269,14 +317,45 @@ namespace Dynemu {
u64 GetTicksRemaining() override {return 8000;}
};
struct CP14State {
u32 dcc_status;
u32 dcc_read_data;
u32 dcc_write_data;
DCCType dcc_type;
};
class DYNEMU_EXPORT CP14 final : public Dynarmic::A32::Coprocessor {
std::mutex dcc_lock;
u32 dummy_value;
std::optional<Callback> CompileInternalOperation(bool two, unsigned opc1, CoprocReg CRd, CoprocReg CRn, CoprocReg CRm, unsigned opc2) override;
CallbackOrAccessOneWord CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm, unsigned opc2) override;
CallbackOrAccessTwoWords CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) override;
CallbackOrAccessOneWord CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm, unsigned opc2) override;
CallbackOrAccessTwoWords CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) override;
std::optional<Callback> CompileLoadWords(bool two, bool long_transfer, CoprocReg CRd, std::optional<std::uint8_t> option) override;
std::optional<Callback> CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd, std::optional<std::uint8_t> option) override;
private:
CP14State &state;
public:
CP14(DCCType dcc_type, CP14State &state);
void Send(u32 value);
u32 Get();
};
class DYNEMU_EXPORT Emulator final {
private:
std::unique_ptr<Dynarmic::A32::Jit> cpu;
std::unique_ptr<Dynarmic::ExclusiveMonitor> mon;
DCCType dcc_type;
CP14State cp14_state;
public:
MyEmulator env;
std::shared_ptr<CP14> cp14;
Emulator();
Emulator(DCCType dcc_type=DCCType::ARM7_ARM9, bool no_opts=false, bool big_endian=false);
};
}

View file

@ -2,4 +2,9 @@ include_directories(.)
set_property(DIRECTORY APPEND PROPERTY
COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
add_subdirectory(lib)
add_subdirectory(lib)
if (DYNEMU_PYTHON)
set(PYBIND11_FINDPYTHON ON)
add_subdirectory(pybind)
endif()
add_subdirectory(test)

View file

@ -4,8 +4,8 @@ using namespace Dynemu;
extern "C" {
#include "dynemu/libdynemu.h"
Emulator *dynemu_open() {
auto temp = new Emulator();
Emulator *dynemu_open(enum DY_DCCType dcc_type, bool no_opts, bool big_endian) {
auto temp = new Emulator((DCCType)dcc_type, no_opts, big_endian);
return temp;
}
@ -17,6 +17,12 @@ extern "C" {
return e->env.CreateMemoryMap(vaddr, size);
}
void dynemu_mmio(Emulator *e, u32 vaddr, u32 size, DY_MMIO_Read read, DY_MMIO_Write write) {
auto read_cb = std::bind(read, std::placeholders::_1, std::placeholders::_2);
auto write_cb = std::bind(write, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
return e->env.CreateMMIO(vaddr, size, read_cb, write_cb);
}
size_t dynemu_upload_file(Emulator *e, const char *filename, std::int64_t dest, u32 size) {
return e->env.UploadToMemory(filename, dest, size);
}
@ -25,68 +31,44 @@ extern "C" {
return e->env.UploadFLSFromBytes(fls);
}
void dynemu_upload_memstub(Emulator *e, u32 dest, u32 mem_start, u32 mem_size, bool is_big) {
return e->env.UploadOSStub(dest, mem_start, mem_size, is_big);
void dynemu_upload_memstub(Emulator *e, u32 dest, u32 mem_start, u32 mem_size) {
return e->env.UploadOSStub(dest, mem_start, mem_size);
}
void dynemu_upload_memstub_nooffset(Emulator *e, u32 mem_start, u32 mem_size, bool is_big) {
return e->env.UploadOSStub(mem_start, mem_size, is_big);
void dynemu_upload_memstub_nooffset(Emulator *e, u32 mem_start, u32 mem_size) {
return e->env.UploadOSStub(mem_start, mem_size);
}
u8 dynemu_read_u8(Emulator *e, u32 vaddr) {
return e->env.MemoryRead8(vaddr);
}
u16 dynemu_read_u16(Emulator *e, u32 vaddr, bool is_big) {
if (is_big) {
return e->env.MemoryRead16_Big(vaddr);
} else {
return e->env.MemoryRead16(vaddr);
}
u16 dynemu_read_u16(Emulator *e, u32 vaddr) {
return e->env.MemoryRead16(vaddr);
}
u32 dynemu_read_u32(Emulator *e, u32 vaddr, bool is_big) {
if (is_big) {
return e->env.MemoryRead32_Big(vaddr);
} else {
return e->env.MemoryRead32(vaddr);
}
u32 dynemu_read_u32(Emulator *e, u32 vaddr) {
return e->env.MemoryRead32(vaddr);
}
u64 dynemu_read_u64(Emulator *e, u32 vaddr, bool is_big) {
if (is_big) {
return e->env.MemoryRead64_Big(vaddr);
} else {
return e->env.MemoryRead64(vaddr);
}
u64 dynemu_read_u64(Emulator *e, u32 vaddr) {
return e->env.MemoryRead64(vaddr);
}
void dynemu_write_u8(Emulator *e, u32 vaddr, u8 value) {
e->env.MemoryWrite8(vaddr, value);
}
void dynemu_write_u16(Emulator *e, u32 vaddr, u16 value, bool is_big) {
if (is_big) {
e->env.MemoryWrite16_Big(vaddr, value);
} else {
e->env.MemoryWrite16(vaddr, value);
}
void dynemu_write_u16(Emulator *e, u32 vaddr, u16 value) {
e->env.MemoryWrite16(vaddr, value);
}
void dynemu_write_u32(Emulator *e, u32 vaddr, u32 value, bool is_big) {
if (is_big) {
e->env.MemoryWrite32_Big(vaddr, value);
} else {
e->env.MemoryWrite32(vaddr, value);
}
void dynemu_write_u32(Emulator *e, u32 vaddr, u32 value) {
e->env.MemoryWrite32(vaddr, value);
}
void dynemu_write_u64(Emulator *e, u32 vaddr, u64 value, bool is_big) {
if (is_big) {
e->env.MemoryWrite64_Big(vaddr, value);
} else {
e->env.MemoryWrite64(vaddr, value);
}
void dynemu_write_u64(Emulator *e, u32 vaddr, u64 value) {
e->env.MemoryWrite64(vaddr, value);
}
void dynemu_read_bytes(Emulator *e, u32 vaddr, u8 *output, u32 size) {
@ -97,8 +79,8 @@ extern "C" {
e->env.MemoryWriteBytes(vaddr, input, size);
}
u32 dynemu_execute(Emulator *e, u32 pc, bool is_big) {
return e->env.Execute(pc, is_big);
u32 dynemu_execute(Emulator *e, u32 pc, bool stub_mode) {
return e->env.Execute(pc, stub_mode);
}
u32 dynemu_read_reg(Emulator *e, u8 reg) {

View file

@ -1,6 +1,5 @@
/* (c) 2025 Wrapper Inc. - The New Emulation Library */
#include <iostream>
#include <fstream>
#include "fmt/format.h"
@ -11,11 +10,15 @@
#include "dynarmic/interface/A32/config.h"
#include "dynarmic/interface/A32/coprocessor.h"
#include "dynarmic/interface/exclusive_monitor.h"
#include "dynarmic/interface/optimization_flags.h"
#include "mcl/assert.hpp"
#define DEBUG 0
#define BLOCK_ALLIGN 0x10000
#define ALIGN_BLOCK(x) ((x + (BLOCK_ALLIGN - 1)) & ~(BLOCK_ALLIGN - 1))
#define EMU_MIN(a,b) ((a) < (b) ? (a) : (b))
#define EMU_MAX(a,b) ((a) > (b) ? (a) : (b))
namespace Dynemu {
struct FlashPartition {
@ -31,25 +34,42 @@ namespace Dynemu {
};
/* 01 - Private */
u8 MyEmulator::find_memory_map_id(u32 vaddr) {
for (int i = 0; i < mem_map.size(); i++) {
if (vaddr >= mem_map[i].start && vaddr < (mem_map[i].start + mem_map[i].size)) return i;
std::optional<u8> MyEmulator::find_memory_map_id(u32 vaddr) {
for (int i = 0; i < mem_map_index; i++) {
if (vaddr >= mem_map[i].start && vaddr < (mem_map[i].start + mem_map[i].size))
return i;
}
std::cerr << fmt::format("Memory map {:#08x} OOB, Emulation cannot continue", vaddr) << std::endl;
throw std::out_of_range("Data abort");
return std::nullopt;
}
bool MyEmulator::is_mmap_exists(u32 vaddr) {
for (int i = 0; i < mem_map.size(); i++) {
if (vaddr >= mem_map[i].start && vaddr < (mem_map[i].start + mem_map[i].size)) return true;
std::optional<u8> MyEmulator::find_mmio_id(u32 vaddr) {
for (int i = 0; i < mmio_index; i++) {
if (vaddr >= mmio_map[i].start && vaddr < (mmio_map[i].start + mmio_map[i].size))
return i;
}
return false;
return std::nullopt;
}
template <std::size_t N>
bool MyEmulator::CheckExecOverridePC(const std::array<u32, N> &list, u32 pc) {
for (int i = 0; i < N; i++) {
if (pc == list[i]) return true;
std::optional<u32> MyEmulator::handle_mmio_read(u32 vaddr, u8 access) {
if (auto id = find_mmio_id(vaddr); id.has_value()) {
#if DEBUG
printf("Handling MMIO READ for 0x%08x\n", vaddr);
#endif
return mmio_map[id.value()].read(vaddr - mmio_map[id.value()].start, access);
}
return std::nullopt;
}
bool MyEmulator::handle_mmio_write(u32 vaddr, u32 value, u8 access) {
if (auto id = find_mmio_id(vaddr); id.has_value()) {
#if DEBUG
printf("Handling MMIO WRITE for 0x%08x\n", vaddr);
#endif
mmio_map[id.value()].write(vaddr - mmio_map[id.value()].start, value, access);
return true;
}
return false;
@ -57,28 +77,19 @@ namespace Dynemu {
/* 02 - Public */
void MyEmulator::CreateMemoryMap(u32 vaddr, u32 size) {
if (!is_mmap_exists(vaddr)) mem_map[map_index++] = {vaddr, size, std::make_unique<u8[]>(size)};
ASSERT_MSG(!find_mmio_id(vaddr).has_value(), "Cannot create memory map on MMIO-mapped addresses");
if (!find_memory_map_id(vaddr).has_value())
mem_map[mem_map_index++] = {vaddr, size, std::make_unique<u8[]>(size)};
}
template <std::size_t N>
void MyEmulator::RoutineReplaceWithBXLR(const std::array<u32, N> &list, bool is_thumb, bool is_big) {
for (int i = 0; i < N; i++) {
if (is_thumb) {
MemoryWrite16(list[i], is_big ? 0x7047 : 0x4770);
} else {
MemoryWrite32(list[i], is_big ? 0x1eff2fe1 : 0xe12fff1e);
}
}
}
size_t MyEmulator::UploadToMemory(std::string filename, std::int64_t dest, u32 size) {
size_t MyEmulator::UploadToMemory(const std::string &filename, std::int64_t dest, u32 size) {
std::ifstream file(filename, std::ios::binary);
u8 buf[2048];
u32 offset = 0;
int to_read = 0;
u32 read_n;
if (!file.is_open()) {
std::cerr << fmt::format("Unable to open {}: {}", filename, std::strerror(errno)) << std::endl;
std::cerr << fmt::format("dynemu: unable to open code file {}: {}", filename, std::strerror(errno)) << std::endl;
return 0;
}
@ -87,28 +98,38 @@ namespace Dynemu {
size_t fileSize = file.tellg();
file.seekg(0, file.beg);
if (size < fileSize && !is_mmap_exists((u32)dest)) {
std::cerr << "Code file too large to fit into memory" << std::endl;
auto mmap_id = find_memory_map_id((u32)dest);
bool fitsOnMemoryMap = !mmap_id.has_value() ? (size >= fileSize) : false;
if (!fitsOnMemoryMap && mmap_id.has_value()) {
u32 max_range = mem_map[mmap_id.value()].start + mem_map[mmap_id.value()].size;
fitsOnMemoryMap = (max_range - dest) >= fileSize;
}
if (!fitsOnMemoryMap) {
std::cerr << "dynemu: code file too large to fit into memory" << std::endl;
return 0;
}
CreateMemoryMap((u32)dest, size);
CreateMemoryMap((u32)dest, ALIGN_BLOCK(size));
while ((size_t)file.tellg() < fileSize) {
to_read = (int)((fileSize - file.tellg()) >= 2048 ? 2048 : (fileSize - file.tellg()));
file.read((char *)buf, to_read);
MemoryWriteBytes((u32)dest + offset, buf, to_read);
offset += to_read;
read_n = EMU_MIN(fileSize - file.tellg(), sizeof(buf));
file.read((char *)buf, read_n);
MemoryWriteBytes((u32)(dest + offset), buf, read_n);
offset += read_n;
}
return fileSize;
} else {
FlashLoader fl{};
u32 codeSize = 0;
file.read((char *)&fl.magic, sizeof(fl.magic));
file.read((char *)&fl.count, sizeof(fl.count));
if (fl.magic != 0x4c46) {
std::cerr << "Invalid FL magic check" << std::endl;
std::cerr << "dynemu: nvalid FL magic check" << std::endl;
return 0;
}
@ -120,11 +141,13 @@ namespace Dynemu {
fl.data.get()[i].data = std::make_unique<u8[]>(fl.data.get()[i].size);
file.read((char *)fl.data.get()[i].data.get(), fl.data.get()[i].size);
CreateMemoryMap(fl.data.get()[i].start_vaddr, fl.data.get()[i].size + ((fl.data.get()[i].size % BLOCK_ALLIGN) ? (BLOCK_ALLIGN - (fl.data.get()[i].size % BLOCK_ALLIGN)) : 0));
codeSize += fl.data.get()[i].size;
CreateMemoryMap(fl.data.get()[i].start_vaddr, ALIGN_BLOCK(fl.data.get()[i].size));
MemoryWriteBytes(fl.data.get()[i].start_vaddr, fl.data.get()[i].data.get(), fl.data.get()[i].size);
}
return 1;
return codeSize;
}
}
@ -151,136 +174,247 @@ namespace Dynemu {
offset += sizeof(fl.data.get()[i].start_vaddr) + sizeof(fl.data.get()[i].size) + fl.data.get()[i].size;
CreateMemoryMap(fl.data.get()[i].start_vaddr, fl.data.get()[i].size + ((fl.data.get()[i].size % BLOCK_ALLIGN) ? (BLOCK_ALLIGN - (fl.data.get()[i].size % BLOCK_ALLIGN)) : 0));
CreateMemoryMap(fl.data.get()[i].start_vaddr, ALIGN_BLOCK(fl.data.get()[i].size));
MemoryWriteBytes(fl.data.get()[i].start_vaddr, fl.data.get()[i].data.get(), fl.data.get()[i].size);
}
return true;
}
void MyEmulator::UploadOSStub(u32 dest, u32 mem_start, u32 mem_size, bool is_big) {
if (is_big) {
void MyEmulator::UploadOSStub(u32 dest, u32 mem_start, u32 mem_size) {
if (big_endian) {
MemoryWriteBytes(dest, OS_LoopLoader_BE.data(), (u32)OS_LoopLoader_BE.size());
MemoryWrite32_Big(dest + MEM_INIT_REG_OFFSET, mem_start);
MemoryWrite32_Big(dest + MEM_INIT_REG_SIZE, mem_size);
} else {
MemoryWriteBytes(dest, OS_LoopLoader.data(), (u32)OS_LoopLoader.size());
MemoryWrite32(dest + MEM_INIT_REG_OFFSET, mem_start);
MemoryWrite32(dest + MEM_INIT_REG_SIZE, mem_size);
}
MemoryWrite32(dest + MEM_INIT_REG_OFFSET, mem_start);
MemoryWrite32(dest + MEM_INIT_REG_SIZE, mem_size);
os_offset = dest;
}
void MyEmulator::CreateMMIO(u32 addr_start, u32 addr_size, const MMIO_Read &read, const MMIO_Write &write) {
ASSERT_MSG(!find_memory_map_id(addr_start).has_value(), "Cannot create MMIO map on memory-mapped address");
if (!find_mmio_id(addr_start).has_value())
mmio_map[mmio_index++] = {addr_start, addr_size, read, write};
}
bool MyEmulator::PreCodeReadHook(bool is_thumb, u32 pc, Dynarmic::A32::IREmitter& ir) {
#if DEBUG
std::cout << fmt::format("executing at {:#08x}", pc) << std::endl;
std::cout << fmt::format("REG P0 {:#08x}", cpu->Regs()[0]) << std::endl;
std::cout << fmt::format("REG P1 {:#08x}", cpu->Regs()[1]) << std::endl;
std::cout << fmt::format("REG P2 {:#08x}", cpu->Regs()[2]) << std::endl;
std::cout << fmt::format("REG P12 {:#08x}", cpu->Regs()[12]) << std::endl;
std::cout << fmt::format("REG P15 {:#08x}", cpu->Regs()[15]) << std::endl;
std::cout << fmt::format("executing at {:#010x}", pc) << std::endl;
std::cout << fmt::format("REG P0 {:#010x}", cpu->Regs()[0]) << std::endl;
std::cout << fmt::format("REG P1 {:#010x}", cpu->Regs()[1]) << std::endl;
std::cout << fmt::format("REG P2 {:#010x}", cpu->Regs()[2]) << std::endl;
std::cout << fmt::format("REG P12 {:#010x}", cpu->Regs()[12]) << std::endl;
std::cout << fmt::format("REG P15 {:#010x}", cpu->Regs()[15]) << std::endl;
#endif
if (pc == (os_offset + ROUTE_EXIT)) cpu->HaltExecution();
thumb_mode = is_thumb;
if (stub_mode && pc == (os_offset + ROUTE_EXIT)) {
return false;
}
return true;
return true;
}
std::optional<u32> MyEmulator::MemoryReadCode(u32 vaddr) {
u32 temp;
if (auto ret = handle_mmio_read(vaddr, 4); ret.has_value()) {
return ret.value();
} else {
if (!find_memory_map_id(vaddr).has_value()) {
std::cerr << fmt::format("dynemu: Invalid memory fetch at {:#010x}", vaddr) << std::endl;
return std::nullopt;
}
if (big_endian && thumb_mode) {
temp = MemoryRead8(vaddr + 1);
temp |= MemoryRead8(vaddr) << 8;
temp |= MemoryRead8(vaddr + 3) << 16;
temp |= MemoryRead8(vaddr + 2) << 24;
return temp;
}
}
return MemoryRead32(vaddr);
}
u8 MyEmulator::MemoryRead8(u32 vaddr) {
if (auto ret = handle_mmio_read(vaddr, 1); ret.has_value())
return ret.value();
#if DEBUG
if (cpu->IsExecuting()) printf("Reading at 0x%08x\n", vaddr);
#endif
try {
u8 mem_now = find_memory_map_id(vaddr);
return mem_map[mem_now].data.get()[vaddr - mem_map[mem_now].start];
} catch (std::out_of_range) {
cpu->HaltExecution(Dynarmic::HaltReason::MemoryAbort);
return 0xff; // Undefined behavior can occur
}
}
void MyEmulator::MemoryReadBytes(u32 vaddr, u8 *output, u32 size) {
u8 mem_now = find_memory_map_id(vaddr);
ASSERT(((vaddr - mem_map[mem_now].start) + size) <= mem_map[mem_now].size);
memcpy(output, mem_map[mem_now].data.get() + (vaddr - mem_map[mem_now].start), size);
auto mmap = find_memory_map_id(vaddr);
if (!mmap.has_value()) {
std::cerr << fmt::format("dynemu: Invalid memory read at {:#010x}", vaddr) << std::endl;
cpu->HaltExecution(Dynarmic::HaltReason::MemoryAbort);
return -1; // Undefined behavior can occur
}
return mem_map[mmap.value()].data.get()[vaddr - mem_map[mmap.value()].start];
}
u16 MyEmulator::MemoryRead16(u32 vaddr) {
return u16(MemoryRead8(vaddr)) | u16(MemoryRead8(vaddr + 1)) << 8;
if (auto ret = handle_mmio_read(vaddr, 2); ret.has_value())
return ret.value();
auto mmap = find_memory_map_id(vaddr);
if (!mmap.has_value()) {
std::cerr << fmt::format("dynemu: Invalid memory read at {:#010x}", vaddr) << std::endl;
cpu->HaltExecution(Dynarmic::HaltReason::MemoryAbort);
return -1; // Undefined behavior can occur
}
u16 temp;
if (big_endian) {
temp = MemoryRead8(vaddr + 1);
temp |= MemoryRead8(vaddr) << 8;
} else {
temp = MemoryRead8(vaddr);
temp |= MemoryRead8(vaddr + 1) << 8;
}
return temp;
}
u32 MyEmulator::MemoryRead32(u32 vaddr) {
return u32(MemoryRead16(vaddr)) | u32(MemoryRead16(vaddr + 2)) << 16;
if (auto ret = handle_mmio_read(vaddr, 4); ret.has_value())
return ret.value();
auto mmap = find_memory_map_id(vaddr);
if (!mmap.has_value()) {
std::cerr << fmt::format("dynemu: Invalid memory read at {:#010x}", vaddr) << std::endl;
cpu->HaltExecution(Dynarmic::HaltReason::MemoryAbort);
return -1; // Undefined behavior can occur
}
u32 temp;
if (big_endian) {
temp = MemoryRead8(vaddr + 3);
temp |= MemoryRead8(vaddr + 2) << 8;
temp |= MemoryRead8(vaddr + 1) << 16;
temp |= MemoryRead8(vaddr) << 24;
} else {
temp = MemoryRead8(vaddr);
temp |= MemoryRead8(vaddr + 1) << 8;
temp |= MemoryRead8(vaddr + 2) << 16;
temp |= MemoryRead8(vaddr + 3) << 24;
}
return temp;
}
u64 MyEmulator::MemoryRead64(u32 vaddr) {
return u64(MemoryRead32(vaddr)) | u64(MemoryRead32(vaddr + 4)) << 32;
}
u16 MyEmulator::MemoryRead16_Big(u32 vaddr) {
return u16(MemoryRead8(vaddr + 1)) | u16(MemoryRead8(vaddr)) << 8;
}
u32 MyEmulator::MemoryRead32_Big(u32 vaddr) {
return u32(MemoryRead16_Big(vaddr + 2)) | u32(MemoryRead16_Big(vaddr)) << 16;
}
u64 MyEmulator::MemoryRead64_Big(u32 vaddr) {
return u64(MemoryRead32_Big(vaddr + 4)) | u64(MemoryRead32_Big(vaddr)) << 32;
ASSERT_FALSE("target is not a 64-bit device");
}
void MyEmulator::MemoryWrite8(u32 vaddr, u8 value) {
if (handle_mmio_write(vaddr, value, 1))
return;
#if DEBUG
if (cpu->IsExecuting()) printf("Writing at 0x%08x 0x%02x\n", vaddr, value);
#endif
try {
u8 mem_now = find_memory_map_id(vaddr);
mem_map[mem_now].data.get()[vaddr - mem_map[mem_now].start] = value;
} catch (std::out_of_range) {
cpu->HaltExecution(Dynarmic::HaltReason::MemoryAbort);
}
}
void MyEmulator::MemoryWriteBytes(u32 vaddr, const u8 *input, u32 size) {
u8 mem_now = find_memory_map_id(vaddr);
ASSERT(((vaddr - mem_map[mem_now].start) + size) <= mem_map[mem_now].size);
memcpy(mem_map[mem_now].data.get() + (vaddr - mem_map[mem_now].start), input, size);
auto mmap = find_memory_map_id(vaddr);
if (!mmap.has_value()) {
std::cerr << fmt::format("dynemu: Invalid memory write at {:#010x}", vaddr) << std::endl;
cpu->HaltExecution(Dynarmic::HaltReason::MemoryAbort);
return; // Undefined behavior can occur
}
mem_map[mmap.value()].data.get()[vaddr - mem_map[mmap.value()].start] = value;
}
void MyEmulator::MemoryWrite16(u32 vaddr, u16 value) {
MemoryWrite8(vaddr, u8(value));
MemoryWrite8(vaddr + 1, u8(value >> 8));
if (handle_mmio_write(vaddr, value, 2))
return;
auto mmap = find_memory_map_id(vaddr);
if (!mmap.has_value()) {
std::cerr << fmt::format("dynemu: Invalid memory write at {:#010x}", vaddr) << std::endl;
cpu->HaltExecution(Dynarmic::HaltReason::MemoryAbort);
return; // Undefined behavior can occur
}
if (big_endian) {
MemoryWrite8(vaddr, value >> 8);
MemoryWrite8(vaddr + 1, value);
} else {
MemoryWrite8(vaddr, value);
MemoryWrite8(vaddr + 1, value >> 8);
}
}
void MyEmulator::MemoryWrite32(u32 vaddr, u32 value) {
MemoryWrite16(vaddr, u16(value));
MemoryWrite16(vaddr + 2, u16(value >> 16));
if (handle_mmio_write(vaddr, value, 4))
return;
auto mmap = find_memory_map_id(vaddr);
if (!mmap.has_value()) {
std::cerr << fmt::format("dynemu: Invalid memory write at {:#010x}", vaddr) << std::endl;
cpu->HaltExecution(Dynarmic::HaltReason::MemoryAbort);
return; // Undefined behavior can occur
}
if (big_endian) {
MemoryWrite8(vaddr, value >> 24);
MemoryWrite8(vaddr + 1, value >> 16);
MemoryWrite8(vaddr + 2, value >> 8);
MemoryWrite8(vaddr + 3, value);
} else {
MemoryWrite8(vaddr, value);
MemoryWrite8(vaddr + 1, value >> 8);
MemoryWrite8(vaddr + 2, value >> 16);
MemoryWrite8(vaddr + 3, value >> 24);
}
}
void MyEmulator::MemoryWrite64(u32 vaddr, u64 value) {
MemoryWrite32(vaddr, u32(value));
MemoryWrite32(vaddr + 4, u32(value >> 32));
ASSERT_FALSE("target is not a 64-bit device");
}
void MyEmulator::MemoryWrite16_Big(u32 vaddr, u16 value) {
MemoryWrite8(vaddr + 1, u8(value));
MemoryWrite8(vaddr, u8(value >> 8));
void MyEmulator::MemoryReadBytes(u32 vaddr, u8 *output, u32 size) {
ASSERT_MSG(!find_mmio_id(vaddr).has_value(), "You're trying to read a MMIO mapped address. Use coresponding MMIO routines instead.");
auto mmap = find_memory_map_id(vaddr);
ASSERT_MSG(mmap.has_value(), "Invalid memory read");
u32 maddr = vaddr - mem_map[mmap.value()].start;
ASSERT_MSG((maddr + size) <= mem_map[mmap.value()].size, "Invalid memory read");
memcpy(output, mem_map[mmap.value()].data.get() + maddr, size);
}
void MyEmulator::MemoryWrite32_Big(u32 vaddr, u32 value) {
MemoryWrite16_Big(vaddr + 2, u16(value));
MemoryWrite16_Big(vaddr, u16(value >> 16));
void MyEmulator::MemoryWriteBytes(u32 vaddr, const u8 *input, u32 size) {
ASSERT_MSG(!find_mmio_id(vaddr).has_value(), "You're trying to write a MMIO mapped address. Use coresponding MMIO routines instead.");
auto mmap = find_memory_map_id(vaddr);
ASSERT_MSG(mmap.has_value(), "Invalid memory write");
u32 maddr = vaddr - mem_map[mmap.value()].start;
ASSERT_MSG((maddr + size) <= mem_map[mmap.value()].size, "Invalid memory write");
memcpy(mem_map[mmap.value()].data.get() + maddr, input, size);
}
void MyEmulator::MemoryWrite64_Big(u32 vaddr, u64 value) {
MemoryWrite32_Big(vaddr + 4, u32(value));
MemoryWrite32_Big(vaddr, u32(value >> 32));
}
u32 MyEmulator::Execute(u32 pc, bool is_big) {
cpu->Regs()[14] = os_offset + ROUTE_EXIT;
u32 MyEmulator::Execute(u32 pc, bool stub_mode) {
if (stub_mode)
cpu->Regs()[14] = os_offset + ROUTE_EXIT;
this->stub_mode = stub_mode;
cpu->Regs()[15] = pc & ~1;
cpu->SetCpsr(((pc & 1) ? 0x30 : 0x1d0) | (is_big ? 0x200 : 0x0));
cpu->SetCpsr((pc & 1) ? 0x30 : 0x1d0);
Dynarmic::HaltReason returnCode = cpu->Run();
cpu->ClearCache();
@ -288,6 +422,230 @@ namespace Dynemu {
return cpu->Regs()[0];
}
std::future<u32> MyEmulator::Spawn(u32 pc, bool stub_mode) {
return std::async(std::launch::async, [this, pc, stub_mode] {
return Execute(pc, stub_mode);
});
}
std::optional<Callback> CP14::CompileInternalOperation(bool two, unsigned opc1, CoprocReg CRd, CoprocReg CRn, CoprocReg CRm, unsigned opc2) {
return std::nullopt;
}
// as MCR (Write Coprocessor), when output is u32 pointer, write to data
CallbackOrAccessOneWord CP14::CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm, unsigned opc2) {
// const std::lock_guard<std::mutex> lock(dcc_lock);
switch (state.dcc_type) {
case DCCType::ARM7_ARM9:
if (!two && opc1 == 0 && CRm == CoprocReg::C0 && opc2 == 0) {
switch (CRn) {
case CoprocReg::C0:
return &state.dcc_status;
case CoprocReg::C1:
return Callback{
[](void* cp14_state, std::uint32_t write_value, std::uint32_t word2_unused) -> std::uint64_t {
CP14State& state = *reinterpret_cast<CP14State*>(cp14_state);
#if DEBUG
printf("DEBUG WRITE: 0x%08x\n", write_value);
#endif
state.dcc_write_data = write_value;
state.dcc_status |= 0b10;
return 0;
},
reinterpret_cast<void*>(&state),
};
default:
break;
}
}
break;
case DCCType::ARM11_CORTEX:
if (!two && opc1 == 0 && CRn == CoprocReg::C0 && opc2 == 2) {
switch (CRm) {
case CoprocReg::C0:
return &dummy_value;
case CoprocReg::C1:
return &dummy_value;
case CoprocReg::C2:
return &state.dcc_status;
case CoprocReg::C3:
return Callback{
[](void* cp14_state, std::uint32_t write_value, std::uint32_t word2_unused) -> std::uint64_t {
CP14State& state = *reinterpret_cast<CP14State*>(cp14_state);
#if DEBUG
printf("DEBUG WRITE: 0x%08x\n", write_value);
#endif
state.dcc_write_data = write_value;
state.dcc_status |= (0b01 << 29);
return 0;
},
reinterpret_cast<void*>(&state),
};
default:
break;
}
}
break;
}
std::cerr << "dynemu: something is wrong with CP14 state" << std::endl;
return CallbackOrAccessOneWord{};
}
CallbackOrAccessTwoWords CP14::CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) {
return CallbackOrAccessTwoWords{};
}
// as MRC (Read Coprocessor), when output is u32 pointer, read from data
CallbackOrAccessOneWord CP14::CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm, unsigned opc2) {
// const std::lock_guard<std::mutex> lock(dcc_lock);
switch (state.dcc_type) {
case DCCType::ARM7_ARM9:
if (!two && opc1 == 0 && CRm == CoprocReg::C0 && opc2 == 0) {
switch (CRn) {
case CoprocReg::C0:
return &state.dcc_status;
case CoprocReg::C1:
return Callback{
[](void* cp14_state, std::uint32_t word1_unused, std::uint32_t word2_unused) -> std::uint64_t {
CP14State& state = *reinterpret_cast<CP14State*>(cp14_state);
#if DEBUG
printf("DEBUG READ: 0x%08x\n", state.dcc_read_data);
#endif
u32 temp = state.dcc_read_data;
state.dcc_status &= ~0b01;
return temp;
},
reinterpret_cast<void*>(&state),
};
default:
break;
}
}
break;
case DCCType::ARM11_CORTEX:
if (!two && opc1 == 0 && CRn == CoprocReg::C0 && opc2 == 2) {
switch (CRm) {
case CoprocReg::C0:
return Callback{
[](void* cp14_state, std::uint32_t word1_unused, std::uint32_t word2_unused) -> std::uint64_t {
CP14State& state = *reinterpret_cast<CP14State*>(cp14_state);
#if DEBUG
printf("DEBUG READ: 0x%08x\n", state.dcc_read_data);
#endif
u32 temp = state.dcc_read_data;
state.dcc_status &= ~(0b10 << 29);
return temp;
},
reinterpret_cast<void*>(&state),
};
case CoprocReg::C1:
return &dummy_value;
case CoprocReg::C2:
return &state.dcc_status;
case CoprocReg::C3:
return &dummy_value;
default:
break;
}
}
break;
}
std::cerr << "dynemu: something is wrong with CP14 state" << std::endl;
return CallbackOrAccessOneWord{};
}
CallbackOrAccessTwoWords CP14::CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) {
return CallbackOrAccessTwoWords{};
}
std::optional<Callback> CP14::CompileLoadWords(bool two, bool long_transfer, CoprocReg CRd, std::optional<std::uint8_t> option) {
return std::nullopt;
}
std::optional<Callback> CP14::CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd, std::optional<std::uint8_t> option) {
return std::nullopt;
}
CP14::CP14(DCCType dcc_type, CP14State &state) : state(state) {
switch (dcc_type) {
case DCCType::ARM7_ARM9:
state.dcc_status = 0b01;
break;
case DCCType::ARM11_CORTEX:
state.dcc_status = 0b10 << 29;
break;
}
state.dcc_type = dcc_type;
state.dcc_write_data = 0;
state.dcc_read_data = 0;
}
void CP14::Send(u32 value) {
const std::lock_guard<std::mutex> lock(dcc_lock);
switch (state.dcc_type) {
case DCCType::ARM7_ARM9:
while (state.dcc_status & 0b01);
state.dcc_read_data = value;
state.dcc_status |= 0b01;
break;
case DCCType::ARM11_CORTEX:
while (state.dcc_status & (0b10 << 29));
state.dcc_read_data = value;
state.dcc_status |= (0b10 << 29);
break;
}
}
u32 CP14::Get() {
const std::lock_guard<std::mutex> lock(dcc_lock);
u32 temp;
switch (state.dcc_type) {
case DCCType::ARM7_ARM9:
while (!(state.dcc_status & 0b10));
temp = state.dcc_write_data;
state.dcc_status &= ~0b10;
break;
case DCCType::ARM11_CORTEX:
while (!(state.dcc_status & (0b01 << 29)));
temp = state.dcc_write_data;
state.dcc_status &= ~(0b01 << 29);
break;
}
return temp;
}
class CP15 final : public Dynarmic::A32::Coprocessor {
using Callback = Dynarmic::A32::Coprocessor::Callback;
using CoprocReg = Dynarmic::A32::CoprocReg;
@ -341,12 +699,19 @@ namespace Dynemu {
}
};
Emulator::Emulator() {
Emulator::Emulator(DCCType dcc_type, bool no_opts, bool big_endian) : env(big_endian) {
Dynarmic::A32::UserConfig user_config;
user_config.callbacks = &env;
user_config.coprocessors[15] = std::make_shared<CP15>();
cp14 = std::make_shared<CP14>(dcc_type, cp14_state);
user_config.enable_cycle_counting = false;
if (no_opts)
user_config.optimizations = Dynarmic::no_optimizations;
user_config.callbacks = &env;
user_config.check_halt_on_memory_access = true;
user_config.coprocessors[14] = cp14;
user_config.coprocessors[15] = std::make_shared<CP15>();
user_config.arch_version = Dynarmic::A32::ArchVersion::v7;
mon = std::make_unique<Dynarmic::ExclusiveMonitor>(1);

View file

@ -0,0 +1,6 @@
pybind11_add_module(dynemup dynemu.cpp)
add_dependencies(dynemup dynemu)
target_link_libraries(dynemup PUBLIC dynemu)
install(TARGETS dynemup LIBRARY DESTINATION ./dynemup)

170
src/pybind/dynemu.cpp Normal file
View file

@ -0,0 +1,170 @@
#include <pybind11/pybind11.h>
#include <pybind11/functional.h>
#include <pybind11/pytypes.h>
#include <pybind11/stl.h>
#include <pybind11/native_enum.h>
#include "dynemu/sys_emulator.hpp"
namespace py = pybind11;
struct Emu {
Emu(DCCType dcc_type = DCCType::ARM7_ARM9, bool no_opts = false, bool big_endian = false) : emu(dcc_type, no_opts, big_endian) { }
void memory_map(u32 vaddr, u32 size) {
emu.env.CreateMemoryMap(vaddr, size);
}
void upload_file(const std::string &filename, u32 vaddr, u32 size) {
emu.env.UploadToMemory(filename, vaddr, size);
}
void upload_fls_memory(py::bytes data) {
py::buffer_info info(py::buffer(data).request());
emu.env.UploadFLSFromBytes((const u8 *)info.ptr);
}
void upload_fls_memory(py::bytearray data) {
py::buffer_info info(py::buffer(data).request());
emu.env.UploadFLSFromBytes((const u8 *)info.ptr);
}
void upload_memstub(u32 vaddr, u32 mem_start = 0xa0000000, u32 mem_size = 0x2000000) {
emu.env.UploadOSStub(vaddr, mem_start, mem_size);
}
void upload_memstub(u32 mem_start = 0xa0000000, u32 mem_size = 0x2000000) {
emu.env.UploadOSStub(mem_start, mem_size);
}
void mmio(u32 vaddr, u32 size, const MMIO_Read &read, const MMIO_Write &write) {
emu.env.CreateMMIO(vaddr, size, read, write);
}
void mmio(u32 vaddr, u32 size, const MMIO_Read &read) {
emu.env.CreateMMIO(vaddr, size, read);
}
void mmio(u32 vaddr, u32 size, const MMIO_Write &write) {
emu.env.CreateMMIO(vaddr, size, [](u64, u8) -> u64 {return -1;}, write);
}
void mmio(u32 vaddr, u32 size, const MMIO_Write &write, const MMIO_Read &read) {
emu.env.CreateMMIO(vaddr, size, read, write);
}
void mmio(u32 vaddr, u32 size) {
emu.env.CreateMMIO(vaddr, size);
}
u8 read_u8(u32 vaddr) {
return emu.env.MemoryRead8(vaddr);
}
u16 read_u16(u32 vaddr) {
return emu.env.MemoryRead16(vaddr);
}
u32 read_u32(u32 vaddr) {
return emu.env.MemoryRead32(vaddr);
}
u64 read_u64(u32 vaddr) {
return emu.env.MemoryRead64(vaddr);
}
void write_u8(u32 vaddr, u8 value) {
emu.env.MemoryWrite8(vaddr, value);
}
void write_u16(u32 vaddr, u16 value) {
emu.env.MemoryWrite16(vaddr, value);
}
void write_u32(u32 vaddr, u32 value) {
emu.env.MemoryWrite32(vaddr, value);
}
void write_u64(u32 vaddr, u64 value) {
emu.env.MemoryWrite64(vaddr, value);
}
py::bytes read_bytes(u32 vaddr, u32 size) {
auto buf = std::vector<u8>(size);
emu.env.MemoryReadBytes(vaddr, buf);
return py::bytes((const char *)buf.data(), buf.size());
}
void write_bytes(u32 vaddr, py::bytes data) {
py::buffer_info info(py::buffer(data).request());
emu.env.MemoryWriteBytes(vaddr, (const u8 *)info.ptr, info.size);
}
void write_bytes(u32 vaddr, py::bytearray data) {
py::buffer_info info(py::buffer(data).request());
emu.env.MemoryWriteBytes(vaddr, (const u8 *)info.ptr, info.size);
}
u32 execute(u32 pc, bool stub_mode=true) {
return emu.env.Execute(pc, stub_mode);
}
u32 get_reg(u8 reg_num) {
return emu.env.cpu->Regs()[reg_num];
}
void set_reg(u8 reg_num, u32 value) {
emu.env.cpu->Regs()[reg_num] = value;
}
u32 dcc_read() {
return emu.cp14->Get();
}
void dcc_write(u32 value) {
emu.cp14->Send(value);
}
Dynemu::Emulator emu;
};
PYBIND11_MODULE(dynemup, module, py::mod_gil_not_used()) {
module.doc() = "Dynemu";
py::class_<Emu> emu(module, "Dynemu");
py::native_enum<DCCType>(emu, "DCCType", "enum.Enum")
.value("ARM7_ARM9", DCCType::ARM7_ARM9)
.value("ARM11_CORTEX", DCCType::ARM11_CORTEX)
.export_values()
.finalize();
emu.def(py::init<DCCType, bool, bool>(), py::arg("dcc_type") = DCCType::ARM7_ARM9, py::arg("no_opts") = false, py::arg("big_endian") = false)
.def("memory_map", &Emu::memory_map, py::arg("vaddr"), py::arg("size"))
.def("upload_file", &Emu::upload_file, py::arg("filename"), py::arg("vaddr"), py::arg("size"))
.def("upload_fls_memory", static_cast<void (Emu::*)(py::bytes)>(&Emu::upload_fls_memory), py::arg("data"))
.def("upload_fls_memory", static_cast<void (Emu::*)(py::bytearray)>(&Emu::upload_fls_memory), py::arg("data"))
.def("upload_memstub", static_cast<void (Emu::*)(u32, u32, u32)>(&Emu::upload_memstub), py::arg("vaddr"), py::arg("mem_start") = 0xa0000000, py::arg("mem_size") = 0x2000000)
.def("upload_memstub", static_cast<void (Emu::*)(u32, u32)>(&Emu::upload_memstub), py::arg("mem_start") = 0xa0000000, py::arg("mem_size") = 0x2000000)
.def("mmio", static_cast<void (Emu::*)(u32, u32)>(&Emu::mmio), py::arg("vaddr"), py::arg("size"))
.def("mmio", static_cast<void (Emu::*)(u32, u32, const MMIO_Read &)>(&Emu::mmio), py::arg("vaddr"), py::arg("size"), py::arg("read"))
.def("mmio", static_cast<void (Emu::*)(u32, u32, const MMIO_Write &)>(&Emu::mmio), py::arg("vaddr"), py::arg("size"), py::arg("write"))
.def("mmio", static_cast<void (Emu::*)(u32, u32, const MMIO_Read &, const MMIO_Write &)>(&Emu::mmio), py::arg("vaddr"), py::arg("size"), py::arg("read"), py::arg("write"))
.def("mmio", static_cast<void (Emu::*)(u32, u32, const MMIO_Write &, const MMIO_Read &)>(&Emu::mmio), py::arg("vaddr"), py::arg("size"), py::arg("write"), py::arg("read"))
.def("read_u8", &Emu::read_u8, py::arg("vaddr"))
.def("read_u16", &Emu::read_u16, py::arg("vaddr"))
.def("read_u32", &Emu::read_u32, py::arg("vaddr"))
.def("read_u64", &Emu::read_u64, py::arg("vaddr"))
.def("write_u8", &Emu::write_u8, py::arg("vaddr"), py::arg("value"))
.def("write_u16", &Emu::write_u16, py::arg("vaddr"), py::arg("value"))
.def("write_u32", &Emu::write_u32, py::arg("vaddr"), py::arg("value"))
.def("write_u64", &Emu::write_u64, py::arg("vaddr"), py::arg("value"))
.def("read_bytes", &Emu::read_bytes, py::arg("vaddr"), py::arg("size"))
.def("write_bytes", static_cast<void (Emu::*)(u32, py::bytes)>(&Emu::write_bytes), py::arg("vaddr"), py::arg("data"))
.def("write_bytes", static_cast<void (Emu::*)(u32, py::bytearray)>(&Emu::write_bytes), py::arg("vaddr"), py::arg("data"))
.def("execute", &Emu::execute, py::arg("pc"), py::arg("stub_mode") = true)
.def("get_reg", &Emu::get_reg, py::arg("reg_num"))
.def("set_reg", &Emu::set_reg, py::arg("reg_num"), py::arg("value"))
.def("dcc_read", &Emu::dcc_read)
.def("dcc_write", &Emu::dcc_write, py::arg("value"));
}