Stable checkpoint: game reaches playable 3D gameplay on ARM64
Verified live on a Pixel 6a: the game passes its EULA, loads the prologue and renders real 3D gameplay, with zero heap exhaustion, zero faults and zero rejected frees over a full session. Root causes fixed in this state, each backed by a measurement (details and the list of refuted theories live in ARM64_TRANSLATION_LAYER.md): * JNI varargs float->double promotion. C promotes float to double in any varargs call and every Call*Method form is varargs, so reading one 4-byte slot yielded the double's always-zero low half. EVERY float argument passed to Java was silently becoming 0; text was just where it showed. * GuestHeap ~4x memory overhead. Power-of-two size classes carving the full class, plus segregated free lists that could never share memory between sizes. Reworked to exact sizing with O(log n) best-fit reuse and splitting (deliberately not a linear scan - this allocator already had an O(n) perf cliff in its history). Peak live now 207MB against the real A9's 199MB, fragmentation ~2.5MB. Also fixed: realloc reading past the old block on shrink, a 32-bit overflow in calloc, and drifting payload alignment. * Unbounded FMOD fake-handle leak into the never-freeing permanent arena, which is why enlarging that arena had not helped. * Frame presentation, corrected against A9 ground truth: the real frame has three default-framebuffer colour clears and ONE present at the end; this engine had been presenting on each of them. Load-time acceleration (zlib_accel.cpp): host zlib now serves inflate and crc32, the latter measured by the block profiler as the single hottest guest routine at 17.7%. Streams are only taken over when this layer saw their own inflateInit2_, so unknown streams (libpng's, among others) still run the original emulated path. name_lookup_accel.cpp is present but its hook is NOT registered - it crashed on bad assumptions about guest table lifetime and is kept as a starting point, with both mistakes recorded in its comments. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+11
@@ -13,3 +13,14 @@
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
|
||||
# IDA databases - 148MB for libapp.so.i64 alone, and regenerable from the .so
|
||||
*.i64
|
||||
*.id0
|
||||
*.id1
|
||||
*.nam
|
||||
*.til
|
||||
# trace_agent is built by its own build.sh, outside Gradle
|
||||
trace_agent/build/
|
||||
# ad-hoc device capture logs
|
||||
device_run_*.log
|
||||
|
||||
@@ -2,11 +2,69 @@ cmake_minimum_required(VERSION 3.22.1)
|
||||
|
||||
project("mpcore")
|
||||
|
||||
# Force an optimized build regardless of the Android Gradle Plugin's own
|
||||
# build variant (2026-09-05, "why is this ~100x slower than native"
|
||||
# investigation - see ARM64_TRANSLATION_LAYER.md). Confirmed live via the
|
||||
# actual generated build.ninja: neither this project's own C++ (guest_engine.cpp
|
||||
# et al.) nor vendored Unicorn/QEMU-TCG's own C sources ever received an -O
|
||||
# flag - AGP's external CMake integration never sets CMAKE_BUILD_TYPE here,
|
||||
# and CMake's own default (empty CMAKE_BUILD_TYPE) means no per-build-type
|
||||
# flags get added at all, i.e. plain -O0. Two separate, targeted dispatch-
|
||||
# overhead fixes made zero measurable difference to a tight guest hot loop's
|
||||
# wall-clock rate - this is why: a CPU emulator's performance is dominated
|
||||
# by how well the COMPILER optimizes its OWN interpreter/JIT dispatch, and
|
||||
# -O0 there dwarfs any micro-optimization in the C++ source. RelWithDebInfo
|
||||
# (not plain Release) keeps -g/debug info for native crash symbolication -
|
||||
# this project's own crash/fault diagnostics throughout this session depend
|
||||
# on it. FORCE + setting it before add_subdirectory so Unicorn's nested
|
||||
# CMake build (which does NOT set its own CMAKE_BUILD_TYPE) inherits it too.
|
||||
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "" FORCE)
|
||||
|
||||
# ---- Vendored Unicorn Engine (ARM32-on-ARM64 CPU-emulation core) ----
|
||||
# Only the ARM (AArch32) backend is built - this project never needs any of
|
||||
# Unicorn's other target architectures. See ARM64_TRANSLATION_LAYER.md for
|
||||
# why Unicorn specifically (embeddable-by-design, MIT-licensed, QEMU-TCG-
|
||||
# derived JIT) was picked over a full QEMU-user-mode process.
|
||||
set(UNICORN_ARCH "arm" CACHE STRING "" FORCE)
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||
set(UNICORN_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(UNICORN_INSTALL OFF CACHE BOOL "" FORCE)
|
||||
set(UNICORN_FUZZ OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory(third_party/unicorn)
|
||||
|
||||
add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||
main.cpp
|
||||
game_lifecycle_stubs.cpp
|
||||
game_lifecycle_stubs_extra.cpp
|
||||
game_lifecycle_stubs_extra2.cpp
|
||||
util/util.cpp
|
||||
util/armhook.cpp)
|
||||
util/armhook.cpp
|
||||
emu/guest_heap.cpp
|
||||
emu/guest_engine.cpp
|
||||
emu/import_shims.cpp
|
||||
emu/pthread_shim.cpp
|
||||
emu/jni_shim.cpp
|
||||
emu/gles_shim.cpp
|
||||
emu/libc_shims.cpp
|
||||
emu/rtti_shims.cpp
|
||||
emu/fmod_shims.cpp
|
||||
emu/zlib_accel.cpp
|
||||
emu/name_lookup_accel.cpp
|
||||
emu/profiler.cpp
|
||||
emu/guest_trace.cpp
|
||||
emu/tcg_bench.cpp
|
||||
emu/ostream_repro_test.cpp)
|
||||
|
||||
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
|
||||
third_party/unicorn/include)
|
||||
|
||||
target_compile_features(${CMAKE_PROJECT_NAME} PRIVATE cxx_std_17)
|
||||
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}
|
||||
unicorn
|
||||
android
|
||||
log)
|
||||
log
|
||||
GLESv2
|
||||
z
|
||||
EGL
|
||||
jnigraphics)
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
#include "fmod_shims.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t kFmodOk = 0;
|
||||
|
||||
// 2026-09-06 (ARM64_TRANSLATION_LAYER.md "PC wanders into .data/.bss"
|
||||
// investigation): FakeHandle used to hand out 16 raw AllocPermanent bytes
|
||||
// with NO vtable pointer set (i.e. a real 0 at offset 0). That's fine for
|
||||
// the handful of calls this file itself makes on a fake object (none - it
|
||||
// never dereferences its own handles), but real guest code that later
|
||||
// calls a genuinely virtual method through one of these handles (confirmed
|
||||
// live: SoundManager::SetVolume calling EventCategory::setVolume via
|
||||
// vtable+0x20 on a getCategory() fake handle) reads *(0+0x20) - the guest's
|
||||
// own ELF header's e_shoff field, a real but unrelated large integer - and
|
||||
// jumps into it as code, landing deep in .data with no diagnostic trail
|
||||
// pointing back here. Give every fake handle a real, generous, callable
|
||||
// vtable whose slots all just return 0/kFmodOk instead - same pattern this
|
||||
// project already uses for RTTI/libc++ facet objects it doesn't fully
|
||||
// reimplement (see rtti_shims.cpp's ctype<char>/num_put<char> vtables).
|
||||
// Built once, shared by every FakeHandle() call (all real FMOD interfaces
|
||||
// this project stubs - EventSystem, Event, EventCategory, ChannelGroup,
|
||||
// DSP, Sound, Channel - are small enough that no real vtable comes close
|
||||
// to 64 slots).
|
||||
GuestAddr g_fmodNoOpVtable = 0;
|
||||
GuestAddr FmodNoOpVtable(GuestEngine& eng) {
|
||||
if (g_fmodNoOpVtable) return g_fmodNoOpVtable;
|
||||
constexpr int kSlots = 64;
|
||||
GuestAddr vtable = eng.AllocPermanent((uint32_t)kSlots * 4);
|
||||
for (int i = 0; i < kSlots; i++) {
|
||||
GuestAddr stub = eng.AllocCodeStub(
|
||||
[](uc_engine* uc, uint64_t, uint32_t, void*) {
|
||||
uint32_t zero = 0;
|
||||
uc_reg_write(uc, UC_ARM_REG_R0, &zero);
|
||||
},
|
||||
nullptr);
|
||||
if (vtable && stub) memcpy(eng.G2H(vtable + (uint32_t)i * 4), &stub, 4);
|
||||
}
|
||||
g_fmodNoOpVtable = vtable;
|
||||
return vtable;
|
||||
}
|
||||
// FIX (2026-09-19): this used to call AllocPermanent(16) on EVERY invocation.
|
||||
// AllocPermanent is a bump allocator that never frees (by design - see its
|
||||
// own comment), while FakeHandle is called from every FMOD factory/getter
|
||||
// shim below (EventSystem_Create, getEvent, getChannel, getSound, ...), which
|
||||
// the game hits continuously while loading a level. That is an unbounded
|
||||
// leak: confirmed live on the Pixel 6a, where a prologue load drained the
|
||||
// control arena and then logged "control arena exhausted (requested 16 bytes,
|
||||
// 0 remaining)" roughly 120 times a second, indefinitely. Raising the arena
|
||||
// size did not help and could not have - a leak is not a capacity problem.
|
||||
//
|
||||
// These handles are interchangeable opaque dummies: their only content is the
|
||||
// shared no-op vtable pointer, and every shim that receives one ignores it
|
||||
// and returns kFmodOk. So they are recycled from a fixed pool allocated once.
|
||||
// The pool keeps them DISTINCT rather than returning one singleton, because
|
||||
// guest code may legitimately compare two handles for inequality (e.g. "find
|
||||
// a channel that isn't the current one"), and a single shared address could
|
||||
// turn such a search into a spin. 1024 far exceeds the number of FMOD objects
|
||||
// the game holds live at once, so live handles never alias in practice.
|
||||
constexpr int kFakeHandlePoolSize = 1024;
|
||||
GuestAddr g_fakeHandlePool = 0;
|
||||
int g_fakeHandleCursor = 0;
|
||||
|
||||
GuestAddr FakeHandle(GuestEngine& eng) {
|
||||
GuestAddr vt = FmodNoOpVtable(eng);
|
||||
if (!g_fakeHandlePool) {
|
||||
g_fakeHandlePool = eng.AllocPermanent(kFakeHandlePoolSize * 16);
|
||||
if (!g_fakeHandlePool) return 0;
|
||||
// Every slot carries the same vtable pointer; the remaining 12 bytes
|
||||
// stay zero, exactly as the per-call version left them.
|
||||
for (int i = 0; i < kFakeHandlePoolSize; i++) {
|
||||
GuestAddr slot = g_fakeHandlePool + (uint32_t)i * 16;
|
||||
if (vt) memcpy(eng.G2H(slot), &vt, 4);
|
||||
}
|
||||
}
|
||||
GuestAddr obj = g_fakeHandlePool + (uint32_t)g_fakeHandleCursor * 16;
|
||||
g_fakeHandleCursor = (g_fakeHandleCursor + 1) % kFakeHandlePoolSize;
|
||||
return obj;
|
||||
}
|
||||
void OutPtr(GuestEngine& eng, uint32_t slot, GuestAddr v) { if (slot) memcpy(eng.G2H(slot), &v, 4); }
|
||||
void OutFloat(GuestEngine& eng, uint32_t slot, float v) { if (slot) memcpy(eng.G2H(slot), &v, 4); }
|
||||
void OutBool(GuestEngine& eng, uint32_t slot, bool v) { if (slot) { uint32_t b = v ? 1u : 0u; memcpy(eng.G2H(slot), &b, 4); } }
|
||||
void OutU32(GuestEngine& eng, uint32_t slot, uint32_t v) { if (slot) memcpy(eng.G2H(slot), &v, 4); }
|
||||
// FMOD_VECTOR = 3 floats (12 bytes) - stable/unchanged across every FMOD
|
||||
// version, safe to zero exactly (unlike the bigger, uncertain structs this
|
||||
// file otherwise leaves untouched - see fmod_shims.h's own comment).
|
||||
void OutVectorZero(GuestEngine& eng, uint32_t slot) { if (slot) memset(eng.G2H(slot), 0, 12); }
|
||||
|
||||
// ---- Factory functions ----
|
||||
uint32_t Shim_FMOD_EventSystem_Create(GuestEngine& eng, uint32_t out, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
OutPtr(eng, out, FakeHandle(eng));
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_FMOD_Memory_Initialize(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
|
||||
// ---- EventSystem ----
|
||||
uint32_t Shim_EventSystem_init(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_EventSystem_update(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_EventSystem_release(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_EventSystem_getSystemObject(GuestEngine& eng, uint32_t, uint32_t out, uint32_t, uint32_t, uint32_t) {
|
||||
OutPtr(eng, out, FakeHandle(eng));
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_EventSystem_getMusicSystem(GuestEngine& eng, uint32_t, uint32_t out, uint32_t, uint32_t, uint32_t) {
|
||||
OutPtr(eng, out, FakeHandle(eng));
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_EventSystem_getEvent(GuestEngine& eng, uint32_t, uint32_t, uint32_t, uint32_t out, uint32_t) {
|
||||
OutPtr(eng, out, FakeHandle(eng));
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_EventSystem_getGroup(GuestEngine& eng, uint32_t, uint32_t, uint32_t, uint32_t out, uint32_t) {
|
||||
OutPtr(eng, out, FakeHandle(eng));
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_EventSystem_getCategory(GuestEngine& eng, uint32_t, uint32_t, uint32_t out, uint32_t, uint32_t) {
|
||||
OutPtr(eng, out, FakeHandle(eng));
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_EventSystem_load(GuestEngine& eng, uint32_t, uint32_t, uint32_t, uint32_t out, uint32_t) {
|
||||
OutPtr(eng, out, FakeHandle(eng));
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_EventSystem_getReverbPreset(GuestEngine& eng, uint32_t, uint32_t, uint32_t, uint32_t indexOut, uint32_t) {
|
||||
OutU32(eng, indexOut, 0);
|
||||
return kFmodOk; // FMOD_REVERB_PROPERTIES* left untouched - size not independently confirmed, see fmod_shims.h
|
||||
}
|
||||
uint32_t Shim_EventSystem_setReverbProperties(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_EventSystem_set3DListenerAttributes(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
|
||||
// ---- ChannelGroup ----
|
||||
uint32_t Shim_ChannelGroup_getSystemObject(GuestEngine& eng, uint32_t, uint32_t out, uint32_t, uint32_t, uint32_t) {
|
||||
OutPtr(eng, out, FakeHandle(eng));
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_ChannelGroup_addDSP(GuestEngine& eng, uint32_t, uint32_t, uint32_t out, uint32_t, uint32_t) {
|
||||
OutPtr(eng, out, FakeHandle(eng));
|
||||
return kFmodOk;
|
||||
}
|
||||
|
||||
// ---- EventParameter ----
|
||||
uint32_t Shim_EventParameter_keyOff(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_EventParameter_getValue(GuestEngine& eng, uint32_t, uint32_t out, uint32_t, uint32_t, uint32_t) {
|
||||
OutFloat(eng, out, 0.0f);
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_EventParameter_setValue(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
|
||||
// ---- DSP ----
|
||||
uint32_t Shim_DSP_setParameter(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_DSP_release(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
|
||||
// ---- Event ----
|
||||
uint32_t Shim_Event_getCategory(GuestEngine& eng, uint32_t, uint32_t out, uint32_t, uint32_t, uint32_t) {
|
||||
OutPtr(eng, out, FakeHandle(eng));
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_Event_setCallback(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; } // callback never invoked - no real event ever fires
|
||||
uint32_t Shim_Event_getParameter(GuestEngine& eng, uint32_t, uint32_t, uint32_t out, uint32_t, uint32_t) {
|
||||
OutPtr(eng, out, FakeHandle(eng));
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_Event_get3DOcclusion(GuestEngine& eng, uint32_t, uint32_t out1, uint32_t out2, uint32_t, uint32_t) {
|
||||
OutFloat(eng, out1, 0.0f);
|
||||
OutFloat(eng, out2, 0.0f);
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_Event_set3DOcclusion(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_Event_get3DAttributes(GuestEngine& eng, uint32_t, uint32_t out1, uint32_t out2, uint32_t, uint32_t) {
|
||||
OutVectorZero(eng, out1);
|
||||
OutVectorZero(eng, out2);
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_Event_getChannelGroup(GuestEngine& eng, uint32_t, uint32_t out, uint32_t, uint32_t, uint32_t) {
|
||||
OutPtr(eng, out, FakeHandle(eng));
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_Event_set3DAttributes(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_Event_setPropertyByIndex(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_Event_stop(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_Event_start(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_Event_getInfo(GuestEngine& eng, uint32_t, uint32_t indexOut, uint32_t, uint32_t, uint32_t) {
|
||||
OutU32(eng, indexOut, 0);
|
||||
return kFmodOk; // name (char**) and FMOD_EVENT_INFO* left untouched - see fmod_shims.h
|
||||
}
|
||||
uint32_t Shim_Event_getMute(GuestEngine& eng, uint32_t, uint32_t out, uint32_t, uint32_t, uint32_t) {
|
||||
OutBool(eng, out, false);
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_Event_release(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_Event_setMute(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_Event_getPitch(GuestEngine& eng, uint32_t, uint32_t out, uint32_t, uint32_t, uint32_t) {
|
||||
OutFloat(eng, out, 1.0f); // neutral pitch
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_Event_getState(GuestEngine& eng, uint32_t, uint32_t out, uint32_t, uint32_t, uint32_t) {
|
||||
OutU32(eng, out, 0); // no state flags active - not playing/loading/etc
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_Event_setPitch(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_Event_getPaused(GuestEngine& eng, uint32_t, uint32_t out, uint32_t, uint32_t, uint32_t) {
|
||||
OutBool(eng, out, false);
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_Event_getVolume(GuestEngine& eng, uint32_t, uint32_t out, uint32_t, uint32_t, uint32_t) {
|
||||
OutFloat(eng, out, 1.0f);
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_Event_setPaused(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_Event_setVolume(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
|
||||
// ---- Sound ----
|
||||
uint32_t Shim_Sound_release(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
|
||||
// ---- System ----
|
||||
uint32_t Shim_System_createSound(GuestEngine& eng, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t sp) {
|
||||
uint32_t out = eng.ReadIncomingArg(4, 0, 0, 0, 0, sp); // 5th total arg (this,name,mode,exinfo,+sound**)
|
||||
OutPtr(eng, out, FakeHandle(eng));
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_System_getCPUUsage(GuestEngine& eng, uint32_t, uint32_t out1, uint32_t out2, uint32_t out3, uint32_t sp) {
|
||||
uint32_t out4 = eng.ReadIncomingArg(4, 0, out1, out2, out3, sp);
|
||||
uint32_t out5 = eng.ReadIncomingArg(5, 0, out1, out2, out3, sp);
|
||||
OutFloat(eng, out1, 0.0f);
|
||||
OutFloat(eng, out2, 0.0f);
|
||||
OutFloat(eng, out3, 0.0f);
|
||||
OutFloat(eng, out4, 0.0f);
|
||||
OutFloat(eng, out5, 0.0f);
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_System_setFileSystem(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
// Real signature takes ~10 args (mostly guest function-pointer
|
||||
// callbacks for async file I/O) - none of them will ever be called
|
||||
// (no real audio, no real file streaming), so there's nothing worth
|
||||
// reading even the extra stack args for.
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_System_setSpeakerMode(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_System_createDSPByType(GuestEngine& eng, uint32_t, uint32_t, uint32_t out, uint32_t, uint32_t) {
|
||||
OutPtr(eng, out, FakeHandle(eng));
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_System_setDSPBufferSize(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_System_getSoftwareFormat(GuestEngine& eng, uint32_t, uint32_t out1, uint32_t out2, uint32_t out3, uint32_t sp) {
|
||||
uint32_t out4 = eng.ReadIncomingArg(4, 0, out1, out2, out3, sp);
|
||||
uint32_t out5 = eng.ReadIncomingArg(5, 0, out1, out2, out3, sp);
|
||||
uint32_t out6 = eng.ReadIncomingArg(6, 0, out1, out2, out3, sp);
|
||||
OutU32(eng, out1, 0);
|
||||
OutU32(eng, out2, 0);
|
||||
OutU32(eng, out3, 0);
|
||||
OutU32(eng, out4, 0);
|
||||
OutU32(eng, out5, 0);
|
||||
OutU32(eng, out6, 0);
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_System_setSoftwareFormat(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_System_set3DSpeakerPosition(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_System_playSound(GuestEngine& eng, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t sp) {
|
||||
uint32_t out = eng.ReadIncomingArg(4, 0, 0, 0, 0, sp); // 5th total arg (this,channelid,sound,paused,+channel**)
|
||||
OutPtr(eng, out, FakeHandle(eng));
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_System_setOutput(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
|
||||
// ---- Channel ----
|
||||
uint32_t Shim_Channel_setCallback(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_Channel_setPriority(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_Channel_stop(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_Channel_setMute(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_Channel_getPaused(GuestEngine& eng, uint32_t, uint32_t out, uint32_t, uint32_t, uint32_t) {
|
||||
OutBool(eng, out, false);
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_Channel_isPlaying(GuestEngine& eng, uint32_t, uint32_t out, uint32_t, uint32_t, uint32_t) {
|
||||
OutBool(eng, out, false);
|
||||
return kFmodOk;
|
||||
}
|
||||
uint32_t Shim_Channel_setPaused(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
uint32_t Shim_Channel_setVolume(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }
|
||||
|
||||
} // namespace
|
||||
|
||||
void RegisterFmodImportShims(GuestEngine& engine) {
|
||||
engine.RegisterImportShim("FMOD_EventSystem_Create", Shim_FMOD_EventSystem_Create);
|
||||
engine.RegisterImportShim("FMOD_Memory_Initialize", Shim_FMOD_Memory_Initialize);
|
||||
|
||||
engine.RegisterImportShim("_ZN4FMOD11EventSystem4initEijPvj", Shim_EventSystem_init);
|
||||
engine.RegisterImportShim("_ZN4FMOD11EventSystem6updateEv", Shim_EventSystem_update);
|
||||
engine.RegisterImportShim("_ZN4FMOD11EventSystem7releaseEv", Shim_EventSystem_release);
|
||||
engine.RegisterImportShim("_ZN4FMOD11EventSystem15getSystemObjectEPPNS_6SystemE", Shim_EventSystem_getSystemObject);
|
||||
engine.RegisterImportShim("_ZN4FMOD11EventSystem14getMusicSystemEPPNS_11MusicSystemE", Shim_EventSystem_getMusicSystem);
|
||||
engine.RegisterImportShim("_ZN4FMOD11EventSystem8getEventEPKcjPPNS_5EventE", Shim_EventSystem_getEvent);
|
||||
engine.RegisterImportShim("_ZN4FMOD11EventSystem8getGroupEPKcbPPNS_10EventGroupE", Shim_EventSystem_getGroup);
|
||||
engine.RegisterImportShim("_ZN4FMOD11EventSystem11getCategoryEPKcPPNS_13EventCategoryE", Shim_EventSystem_getCategory);
|
||||
engine.RegisterImportShim("_ZN4FMOD11EventSystem4loadEPKcP19FMOD_EVENT_LOADINFOPPNS_12EventProjectE", Shim_EventSystem_load);
|
||||
engine.RegisterImportShim("_ZN4FMOD11EventSystem15getReverbPresetEPKcP22FMOD_REVERB_PROPERTIESPi", Shim_EventSystem_getReverbPreset);
|
||||
engine.RegisterImportShim("_ZN4FMOD11EventSystem19setReverbPropertiesEPK22FMOD_REVERB_PROPERTIES", Shim_EventSystem_setReverbProperties);
|
||||
engine.RegisterImportShim("_ZN4FMOD11EventSystem23set3DListenerAttributesEiPK11FMOD_VECTORS3_S3_S3_", Shim_EventSystem_set3DListenerAttributes);
|
||||
|
||||
engine.RegisterImportShim("_ZN4FMOD12ChannelGroup15getSystemObjectEPPNS_6SystemE", Shim_ChannelGroup_getSystemObject);
|
||||
engine.RegisterImportShim("_ZN4FMOD12ChannelGroup6addDSPEPNS_3DSPEPPNS_13DSPConnectionE", Shim_ChannelGroup_addDSP);
|
||||
|
||||
engine.RegisterImportShim("_ZN4FMOD14EventParameter6keyOffEv", Shim_EventParameter_keyOff);
|
||||
engine.RegisterImportShim("_ZN4FMOD14EventParameter8getValueEPf", Shim_EventParameter_getValue);
|
||||
engine.RegisterImportShim("_ZN4FMOD14EventParameter8setValueEf", Shim_EventParameter_setValue);
|
||||
|
||||
engine.RegisterImportShim("_ZN4FMOD3DSP12setParameterEif", Shim_DSP_setParameter);
|
||||
engine.RegisterImportShim("_ZN4FMOD3DSP7releaseEv", Shim_DSP_release);
|
||||
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event11getCategoryEPPNS_13EventCategoryE", Shim_Event_getCategory);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event11setCallbackEPF11FMOD_RESULTP10FMOD_EVENT23FMOD_EVENT_CALLBACKTYPEPvS5_S5_ES5_", Shim_Event_setCallback);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event12getParameterEPKcPPNS_14EventParameterE", Shim_Event_getParameter);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event14get3DOcclusionEPfS1_", Shim_Event_get3DOcclusion);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event14set3DOcclusionEff", Shim_Event_set3DOcclusion);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event15get3DAttributesEP11FMOD_VECTORS2_S2_", Shim_Event_get3DAttributes);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event15getChannelGroupEPPNS_12ChannelGroupE", Shim_Event_getChannelGroup);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event15set3DAttributesEPK11FMOD_VECTORS3_S3_", Shim_Event_set3DAttributes);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event18setPropertyByIndexEiPvb", Shim_Event_setPropertyByIndex);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event4stopEb", Shim_Event_stop);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event5startEv", Shim_Event_start);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event7getInfoEPiPPcP15FMOD_EVENT_INFO", Shim_Event_getInfo);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event7getMuteEPb", Shim_Event_getMute);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event7releaseEbb", Shim_Event_release);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event7setMuteEb", Shim_Event_setMute);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event8getPitchEPf21FMOD_EVENT_PITCHUNITS", Shim_Event_getPitch);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event8getStateEPj", Shim_Event_getState);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event8setPitchEf21FMOD_EVENT_PITCHUNITS", Shim_Event_setPitch);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event9getPausedEPb", Shim_Event_getPaused);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event9getVolumeEPf", Shim_Event_getVolume);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event9setPausedEb", Shim_Event_setPaused);
|
||||
engine.RegisterImportShim("_ZN4FMOD5Event9setVolumeEf", Shim_Event_setVolume);
|
||||
|
||||
engine.RegisterImportShim("_ZN4FMOD5Sound7releaseEv", Shim_Sound_release);
|
||||
|
||||
engine.RegisterImportShim("_ZN4FMOD6System11createSoundEPKcjP22FMOD_CREATESOUNDEXINFOPPNS_5SoundE", Shim_System_createSound);
|
||||
engine.RegisterImportShim("_ZN4FMOD6System11getCPUUsageEPfS1_S1_S1_S1_", Shim_System_getCPUUsage);
|
||||
engine.RegisterImportShim(
|
||||
"_ZN4FMOD6System13setFileSystemEPF11FMOD_RESULTPKciPjPPvS6_EPFS1_S5_S5_EPFS1_S5_S5_jS4_S5_EPFS1_S5_jS5_EPFS1_P18FMOD_ASYNCREADINFOS5_ESA_i",
|
||||
Shim_System_setFileSystem);
|
||||
engine.RegisterImportShim("_ZN4FMOD6System14setSpeakerModeE16FMOD_SPEAKERMODE", Shim_System_setSpeakerMode);
|
||||
engine.RegisterImportShim("_ZN4FMOD6System15createDSPByTypeE13FMOD_DSP_TYPEPPNS_3DSPE", Shim_System_createDSPByType);
|
||||
engine.RegisterImportShim("_ZN4FMOD6System16setDSPBufferSizeEji", Shim_System_setDSPBufferSize);
|
||||
engine.RegisterImportShim("_ZN4FMOD6System17getSoftwareFormatEPiP17FMOD_SOUND_FORMATS1_S1_P18FMOD_DSP_RESAMPLERS1_", Shim_System_getSoftwareFormat);
|
||||
engine.RegisterImportShim("_ZN4FMOD6System17setSoftwareFormatEi17FMOD_SOUND_FORMATii18FMOD_DSP_RESAMPLER", Shim_System_setSoftwareFormat);
|
||||
engine.RegisterImportShim("_ZN4FMOD6System20set3DSpeakerPositionE12FMOD_SPEAKERffb", Shim_System_set3DSpeakerPosition);
|
||||
engine.RegisterImportShim("_ZN4FMOD6System9playSoundE17FMOD_CHANNELINDEXPNS_5SoundEbPPNS_7ChannelE", Shim_System_playSound);
|
||||
engine.RegisterImportShim("_ZN4FMOD6System9setOutputE15FMOD_OUTPUTTYPE", Shim_System_setOutput);
|
||||
|
||||
engine.RegisterImportShim("_ZN4FMOD7Channel11setCallbackEPF11FMOD_RESULTP12FMOD_CHANNEL25FMOD_CHANNEL_CALLBACKTYPEPvS5_E", Shim_Channel_setCallback);
|
||||
engine.RegisterImportShim("_ZN4FMOD7Channel11setPriorityEi", Shim_Channel_setPriority);
|
||||
engine.RegisterImportShim("_ZN4FMOD7Channel4stopEv", Shim_Channel_stop);
|
||||
engine.RegisterImportShim("_ZN4FMOD7Channel7setMuteEb", Shim_Channel_setMute);
|
||||
engine.RegisterImportShim("_ZN4FMOD7Channel9getPausedEPb", Shim_Channel_getPaused);
|
||||
engine.RegisterImportShim("_ZN4FMOD7Channel9isPlayingEPb", Shim_Channel_isPlaying);
|
||||
engine.RegisterImportShim("_ZN4FMOD7Channel9setPausedEb", Shim_Channel_setPaused);
|
||||
engine.RegisterImportShim("_ZN4FMOD7Channel9setVolumeEf", Shim_Channel_setVolume);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "guest_engine.h"
|
||||
|
||||
// FMOD Ex "Event System" API stubs (28 symbols: FMOD_EventSystem_Create,
|
||||
// FMOD_Memory_Initialize, and the EventSystem/Event/EventParameter/
|
||||
// EventCategory/EventGroup/ChannelGroup/DSP/System/Channel/Sound methods
|
||||
// this game's own dynsym relocations reference). No real audio - confirmed
|
||||
// this session that no arm64-v8a build of FMOD Ex (this specific, long-
|
||||
// deprecated Event System API generation, distinct from modern FMOD
|
||||
// Studio, which doesn't even have these classes) is available to link
|
||||
// against for real. See ARM64_TRANSLATION_LAYER.md.
|
||||
//
|
||||
// Every FMOD_RESULT-returning function here returns FMOD_OK (0) - the
|
||||
// GAME's own logic should proceed as if audio initialized successfully
|
||||
// rather than getting stuck on an audio-readiness gate, exactly the
|
||||
// "unresolved import silently returns 0" pattern this whole session has
|
||||
// been replacing elsewhere, except here 0 (FMOD_OK) genuinely IS the
|
||||
// semantically-correct "everything's fine" answer to give, not an
|
||||
// accidental one. Every `T**`-shaped output parameter (getEvent,
|
||||
// createSound, getSystemObject, ...) gets a small, valid, non-null FAKE
|
||||
// HANDLE (a real guest address, safe to store/pass to a later call, never
|
||||
// dereferenced beyond identity) instead of NULL, so calling code that
|
||||
// reasonably checks "did I get a real object back" doesn't bail out of
|
||||
// its own subsequent logic. Getters write plausible neutral defaults
|
||||
// (volume=1.0, paused=false, pitch=1.0, no active state flags) rather than
|
||||
// leaving output params untouched. Struct-shaped output parameters whose
|
||||
// EXACT size this file can't independently confirm (FMOD_EVENT_INFO,
|
||||
// FMOD_REVERB_PROPERTIES, FMOD_CREATESOUNDEXINFO) are deliberately left
|
||||
// untouched rather than guessed-and-memset, to avoid a wrong-sized write
|
||||
// overflowing into adjacent guest memory - the one well-known, stable FMOD
|
||||
// struct (FMOD_VECTOR - 3 floats, unchanged across every FMOD version)
|
||||
// does get zeroed. Callback registrations (Event::setCallback,
|
||||
// Channel::setCallback) accept and discard the guest callback pointer -
|
||||
// consistent with "no real audio ever plays," no event will ever fire to
|
||||
// invoke it.
|
||||
void RegisterFmodImportShims(GuestEngine& engine);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
// Real GLES2 shim layer: forwards every gl* import the guest calls to the
|
||||
// REAL host GLES2 functions - not a translated/emulated GPU driver, just
|
||||
// argument marshaling (mechanically identical to the JNI shim: pointer args
|
||||
// G2H-translated, GLfloat args are raw-bit-reinterpreted since armeabi-v7a
|
||||
// uses the softfp calling convention - see import_shims.cpp's own top
|
||||
// comment). This works because the guest code calling these is executed
|
||||
// synchronously on whatever real host thread issued the CallGuestFunction
|
||||
// (the real engine's own GLThread, via nativeSurfaceCreated/nativeOnDrawFrame
|
||||
// - see real_native_offsets.h) - Android's own GLSurfaceView machinery has
|
||||
// ALREADY made a real, current EGL context current on that exact thread
|
||||
// before calling into any of this, so there is no separate EGL context to
|
||||
// stand up here; GL calls just land in the real, already-current context.
|
||||
//
|
||||
// Covers the full 142-function GLES2 core API (GLES2/gl2.h) minus 3 handled
|
||||
// by hand for pointer-indirection/return-ownership reasons (see
|
||||
// gles_shim.cpp): glGetString (returns a driver-owned string, copied into
|
||||
// guest heap memory rather than returning a raw host pointer),
|
||||
// glShaderSource (its `string` parameter is an ARRAY of guest pointers,
|
||||
// each element needs its own translation), glGetVertexAttribPointerv
|
||||
// (writes a pointer *value* into guest memory - needs H2G on the result,
|
||||
// not a direct G2H pass-through like every other pointer arg).
|
||||
//
|
||||
// Also covers AndroidBitmap_{getInfo,lockPixels,unlockPixels} (<android/
|
||||
// bitmap.h> - texture loading from Android Bitmap objects).
|
||||
//
|
||||
// Known correctness gap (semantic, not a crash): `glVertexAttribPointer`'s
|
||||
// and `glDrawElements`'s final pointer-shaped parameter is genuinely
|
||||
// dual-purpose in real OpenGL ES - a real pointer when no buffer is bound
|
||||
// (GL_ARRAY_BUFFER / GL_ELEMENT_ARRAY_BUFFER respectively), but a small
|
||||
// integer BYTE OFFSET into the currently-bound buffer object when one IS
|
||||
// bound (extremely common in real GLES2 rendering code - VBO-based
|
||||
// pipelines pass small offsets like 0/12/24 here, not pointers). The
|
||||
// generic "every pointer parameter is G2H-translated" rule these two share
|
||||
// with the other 137 functions is WRONG for the buffer-bound case: it
|
||||
// silently turns a small offset into a nonsense (but non-crashing) host
|
||||
// address instead of passing the offset through unchanged. Fixing this
|
||||
// properly needs this shim layer to track which buffer is currently bound
|
||||
// (mirroring glBindBuffer calls) and skip G2H when one is - not done yet;
|
||||
// documented rather than silently wrong. Low priority until real per-frame
|
||||
// draw calls are being exercised (this session got only as far as
|
||||
// GL-capability-detection calls, not actual drawing).
|
||||
#include "guest_engine.h"
|
||||
|
||||
void RegisterGlesImportShims(GuestEngine& engine);
|
||||
|
||||
// Diagnostic for the "nothing renders past the splash" investigation (see
|
||||
// ARM64_TRANSLATION_LAYER.md) - starts a detached background thread that
|
||||
// periodically logs how many real glClear/glDrawArrays/glDrawElements/
|
||||
// glUseProgram calls have happened, mirroring profiler.h's
|
||||
// StartProfileDumpThread pattern. Answers whether the render pipeline is
|
||||
// being exercised at all once the game reaches its post-splash state.
|
||||
void StartGlesCounterDumpThread();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,583 @@
|
||||
#pragma once
|
||||
|
||||
// Core ARM32-on-ARM64 in-process CPU-level translation engine.
|
||||
// See /ARM64_TRANSLATION_LAYER.md for the design rationale, and this
|
||||
// session's desktop spike (scratchpad/spike_load.py, run against the real
|
||||
// native_lib/libapp.so) for what's been validated outside the NDK build
|
||||
// before this C++ was written.
|
||||
//
|
||||
// Design in one paragraph: libapp.so is loaded as an ET_DYN ELF32 whose own
|
||||
// preferred base is vaddr 0 (confirmed via readelf/pyelftools) - so instead
|
||||
// of inventing a separate guest/host address translation scheme, this
|
||||
// engine deliberately maps the image at guest address 0 too, backed by a
|
||||
// SINGLE contiguous host mmap covering [0, region_size). That makes
|
||||
// G2H(addr) = host_base + addr and H2G(ptr) = ptr - host_base plain pointer
|
||||
// arithmetic, and - more importantly - makes it the EXACT SAME arithmetic
|
||||
// every OFFSET macro already scattered through main.cpp/lan_event_injection.h
|
||||
// etc. already performs (APP_ADDR(offset) == (uintptr_t)libapp + offset).
|
||||
// None of that pre-existing reverse-engineering work needs to change.
|
||||
//
|
||||
// Hooking model: InstallTrampolineHook<Ret,Args...>(target, hookFn, name)
|
||||
// registers a Unicorn UC_HOOK_CODE at `target` (no byte-patching, unlike
|
||||
// this project's old armhook.cpp - Unicorn's hook fires BEFORE the real
|
||||
// instruction there is fetched, so nothing needs to be overwritten). The
|
||||
// dispatcher decodes r0-r3 into hookFn's real C++ argument types (pointer
|
||||
// args are G2H-translated automatically), calls hookFn (real host C++,
|
||||
// unchanged from what it always was), writes its return value into r0, and
|
||||
// sets PC=LR - i.e. from the guest's point of view the target function ran
|
||||
// and returned instantly. If hookFn wants to also run the REAL original
|
||||
// code (this project's universal "call orig_XXX(...) and use/return its
|
||||
// result" pattern), it calls the GuestFn this function returns, which
|
||||
// invokes a small trampoline built IN GUEST MEMORY (a verbatim copy of the
|
||||
// two displaced original instructions + a jump back to target+8, built the
|
||||
// same way this project's old InstallArmTrampolineHook built one in host
|
||||
// RWX memory) via CallGuestFunction - a real, separate, re-entrant Unicorn
|
||||
// call, so the rest of the original function actually executes.
|
||||
//
|
||||
// Multithreading model (added 2026-09-01, after a real on-device hang -
|
||||
// see ARM64_TRANSLATION_LAYER.md): a single Unicorn `uc_engine` holds ONE
|
||||
// CPU register set, so it cannot run more than one guest instruction stream
|
||||
// concurrently - real guest threads (spawned via pthread_create, see
|
||||
// emu/pthread_shim.*) each get their OWN `uc_engine*`, mapped onto the SAME
|
||||
// shared `host_region_` buffer via `uc_mem_map_ptr` (exactly mirroring how
|
||||
// real OS threads share one process's memory but have separate register/
|
||||
// stack state) and their own freshly-carved guest stack region (see
|
||||
// CarveThreadStack). All UC_HOOK_CODE registrations (import stubs, JNI/GLES
|
||||
// slots, trampoline hooks) are recorded in `hook_registrations_` at install
|
||||
// time and REPLAYED onto every new engine (ReplayHooksOnEngine) - Unicorn
|
||||
// hooks are per-engine, not shared. `uc_` itself is `thread_local`, and
|
||||
// every public entry point that might run on a new host thread
|
||||
// (CallGuestFunction) calls EnsureThreadEngine() first, so callers never
|
||||
// need to think about which engine is "current" - existing code that reads
|
||||
// `eng.uc()` automatically gets the right one for whichever thread it's
|
||||
// running on.
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <dlfcn.h>
|
||||
#include <unicorn/unicorn.h>
|
||||
#include "guest_types.h"
|
||||
#include "guest_heap.h"
|
||||
#include "../util/util.h"
|
||||
|
||||
// A resolved-and-loaded import shim: called when guest code reaches a GOT
|
||||
// slot's stub address instead of a real function (see import_shims.h/.cpp
|
||||
// and emu/gles_shim.h/.cpp for the actual implementations). Args are raw
|
||||
// r0-r3 plus `sp` (the entry stack pointer, needed to read AAPCS32 stack
|
||||
// args for any import with more than 4 parameters - e.g. most GLES2
|
||||
// functions - via engine.ReadIncomingArg()); return is written into r0.
|
||||
// Marshaling to/from real host types is the shim's own job (same contract
|
||||
// as a hook callback).
|
||||
using ImportShimFn = uint32_t (*)(class GuestEngine& engine, uint32_t r0, uint32_t r1, uint32_t r2, uint32_t r3, uint32_t sp);
|
||||
|
||||
class GuestEngine {
|
||||
public:
|
||||
static GuestEngine& Instance();
|
||||
|
||||
// Loads libapp.so from `path` (a real file - see app module's asset
|
||||
// extraction for how it gets there since it can no longer be a normal
|
||||
// jniLibs/armeabi-v7a entry the loader dlopen()s). Returns false and
|
||||
// logs on any failure (bad ELF, mmap failure, etc.) - this project's
|
||||
// existing convention (see util/util.cpp's Log) of "log and return
|
||||
// false, never abort/crash the host process on a load failure".
|
||||
bool LoadImage(const char* path);
|
||||
|
||||
// Loads a SEPARATE ARM32 ELF32 .so image alongside whatever LoadImage
|
||||
// already loaded, for cases that need a real, independently-compiled
|
||||
// artifact running under this engine without disturbing libapp.so's own
|
||||
// state (2026-09-16, ARM64_TRANSLATION_LAYER.md's "isolated
|
||||
// std::ostringstream repro" test - see emu/ostream_repro_test.cpp for
|
||||
// the actual caller). Deliberately NOT a second call to LoadImage:
|
||||
// MapSegments is a single-image design end to end (host_region_ is one
|
||||
// mmap sized around exactly one image's own image_end_, and depends on
|
||||
// the loaded image's OWN preferred ET_DYN base being guest address 0 -
|
||||
// see this class's own top comment) - calling it twice would re-mmap
|
||||
// host_region_ out from under the already-loaded primary image, losing
|
||||
// its heap/hooks/relocations entirely. Instead, this carves space for
|
||||
// the WHOLE second image out of the existing AllocMmap() arena (already
|
||||
// part of the SAME host_region_ every engine thread maps - see
|
||||
// AllocMmap's own comment) at whatever guest address that arena's bump
|
||||
// allocator hands out, then repeats MapSegments/ProcessRelocations'
|
||||
// logic with a real, non-zero BIAS added to every relocation (the
|
||||
// primary loader gets away with skipping R_ARM_RELATIVE entirely
|
||||
// because its own bias is always exactly 0 - see ProcessRelocations'
|
||||
// own comment - this one actually adds `base` to each one). External
|
||||
// symbol references resolve through the EXACT SAME
|
||||
// ResolveOrCreateImportStub/RegisterImportShim table the primary
|
||||
// image's own imports already use, so this needs no new engine-side
|
||||
// shim plumbing by itself - whatever real shims (or "unresolved import,
|
||||
// log once, return 0" fallbacks) libapp.so's loading already registered
|
||||
// apply here unchanged. This is deliberately NOT a general-purpose
|
||||
// second dynamic linker (no DT_NEEDED handling, no symbol versioning,
|
||||
// no PLT-lazy-binding tricks) - just enough to run one small,
|
||||
// self-contained test artifact end to end.
|
||||
//
|
||||
// `entrySymbol` is looked up in the image's own .dynsym via its section
|
||||
// headers (survives a normal `strip`, unlike .symtab - see this
|
||||
// function's own .cpp comment for why section headers rather than
|
||||
// DT_SYMTAB are used for this specific lookup). Returns the resolved
|
||||
// guest address (Thumb bit already included, same convention as every
|
||||
// other resolved address in this engine) ready to hand straight to
|
||||
// CallGuestFunction, or 0 on any failure (bad ELF, arena exhaustion,
|
||||
// symbol not found - all logged).
|
||||
GuestAddr LoadSecondaryImage(const char* path, const char* entrySymbol);
|
||||
|
||||
bool loaded() const { return host_region_ != nullptr; }
|
||||
|
||||
// ---- Address translation ----
|
||||
// Both are plain pointer arithmetic (see class comment) - cheap enough
|
||||
// to call at every struct-field access site, matching how this
|
||||
// project's existing APP_ADDR(offset) macro is already used everywhere.
|
||||
void* G2H(GuestAddr addr) const {
|
||||
// host_region_ is a single contiguous mmap of exactly region_size_
|
||||
// bytes covering every real arena (image/heap/trampoline/import-
|
||||
// stub/misc-stub/control/thread-stacks) - any addr past that is
|
||||
// definitely not a real guest address. Left unchecked, this used to
|
||||
// be plain pointer arithmetic handed straight to whatever the
|
||||
// caller does next (memcpy, strlen, direct struct access...) - one
|
||||
// of this engine's own Shim_*/Impl_* functions handed a garbage
|
||||
// guest-supplied pointer (e.g. Shim_time()'s tPtr) would compute a
|
||||
// wild HOST pointer and crash the entire process with a real
|
||||
// SIGSEGV, not a graceful, recoverable guest-level fault the way a
|
||||
// bad access from actual emulated ARM32 code would (that path goes
|
||||
// through Unicorn's own protection and mem_fault_hook_cb instead -
|
||||
// this one bypasses it entirely, since it's host C++ dereferencing
|
||||
// directly). Confirmed live this session via a symbolicated
|
||||
// tombstone: Shim_time() got a garbage tPtr, memcpy'd 4 bytes
|
||||
// through G2H(tPtr), and took down the whole app. G2H() is the
|
||||
// single choke point for all such guest-pointer translation
|
||||
// (250+ call sites across emu/*.cpp) - bounds-checking it here
|
||||
// closes the entire bug class at once instead of guarding each
|
||||
// call site individually.
|
||||
if (addr >= region_size_) {
|
||||
LogOutOfRangeG2H(addr);
|
||||
static thread_local uint8_t scratch[64];
|
||||
return scratch;
|
||||
}
|
||||
return host_region_ + addr;
|
||||
}
|
||||
GuestAddr H2G(const void* hostPtr) const {
|
||||
return static_cast<GuestAddr>(reinterpret_cast<const uint8_t*>(hostPtr) - host_region_);
|
||||
}
|
||||
// True if `hostPtr` actually falls inside this engine's guest-backed
|
||||
// region - H2G() on a pointer that DOESN'T (e.g. a real driver-owned
|
||||
// buffer, like an AndroidBitmap pixel buffer - see gles_shim.cpp) would
|
||||
// silently produce a garbage/wraparound "guest address" rather than an
|
||||
// error, so callers that got a pointer from somewhere OTHER than this
|
||||
// engine's own G2H/heap should check this before calling H2G.
|
||||
bool IsHostPointerInRegion(const void* hostPtr) const {
|
||||
auto p = reinterpret_cast<const uint8_t*>(hostPtr);
|
||||
return p >= host_region_ && p < host_region_ + region_size_;
|
||||
}
|
||||
// host_region_ itself, for code that wants the old-style
|
||||
// "(uintptr_t)libapp + offset" spelling unchanged (see main.h's
|
||||
// APP_ADDR macro, redefined in terms of this).
|
||||
uint8_t* image_host_base() const { return host_region_; }
|
||||
|
||||
// One-past-the-end guest address of the loaded ELF image's own PT_LOAD
|
||||
// segments (page-aligned) - any guest address >= this is one of THIS
|
||||
// engine's own arenas (heap/trampoline/import-stub/misc-stub/thread-
|
||||
// stacks), never real libapp.so code, useful for diagnosing a function
|
||||
// pointer the guest passes around (e.g. pthread_shim.cpp's
|
||||
// pthread_create logging) that turns out to point at one of our own
|
||||
// stubs instead of real code.
|
||||
GuestAddr image_end() const { return image_end_; }
|
||||
|
||||
// ---- Guest heap (for malloc/free/calloc shims and any hook body that
|
||||
// still wants to allocate guest-visible memory directly, e.g.
|
||||
// InjectSyntheticEvent's RaceEvent/CashReward/FakeActor objects) ----
|
||||
GuestHeap& heap() { return heap_; }
|
||||
|
||||
// Bump-allocates from a small, SEPARATE, never-freed arena reserved for
|
||||
// permanent, safety-critical control structures - currently just the
|
||||
// guest JNIEnv/JavaVM (see jni_shim.cpp's BuildGuestJNIEnv/
|
||||
// BuildGuestJavaVM). Deliberately NOT part of heap() (GuestHeap backs
|
||||
// the guest program's own uncontrolled malloc/free churn - see
|
||||
// guest_heap.h's class comment for the corruption this session traced a
|
||||
// real crash to) - anything allocated here is meant to live for the
|
||||
// rest of the process and must never be reachable by a stray guest
|
||||
// free()/overflow in the general heap. No Free() counterpart on
|
||||
// purpose: nothing here is ever supposed to go away.
|
||||
GuestAddr AllocPermanent(uint32_t size);
|
||||
|
||||
// Bump-allocates from a dedicated arena backing real mmap() (see
|
||||
// libc_shims.cpp's Shim_mmap) for the anonymous-mapping case. Same
|
||||
// "never freed" simplicity as AllocPermanent (munmap() stays a no-op -
|
||||
// no evidence yet that guest code depends on reclaiming mmap'd space),
|
||||
// but page-granular and its own cursor/mutex since mmap allocations are
|
||||
// arbitrarily larger than AllocPermanent's small control structures.
|
||||
// Returns 0 (and logs) if the arena is exhausted.
|
||||
GuestAddr AllocMmap(uint32_t length);
|
||||
|
||||
// ---- Calling into guest code from host C++ ----
|
||||
// Up to 4 integer/pointer args go in r0-r3; any beyond that are pushed
|
||||
// onto the guest stack per AAPCS32 (args[4] at [sp+0], args[5] at
|
||||
// [sp+4], ...) - needed for real JNI entry points like
|
||||
// nativeSurfaceChanged(env,thiz,gl10,w,h) (5 args) and arbitrary-arity
|
||||
// JNI Call*Method calls (see emu/jni_shim.*).
|
||||
uint32_t CallGuestFunction(GuestAddr target, const uint32_t* args, int argCount);
|
||||
// Convenience overload for the common <=4-arg case (existing call sites).
|
||||
uint32_t CallGuestFunction(GuestAddr target, uint32_t a0 = 0, uint32_t a1 = 0,
|
||||
uint32_t a2 = 0, uint32_t a3 = 0) {
|
||||
uint32_t args[4] = {a0, a1, a2, a3};
|
||||
return CallGuestFunction(target, args, 4);
|
||||
}
|
||||
|
||||
// The r1 half of the r0:r1 pair a guest function declared to return a
|
||||
// 64-bit value (long/double, per AAPCS32) left behind at the end of the
|
||||
// MOST RECENT CallGuestFunction() call on this thread - call this
|
||||
// immediately after CallGuestFunction() returns, before making any other
|
||||
// guest call on the same thread (a nested/reentrant CallGuestFunction
|
||||
// would overwrite it). Added for jni_shim.cpp's RegisterNatives reverse
|
||||
// bridge (TrampolineBodyWide) to support Java-calls-guest native methods
|
||||
// declared to return long/double, matching the same r0:r1-pair handling
|
||||
// already used for the forward (Call*Method) direction - see
|
||||
// jni_shim.cpp's InvokeCall.
|
||||
uint32_t LastCallHighWord() const { return t_state_.lastCallHiWord; }
|
||||
|
||||
// Reads incoming argument N (0-based) of the function whose UC_HOOK_CODE
|
||||
// callback is currently executing - r0-r3 for N<4, else the guest stack
|
||||
// at the point of entry (sp is the entry SP, as seen by the hook
|
||||
// callback). Used by the JNI shim's dispatcher (jni_shim.cpp) to read
|
||||
// arguments beyond the 4 general-purpose registers.
|
||||
uint32_t ReadIncomingArg(int n, uint32_t r0, uint32_t r1, uint32_t r2, uint32_t r3, uint32_t sp) const {
|
||||
switch (n) {
|
||||
case 0: return r0;
|
||||
case 1: return r1;
|
||||
case 2: return r2;
|
||||
case 3: return r3;
|
||||
default: {
|
||||
uint32_t v = 0;
|
||||
memcpy(&v, G2H(sp + (uint32_t)(n - 4) * 4), 4);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Hooking ----
|
||||
// Builds a guest-memory trampoline (verbatim copy of target's first two
|
||||
// instruction words + a jump back to target+8 - REQUIRES those two
|
||||
// words to be position-independent, exactly the same precondition this
|
||||
// project's old InstallArmTrampolineHook already required and every
|
||||
// existing hook site was already manually verified against via IDA
|
||||
// disasm before hooking) and registers a UC_HOOK_CODE at `target`.
|
||||
// Returns the trampoline's guest address (0 on failure) - wrap it in a
|
||||
// GuestFn<Ret,Args...> to get an "orig_XXX"-shaped callable.
|
||||
GuestAddr InstallTrampolineHookRaw(GuestAddr target, void (*dispatch)(GuestEngine&, void*),
|
||||
void* userData, const char* debugName);
|
||||
|
||||
// ---- Imports ----
|
||||
void RegisterImportShim(const char* symbolName, ImportShimFn fn);
|
||||
|
||||
// Same registration role as RegisterImportShim, but for symbols that
|
||||
// are semantically DATA (typeinfo objects, vtables), not callable
|
||||
// functions - e.g. `_ZTIi` (int's type_info) is referenced as `&_ZTIi`
|
||||
// and read through directly (vtable-pointer field, name-pointer field),
|
||||
// never called. Resolving a data symbol through the normal code-stub
|
||||
// path (AllocCodeStub) would hand out an address in the misc-stub arena
|
||||
// holding a 4-byte UC_HOOK_CODE placeholder - reading struct fields
|
||||
// through that is garbage, the same "read/write through a wrong-shaped
|
||||
// address" bug class this session already traced a real corruption to.
|
||||
// `address` must be a real, pre-built guest address (see
|
||||
// rtti_shims.cpp for the typeinfo objects this backs) - checked in
|
||||
// ResolveOrCreateImportStub BEFORE falling back to a code stub.
|
||||
void RegisterDataSymbol(const char* symbolName, GuestAddr address);
|
||||
|
||||
// Registers a callback GuestEngine invokes exactly once per LoadImage
|
||||
// call, right after MapSegments succeeds (so host_region_/
|
||||
// AllocPermanent are usable) but strictly BEFORE ProcessRelocations
|
||||
// resolves any GOT slot - lets a caller (rtti_shims.cpp's
|
||||
// SetupRttiDataSymbols) build real guest-memory-backed data objects and
|
||||
// RegisterDataSymbol() them before anything could reference them. Kept
|
||||
// as a callback rather than a hard dependency so guest_engine.h/.cpp
|
||||
// doesn't need to know rtti_shims.h exists - main.cpp wires the two
|
||||
// together by calling this before engine.LoadImage(path).
|
||||
using DataSymbolSetupFn = void (*)(GuestEngine&);
|
||||
void RegisterDataSymbolSetup(DataSymbolSetupFn fn) { data_symbol_setup_fns_.push_back(fn); }
|
||||
|
||||
// Generic building block behind both the ELF import-stub resolver above
|
||||
// and jni_shim.cpp's per-slot JNIEnv stubs: carves one fresh guest
|
||||
// address out of a small reserved arena and registers a UC_HOOK_CODE
|
||||
// there directly (no trampoline/displaced-instruction copying needed,
|
||||
// unlike InstallTrampolineHookRaw - there's no real instruction bytes
|
||||
// at a stub address, it only ever exists to be intercepted).
|
||||
GuestAddr AllocCodeStub(uc_cb_hookcode_t callback, void* userData);
|
||||
|
||||
// Resolves `symbolName` to a real, guest-callable address, creating a
|
||||
// fresh import stub (via AllocCodeStub) the first time it's asked for
|
||||
// and caching it thereafter - the same path a real ELF PLT import goes
|
||||
// through, but callable directly by name for symbols that were never
|
||||
// themselves a real import (2026-09-18: eglGetProcAddress's own shim
|
||||
// needs this - some real games resolve even core, non-optional
|
||||
// functions like eglSwapBuffers this way instead of a direct import -
|
||||
// see libc_shims.cpp's own Shim_eglGetProcAddress). Was private; made
|
||||
// public for that use case, no behavior change.
|
||||
GuestAddr ResolveOrCreateImportStub(const std::string& symbolName);
|
||||
|
||||
// Diagnostic: classifies a guest address by which arena it falls into
|
||||
// (real image code, heap, trampoline/import-stub/misc-stub/control,
|
||||
// thread-stacks, or fully out of range) - and, for the import-stub
|
||||
// arena specifically, which registered symbol's stub it is (reverse
|
||||
// lookup over import_stub_by_symbol_, a small map - fine for a rare
|
||||
// diagnostic call, not a hot path). Added to investigate a guest
|
||||
// function pointer (pthread_create's start_routine) landing on one of
|
||||
// this engine's own stub addresses instead of real ARM32 code - see
|
||||
// pthread_shim.cpp's own use and ARM64_TRANSLATION_LAYER.md.
|
||||
std::string DescribeAddress(GuestAddr addr) const;
|
||||
|
||||
// The CURRENT host thread's own guest CPU - see class comment's
|
||||
// "Multithreading model". Never null when called from inside a
|
||||
// dispatch callback (Unicorn always hands the callback the engine it
|
||||
// fired on) or after EnsureThreadEngine() has run for this thread.
|
||||
uc_engine* uc() const { return t_state_.uc; }
|
||||
|
||||
// Guarantees the CALLING host thread has its own `uc_engine*` (mapped
|
||||
// onto the shared guest memory, with every hook replayed) and its own
|
||||
// guest stack region, creating them on first use if needed. Called
|
||||
// automatically by CallGuestFunction, so ordinary callers never need to
|
||||
// call this themselves - exposed mainly for pthread_shim.cpp, which
|
||||
// must call it as the very first thing on a freshly spawned host thread
|
||||
// before that thread can run any guest code at all.
|
||||
void EnsureThreadEngine();
|
||||
|
||||
// Reserved, never-fetched guest PC used as the "return to caller"
|
||||
// target for every CallGuestFunction invocation (see .cpp for why a
|
||||
// fixed constant is safe to reuse across nested calls).
|
||||
static constexpr GuestAddr kCallReturnSentinel = 0xFFFFFFF0u;
|
||||
|
||||
// True once any CallGuestFunction call has hit a fault-class uc_err
|
||||
// (unmapped/protected memory access, invalid instruction, ...) - see
|
||||
// CallGuestFunction's own comment for why every subsequent call then
|
||||
// refuses to run instead of re-entering guest code whose SHARED memory
|
||||
// (host_region_, one buffer for every thread - see class comment) may
|
||||
// already be corrupted.
|
||||
bool crashed() const { return crashed_.load(std::memory_order_relaxed); }
|
||||
|
||||
private:
|
||||
GuestEngine() = default;
|
||||
|
||||
bool MapSegments(const uint8_t* fileData, size_t fileSize);
|
||||
bool ProcessRelocations(const uint8_t* fileData, size_t fileSize);
|
||||
void ReplayHooksOnEngine(uc_engine* newUc);
|
||||
// Fix (2026-09-16, ARM64_TRANSLATION_LAYER.md - uc_emu_start()
|
||||
// reentrancy hang). Everything EnsureThreadEngine() used to do inline to
|
||||
// stand up a brand-new uc_engine* - uc_open, mapping host_region_ onto
|
||||
// it, guard-page/RELRO protection, VFP/NEON enable, ReplayHooksOnEngine,
|
||||
// the MiscStubDispatch/mem_fault_hook_cb/profiling hooks, and every
|
||||
// diagnostic one-off probe hook - factored out so BOTH the thread's
|
||||
// primary engine (EnsureThreadEngine) and any per-depth nested engine
|
||||
// (GetOrCreateNestedEngine) get the exact same setup. Returns nullptr on
|
||||
// any hard failure (logs its own reason). Deliberately does NOT touch
|
||||
// t_state_ or carve a guest stack - callers own that part, since a
|
||||
// nested engine shares the primary engine's existing stack range rather
|
||||
// than getting its own (see CallGuestFunction's SP-reuse logic).
|
||||
uc_engine* CreateConfiguredEngine();
|
||||
// Returns (creating on first use) the uc_engine* this thread uses for
|
||||
// CallGuestFunction calls at the given reentrancy depth (>=1; depth 0
|
||||
// always uses t_state_.uc directly, see CallGuestFunction) - see
|
||||
// ThreadState::nestedEngines' own comment. Returns nullptr and logs if
|
||||
// depth exceeds kMaxNestedEngines.
|
||||
uc_engine* GetOrCreateNestedEngine(uint32_t depth);
|
||||
// The ONE UC_HOOK_CODE ever registered over the whole misc-stub arena
|
||||
// (see EnsureThreadEngine) - looks up the real {callback, userData} for
|
||||
// the faulting address in misc_stub_dispatch_table_ via O(1) array
|
||||
// indexing and delegates to it. Static (matches uc_cb_hookcode_t's
|
||||
// plain-function-pointer signature - no `this` to pass) - reaches state
|
||||
// via GuestEngine::Instance(), same singleton-access pattern used
|
||||
// throughout this file's own free-function callbacks. See
|
||||
// misc_stub_arena_start_'s own comment (above) for why this exists.
|
||||
static void MiscStubDispatch(uc_engine* uc, uint64_t address, uint32_t size, void* userData);
|
||||
// G2H()'s out-of-range guard - see G2H()'s own comment. Rate-limited
|
||||
// (first kMaxOutOfRangeG2HLogs occurrences, then a final "suppressed"
|
||||
// notice and silence) so a call site that gets hit in a loop can't
|
||||
// flood logcat; different bad addresses across those occurrences
|
||||
// likely mean different underlying bugs, so this logs every one of
|
||||
// them up to the cap rather than a single "logged once ever" flag.
|
||||
void LogOutOfRangeG2H(GuestAddr addr) const;
|
||||
// Carves one fresh, never-reused kThreadStackSize-byte region out of the
|
||||
// shared thread-stacks arena and returns its TOP (highest usable
|
||||
// address) - 0 if the arena is exhausted (see kMaxGuestThreads).
|
||||
GuestAddr CarveThreadStack();
|
||||
|
||||
// Cap on ThreadState::nestedEngines below - see its own comment. Purely
|
||||
// a safety net (mirrors kMaxCallIterations' "not an expected limit"
|
||||
// philosophy), not a value callers should ever need to approach.
|
||||
static constexpr uint32_t kMaxNestedEngines = 8;
|
||||
|
||||
// Per-host-thread state - see class comment's "Multithreading model".
|
||||
// `static thread_local` because GuestEngine is a singleton (one logical
|
||||
// instance) but each real host thread needs to see its OWN CPU/stack -
|
||||
// this is the ONLY thing that needs to be per-thread; host_region_,
|
||||
// heap_, and every *_cursor_/*_end_ arena boundary below are genuinely
|
||||
// shared (real threads share one process's memory, which is exactly
|
||||
// what host_region_ already models).
|
||||
struct ThreadState {
|
||||
uc_engine* uc = nullptr;
|
||||
GuestAddr stackBase = 0, stackTop = 0;
|
||||
// R1 as it stood the instant the most recent CallGuestFunction() on
|
||||
// THIS thread finished (before the reentrancy save/restore at the
|
||||
// end of that function overwrites it with the outer call's value) -
|
||||
// the high word of a 64-bit (long/double) guest return, which the
|
||||
// public CallGuestFunction() API itself only ever exposes r0 of. See
|
||||
// LastCallHighWord()'s own comment.
|
||||
uint32_t lastCallHiWord = 0;
|
||||
// Diagnostic (2026-09-16, ARM64_TRANSLATION_LAYER.md - synthetic
|
||||
// unit test confirmed a reentrant CallGuestFunction() call, invoked
|
||||
// from within a UC_HOOK_CODE callback that's itself running during
|
||||
// an already-active uc_emu_start() on this thread's engine, hangs
|
||||
// (a nested uc_emu_start() on the same uc_engine* is not safely
|
||||
// reentrant in this Unicorn build). >0 means CallGuestFunction is
|
||||
// currently executing on this thread - lets CallGuestFunction log
|
||||
// when it's entered reentrantly, to check whether the REAL game
|
||||
// code path that leads to the sub_43FDE0 crash ever actually
|
||||
// triggers this exact mechanism.
|
||||
uint32_t callDepth = 0;
|
||||
// Fix (2026-09-16, ARM64_TRANSLATION_LAYER.md - the confirmed
|
||||
// uc_emu_start() reentrancy hang). One lazily-created uc_engine* per
|
||||
// reentrancy depth beyond the outermost (index 0 here == depth 1,
|
||||
// since depth 0 always uses `uc` above) - nestedEngines[callDepth-1]
|
||||
// is the engine a CallGuestFunction() invoked from within a
|
||||
// UC_HOOK_CODE callback (itself running during an already-active
|
||||
// uc_emu_start() on `uc`) runs on, instead of re-entering `uc`
|
||||
// itself. Created once per depth per thread and kept for the
|
||||
// thread's lifetime (same "leaked deliberately, cheap to keep"
|
||||
// pattern as `uc` itself - see EnsureThreadEngine), not
|
||||
// recreated per call. kMaxNestedEngines is a generous safety cap
|
||||
// (deepest depth actually observed live was 2), not an expected
|
||||
// limit - see GetOrCreateNestedEngine.
|
||||
uc_engine* nestedEngines[kMaxNestedEngines] = {};
|
||||
// Fix (2026-09-16, ARM64_TRANSLATION_LAYER.md - the WRITE_PROT
|
||||
// fault @0xac77e8/sub_3D58BC bisection). Each depth's own DEDICATED
|
||||
// stack range, top address only (base = top - kStackSize, same
|
||||
// convention as stackTop/stackBase above) - carved via
|
||||
// CarveThreadStack() the same way a real host thread's own stack
|
||||
// is, the first time that depth is used. Without this, a depth>0
|
||||
// call's SP defaults to "this engine's own SP minus a small gap",
|
||||
// which - since nestedEngines[i] is a SEPARATE engine with its OWN
|
||||
// independent SP, unrelated to how deep the OUTER (suspended)
|
||||
// frame actually is in ITS stack - always lands near the SAME
|
||||
// small window close to stackTop, REGARDLESS of depth or which
|
||||
// outer frame triggered it. Confirmed live: an outer frame that
|
||||
// itself happens to run near the top of its own thread's stack
|
||||
// (sub_75E40, entered only a few frames deep) collided directly
|
||||
// with reentrant JNI/misc-stub and pthread_once (sub_87b968/
|
||||
// sub_88ccd0) calls all anchored at that same stackTop-adjacent
|
||||
// window, corrupting the outer frame's own saved-register stack
|
||||
// slot. A dedicated per-depth stack (same isolation principle
|
||||
// nestedEngines already gives the CPU register file) makes this
|
||||
// physically impossible - see GetOrCreateNestedEngine.
|
||||
GuestAddr nestedStackTop[kMaxNestedEngines] = {};
|
||||
};
|
||||
static thread_local ThreadState t_state_;
|
||||
|
||||
uint8_t* host_region_ = nullptr; // guest address 0 == this host pointer
|
||||
uint32_t region_size_ = 0;
|
||||
GuestAddr image_end_ = 0;
|
||||
// PT_GNU_RELRO range (page-aligned, conservatively rounded inward - see
|
||||
// MapSegments' own comment), mprotect'd UC_PROT_READ on every engine
|
||||
// after relocations write into it. 0 size = not present in this ELF /
|
||||
// rounding left nothing to protect - skip.
|
||||
GuestAddr relro_start_ = 0;
|
||||
uint32_t relro_size_ = 0;
|
||||
GuestAddr heap_base_ = 0, heap_end_ = 0;
|
||||
GuestAddr trampoline_cursor_ = 0, trampoline_end_ = 0;
|
||||
GuestAddr import_stub_cursor_ = 0, import_stub_end_ = 0;
|
||||
GuestAddr misc_stub_cursor_ = 0, misc_stub_end_ = 0; // AllocCodeStub arena (jni_shim.cpp's 233 slots, etc.)
|
||||
// Fixed start of the misc-stub arena (misc_stub_cursor_ itself moves as
|
||||
// a bump pointer, so a separate fixed value is needed to compute a
|
||||
// stub's index later) - set once, at the same place misc_stub_cursor_
|
||||
// gets its own one-time initial value. See MiscStubDispatch's own
|
||||
// comment (guest_engine.cpp) for why this exists: 2026-09-06,
|
||||
// ARM64_TRANSLATION_LAYER.md - consolidating ~600+ individually
|
||||
// Unicorn-registered UC_HOOK_CODE hooks (one per AllocCodeStub call)
|
||||
// into one hook + an O(1) array lookup, after confirming (both
|
||||
// empirically, via a 284x isolated-vs-real-context benchmark gap, and
|
||||
// mechanistically, via Unicorn's own hook storage being a linked list
|
||||
// walked per translated block - third_party/unicorn/include/uc_priv.h)
|
||||
// that registering hundreds of individual hooks was a real, engine-wide
|
||||
// performance tax paid on every block anywhere in the address space.
|
||||
GuestAddr misc_stub_arena_start_ = 0;
|
||||
GuestAddr control_cursor_ = 0, control_end_ = 0; // AllocPermanent arena - see its own comment
|
||||
GuestAddr thread_stacks_cursor_ = 0, thread_stacks_end_ = 0; // CarveThreadStack arena
|
||||
GuestAddr mmap_cursor_ = 0, mmap_end_ = 0; // AllocMmap arena - see its own comment
|
||||
|
||||
std::mutex thread_stacks_mutex_; // guards thread_stacks_cursor_ (concurrent pthread_create calls)
|
||||
std::mutex control_mutex_; // guards control_cursor_
|
||||
std::mutex mmap_mutex_; // guards mmap_cursor_
|
||||
|
||||
GuestHeap heap_;
|
||||
|
||||
std::unordered_map<std::string, ImportShimFn> registered_shims_;
|
||||
std::unordered_map<std::string, GuestAddr> registered_data_symbols_; // see RegisterDataSymbol
|
||||
std::vector<DataSymbolSetupFn> data_symbol_setup_fns_; // see RegisterDataSymbolSetup
|
||||
std::unordered_map<std::string, GuestAddr> import_stub_by_symbol_; // dedupe: one stub per symbol name
|
||||
std::unordered_map<GuestAddr, ImportShimFn> shim_by_stub_addr_;
|
||||
std::unordered_map<GuestAddr, std::string> unresolved_stub_names_; // for shims we don't implement - logged once, then return 0
|
||||
|
||||
struct TrampolineHookEntry {
|
||||
void (*dispatch)(GuestEngine&, void*);
|
||||
void* userData;
|
||||
std::string debugName;
|
||||
};
|
||||
|
||||
// Every UC_HOOK_CODE ever installed (import stubs, JNI/GLES slots,
|
||||
// trampoline hooks) - replayed onto each new guest thread's own engine
|
||||
// by ReplayHooksOnEngine, since Unicorn hooks are per-engine. Installed
|
||||
// once, during single-threaded setup (before any guest thread exists),
|
||||
// but mutex-guarded anyway since EnsureThreadEngine (reading this list)
|
||||
// could in principle race a very-early AllocCodeStub call.
|
||||
struct HookRegistration {
|
||||
uc_cb_hookcode_t callback;
|
||||
void* userData;
|
||||
GuestAddr addr;
|
||||
};
|
||||
std::vector<HookRegistration> hook_registrations_;
|
||||
std::mutex hook_registrations_mutex_;
|
||||
|
||||
// AllocCodeStub's own dispatch table (see MiscStubDispatch,
|
||||
// guest_engine.cpp) - one entry per stub, in allocation order, which
|
||||
// equals address order since misc_stub_cursor_ is a pure sequential
|
||||
// 4-byte bump allocator with no frees (same guarantee hook_registrations_
|
||||
// already relied on for its own replay). Index = (addr -
|
||||
// misc_stub_arena_start_) / 4. Replaces per-stub uc_hook_add calls with
|
||||
// one array lookup - see misc_stub_arena_start_'s own comment for why.
|
||||
struct MiscStubEntry {
|
||||
uc_cb_hookcode_t callback;
|
||||
void* userData;
|
||||
};
|
||||
std::vector<MiscStubEntry> misc_stub_dispatch_table_;
|
||||
std::mutex misc_stub_dispatch_table_mutex_;
|
||||
|
||||
// See crashed()'s own comment. Global (not per-thread) on purpose - a
|
||||
// fault on ANY thread means the ONE shared host_region_ every thread's
|
||||
// engine maps may be corrupted for everyone, not just the thread that
|
||||
// happened to hit it.
|
||||
std::atomic<bool> crashed_{false};
|
||||
std::atomic<bool> crashed_logged_{false}; // so the "refusing to run" log fires once, not on every retry
|
||||
// Counter (was a one-shot bool) for CallGuestFunction's 2026-09-16
|
||||
// reentrancy probe - the first log-once version only ever showed the
|
||||
// FIRST reentrant call, which turned out (2026-09-16, same-day
|
||||
// follow-up) to be a harmless trivial stub. Now logs every occurrence
|
||||
// up to reentrancy_log_cap_ (see CallGuestFunction), so occurrences
|
||||
// CLOSER to an actual crash are visible too - capped, not fully
|
||||
// unconditional, because a runaway retry loop (the exact synthetic-test
|
||||
// scenario this probe was built to detect) re-enters at ~6000 calls/sec
|
||||
// against the SAME target and would otherwise flood logcat's ring
|
||||
// buffer with nothing else visible within milliseconds.
|
||||
std::atomic<int> reentrancy_log_count_{0};
|
||||
static constexpr int kReentrancyLogCap = 200;
|
||||
};
|
||||
|
||||
// Diagnostic for the render-stall investigation (see
|
||||
// ARM64_TRANSLATION_LAYER.md's "Periodic live instruction-trace dump" plan)
|
||||
// - starts a detached background thread that periodically logs the last N
|
||||
// executed guest blocks in true execution order (not a sampled histogram
|
||||
// like profiler.h's own dump), so a loop that never faults (and so never
|
||||
// hits CallGuestFunction's own fault-branch trace dump) can still be traced.
|
||||
void StartLiveTraceDumpThread();
|
||||
@@ -0,0 +1,144 @@
|
||||
#pragma once
|
||||
|
||||
// Typed calling-convention layer on top of GuestEngine's raw
|
||||
// CallGuestFunction/InstallTrampolineHookRaw. This is what lets the rest of
|
||||
// mpcore's existing hook code (main.cpp, lan_event_injection.h, ...) keep
|
||||
// its original shape almost unchanged: a "resolved function pointer" this
|
||||
// codebase already declares as e.g.
|
||||
// typedef void* (*RaceEventCtorFn)();
|
||||
// static RaceEventCtorFn RaceEventCtor = (RaceEventCtorFn)APP_ADDR(OFFSET);
|
||||
// becomes
|
||||
// static GuestFn<void*> RaceEventCtor(OFFSET);
|
||||
// and is still called exactly the same way (`RaceEventCtor()`). Likewise
|
||||
// InstallArmTrampolineHook's "returns a callable orig_X" pattern becomes
|
||||
// InstallTrampolineHook<Ret,Args...>(target, &Hook_X, "name"), still
|
||||
// returning something callable the same way.
|
||||
//
|
||||
// See ARM64_TRANSLATION_LAYER.md and guest_engine.h's own class comment for
|
||||
// the full design; this file is deliberately "just" marshaling glue.
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "guest_engine.h"
|
||||
|
||||
// ---- Value marshaling ----
|
||||
// Pointer-shaped C++ types translate through G2H/H2G automatically.
|
||||
// Everything else (int, uint32_t, bool, enums, ...) passes through as a raw
|
||||
// 32-bit register value unchanged. This deliberately also covers this
|
||||
// codebase's existing "int, but actually holds an address" declarations
|
||||
// (e.g. GetCacheContextFn(int anyComponentPtr)) INCORRECTLY if left as
|
||||
// `int` - see the port notes in main.cpp/lan_event_injection.h for exactly
|
||||
// which declarations were changed from `int` to a real pointer type for
|
||||
// this reason, and which genuinely small integers (paintJobIndex, evType,
|
||||
// deltaMs, ...) were deliberately left as plain integer types.
|
||||
template<typename T, typename = void>
|
||||
struct GuestMarshal {
|
||||
static uint32_t ToReg(T v) { return static_cast<uint32_t>(v); }
|
||||
static T FromReg(uint32_t v) { return static_cast<T>(v); }
|
||||
};
|
||||
template<typename T>
|
||||
struct GuestMarshal<T, std::enable_if_t<std::is_pointer<T>::value>> {
|
||||
static uint32_t ToReg(T v) {
|
||||
return v ? GuestEngine::Instance().H2G(v) : 0;
|
||||
}
|
||||
static T FromReg(uint32_t v) {
|
||||
return v ? reinterpret_cast<T>(GuestEngine::Instance().G2H(v)) : nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
// Max args GuestFn/InstallTrampolineHook support - r0-r3 plus stack args,
|
||||
// generous headroom over anything this codebase's hooks or the JNI shim's
|
||||
// own trampolines actually need.
|
||||
constexpr size_t kMaxGuestFnArgs = 10;
|
||||
|
||||
namespace guest_fn_detail {
|
||||
|
||||
template<typename Ret, typename... Args, size_t... I>
|
||||
uint32_t InvokeAndPack(Ret (*fn)(Args...), const uint32_t regs[kMaxGuestFnArgs], std::index_sequence<I...>) {
|
||||
if constexpr (std::is_void<Ret>::value) {
|
||||
fn(GuestMarshal<Args>::FromReg(regs[I])...);
|
||||
return 0;
|
||||
} else {
|
||||
Ret result = fn(GuestMarshal<Args>::FromReg(regs[I])...);
|
||||
return GuestMarshal<Ret>::ToReg(result);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
void DispatchCall(GuestEngine& eng, Ret (*fn)(Args...)) {
|
||||
static_assert(sizeof...(Args) <= kMaxGuestFnArgs, "too many args for GuestFn/InstallTrampolineHook");
|
||||
uint32_t r0 = 0, r1 = 0, r2 = 0, r3 = 0, sp = 0;
|
||||
uc_reg_read(eng.uc(), UC_ARM_REG_R0, &r0);
|
||||
uc_reg_read(eng.uc(), UC_ARM_REG_R1, &r1);
|
||||
uc_reg_read(eng.uc(), UC_ARM_REG_R2, &r2);
|
||||
uc_reg_read(eng.uc(), UC_ARM_REG_R3, &r3);
|
||||
uc_reg_read(eng.uc(), UC_ARM_REG_SP, &sp);
|
||||
uint32_t regs[kMaxGuestFnArgs];
|
||||
for (size_t i = 0; i < sizeof...(Args); i++) {
|
||||
regs[i] = eng.ReadIncomingArg((int)i, r0, r1, r2, r3, sp);
|
||||
}
|
||||
uint32_t result = InvokeAndPack(fn, regs, std::index_sequence_for<Args...>{});
|
||||
uint32_t lr = 0;
|
||||
uc_reg_read(eng.uc(), UC_ARM_REG_LR, &lr);
|
||||
uc_reg_write(eng.uc(), UC_ARM_REG_R0, &result);
|
||||
// Simulate "the hooked function ran and returned" - jump straight to
|
||||
// the caller's LR. Unicorn switches ARM/Thumb decode based on bit0 of
|
||||
// the PC value written, matching real AArch32 interworking.
|
||||
uc_reg_write(eng.uc(), UC_ARM_REG_PC, &lr);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void PackArgs(uint32_t*, size_t) {}
|
||||
template<typename Head, typename... Tail>
|
||||
void PackArgs(uint32_t* regs, size_t i, Head h, Tail... tail) {
|
||||
regs[i] = GuestMarshal<Head>::ToReg(h);
|
||||
PackArgs(regs, i + 1, tail...);
|
||||
}
|
||||
|
||||
} // namespace guest_fn_detail
|
||||
|
||||
// A resolved, callable guest function - the "GetOutputNode/RaceEventCtor/
|
||||
// ..." replacement for a raw `(FnType)APP_ADDR(OFFSET)` cast.
|
||||
template<typename Ret, typename... Args>
|
||||
class GuestFn {
|
||||
public:
|
||||
static_assert(sizeof...(Args) <= kMaxGuestFnArgs, "too many args for GuestFn");
|
||||
GuestFn() = default;
|
||||
explicit GuestFn(GuestAddr addr) : addr_(addr) {}
|
||||
explicit operator bool() const { return addr_ != 0; }
|
||||
GuestAddr addr() const { return addr_; }
|
||||
|
||||
Ret operator()(Args... args) const {
|
||||
uint32_t regs[kMaxGuestFnArgs] = {0};
|
||||
guest_fn_detail::PackArgs(regs, 0, args...);
|
||||
uint32_t r0 = GuestEngine::Instance().CallGuestFunction(addr_, regs, (int)sizeof...(Args));
|
||||
if constexpr (std::is_void<Ret>::value) {
|
||||
(void)r0;
|
||||
} else {
|
||||
return GuestMarshal<Ret>::FromReg(r0);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
GuestAddr addr_ = 0;
|
||||
};
|
||||
|
||||
// Installs a trampoline hook exactly like this codebase's old
|
||||
// InstallArmTrampolineHook, but backed by GuestEngine - see this file's own
|
||||
// top comment and guest_engine.h's class comment for the mechanism. hookFn
|
||||
// must be a plain (non-capturing) function pointer, matching every existing
|
||||
// Hook_X function in this codebase already.
|
||||
template<typename Ret, typename... Args>
|
||||
GuestFn<Ret, Args...> InstallTrampolineHook(GuestAddr target, Ret (*hookFn)(Args...), const char* debugName) {
|
||||
using FnPtr = Ret (*)(Args...);
|
||||
auto* ctx = new FnPtr(hookFn); // leaked deliberately, see guest_engine.cpp's own note
|
||||
|
||||
auto dispatch = +[](GuestEngine& eng, void* userData) {
|
||||
auto* fn = static_cast<FnPtr*>(userData);
|
||||
guest_fn_detail::DispatchCall(eng, *fn);
|
||||
};
|
||||
|
||||
GuestAddr tramp = GuestEngine::Instance().InstallTrampolineHookRaw(target, dispatch, ctx, debugName);
|
||||
return GuestFn<Ret, Args...>(tramp);
|
||||
}
|
||||
@@ -21,18 +21,30 @@ void GuestHeap::Init(uint8_t* hostBase, GuestAddr arenaStart, uint32_t arenaSize
|
||||
host_base_ = hostBase;
|
||||
arena_start_ = arenaStart;
|
||||
arena_end_ = arenaStart + arenaSize;
|
||||
free_cursor_ = arenaStart;
|
||||
for (auto& head : free_list_heads_) head = 0;
|
||||
// Align the bump cursor so every payload (header is a multiple of kAlign
|
||||
// by construction, see BlockHeader's own comment) comes back kAlign-aligned.
|
||||
free_cursor_ = AlignUp(arenaStart, kAlign);
|
||||
free_by_size_.clear();
|
||||
live_bytes_ = peak_live_bytes_ = free_bytes_ = 0;
|
||||
free_blocks_ = 0;
|
||||
}
|
||||
|
||||
int GuestHeap::SizeClassFor(uint32_t payload) {
|
||||
if (payload > kMaxSizeClassBytes) return -1;
|
||||
uint32_t classBytes = kAlign;
|
||||
for (int i = 0; i < kNumSizeClasses; i++) {
|
||||
if (payload <= classBytes) return i;
|
||||
classBytes <<= 1;
|
||||
void GuestHeap::PushFree(GuestAddr dataAddr, uint32_t size) {
|
||||
free_bytes_ += size;
|
||||
free_blocks_++;
|
||||
auto it = free_by_size_.find(size);
|
||||
uint32_t next = (it == free_by_size_.end()) ? 0u : it->second;
|
||||
memcpy(host_base_ + dataAddr, &next, 4);
|
||||
if (it == free_by_size_.end()) {
|
||||
free_by_size_.emplace(size, dataAddr);
|
||||
} else {
|
||||
it->second = dataAddr;
|
||||
}
|
||||
return -1; // unreachable given the check above, but safe
|
||||
}
|
||||
|
||||
uint32_t GuestHeap::BlockSize(GuestAddr addr) const {
|
||||
if (!IsValidLiveBlock(addr)) return 0;
|
||||
return HeaderAt(addr)->size;
|
||||
}
|
||||
|
||||
GuestAddr GuestHeap::Alloc(uint32_t size) {
|
||||
@@ -41,84 +53,78 @@ GuestAddr GuestHeap::Alloc(uint32_t size) {
|
||||
uint64_t callNo = g_allocCalls2.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
uint64_t t0 = NowNs2();
|
||||
uint32_t payload = AlignUp(size, kAlign);
|
||||
int classIdx = SizeClassFor(payload);
|
||||
auto logIfSampled = [&](const char* how) {
|
||||
if (callNo <= 5 || callNo % 20000 == 0) {
|
||||
Log("GuestHeap::Alloc: call #%llu (%s), took %lluns",
|
||||
(unsigned long long)callNo, how, (unsigned long long)(NowNs2() - t0));
|
||||
}
|
||||
};
|
||||
// Heap gauge (2026-09-19). Reads the member counters DIRECTLY rather
|
||||
// than calling GetStats() - this function already holds mutex_, and
|
||||
// GetStats takes it too, which on a non-recursive std::mutex would
|
||||
// deadlock. Tied to allocation count rather than a timer so it reports
|
||||
// during heavy loading and stays silent when the game is idle.
|
||||
// "live" is what the guest actually holds right now; "carved" is how far
|
||||
// the bump cursor has travelled, so carved-minus-live-minus-free is the
|
||||
// fragmentation this allocator has not yet managed to give back.
|
||||
if (callNo % 50000 == 0) {
|
||||
constexpr double kMB = 1024.0 * 1024.0;
|
||||
Log("GuestHeap: gauge @%lluk allocs - live=%.1fMB peak=%.1fMB free=%.1fMB "
|
||||
"carved=%.1fMB of %.1fMB arena | freeBlocks=%u distinctSizes=%u",
|
||||
(unsigned long long)(callNo / 1000), live_bytes_ / kMB, peak_live_bytes_ / kMB,
|
||||
free_bytes_ / kMB,
|
||||
((free_cursor_ >= arena_start_) ? (free_cursor_ - arena_start_) : 0) / kMB,
|
||||
((arena_end_ >= arena_start_) ? (arena_end_ - arena_start_) : 0) / kMB,
|
||||
free_blocks_, (unsigned)free_by_size_.size());
|
||||
}
|
||||
|
||||
if (classIdx >= 0) {
|
||||
uint32_t classBytes = SizeClassBytes(classIdx);
|
||||
GuestAddr freeAddr = free_list_heads_[classIdx];
|
||||
if (freeAddr) {
|
||||
// Pop the free-list head - the "next" pointer lives in the
|
||||
// block's own (now-unused) payload, always at least kAlign=8
|
||||
// bytes so a 32-bit guest address always fits.
|
||||
uint32_t next = 0;
|
||||
memcpy(&next, host_base_ + freeAddr, 4);
|
||||
free_list_heads_[classIdx] = next;
|
||||
HeaderAt(freeAddr)->free = 0;
|
||||
std::memset(host_base_ + freeAddr, 0, classBytes);
|
||||
logIfSampled("pool-reuse");
|
||||
return freeAddr;
|
||||
// Reuse: smallest free block that fits. O(log n) via the size-ordered
|
||||
// map (an exact-size hit costs the same lookup) - deliberately not a
|
||||
// linear scan over free blocks, see guest_heap.h's 2026-09-19 note and
|
||||
// this allocator's own 2026-09-06 history with an O(n) scan.
|
||||
auto it = free_by_size_.lower_bound(payload);
|
||||
if (it != free_by_size_.end()) {
|
||||
uint32_t blockSize = it->first;
|
||||
GuestAddr blockAddr = it->second;
|
||||
uint32_t next = 0;
|
||||
memcpy(&next, host_base_ + blockAddr, 4);
|
||||
if (next) {
|
||||
it->second = next;
|
||||
} else {
|
||||
free_by_size_.erase(it);
|
||||
}
|
||||
// No free block of this class yet - bump-allocate a new one at the
|
||||
// CLASS size (not the raw requested payload), so any future free()
|
||||
// of a different-sized request that rounds to this same class can
|
||||
// still reuse it exactly.
|
||||
uint32_t needed = sizeof(BlockHeader) + classBytes;
|
||||
if (free_cursor_ + needed > arena_end_) {
|
||||
// Loud on purpose (2026-09-18) - since the size-class widening,
|
||||
// this is now the path any oversized allocation's exhaustion
|
||||
// actually takes (the old "large path" fallback below is only
|
||||
// reached past 512MiB, effectively unreachable in practice) -
|
||||
// losing this class's own copy of the 2026-09-17 "HEAP
|
||||
// EXHAUSTED" loud-logging fix would silently regress that
|
||||
// diagnostic for the exact case it was added for.
|
||||
Log("GuestHeap::Alloc: HEAP EXHAUSTED - class=%d classBytes=%u needed=%u but only "
|
||||
"%u bytes remain (free_cursor_=0x%x arena_end_=0x%x) - returning 0 (null)",
|
||||
classIdx, classBytes, needed,
|
||||
(free_cursor_ <= arena_end_) ? (arena_end_ - free_cursor_) : 0,
|
||||
free_cursor_, arena_end_);
|
||||
return 0;
|
||||
}
|
||||
BlockHeader* h = reinterpret_cast<BlockHeader*>(host_base_ + free_cursor_);
|
||||
h->magic = kMagic;
|
||||
h->size = classBytes;
|
||||
free_bytes_ -= blockSize;
|
||||
free_blocks_--;
|
||||
BlockHeader* h = HeaderAt(blockAddr);
|
||||
h->free = 0;
|
||||
GuestAddr dataAddr = free_cursor_ + sizeof(BlockHeader);
|
||||
free_cursor_ += needed;
|
||||
std::memset(host_base_ + dataAddr, 0, classBytes);
|
||||
logIfSampled("pool-bump-new");
|
||||
return dataAddr;
|
||||
// Split the tail back into the free structure when what is left over
|
||||
// can hold a header plus a minimally useful payload. Without this,
|
||||
// exact sizing would just re-create stranding in a new shape (a 8MiB
|
||||
// block permanently consumed by a 64KiB request).
|
||||
uint32_t remainder = blockSize - payload;
|
||||
if (remainder >= kMinSplitRemainder) {
|
||||
h->size = payload;
|
||||
GuestAddr tailHeader = blockAddr + payload;
|
||||
BlockHeader* th = reinterpret_cast<BlockHeader*>(host_base_ + tailHeader);
|
||||
th->magic = kMagic;
|
||||
th->size = remainder - sizeof(BlockHeader);
|
||||
th->free = 1;
|
||||
th->pad = 0;
|
||||
PushFree(tailHeader + sizeof(BlockHeader), th->size);
|
||||
}
|
||||
live_bytes_ += h->size;
|
||||
if (live_bytes_ > peak_live_bytes_) peak_live_bytes_ = live_bytes_;
|
||||
logIfSampled("reuse");
|
||||
return blockAddr;
|
||||
}
|
||||
|
||||
// Fallback for a request bigger than even the largest size class
|
||||
// (kMaxSizeClassBytes, now 512MiB as of the 2026-09-18 widening - see
|
||||
// guest_heap.h's own comment) - genuinely unreachable in practice for
|
||||
// this codebase's real allocation sizes, so still just bump-allocates
|
||||
// fresh and never reuses on free, same as every size WAS before that
|
||||
// widening. Kept only as a defensive ceiling, not the routine path it
|
||||
// used to be.
|
||||
// Nothing reusable - carve a fresh block at the EXACT requested size.
|
||||
uint32_t needed = sizeof(BlockHeader) + payload;
|
||||
// Diagnostic (2026-09-17, ARM64_TRANSLATION_LAYER.md - the 0x1e0
|
||||
// wild-jump chase; kept live after the 2026-09-18 size-class widening
|
||||
// since this branch, though now rare, still never reclaims). Logging
|
||||
// every allocation that lands here (>512MiB, past even the widened
|
||||
// size-class ceiling), and LOUDLY logging outright exhaustion (which a
|
||||
// silent `return 0` would let downstream shims like Shim_aeabi_memcpy's
|
||||
// own null-check quietly swallow instead of failing loud).
|
||||
if (payload > 1024 * 1024) {
|
||||
Log("GuestHeap::Alloc: request past the widened size-class ceiling, payload=%u "
|
||||
"(%.1fMB) - free_cursor_=0x%x arena_end_=0x%x headroom=%u bytes",
|
||||
payload, payload / (1024.0 * 1024.0), free_cursor_, arena_end_,
|
||||
(free_cursor_ <= arena_end_) ? (arena_end_ - free_cursor_) : 0);
|
||||
}
|
||||
if (free_cursor_ + needed > arena_end_) {
|
||||
Log("GuestHeap::Alloc: HEAP EXHAUSTED - payload=%u needed=%u but only %u bytes "
|
||||
"remain (free_cursor_=0x%x arena_end_=0x%x) - returning 0 (null)",
|
||||
// Loud on purpose (2026-09-17): a silent `return 0` gets swallowed by
|
||||
// downstream null-checks, and the real EA code does not check malloc.
|
||||
Log("GuestHeap::Alloc: HEAP EXHAUSTED - payload=%u needed=%u but only %u bytes remain "
|
||||
"(free_cursor_=0x%x arena_end_=0x%x) - returning 0 (null)",
|
||||
payload, needed, (free_cursor_ <= arena_end_) ? (arena_end_ - free_cursor_) : 0,
|
||||
free_cursor_, arena_end_);
|
||||
return 0;
|
||||
@@ -127,10 +133,12 @@ GuestAddr GuestHeap::Alloc(uint32_t size) {
|
||||
h->magic = kMagic;
|
||||
h->size = payload;
|
||||
h->free = 0;
|
||||
h->pad = 0;
|
||||
GuestAddr dataAddr = free_cursor_ + sizeof(BlockHeader);
|
||||
free_cursor_ += needed;
|
||||
std::memset(host_base_ + dataAddr, 0, payload);
|
||||
logIfSampled("large-path");
|
||||
live_bytes_ += payload;
|
||||
if (live_bytes_ > peak_live_bytes_) peak_live_bytes_ = live_bytes_;
|
||||
logIfSampled("bump-new");
|
||||
return dataAddr;
|
||||
}
|
||||
|
||||
@@ -157,19 +165,23 @@ void GuestHeap::Free(GuestAddr addr) {
|
||||
}
|
||||
BlockHeader* h = HeaderAt(addr);
|
||||
h->free = 1;
|
||||
// Pooled (size-classed) blocks always have h->size exactly equal to
|
||||
// their class's byte size (Alloc() only ever bump-allocates a NEW block
|
||||
// at the class size, never the raw request) - push it onto that class's
|
||||
// free list for O(1) reuse. As of the 2026-09-18 widening (see
|
||||
// guest_heap.h's own comment) this now covers every size up to 512MiB,
|
||||
// not just the original <=64KiB range - only a request bigger than
|
||||
// that (h->size > kMaxSizeClassBytes, SizeClassFor returns -1) still
|
||||
// falls outside every class and is simply left free without joining
|
||||
// any list, same tradeoff Alloc() documents for that now-rare fallback.
|
||||
int classIdx = SizeClassFor(h->size);
|
||||
if (classIdx >= 0 && SizeClassBytes(classIdx) == h->size) {
|
||||
uint32_t next = free_list_heads_[classIdx];
|
||||
memcpy(host_base_ + addr, &next, 4);
|
||||
free_list_heads_[classIdx] = addr;
|
||||
}
|
||||
live_bytes_ -= h->size;
|
||||
// Every block carries its exact payload size, so it goes straight onto
|
||||
// the free list for that size - no class rounding, nothing excluded by
|
||||
// being "too big" (the old scheme silently never reclaimed anything past
|
||||
// its largest class). O(log n) for the map lookup.
|
||||
PushFree(addr, h->size);
|
||||
}
|
||||
|
||||
GuestHeap::Stats GuestHeap::GetStats() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
Stats s{};
|
||||
s.liveBytes = live_bytes_;
|
||||
s.peakLiveBytes = peak_live_bytes_;
|
||||
s.freeBytes = free_bytes_;
|
||||
s.carvedBytes = (free_cursor_ >= arena_start_) ? (free_cursor_ - arena_start_) : 0;
|
||||
s.freeBlocks = free_blocks_;
|
||||
s.distinctFreeSizes = (uint32_t)free_by_size_.size();
|
||||
s.arenaBytes = (arena_end_ >= arena_start_) ? (arena_end_ - arena_start_) : 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "guest_types.h"
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
// Minimal first-fit heap allocator carved out of a fixed-size arena that
|
||||
@@ -56,21 +57,84 @@ public:
|
||||
// Returns 0 (a guest NULL) on failure, matching malloc's own contract.
|
||||
// Thread-safe (mutex-guarded) - now that real guest threads exist (see
|
||||
// emu/pthread_shim.h), any of them can call malloc/free concurrently.
|
||||
//
|
||||
// Does NOT zero the returned memory (2026-09-19). malloc makes no such
|
||||
// promise; only calloc does, and Shim_calloc now zeroes explicitly. The
|
||||
// previous behaviour zeroed the whole rounded-up class block on every
|
||||
// single allocation - for an 8MiB class that was an 8MiB memset per call,
|
||||
// and it forced physical commit of pages the guest might never touch.
|
||||
GuestAddr Alloc(uint32_t size);
|
||||
// No-ops (logged) if `addr` doesn't point at a real, currently-allocated
|
||||
// block's payload - see class comment.
|
||||
void Free(GuestAddr addr);
|
||||
// Exact payload size of a live block, or 0 if `addr` isn't one. Lets
|
||||
// Shim_realloc copy min(oldSize,newSize) instead of guessing (it used to
|
||||
// copy the NEW size out of a possibly-smaller old block).
|
||||
uint32_t BlockSize(GuestAddr addr) const;
|
||||
|
||||
struct Stats {
|
||||
uint64_t liveBytes; // payload currently handed out to the guest
|
||||
uint64_t peakLiveBytes; // high-water mark of the above
|
||||
uint64_t freeBytes; // payload sitting in the free structure, reusable
|
||||
uint64_t carvedBytes; // bump cursor travel - memory this arena has ever touched
|
||||
uint32_t freeBlocks;
|
||||
uint32_t distinctFreeSizes;
|
||||
uint32_t arenaBytes;
|
||||
};
|
||||
// Cheap: every field is a counter maintained in O(1) by Alloc/Free, not
|
||||
// computed by walking anything.
|
||||
Stats GetStats() const;
|
||||
|
||||
private:
|
||||
// 16 bytes, not 12: with a 12-byte header the bump cursor advanced by
|
||||
// 12+payload, so payload alignment alternated between 8- and 4-byte and
|
||||
// could hand a 4-aligned buffer to guest code doing 8-byte accesses.
|
||||
// Padding to 16 keeps every payload 8-aligned (arena start is aligned and
|
||||
// payloads are multiples of kAlign), and makes the split arithmetic below
|
||||
// exact. Costs 4 bytes per block, trivial next to the ~4x this file's
|
||||
// 2026-09-19 rework removes.
|
||||
struct BlockHeader {
|
||||
uint32_t magic; // kMagic if this is a real header GuestHeap itself wrote - see class comment
|
||||
uint32_t size; // payload size, not including this header
|
||||
uint32_t free; // 1 = free, 0 = in use
|
||||
uint32_t pad; // alignment only - see above
|
||||
};
|
||||
static constexpr uint32_t kMagic = 0x47484B21; // "GuestHeap blocK!" - arbitrary but distinctive
|
||||
static constexpr uint32_t kAlign = 8;
|
||||
static uint32_t AlignUp(uint32_t v, uint32_t a) { return (v + a - 1) & ~(a - 1); }
|
||||
|
||||
// 2026-09-19 REWORK - exact sizing, measured against real hardware.
|
||||
// The size-class scheme described above (kept in the history below
|
||||
// because its own reasoning was sound for the bug it fixed) rounded
|
||||
// EVERY request up to the next power of two and bump-allocated the full
|
||||
// class size. Measured cost on the Galaxy A9 running the real ARM32
|
||||
// build of this same game: native heap ~46MB in menus, ~199MB with a
|
||||
// race loaded - while this engine exhausted a 768MB arena before the
|
||||
// prologue even finished. That ~4x gap decomposes exactly as the scheme
|
||||
// predicts: ~1.5x average waste from rounding (a 4.1MB request took
|
||||
// 8MB, a 70KB request took 128KB) multiplied by the stranding caused by
|
||||
// segregated per-class free lists, where a freed 8MB block could never
|
||||
// satisfy a 64KB request no matter how much of the arena it held.
|
||||
//
|
||||
// Replaced by exact-size allocation with a single size-ordered free
|
||||
// structure. Deliberately NOT a linear best-fit scan: this allocator's
|
||||
// own history (the 2026-09-06 note above) is that an O(n) scan per
|
||||
// allocation was itself a measured performance cliff, so reuse is a
|
||||
// std::map lookup - lower_bound for "smallest free block that fits",
|
||||
// O(log n), with the same cost for an exact hit. Blocks of identical
|
||||
// size share one intrusive singly-linked list (the "next" pointer lives
|
||||
// in the free block's own payload, always >= kAlign bytes), so the map
|
||||
// holds one node per DISTINCT live free size, not per free block.
|
||||
//
|
||||
// A block larger than the request is split and the remainder returned to
|
||||
// the free structure, which is what keeps exact sizing from simply
|
||||
// re-creating stranding in a different shape. Still no coalescing of
|
||||
// adjacent free blocks - consistent with this allocator's long-standing
|
||||
// choice, and the same reasoning still applies: reuse alone is what
|
||||
// converts "grows forever" into "reaches a steady state".
|
||||
static constexpr uint32_t kMinSplitRemainder = sizeof(BlockHeader) + kAlign;
|
||||
|
||||
// ---- history: the superseded size-class scheme ----
|
||||
// Size classes: kAlign(8), 16, 32, ... up to kAlign << (kNumSizeClasses-1).
|
||||
// 27 classes tops out at 8 << 26 = 512MiB.
|
||||
//
|
||||
@@ -105,12 +169,11 @@ private:
|
||||
// blocks - see the class comment) - not needed for this fix: reuse
|
||||
// alone converts "grows forever" into "reaches a steady state," which
|
||||
// is the actual guarantee being restored here.
|
||||
static constexpr int kNumSizeClasses = 27;
|
||||
static constexpr uint32_t kMaxSizeClassBytes = kAlign << (kNumSizeClasses - 1);
|
||||
// Returns the class index for `payload`, or -1 if it's larger than the
|
||||
// biggest class (caller falls back to a fresh, never-reused allocation).
|
||||
static int SizeClassFor(uint32_t payload);
|
||||
static uint32_t SizeClassBytes(int classIdx) { return kAlign << classIdx; }
|
||||
// (kNumSizeClasses/SizeClassFor/SizeClassBytes removed 2026-09-19 with
|
||||
// the scheme they implemented - see the rework note above.)
|
||||
|
||||
// Pushes a free block onto the intrusive list for its exact size.
|
||||
void PushFree(GuestAddr dataAddr, uint32_t size);
|
||||
|
||||
BlockHeader* HeaderAt(GuestAddr addr) const {
|
||||
return reinterpret_cast<BlockHeader*>(host_base_ + addr - sizeof(BlockHeader));
|
||||
@@ -127,9 +190,18 @@ private:
|
||||
GuestAddr arena_start_ = 0;
|
||||
GuestAddr arena_end_ = 0; // one-past-the-end of the arena
|
||||
GuestAddr free_cursor_ = 0; // next never-yet-used byte (bump pointer for the "no free block fits" case)
|
||||
// Head of each size class's free list (a block's data address, or 0 =
|
||||
// empty) - see class comment. Zero-initialized; Init() also resets it
|
||||
// explicitly in case an instance is ever re-Init()'d.
|
||||
GuestAddr free_list_heads_[kNumSizeClasses] = {};
|
||||
std::mutex mutex_;
|
||||
// exact payload size -> head of that size's intrusive free list (a
|
||||
// block's data address). One map node per DISTINCT free size, not per
|
||||
// free block - see the 2026-09-19 rework note above.
|
||||
std::map<uint32_t, GuestAddr> free_by_size_;
|
||||
// Accounting for GetStats (2026-09-19). Maintained incrementally so the
|
||||
// gauge costs nothing to read - the point of it is to answer "how much
|
||||
// does this game actually need" with a measured number instead of
|
||||
// inferring it from the process's resident set, which mixes in thread
|
||||
// stacks and never shrinks once a page has been touched.
|
||||
uint64_t live_bytes_ = 0;
|
||||
uint64_t peak_live_bytes_ = 0;
|
||||
uint64_t free_bytes_ = 0;
|
||||
uint32_t free_blocks_ = 0;
|
||||
mutable std::mutex mutex_;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
#include "guest_trace.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace {
|
||||
|
||||
std::atomic<bool> g_enabled{false};
|
||||
|
||||
uint64_t NowMs() {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
|
||||
}
|
||||
|
||||
uint64_t g_epochMs = 0;
|
||||
std::once_flag g_epochOnce;
|
||||
|
||||
// Block-trace ring: raw array + atomic write index, same accepted-torn-read
|
||||
// tradeoff as guest_engine.cpp's own g_liveTraceRing (a diagnostic, not a
|
||||
// correctness-critical path) - deliberately NOT a mutex-guarded structure,
|
||||
// since this callback fires once per translated basic block and any lock
|
||||
// there would dominate the very execution speed this trace exists to
|
||||
// measure. 4M entries * 12 bytes = 48MB, comfortably bounded.
|
||||
struct BlockEntry {
|
||||
uint32_t relMs;
|
||||
uint32_t tid;
|
||||
uint32_t addr;
|
||||
};
|
||||
constexpr size_t kRingSize = 4 * 1024 * 1024;
|
||||
BlockEntry g_ring[kRingSize];
|
||||
std::atomic<uint64_t> g_pos{0};
|
||||
|
||||
constexpr char kGuestTracePath[] = "/data/data/com.ea.games.nfs13_arm/files/guest_trace.log";
|
||||
constexpr char kJniTracePath[] = "/data/data/com.ea.games.nfs13_arm/files/jni_trace.log";
|
||||
|
||||
} // namespace
|
||||
|
||||
uint64_t GuestTraceEpochMs() {
|
||||
std::call_once(g_epochOnce, [] { g_epochMs = NowMs(); });
|
||||
return g_epochMs;
|
||||
}
|
||||
|
||||
void EnableFullGuestTrace() {
|
||||
GuestTraceEpochMs(); // establish the shared epoch at the moment tracing is armed
|
||||
g_enabled.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool FullGuestTraceEnabled() {
|
||||
return g_enabled.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void FullGuestTraceHookCb(uc_engine*, uint64_t address, uint32_t, void*) {
|
||||
uint64_t idx = g_pos.fetch_add(1, std::memory_order_relaxed);
|
||||
BlockEntry& e = g_ring[idx % kRingSize];
|
||||
e.relMs = (uint32_t)(NowMs() - GuestTraceEpochMs());
|
||||
e.tid = (uint32_t)gettid();
|
||||
e.addr = (uint32_t)address;
|
||||
}
|
||||
|
||||
void StartGuestTraceDumpThread() {
|
||||
static std::atomic<bool> started{false};
|
||||
bool expected = false;
|
||||
if (!started.compare_exchange_strong(expected, true)) return; // already running
|
||||
|
||||
std::thread([]() {
|
||||
FILE* f = nullptr;
|
||||
uint64_t lastDumped = 0;
|
||||
while (true) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
if (!FullGuestTraceEnabled()) continue;
|
||||
if (!f) {
|
||||
f = fopen(kGuestTracePath, "a");
|
||||
if (!f) continue;
|
||||
fprintf(f, "---- guest_trace opened, epoch_ms(monotonic)=%llu ----\n",
|
||||
(unsigned long long)GuestTraceEpochMs());
|
||||
fflush(f);
|
||||
}
|
||||
uint64_t posNow = g_pos.load(std::memory_order_relaxed);
|
||||
if (posNow <= lastDumped) continue;
|
||||
uint64_t start = lastDumped;
|
||||
if (posNow - lastDumped > kRingSize) {
|
||||
// Consumer fell behind the writer by more than a full ring -
|
||||
// say so explicitly rather than silently presenting a gap as
|
||||
// a continuous sequence (matches caveman_retrieve's own
|
||||
// "non-adjacent" marker convention for the same reason).
|
||||
fprintf(f, "---- [guest_trace: %llu entries dropped, ring overflowed] ----\n",
|
||||
(unsigned long long)(posNow - lastDumped - kRingSize));
|
||||
start = posNow - kRingSize;
|
||||
}
|
||||
for (uint64_t i = start; i < posNow; i++) {
|
||||
const BlockEntry& e = g_ring[i % kRingSize];
|
||||
fprintf(f, "[%u] [tid=%u] 0x%x\n", e.relMs, e.tid, e.addr);
|
||||
}
|
||||
fflush(f);
|
||||
lastDumped = posNow;
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void LogJniCall(const char* fmt, ...) {
|
||||
if (!FullGuestTraceEnabled()) return;
|
||||
|
||||
char buf[512];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
static std::mutex m;
|
||||
static FILE* f = nullptr;
|
||||
std::lock_guard<std::mutex> lock(m);
|
||||
if (!f) {
|
||||
f = fopen(kJniTracePath, "a");
|
||||
if (f) {
|
||||
fprintf(f, "---- jni_trace opened, epoch_ms(monotonic)=%llu ----\n",
|
||||
(unsigned long long)GuestTraceEpochMs());
|
||||
}
|
||||
}
|
||||
if (f) {
|
||||
fprintf(f, "[%llu] [tid=%d] %s\n",
|
||||
(unsigned long long)(NowMs() - GuestTraceEpochMs()), gettid(), buf);
|
||||
fflush(f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <unicorn/unicorn.h>
|
||||
#include <cstdint>
|
||||
#include <cstdarg>
|
||||
|
||||
// Full, file-backed execution trace for the emulated engine - correlates
|
||||
// against the Galaxy A9's own `trace_agent` full-call trace (built the same
|
||||
// day, see ARM64_TRANSLATION_LAYER.md's 2026-09-06 entries) using a shared
|
||||
// monotonic-clock epoch, for the "where does the emulated engine's real
|
||||
// execution first diverge from real hardware's" investigation - both
|
||||
// devices confirmed to take the same useAssetsFileSystem()/OBB-gated path,
|
||||
// yet only one renders, so the divergence must be found downstream of that,
|
||||
// not assumed from a single-variable theory.
|
||||
//
|
||||
// Two files, deliberately not interleaved into one (still directly
|
||||
// comparable by timestamp):
|
||||
// - guest_trace.log: one line per executed basic block (UC_HOOK_BLOCK -
|
||||
// the same cheap, already-proven mechanism as profiler.h's own sampling
|
||||
// hook and guest_engine.cpp's existing TraceRing/LiveTraceRing). This is
|
||||
// BLOCK-level, not strictly call-level: reconstructing an accurate
|
||||
// guest call sequence from it means keeping only addresses that are
|
||||
// real function entry points, cross-referenced against the IDA
|
||||
// database offline - classifying that live, per block, would need a
|
||||
// disassembler in the hot path, which this project has deliberately
|
||||
// avoided everywhere else for cost reasons.
|
||||
// - jni_trace.log (written by jni_shim.cpp via LogJniCall): one line per
|
||||
// real Java-side JNI call the guest makes (Call*Method family via
|
||||
// InvokeCall, Get/Set*Field via DoGetField/DoSetField, GetMethodID/
|
||||
// GetFieldID lookups, RegisterNatives) - the same call granularity as
|
||||
// the A9 tracer's own JNI table patch, so the two are diffable line by
|
||||
// line once resolved to real names.
|
||||
void EnableFullGuestTrace();
|
||||
bool FullGuestTraceEnabled();
|
||||
|
||||
// UC_HOOK_BLOCK callback - pass directly to uc_hook_add, gated on
|
||||
// FullGuestTraceEnabled() by the caller (see guest_engine.cpp).
|
||||
void FullGuestTraceHookCb(uc_engine* uc, uint64_t address, uint32_t size, void* user_data);
|
||||
|
||||
// Starts (once) a background thread that appends newly-recorded block-trace
|
||||
// entries to guest_trace.log every 2 seconds until the process exits. No-op
|
||||
// (just sleeps) while FullGuestTraceEnabled() is false.
|
||||
void StartGuestTraceDumpThread();
|
||||
|
||||
// Monotonic-clock epoch (ms since CLOCK_MONOTONIC's own reference point),
|
||||
// established the moment EnableFullGuestTrace() is first called. Both
|
||||
// guest_trace.log and jni_trace.log timestamp every line as an offset from
|
||||
// this same epoch, so the two files line up directly - no wall-clock/RTC
|
||||
// involved, matching this project's existing NowMs()-style conventions.
|
||||
uint64_t GuestTraceEpochMs();
|
||||
|
||||
// Shared JNI-call logger (used by jni_shim.cpp) - appends a timestamped,
|
||||
// tid-tagged line to jni_trace.log. No-op while FullGuestTraceEnabled() is
|
||||
// false, so ordinary runs pay no cost beyond the one atomic flag check.
|
||||
void LogJniCall(const char* fmt, ...);
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
// Shared basic types for the ARM32-on-ARM64 in-process translation core.
|
||||
// See /ARM64_TRANSLATION_LAYER.md for the design this implements.
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// A guest address is "offset from the loaded libapp.so image's own base"
|
||||
// (vaddr 0, since the ELF is ET_DYN with a preferred base of 0 - confirmed
|
||||
// via readelf/pyelftools this session). This is DELIBERATELY the exact same
|
||||
// number space every existing OFFSET macro in this codebase already uses
|
||||
// (ARCHITECTURE.md's own "IDA addresses == file offsets == APP_ADDR
|
||||
// argument" observation) - the whole point of choosing this layout is that
|
||||
// none of the ~150 hand-derived offsets in lan_event_injection.h etc. need
|
||||
// to change.
|
||||
using GuestAddr = uint32_t;
|
||||
|
||||
// bit0 of a GuestAddr, when passed to CallGuestFunction/InstallCodeHook,
|
||||
// selects Thumb vs ARM instruction decoding - same convention this
|
||||
// project's own armhook.cpp (isThumbMode/makeThumbPtr) already used for a
|
||||
// real (non-emulated) process. Every hook site in this codebase so far
|
||||
// targets ARM-mode code (confirmed via IDA disasm before hooking each one),
|
||||
// so bit0 is 0 everywhere in practice today, but the mechanism supports
|
||||
// Thumb targets too.
|
||||
constexpr GuestAddr kThumbBit = 1;
|
||||
|
||||
enum class HookMode {
|
||||
// Callback runs, then guest execution CONTINUES from the original
|
||||
// instruction (nothing skipped) - used to observe/modify state without
|
||||
// altering control flow, or as the entry point for a "modify args, then
|
||||
// fall through to real code" hook (matches this project's existing
|
||||
// "wrap" hooks, e.g. Hook_BuildTrackScenePath).
|
||||
kWrap,
|
||||
// Callback fully replaces the target: after it returns, execution jumps
|
||||
// straight to the guest LR (as if the target function returned) without
|
||||
// ever running the target's own instructions (matches e.g.
|
||||
// Hook_CopSoundsTick's "skip the body entirely" pattern).
|
||||
kReplace,
|
||||
};
|
||||
@@ -0,0 +1,529 @@
|
||||
#include "import_shims.h"
|
||||
#include "jni_shim.h"
|
||||
#include "../util/util.h"
|
||||
#include <cstring>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <math.h>
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <android/log.h>
|
||||
|
||||
// ---- Known, documented gaps in this shim layer (prototype scope) ----
|
||||
// - Corrected (2026-08-29): `armeabi-v7a` uses the **softfp** calling
|
||||
// convention (confirmed - this is the standard, documented Android NDK
|
||||
// choice for this ABI, kept for compatibility with older soft-float
|
||||
// armeabi code even though the CPU has real VFP hardware) - float/double
|
||||
// args and return values pass through r0-r3 (and the stack) as raw bit
|
||||
// patterns, NOT through S/D VFP registers. So float marshaling via
|
||||
// `memcpy` on the raw uint32_t register value (see GetFloatField in
|
||||
// jni_shim.cpp, and every GLfloat parameter in gles_shim.cpp) is already
|
||||
// CORRECT, not a gap - the earlier version of this comment overstated the
|
||||
// risk. Only real jlong/jdouble (8-byte, register-pair-aligned) values
|
||||
// remain genuinely unhandled (see MarshalArgs*'s own comment in
|
||||
// jni_shim.cpp), which is a JNI/varargs marshaling limitation, not a
|
||||
// float-ABI one.
|
||||
// - __cxa_guard_acquire/release below are still NOT thread-safe (no atomic
|
||||
// CAS, no futex wait for a concurrent initializer) - a real race here
|
||||
// (two guest threads racing the same function-local static's first-time
|
||||
// init) would need actual fixing, not just documenting, but hasn't been
|
||||
// observed yet. Real pthread_create/join/mutex/cond support now lives in
|
||||
// emu/pthread_shim.h/.cpp (added once the prototype actually needed guest
|
||||
// threads to stop the engine deadlocking on its own worker threads - see
|
||||
// ARM64_TRANSLATION_LAYER.md). The pthread_key_*/TLS shims below now use
|
||||
// a `thread_local` value array (fixed at this same session) - only key
|
||||
// ALLOCATION (pthread_key_create's counter) is still process-wide/shared,
|
||||
// matching real bionic's own TLS-key semantics.
|
||||
// - __cxa_atexit is a no-op (guest static destructors never run) - harmless
|
||||
// for a process that's never expected to cleanly "exit" its guest image.
|
||||
// - dladdr/__dynamic_cast/getauxval/qsort/longjmp/the *printf family are
|
||||
// NOT implemented (still trap to the generic "unresolved, return 0"
|
||||
// handler) - each needs either real variadic-argument marshaling
|
||||
// (printf/snprintf/__android_log_print), a guest-callback trampoline
|
||||
// (qsort's comparator), or non-trivial semantics (longjmp) this pass
|
||||
// deliberately didn't attempt. File I/O (fopen/fread/fclose/...) is in
|
||||
// the same "deliberately not attempted" bucket - would need a guest
|
||||
// FILE* handle table, same shape as JniHandleTable, not built yet.
|
||||
// Every gap above was a deliberate scope cut for this session, not an
|
||||
// oversight - see ARM64_TRANSLATION_LAYER.md's own "open risks" section for
|
||||
// the class of work this belongs to.
|
||||
|
||||
namespace {
|
||||
|
||||
uint32_t Shim_aeabi_memcpy(GuestEngine& eng, uint32_t dest, uint32_t src, uint32_t n, uint32_t, uint32_t) {
|
||||
if (dest && src && n) memcpy(eng.G2H(dest), eng.G2H(src), n);
|
||||
return dest;
|
||||
}
|
||||
uint32_t Shim_aeabi_memmove(GuestEngine& eng, uint32_t dest, uint32_t src, uint32_t n, uint32_t, uint32_t) {
|
||||
if (dest && src && n) memmove(eng.G2H(dest), eng.G2H(src), n);
|
||||
return dest;
|
||||
}
|
||||
// AEABI memset/memclr use (dest, n, c) - deliberately reversed vs libc
|
||||
// memset(dest, c, n). See ARM IHI 0043 (AEABI) sec 7.2.
|
||||
uint32_t Shim_aeabi_memset(GuestEngine& eng, uint32_t dest, uint32_t n, uint32_t c, uint32_t, uint32_t) {
|
||||
if (dest && n) memset(eng.G2H(dest), (int)c, n);
|
||||
return dest;
|
||||
}
|
||||
// __memcpy_chk (2026-09-16, ARM64_TRANSLATION_LAYER.md - the "isolated
|
||||
// std::ostringstream repro" test): bionic's _FORTIFY_SOURCE=2 wrapper around
|
||||
// memcpy, emitted by clang whenever the compiler can prove a destination
|
||||
// object's size at compile time - unlike __aeabi_memcpy* above, this is a
|
||||
// LIBC symbol, not an AEABI one, so a freshly-built c++_static artifact
|
||||
// linking straight against bionic (rather than going through libapp.so's
|
||||
// own already-compiled, already-relocated calls) can reference it even
|
||||
// though nothing in this codebase had needed it before. Confirmed live:
|
||||
// missing this shim silently turned every one of ostream_repro.so's own
|
||||
// `memcpy(outBuf, ...)` calls into a no-op (the generic "unresolved import,
|
||||
// return 0" fallback doesn't copy anything), which looked EXACTLY like a
|
||||
// real ostringstream-extraction bug (result buffer stayed all-zero) until
|
||||
// this was traced back to the missing shim - a confound worth documenting
|
||||
// so it doesn't get mistaken for engine-level memory corruption again.
|
||||
// destlen is bionic's real 4th argument (the compiler-computed destination
|
||||
// object size); this shim logs instead of aborting if n exceeds it (real
|
||||
// bionic would abort() - a hard crash - which is almost certainly less
|
||||
// useful for debugging a guest than a loud log line here).
|
||||
uint32_t Shim_memcpy_chk(GuestEngine& eng, uint32_t dest, uint32_t src, uint32_t n, uint32_t destlen, uint32_t) {
|
||||
if (destlen != 0xFFFFFFFFu && n > destlen) {
|
||||
Log("GuestEngine: __memcpy_chk: n(%u) > destlen(%u) at dest=0x%x, src=0x%x - real bionic "
|
||||
"would abort() here; copying anyway (see import_shims.cpp)", n, destlen, dest, src);
|
||||
}
|
||||
if (dest && src && n) memcpy(eng.G2H(dest), eng.G2H(src), n);
|
||||
return dest;
|
||||
}
|
||||
// ---- Minimal bionic locale-family shims (2026-09-16, same investigation)
|
||||
// ----
|
||||
// A statically-linked libc++abi's classic/"C" locale singleton calls these
|
||||
// real bionic entry points during its own one-time setup - none had ever
|
||||
// been needed before this session (every previous locale-touching call
|
||||
// site in this codebase went through rtti_shims.cpp's own hand-written
|
||||
// _ZNSt6__ndk1*-prefixed libc++-internal shims instead, which never call
|
||||
// down into bionic's OWN locale layer at all - see rtti_shims.cpp's top
|
||||
// comment). newlocale returning 0 (this engine's default "unresolved,
|
||||
// return 0" behavior) was tested live and did NOT block basic char-only
|
||||
// ostringstream writes from working (this session's own repro test's
|
||||
// reported length came back correct even before this shim was added), but
|
||||
// leaving it unresolved is still a real, avoidable confound for any FUTURE
|
||||
// artifact that touches actual locale-sensitive formatting - a minimal,
|
||||
// always-succeeds "C locale" stand-in costs nothing and removes the
|
||||
// ambiguity. Genuinely locale-SENSITIVE behavior (real multi-locale
|
||||
// support) is out of scope, same as rtti_shims.cpp's own use_facet gap -
|
||||
// this only needs to make single-locale ("C"/classic) code paths not
|
||||
// silently fail.
|
||||
GuestAddr g_fakeLocaleT = 0; // lazily allocated on first newlocale() call
|
||||
uint32_t Shim_newlocale(GuestEngine& eng, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
if (!g_fakeLocaleT) g_fakeLocaleT = eng.AllocPermanent(4); // never freed - see AllocPermanent's own comment
|
||||
return g_fakeLocaleT ? g_fakeLocaleT : 1u; // never return a real 0/NULL "failed" locale_t
|
||||
}
|
||||
uint32_t Shim_uselocale(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
return g_fakeLocaleT ? g_fakeLocaleT : 1u; // "previous locale" - same single fake handle either way
|
||||
}
|
||||
uint32_t Shim_freelocale(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
return 0; // never actually freed - g_fakeLocaleT is a permanent singleton, matches AllocPermanent's own contract
|
||||
}
|
||||
uint32_t Shim_aeabi_memclr(GuestEngine& eng, uint32_t dest, uint32_t n, uint32_t, uint32_t, uint32_t) {
|
||||
if (dest && n) memset(eng.G2H(dest), 0, n);
|
||||
return dest;
|
||||
}
|
||||
|
||||
// Diagnostic (2026-09-17, ARM64_TRANSLATION_LAYER.md - the 0x3d3d3d3d
|
||||
// use-after-free-shaped chase): per-allocation malloc/free tracing with the
|
||||
// caller's guest LR, to reconstruct which call sites touch a given address
|
||||
// across its lifetime.
|
||||
//
|
||||
// OFF by default since 2026-09-19. That investigation is closed, and this
|
||||
// pair turned out to be by far the loudest thing in the engine: a single
|
||||
// prologue-load capture held 554,383 malloc lines and 539,003 free lines -
|
||||
// 93% of a 1.38-million-line log once __aeabi_memcpy's probe is counted in.
|
||||
// That volume does not burn CPU so much as block the app against logd, which
|
||||
// is exactly the "loading takes forever but the phone isn't even warm"
|
||||
// symptom the user reported. Same opt-in discipline as the UC_HOOK_BLOCK
|
||||
// diagnostics (see guest_engine.cpp) - flip to true only for a short,
|
||||
// targeted capture, never leave it on.
|
||||
constexpr bool kTraceHeapAllocations = false;
|
||||
|
||||
uint32_t Shim_malloc(GuestEngine& eng, uint32_t size, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
GuestAddr addr = eng.heap().Alloc(size);
|
||||
// Small allocations only (<=256 bytes - covers both the tiny
|
||||
// attribute-map bucket array and the ~33-byte shader-header string
|
||||
// buffer implicated in that investigation).
|
||||
if (kTraceHeapAllocations && size <= 256) {
|
||||
uint32_t callerLr = 0;
|
||||
if (uc_engine* uc = eng.uc()) uc_reg_read(uc, UC_ARM_REG_LR, &callerLr);
|
||||
Log("GuestHeap: malloc(size=%u) -> 0x%x from guest LR=0x%x", size, addr, callerLr);
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
uint32_t Shim_free(GuestEngine& eng, uint32_t ptr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
// No size available here without reading GuestHeap's own private header,
|
||||
// so this logs every free() - fine for a bounded capture, ruinous left on.
|
||||
if (kTraceHeapAllocations) {
|
||||
uint32_t callerLr = 0;
|
||||
if (uc_engine* uc = eng.uc()) uc_reg_read(uc, UC_ARM_REG_LR, &callerLr);
|
||||
Log("GuestHeap: free(0x%x) from guest LR=0x%x", ptr, callerLr);
|
||||
}
|
||||
eng.heap().Free(ptr);
|
||||
return 0;
|
||||
}
|
||||
uint32_t Shim_calloc(GuestEngine& eng, uint32_t nmemb, uint32_t size, uint32_t, uint32_t, uint32_t) {
|
||||
// 2026-09-19: GuestHeap::Alloc no longer zero-fills (it used to memset
|
||||
// the whole rounded-up class block on every malloc - see its own
|
||||
// comment), so calloc has to do it itself, which is where the cost
|
||||
// belongs. Overflow-checked: nmemb*size in 32-bit guest arithmetic can
|
||||
// wrap, and a wrapped-small allocation followed by a full-size memset
|
||||
// would be a heap overflow.
|
||||
uint64_t total = (uint64_t)nmemb * (uint64_t)size;
|
||||
if (total > 0xFFFFFFFFull) {
|
||||
Log("Shim_calloc: %u * %u overflows 32 bits - returning NULL", nmemb, size);
|
||||
return 0;
|
||||
}
|
||||
GuestAddr addr = eng.heap().Alloc((uint32_t)total);
|
||||
if (addr && total) memset(eng.G2H(addr), 0, (size_t)total);
|
||||
return addr;
|
||||
}
|
||||
uint32_t Shim_realloc(GuestEngine& eng, uint32_t ptr, uint32_t size, uint32_t, uint32_t, uint32_t) {
|
||||
// No real "grow in place" support in GuestHeap (see its own class
|
||||
// comment - no coalescing) - always relocates. Correct but wasteful;
|
||||
// fine for a prototype's expected allocation volume.
|
||||
// 2026-09-19: the old block's exact size is now available (GuestHeap
|
||||
// stores it per block and exposes BlockSize), so this copies the real
|
||||
// min(oldSize,newSize). It used to copy `size` unconditionally, which on
|
||||
// a SHRINKING realloc read past the end of the old block - harmless in
|
||||
// practice only because the arena is one contiguous mapping.
|
||||
uint32_t oldSize = ptr ? eng.heap().BlockSize(ptr) : 0;
|
||||
GuestAddr newAddr = eng.heap().Alloc(size);
|
||||
if (ptr && newAddr) {
|
||||
uint32_t toCopy = (oldSize && oldSize < size) ? oldSize : size;
|
||||
memcpy(eng.G2H(newAddr), eng.G2H(ptr), toCopy);
|
||||
}
|
||||
if (ptr) eng.heap().Free(ptr);
|
||||
return newAddr;
|
||||
}
|
||||
|
||||
uint32_t Shim_strlen(GuestEngine& eng, uint32_t s, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
if (!s) return 0;
|
||||
uint32_t len = (uint32_t)strlen((const char*)eng.G2H(s));
|
||||
// Diagnostic (2026-09-17, ARM64_TRANSLATION_LAYER.md - the 0x1e0 wild-jump
|
||||
// chase). strlen() runs on the HOST over the translated pointer, so an
|
||||
// unterminated guest string doesn't fault - it just keeps scanning
|
||||
// forward through whatever else lives in the shared host_region_ arena
|
||||
// until it happens to hit a stray zero byte, potentially megabytes later.
|
||||
// That exact symptom (a huge, ever-doubling "length" feeding into a
|
||||
// runaway std::string/streambuf reallocation) is what's driving the
|
||||
// 0x1e0 crash - flagging any suspiciously large result (not the normal
|
||||
// case, so cheap to check) to catch it at the source instead of several
|
||||
// frames downstream.
|
||||
if (len > 4096) {
|
||||
uint32_t callerLr = 0;
|
||||
if (uc_engine* uc = eng.uc()) uc_reg_read(uc, UC_ARM_REG_LR, &callerLr);
|
||||
Log("GuestEngine: Shim_strlen: suspiciously large result - s=0x%x len=%u from guest LR=0x%x",
|
||||
s, len, callerLr);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
uint32_t Shim_strcmp(GuestEngine& eng, uint32_t a, uint32_t b, uint32_t, uint32_t, uint32_t) {
|
||||
return (uint32_t)(int32_t)strcmp((const char*)eng.G2H(a), (const char*)eng.G2H(b));
|
||||
}
|
||||
uint32_t Shim_strcpy(GuestEngine& eng, uint32_t dst, uint32_t src, uint32_t, uint32_t, uint32_t) {
|
||||
strcpy((char*)eng.G2H(dst), (const char*)eng.G2H(src));
|
||||
return dst;
|
||||
}
|
||||
uint32_t Shim_strncpy(GuestEngine& eng, uint32_t dst, uint32_t src, uint32_t n, uint32_t, uint32_t) {
|
||||
strncpy((char*)eng.G2H(dst), (const char*)eng.G2H(src), n);
|
||||
return dst;
|
||||
}
|
||||
uint32_t Shim_tolower(GuestEngine&, uint32_t c, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
return (uint32_t)tolower((int)c);
|
||||
}
|
||||
|
||||
// pthread_mutexattr_t is entirely ignored (see pthread_shim.h's gap list -
|
||||
// every guest mutex is a real std::recursive_mutex regardless of the attr
|
||||
// type requested, so there's nothing for init/settype/destroy to actually
|
||||
// configure); these three stay plain always-succeed no-ops.
|
||||
uint32_t Shim_pthread_noop_success(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return 0; }
|
||||
|
||||
uint32_t Shim_raise(GuestEngine&, uint32_t sig, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
Log("import_shims: guest code called raise(%u) - not delivering a real signal to the guest "
|
||||
"(no guest signal handling exists), returning as if handled", sig);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EA's own thin JNIEnv/classloader-caching wrappers (EA::Nimble::getEnv/
|
||||
// findClass) - genuinely external to libapp.so itself (normally provided by
|
||||
// libNimble.so, which - like libapp.so - has no arm64-v8a build and isn't
|
||||
// loaded into the emulator). Implemented directly against JniShim instead
|
||||
// of emulating libNimble.so's own code, since these two are simple enough
|
||||
// to reimplement natively (same "wrap the API, don't translate the
|
||||
// implementation" principle as the rest of this shim layer).
|
||||
uint32_t Shim_ea_nimble_getEnv(GuestEngine& eng, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
return JniShim::Instance().BuildGuestJNIEnv(eng);
|
||||
}
|
||||
uint32_t Shim_ea_nimble_findClass(GuestEngine& eng, uint32_t namePtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
if (!namePtr) return 0;
|
||||
// Real JNI FindClass requires slash-separated names ("com/ea/..."), but
|
||||
// callers of this EA convenience wrapper pass dot-separated ones
|
||||
// ("com.ea.nimble.Log", confirmed live via a CheckJNI "illegal class
|
||||
// name" abort this session) - the real libNimble.so implementation
|
||||
// clearly did this same conversion internally before calling the real
|
||||
// FindClass, so this reimplementation must too.
|
||||
std::string name = (const char*)eng.G2H(namePtr);
|
||||
for (char& c : name) if (c == '.') c = '/';
|
||||
jclass c = JniShim::Instance().RealEnv()
|
||||
? JniShim::Instance().FindClassWithFallback(JniShim::Instance().RealEnv(), name)
|
||||
: nullptr;
|
||||
return c ? JniShim::Instance().handles().Alloc(c) : 0;
|
||||
}
|
||||
|
||||
uint32_t Shim_cxa_guard_acquire(GuestEngine& eng, uint32_t guard, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
uint8_t* p = (uint8_t*)eng.G2H(guard);
|
||||
return (*p == 0) ? 1u : 0u; // 1 = "you run the initializer", 0 = "already done"
|
||||
}
|
||||
uint32_t Shim_cxa_guard_release(GuestEngine& eng, uint32_t guard, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
*(uint8_t*)eng.G2H(guard) = 1;
|
||||
return 0;
|
||||
}
|
||||
uint32_t Shim_cxa_guard_abort(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return 0; }
|
||||
uint32_t Shim_cxa_atexit(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return 0; }
|
||||
uint32_t Shim_cxa_pure_virtual(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
Log("import_shims: __cxa_pure_virtual called (guest called a pure-virtual method) - "
|
||||
"this is a real bug signal, not expected in normal operation");
|
||||
return 0;
|
||||
}
|
||||
uint32_t Shim_abort(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
Log("import_shims: guest code called abort() - NOT actually aborting the host process "
|
||||
"(would take down the whole app); this shim just logs and returns, so guest execution "
|
||||
"after this point is running past what the real binary would have allowed. See this "
|
||||
"file's own top-of-file gap notes.");
|
||||
return 0;
|
||||
}
|
||||
uint32_t Shim_dladdr(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return 0; } // always "not found"
|
||||
|
||||
// ---- Single-precision math (softfp - see this file's top comment) ----
|
||||
// All take/return one or two GLfloat-shaped 32-bit register values; no
|
||||
// alignment concerns since none of these have a double-precision (8-byte)
|
||||
// parameter to worry about.
|
||||
#define MATH1F_SHIM(name) \
|
||||
uint32_t Shim_##name(GuestEngine&, uint32_t r0, uint32_t, uint32_t, uint32_t, uint32_t) { \
|
||||
float x, r; \
|
||||
memcpy(&x, &r0, 4); \
|
||||
r = name(x); \
|
||||
uint32_t bits; \
|
||||
memcpy(&bits, &r, 4); \
|
||||
return bits; \
|
||||
}
|
||||
#define MATH2F_SHIM(name) \
|
||||
uint32_t Shim_##name(GuestEngine&, uint32_t r0, uint32_t r1, uint32_t, uint32_t, uint32_t) { \
|
||||
float x, y, r; \
|
||||
memcpy(&x, &r0, 4); \
|
||||
memcpy(&y, &r1, 4); \
|
||||
r = name(x, y); \
|
||||
uint32_t bits; \
|
||||
memcpy(&bits, &r, 4); \
|
||||
return bits; \
|
||||
}
|
||||
MATH1F_SHIM(acosf)
|
||||
MATH1F_SHIM(asinf)
|
||||
MATH1F_SHIM(ceilf)
|
||||
MATH1F_SHIM(cosf)
|
||||
MATH1F_SHIM(expf)
|
||||
MATH1F_SHIM(floorf)
|
||||
MATH1F_SHIM(roundf)
|
||||
MATH1F_SHIM(sinf)
|
||||
MATH1F_SHIM(sqrtf)
|
||||
MATH1F_SHIM(tanf)
|
||||
MATH2F_SHIM(atan2f)
|
||||
MATH2F_SHIM(fmodf)
|
||||
MATH2F_SHIM(powf)
|
||||
#undef MATH1F_SHIM
|
||||
#undef MATH2F_SHIM
|
||||
|
||||
uint32_t Shim_atoi(GuestEngine& eng, uint32_t s, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
return s ? (uint32_t)atoi((const char*)eng.G2H(s)) : 0;
|
||||
}
|
||||
uint32_t Shim_memcmp(GuestEngine& eng, uint32_t a, uint32_t b, uint32_t n, uint32_t, uint32_t) {
|
||||
return (uint32_t)(int32_t)memcmp(eng.G2H(a), eng.G2H(b), n);
|
||||
}
|
||||
uint32_t Shim_memset(GuestEngine& eng, uint32_t dst, uint32_t c, uint32_t n, uint32_t, uint32_t) {
|
||||
memset(eng.G2H(dst), (int)c, n);
|
||||
return dst;
|
||||
}
|
||||
uint32_t Shim_strncmp(GuestEngine& eng, uint32_t a, uint32_t b, uint32_t n, uint32_t, uint32_t) {
|
||||
return (uint32_t)(int32_t)strncmp((const char*)eng.G2H(a), (const char*)eng.G2H(b), n);
|
||||
}
|
||||
uint32_t Shim_strrchr(GuestEngine& eng, uint32_t s, uint32_t c, uint32_t, uint32_t, uint32_t) {
|
||||
if (!s) return 0;
|
||||
char* p = strrchr((char*)eng.G2H(s), (int)c);
|
||||
return p ? eng.H2G(p) : 0;
|
||||
}
|
||||
uint32_t Shim_strstr(GuestEngine& eng, uint32_t hay, uint32_t needle, uint32_t, uint32_t, uint32_t) {
|
||||
if (!hay || !needle) return 0;
|
||||
char* p = strstr((char*)eng.G2H(hay), (const char*)eng.G2H(needle));
|
||||
return p ? eng.H2G(p) : 0;
|
||||
}
|
||||
uint32_t Shim_toupper(GuestEngine&, uint32_t c, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
return (uint32_t)toupper((int)c);
|
||||
}
|
||||
uint32_t Shim_lrand48(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
return (uint32_t)lrand48();
|
||||
}
|
||||
|
||||
// clock_gettime/nanosleep/time all write into a guest-addressed struct/
|
||||
// pointer - direct G2H translation, same as everywhere else in this file.
|
||||
// Real host struct layouts (`struct timespec`) are ABI-identical between
|
||||
// 32-bit and 64-bit (two `long`-ish fields that both fit this project's
|
||||
// existing 32-bit-guest assumption closely enough for timing purposes,
|
||||
// though a fully correct implementation would need to know the guest's own
|
||||
// expected struct timespec field widths - approximated here, documented).
|
||||
uint32_t Shim_clock_gettime(GuestEngine& eng, uint32_t clockId, uint32_t tsPtr, uint32_t, uint32_t, uint32_t) {
|
||||
struct timespec ts{};
|
||||
int rc = clock_gettime((clockid_t)clockId, &ts);
|
||||
if (tsPtr) {
|
||||
uint32_t sec = (uint32_t)ts.tv_sec, nsec = (uint32_t)ts.tv_nsec;
|
||||
memcpy(eng.G2H(tsPtr), &sec, 4);
|
||||
memcpy((uint8_t*)eng.G2H(tsPtr) + 4, &nsec, 4);
|
||||
}
|
||||
return (uint32_t)rc;
|
||||
}
|
||||
uint32_t Shim_nanosleep(GuestEngine& eng, uint32_t reqPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
if (!reqPtr) return -1;
|
||||
uint32_t sec = 0, nsec = 0;
|
||||
memcpy(&sec, eng.G2H(reqPtr), 4);
|
||||
memcpy(&nsec, (uint8_t*)eng.G2H(reqPtr) + 4, 4);
|
||||
struct timespec req{(time_t)sec, (long)nsec};
|
||||
return (uint32_t)nanosleep(&req, nullptr);
|
||||
}
|
||||
uint32_t Shim_time(GuestEngine& eng, uint32_t tPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
time_t t = time(nullptr);
|
||||
if (tPtr) { uint32_t v = (uint32_t)t; memcpy(eng.G2H(tPtr), &v, 4); }
|
||||
return (uint32_t)t;
|
||||
}
|
||||
|
||||
uint32_t Shim_android_log_write(GuestEngine& eng, uint32_t prio, uint32_t tagPtr, uint32_t msgPtr, uint32_t, uint32_t) {
|
||||
const char* tag = tagPtr ? (const char*)eng.G2H(tagPtr) : "libapp";
|
||||
const char* msg = msgPtr ? (const char*)eng.G2H(msgPtr) : "";
|
||||
return (uint32_t)__android_log_write((int)prio, tag, msg);
|
||||
}
|
||||
|
||||
// `thread_local` (not a single shared array) - now that real guest threads
|
||||
// exist (emu/pthread_shim.cpp), a flat shared array would let one guest
|
||||
// thread's pthread_setspecific silently clobber every other thread's value
|
||||
// for the same key, which is exactly backwards from real TLS semantics. Key
|
||||
// ALLOCATION (g_tlsKeyCount) is still process-wide/shared, as real bionic's
|
||||
// is - only the per-key VALUES are per-thread.
|
||||
constexpr int kMaxTlsKeys = 64;
|
||||
thread_local uint32_t g_tlsValues[kMaxTlsKeys] = {};
|
||||
std::mutex g_tlsKeyCountMutex;
|
||||
int g_tlsKeyCount = 0;
|
||||
uint32_t Shim_pthread_key_create(GuestEngine& eng, uint32_t keyOutPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
std::lock_guard<std::mutex> lock(g_tlsKeyCountMutex);
|
||||
if (g_tlsKeyCount >= kMaxTlsKeys) return -1;
|
||||
uint32_t key = (uint32_t)g_tlsKeyCount++;
|
||||
if (keyOutPtr) memcpy(eng.G2H(keyOutPtr), &key, 4);
|
||||
return 0;
|
||||
}
|
||||
uint32_t Shim_pthread_key_delete(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return 0; }
|
||||
uint32_t Shim_pthread_getspecific(GuestEngine&, uint32_t key, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
return (key < (uint32_t)kMaxTlsKeys) ? g_tlsValues[key] : 0;
|
||||
}
|
||||
uint32_t Shim_pthread_setspecific(GuestEngine&, uint32_t key, uint32_t value, uint32_t, uint32_t, uint32_t) {
|
||||
if (key < (uint32_t)kMaxTlsKeys) g_tlsValues[key] = value;
|
||||
return 0;
|
||||
}
|
||||
// Per-real-host-thread fake id (was a single hardcoded `1` when the guest
|
||||
// was single-threaded) - a thread_local counter assigned once per thread on
|
||||
// first call, distinct across real guest threads, still opaque/never a real
|
||||
// bionic pthread_t.
|
||||
uint32_t Shim_pthread_self(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
static std::atomic<uint32_t> nextId{1};
|
||||
static thread_local uint32_t id = nextId.fetch_add(1);
|
||||
return id;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void RegisterCoreImportShims(GuestEngine& engine) {
|
||||
engine.RegisterImportShim("__aeabi_memcpy", Shim_aeabi_memcpy);
|
||||
engine.RegisterImportShim("__aeabi_memcpy4", Shim_aeabi_memcpy);
|
||||
engine.RegisterImportShim("__aeabi_memcpy8", Shim_aeabi_memcpy);
|
||||
engine.RegisterImportShim("__memcpy_chk", Shim_memcpy_chk);
|
||||
engine.RegisterImportShim("newlocale", Shim_newlocale);
|
||||
engine.RegisterImportShim("uselocale", Shim_uselocale);
|
||||
engine.RegisterImportShim("freelocale", Shim_freelocale);
|
||||
engine.RegisterImportShim("__aeabi_memmove", Shim_aeabi_memmove);
|
||||
engine.RegisterImportShim("__aeabi_memmove4", Shim_aeabi_memmove);
|
||||
engine.RegisterImportShim("__aeabi_memmove8", Shim_aeabi_memmove);
|
||||
engine.RegisterImportShim("__aeabi_memset", Shim_aeabi_memset);
|
||||
engine.RegisterImportShim("__aeabi_memset4", Shim_aeabi_memset);
|
||||
engine.RegisterImportShim("__aeabi_memset8", Shim_aeabi_memset);
|
||||
engine.RegisterImportShim("__aeabi_memclr", Shim_aeabi_memclr);
|
||||
engine.RegisterImportShim("__aeabi_memclr4", Shim_aeabi_memclr);
|
||||
engine.RegisterImportShim("__aeabi_memclr8", Shim_aeabi_memclr);
|
||||
engine.RegisterImportShim("memcpy", Shim_aeabi_memcpy);
|
||||
engine.RegisterImportShim("memmove", Shim_aeabi_memmove);
|
||||
|
||||
engine.RegisterImportShim("malloc", Shim_malloc);
|
||||
engine.RegisterImportShim("free", Shim_free);
|
||||
engine.RegisterImportShim("calloc", Shim_calloc);
|
||||
engine.RegisterImportShim("realloc", Shim_realloc);
|
||||
|
||||
engine.RegisterImportShim("strlen", Shim_strlen);
|
||||
engine.RegisterImportShim("strcmp", Shim_strcmp);
|
||||
engine.RegisterImportShim("strcpy", Shim_strcpy);
|
||||
engine.RegisterImportShim("strncpy", Shim_strncpy);
|
||||
engine.RegisterImportShim("tolower", Shim_tolower);
|
||||
|
||||
engine.RegisterImportShim("pthread_mutexattr_init", Shim_pthread_noop_success);
|
||||
engine.RegisterImportShim("pthread_mutexattr_settype", Shim_pthread_noop_success);
|
||||
engine.RegisterImportShim("pthread_mutexattr_destroy", Shim_pthread_noop_success);
|
||||
// Real pthread_create/join/mutex_*/cond_* shims are registered by
|
||||
// RegisterPthreadImportShims (emu/pthread_shim.cpp) - the caller (see
|
||||
// main.cpp) calls that after this function.
|
||||
engine.RegisterImportShim("raise", Shim_raise);
|
||||
|
||||
engine.RegisterImportShim("_ZN2EA6Nimble6getEnvEv", Shim_ea_nimble_getEnv);
|
||||
engine.RegisterImportShim("_ZN2EA6Nimble9findClassEPKc", Shim_ea_nimble_findClass);
|
||||
|
||||
engine.RegisterImportShim("__cxa_guard_acquire", Shim_cxa_guard_acquire);
|
||||
engine.RegisterImportShim("__cxa_guard_release", Shim_cxa_guard_release);
|
||||
engine.RegisterImportShim("__cxa_guard_abort", Shim_cxa_guard_abort);
|
||||
engine.RegisterImportShim("__cxa_atexit", Shim_cxa_atexit);
|
||||
engine.RegisterImportShim("__cxa_pure_virtual", Shim_cxa_pure_virtual);
|
||||
engine.RegisterImportShim("abort", Shim_abort);
|
||||
engine.RegisterImportShim("dladdr", Shim_dladdr);
|
||||
|
||||
engine.RegisterImportShim("acosf", Shim_acosf);
|
||||
engine.RegisterImportShim("asinf", Shim_asinf);
|
||||
engine.RegisterImportShim("ceilf", Shim_ceilf);
|
||||
engine.RegisterImportShim("cosf", Shim_cosf);
|
||||
engine.RegisterImportShim("expf", Shim_expf);
|
||||
engine.RegisterImportShim("floorf", Shim_floorf);
|
||||
engine.RegisterImportShim("roundf", Shim_roundf);
|
||||
engine.RegisterImportShim("sinf", Shim_sinf);
|
||||
engine.RegisterImportShim("sqrtf", Shim_sqrtf);
|
||||
engine.RegisterImportShim("tanf", Shim_tanf);
|
||||
engine.RegisterImportShim("atan2f", Shim_atan2f);
|
||||
engine.RegisterImportShim("fmodf", Shim_fmodf);
|
||||
engine.RegisterImportShim("powf", Shim_powf);
|
||||
|
||||
engine.RegisterImportShim("atoi", Shim_atoi);
|
||||
engine.RegisterImportShim("memcmp", Shim_memcmp);
|
||||
engine.RegisterImportShim("memset", Shim_memset);
|
||||
engine.RegisterImportShim("strncmp", Shim_strncmp);
|
||||
engine.RegisterImportShim("strrchr", Shim_strrchr);
|
||||
engine.RegisterImportShim("strstr", Shim_strstr);
|
||||
engine.RegisterImportShim("toupper", Shim_toupper);
|
||||
engine.RegisterImportShim("lrand48", Shim_lrand48);
|
||||
|
||||
engine.RegisterImportShim("clock_gettime", Shim_clock_gettime);
|
||||
engine.RegisterImportShim("nanosleep", Shim_nanosleep);
|
||||
engine.RegisterImportShim("time", Shim_time);
|
||||
|
||||
engine.RegisterImportShim("__android_log_write", Shim_android_log_write);
|
||||
|
||||
engine.RegisterImportShim("pthread_key_create", Shim_pthread_key_create);
|
||||
engine.RegisterImportShim("pthread_key_delete", Shim_pthread_key_delete);
|
||||
engine.RegisterImportShim("pthread_getspecific", Shim_pthread_getspecific);
|
||||
engine.RegisterImportShim("pthread_setspecific", Shim_pthread_setspecific);
|
||||
engine.RegisterImportShim("pthread_self", Shim_pthread_self);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "guest_engine.h"
|
||||
|
||||
// Registers the small, deliberately-bounded set of real import shims this
|
||||
// prototype implements (see import_shims.cpp for the list and the
|
||||
// class-level notes in that file about what's NOT covered yet - notably
|
||||
// libc++ locale/iostream/RTTI beyond trivial __cxa_guard/pure_virtual
|
||||
// stubs). Real pthread_create/join/mutex/cond support is registered
|
||||
// separately by RegisterPthreadImportShims (emu/pthread_shim.h) - call both
|
||||
// (see main.cpp). Anything not registered by either still gets a guest stub
|
||||
// address (so relocations always resolve to *something*), it just logs
|
||||
// "unresolved import" once and returns 0 instead of crashing - see
|
||||
// GuestEngine::ResolveOrCreateImportStub / import_stub_dispatch_cb.
|
||||
void RegisterCoreImportShims(GuestEngine& engine);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,199 @@
|
||||
#pragma once
|
||||
|
||||
// Guest-visible JNIEnv - lets guest code (running inside GuestEngine) call
|
||||
// back into the REAL Android JVM, e.g. from libapp.so's own real
|
||||
// nativeOnCreate/JNI_OnLoad now that they're invoked via CallGuestFunction
|
||||
// instead of being no-op host stubs. See ARM64_TRANSLATION_LAYER.md's
|
||||
// "JNI upcalls" section - this is the "least risky, most mechanical" piece
|
||||
// that doc predicted, now actually built.
|
||||
//
|
||||
// Mechanism: builds a real `struct JNINativeInterface` layout (233 function-
|
||||
// pointer slots, exact order extracted from this NDK's own jni.h) in guest
|
||||
// memory, one stub guest-address per slot (same UC_HOOK_CODE "replace and
|
||||
// return via LR" pattern as GuestEngine's import stubs / InstallTrampolineHook).
|
||||
// When guest code calls env->FindClass(...), Unicorn's hook fires at that
|
||||
// slot's stub address, the dispatcher reads the incoming args (r1.. plus
|
||||
// stack, since r0 is always the guest JNIEnv* itself), forwards to the REAL
|
||||
// host JNIEnv, and translates the result back into a guest-visible handle.
|
||||
//
|
||||
// Reference-shaped JNI values (jobject/jclass/jstring/jarray/jmethodID/
|
||||
// jfieldID/jthrowable/jweak) are all real 64-bit-ish host values on this
|
||||
// (64-bit ART) runtime and cannot fit in a 32-bit guest register - JniHandleTable
|
||||
// hands out small sequential 32-bit guest handles and resolves them back,
|
||||
// the same "handle indirection" every ARM-on-64-bit-host JNI bridge needs.
|
||||
//
|
||||
// Deliberately bounded scope (documented, not silent): only ~25 of the 233
|
||||
// slots have real implementations (the ones covering class/method/field
|
||||
// lookup, 0-argument method calls, strings, refs, exceptions - see
|
||||
// jni_shim.cpp's own top comment for the exact list and the known gaps in
|
||||
// Call*Method argument marshaling for methods that take parameters). Every
|
||||
// other slot gets a guest stub that logs "unresolved JNI slot N (name)" once
|
||||
// and returns 0, exactly matching GuestEngine's own import-stub philosophy.
|
||||
|
||||
#include <jni.h>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include "guest_engine.h"
|
||||
|
||||
// Bidirectional 32-bit guest handle <-> real 64-bit-capable JNI reference.
|
||||
// Never reuses/frees slots in this prototype (documented leak, acceptable
|
||||
// for a short test run - see class comment above). Mutex-guarded - now that
|
||||
// real guest threads exist (see emu/pthread_shim.h), more than one could in
|
||||
// principle resolve/allocate a JNI handle concurrently (e.g. two threads
|
||||
// each calling a Call*Method JNI slot at the same time).
|
||||
//
|
||||
// Each slot also records the real host thread that created it and whether
|
||||
// it's a durable ("global") reference. Real JNI local references are only
|
||||
// valid on the thread (and, strictly, the native-call frame) that created
|
||||
// them - reused elsewhere they're either NULL (harmless) or a live-looking
|
||||
// pointer to a DIFFERENT, unrelated real object, which real ART's CheckJNI
|
||||
// hard-aborts the whole process on (confirmed live 2026-09-05 - see
|
||||
// ARM64_TRANSLATION_LAYER.md's "invalid local jclass" investigation: a guest
|
||||
// C++ helper cached a bare local jclass once on the main thread and reused
|
||||
// it ~90s later from the GLThread inside RunLoop.nativeOnRunLoopTick - a
|
||||
// real bug in that ~2013 EA code, not this shim, but one this shim can
|
||||
// detect and degrade gracefully instead of crashing the whole process).
|
||||
// IsSafeToUseFromCurrentThread lets a call site check this BEFORE handing
|
||||
// the resolved pointer to any real JNI function - deliberately never asks
|
||||
// ART itself (e.g. via GetObjectRefType), since CheckJNI validates every
|
||||
// reference argument to every JNI function, including that one, so there is
|
||||
// no real-JNI-call-based way to probe a reference's validity that doesn't
|
||||
// risk aborting on exactly the kind of stale reference being checked for.
|
||||
class JniHandleTable {
|
||||
public:
|
||||
uint32_t Alloc(void* real, bool isGlobal = false);
|
||||
void* Resolve(uint32_t handle) const;
|
||||
// Fix (2026-09-17, ARM64_TRANSLATION_LAYER.md - the SIGABRT-in-GLThread
|
||||
// chase, ROOT CAUSE). This class's own comment above already called out
|
||||
// "and, strictly, the native-call frame" as part of what makes a local
|
||||
// ref valid - but the implementation only ever checked the owning
|
||||
// THREAD, never the owning CALL. Confirmed live: `nativeOnRunLoopTick`
|
||||
// is called repeatedly, ONCE PER FRAME, on the SAME real "GLThread" -
|
||||
// each call is its own fresh JNI native-method invocation with its own
|
||||
// local-ref frame (real_native_call.h's CallRealNative calls
|
||||
// SetRealEnv(env) on every single entry, confirming this), so a jclass
|
||||
// cached during one tick and reused during a LATER tick is stale THE
|
||||
// MOMENT the tick that created it returns to Java - even though it's
|
||||
// the exact same host thread throughout. BumpCallEpoch() is called
|
||||
// from SetRealEnv (see its own comment) to mark each such boundary;
|
||||
// IsSafeToUseFromCurrentThread now checks BOTH the owning thread AND
|
||||
// the owning epoch.
|
||||
bool IsSafeToUseFromCurrentThread(uint32_t handle) const;
|
||||
static void BumpCallEpoch() { g_callEpoch.fetch_add(1, std::memory_order_relaxed); }
|
||||
|
||||
private:
|
||||
static std::atomic<uint32_t> g_callEpoch;
|
||||
struct Entry {
|
||||
void* real = nullptr;
|
||||
std::thread::id owner;
|
||||
bool isGlobal = false;
|
||||
uint32_t epoch = 0; // g_callEpoch's value at Alloc() time - see IsSafeToUseFromCurrentThread
|
||||
};
|
||||
mutable std::mutex mutex_;
|
||||
std::vector<Entry> table_{Entry{}}; // index 0 reserved for guest NULL
|
||||
};
|
||||
|
||||
class JniShim {
|
||||
public:
|
||||
static JniShim& Instance();
|
||||
|
||||
// Builds the guest JNIEnv structure (once) and returns its guest
|
||||
// address - pass this as the first (r0) argument to any real guest
|
||||
// function that expects a JNIEnv*.
|
||||
GuestAddr BuildGuestJNIEnv(GuestEngine& engine);
|
||||
|
||||
// Builds a minimal guest JavaVM (8-slot JNIInvokeInterface) - only
|
||||
// GetEnv and AttachCurrentThread are real (both just return the same
|
||||
// guest JNIEnv from BuildGuestJNIEnv - this shim only ever has one
|
||||
// "thread"/env, see SetRealEnv's own comment); DestroyJavaVM/
|
||||
// DetachCurrentThread/AttachCurrentThreadAsDaemon are logged no-ops.
|
||||
// Needed to call libapp.so's own real JNI_OnLoad(JavaVM*, void*).
|
||||
GuestAddr BuildGuestJavaVM(GuestEngine& engine);
|
||||
|
||||
// Must be called before each top-level CallGuestFunction into guest
|
||||
// code that might call back into Java - stores the REAL, currently
|
||||
// valid JNIEnv* for the CALLING thread (JNIEnv* is only valid on the
|
||||
// thread that obtained it - this is `thread_local` specifically so that
|
||||
// holds, now that real guest pthreads exist - see emu/pthread_shim.h).
|
||||
// Every current call site (main.cpp / game_lifecycle_stubs*.cpp) still
|
||||
// only ever runs on the main/UI thread, so this doesn't change today's
|
||||
// behavior - it just means a guest worker thread that starts calling
|
||||
// into JNI won't silently corrupt/steal the main thread's slot; it will
|
||||
// need to set its OWN via AttachCurrentThread first (see
|
||||
// Impl_Vm_AttachCurrentThread in jni_shim.cpp), same as real Android JNI
|
||||
// requires.
|
||||
// Fix (2026-09-17, ARM64_TRANSLATION_LAYER.md - the SIGSEGV-in-GLThread
|
||||
// chase, directly following on from this class's own comment above
|
||||
// about a guest worker thread needing to AttachCurrentThread first).
|
||||
// Confirmed live: a real, engine-spawned "GLThread" pthread eventually
|
||||
// calls a JNI slot (GetMethodID) with its own real_env_ still null -
|
||||
// this thread's slot was never populated, because
|
||||
// Impl_Vm_AttachCurrentThread (jni_shim.cpp) only ever built a FAKE
|
||||
// guest JNIEnv handle and never actually attached the calling HOST
|
||||
// thread to the real JVM at all. Caches the process-wide `JavaVM*` the
|
||||
// first time any thread supplies a real env (JavaVM* itself, unlike
|
||||
// JNIEnv*, is valid across every thread) so Impl_Vm_AttachCurrentThread
|
||||
// can call the REAL AttachCurrentThread for whichever host thread asks.
|
||||
void SetRealEnv(JNIEnv* env) {
|
||||
real_env_ = env;
|
||||
if (env && !real_vm_) {
|
||||
env->GetJavaVM(&real_vm_);
|
||||
}
|
||||
// Fix (2026-09-17) - see JniHandleTable::BumpCallEpoch's own
|
||||
// comment. This call site is exactly "a new top-level native call
|
||||
// is beginning" (real_native_call.h's CallRealNative calls this on
|
||||
// every single entry) - every local ref handed out during the
|
||||
// PREVIOUS call is now stale, whether or not it's the same thread.
|
||||
if (env) handles_.BumpCallEpoch();
|
||||
}
|
||||
// Defensive fallback (same fix): if this HOST thread's own real_env_ is
|
||||
// still null (guest code never called AttachCurrentThread on this
|
||||
// thread, or it hasn't run yet), lazily attach for real here too,
|
||||
// rather than only relying on Impl_Vm_AttachCurrentThread being the
|
||||
// one and only path that populates it. Matches real Android's own
|
||||
// forgiving behavior for JNI calls arriving on a not-yet-attached
|
||||
// native thread. Safe to call from any thread; no-ops if there's no
|
||||
// real JavaVM cached yet (nothing to attach to) or the thread is
|
||||
// already attached (real AttachCurrentThread is itself idempotent).
|
||||
JNIEnv* RealEnv() {
|
||||
if (!real_env_ && real_vm_) {
|
||||
JNIEnv* env = nullptr;
|
||||
if (real_vm_->AttachCurrentThread(&env, nullptr) == JNI_OK && env) {
|
||||
real_env_ = env;
|
||||
}
|
||||
}
|
||||
return real_env_;
|
||||
}
|
||||
JavaVM* RealVm() const { return real_vm_; }
|
||||
|
||||
JniHandleTable& handles() { return handles_; }
|
||||
|
||||
// Classic Android JNI gotcha: FindClass only sees app classes correctly
|
||||
// when called from the thread that originally loaded the native
|
||||
// library (or one attached the same way) - called from any other
|
||||
// thread (e.g. the real engine's GLThread, confirmed live this session
|
||||
// via a "JNI DETECTED ERROR...GetMethodID received NULL jclass" abort
|
||||
// traced back to a failed FindClass inside nativeSurfaceCreated) it
|
||||
// silently only sees bootclasspath classes. Standard fix: cache the
|
||||
// app's real ClassLoader once (from any already-reachable app object,
|
||||
// on the main thread) and fall back to ClassLoader.loadClass() when a
|
||||
// direct FindClass call fails. Call once, early (LoadEmulatedLibapp).
|
||||
void CacheClassLoader(JNIEnv* env, jobject anyAppObject);
|
||||
// Resolves `name` (slash-separated, real JNI FindClass convention) via
|
||||
// a direct FindClass first, falling back to the cached ClassLoader if
|
||||
// that fails/throws - this is what Impl_FindClass (jni_shim.cpp) and
|
||||
// the EA::Nimble::findClass shim (import_shims.cpp) both call through.
|
||||
jclass FindClassWithFallback(JNIEnv* env, const std::string& slashName);
|
||||
|
||||
private:
|
||||
JniShim() = default;
|
||||
static thread_local JNIEnv* real_env_;
|
||||
JavaVM* real_vm_ = nullptr; // process-wide, NOT thread_local - see SetRealEnv's own comment
|
||||
JniHandleTable handles_;
|
||||
GuestAddr guest_env_ = 0;
|
||||
GuestAddr guest_vm_ = 0;
|
||||
jobject class_loader_ = nullptr; // global ref
|
||||
jmethodID load_class_method_ = nullptr;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "guest_engine.h"
|
||||
|
||||
// Real implementations for the ~180 basic libc/POSIX imports libapp.so
|
||||
// actually calls that import_shims.cpp didn't cover (string.h/ctype.h/
|
||||
// math.h/stdio.h, POSIX file I/O, time.h, process/signal, dlopen family,
|
||||
// network/socket, pthread extras, sem_*, __aeabi_* ARM EABI helpers, and a
|
||||
// handful of Android/EGL/GLES-extension odds and ends).
|
||||
//
|
||||
// Root cause this file exists: EVERY one of these previously fell through
|
||||
// to the generic "unresolved import, log once, return 0" handler. That's
|
||||
// not a harmless gap - guest code that reasonably assumes fopen()/getenv()/
|
||||
// strdup() succeeded and got a real pointer back, then dereferences a
|
||||
// silently-substituted NULL/0, is undefined behavior from that point on -
|
||||
// this session traced a real, hard-to-diagnose memory corruption bug back
|
||||
// to exactly this pattern (a burst of unresolved fread/fseek/fwrite/RTTI
|
||||
// symbols during libc++ runtime bootstrap, immediately followed by
|
||||
// something writing garbage into unrelated guest memory). "Return 0 and
|
||||
// hope" is not an acceptable default for anything actually called - see
|
||||
// ARM64_TRANSLATION_LAYER.md's "go through all the imports" entry.
|
||||
//
|
||||
// Same marshaling conventions as import_shims.cpp throughout: pointer args
|
||||
// are guest addresses, G2H-translated before use; functions returning a
|
||||
// pointer INTO an already-G2H'd buffer (strchr, memchr, ...) H2G-translate
|
||||
// the result back; functions returning a host-owned string (strerror,
|
||||
// getenv, strdup, ...) copy it into a freshly heap-allocated guest buffer
|
||||
// (same pattern as jni_shim.cpp's GetStringUTFChars); FILE*/DIR* (real host
|
||||
// pointers, don't fit a 32-bit guest register) go through small handle
|
||||
// tables, same shape as JniHandleTable.
|
||||
void RegisterLibcImportShims(GuestEngine& engine);
|
||||
|
||||
// A GuestEngine::DataSymbolSetupFn (register via
|
||||
// engine.RegisterDataSymbolSetup BEFORE engine.LoadImage, same as
|
||||
// rtti_shims.h's SetupRttiDataSymbols) - builds real guest-visible data for
|
||||
// the handful of libc DATA symbols (not callable functions) this sweep
|
||||
// turned up: __stack_chk_guard (a stack-canary value - see its own
|
||||
// comment for why any stable value works), timezone/tzname (mirrored from
|
||||
// the real host globals, refreshed on every tzset() call), and __sF (real
|
||||
// FILE* handles for stdin/stdout/stderr, via the same handle table
|
||||
// fopen/fclose/etc already use).
|
||||
void SetupLibcDataSymbols(GuestEngine& engine);
|
||||
@@ -0,0 +1,138 @@
|
||||
#include "name_lookup_accel.h"
|
||||
|
||||
#include "guest_engine.h"
|
||||
#include "../util/util.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace name_lookup_accel {
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t kOffCacheFlag = 8;
|
||||
constexpr uint32_t kOffEntriesA = 196;
|
||||
constexpr uint32_t kOffCountA = 200;
|
||||
constexpr uint32_t kOffEntriesB = 204;
|
||||
constexpr uint32_t kOffCountB = 220;
|
||||
constexpr uint32_t kOffPoolA = 224;
|
||||
constexpr uint32_t kOffPoolThreshold = 228;
|
||||
constexpr uint32_t kOffPoolB = 232;
|
||||
|
||||
struct TableCache {
|
||||
uint32_t countA = 0;
|
||||
uint32_t countB = 0;
|
||||
// name -> entry index. First occurrence wins, matching the guest loop,
|
||||
// which returns as soon as it finds a match.
|
||||
std::unordered_map<std::string, int32_t> byName;
|
||||
};
|
||||
|
||||
std::mutex g_mutex;
|
||||
std::map<uint32_t, TableCache> g_tables; // keyed by the guest table address
|
||||
|
||||
std::atomic<uint64_t> g_hits{0};
|
||||
std::atomic<uint64_t> g_builds{0};
|
||||
|
||||
uint32_t ReadU32(uc_engine* uc, uint32_t addr) {
|
||||
uint32_t v = 0;
|
||||
uc_mem_read(uc, addr, &v, 4);
|
||||
return v;
|
||||
}
|
||||
|
||||
void ReturnToCaller(uc_engine* uc, uint32_t ret) {
|
||||
uint32_t lr = 0;
|
||||
uc_reg_read(uc, UC_ARM_REG_LR, &lr);
|
||||
uc_reg_write(uc, UC_ARM_REG_R0, &ret);
|
||||
uc_reg_write(uc, UC_ARM_REG_PC, &lr);
|
||||
uc_emu_stop(uc);
|
||||
}
|
||||
|
||||
// Reads a NUL-terminated guest string directly out of the shared region.
|
||||
std::string ReadGuestString(GuestEngine& eng, uint32_t addr) {
|
||||
if (!addr) return {};
|
||||
const char* p = reinterpret_cast<const char*>(eng.G2H(addr));
|
||||
if (!p) return {};
|
||||
return std::string(p);
|
||||
}
|
||||
|
||||
// Builds (or rebuilds) the host-side index for one guest table. Mirrors the
|
||||
// guest loop's own addressing exactly, including the two-array split and the
|
||||
// threshold that selects which string pool an entry's offset belongs to.
|
||||
void BuildIndex(GuestEngine& eng, uc_engine* uc, uint32_t self, TableCache& cache) {
|
||||
cache.byName.clear();
|
||||
cache.countA = ReadU32(uc, self + kOffCountA);
|
||||
cache.countB = ReadU32(uc, self + kOffCountB);
|
||||
const uint32_t entriesA = ReadU32(uc, self + kOffEntriesA);
|
||||
const uint32_t entriesB = ReadU32(uc, self + kOffEntriesB);
|
||||
const uint32_t poolA = ReadU32(uc, self + kOffPoolA);
|
||||
const uint32_t poolB = ReadU32(uc, self + kOffPoolB);
|
||||
const uint32_t threshold = ReadU32(uc, self + kOffPoolThreshold);
|
||||
|
||||
const uint32_t total = cache.countA + cache.countB;
|
||||
cache.byName.reserve(total * 2);
|
||||
for (uint32_t i = 0; i < total; i++) {
|
||||
uint32_t entry = (i >= cache.countA)
|
||||
? entriesB + (i - cache.countA) * 8
|
||||
: entriesA + i * 8;
|
||||
uint32_t off = ReadU32(uc, entry);
|
||||
uint32_t base = poolA;
|
||||
if (off >= threshold) {
|
||||
off -= threshold;
|
||||
base = poolB;
|
||||
}
|
||||
std::string name = ReadGuestString(eng, base + off);
|
||||
if (name.empty()) continue;
|
||||
cache.byName.emplace(std::move(name), (int32_t)i);
|
||||
}
|
||||
Log("name_lookup_accel: indexed table 0x%x - %u entries (%u+%u), %zu distinct names",
|
||||
self, total, cache.countA, cache.countB, cache.byName.size());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void HookCb(uc_engine* uc, uint64_t, uint32_t, void*) {
|
||||
uint32_t self = 0, namePtr = 0;
|
||||
uc_reg_read(uc, UC_ARM_REG_R0, &self);
|
||||
uc_reg_read(uc, UC_ARM_REG_R1, &namePtr);
|
||||
if (!self || !namePtr) return; // let the guest handle its own edge cases
|
||||
|
||||
// If the guest's own cache is ever switched on, step aside entirely -
|
||||
// that path also WRITES into guest structures (it memoises the result),
|
||||
// and duplicating that behaviour here would be guesswork.
|
||||
uint8_t cacheFlag = 0;
|
||||
if (uc_mem_read(uc, self + kOffCacheFlag, &cacheFlag, 1) != UC_ERR_OK) return;
|
||||
if (cacheFlag) return;
|
||||
|
||||
auto& eng = GuestEngine::Instance();
|
||||
std::string name = ReadGuestString(eng, namePtr);
|
||||
if (name.empty()) return;
|
||||
|
||||
int32_t result = -1;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
TableCache& cache = g_tables[self];
|
||||
// Rebuild when the table has grown/shrunk since it was indexed. The
|
||||
// counts are the same two fields the guest loop bounds itself with,
|
||||
// so any change it can see, this sees too.
|
||||
uint32_t countA = ReadU32(uc, self + kOffCountA);
|
||||
uint32_t countB = ReadU32(uc, self + kOffCountB);
|
||||
if (cache.byName.empty() || cache.countA != countA || cache.countB != countB) {
|
||||
BuildIndex(eng, uc, self, cache);
|
||||
g_builds.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
auto it = cache.byName.find(name);
|
||||
if (it != cache.byName.end()) result = it->second;
|
||||
}
|
||||
|
||||
uint64_t n = g_hits.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
if (n % 100000 == 0) {
|
||||
Log("name_lookup_accel: %llu lookups served natively (%llu index builds)",
|
||||
(unsigned long long)n, (unsigned long long)g_builds.load(std::memory_order_relaxed));
|
||||
}
|
||||
ReturnToCaller(uc, (uint32_t)result);
|
||||
}
|
||||
|
||||
} // namespace name_lookup_accel
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <unicorn/unicorn.h>
|
||||
|
||||
// Native acceleration for the game's "find resource index by name" lookup
|
||||
// (guest sub_4F3704), measured by the block profiler as 18.8% of all
|
||||
// load-time samples - the second hottest thing after zlib's crc32.
|
||||
//
|
||||
// Why it is so expensive, from a live probe rather than from reading the
|
||||
// code: the function HAS a hash-map fast path with memoisation, but both
|
||||
// halves are gated on a byte at *(self+8), and that byte is **0** in this
|
||||
// build - so the cache is simply never used and every call falls through to
|
||||
// a LINEAR scan doing strcmp against a 6232-entry name table. Measured
|
||||
// 320,000+ calls in ~40 seconds, i.e. on the order of a billion emulated
|
||||
// strcmp comparisons. The names seen are shader/material parameters
|
||||
// ("AmbientR", "LightingIndex", "LateralSlices", "Z"), which is why this
|
||||
// hurts frame time as well as load time - they are resolved per object.
|
||||
//
|
||||
// Real hardware runs the same disabled-cache code and absorbs it; at ~12.8M
|
||||
// emulated instructions/sec this layer cannot.
|
||||
//
|
||||
// Approach: keep the guest's data structures untouched and answer the lookup
|
||||
// from a host-side hash map built once per table (rebuilt if the table's own
|
||||
// entry counts change). Same interception mechanism as zlib_accel and
|
||||
// FnvHashAccelHookCb - UC_HOOK_CODE at the entry, PC=LR, uc_emu_stop.
|
||||
//
|
||||
// Guest layout, read straight out of the decompile:
|
||||
// *(self+8) byte - cache-enabled flag (0 here; if ever non-zero this
|
||||
// layer steps aside and lets the guest run its own
|
||||
// cached path)
|
||||
// *(self+196) ptr - first entry array, 8 bytes per entry {offset, len}
|
||||
// *(self+200) u32 - number of entries in the first array
|
||||
// *(self+204) ptr - second entry array, same element layout
|
||||
// *(self+220) u32 - number of entries in the second array
|
||||
// *(self+224) ptr - string pool A
|
||||
// *(self+228) u32 - offset threshold selecting pool A vs B
|
||||
// *(self+232) ptr - string pool B
|
||||
// Return value: the entry index, or -1 when the name is absent.
|
||||
namespace name_lookup_accel {
|
||||
|
||||
constexpr uint64_t kLookupAddr = 0x4f3704;
|
||||
|
||||
void HookCb(uc_engine* uc, uint64_t address, uint32_t size, void* userData);
|
||||
|
||||
} // namespace name_lookup_accel
|
||||
@@ -0,0 +1,170 @@
|
||||
#include "ostream_repro_test.h"
|
||||
#include "guest_engine.h"
|
||||
|
||||
#include <android/log.h>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
|
||||
namespace {
|
||||
|
||||
// Real device path the artifact gets pushed to ahead of time (see
|
||||
// ostream_repro/build.sh's own instructions and this session's
|
||||
// ARM64_TRANSLATION_LAYER.md entry for the exact adb invocation used) -
|
||||
// this app's own internal files dir, the same directory
|
||||
// GameActivityMain.kt's loadEmulatedLibappFromAssets() extracts the real
|
||||
// libapp.so asset into (`filesDir`), just for a file this project's build
|
||||
// never bundles as an asset itself since it's a throwaway diagnostic, not a
|
||||
// real dependency of the app.
|
||||
constexpr const char* kOstreamReproPath = "/data/data/com.ea.games.nfs13_arm/files/ostream_repro.so";
|
||||
constexpr const char* kEntrySymbol = "TestOstreamAssembly";
|
||||
|
||||
// MUST match ostream_repro.cpp's own four separate operator<< writes
|
||||
// exactly - this is the host-side oracle this test checks the guest's
|
||||
// extracted string against.
|
||||
constexpr const char* kExpected =
|
||||
"//FRAGMENT SHADER\n"
|
||||
"//===========\n\n"
|
||||
"void main()\n{\n"
|
||||
"}\n";
|
||||
|
||||
constexpr uint32_t kResultBufSize = 260; // 4 (int32 length) + up to 256 bytes of content
|
||||
|
||||
} // namespace
|
||||
|
||||
void RunOstreamAssemblyReproTest(GuestEngine& engine) {
|
||||
GuestAddr entry = engine.LoadSecondaryImage(kOstreamReproPath, kEntrySymbol);
|
||||
if (!entry) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "OSTREAM_REPRO",
|
||||
"RunOstreamAssemblyReproTest: LoadSecondaryImage(%s) failed or the entry symbol "
|
||||
"wasn't found - see the preceding GuestEngine log line for which. Skipping (not "
|
||||
"fatal - this artifact is a throwaway diagnostic, not a real app dependency); push "
|
||||
"it via ostream_repro/build.sh + adb if you want this test to actually run.",
|
||||
kOstreamReproPath);
|
||||
return;
|
||||
}
|
||||
|
||||
GuestAddr resultBuf = engine.AllocPermanent(kResultBufSize);
|
||||
if (!resultBuf) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "OSTREAM_REPRO",
|
||||
"RunOstreamAssemblyReproTest: AllocPermanent(%u) for the result buffer failed",
|
||||
kResultBufSize);
|
||||
return;
|
||||
}
|
||||
|
||||
// TestOstreamAssembly(char* outBuf, int outBufSize) - AAPCS32 r0/r1,
|
||||
// matches the convenience 2-arg CallGuestFunction overload exactly.
|
||||
uint32_t retVal = engine.CallGuestFunction(entry, resultBuf, kResultBufSize);
|
||||
|
||||
int32_t reportedLen = 0;
|
||||
memcpy(&reportedLen, engine.G2H(resultBuf), sizeof(reportedLen));
|
||||
int copyLen = reportedLen;
|
||||
if (copyLen < 0) copyLen = 0;
|
||||
if (copyLen > (int)(kResultBufSize - 4)) copyLen = (int)(kResultBufSize - 4);
|
||||
char content[kResultBufSize - 4 + 1] = {0};
|
||||
memcpy(content, (const char*)engine.G2H(resultBuf) + 4, (size_t)copyLen);
|
||||
content[copyLen] = 0;
|
||||
|
||||
size_t expectedLen = strlen(kExpected);
|
||||
bool lengthMatches = (reportedLen == (int32_t)expectedLen);
|
||||
bool contentMatches = lengthMatches && (memcmp(content, kExpected, expectedLen) == 0);
|
||||
|
||||
if (contentMatches) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "OSTREAM_REPRO",
|
||||
"RunOstreamAssemblyReproTest: PASS - extracted std::ostringstream content matches "
|
||||
"exactly what was written (len=%d, retVal=%u). The isolated write-then-extract "
|
||||
"pattern works correctly under GuestEngine in total isolation from libapp.so - "
|
||||
"whatever breaks the real game's sub_4702D8/sub_27160C path is NOT a general "
|
||||
"ostringstream/basic_stringbuf-extraction bug in this engine.",
|
||||
reportedLen, retVal);
|
||||
} else if (reportedLen == 0) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "OSTREAM_REPRO",
|
||||
"RunOstreamAssemblyReproTest: FAIL (REPRODUCES the real-game symptom) - extraction "
|
||||
"came back EMPTY (len=0, retVal=%u) despite writing %zu bytes of real content via "
|
||||
"4 separate operator<< calls, in total isolation from libapp.so. This is the SAME "
|
||||
"'write succeeds, extraction returns empty' symptom already traced through the real "
|
||||
"game's own sub_4702D8/sub_27160C - reproducing it here, in a minimal standalone "
|
||||
"artifact with no game code involved at all, is real evidence this is a general "
|
||||
"std::ostringstream/basic_stringbuf<char>::str()-extraction correctness bug in "
|
||||
"GuestEngine itself, not something specific to libapp.so's own state.",
|
||||
retVal, expectedLen);
|
||||
} else {
|
||||
__android_log_print(ANDROID_LOG_INFO, "OSTREAM_REPRO",
|
||||
"RunOstreamAssemblyReproTest: FAIL (different from the real-game symptom) - "
|
||||
"extracted len=%d (expected %zu), retVal=%u, content=\"%s\" - non-empty but WRONG "
|
||||
"content is a different failure mode than the real game's clean empty-string "
|
||||
"symptom; inspect `content` above before drawing a conclusion either way.",
|
||||
reportedLen, expectedLen, retVal, content);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr const char* kNestedEntrySymbol = "TestOstreamAssemblyNested";
|
||||
// MUST match ostream_repro.cpp's own TestOstreamAssemblyNested exactly.
|
||||
constexpr const char* kNestedExpected =
|
||||
"//VERTEX SHADER\n//=============\n\n"
|
||||
"//Attributes\n//==========\n";
|
||||
} // namespace
|
||||
|
||||
// 2026-09-17, ARM64_TRANSLATION_LAYER.md - the 0x3d3d3d3d heap-overflow
|
||||
// chase. Reuses the same LoadSecondaryImage/CallGuestFunction plumbing as
|
||||
// RunOstreamAssemblyReproTest above, against ostream_repro.cpp's NEW
|
||||
// TestOstreamAssemblyNested export - the FIRST write alone (33 bytes)
|
||||
// already forces libc++'s SSO->heap transition, immediately followed by a
|
||||
// REAL function-call boundary (WriteAttributesSectionNested, noinline) that
|
||||
// writes MORE content into the SAME shared stream - the exact shape of
|
||||
// sub_46FD58 -> sub_4711C8's own real call, in total isolation from every
|
||||
// other line of game code. If this ALSO corrupts/misbehaves under
|
||||
// GuestEngine, that's decisive: the bug is in this engine's own handling of
|
||||
// "grow past SSO on the first write, then write again across a function
|
||||
// call boundary" - not something specific to the real game's broader state.
|
||||
void RunOstreamAssemblyNestedReproTest(GuestEngine& engine) {
|
||||
GuestAddr entry = engine.LoadSecondaryImage(kOstreamReproPath, kNestedEntrySymbol);
|
||||
if (!entry) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "OSTREAM_REPRO",
|
||||
"RunOstreamAssemblyNestedReproTest: LoadSecondaryImage(%s, %s) failed - see the "
|
||||
"preceding GuestEngine log line for why. Push a freshly-rebuilt ostream_repro.so "
|
||||
"if this is unexpected.",
|
||||
kOstreamReproPath, kNestedEntrySymbol);
|
||||
return;
|
||||
}
|
||||
|
||||
GuestAddr resultBuf = engine.AllocPermanent(kResultBufSize);
|
||||
if (!resultBuf) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "OSTREAM_REPRO",
|
||||
"RunOstreamAssemblyNestedReproTest: AllocPermanent(%u) for the result buffer failed",
|
||||
kResultBufSize);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t retVal = engine.CallGuestFunction(entry, resultBuf, kResultBufSize);
|
||||
|
||||
int32_t reportedLen = 0;
|
||||
memcpy(&reportedLen, engine.G2H(resultBuf), sizeof(reportedLen));
|
||||
int copyLen = reportedLen;
|
||||
if (copyLen < 0) copyLen = 0;
|
||||
if (copyLen > (int)(kResultBufSize - 4)) copyLen = (int)(kResultBufSize - 4);
|
||||
char content[kResultBufSize - 4 + 1] = {0};
|
||||
memcpy(content, (const char*)engine.G2H(resultBuf) + 4, (size_t)copyLen);
|
||||
content[copyLen] = 0;
|
||||
|
||||
size_t expectedLen = strlen(kNestedExpected);
|
||||
bool lengthMatches = (reportedLen == (int32_t)expectedLen);
|
||||
bool contentMatches = lengthMatches && (memcmp(content, kNestedExpected, expectedLen) == 0);
|
||||
|
||||
if (contentMatches) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "OSTREAM_REPRO",
|
||||
"RunOstreamAssemblyNestedReproTest: PASS - len=%d retVal=%u matches exactly. The "
|
||||
"'long first write forcing SSO->heap, then a nested function call writing more into "
|
||||
"the same stream' pattern works correctly in total isolation - the real crash's bug "
|
||||
"is NOT reproduced by this shape alone, so something ELSE about the real game's "
|
||||
"broader state/heap layout at that moment is implicated instead.",
|
||||
reportedLen, retVal);
|
||||
} else {
|
||||
__android_log_print(ANDROID_LOG_INFO, "OSTREAM_REPRO",
|
||||
"RunOstreamAssemblyNestedReproTest: FAIL - len=%d (expected %zu) retVal=%u "
|
||||
"content=\"%s\" - THIS ISOLATED SHAPE ALONE reproduces a problem, independent of "
|
||||
"the real game's own state - strong evidence the bug is in GuestEngine's general "
|
||||
"handling of this exact call pattern, not something libapp.so-specific.",
|
||||
reportedLen, expectedLen, retVal, content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
class GuestEngine;
|
||||
|
||||
// 2026-09-16, ARM64_TRANSLATION_LAYER.md - "isolated std::ostringstream
|
||||
// repro" test. Loads a SEPARATE, tiny, independently-compiled armeabi-v7a
|
||||
// artifact (../../../ostream_repro/ostream_repro.cpp, built via
|
||||
// ostream_repro/build.sh, pushed to the device ahead of time - NOT part of
|
||||
// this Gradle build, see that file's own comments) via
|
||||
// GuestEngine::LoadSecondaryImage, then calls its one exported entry point
|
||||
// (TestOstreamAssembly) and logs whether the write-then-extract
|
||||
// std::ostringstream pattern this artifact exercises comes back correct or
|
||||
// empty - the same symptom this session spent most of 2026-09-16 tracing
|
||||
// through the real game's own sub_4702D8/sub_27160C, but here in total
|
||||
// isolation from every other line of game code. Per the user's own explicit
|
||||
// direction ("ты сейчас пытаешься подогнать эмуляцию к одному единственному
|
||||
// бинарнику, это не правильный подход"), this is what actually answers
|
||||
// "is this a general bug in GuestEngine, or something specific to
|
||||
// libapp.so's own state" - continuing to probe more hardcoded libapp.so
|
||||
// addresses could not have answered that question no matter how far it
|
||||
// went.
|
||||
//
|
||||
// Logs its own PASS/FAIL verdict unambiguously (tag "OSTREAM_REPRO") -
|
||||
// see ostream_repro_test.cpp's own top comment for exactly what counts as
|
||||
// each. No-ops (logs and returns) if the secondary image fails to load -
|
||||
// e.g. the artifact was never pushed to this device - so this is safe to
|
||||
// leave wired into LoadEmulatedLibapp without risking the real game's own
|
||||
// boot sequence if the file is simply missing.
|
||||
void RunOstreamAssemblyReproTest(GuestEngine& engine);
|
||||
|
||||
// 2026-09-17, ARM64_TRANSLATION_LAYER.md - the 0x3d3d3d3d heap-overflow
|
||||
// chase. Same idea as above, against ostream_repro.cpp's
|
||||
// TestOstreamAssemblyNested export instead - a long first write forcing an
|
||||
// immediate SSO->heap transition, then a real (noinline) function-call
|
||||
// boundary writing more into the same shared stream, matching
|
||||
// sub_46FD58->sub_4711C8's own shape. See ostream_repro_test.cpp's own
|
||||
// comment for the full rationale.
|
||||
void RunOstreamAssemblyNestedReproTest(GuestEngine& engine);
|
||||
@@ -0,0 +1,67 @@
|
||||
#include "profiler.h"
|
||||
#include "../util/util.h"
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
namespace {
|
||||
|
||||
std::atomic<bool> g_profilingEnabled{false};
|
||||
std::mutex g_histMutex;
|
||||
std::unordered_map<uint64_t, uint64_t> g_hist;
|
||||
std::atomic<uint64_t> g_totalSamples{0};
|
||||
std::atomic<bool> g_dumpThreadStarted{false};
|
||||
|
||||
} // namespace
|
||||
|
||||
void EnableProfiling() { g_profilingEnabled.store(true, std::memory_order_relaxed); }
|
||||
bool ProfilingEnabled() { return g_profilingEnabled.load(std::memory_order_relaxed); }
|
||||
|
||||
void ProfileBlockHookCb(uc_engine*, uint64_t address, uint32_t, void*) {
|
||||
// thread_local, not a shared "last sample" - each real host thread has
|
||||
// its own uc_engine (see guest_engine.h's "Multithreading model") and
|
||||
// fires this independently; gating per-thread avoids one busy thread's
|
||||
// sampling starving another's.
|
||||
static thread_local std::chrono::steady_clock::time_point lastSample{};
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (now - lastSample < std::chrono::milliseconds(2)) return;
|
||||
lastSample = now;
|
||||
|
||||
std::lock_guard<std::mutex> lock(g_histMutex);
|
||||
g_hist[address]++;
|
||||
g_totalSamples.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void StartProfileDumpThread() {
|
||||
bool expected = false;
|
||||
if (!g_dumpThreadStarted.compare_exchange_strong(expected, true)) return; // already running
|
||||
|
||||
std::thread([]() {
|
||||
while (true) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
|
||||
std::vector<std::pair<uint64_t, uint64_t>> top;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_histMutex);
|
||||
top.assign(g_hist.begin(), g_hist.end());
|
||||
}
|
||||
if (top.empty()) continue;
|
||||
|
||||
std::sort(top.begin(), top.end(),
|
||||
[](const auto& a, const auto& b) { return a.second > b.second; });
|
||||
uint64_t total = g_totalSamples.load(std::memory_order_relaxed);
|
||||
|
||||
Log("PROFILE: %llu total samples across %zu distinct block addresses - top hot PCs:",
|
||||
(unsigned long long)total, top.size());
|
||||
for (size_t i = 0; i < top.size() && i < 15; i++) {
|
||||
Log("PROFILE: 0x%llx - %llu samples (%.1f%%)",
|
||||
(unsigned long long)top[i].first, (unsigned long long)top[i].second,
|
||||
total ? 100.0 * (double)top[i].second / (double)total : 0.0);
|
||||
}
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <unicorn/unicorn.h>
|
||||
|
||||
// Throwaway sampling profiler for the "why did onCreate take 115 seconds"
|
||||
// investigation (see ARM64_TRANSLATION_LAYER.md, 2026-09-01 entry) - NOT a
|
||||
// permanent part of the architecture, just the cheapest way to answer "where
|
||||
// does the guest CPU actually spend its time" without a real disassembler-
|
||||
// aware profiler. No-op unless EnableProfiling() is called (see main.cpp).
|
||||
//
|
||||
// Mechanism: a UC_HOOK_BLOCK callback (fires once per translated basic
|
||||
// block, not per instruction - much cheaper) installed on every guest
|
||||
// engine (see GuestEngine::EnsureThreadEngine) across the loaded image's own
|
||||
// code range. Time-gated per-thread sampling (skips most block hits, only
|
||||
// records roughly every 2ms of wall-clock time) keeps overhead from
|
||||
// dominating the very thing being measured. A background thread dumps the
|
||||
// hottest sampled PCs to logcat every few seconds - cross-reference those
|
||||
// addresses against the IDA database (native_lib/libapp.so.i64, guest
|
||||
// addresses ARE real file vaddrs - see guest_engine.h's own class comment on
|
||||
// why bias is always 0) to find which real functions are hot.
|
||||
void EnableProfiling();
|
||||
bool ProfilingEnabled();
|
||||
|
||||
// UC_HOOK_BLOCK callback - pass directly to uc_hook_add.
|
||||
void ProfileBlockHookCb(uc_engine* uc, uint64_t address, uint32_t size, void* user_data);
|
||||
|
||||
// Starts (once) a detached background thread that logs the top hottest
|
||||
// sampled PCs every 3 seconds until the process exits.
|
||||
void StartProfileDumpThread();
|
||||
@@ -0,0 +1,293 @@
|
||||
#include "pthread_shim.h"
|
||||
#include "../util/util.h"
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <chrono>
|
||||
#include <unordered_map>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
|
||||
namespace {
|
||||
|
||||
// ---- Guest pthread_t handle table ----
|
||||
// Real bionic pthread_t values are host-thread-implementation-specific and
|
||||
// never exposed to guest code - guest code only ever receives an opaque
|
||||
// uint32_t handle from Shim_pthread_create and only ever passes it back into
|
||||
// Shim_pthread_join, so this encoding is a private contract of this file,
|
||||
// not a real bionic ABI.
|
||||
struct GuestThreadRecord {
|
||||
std::thread thread;
|
||||
};
|
||||
std::mutex g_threadsMutex;
|
||||
std::unordered_map<uint32_t, GuestThreadRecord*> g_threads;
|
||||
uint32_t g_nextThreadHandle = 1;
|
||||
|
||||
uint32_t Shim_pthread_create(GuestEngine& eng, uint32_t threadOutPtr, uint32_t /*attr*/,
|
||||
uint32_t startRoutine, uint32_t arg, uint32_t) {
|
||||
if (!startRoutine) {
|
||||
Log("pthread_shim: pthread_create with null start_routine");
|
||||
return -1;
|
||||
}
|
||||
// Diagnostic: log the REAL caller address (LR at the point this stub
|
||||
// was entered - register state is still exactly as the hook fired with,
|
||||
// since import_stub_dispatch_cb hasn't written anything back yet at
|
||||
// this point) so a suspiciously-fast-returning thread's actual call
|
||||
// site can be found in IDA, and whether startRoutine itself is real
|
||||
// libapp.so code vs one of our own stub addresses (anything >=
|
||||
// GuestEngine's own image_end() is a stub, not real code - see
|
||||
// guest_engine.h's arena layout).
|
||||
{
|
||||
uint32_t lr = 0;
|
||||
uc_reg_read(eng.uc(), UC_ARM_REG_LR, &lr);
|
||||
Log("pthread_shim: pthread_create called from guest LR=0x%x, startRoutine=0x%x (image_end=0x%x)",
|
||||
lr, startRoutine, eng.image_end());
|
||||
}
|
||||
|
||||
// startRoutine must point into the real loaded image - anything else is
|
||||
// not ARM32 code at all, it's one of this engine's own arena addresses
|
||||
// (heap/trampoline/import-stub/misc-stub/control/thread-stacks all live
|
||||
// past image_end()). Spawning a host thread that calls
|
||||
// CallGuestFunction() on such an address doesn't fail cleanly - if it
|
||||
// happens to land on one of this engine's own AllocCodeStub dispatch
|
||||
// points, it invokes some unrelated real shim with whatever garbage sits
|
||||
// in r1-r3 for a freshly-created, never-primed uc_engine, which was
|
||||
// previously observed to cascade into a burst of unrelated shim calls
|
||||
// with corrupted arguments and an eventual MEM FAULT at image_end_
|
||||
// itself (see ARM64_TRANSLATION_LAYER.md, "register/stack corruption"
|
||||
// investigation). Reject loudly and immediately instead - one clear
|
||||
// diagnostic beats six confusing downstream ones.
|
||||
if (startRoutine >= eng.image_end()) {
|
||||
Log("pthread_shim: REFUSING pthread_create - startRoutine=0x%x is not real image code (%s)",
|
||||
startRoutine, eng.DescribeAddress(startRoutine).c_str());
|
||||
return 22; // EINVAL - matches pthread_create's own errno-style failure contract
|
||||
}
|
||||
|
||||
uint32_t handle;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_threadsMutex);
|
||||
handle = g_nextThreadHandle++;
|
||||
}
|
||||
|
||||
auto* record = new GuestThreadRecord();
|
||||
// `eng` is always GuestEngine::Instance() (a static singleton, see
|
||||
// ImportShimFn's contract) - safe to capture by reference into a thread
|
||||
// that outlives this call.
|
||||
record->thread = std::thread([&eng, startRoutine, arg, handle]() {
|
||||
// Every new host thread needs its OWN uc_engine before it can touch
|
||||
// any guest register - see guest_engine.h's "Multithreading model".
|
||||
eng.EnsureThreadEngine();
|
||||
uint32_t result = eng.CallGuestFunction(startRoutine, arg);
|
||||
Log("pthread_shim: guest thread (handle %u) start_routine returned 0x%x", handle, result);
|
||||
});
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_threadsMutex);
|
||||
g_threads[handle] = record;
|
||||
}
|
||||
|
||||
if (threadOutPtr) memcpy(eng.G2H(threadOutPtr), &handle, sizeof(handle));
|
||||
Log("pthread_shim: pthread_create -> guest handle %u, start_routine=0x%x, arg=0x%x",
|
||||
handle, startRoutine, arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t Shim_pthread_join(GuestEngine& eng, uint32_t handle, uint32_t retvalOutPtr, uint32_t, uint32_t, uint32_t) {
|
||||
GuestThreadRecord* record = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_threadsMutex);
|
||||
auto it = g_threads.find(handle);
|
||||
if (it != g_threads.end()) record = it->second;
|
||||
}
|
||||
if (!record) {
|
||||
Log("pthread_shim: pthread_join(%u) - unknown handle", handle);
|
||||
return 3; // ESRCH
|
||||
}
|
||||
if (record->thread.joinable()) record->thread.join();
|
||||
// The guest start_routine's real void* return value isn't propagated
|
||||
// here (see pthread_shim.h's own gap list) - writing 0 is the closest
|
||||
// correct-shaped stand-in for callers that check *retval for NULL.
|
||||
if (retvalOutPtr) {
|
||||
uint32_t zero = 0;
|
||||
memcpy(eng.G2H(retvalOutPtr), &zero, sizeof(zero));
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_threadsMutex);
|
||||
g_threads.erase(handle);
|
||||
}
|
||||
delete record;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- Mutexes ----
|
||||
// Keyed by the GUEST ADDRESS of the pthread_mutex_t object (stable for its
|
||||
// lifetime - always a field of some other guest struct or a guest global,
|
||||
// never moved). std::recursive_mutex (not std::mutex) regardless of the
|
||||
// real attr type requested - see pthread_shim.h's gap list for why.
|
||||
std::mutex g_mutexTableMutex;
|
||||
std::unordered_map<uint32_t, std::recursive_mutex*> g_mutexes;
|
||||
|
||||
std::recursive_mutex* GetOrCreateMutex(uint32_t guestAddr) {
|
||||
std::lock_guard<std::mutex> lock(g_mutexTableMutex);
|
||||
auto it = g_mutexes.find(guestAddr);
|
||||
if (it != g_mutexes.end()) return it->second;
|
||||
auto* m = new std::recursive_mutex();
|
||||
g_mutexes[guestAddr] = m;
|
||||
return m;
|
||||
}
|
||||
|
||||
uint32_t Shim_pthread_mutex_init(GuestEngine&, uint32_t mutexPtr, uint32_t /*attr*/, uint32_t, uint32_t, uint32_t) {
|
||||
if (mutexPtr) GetOrCreateMutex(mutexPtr);
|
||||
return 0;
|
||||
}
|
||||
uint32_t Shim_pthread_mutex_lock(GuestEngine&, uint32_t mutexPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
if (!mutexPtr) return 22; // EINVAL
|
||||
GetOrCreateMutex(mutexPtr)->lock();
|
||||
return 0;
|
||||
}
|
||||
uint32_t Shim_pthread_mutex_unlock(GuestEngine&, uint32_t mutexPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
if (!mutexPtr) return 22; // EINVAL
|
||||
GetOrCreateMutex(mutexPtr)->unlock();
|
||||
return 0;
|
||||
}
|
||||
uint32_t Shim_pthread_mutex_trylock(GuestEngine&, uint32_t mutexPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
if (!mutexPtr) return 22; // EINVAL
|
||||
return GetOrCreateMutex(mutexPtr)->try_lock() ? 0 : 16; // EBUSY
|
||||
}
|
||||
uint32_t Shim_pthread_mutex_destroy(GuestEngine&, uint32_t mutexPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
std::lock_guard<std::mutex> lock(g_mutexTableMutex);
|
||||
auto it = g_mutexes.find(mutexPtr);
|
||||
if (it != g_mutexes.end()) {
|
||||
delete it->second;
|
||||
g_mutexes.erase(it);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- Condition variables ----
|
||||
// std::condition_variable_any (not std::condition_variable) specifically
|
||||
// because it's the variant that works with std::recursive_mutex - a plain
|
||||
// std::condition_variable only accepts std::unique_lock<std::mutex>.
|
||||
std::mutex g_condTableMutex;
|
||||
std::unordered_map<uint32_t, std::condition_variable_any*> g_conds;
|
||||
|
||||
std::condition_variable_any* GetOrCreateCond(uint32_t guestAddr) {
|
||||
std::lock_guard<std::mutex> lock(g_condTableMutex);
|
||||
auto it = g_conds.find(guestAddr);
|
||||
if (it != g_conds.end()) return it->second;
|
||||
auto* cv = new std::condition_variable_any();
|
||||
g_conds[guestAddr] = cv;
|
||||
return cv;
|
||||
}
|
||||
|
||||
uint32_t Shim_pthread_cond_init(GuestEngine&, uint32_t condPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
if (condPtr) GetOrCreateCond(condPtr);
|
||||
return 0;
|
||||
}
|
||||
uint32_t Shim_pthread_cond_destroy(GuestEngine&, uint32_t condPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
std::lock_guard<std::mutex> lock(g_condTableMutex);
|
||||
auto it = g_conds.find(condPtr);
|
||||
if (it != g_conds.end()) {
|
||||
delete it->second;
|
||||
g_conds.erase(it);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// TEMP diagnostic for the render-stall investigation (see
|
||||
// ARM64_TRANSLATION_LAYER.md's "pthread_cond_wait deadlock" plan) - logs the
|
||||
// real guest caller (LR, same technique Shim_pthread_create already uses)
|
||||
// and the condvar's guest address, so a hang can be traced to the exact
|
||||
// calling function in IDA instead of guessed at.
|
||||
uint32_t GuestCallerLR(GuestEngine& eng) {
|
||||
uint32_t lr = 0;
|
||||
uc_reg_read(eng.uc(), UC_ARM_REG_LR, &lr);
|
||||
return lr;
|
||||
}
|
||||
|
||||
// Diagnostic tracing for the condvar shims (task #7's deadlock hunt). OFF by
|
||||
// default since 2026-09-19: signal alone fired 20,085 times in one
|
||||
// prologue-load capture, and every one of these lines is a blocking write to
|
||||
// logd on a path the engine takes constantly. Flip to true for a targeted
|
||||
// deadlock capture, not for normal runs.
|
||||
constexpr bool kTraceCondVars = false;
|
||||
|
||||
uint32_t Shim_pthread_cond_signal(GuestEngine& eng, uint32_t condPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
if (kTraceCondVars)
|
||||
Log("pthread_shim: pthread_cond_signal(cond=0x%x) from guest LR=0x%x", condPtr, GuestCallerLR(eng));
|
||||
if (condPtr) GetOrCreateCond(condPtr)->notify_one();
|
||||
return 0;
|
||||
}
|
||||
uint32_t Shim_pthread_cond_broadcast(GuestEngine& eng, uint32_t condPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
Log("pthread_shim: pthread_cond_broadcast(cond=0x%x) from guest LR=0x%x", condPtr, GuestCallerLR(eng));
|
||||
if (condPtr) GetOrCreateCond(condPtr)->notify_all();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Real pthread_cond_wait semantics: mutexPtr is ALREADY locked (by this same
|
||||
// guest thread) on entry, must be atomically unlocked while waiting, and
|
||||
// re-locked before returning. std::condition_variable_any::wait(lock) wants
|
||||
// to manage a Lockable itself, so this adopts the ALREADY-held lock
|
||||
// (adopt_lock - no double-lock attempt), waits (which correctly unlocks/
|
||||
// relocks around the real OS wait), then release()s the unique_lock's
|
||||
// ownership WITHOUT unlocking, since the real mutex must stay locked for the
|
||||
// caller on return - exactly matching real pthread_cond_wait's contract.
|
||||
uint32_t Shim_pthread_cond_wait(GuestEngine& eng, uint32_t condPtr, uint32_t mutexPtr, uint32_t, uint32_t, uint32_t) {
|
||||
if (!condPtr || !mutexPtr) return 22; // EINVAL
|
||||
Log("pthread_shim: pthread_cond_wait(cond=0x%x, mutex=0x%x) from guest LR=0x%x - blocking now",
|
||||
condPtr, mutexPtr, GuestCallerLR(eng));
|
||||
auto* cv = GetOrCreateCond(condPtr);
|
||||
auto* mtx = GetOrCreateMutex(mutexPtr);
|
||||
std::unique_lock<std::recursive_mutex> lock(*mtx, std::adopt_lock);
|
||||
cv->wait(lock);
|
||||
lock.release();
|
||||
Log("pthread_shim: pthread_cond_wait(cond=0x%x) woke up", condPtr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t Shim_pthread_cond_timedwait(GuestEngine& eng, uint32_t condPtr, uint32_t mutexPtr,
|
||||
uint32_t abstimePtr, uint32_t, uint32_t) {
|
||||
if (!condPtr || !mutexPtr) return 22; // EINVAL
|
||||
auto* cv = GetOrCreateCond(condPtr);
|
||||
auto* mtx = GetOrCreateMutex(mutexPtr);
|
||||
std::unique_lock<std::recursive_mutex> lock(*mtx, std::adopt_lock);
|
||||
|
||||
std::cv_status status = std::cv_status::no_timeout;
|
||||
if (abstimePtr) {
|
||||
// Guest struct timespec { long tv_sec; long tv_nsec; } - both
|
||||
// 32-bit fields on this ABI, 8 bytes total, same layout host-side.
|
||||
uint32_t sec = 0, nsec = 0;
|
||||
memcpy(&sec, eng.G2H(abstimePtr), 4);
|
||||
memcpy(&nsec, eng.G2H(abstimePtr + 4), 4);
|
||||
// abstime is CLOCK_REALTIME-based unless the guest called
|
||||
// pthread_condattr_setclock first (not observed at any call site) -
|
||||
// std::chrono::system_clock is the closest host equivalent.
|
||||
auto deadline = std::chrono::system_clock::from_time_t((time_t)sec) +
|
||||
std::chrono::nanoseconds(nsec);
|
||||
status = cv->wait_until(lock, deadline);
|
||||
} else {
|
||||
cv->wait(lock);
|
||||
}
|
||||
|
||||
lock.release();
|
||||
return status == std::cv_status::timeout ? 110 : 0; // ETIMEDOUT
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void RegisterPthreadImportShims(GuestEngine& engine) {
|
||||
engine.RegisterImportShim("pthread_create", Shim_pthread_create);
|
||||
engine.RegisterImportShim("pthread_join", Shim_pthread_join);
|
||||
|
||||
engine.RegisterImportShim("pthread_mutex_init", Shim_pthread_mutex_init);
|
||||
engine.RegisterImportShim("pthread_mutex_lock", Shim_pthread_mutex_lock);
|
||||
engine.RegisterImportShim("pthread_mutex_unlock", Shim_pthread_mutex_unlock);
|
||||
engine.RegisterImportShim("pthread_mutex_trylock", Shim_pthread_mutex_trylock);
|
||||
engine.RegisterImportShim("pthread_mutex_destroy", Shim_pthread_mutex_destroy);
|
||||
|
||||
engine.RegisterImportShim("pthread_cond_init", Shim_pthread_cond_init);
|
||||
engine.RegisterImportShim("pthread_cond_destroy", Shim_pthread_cond_destroy);
|
||||
engine.RegisterImportShim("pthread_cond_signal", Shim_pthread_cond_signal);
|
||||
engine.RegisterImportShim("pthread_cond_broadcast", Shim_pthread_cond_broadcast);
|
||||
engine.RegisterImportShim("pthread_cond_wait", Shim_pthread_cond_wait);
|
||||
engine.RegisterImportShim("pthread_cond_timedwait", Shim_pthread_cond_timedwait);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "guest_engine.h"
|
||||
|
||||
// Real pthread threading support - replaces the old single-threaded no-op
|
||||
// fakes (see import_shims.cpp's own top comment for what those used to be).
|
||||
// pthread_create spawns a genuine host std::thread that calls
|
||||
// GuestEngine::EnsureThreadEngine() (its own uc_engine sharing the same
|
||||
// guest memory - see guest_engine.h's "Multithreading model") before running
|
||||
// the guest start_routine; pthread_mutex_t/pthread_cond_t are backed by real
|
||||
// std::recursive_mutex/std::condition_variable_any objects, keyed by the
|
||||
// GUEST ADDRESS of the pthread_mutex_t/pthread_cond_t object itself (stable
|
||||
// for the object's lifetime - these are always fields of some other guest
|
||||
// struct or globals, never moved).
|
||||
//
|
||||
// Known gaps (documented, not silently wrong):
|
||||
// - pthread_attr_t is entirely ignored (detached-vs-joinable, stack size,
|
||||
// scheduling priority) - every guest thread is created host-joinable
|
||||
// regardless of what the guest requested; pthread_join is the only exit
|
||||
// path this shim supports (a guest thread created "detached" that's never
|
||||
// joined will leak its host std::thread object, same as an ordinary
|
||||
// pthread_join call the caller forgets to make - acceptable for a
|
||||
// prototype scope).
|
||||
// - pthread_exit() is NOT implemented (falls through to the generic
|
||||
// "unresolved import, return 0" handler) - a guest thread that calls it
|
||||
// explicitly (rather than just returning from its start_routine, the
|
||||
// common case) will incorrectly resume as if it were an ordinary no-op
|
||||
// call rather than unwinding. Fixing this properly needs
|
||||
// import_stub_dispatch_cb itself (guest_engine.cpp) to let a shim
|
||||
// override the unconditional "write PC=lr" return path, which no other
|
||||
// shim currently needs - deferred.
|
||||
// - std::recursive_mutex (not std::mutex) backs every guest mutex
|
||||
// regardless of the real attr type requested, specifically so a
|
||||
// PTHREAD_MUTEX_RECURSIVE guest mutex (common in game engines) can't
|
||||
// self-deadlock against a plain std::mutex that doesn't support it - a
|
||||
// deliberately safe default, not a precise recursive/non-recursive
|
||||
// distinction.
|
||||
void RegisterPthreadImportShims(GuestEngine& engine);
|
||||
@@ -0,0 +1,819 @@
|
||||
#include "rtti_shims.h"
|
||||
#include "../util/util.h"
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
||||
std::string GuestCStr(GuestEngine& eng, uint32_t p) {
|
||||
return p ? std::string((const char*)eng.G2H(p)) : std::string();
|
||||
}
|
||||
GuestAddr HeapCopyString(GuestEngine& eng, const char* s) {
|
||||
if (!s) return 0;
|
||||
size_t len = strlen(s);
|
||||
GuestAddr buf = eng.heap().Alloc((uint32_t)len + 1);
|
||||
if (buf) memcpy(eng.G2H(buf), s, len + 1);
|
||||
return buf;
|
||||
}
|
||||
// Same idea as HeapCopyString but for content that must outlive the general
|
||||
// heap's own churn (see GuestHeap's own class comment) - the small number
|
||||
// of type_info name strings this file builds once, at load time, never
|
||||
// change or get freed.
|
||||
GuestAddr AllocPermanentString(GuestEngine& eng, const char* s) {
|
||||
size_t len = strlen(s);
|
||||
GuestAddr addr = eng.AllocPermanent((uint32_t)len + 1);
|
||||
if (addr) memcpy(eng.G2H(addr), s, len + 1);
|
||||
return addr;
|
||||
}
|
||||
|
||||
// Itanium C++ ABI: an object's STORED vtable pointer is the address of
|
||||
// vtable slot 2 (skipping the offset-to-top and RTTI-pointer slots at
|
||||
// negative indices) - i.e. "vtable_symbol + 2*sizeof(void*)". On this
|
||||
// 32-bit ABI that's +8. This session's ELF-relocation-addend fix
|
||||
// (guest_engine.cpp's ProcessRelocations) is what makes a real type_info
|
||||
// object's vtable-pointer field actually end up with this +8 already
|
||||
// applied (the file stores it as the relocation's implicit addend) -
|
||||
// without that fix every such field would have silently collapsed back to
|
||||
// the bare vtable symbol address.
|
||||
constexpr uint32_t kVtablePtrAdjust = 8;
|
||||
|
||||
GuestAddr g_classTI = 0, g_siClassTI = 0, g_vmiClassTI = 0, g_pointerTI = 0, g_functionTI = 0;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct GuestSiClassTypeInfo {
|
||||
uint32_t vtable_ptr, name_ptr, base_type;
|
||||
};
|
||||
struct GuestVmiBaseInfo {
|
||||
uint32_t base_type, offset_flags;
|
||||
};
|
||||
struct GuestVmiClassTypeInfoHeader {
|
||||
uint32_t vtable_ptr, name_ptr, flags, base_count;
|
||||
// GuestVmiBaseInfo bases[base_count] follows immediately
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// Walks `typeInfoAddr`'s base-class hierarchy looking for `dstTypeInfoAddr`,
|
||||
// accumulating the byte offset. This is __dynamic_cast's own core
|
||||
// algorithm reimplemented directly over the type_info DATA (base pointers,
|
||||
// offsets) rather than through real virtual dispatch - see rtti_shims.h's
|
||||
// own top comment for why that's both sufficient and safer than guessing
|
||||
// at real libc++abi vtable slot layouts. Handles __class_type_info (no
|
||||
// bases - stops here), __si_class_type_info (single base, recurse), and
|
||||
// __vmi_class_type_info (multiple bases, recurse each, fail on ambiguity).
|
||||
// Virtual-base offsets (need the vbase table, not just a static offset)
|
||||
// are a documented gap - skipped, not silently wrong.
|
||||
bool SearchBase(GuestEngine& eng, GuestAddr typeInfoAddr, GuestAddr dstTypeInfoAddr,
|
||||
int32_t curOffset, int32_t* outOffset, int depth) {
|
||||
if (!typeInfoAddr || depth > 32) return false;
|
||||
if (typeInfoAddr == dstTypeInfoAddr) {
|
||||
*outOffset = curOffset;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t vtablePtr = 0;
|
||||
memcpy(&vtablePtr, eng.G2H(typeInfoAddr), 4);
|
||||
|
||||
if (vtablePtr == g_siClassTI + kVtablePtrAdjust) {
|
||||
GuestSiClassTypeInfo si{};
|
||||
memcpy(&si, eng.G2H(typeInfoAddr), sizeof(si));
|
||||
return SearchBase(eng, si.base_type, dstTypeInfoAddr, curOffset, outOffset, depth + 1);
|
||||
}
|
||||
if (vtablePtr == g_vmiClassTI + kVtablePtrAdjust) {
|
||||
GuestVmiClassTypeInfoHeader hdr{};
|
||||
memcpy(&hdr, eng.G2H(typeInfoAddr), sizeof(hdr));
|
||||
bool found = false;
|
||||
int32_t foundOffset = 0;
|
||||
for (uint32_t i = 0; i < hdr.base_count; i++) {
|
||||
GuestVmiBaseInfo bi{};
|
||||
memcpy(&bi, (uint8_t*)eng.G2H(typeInfoAddr) + sizeof(hdr) + (size_t)i * sizeof(GuestVmiBaseInfo), sizeof(bi));
|
||||
bool isVirtualBase = (bi.offset_flags & 0x1) != 0;
|
||||
if (isVirtualBase) continue; // needs the vbase table - documented gap, not attempted
|
||||
int32_t thisBaseOffset = curOffset + (int32_t)(bi.offset_flags >> 8);
|
||||
int32_t candidateOffset = 0;
|
||||
if (SearchBase(eng, bi.base_type, dstTypeInfoAddr, thisBaseOffset, &candidateOffset, depth + 1)) {
|
||||
if (found && candidateOffset != foundOffset) return false; // ambiguous - real dynamic_cast fails too
|
||||
found = true;
|
||||
foundOffset = candidateOffset;
|
||||
}
|
||||
}
|
||||
if (found) { *outOffset = foundOffset; return true; }
|
||||
return false;
|
||||
}
|
||||
// __class_type_info (no bases), or anything this file didn't
|
||||
// synthesize (a real, already-present class_type_info in the guest
|
||||
// image whose own vtable pointer we don't recognize because its
|
||||
// relocation resolved to something other than our markers - shouldn't
|
||||
// happen for symbols this file registered, but a real class WITHOUT
|
||||
// any bases legitimately looks exactly like this too) - nothing
|
||||
// further to walk.
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t Shim_dynamic_cast(GuestEngine& eng, uint32_t srcPtr, uint32_t /*srcTypeInfo*/,
|
||||
uint32_t dstTypeInfo, uint32_t /*src2dstHint*/, uint32_t) {
|
||||
if (!srcPtr || !dstTypeInfo) return 0;
|
||||
uint32_t objVtablePtr = 0;
|
||||
memcpy(&objVtablePtr, eng.G2H(srcPtr), 4);
|
||||
if (!objVtablePtr) return 0;
|
||||
|
||||
// vtable[-1] (*(vtablePtr-4)) is the object's ACTUAL (most-derived)
|
||||
// dynamic type_info; vtable[-2] (*(vtablePtr-8)) is "offset to top" -
|
||||
// the byte offset from srcPtr back to the start of the complete
|
||||
// object. Both are standard Itanium ABI vtable layout, already present
|
||||
// in the guest binary's own compiled vtables (built by the real
|
||||
// original toolchain) - nothing this file needs to synthesize.
|
||||
uint32_t dynTypeInfo = 0, offsetToTopRaw = 0;
|
||||
memcpy(&dynTypeInfo, eng.G2H(objVtablePtr - 4), 4);
|
||||
memcpy(&offsetToTopRaw, eng.G2H(objVtablePtr - 8), 4);
|
||||
GuestAddr mostDerivedPtr = (GuestAddr)((int32_t)srcPtr + (int32_t)offsetToTopRaw);
|
||||
|
||||
int32_t foundOffset = 0;
|
||||
if (!SearchBase(eng, dynTypeInfo, dstTypeInfo, 0, &foundOffset, 0)) return 0;
|
||||
return (uint32_t)((int32_t)mostDerivedPtr + foundOffset);
|
||||
}
|
||||
|
||||
uint32_t Shim_cxa_bad_typeid(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
// Real __cxa_bad_typeid throws std::bad_typeid, unwinding the guest
|
||||
// call stack to the nearest catch - this engine has no guest stack
|
||||
// unwinder (no DWARF CFI / guest .eh_frame interpretation, no
|
||||
// _Unwind_RaiseException equivalent - a separate, much bigger
|
||||
// subsystem than anything else in this file). Logged, then returns as
|
||||
// if nothing happened, which is wrong (guest code proceeds instead of
|
||||
// unwinding) but safer than corrupting guest state trying to fake a
|
||||
// throw.
|
||||
static bool logged = false;
|
||||
if (!logged) { logged = true; Log("rtti_shims: __cxa_bad_typeid() - can't really throw (no guest stack unwinder), see rtti_shims.h's own comment"); }
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- Minimal std::exception hierarchy ----
|
||||
// Simplified but internally consistent 8-byte layout - {vtable_ptr(4);
|
||||
// msg_ptr(4)} - matching real libc++'s actual runtime_error/logic_error
|
||||
// SIZE (one extra pointer-sized member beyond exception's vtable-only
|
||||
// base, so this fits within whatever the guest's own compiled `new
|
||||
// std::runtime_error(...)` allocated) without replicating libc++'s
|
||||
// internal __libcpp_refstring refcount-header format - safe because libc++
|
||||
// itself was never statically linked here to independently read these
|
||||
// bytes; only this file's own ctor/what()/dtor ever touch them.
|
||||
uint32_t Shim_runtime_error_ctor(GuestEngine& eng, uint32_t thisPtr, uint32_t whatPtr, uint32_t, uint32_t, uint32_t) {
|
||||
if (!thisPtr) return 0;
|
||||
GuestAddr msg = whatPtr ? HeapCopyString(eng, GuestCStr(eng, whatPtr).c_str()) : 0;
|
||||
memcpy((uint8_t*)eng.G2H(thisPtr) + 4, &msg, 4);
|
||||
return thisPtr;
|
||||
}
|
||||
uint32_t Shim_runtime_error_what(GuestEngine& eng, uint32_t thisPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
if (!thisPtr) return 0;
|
||||
uint32_t msg = 0;
|
||||
memcpy(&msg, (uint8_t*)eng.G2H(thisPtr) + 4, 4);
|
||||
return msg;
|
||||
}
|
||||
uint32_t Shim_runtime_error_dtor(GuestEngine& eng, uint32_t thisPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
if (thisPtr) {
|
||||
uint32_t msg = 0;
|
||||
memcpy(&msg, (uint8_t*)eng.G2H(thisPtr) + 4, 4);
|
||||
if (msg) eng.heap().Free(msg);
|
||||
}
|
||||
return thisPtr;
|
||||
}
|
||||
// bad_alloc/exception have no extra members in this simplified model -
|
||||
// nothing for their constructors/destructors to actually do.
|
||||
uint32_t Shim_exception_noop_ctor_dtor(GuestEngine&, uint32_t thisPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
return thisPtr;
|
||||
}
|
||||
uint32_t Shim_uncaught_exception(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
// No real exception is ever genuinely "in flight" from this engine's
|
||||
// perspective (see __cxa_bad_typeid's own comment on why real
|
||||
// throw/unwind isn't attempted) - always false is the only honest
|
||||
// answer available.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- __shared_weak_count (std::shared_ptr's internal control block) ----
|
||||
// Layout {vtable_ptr(4); shared_owners(4); weak_owners(4)} matches real
|
||||
// libc++'s __shared_weak_count ABI (a count of 0 means "1 owner", libc++'s
|
||||
// own convention - decrementing past 0 means the last owner just left).
|
||||
// __add_shared/__release_shared/__release_weak do real atomic-free
|
||||
// refcounting (single-threaded per call via CallGuestFunction's own
|
||||
// re-entrant-but-serialized guest execution, so a plain read-modify-write
|
||||
// is safe here same as everywhere else in this shim layer). The protected
|
||||
// virtual __on_zero_shared()/__on_zero_shared_weak() callbacks that would
|
||||
// actually destroy/free the managed object are deliberately NOT invoked -
|
||||
// doing that correctly needs calling back through the guest object's OWN
|
||||
// real compiled vtable at a specific slot index this file has no confirmed
|
||||
// source for (libc++abi wasn't statically linked here either, same reason
|
||||
// these symbols are undefined imports in the first place) - a wrong guess
|
||||
// risks a worse crash than the alternative: shared_ptr copy/move/reset
|
||||
// semantics all work correctly via real refcounting, the underlying
|
||||
// managed object just leaks when the last reference goes away. Documented,
|
||||
// not silent.
|
||||
uint32_t Shim_shared_weak_count_add_shared(GuestEngine& eng, uint32_t thisPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
if (!thisPtr) return 0;
|
||||
int32_t v = 0;
|
||||
memcpy(&v, (uint8_t*)eng.G2H(thisPtr) + 4, 4);
|
||||
v++;
|
||||
memcpy((uint8_t*)eng.G2H(thisPtr) + 4, &v, 4);
|
||||
return 0;
|
||||
}
|
||||
uint32_t Shim_shared_weak_count_release_shared(GuestEngine& eng, uint32_t thisPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
if (!thisPtr) return 0;
|
||||
int32_t v = 0;
|
||||
memcpy(&v, (uint8_t*)eng.G2H(thisPtr) + 4, 4);
|
||||
if (v == 0) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
logged = true;
|
||||
Log("rtti_shims: __shared_weak_count::__release_shared reached zero owners - NOT invoking "
|
||||
"the real __on_zero_shared() cleanup (documented gap, see rtti_shims.cpp) - the managed "
|
||||
"object leaks rather than risking a wrong vtable-slot guess");
|
||||
}
|
||||
v = -1;
|
||||
} else {
|
||||
v--;
|
||||
}
|
||||
memcpy((uint8_t*)eng.G2H(thisPtr) + 4, &v, 4);
|
||||
return 0;
|
||||
}
|
||||
uint32_t Shim_shared_weak_count_release_weak(GuestEngine& eng, uint32_t thisPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
if (!thisPtr) return 0;
|
||||
int32_t v = 0;
|
||||
memcpy(&v, (uint8_t*)eng.G2H(thisPtr) + 8, 4);
|
||||
v = (v == 0) ? -1 : v - 1;
|
||||
memcpy((uint8_t*)eng.G2H(thisPtr) + 8, &v, 4);
|
||||
return 0;
|
||||
}
|
||||
uint32_t Shim_shared_weak_count_get_deleter(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
return 0; // base implementation's real default - "no deleter" - genuinely correct, not a gap
|
||||
}
|
||||
|
||||
// ---- libc++'s own internal std::mutex ----
|
||||
// libc++'s std::mutex wraps a pthread_mutex_t internally and its
|
||||
// lock()/unlock() are thin forwarders - the object's OWN guest address
|
||||
// plays exactly the role a pthread_mutex_t* already does, so this reuses
|
||||
// the same "real host mutex keyed by guest address" pattern
|
||||
// pthread_shim.cpp already established for guest pthread_mutex_t, just in
|
||||
// its own table (different guest addresses, same technique - not sharing
|
||||
// pthread_shim.cpp's internal table to avoid a cross-file dependency for
|
||||
// what's cheap to duplicate).
|
||||
std::mutex g_cxxMutexTableMutex;
|
||||
std::vector<std::pair<uint32_t, std::recursive_mutex*>> g_cxxMutexes;
|
||||
std::recursive_mutex* GetOrCreateCxxMutex(uint32_t addr) {
|
||||
std::lock_guard<std::mutex> lock(g_cxxMutexTableMutex);
|
||||
for (auto& p : g_cxxMutexes) if (p.first == addr) return p.second;
|
||||
auto* m = new std::recursive_mutex();
|
||||
g_cxxMutexes.push_back({addr, m});
|
||||
return m;
|
||||
}
|
||||
uint32_t Shim_cxx_mutex_lock(GuestEngine&, uint32_t thisPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
if (thisPtr) GetOrCreateCxxMutex(thisPtr)->lock();
|
||||
return 0;
|
||||
}
|
||||
uint32_t Shim_cxx_mutex_unlock(GuestEngine&, uint32_t thisPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
if (thisPtr) GetOrCreateCxxMutex(thisPtr)->unlock();
|
||||
return 0;
|
||||
}
|
||||
uint32_t Shim_noop_returns_this(GuestEngine&, uint32_t thisPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
return thisPtr;
|
||||
}
|
||||
|
||||
// ---- ios_base / locale ----
|
||||
// A single shared "classic locale" data address every locale::locale()
|
||||
// call returns - real libc++ locale objects are refcounted handles to a
|
||||
// shared __imp_ (facet table); since use_facet() below always fails
|
||||
// (logged, not silently wrong - see its own comment), nothing ever reads
|
||||
// through this beyond identity/non-null checks, so one shared stable
|
||||
// address is sufficient.
|
||||
GuestAddr g_classicLocale = 0;
|
||||
|
||||
// Fix (2026-09-17, ARM64_TRANSLATION_LAYER.md - the root cause of the
|
||||
// empty-shader-source investigation running since 2026-09-07, finally
|
||||
// closed). `ios_base::init(streambuf*)` is NOT only called for the global
|
||||
// cin/cout/cerr/clog singletons (the case this shim's original comment
|
||||
// assumed) - the C++ standard requires EVERY ios_base-derived object's
|
||||
// construction to go through it, including a completely ordinary local
|
||||
// `std::ostringstream` (confirmed live: `sub_4702D8`'s own inlined
|
||||
// std::ostringstream local calls this exact import at 0x47051c). Real
|
||||
// `ios_base::init()` zeroes `__rdstate_` (the "current error state" field,
|
||||
// confirmed via sub_79CD4's real disasm - `basic_ostream::operator<<`
|
||||
// checks `[this+0x10]==0` i.e. `good()` before writing ANYTHING, and
|
||||
// `ios_base::clear(uint)` ORs new bits into that same offset) among other
|
||||
// per-object defaults. This shim's previous `return 0` left that field
|
||||
// COMPLETELY UNINITIALIZED - i.e. whatever stack garbage happened to
|
||||
// already be at that address (confirmed live: 0x4404c0, leftover data from
|
||||
// an unrelated earlier stack frame) - which is essentially always nonzero,
|
||||
// meaning `good()` was false from the object's very first use, meaning
|
||||
// EVERY subsequent `operator<<` on ANY freshly-constructed ostringstream
|
||||
// silently no-ops (the real code takes the "stream already failed, skip
|
||||
// the write" sentry fast path on every single call, never reaching the
|
||||
// actual buffer-write logic) - with no crash, no visible error, just an
|
||||
// object that always extracts empty via `str()`. This is not a "no
|
||||
// formatted-stream output needed" case at all; it was silently breaking
|
||||
// EVERY ostringstream/stringstream construction in the entire engine.
|
||||
// Signature: `void ios_base::init(streambuf* sb)`, called as
|
||||
// `this->init(sb)` - r0=this, r1=sb.
|
||||
//
|
||||
// Follow-up correction (2026-09-17, "Гонимся за 0x4b3f09c" - chasing the
|
||||
// fault this SAME fix's first version immediately unblocked). Originally
|
||||
// assumed `sb` (r1) was unnecessary here - "the streambuf association
|
||||
// itself is a separate concern, owned by basic_ios::init() one level up in
|
||||
// the real class hierarchy." Confirmed live this was WRONG: this NDK's
|
||||
// libc++ flattens `basic_ios<charT>::init(streambuf*)`'s own rdbuf_
|
||||
// assignment directly into `ios_base::init()` - real disasm of
|
||||
// `sub_4702D8` shows it passing the SAME value as both `ios_base::init`'s
|
||||
// own `sb` argument (r1 @0x47051c) AND, later, `sub_27160C`'s own second
|
||||
// argument (@0x4706c8) - i.e. the genuine embedded streambuf sub-object
|
||||
// address, not something ios_base::init is free to ignore. Dropping `sb`
|
||||
// left `[this+0x18]` (the exact offset `sub_79CD4`'s real disasm reads as
|
||||
// the streambuf pointer before making its own virtual sputn() call)
|
||||
// uninitialized - same class of bug as `__rdstate_` above, just a
|
||||
// different field, and confirmed live to be just as damaging: leftover
|
||||
// stack garbage there happened to be a real `im::isis::Shader*` from an
|
||||
// unrelated earlier frame, so the "streambuf" vtable dispatch actually
|
||||
// called through SHADER's own vtable slot #12, landing on a data address
|
||||
// (not real code) and faulting with UC_ERR_EXCEPTION the moment any
|
||||
// ostringstream write actually reached the real write path (which the
|
||||
// __rdstate_ fix above was what first let happen at all).
|
||||
uint32_t Shim_ios_base_init(GuestEngine& eng, uint32_t thisPtr, uint32_t sb, uint32_t, uint32_t, uint32_t) {
|
||||
// Diagnostic (2026-09-17, "Гонимся за 0x4b3f09c" follow-up - the NEW,
|
||||
// EARLIER UC_ERR_INSN_INVALID @0x1e0 this same fix's `sb` write
|
||||
// unblocked). ios_base::init() is called once per stream CONSTRUCTION,
|
||||
// not once per character - low enough volume to log every call
|
||||
// unconditionally. Logs the guest LR (real caller) for every
|
||||
// invocation, to identify which ios_base::init() call site is
|
||||
// responsible for this new fault, rather than guessing from the
|
||||
// sub_4702D8-scoped probes (which never fired this run, meaning the
|
||||
// crash now happens at a DIFFERENT, unscoped call site).
|
||||
uint32_t callerLr = 0;
|
||||
if (uc_engine* uc = eng.uc()) uc_reg_read(uc, UC_ARM_REG_LR, &callerLr);
|
||||
Log("GuestEngine: Shim_ios_base_init: this=0x%x sb=0x%x from guest LR=0x%x",
|
||||
thisPtr, sb, callerLr);
|
||||
if (thisPtr) {
|
||||
uint32_t goodState = 0;
|
||||
memcpy(eng.G2H(thisPtr + 0x10), &goodState, 4);
|
||||
memcpy(eng.G2H(thisPtr + 0x18), &sb, 4);
|
||||
// Fix (2026-09-17, ARM64_TRANSLATION_LAYER.md - the 0x3d3d3d3d heap-
|
||||
// overflow chase, root cause finally found). This shim only ever
|
||||
// zeroed __rdstate_ (+0x10) and set __rdbuf_ (+0x18) - real
|
||||
// ios_base::init() ALSO resets __width_ (+0xC, confirmed via
|
||||
// sub_79E50's real disasm: `LDR R1,[a5,#0xC]` reads THIS field as
|
||||
// the field-width for padding, called unconditionally from every
|
||||
// plain operator<<(const char*,size_t) write). Leaving it
|
||||
// unzeroed meant every freshly-constructed stream inherited
|
||||
// whatever GARBAGE happened to be on the stack/heap at that offset
|
||||
// as its "width" - on the rare occasion that garbage was, by
|
||||
// chance, larger than the string being written, sub_79E50's
|
||||
// padding-buffer logic (alloc a scratch buffer sized off that
|
||||
// garbage width, pass it through a vtable sputn() call, then free
|
||||
// it) ran when it was never supposed to at all, corrupting
|
||||
// whatever heap memory the scratch buffer or the subsequent wrong
|
||||
// free() touched - exactly the mechanism behind the
|
||||
// MEM FAULT READ_UNMAPPED @0x3d3d3d3d crash in sub_4711C8. Real
|
||||
// ios_base::init() also resets __precision_ to 6 and clears
|
||||
// __fmtflags_/__exceptions_, but those aren't yet confirmed at a
|
||||
// specific offset in THIS binary's layout and nothing observed so
|
||||
// far depends on them - zeroing only the confirmed, evidenced field
|
||||
// rather than guessing further offsets.
|
||||
uint32_t zeroWidth = 0;
|
||||
memcpy(eng.G2H(thisPtr + 0xC), &zeroWidth, 4);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// Real `void ios_base::clear(iostate state = goodbit)` - a plain assignment
|
||||
// into the same `__rdstate_` field Shim_ios_base_init now zeroes (callers
|
||||
// pre-OR any new bits into their own local copy before calling this, per
|
||||
// sub_79CD4's own real disasm - `ORR R1,R1,#5; BL ios_base::clear` - so this
|
||||
// is a straight overwrite, not an OR, matching the real function). Same
|
||||
// r0=this convention as every other shim here.
|
||||
uint32_t Shim_ios_base_clear(GuestEngine& eng, uint32_t thisPtr, uint32_t state, uint32_t, uint32_t, uint32_t) {
|
||||
if (thisPtr) memcpy(eng.G2H(thisPtr + 0x10), &state, 4);
|
||||
return 0;
|
||||
}
|
||||
uint32_t Shim_ios_base_getloc(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
return g_classicLocale;
|
||||
}
|
||||
uint32_t Shim_locale_ctor(GuestEngine& eng, uint32_t thisPtr, uint32_t, uint32_t, uint32_t, uint32_t) {
|
||||
// locale::locale() (the default/"classic" constructor overload - the
|
||||
// only one this engine has seen a call site for) - writes the shared
|
||||
// classic-locale handle into the constructed object. Real libc++
|
||||
// locale is itself just a single refcounted pointer member, matching
|
||||
// this 4-byte write.
|
||||
if (thisPtr) memcpy(eng.G2H(thisPtr), &g_classicLocale, 4);
|
||||
return thisPtr;
|
||||
}
|
||||
// ---- ctype<char> facet - a REAL, callable vtable, not an inert marker ----
|
||||
// Confirmed via IDA cross-referencing (not guesswork) that guest code
|
||||
// actually calls through a facet returned by use_facet<ctype<char>>():
|
||||
// std::ctype<char>::id has 50+ xrefs across the whole binary, and every
|
||||
// sampled call site (ostream::operator<< for C-strings, single chars, and
|
||||
// numeric formatting - sub_79CD4/sub_751DC/sub_752EC/sub_7C1F8 in the real
|
||||
// binary) calls the SAME vtable byte-offset (28) with a single scalar char
|
||||
// argument (e.g. '\n' or ' ') - unambiguously ctype<char>::do_widen(char),
|
||||
// which the C++ standard guarantees is an identity function for char (never
|
||||
// locale-dependent, never guessable-wrong). Every OTHER slot in this vtable
|
||||
// is unconfirmed - rather than guess the rest of libc++'s real ABI layout
|
||||
// from memory (a wrong guess here misdirects a real virtual call to the
|
||||
// WRONG method, a worse failure mode than the NULL-facet crash this
|
||||
// replaces), every other slot routes to a shared stub that logs exactly
|
||||
// which byte offset was called and returns 0 - the next such gap becomes a
|
||||
// direct "implement offset N" lead instead of another multi-hop
|
||||
// "\x7fELF"-as-a-pointer chase.
|
||||
struct FacetSlotCtx {
|
||||
int byteOffset;
|
||||
ImportShimFn impl; // nullptr = unimplemented, log and return 0
|
||||
};
|
||||
void FacetSlotDispatch(uc_engine* uc, uint64_t /*address*/, uint32_t /*size*/, void* userData) {
|
||||
auto* ctx = static_cast<FacetSlotCtx*>(userData);
|
||||
uint32_t r0 = 0, r1 = 0, r2 = 0, r3 = 0, sp = 0, lr = 0;
|
||||
uc_reg_read(uc, UC_ARM_REG_R0, &r0);
|
||||
uc_reg_read(uc, UC_ARM_REG_R1, &r1);
|
||||
uc_reg_read(uc, UC_ARM_REG_R2, &r2);
|
||||
uc_reg_read(uc, UC_ARM_REG_R3, &r3);
|
||||
uc_reg_read(uc, UC_ARM_REG_SP, &sp);
|
||||
uc_reg_read(uc, UC_ARM_REG_LR, &lr);
|
||||
|
||||
uint32_t result = 0;
|
||||
if (ctx->impl) {
|
||||
result = ctx->impl(GuestEngine::Instance(), r0, r1, r2, r3, sp);
|
||||
} else {
|
||||
Log("rtti_shims: ctype<char> facet - unimplemented virtual method at vtable offset %d "
|
||||
"called (this=0x%x arg1=0x%x) - returning 0 (see rtti_shims.cpp's FacetSlotCtx comment)",
|
||||
ctx->byteOffset, r0, r1);
|
||||
}
|
||||
uc_reg_write(uc, UC_ARM_REG_R0, &result);
|
||||
uc_reg_write(uc, UC_ARM_REG_PC, &lr);
|
||||
uc_emu_stop(uc); // see guest_engine.cpp's import_stub_dispatch_cb - same reasoning
|
||||
}
|
||||
// do_widen(char_type c) const -> char_type - identity for ctype<char>
|
||||
// (char_type == char), per the C++ standard. r0 = this (unused), r1 = c.
|
||||
uint32_t Shim_ctype_char_do_widen(GuestEngine&, uint32_t /*this*/, uint32_t c, uint32_t, uint32_t, uint32_t) {
|
||||
return c;
|
||||
}
|
||||
|
||||
GuestAddr g_ctypeCharId = 0, g_ctypeCharFacet = 0;
|
||||
GuestAddr g_numPutCharId = 0, g_numPutCharFacet = 0;
|
||||
|
||||
// ---- num_put<char> facet - same "real vtable, only confirmed slots
|
||||
// implemented" approach as ctype<char> above. Confirmed via IDA
|
||||
// (entity_query/xrefs_to on the two facet-id import slots): num_get<char>
|
||||
// has only 2 xrefs total (1 real call site + its own GOT self-reference) -
|
||||
// no evidence it's ever actually reached - while num_put<char> has 9,
|
||||
// including the one that produced this session's crash. Sampled 5 of the
|
||||
// 9 real call sites (sub_8E8F4/sub_232550/sub_2323B8/sub_AA298/sub_A3128) -
|
||||
// all are ostream::operator<< for some numeric type, each landing on a
|
||||
// DIFFERENT vtable byte-offset with a distinct argument shape: 16/24/40 =
|
||||
// single 32-bit value (three of num_put's eight standard overloads - bool/
|
||||
// long/unsigned long - indistinguishable from the call site alone, which
|
||||
// exact one maps to which offset isn't confirmed); 20 = a 64-bit value
|
||||
// (long long or unsigned long long); 32 = confirmed float/double (from
|
||||
// ostream::operator<<(float)). Every other slot: the same generic logged
|
||||
// stub as ctype<char>, not a guess.
|
||||
//
|
||||
// The do_put "iter_type" argument (r1 at every sampled call site) is a raw
|
||||
// streambuf pointer, not a separate wrapper - confirmed by cross-checking
|
||||
// its value against sub_7A0CC (see WriteCharToStreambuf's own comment)'s
|
||||
// own field-offset math, which computes the identical address from the
|
||||
// owning ostream. ostreambuf_iterator<char> in this ABI is just that
|
||||
// pointer.
|
||||
//
|
||||
// WriteCharToStreambuf replicates sub_7A0CC's own logic - REAL,
|
||||
// already-compiled guest code (not an external symbol, so not something
|
||||
// this engine ever had to shim) found by decompiling one of the
|
||||
// ctype<char>-adjacent call sites this session already investigated: a
|
||||
// streambuf's next-write position ("pptr") lives at byte offset 24, the
|
||||
// buffer's end ("epptr") at offset 28. Room available -> write directly
|
||||
// and advance pptr (the fast path every real character write takes).
|
||||
// Buffer full -> call the REAL guest virtual overflow() (the streambuf's
|
||||
// own vtable, offset 52) via the existing CallGuestFunction, reusing real
|
||||
// guest code for flush/grow semantics instead of reimplementing them -
|
||||
// the same "call real guest code where possible, only shim what's truly
|
||||
// external" principle used throughout this engine.
|
||||
// Diagnostic (2026-09-16, ARM64_TRANSLATION_LAYER.md - "reentrancy probe on
|
||||
// the real path" follow-up). The CallGuestFunction(overflowFn, ...) call
|
||||
// below is a REENTRANT guest-into-guest call - it was directly observed
|
||||
// firing on the GL thread during real gameplay, ~16 seconds before the
|
||||
// long-chased sub_56962C MEM FAULT on the SAME thread. CallGuestFunction
|
||||
// itself already saves/restores the full register file (r0-r12/sp/lr/pc/
|
||||
// cpsr + all D-registers) around its OWN call, so in principle the CALLER's
|
||||
// registers should be unaffected either way - this snapshot exists to
|
||||
// empirically verify that promise actually holds for THIS specific call
|
||||
// site, rather than assuming it does. Snapshots the full core register file
|
||||
// immediately before and after, logs any register whose value differs.
|
||||
// Diagnostic (2026-09-16 continuation): both register-diff candidates
|
||||
// (this WriteCharToStreambuf call and Shim_pthread_once's) came back clean
|
||||
// at the register level, so this snapshots GUEST MEMORY instead - the
|
||||
// small cluster of globals sub_56962C's crash-site arithmetic reads
|
||||
// directly by fixed offset (dword_AE00D8 at +0, dword_AE00DC at +4,
|
||||
// dword_AE00E0 at +8, dword_AE00FC at +0x24 - all read as PLAIN DATA at
|
||||
// these fixed guest addresses in sub_56962C's own decompilation, NOT via
|
||||
// pointer indirection through dword_AE00D8's own value). If a reentrant
|
||||
// call's callee body writes somewhere it shouldn't, this window is a
|
||||
// direct, targeted place to catch it rather than registers, which are
|
||||
// already known clean.
|
||||
constexpr GuestAddr kAE00D8WindowBase = 0xae00d8;
|
||||
constexpr int kAE00D8WindowLen = 0x30; // covers AE00D8..AE0108, includes dword_AE00FC
|
||||
std::string SnapshotAE00D8Window(GuestEngine& eng) {
|
||||
return std::string(reinterpret_cast<char*>(eng.G2H(kAE00D8WindowBase)), kAE00D8WindowLen);
|
||||
}
|
||||
void LogAE00D8WindowDiff(const char* tag, const std::string& before, const std::string& after) {
|
||||
std::string diffs;
|
||||
for (int i = 0; i + 4 <= kAE00D8WindowLen; i += 4) {
|
||||
uint32_t b = 0, a = 0;
|
||||
memcpy(&b, before.data() + i, 4);
|
||||
memcpy(&a, after.data() + i, 4);
|
||||
if (b != a) {
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), " [AE00D8+0x%x]:0x%x->0x%x", i, b, a);
|
||||
diffs += buf;
|
||||
}
|
||||
}
|
||||
Log("%s: dword_AE00D8 window [0x%x,0x%x) diffs:%s", tag, kAE00D8WindowBase,
|
||||
kAE00D8WindowBase + kAE00D8WindowLen, diffs.empty() ? " (none)" : diffs.c_str());
|
||||
}
|
||||
void LogRegSnapshotDiff(GuestEngine& eng, const char* tag, GuestAddr overflowFn, GuestAddr sb, uint8_t c) {
|
||||
static const int kRegIds[] = {
|
||||
UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3,
|
||||
UC_ARM_REG_R4, UC_ARM_REG_R5, UC_ARM_REG_R6, UC_ARM_REG_R7,
|
||||
UC_ARM_REG_R8, UC_ARM_REG_R9, UC_ARM_REG_R10, UC_ARM_REG_R11,
|
||||
UC_ARM_REG_R12, UC_ARM_REG_SP, UC_ARM_REG_LR, UC_ARM_REG_PC,
|
||||
UC_ARM_REG_CPSR,
|
||||
};
|
||||
static const char* kRegNames[] = {
|
||||
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
|
||||
"r8", "r9", "r10", "r11", "r12", "sp", "lr", "pc", "cpsr",
|
||||
};
|
||||
uc_engine* uc = eng.uc();
|
||||
if (!uc) return;
|
||||
uint32_t before[17] = {0};
|
||||
for (int i = 0; i < 17; i++) uc_reg_read(uc, kRegIds[i], &before[i]);
|
||||
std::string memBefore = SnapshotAE00D8Window(eng);
|
||||
|
||||
eng.CallGuestFunction(overflowFn, sb, (uint32_t)c);
|
||||
|
||||
uint32_t after[17] = {0};
|
||||
for (int i = 0; i < 17; i++) uc_reg_read(uc, kRegIds[i], &after[i]);
|
||||
std::string memAfter = SnapshotAE00D8Window(eng);
|
||||
|
||||
std::string diffs;
|
||||
for (int i = 0; i < 17; i++) {
|
||||
if (before[i] != after[i]) {
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), " %s:0x%x->0x%x", kRegNames[i], before[i], after[i]);
|
||||
diffs += buf;
|
||||
}
|
||||
}
|
||||
Log("%s: overflowFn=0x%x sb=0x%x c=0x%x - reg diffs after nested CallGuestFunction:%s",
|
||||
tag, overflowFn, sb, (unsigned)c, diffs.empty() ? " (none)" : diffs.c_str());
|
||||
LogAE00D8WindowDiff(tag, memBefore, memAfter);
|
||||
}
|
||||
void WriteCharToStreambuf(GuestEngine& eng, GuestAddr sb, uint8_t c) {
|
||||
if (!sb) return;
|
||||
uint32_t pptr = 0, epptr = 0;
|
||||
memcpy(&pptr, eng.G2H(sb + 24), 4);
|
||||
memcpy(&epptr, eng.G2H(sb + 28), 4);
|
||||
if (pptr && pptr != epptr) {
|
||||
memcpy(eng.G2H(pptr), &c, 1);
|
||||
uint32_t newPptr = pptr + 1;
|
||||
memcpy(eng.G2H(sb + 24), &newPptr, 4);
|
||||
return;
|
||||
}
|
||||
uint32_t vtable = 0;
|
||||
memcpy(&vtable, eng.G2H(sb), 4);
|
||||
if (!vtable) return;
|
||||
uint32_t overflowFn = 0;
|
||||
memcpy(&overflowFn, eng.G2H(vtable + 52), 4);
|
||||
if (overflowFn) LogRegSnapshotDiff(eng, "WriteCharToStreambuf", overflowFn, sb, c);
|
||||
}
|
||||
void WriteStringToStreambuf(GuestEngine& eng, GuestAddr sb, const char* s, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) WriteCharToStreambuf(eng, sb, (uint8_t)s[i]);
|
||||
}
|
||||
// do_put overloads - format via the real host snprintf (same "offload
|
||||
// formatting to real libc, don't reimplement it" pattern as the
|
||||
// printf-family fix) and write the result through the target streambuf a
|
||||
// character at a time. Deliberately does NOT read/honor ios_base's
|
||||
// formatting flags (width/fill/base/precision/uppercase - not at a known
|
||||
// field offset) - plain default-format output, an explicit, documented
|
||||
// scope cut, same pragmatism already accepted for %p formatting and the
|
||||
// FMOD stubs. r0=this(facet, unused), r1=streambuf (the iter_type/return
|
||||
// value), r2=ios_base(unused, same reason), r3=fill char(unused, same
|
||||
// reason), the value itself is on the stack (slot 4[,5] - see
|
||||
// ReadIncomingArg).
|
||||
uint32_t Shim_num_put_int32(GuestEngine& eng, uint32_t, uint32_t streambuf, uint32_t, uint32_t, uint32_t sp) {
|
||||
int32_t v = (int32_t)eng.ReadIncomingArg(4, 0, 0, 0, 0, sp);
|
||||
char buf[32];
|
||||
int n = snprintf(buf, sizeof(buf), "%d", v);
|
||||
if (n > 0) WriteStringToStreambuf(eng, streambuf, buf, (size_t)n);
|
||||
return streambuf;
|
||||
}
|
||||
uint32_t Shim_num_put_int64(GuestEngine& eng, uint32_t, uint32_t streambuf, uint32_t, uint32_t, uint32_t sp) {
|
||||
uint32_t lo = eng.ReadIncomingArg(4, 0, 0, 0, 0, sp);
|
||||
uint32_t hi = eng.ReadIncomingArg(5, 0, 0, 0, 0, sp);
|
||||
int64_t v = (int64_t)(((uint64_t)hi << 32) | lo);
|
||||
char buf[32];
|
||||
int n = snprintf(buf, sizeof(buf), "%lld", (long long)v);
|
||||
if (n > 0) WriteStringToStreambuf(eng, streambuf, buf, (size_t)n);
|
||||
return streambuf;
|
||||
}
|
||||
uint32_t Shim_num_put_double(GuestEngine& eng, uint32_t, uint32_t streambuf, uint32_t, uint32_t, uint32_t sp) {
|
||||
uint32_t lo = eng.ReadIncomingArg(4, 0, 0, 0, 0, sp);
|
||||
uint32_t hi = eng.ReadIncomingArg(5, 0, 0, 0, 0, sp);
|
||||
uint64_t bits = ((uint64_t)hi << 32) | lo;
|
||||
double v;
|
||||
memcpy(&v, &bits, 8);
|
||||
char buf[64];
|
||||
int n = snprintf(buf, sizeof(buf), "%g", v);
|
||||
if (n > 0) WriteStringToStreambuf(eng, streambuf, buf, (size_t)n);
|
||||
return streambuf;
|
||||
}
|
||||
|
||||
// use_facet<Facet>(locale) looks up a facet by its static `id` member and
|
||||
// THROWS std::bad_cast if not found - facet types with no evidence of ever
|
||||
// being requested by this binary (num_get, ...) keep the original
|
||||
// behavior: log once, return NULL (about as safe as an unimplemented
|
||||
// facet lookup can be without a real guest stack unwinder to throw
|
||||
// std::bad_cast through - see __cxa_bad_typeid's own comment). ctype<char>
|
||||
// and num_put<char> are different - confirmed real, actually-called use
|
||||
// (see their own comments above) - return the real facet objects built
|
||||
// above.
|
||||
uint32_t Shim_use_facet(GuestEngine&, uint32_t, uint32_t idAddr, uint32_t, uint32_t, uint32_t) {
|
||||
if (idAddr == g_ctypeCharId) return g_ctypeCharFacet;
|
||||
if (idAddr == g_numPutCharId) return g_numPutCharFacet;
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
logged = true;
|
||||
Log("rtti_shims: std::locale::use_facet() for an id other than ctype<char>/num_put<char> "
|
||||
"(0x%x) - this engine's locale objects have no real facet table for it (see "
|
||||
"rtti_shims.cpp) - returning NULL rather than a facet object a subsequent virtual call "
|
||||
"would crash through; real libc++ would throw std::bad_cast here, which needs a guest "
|
||||
"stack unwinder this engine doesn't have (see __cxa_bad_typeid's own comment)", idAddr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SetupRttiDataSymbols(GuestEngine& engine) {
|
||||
// ---- Vtable identity markers for the 5 __cxxabiv1 abstract RTTI base
|
||||
// classes - content is irrelevant (this file's own SearchBase/
|
||||
// Shim_dynamic_cast recognize them by ADDRESS, never call through
|
||||
// them), only needs to be a small, stable, unique allocation each.
|
||||
g_classTI = engine.AllocPermanent(4);
|
||||
g_siClassTI = engine.AllocPermanent(4);
|
||||
g_vmiClassTI = engine.AllocPermanent(4);
|
||||
g_pointerTI = engine.AllocPermanent(4);
|
||||
g_functionTI = engine.AllocPermanent(4);
|
||||
engine.RegisterDataSymbol("_ZTVN10__cxxabiv117__class_type_infoE", g_classTI);
|
||||
engine.RegisterDataSymbol("_ZTVN10__cxxabiv120__si_class_type_infoE", g_siClassTI);
|
||||
engine.RegisterDataSymbol("_ZTVN10__cxxabiv121__vmi_class_type_infoE", g_vmiClassTI);
|
||||
engine.RegisterDataSymbol("_ZTVN10__cxxabiv119__pointer_type_infoE", g_pointerTI);
|
||||
engine.RegisterDataSymbol("_ZTVN10__cxxabiv120__function_type_infoE", g_functionTI);
|
||||
|
||||
// ---- Primitive (__fundamental_type_info-shaped) type_info objects ----
|
||||
// {vtable_ptr; name_ptr} - vtable_ptr left 0 (never dereferenced -
|
||||
// these are always leaf nodes for SearchBase, never walked further),
|
||||
// name_ptr is the real Itanium-mangled single-letter type code.
|
||||
auto makePrimitive = [&](const char* symbolName, const char* mangledCode) {
|
||||
GuestAddr nameAddr = AllocPermanentString(engine, mangledCode);
|
||||
GuestAddr obj = engine.AllocPermanent(8);
|
||||
memcpy((uint8_t*)engine.G2H(obj) + 4, &nameAddr, 4);
|
||||
engine.RegisterDataSymbol(symbolName, obj);
|
||||
return obj;
|
||||
};
|
||||
makePrimitive("_ZTIa", "a"); // signed char
|
||||
makePrimitive("_ZTIf", "f"); // float
|
||||
makePrimitive("_ZTIi", "i"); // int
|
||||
makePrimitive("_ZTIs", "s"); // short
|
||||
makePrimitive("_ZTIt", "t"); // unsigned short
|
||||
|
||||
// ---- STL exception class typeinfo ----
|
||||
// __class_type_info-shaped (no bases): {vtable_ptr = classTI+8; name_ptr}
|
||||
auto makeClassTypeInfo = [&](const char* symbolName, const char* name) {
|
||||
GuestAddr nameAddr = AllocPermanentString(engine, name);
|
||||
GuestAddr obj = engine.AllocPermanent(8);
|
||||
uint32_t vt = g_classTI + kVtablePtrAdjust;
|
||||
memcpy(engine.G2H(obj), &vt, 4);
|
||||
memcpy((uint8_t*)engine.G2H(obj) + 4, &nameAddr, 4);
|
||||
engine.RegisterDataSymbol(symbolName, obj);
|
||||
return obj;
|
||||
};
|
||||
// __si_class_type_info-shaped (single base): {vtable_ptr = siClassTI+8; name_ptr; base_type}
|
||||
auto makeSiClassTypeInfo = [&](const char* symbolName, const char* name, GuestAddr baseTypeInfo) {
|
||||
GuestAddr nameAddr = AllocPermanentString(engine, name);
|
||||
GuestAddr obj = engine.AllocPermanent(12);
|
||||
uint32_t vt = g_siClassTI + kVtablePtrAdjust;
|
||||
memcpy(engine.G2H(obj), &vt, 4);
|
||||
memcpy((uint8_t*)engine.G2H(obj) + 4, &nameAddr, 4);
|
||||
memcpy((uint8_t*)engine.G2H(obj) + 8, &baseTypeInfo, 4);
|
||||
engine.RegisterDataSymbol(symbolName, obj);
|
||||
return obj;
|
||||
};
|
||||
|
||||
GuestAddr exceptionTI = makeClassTypeInfo("_ZTISt9exception", "St9exception");
|
||||
makeSiClassTypeInfo("_ZTISt13runtime_error", "St13runtime_error", exceptionTI);
|
||||
makeClassTypeInfo("_ZTINSt6__ndk119__shared_weak_countE", "N5std6__ndk119__shared_weak_countE");
|
||||
|
||||
// std::length_error's real VTABLE (not typeinfo) - length_error is
|
||||
// never independently constructed/thrown by anything this session
|
||||
// observed reaching (only runtime_error's typeinfo was actually
|
||||
// needed live), so this is just a stable, valid-looking address for
|
||||
// whatever static initializer or unused code path references it -
|
||||
// NOT a real vtable with working virtual dispatch. Documented gap if
|
||||
// that ever changes.
|
||||
engine.RegisterDataSymbol("_ZTVSt12length_error", engine.AllocPermanent(16));
|
||||
|
||||
// ---- ios_base's own typeinfo (class_type_info-shaped, no bases needed
|
||||
// for this engine's purposes - never walked into by dynamic_cast) ----
|
||||
makeClassTypeInfo("_ZTINSt6__ndk18ios_baseE", "N5std6__ndk18ios_baseE");
|
||||
|
||||
// ---- std::cerr - generously oversized (256B, real basic_ostream<char>
|
||||
// is smaller, but exact libc++ layout for this NDK isn't independently
|
||||
// verified) inert data blob. No real formatted-output support - see
|
||||
// this file's own top comment. ----
|
||||
engine.RegisterDataSymbol("_ZNSt6__ndk14cerrE", engine.AllocPermanent(256));
|
||||
|
||||
// ---- ctype<char> facet - a real, callable vtable (see FacetSlotCtx's
|
||||
// own comment for why offset 28 alone is implemented and everything
|
||||
// else is a logged stub, not a guess). Generously sized (24 slots) -
|
||||
// more than any real ctype<char> needs, so the exact total slot count
|
||||
// doesn't need to be pinned down; unused trailing slots just never get
|
||||
// called.
|
||||
{
|
||||
constexpr int kCtypeCharVtableSlots = 24;
|
||||
constexpr int kDoWidenSlot = 7; // byte offset 28 / 4 - the confirmed one
|
||||
GuestAddr vtable = engine.AllocPermanent(kCtypeCharVtableSlots * 4);
|
||||
for (int i = 0; i < kCtypeCharVtableSlots; i++) {
|
||||
ImportShimFn impl = (i == kDoWidenSlot) ? Shim_ctype_char_do_widen : nullptr;
|
||||
auto* ctx = new FacetSlotCtx{i * 4, impl};
|
||||
GuestAddr stub = engine.AllocCodeStub(FacetSlotDispatch, ctx);
|
||||
if (vtable && stub) memcpy(engine.G2H(vtable + (uint32_t)i * 4), &stub, 4);
|
||||
}
|
||||
g_ctypeCharFacet = engine.AllocPermanent(4);
|
||||
if (g_ctypeCharFacet && vtable) memcpy(engine.G2H(g_ctypeCharFacet), &vtable, 4);
|
||||
g_ctypeCharId = engine.AllocPermanent(4);
|
||||
engine.RegisterDataSymbol("_ZNSt6__ndk15ctypeIcE2idE", g_ctypeCharId);
|
||||
}
|
||||
// ---- num_put<char> facet - a real, callable vtable, same approach as
|
||||
// ctype<char> above (see Shim_num_put_int32's own comment for which 5
|
||||
// slots are confirmed-and-implemented vs. logged-stub). Generously
|
||||
// sized for the same reason as ctype<char>'s vtable.
|
||||
{
|
||||
constexpr int kNumPutCharVtableSlots = 24;
|
||||
GuestAddr vtable = engine.AllocPermanent(kNumPutCharVtableSlots * 4);
|
||||
for (int i = 0; i < kNumPutCharVtableSlots; i++) {
|
||||
int byteOffset = i * 4;
|
||||
ImportShimFn impl = nullptr;
|
||||
if (byteOffset == 16 || byteOffset == 24 || byteOffset == 40) impl = Shim_num_put_int32;
|
||||
else if (byteOffset == 20) impl = Shim_num_put_int64;
|
||||
else if (byteOffset == 32) impl = Shim_num_put_double;
|
||||
auto* ctx = new FacetSlotCtx{byteOffset, impl};
|
||||
GuestAddr stub = engine.AllocCodeStub(FacetSlotDispatch, ctx);
|
||||
if (vtable && stub) memcpy(engine.G2H(vtable + (uint32_t)byteOffset), &stub, 4);
|
||||
}
|
||||
g_numPutCharFacet = engine.AllocPermanent(4);
|
||||
if (g_numPutCharFacet && vtable) memcpy(engine.G2H(g_numPutCharFacet), &vtable, 4);
|
||||
g_numPutCharId = engine.AllocPermanent(4);
|
||||
engine.RegisterDataSymbol("_ZNSt6__ndk17num_putIcNS_19ostreambuf_iteratorIcNS_11char_traitsIcEEEEE2idE", g_numPutCharId);
|
||||
}
|
||||
// ---- Other facet `id` statics - inert (see Shim_use_facet's own
|
||||
// comment - no evidence anything requests these yet) ----
|
||||
engine.RegisterDataSymbol("_ZNSt6__ndk17num_getIcNS_19istreambuf_iteratorIcNS_11char_traitsIcEEEEE2idE", engine.AllocPermanent(4));
|
||||
|
||||
// ---- The shared "classic locale" every locale::locale() call returns
|
||||
// (see Shim_locale_ctor's own comment) ----
|
||||
g_classicLocale = engine.AllocPermanent(4);
|
||||
}
|
||||
|
||||
void RegisterRttiImportShims(GuestEngine& engine) {
|
||||
engine.RegisterImportShim("__dynamic_cast", Shim_dynamic_cast);
|
||||
engine.RegisterImportShim("__cxa_bad_typeid", Shim_cxa_bad_typeid);
|
||||
|
||||
engine.RegisterImportShim("_ZNSt13runtime_errorC2EPKc", Shim_runtime_error_ctor);
|
||||
engine.RegisterImportShim("_ZNKSt13runtime_error4whatEv", Shim_runtime_error_what);
|
||||
engine.RegisterImportShim("_ZNSt13runtime_errorD2Ev", Shim_runtime_error_dtor);
|
||||
engine.RegisterImportShim("_ZNSt11logic_errorC2EPKc", Shim_runtime_error_ctor);
|
||||
engine.RegisterImportShim("_ZNKSt11logic_error4whatEv", Shim_runtime_error_what);
|
||||
|
||||
engine.RegisterImportShim("_ZNSt9bad_allocC1Ev", Shim_exception_noop_ctor_dtor);
|
||||
engine.RegisterImportShim("_ZNSt9bad_allocD1Ev", Shim_exception_noop_ctor_dtor);
|
||||
engine.RegisterImportShim("_ZNSt9exceptionD2Ev", Shim_exception_noop_ctor_dtor);
|
||||
engine.RegisterImportShim("_ZSt18uncaught_exceptionv", Shim_uncaught_exception);
|
||||
|
||||
engine.RegisterImportShim("_ZNSt6__ndk119__shared_weak_count12__add_sharedEv", Shim_shared_weak_count_add_shared);
|
||||
engine.RegisterImportShim("_ZNSt6__ndk119__shared_weak_count16__release_sharedEv", Shim_shared_weak_count_release_shared);
|
||||
engine.RegisterImportShim("_ZNSt6__ndk119__shared_weak_count14__release_weakEv", Shim_shared_weak_count_release_weak);
|
||||
engine.RegisterImportShim("_ZNSt6__ndk119__shared_weak_countD2Ev", Shim_exception_noop_ctor_dtor);
|
||||
engine.RegisterImportShim("_ZNKSt6__ndk119__shared_weak_count13__get_deleterERKSt9type_info", Shim_shared_weak_count_get_deleter);
|
||||
|
||||
engine.RegisterImportShim("_ZNSt6__ndk15mutex4lockEv", Shim_cxx_mutex_lock);
|
||||
engine.RegisterImportShim("_ZNSt6__ndk15mutex6unlockEv", Shim_cxx_mutex_unlock);
|
||||
engine.RegisterImportShim("_ZNSt6__ndk15mutexD1Ev", Shim_noop_returns_this);
|
||||
|
||||
engine.RegisterImportShim("_ZNSt6__ndk18ios_base4initEPv", Shim_ios_base_init);
|
||||
engine.RegisterImportShim("_ZNSt6__ndk18ios_base5clearEj", Shim_ios_base_clear);
|
||||
engine.RegisterImportShim("_ZNSt6__ndk18ios_baseD2Ev", Shim_noop_returns_this);
|
||||
engine.RegisterImportShim("_ZNKSt6__ndk18ios_base6getlocEv", Shim_ios_base_getloc);
|
||||
|
||||
engine.RegisterImportShim("_ZNSt6__ndk16localeC1Ev", Shim_locale_ctor);
|
||||
engine.RegisterImportShim("_ZNSt6__ndk16localeD1Ev", Shim_noop_returns_this);
|
||||
engine.RegisterImportShim("_ZNKSt6__ndk16locale9use_facetERNS0_2idE", Shim_use_facet);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "guest_engine.h"
|
||||
|
||||
// Real RTTI (typeid/dynamic_cast) and minimal C++ exception-class support
|
||||
// for the imports libc++abi/libc++ would normally provide (not statically
|
||||
// linked into libapp.so - these came up UNDEFINED same as every other gap
|
||||
// this session has been closing, see ARM64_TRANSLATION_LAYER.md's "go
|
||||
// through all the imports" entry).
|
||||
//
|
||||
// Two distinct pieces, because these symbols are a mix of DATA and CODE:
|
||||
//
|
||||
// SetupRttiDataSymbols(engine) - a GuestEngine::DataSymbolSetupFn (register
|
||||
// via engine.RegisterDataSymbolSetup BEFORE engine.LoadImage) - builds real,
|
||||
// permanently-allocated (see GuestEngine::AllocPermanent) type_info objects
|
||||
// for the 5 primitive types actually referenced (_ZTIa/_ZTIf/_ZTIi/_ZTIs/
|
||||
// _ZTIt - char/float/int/short/unsigned short) plus small "vtable identity"
|
||||
// marker blobs for the 5 __cxxabiv1 abstract RTTI base classes
|
||||
// (__class_type_info/__si_class_type_info/__vmi_class_type_info/
|
||||
// __pointer_type_info/__function_type_info). These markers are NOT real
|
||||
// vtables with real function pointers - RegisterRttiImportShims's own
|
||||
// __dynamic_cast implementation recognizes them by ADDRESS (matching how
|
||||
// the Itanium ABI's __dynamic_cast already distinguishes a type_info's
|
||||
// concrete kind by comparing its vtable pointer against known base-class
|
||||
// vtable addresses, not by making a virtual call) and interprets the
|
||||
// following fields directly, so no real virtual dispatch through these
|
||||
// markers is ever needed. Getting these resolved as real DATA (not a
|
||||
// callable code stub - see GuestEngine::RegisterDataSymbol's own comment)
|
||||
// depended on this session's ELF-relocation-addend fix (guest_engine.cpp's
|
||||
// ProcessRelocations) - a type_info object's own stored vtable-pointer
|
||||
// field is laid out at compile time as "vtable_symbol + 2*sizeof(void*)"
|
||||
// (the Itanium ABI's offset-to-top/rtti-slot skip), which is exactly the
|
||||
// non-zero implicit addend that fix started honoring.
|
||||
//
|
||||
// RegisterRttiImportShims(engine) - the callable pieces: a real
|
||||
// __dynamic_cast (walks the type_info hierarchy directly, not through
|
||||
// virtual dispatch - handles the common single/no-inheritance and
|
||||
// multiple-non-virtual-base cases; true virtual-inheritance diamonds are a
|
||||
// documented gap, not silently wrong), __cxa_bad_typeid (can't really
|
||||
// throw - see its own comment), minimal std::exception/logic_error/
|
||||
// runtime_error/bad_alloc constructor/destructor/what() (a simplified but
|
||||
// internally-consistent object layout - not byte-identical to real
|
||||
// libc++'s __libcpp_refstring-backed one, which is fine since libc++
|
||||
// itself was never statically linked here to compare against - only code
|
||||
// THIS file also wrote ever reads these bytes), std::uncaught_exception
|
||||
// (always false - no real exception is ever "in flight", see the
|
||||
// dedicated comment on why real throw/catch unwinding isn't attempted),
|
||||
// and a partial (refcount-only, no deleter-callback) __shared_weak_count
|
||||
// (std::shared_ptr's internal control block).
|
||||
//
|
||||
// Also covers the small "misc libc++ runtime" surface that came up
|
||||
// alongside RTTI in the same undefined-symbol sweep: std::cerr (a
|
||||
// generously-oversized inert data blob - real formatted-output support
|
||||
// would need libc++'s actual ostream/streambuf machinery, not attempted),
|
||||
// ios_base/locale's init/clear/destructor/getloc (safe no-ops/trivial
|
||||
// objects), the ctype<char>/num_get/num_put facet `id` statics (inert data
|
||||
// - nothing ever successfully resolves a facet lookup through them, see
|
||||
// use_facet's own comment for why), and libc++'s own internal std::mutex
|
||||
// (real - backed by the same real-mutex-table pattern pthread_shim.cpp
|
||||
// already uses for guest pthread_mutex_t, keyed by the object's own guest
|
||||
// address).
|
||||
void SetupRttiDataSymbols(GuestEngine& engine);
|
||||
void RegisterRttiImportShims(GuestEngine& engine);
|
||||
@@ -0,0 +1,511 @@
|
||||
#include "tcg_bench.h"
|
||||
#include "guest_engine.h"
|
||||
|
||||
#include <unicorn/unicorn.h>
|
||||
#include <android/log.h>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
// ---- Minimal Thumb/Thumb-2 hand-assembler, just enough for the synthetic
|
||||
// engine tests below (2026-09-16, ARM64_TRANSLATION_LAYER.md). Not a real
|
||||
// assembler - just the handful of encodings these tests need, each with the
|
||||
// bit-layout spelled out so it can be checked against the ARM ARM directly
|
||||
// rather than trusted blindly.
|
||||
|
||||
void Emit16(std::vector<uint8_t>& out, uint16_t hw) {
|
||||
out.push_back((uint8_t)(hw & 0xFF));
|
||||
out.push_back((uint8_t)(hw >> 8));
|
||||
}
|
||||
|
||||
// PUSH {r4-r7, lr} - Thumb-16 "B5F0": 1011 010 L rrrrrrrr, L=1 (include LR),
|
||||
// register_list bits0-7 = r0..r7 presence (r4,r5,r6,r7 set = 0xF0).
|
||||
void EmitPushR4toR7Lr(std::vector<uint8_t>& out) {
|
||||
Emit16(out, 0xB5F0);
|
||||
}
|
||||
|
||||
// POP {r4-r7, pc} - Thumb-16 "BDF0": 1011 110 P rrrrrrrr, P=1 (include PC).
|
||||
void EmitPopR4toR7Pc(std::vector<uint8_t>& out) {
|
||||
Emit16(out, 0xBDF0);
|
||||
}
|
||||
|
||||
// MOVS Rd, #imm8 (Thumb-16, low registers r0-r7 only): 00100 ddd iiiiiiii.
|
||||
void EmitMovsImm8(std::vector<uint8_t>& out, uint8_t rd, uint8_t imm8) {
|
||||
uint16_t hw = (uint16_t)(0x2000 | ((rd & 0x7) << 8) | imm8);
|
||||
Emit16(out, hw);
|
||||
}
|
||||
|
||||
// STR Rt, [Rn, #imm5*4] (Thumb-16, word, low registers): 01100 iiiii nnn ttt.
|
||||
void EmitStrImm5(std::vector<uint8_t>& out, uint8_t rt, uint8_t rn, uint8_t imm5) {
|
||||
uint16_t hw = (uint16_t)(0x6000 | ((imm5 & 0x1F) << 6) | ((rn & 0x7) << 3) | (rt & 0x7));
|
||||
Emit16(out, hw);
|
||||
}
|
||||
|
||||
// BLX Rm (Thumb-16): 010001111 mmmm 000.
|
||||
void EmitBlxReg(std::vector<uint8_t>& out, uint8_t rm) {
|
||||
uint16_t hw = (uint16_t)(0x4780 | ((rm & 0xF) << 3));
|
||||
Emit16(out, hw);
|
||||
}
|
||||
|
||||
// MOVW/MOVT Rd, #imm16 (Thumb-2, 32-bit, T3 encoding). First halfword:
|
||||
// 11110 i 10 op00 imm4 (op=0 MOVW/1 MOVT via bit5 of the "10 op00" group -
|
||||
// concretely opcode field bits[9:4] = 100100 for MOVW, 101100 for MOVT).
|
||||
// Second halfword: 0 imm3 Rd(4) imm8.
|
||||
void EmitMovWT(std::vector<uint8_t>& out, bool isMovt, uint8_t rd, uint16_t imm16) {
|
||||
uint16_t imm4 = (imm16 >> 12) & 0xF;
|
||||
uint16_t i = (imm16 >> 11) & 0x1;
|
||||
uint16_t imm3 = (imm16 >> 8) & 0x7;
|
||||
uint16_t imm8 = imm16 & 0xFF;
|
||||
uint16_t hw1 = (uint16_t)(0xF000 | (i << 10) | (isMovt ? 0x02C0 : 0x0240) | imm4);
|
||||
uint16_t hw2 = (uint16_t)((imm3 << 12) | ((rd & 0xF) << 8) | imm8);
|
||||
Emit16(out, hw1);
|
||||
Emit16(out, hw2);
|
||||
}
|
||||
|
||||
// Loads a full 32-bit guest address into Rd via MOVW (low 16) + MOVT (high 16).
|
||||
void EmitLoadAddr32(std::vector<uint8_t>& out, uint8_t rd, uint32_t addr) {
|
||||
EmitMovWT(out, /*isMovt=*/false, rd, (uint16_t)(addr & 0xFFFF));
|
||||
EmitMovWT(out, /*isMovt=*/true, rd, (uint16_t)(addr >> 16));
|
||||
}
|
||||
|
||||
// BX LR (Thumb-16): 010001110 mmmm 000, Rm=LR(1110).
|
||||
void EmitBxLr(std::vector<uint8_t>& out) {
|
||||
Emit16(out, 0x4770);
|
||||
}
|
||||
|
||||
// LDR Rt, [SP, #imm8*4] (Thumb-16, SP-relative load): 10011 ttt iiiiiiii.
|
||||
// Reads a stack slot directly without needing SP loaded into a general
|
||||
// register first - exactly what a function reading its own stack-passed
|
||||
// arguments (AAPCS32, args beyond the first 4) does.
|
||||
void EmitLdrSpImm8(std::vector<uint8_t>& out, uint8_t rt, uint8_t imm8) {
|
||||
uint16_t hw = (uint16_t)(0x9800 | ((rt & 0x7) << 8) | imm8);
|
||||
Emit16(out, hw);
|
||||
}
|
||||
|
||||
// No-op stub callback - deliberately does NOTHING to guest registers beyond
|
||||
// what AllocCodeStub's own dispatch (MiscStubDispatch/whatever fires this)
|
||||
// does on its own, matching a REAL import shim as closely as possible
|
||||
// (RegisterImportShim's own real-shim path reads r0-r3/sp and writes r0 -
|
||||
// see import_stub_dispatch_cb in guest_engine.cpp - this callback is
|
||||
// exactly that same shape, just with trivial body).
|
||||
uint32_t g_calleeSavedTestNoopHits = 0;
|
||||
void CalleeSavedTestNoopCb(uc_engine*, uint64_t, uint32_t, void*) {
|
||||
g_calleeSavedTestNoopHits++;
|
||||
}
|
||||
|
||||
// Reentrant-test stub callback - userData carries the "inner" guest
|
||||
// function's address (Thumb-tagged). Calling GuestEngine::CallGuestFunction
|
||||
// from WITHIN a UC_HOOK_CODE callback exercises the exact same "shim needs
|
||||
// to call back into guest code" shape as a real JNI upcall, deliberately
|
||||
// nested one level inside the outer test call this file already builds.
|
||||
uint32_t g_reentrantTestStubHits = 0;
|
||||
void ReentrantTestStubCb(uc_engine*, uint64_t, uint32_t, void* userData) {
|
||||
g_reentrantTestStubHits++;
|
||||
GuestAddr innerAddr = (GuestAddr)(uintptr_t)userData;
|
||||
GuestEngine::Instance().CallGuestFunction(innerAddr);
|
||||
}
|
||||
|
||||
// Real bytes of sub_4EDAD4 (0x4edad4, 56 bytes), copied verbatim from
|
||||
// native_lib/libapp.so via IDA - the exact FNV-1a hash-update loop this
|
||||
// session root-caused and shimmed (guest_engine.cpp's FnvHashAccelHookCb).
|
||||
// ARM-mode (not Thumb) machine code:
|
||||
// push {r11, lr}
|
||||
// mov r11, sp
|
||||
// cmp r2, #1
|
||||
// blt <exit>
|
||||
// loop:
|
||||
// ldrb r3, [r1], #1 ; r4 = *bytes++ (actual reg numbers per IDA)
|
||||
// ...multiply by 16777619, xor, store...
|
||||
// subs r2, r2, #1
|
||||
// bne loop
|
||||
// exit:
|
||||
// pop {r11, pc}
|
||||
const uint8_t kFnvCode[] = {
|
||||
0x00, 0x48, 0x2d, 0xe9, 0x0d, 0xb0, 0xa0, 0xe1, 0x01, 0x00, 0x52, 0xe3, 0x00, 0x88, 0xbd, 0xb8,
|
||||
0x00, 0x30, 0x90, 0xe5, 0x93, 0xc1, 0x00, 0xe3, 0x00, 0xc1, 0x40, 0xe3, 0x93, 0x0c, 0x0e, 0xe0,
|
||||
0x01, 0x30, 0xd1, 0xe4, 0x01, 0x20, 0x52, 0xe2, 0x03, 0x30, 0x2e, 0xe0, 0x00, 0x30, 0x80, 0xe5,
|
||||
0xf9, 0xff, 0xff, 0x1a, 0x00, 0x88, 0xbd, 0xe8,
|
||||
};
|
||||
|
||||
constexpr uint64_t kCodeAddr = 0x1000;
|
||||
constexpr uint64_t kCodeSize = 0x1000;
|
||||
constexpr uint64_t kResultAddr = 0x2000;
|
||||
constexpr uint64_t kResultSize = 0x1000;
|
||||
constexpr uint64_t kStackAddr = 0x9000;
|
||||
constexpr uint64_t kStackSize = 0x1000;
|
||||
constexpr uint64_t kDataAddr = 0x10000000;
|
||||
constexpr uint64_t kDataSize = 16u * 1024 * 1024; // 16 MiB - representative of a real .sb bundle section
|
||||
constexpr uint64_t kSentinelReturn = 0xfffffff0u; // never mapped - uc_emu_start's `until` stops here cleanly
|
||||
|
||||
} // namespace
|
||||
|
||||
void RunTcgBenchmark() {
|
||||
uc_engine* uc = nullptr;
|
||||
uc_err err = uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc);
|
||||
if (err != UC_ERR_OK) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "TCG_BENCH", "uc_open failed: %d", (int)err);
|
||||
return;
|
||||
}
|
||||
|
||||
uc_mem_map(uc, kCodeAddr, kCodeSize, UC_PROT_ALL);
|
||||
uc_mem_map(uc, kResultAddr, kResultSize, UC_PROT_ALL);
|
||||
uc_mem_map(uc, kStackAddr, kStackSize, UC_PROT_ALL);
|
||||
uc_mem_map(uc, kDataAddr, kDataSize, UC_PROT_ALL);
|
||||
|
||||
uc_mem_write(uc, kCodeAddr, kFnvCode, sizeof(kFnvCode));
|
||||
|
||||
std::vector<uint8_t> dummy(kDataSize, 0x5a);
|
||||
uc_mem_write(uc, kDataAddr, dummy.data(), dummy.size());
|
||||
|
||||
uint32_t hashState = 2166136261u; // FNV-1a offset basis
|
||||
uc_mem_write(uc, kResultAddr, &hashState, sizeof(hashState));
|
||||
|
||||
uint32_t r0 = (uint32_t)kResultAddr;
|
||||
uint32_t r1 = (uint32_t)kDataAddr;
|
||||
uint32_t r2 = (uint32_t)kDataSize;
|
||||
uint32_t sp = (uint32_t)(kStackAddr + kStackSize - 0x100);
|
||||
uint32_t lr = kSentinelReturn;
|
||||
uc_reg_write(uc, UC_ARM_REG_R0, &r0);
|
||||
uc_reg_write(uc, UC_ARM_REG_R1, &r1);
|
||||
uc_reg_write(uc, UC_ARM_REG_R2, &r2);
|
||||
uc_reg_write(uc, UC_ARM_REG_SP, &sp);
|
||||
uc_reg_write(uc, UC_ARM_REG_LR, &lr);
|
||||
|
||||
auto t0 = std::chrono::steady_clock::now();
|
||||
err = uc_emu_start(uc, kCodeAddr, kSentinelReturn, 0, 0);
|
||||
auto t1 = std::chrono::steady_clock::now();
|
||||
|
||||
double seconds = std::chrono::duration<double>(t1 - t0).count();
|
||||
double bytesPerSec = seconds > 0 ? (double)kDataSize / seconds : 0.0;
|
||||
double itersPerSec = seconds > 0 ? (double)kDataSize / seconds : 0.0;
|
||||
|
||||
__android_log_print(ANDROID_LOG_INFO, "TCG_BENCH",
|
||||
"uc_emu_start rc=%d, %llu bytes in %.4fs = %.0f bytes/sec (%.0f iterations/sec) - "
|
||||
"bare Unicorn/TCG, zero shims/hooks/game code involved",
|
||||
(int)err, (unsigned long long)kDataSize, seconds, bytesPerSec, itersPerSec);
|
||||
|
||||
uc_close(uc);
|
||||
}
|
||||
|
||||
void RunTcgBenchmarkInRealContext(GuestEngine& engine) {
|
||||
// Keep this modest relative to the real 64 MiB guest heap (kHeapSize,
|
||||
// guest_engine.cpp) - this runs after the real image and JNI_OnLoad
|
||||
// have already claimed some of it, and this is a measurement, not a
|
||||
// stress test.
|
||||
constexpr uint32_t kBufSize = 4u * 1024 * 1024; // 4 MiB
|
||||
|
||||
GuestAddr codeAddr = engine.AllocPermanent((uint32_t)sizeof(kFnvCode));
|
||||
GuestAddr resultAddr = engine.AllocPermanent(4);
|
||||
GuestAddr dataAddr = engine.heap().Alloc(kBufSize);
|
||||
if (!codeAddr || !resultAddr || !dataAddr) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "TCG_BENCH",
|
||||
"RunTcgBenchmarkInRealContext: allocation failed (code=0x%x result=0x%x data=0x%x)",
|
||||
codeAddr, resultAddr, dataAddr);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(engine.G2H(codeAddr), kFnvCode, sizeof(kFnvCode));
|
||||
memset(engine.G2H(dataAddr), 0x5a, kBufSize);
|
||||
uint32_t hashState = 2166136261u;
|
||||
memcpy(engine.G2H(resultAddr), &hashState, sizeof(hashState));
|
||||
|
||||
auto t0 = std::chrono::steady_clock::now();
|
||||
engine.CallGuestFunction(codeAddr, resultAddr, dataAddr, kBufSize, 0);
|
||||
auto t1 = std::chrono::steady_clock::now();
|
||||
|
||||
double seconds = std::chrono::duration<double>(t1 - t0).count();
|
||||
double bytesPerSec = seconds > 0 ? (double)kBufSize / seconds : 0.0;
|
||||
|
||||
__android_log_print(ANDROID_LOG_INFO, "TCG_BENCH",
|
||||
"RunTcgBenchmarkInRealContext: %u bytes in %.4fs = %.0f bytes/sec - "
|
||||
"REAL loaded engine (whole game image + all hooks active), identical instruction bytes, "
|
||||
"invoked via the same CallGuestFunction() every real guest call uses",
|
||||
kBufSize, seconds, bytesPerSec);
|
||||
}
|
||||
|
||||
void RunCalleeSavedRegisterTest(GuestEngine& engine) {
|
||||
// Register a stub via the EXACT mechanism every real GLESv2/libc import
|
||||
// shim uses (AllocCodeStub -> real Thumb `BX LR` + MiscStubDispatch's
|
||||
// O(1) table lookup - see guest_engine.cpp). The callback itself does
|
||||
// nothing (see CalleeSavedTestNoopCb above) - the point is to isolate
|
||||
// whether the DISPATCH MECHANISM itself preserves callee-saved
|
||||
// registers (AAPCS32: r4-r11 must survive a function call unchanged),
|
||||
// not any particular shim's own logic.
|
||||
GuestAddr stubAddr = engine.AllocCodeStub(CalleeSavedTestNoopCb, nullptr);
|
||||
if (!stubAddr) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "REG_TEST",
|
||||
"RunCalleeSavedRegisterTest: AllocCodeStub failed");
|
||||
return;
|
||||
}
|
||||
GuestAddr resultsAddr = engine.AllocPermanent(16); // r4,r5,r6,r7 post-call snapshot
|
||||
GuestAddr codeAddr = engine.AllocPermanent(64); // generous - real size is ~24 bytes
|
||||
if (!resultsAddr || !codeAddr) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "REG_TEST",
|
||||
"RunCalleeSavedRegisterTest: allocation failed (results=0x%x code=0x%x)",
|
||||
resultsAddr, codeAddr);
|
||||
return;
|
||||
}
|
||||
|
||||
// stubAddr already carries the Thumb tag (bit0=1) from AllocCodeStub's
|
||||
// own return convention - BLX needs that same tag to switch/stay in
|
||||
// Thumb mode, so use it as-is (not the raw, untagged address).
|
||||
std::vector<uint8_t> code;
|
||||
EmitPushR4toR7Lr(code); // preserve OUR OWN caller's r4-r7/lr
|
||||
EmitMovsImm8(code, /*rd=*/4, 0x44); // r4 = sentinel 0x44
|
||||
EmitMovsImm8(code, /*rd=*/5, 0x55); // r5 = sentinel 0x55
|
||||
EmitMovsImm8(code, /*rd=*/6, 0x66); // r6 = sentinel 0x66 (the exact register the real bug showed corrupted)
|
||||
EmitMovsImm8(code, /*rd=*/7, 0x77); // r7 = sentinel 0x77
|
||||
EmitLoadAddr32(code, /*rd=*/0, stubAddr); // r0 = stub address (Thumb-tagged)
|
||||
EmitBlxReg(code, /*rm=*/0); // call it - real import-stub call path
|
||||
EmitLoadAddr32(code, /*rd=*/1, resultsAddr); // r1 = results buffer
|
||||
EmitStrImm5(code, /*rt=*/4, /*rn=*/1, /*imm5=*/0); // results[0] = r4 (post-call)
|
||||
EmitStrImm5(code, /*rt=*/5, /*rn=*/1, /*imm5=*/1); // results[1] = r5
|
||||
EmitStrImm5(code, /*rt=*/6, /*rn=*/1, /*imm5=*/2); // results[2] = r6
|
||||
EmitStrImm5(code, /*rt=*/7, /*rn=*/1, /*imm5=*/3); // results[3] = r7
|
||||
EmitPopR4toR7Pc(code); // restore OUR caller's r4-r7, return via pc
|
||||
|
||||
memcpy(engine.G2H(codeAddr), code.data(), code.size());
|
||||
|
||||
uint32_t before = g_calleeSavedTestNoopHits;
|
||||
engine.CallGuestFunction(codeAddr | 1u); // Thumb-tagged entry, no args needed
|
||||
bool stubWasHit = g_calleeSavedTestNoopHits > before;
|
||||
|
||||
uint32_t results[4] = {0, 0, 0, 0};
|
||||
memcpy(results, engine.G2H(resultsAddr), sizeof(results));
|
||||
const uint32_t expected[4] = {0x44, 0x55, 0x66, 0x77};
|
||||
const char* names[4] = {"r4", "r5", "r6", "r7"};
|
||||
bool anyClobbered = false;
|
||||
char summary[256] = {0};
|
||||
int off = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
bool ok = results[i] == expected[i];
|
||||
if (!ok) anyClobbered = true;
|
||||
off += snprintf(summary + off, sizeof(summary) - off, "%s=0x%x(%s) ",
|
||||
names[i], results[i], ok ? "OK" : "CLOBBERED");
|
||||
}
|
||||
__android_log_print(ANDROID_LOG_INFO, "REG_TEST",
|
||||
"RunCalleeSavedRegisterTest: stub_hit=%s result=%s | %s",
|
||||
stubWasHit ? "yes" : "NO(!)",
|
||||
anyClobbered ? "FAIL - callee-saved register(s) clobbered by the import-stub dispatch path"
|
||||
: "PASS - all callee-saved registers survived the stub call intact",
|
||||
summary);
|
||||
}
|
||||
|
||||
void RunReentrantCallRegisterTest(GuestEngine& engine) {
|
||||
// Inner guest function: MOVS r0, #0x99 ; BX LR - trivial, just needs to
|
||||
// be a real, callable guest function for CallGuestFunction to run.
|
||||
std::vector<uint8_t> innerCode;
|
||||
EmitMovsImm8(innerCode, /*rd=*/0, 0x99);
|
||||
EmitBxLr(innerCode);
|
||||
GuestAddr innerAddr = engine.AllocPermanent(16);
|
||||
if (!innerAddr) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "REG_TEST", "RunReentrantCallRegisterTest: inner alloc failed");
|
||||
return;
|
||||
}
|
||||
memcpy(engine.G2H(innerAddr), innerCode.data(), innerCode.size());
|
||||
|
||||
// Stub whose C++ callback reenters the guest via CallGuestFunction -
|
||||
// userData carries the Thumb-tagged inner function address.
|
||||
GuestAddr stubAddr = engine.AllocCodeStub(ReentrantTestStubCb, (void*)(uintptr_t)(innerAddr | 1u));
|
||||
GuestAddr resultsAddr = engine.AllocPermanent(16);
|
||||
GuestAddr codeAddr = engine.AllocPermanent(64);
|
||||
if (!stubAddr || !resultsAddr || !codeAddr) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "REG_TEST",
|
||||
"RunReentrantCallRegisterTest: allocation failed (stub=0x%x results=0x%x code=0x%x)",
|
||||
stubAddr, resultsAddr, codeAddr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Same outer shape as RunCalleeSavedRegisterTest - sentinels in r4-r7,
|
||||
// call the stub (which now reenters CallGuestFunction internally
|
||||
// instead of just returning), snapshot r4-r7 afterward.
|
||||
std::vector<uint8_t> code;
|
||||
EmitPushR4toR7Lr(code);
|
||||
EmitMovsImm8(code, 4, 0x14);
|
||||
EmitMovsImm8(code, 5, 0x15);
|
||||
EmitMovsImm8(code, 6, 0x16);
|
||||
EmitMovsImm8(code, 7, 0x17);
|
||||
EmitLoadAddr32(code, 0, stubAddr);
|
||||
EmitBlxReg(code, 0);
|
||||
EmitLoadAddr32(code, 1, resultsAddr);
|
||||
EmitStrImm5(code, 4, 1, 0);
|
||||
EmitStrImm5(code, 5, 1, 1);
|
||||
EmitStrImm5(code, 6, 1, 2);
|
||||
EmitStrImm5(code, 7, 1, 3);
|
||||
EmitPopR4toR7Pc(code);
|
||||
|
||||
memcpy(engine.G2H(codeAddr), code.data(), code.size());
|
||||
|
||||
uint32_t before = g_reentrantTestStubHits;
|
||||
engine.CallGuestFunction(codeAddr | 1u);
|
||||
bool stubWasHit = g_reentrantTestStubHits > before;
|
||||
|
||||
uint32_t results[4] = {0, 0, 0, 0};
|
||||
memcpy(results, engine.G2H(resultsAddr), sizeof(results));
|
||||
const uint32_t expected[4] = {0x14, 0x15, 0x16, 0x17};
|
||||
const char* names[4] = {"r4", "r5", "r6", "r7"};
|
||||
bool anyClobbered = false;
|
||||
char summary[256] = {0};
|
||||
int off = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
bool ok = results[i] == expected[i];
|
||||
if (!ok) anyClobbered = true;
|
||||
off += snprintf(summary + off, sizeof(summary) - off, "%s=0x%x(%s) ",
|
||||
names[i], results[i], ok ? "OK" : "CLOBBERED");
|
||||
}
|
||||
__android_log_print(ANDROID_LOG_INFO, "REG_TEST",
|
||||
"RunReentrantCallRegisterTest: stub_hit=%s result=%s | %s",
|
||||
stubWasHit ? "yes" : "NO(!)",
|
||||
anyClobbered ? "FAIL - outer call's callee-saved register(s) clobbered by a nested reentrant CallGuestFunction"
|
||||
: "PASS - outer call's callee-saved registers survived a nested reentrant call intact",
|
||||
summary);
|
||||
}
|
||||
|
||||
void RunStackArgMarshalingTest(GuestEngine& engine) {
|
||||
// Leaf function - deliberately never pushes/pops or calls anything else,
|
||||
// so there's no need to preserve r4-r7 for a caller beyond this test's
|
||||
// own use of them as scratch. Reads r0-r3 (register-passed args 0-3)
|
||||
// FIRST (before overwriting them), storing each straight to the results
|
||||
// buffer via r4 (loaded once, untouched by anything else here), THEN
|
||||
// reuses r0-r3 as scratch to read the stack-passed args 4-7 directly
|
||||
// via SP-relative loads (exactly where CallGuestFunction's own
|
||||
// marshaling is documented to place them: [sp+0]=args[4], [sp+4]=
|
||||
// args[5], [sp+8]=args[6], [sp+12]=args[7]).
|
||||
GuestAddr resultsAddr = engine.AllocPermanent(32); // 8 x uint32_t
|
||||
GuestAddr codeAddr = engine.AllocPermanent(96);
|
||||
if (!resultsAddr || !codeAddr) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "REG_TEST",
|
||||
"RunStackArgMarshalingTest: allocation failed (results=0x%x code=0x%x)",
|
||||
resultsAddr, codeAddr);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> code;
|
||||
EmitLoadAddr32(code, /*rd=*/4, resultsAddr);
|
||||
EmitStrImm5(code, /*rt=*/0, /*rn=*/4, /*imm5=*/0); // results[0] = arg0 (r0)
|
||||
EmitStrImm5(code, /*rt=*/1, /*rn=*/4, /*imm5=*/1); // results[1] = arg1 (r1)
|
||||
EmitStrImm5(code, /*rt=*/2, /*rn=*/4, /*imm5=*/2); // results[2] = arg2 (r2)
|
||||
EmitStrImm5(code, /*rt=*/3, /*rn=*/4, /*imm5=*/3); // results[3] = arg3 (r3)
|
||||
EmitLdrSpImm8(code, /*rt=*/0, /*imm8=*/0); // r0 = [sp+0] = arg4
|
||||
EmitStrImm5(code, /*rt=*/0, /*rn=*/4, /*imm5=*/4);
|
||||
EmitLdrSpImm8(code, /*rt=*/0, /*imm8=*/1); // r0 = [sp+4] = arg5
|
||||
EmitStrImm5(code, /*rt=*/0, /*rn=*/4, /*imm5=*/5);
|
||||
EmitLdrSpImm8(code, /*rt=*/0, /*imm8=*/2); // r0 = [sp+8] = arg6
|
||||
EmitStrImm5(code, /*rt=*/0, /*rn=*/4, /*imm5=*/6);
|
||||
EmitLdrSpImm8(code, /*rt=*/0, /*imm8=*/3); // r0 = [sp+12] = arg7
|
||||
EmitStrImm5(code, /*rt=*/0, /*rn=*/4, /*imm5=*/7);
|
||||
EmitBxLr(code);
|
||||
|
||||
memcpy(engine.G2H(codeAddr), code.data(), code.size());
|
||||
|
||||
const uint32_t sentinels[8] = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17};
|
||||
engine.CallGuestFunction(codeAddr | 1u, sentinels, 8);
|
||||
|
||||
uint32_t results[8] = {0};
|
||||
memcpy(results, engine.G2H(resultsAddr), sizeof(results));
|
||||
bool anyWrong = false;
|
||||
char summary[384] = {0};
|
||||
int off = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
bool ok = results[i] == sentinels[i];
|
||||
if (!ok) anyWrong = true;
|
||||
off += snprintf(summary + off, sizeof(summary) - off, "arg%d=0x%x(%s) ",
|
||||
i, results[i], ok ? "OK" : "WRONG");
|
||||
}
|
||||
__android_log_print(ANDROID_LOG_INFO, "REG_TEST",
|
||||
"RunStackArgMarshalingTest: result=%s | %s",
|
||||
anyWrong ? "FAIL - stack-passed argument(s) (argCount>4) marshaled incorrectly"
|
||||
: "PASS - all 4 register args and 4 stack-marshaled args (argCount=8) arrived correctly",
|
||||
summary);
|
||||
}
|
||||
|
||||
void RunSequentialCallStateLeakTest(GuestEngine& engine) {
|
||||
// Reuses CalleeSavedTestNoopCb (the same trivial no-op stub
|
||||
// RunCalleeSavedRegisterTest already validated preserves r4-r7 within
|
||||
// ONE call) - this test's question is different: does calling the
|
||||
// SAME stub, through the SAME dispatch table entry, on the SAME
|
||||
// thread, MULTIPLE TIMES IN A ROW (not nested/reentrant - each call
|
||||
// fully completes before the next starts) ever let one call's state
|
||||
// leak into another's, e.g. via a caching bug in CallGuestFunction's
|
||||
// save/restore bookkeeping or AllocCodeStub/MiscStubDispatch's table
|
||||
// lookup that only manifests on a second or third call.
|
||||
GuestAddr stubAddr = engine.AllocCodeStub(CalleeSavedTestNoopCb, nullptr);
|
||||
GuestAddr resultsAddr = engine.AllocPermanent(16);
|
||||
GuestAddr codeAddr = engine.AllocPermanent(64);
|
||||
if (!stubAddr || !resultsAddr || !codeAddr) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "REG_TEST",
|
||||
"RunSequentialCallStateLeakTest: allocation failed (stub=0x%x results=0x%x code=0x%x)",
|
||||
stubAddr, resultsAddr, codeAddr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Same shape as RunCalleeSavedRegisterTest, but the sentinel values are
|
||||
// baked into three SEPARATE code buffers (one per call) so each call's
|
||||
// expected values are unambiguous and distinct from the others -
|
||||
// avoids any chance of a false PASS from comparing against a leftover
|
||||
// value that just happens to equal what THIS call wrote anyway.
|
||||
struct Round { uint32_t sentinels[4]; GuestAddr codeAddr; };
|
||||
Round rounds[3] = {
|
||||
{{0x21, 0x22, 0x23, 0x24}, 0},
|
||||
{{0x31, 0x32, 0x33, 0x34}, 0},
|
||||
{{0x41, 0x42, 0x43, 0x44}, 0},
|
||||
};
|
||||
|
||||
bool allocOk = true;
|
||||
for (auto& round : rounds) {
|
||||
round.codeAddr = engine.AllocPermanent(64);
|
||||
if (!round.codeAddr) { allocOk = false; break; }
|
||||
std::vector<uint8_t> code;
|
||||
EmitPushR4toR7Lr(code);
|
||||
EmitMovsImm8(code, 4, (uint8_t)round.sentinels[0]);
|
||||
EmitMovsImm8(code, 5, (uint8_t)round.sentinels[1]);
|
||||
EmitMovsImm8(code, 6, (uint8_t)round.sentinels[2]);
|
||||
EmitMovsImm8(code, 7, (uint8_t)round.sentinels[3]);
|
||||
EmitLoadAddr32(code, 0, stubAddr);
|
||||
EmitBlxReg(code, 0);
|
||||
EmitLoadAddr32(code, 1, resultsAddr);
|
||||
EmitStrImm5(code, 4, 1, 0);
|
||||
EmitStrImm5(code, 5, 1, 1);
|
||||
EmitStrImm5(code, 6, 1, 2);
|
||||
EmitStrImm5(code, 7, 1, 3);
|
||||
EmitPopR4toR7Pc(code);
|
||||
memcpy(engine.G2H(round.codeAddr), code.data(), code.size());
|
||||
}
|
||||
if (!allocOk) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "REG_TEST",
|
||||
"RunSequentialCallStateLeakTest: per-round code allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
bool anyFailed = false;
|
||||
char summary[512] = {0};
|
||||
int off = 0;
|
||||
for (int r = 0; r < 3; r++) {
|
||||
// Poison the results buffer with a recognizable non-sentinel value
|
||||
// between rounds - a leftover-value false PASS is impossible here,
|
||||
// since 0xDEADBEEF never matches any round's real sentinels.
|
||||
uint32_t poison[4] = {0xDEADBEEFu, 0xDEADBEEFu, 0xDEADBEEFu, 0xDEADBEEFu};
|
||||
memcpy(engine.G2H(resultsAddr), poison, sizeof(poison));
|
||||
|
||||
engine.CallGuestFunction(rounds[r].codeAddr | 1u);
|
||||
|
||||
uint32_t results[4] = {0};
|
||||
memcpy(results, engine.G2H(resultsAddr), sizeof(results));
|
||||
bool roundOk = true;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (results[i] != rounds[r].sentinels[i]) roundOk = false;
|
||||
}
|
||||
if (!roundOk) anyFailed = true;
|
||||
off += snprintf(summary + off, sizeof(summary) - off,
|
||||
"round%d=%s[0x%x,0x%x,0x%x,0x%x] ", r, roundOk ? "OK" : "FAIL",
|
||||
results[0], results[1], results[2], results[3]);
|
||||
}
|
||||
__android_log_print(ANDROID_LOG_INFO, "REG_TEST",
|
||||
"RunSequentialCallStateLeakTest: result=%s | %s",
|
||||
anyFailed ? "FAIL - state leaked across sequential (non-reentrant) calls"
|
||||
: "PASS - three sequential calls to the same stub each saw only their own sentinels",
|
||||
summary);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
class GuestEngine;
|
||||
|
||||
// One-shot, throwaway benchmark (2026-09-06, ARM64_TRANSLATION_LAYER.md -
|
||||
// "is Unicorn/QEMU-TCG itself the bottleneck, or just this project's own
|
||||
// overhead on top of it" question, raised directly by the user comparing
|
||||
// against libhoudini's own ARM32->x86_64 translation achieving good
|
||||
// real-world game performance). Runs the EXACT real machine code bytes of
|
||||
// sub_4EDAD4 (the FNV-1a hash loop already root-caused and shimmed this
|
||||
// session) on a brand-new, hook-free uc_engine with no relationship to
|
||||
// GuestEngine/the loaded game image at all - the purest possible measure
|
||||
// of "what can bare Unicorn/TCG achieve for this exact instruction
|
||||
// sequence," isolated from every other project-specific cost this session
|
||||
// already found and fixed (uc_emu_stop() round-trips, GuestHeap's O(n)
|
||||
// scan, per-block hook overhead). Call once, log the result, then remove.
|
||||
void RunTcgBenchmark();
|
||||
|
||||
// Same measurement, same exact instruction bytes, but run INSIDE the real,
|
||||
// fully-loaded GuestEngine (whole ~9+ MB game image mapped, every other
|
||||
// hook this project registers still active, invoked via the same
|
||||
// CallGuestFunction() every other real guest call in this codebase uses) -
|
||||
// via a scratch copy of the code at a different address so it doesn't hit
|
||||
// FnvHashAccelHookCb's own address-pinned skip. Directly answers: is bare
|
||||
// Unicorn/TCG itself slower once the real binary is loaded (translation-
|
||||
// cache pressure being the leading candidate), or is the gap this
|
||||
// session's earlier real-world measurements found actually coming from
|
||||
// surrounding work (marshaling, allocations, other per-call bookkeeping)
|
||||
// rather than the instruction-emulation cost of the loop itself?
|
||||
void RunTcgBenchmarkInRealContext(GuestEngine& engine);
|
||||
|
||||
// Synthetic unit test (2026-09-16, ARM64_TRANSLATION_LAYER.md - the
|
||||
// sub_43FDE0/dword_ADBFB8 investigation). Real game code shows a
|
||||
// callee-saved register (R6, holding a value that should survive
|
||||
// unchanged across several nested guest-to-guest BL calls including real
|
||||
// GLESv2 import calls like glUseProgram/glVertexAttribPointer) ending up
|
||||
// corrupted by the time it's read many instructions later. Rather than
|
||||
// keep tracing further through real, complex game code, this builds a
|
||||
// minimal synthetic guest function that: sets R4-R7 to known sentinel
|
||||
// values, calls a stub allocated via the EXACT SAME AllocCodeStub/
|
||||
// MiscStubDispatch/import_stub_dispatch_cb mechanism every real GLES
|
||||
// import shim uses (not a simplified stand-in), then checks whether R4-R7
|
||||
// still hold their sentinels. Directly tests whether the import-stub call
|
||||
// path preserves callee-saved registers (AAPCS32) - isolates the
|
||||
// mechanism in one controlled call instead of chasing it through real
|
||||
// game logic. Logs a bitmask of which registers (if any) got clobbered.
|
||||
void RunCalleeSavedRegisterTest(GuestEngine& engine);
|
||||
|
||||
// Same idea, but for the REENTRANT case: a stub callback that itself
|
||||
// triggers a nested CallGuestFunction() from within its own C++ body
|
||||
// (matching what a JNI upcall or any "shim needs to call back into guest
|
||||
// code" path does - CallGuestFunction's own elaborate save/restore of
|
||||
// r0-r12/sp/lr/pc/cpsr + all 32 D-registers exists specifically for this
|
||||
// case, per its own comment in guest_engine.cpp). Verifies the OUTER,
|
||||
// suspended call's callee-saved registers survive a nested nested call
|
||||
// correctly - a much less-exercised path than a single flat stub call.
|
||||
void RunReentrantCallRegisterTest(GuestEngine& engine);
|
||||
|
||||
// Synthetic unit test (2026-09-16, ARM64_TRANSLATION_LAYER.md - the
|
||||
// broader "покрой синтетикой весь свой код" directive, and specifically
|
||||
// the earlier-in-session "Копай CallGuestFunction и маршалинг
|
||||
// stack-аргументов" instruction that was never actually followed up with a
|
||||
// dedicated test). `CallGuestFunction(target, args, argCount)` marshals
|
||||
// argCount>4 by writing args[4..] onto the guest stack per AAPCS32
|
||||
// (args[4] at [sp+0], args[5] at [sp+4], ...) - real call sites depend on
|
||||
// this (JNI entry points with >4 params, arbitrary-arity Call*Method) but
|
||||
// it had no isolated correctness test of its own. Builds a minimal LEAF
|
||||
// guest function that reads r0-r3 (register-passed args) plus [sp+0],
|
||||
// [sp+4], [sp+8], [sp+12] (stack-passed args) directly, writes all 8 back
|
||||
// to a results buffer, and calls it with 8 known sentinel values via the
|
||||
// array-taking CallGuestFunction overload. Directly answers whether the
|
||||
// stack portion of the marshaling is correct (right values, right
|
||||
// alignment/offsets) rather than just the register portion every other
|
||||
// existing test already covers.
|
||||
void RunStackArgMarshalingTest(GuestEngine& engine);
|
||||
|
||||
// Synthetic unit test (2026-09-16, ARM64_TRANSLATION_LAYER.md - same
|
||||
// broader coverage directive). Every existing register-preservation test
|
||||
// checks ONE call in isolation. This checks whether STATE LEAKS ACROSS
|
||||
// SEQUENTIAL (not nested/reentrant) calls on the same thread - e.g. a
|
||||
// caching bug in CallGuestFunction's own save/restore bookkeeping, or in
|
||||
// AllocCodeStub/MiscStubDispatch's table lookup, that only manifests on
|
||||
// the second or third call and not the first. Calls the SAME
|
||||
// AllocCodeStub-dispatched stub three times in a row, each time with
|
||||
// DIFFERENT sentinel values in r4-r7, checking after EACH call that only
|
||||
// THAT call's own sentinels come back (not a stale value from the
|
||||
// previous call).
|
||||
void RunSequentialCallStateLeakTest(GuestEngine& engine);
|
||||
@@ -0,0 +1,236 @@
|
||||
#include "zlib_accel.h"
|
||||
|
||||
#include "guest_engine.h"
|
||||
#include "../util/util.h"
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
namespace zlib_accel {
|
||||
namespace {
|
||||
|
||||
// z_stream on this 32-bit ABI (sizeof == 56, which inflateInit2_ itself
|
||||
// checks - see its decompile in ARM64_TRANSLATION_LAYER.md):
|
||||
constexpr uint32_t kOffNextIn = 0;
|
||||
constexpr uint32_t kOffAvailIn = 4;
|
||||
constexpr uint32_t kOffTotalIn = 8;
|
||||
constexpr uint32_t kOffNextOut = 12;
|
||||
constexpr uint32_t kOffAvailOut = 16;
|
||||
constexpr uint32_t kOffTotalOut = 20;
|
||||
constexpr uint32_t kOffMsg = 24;
|
||||
constexpr uint32_t kOffState = 28;
|
||||
constexpr uint32_t kOffAdler = 48;
|
||||
constexpr uint32_t kGuestZStreamSize = 56;
|
||||
|
||||
std::mutex g_mutex;
|
||||
// guest z_stream address -> the host stream doing the real work.
|
||||
std::map<uint32_t, z_stream*> g_streams;
|
||||
|
||||
std::atomic<uint64_t> g_calls{0};
|
||||
std::atomic<uint64_t> g_bytesOut{0};
|
||||
|
||||
uint32_t ReadU32(uc_engine* uc, uint32_t addr) {
|
||||
uint32_t v = 0;
|
||||
uc_mem_read(uc, addr, &v, 4);
|
||||
return v;
|
||||
}
|
||||
void WriteU32(uc_engine* uc, uint32_t addr, uint32_t v) { uc_mem_write(uc, addr, &v, 4); }
|
||||
|
||||
// Returns from the intercepted guest function with `ret` in r0, without ever
|
||||
// executing its body - same mechanism as guest_engine.cpp's
|
||||
// FnvHashAccelHookCb (write PC=LR, stop the emulation so it resumes there).
|
||||
void ReturnToCaller(uc_engine* uc, uint32_t ret) {
|
||||
uint32_t lr = 0;
|
||||
uc_reg_read(uc, UC_ARM_REG_LR, &lr);
|
||||
uc_reg_write(uc, UC_ARM_REG_R0, &ret);
|
||||
uc_reg_write(uc, UC_ARM_REG_PC, &lr);
|
||||
uc_emu_stop(uc);
|
||||
}
|
||||
|
||||
z_stream* FindStream(uint32_t guestStrm) {
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
auto it = g_streams.find(guestStrm);
|
||||
return it == g_streams.end() ? nullptr : it->second;
|
||||
}
|
||||
|
||||
void HandleInit2(uc_engine* uc) {
|
||||
uint32_t strm = 0, windowBits = 0, version = 0, streamSize = 0;
|
||||
uc_reg_read(uc, UC_ARM_REG_R0, &strm);
|
||||
uc_reg_read(uc, UC_ARM_REG_R1, &windowBits);
|
||||
uc_reg_read(uc, UC_ARM_REG_R2, &version);
|
||||
uc_reg_read(uc, UC_ARM_REG_R3, &streamSize);
|
||||
|
||||
// Mirror the real function's own argument validation exactly, so a
|
||||
// caller that gets this wrong still sees the error code it expects
|
||||
// rather than silently succeeding.
|
||||
if (!version) { ReturnToCaller(uc, (uint32_t)Z_VERSION_ERROR); return; }
|
||||
uint8_t versionFirst = 0;
|
||||
uc_mem_read(uc, version, &versionFirst, 1);
|
||||
if (streamSize != kGuestZStreamSize || versionFirst != '1') {
|
||||
ReturnToCaller(uc, (uint32_t)Z_VERSION_ERROR);
|
||||
return;
|
||||
}
|
||||
if (!strm) { ReturnToCaller(uc, (uint32_t)Z_STREAM_ERROR); return; }
|
||||
|
||||
auto* hs = new z_stream();
|
||||
std::memset(hs, 0, sizeof(*hs));
|
||||
// Host allocation on purpose: the guest's own zalloc/zfree hand out
|
||||
// GuestHeap memory, and the window/state buffers are pure zlib internals
|
||||
// the guest never looks at. Keeping them off the guest heap also keeps
|
||||
// this acceleration from competing for the guest's own arena.
|
||||
int ret = inflateInit2(hs, (int)windowBits);
|
||||
if (ret != Z_OK) {
|
||||
delete hs;
|
||||
ReturnToCaller(uc, (uint32_t)ret);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
auto existing = g_streams.find(strm);
|
||||
if (existing != g_streams.end()) {
|
||||
// Re-init of an address we already own (the guest freed and
|
||||
// reallocated a z_stream at the same address). Drop the old one
|
||||
// rather than leaking it.
|
||||
inflateEnd(existing->second);
|
||||
delete existing->second;
|
||||
existing->second = hs;
|
||||
} else {
|
||||
g_streams.emplace(strm, hs);
|
||||
}
|
||||
}
|
||||
|
||||
// Deliberately leave the guest's `state` NULL. Nothing in the guest
|
||||
// dereferences it while we own the stream, and if some zlib entry point
|
||||
// this layer does NOT intercept is ever called on this stream, zlib's
|
||||
// own NULL-state check makes it return Z_STREAM_ERROR - a clean,
|
||||
// debuggable failure instead of walking a fabricated pointer.
|
||||
WriteU32(uc, strm + kOffState, 0);
|
||||
WriteU32(uc, strm + kOffMsg, 0);
|
||||
WriteU32(uc, strm + kOffTotalIn, 0);
|
||||
WriteU32(uc, strm + kOffTotalOut, 0);
|
||||
ReturnToCaller(uc, (uint32_t)Z_OK);
|
||||
}
|
||||
|
||||
void HandleInflate(uc_engine* uc) {
|
||||
uint32_t strm = 0, flush = 0;
|
||||
uc_reg_read(uc, UC_ARM_REG_R0, &strm);
|
||||
uc_reg_read(uc, UC_ARM_REG_R1, &flush);
|
||||
|
||||
z_stream* hs = strm ? FindStream(strm) : nullptr;
|
||||
if (!hs) return; // not ours - let the original emulated code run
|
||||
|
||||
auto& eng = GuestEngine::Instance();
|
||||
uint32_t nextIn = ReadU32(uc, strm + kOffNextIn);
|
||||
uint32_t availIn = ReadU32(uc, strm + kOffAvailIn);
|
||||
uint32_t nextOut = ReadU32(uc, strm + kOffNextOut);
|
||||
uint32_t availOut = ReadU32(uc, strm + kOffAvailOut);
|
||||
|
||||
// The guest's buffers live in the same flat host region, so host zlib
|
||||
// reads and writes them in place - no copying in or out.
|
||||
hs->next_in = availIn ? (Bytef*)eng.G2H(nextIn) : nullptr;
|
||||
hs->avail_in = availIn;
|
||||
hs->next_out = availOut ? (Bytef*)eng.G2H(nextOut) : nullptr;
|
||||
hs->avail_out = availOut;
|
||||
|
||||
int ret = inflate(hs, (int)flush);
|
||||
|
||||
uint32_t consumed = availIn - hs->avail_in;
|
||||
uint32_t produced = availOut - hs->avail_out;
|
||||
WriteU32(uc, strm + kOffNextIn, nextIn + consumed);
|
||||
WriteU32(uc, strm + kOffAvailIn, hs->avail_in);
|
||||
WriteU32(uc, strm + kOffNextOut, nextOut + produced);
|
||||
WriteU32(uc, strm + kOffAvailOut, hs->avail_out);
|
||||
WriteU32(uc, strm + kOffTotalIn, (uint32_t)hs->total_in);
|
||||
WriteU32(uc, strm + kOffTotalOut, (uint32_t)hs->total_out);
|
||||
WriteU32(uc, strm + kOffAdler, (uint32_t)hs->adler);
|
||||
// msg points at a host string literal that the guest cannot read; leave
|
||||
// it NULL rather than handing over an address outside the guest region.
|
||||
WriteU32(uc, strm + kOffMsg, 0);
|
||||
|
||||
uint64_t n = g_calls.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
uint64_t total = g_bytesOut.fetch_add(produced, std::memory_order_relaxed) + produced;
|
||||
if (n % 2000 == 0) {
|
||||
Log("zlib_accel: %llu native inflate calls, %.1fMB produced (host zlib, not emulated)",
|
||||
(unsigned long long)n, total / (1024.0 * 1024.0));
|
||||
}
|
||||
ReturnToCaller(uc, (uint32_t)ret);
|
||||
}
|
||||
|
||||
void HandleReset2(uc_engine* uc) {
|
||||
uint32_t strm = 0, windowBits = 0;
|
||||
uc_reg_read(uc, UC_ARM_REG_R0, &strm);
|
||||
uc_reg_read(uc, UC_ARM_REG_R1, &windowBits);
|
||||
z_stream* hs = strm ? FindStream(strm) : nullptr;
|
||||
if (!hs) return; // not ours
|
||||
int ret = inflateReset2(hs, (int)windowBits);
|
||||
WriteU32(uc, strm + kOffTotalIn, 0);
|
||||
WriteU32(uc, strm + kOffTotalOut, 0);
|
||||
WriteU32(uc, strm + kOffMsg, 0);
|
||||
ReturnToCaller(uc, (uint32_t)ret);
|
||||
}
|
||||
|
||||
void HandleEnd(uc_engine* uc) {
|
||||
uint32_t strm = 0;
|
||||
uc_reg_read(uc, UC_ARM_REG_R0, &strm);
|
||||
if (!strm) return; // not ours (and the real code returns Z_STREAM_ERROR)
|
||||
|
||||
z_stream* hs = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
auto it = g_streams.find(strm);
|
||||
if (it == g_streams.end()) return; // not ours
|
||||
hs = it->second;
|
||||
g_streams.erase(it);
|
||||
}
|
||||
inflateEnd(hs);
|
||||
delete hs;
|
||||
WriteU32(uc, strm + kOffState, 0);
|
||||
ReturnToCaller(uc, (uint32_t)Z_OK);
|
||||
}
|
||||
|
||||
std::atomic<uint64_t> g_crcCalls{0};
|
||||
std::atomic<uint64_t> g_crcBytes{0};
|
||||
|
||||
// crc32(uLong crc, const Bytef* buf, uInt len) - pure, so unlike inflate
|
||||
// there is no stream to own and nothing to fall back to: every call can be
|
||||
// served natively. Mirrors the real function's own `buf == NULL -> 0` case.
|
||||
void HandleCrc32(uc_engine* uc) {
|
||||
uint32_t crc = 0, buf = 0, len = 0;
|
||||
uc_reg_read(uc, UC_ARM_REG_R0, &crc);
|
||||
uc_reg_read(uc, UC_ARM_REG_R1, &buf);
|
||||
uc_reg_read(uc, UC_ARM_REG_R2, &len);
|
||||
|
||||
if (!buf) { ReturnToCaller(uc, 0); return; }
|
||||
|
||||
auto& eng = GuestEngine::Instance();
|
||||
uint32_t result =
|
||||
(uint32_t)::crc32((uLong)crc, (const Bytef*)eng.G2H(buf), (uInt)len);
|
||||
|
||||
uint64_t n = g_crcCalls.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
uint64_t total = g_crcBytes.fetch_add(len, std::memory_order_relaxed) + len;
|
||||
if (n % 20000 == 0) {
|
||||
Log("zlib_accel: %llu native crc32 calls, %.1fMB checksummed (host, not emulated)",
|
||||
(unsigned long long)n, total / (1024.0 * 1024.0));
|
||||
}
|
||||
ReturnToCaller(uc, result);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void HookCb(uc_engine* uc, uint64_t address, uint32_t, void*) {
|
||||
switch (address) {
|
||||
case kInflateInit2Addr: HandleInit2(uc); break;
|
||||
case kInflateAddr: HandleInflate(uc); break;
|
||||
case kInflateReset2Addr: HandleReset2(uc); break;
|
||||
case kInflateEndAddr: HandleEnd(uc); break;
|
||||
case kCrc32Addr: HandleCrc32(uc); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace zlib_accel
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <unicorn/unicorn.h>
|
||||
|
||||
// Native acceleration for the zlib inflate family that is STATICALLY LINKED
|
||||
// into libapp.so (zlib 1.2.5 - confirmed by its embedded copyright string;
|
||||
// the binary imports no libz.so and has no inflate/crc32 dynamic symbols).
|
||||
// Because it is statically linked it runs as guest ARM32 code through
|
||||
// Unicorn, i.e. at the measured ~12.8M instructions/sec against a native
|
||||
// core's ~1G. Decompression costs roughly 10-30 instructions per output byte,
|
||||
// and a single load was measured producing 66.2MB from ONE stream - minutes
|
||||
// of pure emulation. See ARM64_TRANSLATION_LAYER.md (task #43).
|
||||
//
|
||||
// The interception follows the same mechanism as FnvHashAccelHookCb
|
||||
// (guest_engine.cpp): a UC_HOOK_CODE at the function's entry address, the
|
||||
// work done natively, then PC=LR and uc_emu_stop so the guest body never
|
||||
// runs. The difference, and the reason this needs its own file: FNV-1a was a
|
||||
// pure function, whereas inflate is STATEFUL - the state is created by
|
||||
// inflateInit2_ and threaded through many inflate() calls - so the whole
|
||||
// family has to be taken over together, with a real host z_stream kept per
|
||||
// guest stream.
|
||||
//
|
||||
// SAFETY: a stream is only taken over if this layer saw its own
|
||||
// inflateInit2_ go through. libapp.so has nine distinct callers of inflate
|
||||
// (libpng among them, with its own streams and its own use of functions this
|
||||
// layer does not intercept); any stream this layer does not recognise is left
|
||||
// entirely alone and runs the original emulated code, slowly but correctly.
|
||||
//
|
||||
// Guest addresses below are from this exact libapp.so build:
|
||||
// inflateInit2_ 0x667D64 (strm, windowBits, version, stream_size)
|
||||
// inflateReset2 0x667C44 (strm, windowBits)
|
||||
// inflate 0x667FFC (strm, flush)
|
||||
// inflateEnd 0x669B64 (strm)
|
||||
namespace zlib_accel {
|
||||
|
||||
constexpr uint64_t kInflateInit2Addr = 0x667d64;
|
||||
constexpr uint64_t kInflateReset2Addr = 0x667c44;
|
||||
constexpr uint64_t kInflateAddr = 0x667ffc;
|
||||
constexpr uint64_t kInflateEndAddr = 0x669b64;
|
||||
// zlib's crc32(crc, buf, len) - identified by its ~crc on entry and exit, the
|
||||
// 8x256 slice-by-8 table at dword_A40B84, and the 32-byte unrolled loop.
|
||||
// Measured by the block profiler as the single hottest thing during a load:
|
||||
// 17.7% of all samples. Unlike inflate this is PURE - no state, no lifetime -
|
||||
// so it is the same trivial, safe interception as FnvHashAccelHookCb.
|
||||
// The game calls it directly on decompressed data (archive integrity
|
||||
// checks), i.e. on top of whatever crc32 work happens inside inflate itself.
|
||||
constexpr uint64_t kCrc32Addr = 0x65f6c8;
|
||||
|
||||
// One callback for all four - it dispatches on the hook address, the same way
|
||||
// QuadGeometryProbeHookCb does, so registration stays a handful of lines.
|
||||
void HookCb(uc_engine* uc, uint64_t address, uint32_t size, void* userData);
|
||||
|
||||
} // namespace zlib_accel
|
||||
@@ -0,0 +1,82 @@
|
||||
// ARM64-only-device prototype: `GameActivityMain`'s native lifecycle/GL
|
||||
// callbacks, wired for real (2026-08-29) - see ARM64_TRANSLATION_LAYER.md's
|
||||
// "boot the game" follow-up. Each of these forwards into libapp.so's own
|
||||
// real implementation (real_native_offsets.h - found via a plain .dynsym
|
||||
// dump, not IDA RE, since JNI export names survive stripping) via
|
||||
// CallGuestFunction, through the guest JNIEnv bridge (emu/jni_shim.*).
|
||||
//
|
||||
// `nativeOnPhysicalKeyboardVisibilityChanged` has no real libapp.so
|
||||
// implementation (absent from .dynsym - confirmed, not just unresolved by
|
||||
// this port) and stays a no-op stub.
|
||||
#include <jni.h>
|
||||
#include "util/util.h"
|
||||
#include "real_native_call.h"
|
||||
#include "real_native_offsets.h"
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameActivityMain_nativeOnCreate(JNIEnv* env, jobject thiz) {
|
||||
CallRealNative(env, thiz, REAL_GAMEACTIVITY_ONCREATE_OFFSET);
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameActivityMain_nativeOnDestroy(JNIEnv* env, jobject thiz) {
|
||||
CallRealNative(env, thiz, REAL_GAMEACTIVITY_ONDESTROY_OFFSET);
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameActivityMain_nativeOnMusicPlayerStateChanged(JNIEnv* env, jobject thiz) {
|
||||
CallRealNative(env, thiz, REAL_GAMEACTIVITY_ONMUSICPLAYERSTATECHANGED_OFFSET);
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameActivityMain_nativeOnPause(JNIEnv* env, jobject thiz) {
|
||||
CallRealNative(env, thiz, REAL_GAMEACTIVITY_ONPAUSE_OFFSET);
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameActivityMain_nativeOnRestart(JNIEnv* env, jobject thiz) {
|
||||
CallRealNative(env, thiz, REAL_GAMEACTIVITY_ONRESTART_OFFSET);
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameActivityMain_nativeOnResume(JNIEnv* env, jobject thiz) {
|
||||
CallRealNative(env, thiz, REAL_GAMEACTIVITY_ONRESUME_OFFSET);
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameActivityMain_nativeOnStart(JNIEnv* env, jobject thiz) {
|
||||
CallRealNative(env, thiz, REAL_GAMEACTIVITY_ONSTART_OFFSET);
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameActivityMain_nativeOnStop(JNIEnv* env, jobject thiz) {
|
||||
CallRealNative(env, thiz, REAL_GAMEACTIVITY_ONSTOP_OFFSET);
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameActivityMain_nativeOnOrientationChange(JNIEnv* env, jobject thiz, jint i) {
|
||||
CallRealNative(env, thiz, REAL_GAMEACTIVITY_ONORIENTATIONCHANGE_OFFSET, {(uint32_t)i});
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameActivityMain_nativeOnPhysicalKeyDown(JNIEnv* env, jobject thiz, jint keyCode, jint scanCode) {
|
||||
CallRealNative(env, thiz, REAL_GAMEACTIVITY_ONPHYSICALKEYDOWN_OFFSET, {(uint32_t)keyCode, (uint32_t)scanCode});
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameActivityMain_nativeOnPhysicalKeyUp(JNIEnv* env, jobject thiz, jint i, jint i2) {
|
||||
CallRealNative(env, thiz, REAL_GAMEACTIVITY_ONPHYSICALKEYUP_OFFSET, {(uint32_t)i, (uint32_t)i2});
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameActivityMain_nativeOnPhysicalKeyboardVisibilityChanged(JNIEnv*, jobject, jboolean) {
|
||||
Log("game_lifecycle_stubs: nativeOnPhysicalKeyboardVisibilityChanged() - no real libapp.so "
|
||||
"implementation exists (absent from .dynsym) - staying a no-op");
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameActivityMain_nativeOnPhysicalNavigationVisibilityChanged(JNIEnv* env, jobject thiz, jboolean z) {
|
||||
CallRealNative(env, thiz, REAL_GAMEACTIVITY_ONPHYSICALNAVIGATIONVISIBILITYCHANGED_OFFSET, {(uint32_t)z});
|
||||
}
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_ea_ironmonkey_GameActivityMain_nativeRestoreContext(JNIEnv* env, jobject thiz) {
|
||||
return (jboolean)CallRealNative(env, thiz, REAL_GAMEACTIVITY_RESTORECONTEXT_OFFSET);
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameActivityMain_nativeSurfaceChanged(JNIEnv* env, jobject thiz, jobject gl10, jint w, jint h) {
|
||||
CallRealNative(env, thiz, REAL_GAMEACTIVITY_SURFACECHANGED_OFFSET,
|
||||
{GuestHandleFor(gl10), (uint32_t)w, (uint32_t)h});
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameActivityMain_nativeSurfaceCreated(JNIEnv* env, jobject thiz, jobject gl10, jobject eglConfig) {
|
||||
CallRealNative(env, thiz, REAL_GAMEACTIVITY_SURFACECREATED_OFFSET,
|
||||
{GuestHandleFor(gl10), GuestHandleFor(eglConfig)});
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// ARM64-only-device prototype: EAIO/StorageDirectory/RunLoop/MogaController
|
||||
// natives, wired for real (2026-08-29) - see game_lifecycle_stubs.cpp's own
|
||||
// top comment for the mechanism. All confirmed present in libapp.so's own
|
||||
// .dynsym (real_native_offsets.h).
|
||||
#include <jni.h>
|
||||
#include "util/util.h"
|
||||
#include "real_native_call.h"
|
||||
#include "real_native_offsets.h"
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_EAIO_EAIO_StartupNativeImpl(JNIEnv* env, jobject thiz, jobject assetManager,
|
||||
jstring dataPath, jstring filesDirPath, jstring externalPath) {
|
||||
CallRealNative(env, thiz, REAL_EAIO_STARTUP_OFFSET,
|
||||
{GuestHandleFor(assetManager), GuestHandleFor(dataPath),
|
||||
GuestHandleFor(filesDirPath), GuestHandleFor(externalPath)});
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_EAIO_EAIO_Shutdown(JNIEnv* env, jclass clazz) {
|
||||
// Shutdown() is @JvmStatic (jclass receiver, not jobject) - the real
|
||||
// guest function itself doesn't care (it never dereferences `thiz` as a
|
||||
// real object here, just needs *some* consistent handle to pass), so
|
||||
// reuse CallRealNative's jobject-shaped helper with the class handle.
|
||||
CallRealNative(env, (jobject)clazz, REAL_EAIO_SHUTDOWN_OFFSET);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_EAMIO_StorageDirectory_StartupNativeImpl(JNIEnv* env, jobject thiz) {
|
||||
CallRealNative(env, thiz, REAL_STORAGEDIR_STARTUP_OFFSET);
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_EAMIO_StorageDirectory_ShutdownNativeImpl(JNIEnv* env, jobject thiz) {
|
||||
CallRealNative(env, thiz, REAL_STORAGEDIR_SHUTDOWN_OFFSET);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_RunLoop_nativeOnRunLoopTick(JNIEnv* env, jobject thiz) {
|
||||
CallRealNative(env, thiz, REAL_RUNLOOP_TICK_OFFSET);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_MogaController_nativeOnKeyEvent(JNIEnv* env, jobject thiz, jobject keyEvent) {
|
||||
CallRealNative(env, thiz, REAL_MOGA_ONKEYEVENT_OFFSET, {GuestHandleFor(keyEvent)});
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_MogaController_nativeOnMotionEvent(JNIEnv* env, jobject thiz, jobject motionEvent) {
|
||||
CallRealNative(env, thiz, REAL_MOGA_ONMOTIONEVENT_OFFSET, {GuestHandleFor(motionEvent)});
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_MogaController_nativeOnStateEvent(JNIEnv* env, jobject thiz, jobject stateEvent) {
|
||||
CallRealNative(env, thiz, REAL_MOGA_ONSTATEEVENT_OFFSET, {GuestHandleFor(stateEvent)});
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// ARM64-only-device prototype - FMOD (libfmodex.so, not loaded into the
|
||||
// emulator - no arm64-v8a build exists at all, see ARM64_TRANSLATION_LAYER.md)
|
||||
// stays stubbed. EA Nimble's lifecycle bridge and GameGLSurfaceView's touch
|
||||
// forwarders ARE inside libapp.so's own .dynsym and are wired for real
|
||||
// (2026-08-29) - see game_lifecycle_stubs.cpp's own top comment.
|
||||
// NimbleCppComponentRegistrar$NimbleCppComponent's 6 methods and
|
||||
// BaseNativeCallback's 2 are NOT in libapp.so's .dynsym (confirmed, not
|
||||
// just unresolved by this port) and stay stubbed too.
|
||||
#include <jni.h>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include "util/util.h"
|
||||
#include "real_native_call.h"
|
||||
#include "real_native_offsets.h"
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_org_fmod_FMODAudioDevice_fmodGetInfo(JNIEnv*, jobject, jint) {
|
||||
return 0;
|
||||
}
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_org_fmod_FMODAudioDevice_fmodProcess(JNIEnv*, jobject, jobject) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define NIMBLE_COMPONENT_STUB(name) \
|
||||
extern "C" JNIEXPORT void JNICALL \
|
||||
Java_com_ea_nimble_bridge_NimbleCppComponentRegistrar_00024NimbleCppComponent_##name(JNIEnv*, jobject) { \
|
||||
Log("game_lifecycle_stubs_extra2: NimbleCppComponent." #name "() - no-op " \
|
||||
"(no real libapp.so implementation - absent from .dynsym)"); \
|
||||
}
|
||||
NIMBLE_COMPONENT_STUB(cleanup)
|
||||
NIMBLE_COMPONENT_STUB(restore)
|
||||
NIMBLE_COMPONENT_STUB(resume)
|
||||
NIMBLE_COMPONENT_STUB(setup)
|
||||
NIMBLE_COMPONENT_STUB(suspend)
|
||||
NIMBLE_COMPONENT_STUB(teardown)
|
||||
#undef NIMBLE_COMPONENT_STUB
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_nimble_bridge_BaseNativeCallback_nativeCallback(JNIEnv*, jclass, jint, jobjectArray) {
|
||||
Log("game_lifecycle_stubs_extra2: BaseNativeCallback.nativeCallback() - no-op");
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_nimble_bridge_BaseNativeCallback_nativeFinalize(JNIEnv*, jclass, jint) {}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_nimble_bridge_NimbleCppApplicationLifeCycle_onApplicationLaunch(JNIEnv* env, jobject thiz, jobject map) {
|
||||
CallRealNative(env, thiz, REAL_NIMBLE_ON_APP_LAUNCH_OFFSET, {GuestHandleFor(map)});
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_nimble_bridge_NimbleCppApplicationLifeCycle_onApplicationQuit(JNIEnv* env, jobject thiz) {
|
||||
CallRealNative(env, thiz, REAL_NIMBLE_ON_APP_QUIT_OFFSET);
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_nimble_bridge_NimbleCppApplicationLifeCycle_onApplicationResume(JNIEnv* env, jobject thiz, jobject map) {
|
||||
CallRealNative(env, thiz, REAL_NIMBLE_ON_APP_RESUME_OFFSET, {GuestHandleFor(map)});
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_nimble_bridge_NimbleCppApplicationLifeCycle_onApplicationSuspend(JNIEnv* env, jobject thiz) {
|
||||
CallRealNative(env, thiz, REAL_NIMBLE_ON_APP_SUSPEND_OFFSET);
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_nimble_bridge_NimbleCppApplicationLifeCycle_onUpdateLaunchMethod(JNIEnv* env, jobject thiz, jobject map) {
|
||||
CallRealNative(env, thiz, REAL_NIMBLE_ON_UPDATE_LAUNCH_METHOD_OFFSET, {GuestHandleFor(map)});
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameGLSurfaceView_nativeTouchPadEvent(JNIEnv* env, jobject thiz, jint i, jint i2, jfloat f, jfloat f2) {
|
||||
uint32_t fBits, f2Bits;
|
||||
memcpy(&fBits, &f, 4);
|
||||
memcpy(&f2Bits, &f2, 4);
|
||||
CallRealNative(env, thiz, REAL_GLSURFACEVIEW_TOUCHPAD_OFFSET, {(uint32_t)i, (uint32_t)i2, fBits, f2Bits});
|
||||
}
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_ea_ironmonkey_GameGLSurfaceView_nativeTouchScreenEvent(JNIEnv* env, jobject thiz, jint i, jint i2, jfloat f, jfloat f2) {
|
||||
uint32_t fBits, f2Bits;
|
||||
memcpy(&fBits, &f, 4);
|
||||
memcpy(&f2Bits, &f2, 4);
|
||||
CallRealNative(env, thiz, REAL_GLSURFACEVIEW_TOUCHSCREEN_OFFSET, {(uint32_t)i, (uint32_t)i2, fBits, f2Bits});
|
||||
}
|
||||
+357
-167
@@ -17,25 +17,313 @@
|
||||
#include "util/armhook.h"
|
||||
#include "util/armhooks.h"
|
||||
#include "game_events.h"
|
||||
#include "lan_event_injection.h"
|
||||
#include "emu/guest_engine.h"
|
||||
#include "emu/ostream_repro_test.h"
|
||||
#include "emu/guest_trace.h"
|
||||
#include "emu/tcg_bench.h"
|
||||
#include "emu/guest_fn.h"
|
||||
#include "emu/import_shims.h"
|
||||
#include "emu/pthread_shim.h"
|
||||
#include "emu/jni_shim.h"
|
||||
#include "emu/gles_shim.h"
|
||||
#include "emu/libc_shims.h"
|
||||
#include "emu/rtti_shims.h"
|
||||
#include "emu/fmod_shims.h"
|
||||
#include "emu/profiler.h"
|
||||
#include "real_native_offsets.h"
|
||||
// ---- ARM64-only-device prototype (2026-08-19): the LAN event-injection
|
||||
// subsystem (lan_event_injection.h + its own nested car_selection.h/
|
||||
// mod_slot_tracking.h/crash_workarounds.h includes) is DELIBERATELY NOT
|
||||
// wired into this emulated build - see ARM64_TRANSLATION_LAYER.md and this
|
||||
// session's own notes. Short version: every function pointer it resolves
|
||||
// (GetOutputNode, ResolveHandle, HashInsert, ...) now needs to route
|
||||
// through GuestFn/CallGuestFunction instead of a raw C call, which is a
|
||||
// small, mechanical change - but several of its call sites pass a pointer
|
||||
// to a LOCAL HOST STACK VARIABLE as an "out parameter" the guest function
|
||||
// writes into (e.g. ResolveHandle(&res, ctx, &key), HashInsert(&insertResult,
|
||||
// ...)) - Unicorn's guest code can only read/write memory inside the mapped
|
||||
// guest region, not arbitrary host process memory, so each of those call
|
||||
// sites needs its own guest-scratch-buffer marshaling (copy in, call,
|
||||
// copy out), individually, by hand. That's real, bounded work, but doing
|
||||
// it blind - with no device or even a desktop harness available for THIS
|
||||
// subsystem's own live-tuned wall-clock timing assumptions - risked
|
||||
// shipping quietly-wrong behavior across ~30 call sites with zero way to
|
||||
// catch a mistake. Left as source (untouched) for a follow-up session with
|
||||
// real testing available, rather than force a rushed port now. The two
|
||||
// self-contained hooks below (BuildTrackScenePath, MapScreenCtor) have no
|
||||
// out-parameter calls at all, so they ARE fully ported and are the real
|
||||
// end-to-end proof this mechanism works.
|
||||
// #include "lan_event_injection.h"
|
||||
|
||||
void* libapp_base = NULL;
|
||||
|
||||
static int find_lib_callback(struct dl_phdr_info* info, size_t size, void* data) {
|
||||
if (strstr(info->dlpi_name, "libapp.so")) {
|
||||
libapp_base = (void*)info->dlpi_addr;
|
||||
LOGD("Found libapp.so at base: 0x%08X", (uintptr_t)libapp_base);
|
||||
return 1; // Останавливаем перебор
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool get_libapp_base() {
|
||||
dl_iterate_phdr(find_lib_callback, NULL);
|
||||
if (!libapp_base) {
|
||||
Log("libapp.so not found in memory!");
|
||||
// Loads libapp.so through the embedded ARM32 CPU-emulation core instead of
|
||||
// finding it via dl_iterate_phdr - there is no real dlopen'd libapp.so to
|
||||
// find in this build; see ARM64_TRANSLATION_LAYER.md. `path` is a real
|
||||
// filesystem path Kotlin extracts the bundled asset to before calling this
|
||||
// (see MultiplayerCore.loadEmulatedLibapp / GameActivityMain.kt) since
|
||||
// JNI_OnLoad itself has no Context/AssetManager access.
|
||||
bool LoadEmulatedLibapp(const char* path, JNIEnv* env, jobject thiz) {
|
||||
// One-shot, throwaway benchmark - see tcg_bench.h's own comment. Remove
|
||||
// once the "is Unicorn/TCG itself the bottleneck" question is answered.
|
||||
RunTcgBenchmark();
|
||||
GuestEngine& engine = GuestEngine::Instance();
|
||||
// EnableProfiling() (see profiler.h) used to be called unconditionally
|
||||
// here for the "why did onCreate take 115s" investigation
|
||||
// (ARM64_TRANSLATION_LAYER.md, 2026-09-01). Confirmed live (2026-09-05,
|
||||
// "100x native" investigation) that this - and the two other
|
||||
// UC_HOOK_BLOCK diagnostics it gates in guest_engine.cpp
|
||||
// (TraceRingHookCb, LiveTraceRingHookCb) - fire on literally every
|
||||
// basic block executed anywhere in the guest binary, for the whole run:
|
||||
// a real, avoidable per-block tax, independent of and on top of the
|
||||
// separate uc_emu_stop()-per-shim-call fix. Left disabled by default;
|
||||
// uncomment when actually debugging a fault, stall, or hot-path
|
||||
// question that needs the block profiler/trace ring.
|
||||
// TEMPORARILY ENABLED 2026-09-19 for ONE capture (task #42). Three
|
||||
// separate load-time theories - log volume, per-draw glGetIntegerv, and
|
||||
// emulated zlib - each turned out to be real but not dominant: the zlib
|
||||
// interception demonstrably moved 156MB of decompression off the
|
||||
// emulator and the load barely moved. Stop theorising, measure.
|
||||
// MUST be commented out again after the capture - this is a
|
||||
// UC_HOOK_BLOCK over the whole image and leaving it on has already
|
||||
// caused a user-visible regression twice, as the comment above records.
|
||||
// Capture done 2026-09-19, profiler switched back off (see the warning
|
||||
// above - a UC_HOOK_BLOCK over the whole image must never be left on).
|
||||
// What it found, over 177,881 samples across 6,477 distinct blocks:
|
||||
// crc32 (sub_65F6C8) 17.7% -> now served by host zlib
|
||||
// name lookup (sub_4F3704) 18.8% -> linear strcmp scan, task #42
|
||||
// glClear's caller (sub_567BD4) 3.8%
|
||||
// i.e. no single dominant hotspot beyond those two; the rest is a long
|
||||
// tail of ordinary guest code, which is why the three earlier
|
||||
// "obvious" load-time theories each moved the needle so little.
|
||||
// EnableProfiling();
|
||||
StartProfileDumpThread();
|
||||
// 2026-09-06: full block+JNI-call trace for the "where does the
|
||||
// emulated engine's execution first diverge from A9's" investigation
|
||||
// (see guest_trace.h and ARM64_TRANSLATION_LAYER.md). Left ENABLED
|
||||
// unconditionally here for one capture session and caused a real,
|
||||
// user-noticed performance regression (clock_gettime()+gettid() on
|
||||
// every single executed block, on top of the already-fixed
|
||||
// uc_emu_stop() cost) - the exact same "always-on UC_HOOK_BLOCK
|
||||
// diagnostic" mistake EnableProfiling()'s own comment above already
|
||||
// documents and fixed once. Disabled by default now, like
|
||||
// EnableProfiling() - uncomment only while actively capturing a new
|
||||
// guest/JNI trace, never leave it on for an ordinary test run.
|
||||
// EnableFullGuestTrace();
|
||||
// StartGuestTraceDumpThread();
|
||||
// See gles_shim.h's own comment - "nothing renders past the splash"
|
||||
// investigation.
|
||||
StartGlesCounterDumpThread();
|
||||
// See guest_engine.h's own comment - periodic live instruction trace for
|
||||
// the same investigation, once RegisterNatives was ruled out.
|
||||
StartLiveTraceDumpThread();
|
||||
// See jni_shim.h's own comment: FindClass only sees app classes when
|
||||
// called from the thread that loaded the native library - cache the
|
||||
// real ClassLoader now (main thread, from a real app object) so later
|
||||
// FindClass calls from other threads (e.g. the real engine's own
|
||||
// GLThread) have a working fallback.
|
||||
JniShim::Instance().CacheClassLoader(env, thiz);
|
||||
RegisterCoreImportShims(engine);
|
||||
// Must run BEFORE RegisterPthreadImportShims - libc_shims.cpp registers
|
||||
// a placeholder "pthread_join" purely so the symbol is never silently
|
||||
// unresolved; pthread_shim.cpp's REAL join implementation needs to
|
||||
// register after it and win (RegisterImportShim is last-registration-
|
||||
// wins, see guest_engine.cpp).
|
||||
RegisterLibcImportShims(engine);
|
||||
RegisterPthreadImportShims(engine);
|
||||
RegisterGlesImportShims(engine);
|
||||
RegisterRttiImportShims(engine);
|
||||
RegisterFmodImportShims(engine);
|
||||
// Must be registered BEFORE LoadImage() - SetupRttiDataSymbols needs to
|
||||
// run after MapSegments (for AllocPermanent) but strictly before
|
||||
// ProcessRelocations resolves any GOT slot referencing these typeinfo
|
||||
// symbols; see GuestEngine::RegisterDataSymbolSetup's own comment for
|
||||
// why this is a callback rather than a direct call here.
|
||||
engine.RegisterDataSymbolSetup(&SetupRttiDataSymbols);
|
||||
engine.RegisterDataSymbolSetup(&SetupLibcDataSymbols);
|
||||
if (!engine.LoadImage(path)) {
|
||||
Log("LoadEmulatedLibapp: GuestEngine::LoadImage(%s) failed", path);
|
||||
return false;
|
||||
}
|
||||
// Every existing "(uintptr_t)libapp_base + OFFSET" expression across
|
||||
// this codebase (car_selection.h, crash_workarounds.h, and the two
|
||||
// hooks below) now resolves to a real host pointer into the emulator's
|
||||
// own guest-backing memory, unchanged - see GuestEngine's class comment
|
||||
// for why this identity mapping is possible.
|
||||
libapp_base = engine.image_host_base();
|
||||
|
||||
// libapp.so's own real JNI_OnLoad (found via .dynsym, same as every
|
||||
// other offset in real_native_offsets.h) - statically-linked engines
|
||||
// commonly do extra runtime registration here beyond what the ELF's own
|
||||
// .init_array (run by GuestEngine::ProcessRelocations, right after
|
||||
// relocations complete - see its own comment) covers. Calling it needs
|
||||
// a guest-visible JavaVM* (see jni_shim.h's own comment).
|
||||
JniShim::Instance().SetRealEnv(env);
|
||||
GuestAddr guestVm = JniShim::Instance().BuildGuestJavaVM(engine);
|
||||
uint32_t jniOnLoadResult = engine.CallGuestFunction(REAL_JNI_ONLOAD_OFFSET, guestVm, 0);
|
||||
Log("LoadEmulatedLibapp: real JNI_OnLoad returned 0x%x", jniOnLoadResult);
|
||||
|
||||
// Synthetic benchmark/unit-test calls (RunTcgBenchmarkInRealContext,
|
||||
// RunCalleeSavedRegisterTest) removed 2026-09-16 - their questions are
|
||||
// answered (see ARM64_TRANSLATION_LAYER.md): TCG itself isn't the
|
||||
// bottleneck, and the import-stub dispatch path preserves callee-saved
|
||||
// registers.
|
||||
//
|
||||
// RunReentrantCallRegisterTest call removed again 2026-09-16 (same day,
|
||||
// second removal) after confirming PASS on-device: once
|
||||
// GuestEngine::CreateConfiguredEngine/GetOrCreateNestedEngine
|
||||
// (guest_engine.h/.cpp) gave depth>0 CallGuestFunction calls their own
|
||||
// uc_engine* instead of re-entering the depth-0 one mid-uc_emu_start(),
|
||||
// this test completed cleanly (stub_hit=yes, r4-r7 all OK) instead of
|
||||
// hanging forever - and the app kept running afterward, with real
|
||||
// reentrant calls (depth=1, even depth=2) happening naturally on other
|
||||
// threads with no hang. Test function stays in tcg_bench.h/.cpp for
|
||||
// reuse if this area is ever suspected again.
|
||||
|
||||
// 2026-09-05/06 (ARM64_TRANSLATION_LAYER.md "PERFORMANCE tier"
|
||||
// investigation): sub_547B40 computes a device "performance tier" from
|
||||
// RAM/a Java-side getPerformanceScore() heuristic/GPU-renderer-string
|
||||
// and caches it in these two globals (byte_ADFD0C = "already computed",
|
||||
// dword_ADFD10 = the value). Confirmed live via a native32/armeabi-v7a
|
||||
// reference build on the Galaxy A9 (real hardware, no emulation): real
|
||||
// devices settle on "High" (tier 23), reached via a SECOND call once the
|
||||
// GL renderer string is known (the first, GPU-string-less call takes an
|
||||
// early-return path that doesn't mark the cache as final, matching the
|
||||
// observed "Tier = Higest" then "Tier = High" sequence in the real
|
||||
// device's own log). Pre-seeding 23 here matches that real behavior -
|
||||
// worth doing regardless, but NOT by itself sufficient to avoid the
|
||||
// crash below (confirmed live: forcing 23 hits the exact same crash as
|
||||
// the unforced/default path, just via a different switch case in
|
||||
// sub_3A4E5C - the real bug is the dword_ADCAA0 issue documented at
|
||||
// this function's next fix, not which tier gets chosen). Placed AFTER
|
||||
// JNI_OnLoad/BuildGuestJavaVM (moved 2026-09-06 - see this function's
|
||||
// own next comment for why ordering here matters).
|
||||
{
|
||||
constexpr GuestAddr kPerfTierCachedFlagAddr = 0xADFD0Cu; // byte_ADFD0C
|
||||
constexpr GuestAddr kPerfTierCachedValueAddr = 0xADFD10u; // dword_ADFD10
|
||||
constexpr uint32_t kHighTier = 23; // sub_547B40's own tier constant for "High" - confirmed on real hardware
|
||||
uint32_t tierValue = kHighTier;
|
||||
uint8_t alreadyCached = 1;
|
||||
memcpy(engine.G2H(kPerfTierCachedValueAddr), &tierValue, sizeof(tierValue));
|
||||
memcpy(engine.G2H(kPerfTierCachedFlagAddr), &alreadyCached, sizeof(alreadyCached));
|
||||
Log("LoadEmulatedLibapp: pre-seeded sub_547B40's performance-tier cache to High (23) - "
|
||||
"see this call site's own comment");
|
||||
}
|
||||
// 2026-09-06 (same investigation, real root cause): dword_ADCAA0 is
|
||||
// libapp.so's own global "debug log stream" object (used from 200+
|
||||
// call sites across the whole binary, e.g. every "Foo = bar" line seen
|
||||
// in logcat under tag "info"/"trace"). Its C++ constructor DOES run
|
||||
// (via .init_array - confirmed live: dword_ADCAA0's vtable pointer and
|
||||
// "good" flag at +76 are set up correctly), but the embedded streambuf-
|
||||
// shaped sub-object's OWN internal pointer (read by sub_3EA194 as "v17",
|
||||
// then called through via vtable+48 while converting a wide string to
|
||||
// UTF-8 for logging) is left null - and nothing in this engine ever
|
||||
// populates it afterward, unlike on real hardware (confirmed via the
|
||||
// same Galaxy A9 native32 reference build: the identical "PERFORMANCE -
|
||||
// level = 3" log line - same function, same wide-string-conversion path
|
||||
// - completes successfully there and the game continues straight into
|
||||
// SoundManager init). Since real devices reach this through machinery
|
||||
// this project doesn't emulate (whatever real ART/libc++ construction
|
||||
// path finishes attaching this sub-object) rather than through anything
|
||||
// under our control, the pragmatic fix - matching this file's existing
|
||||
// "pre-seed a real, reasonable state instead of tracing the original
|
||||
// construction path to its end" precedent for the tier cache above - is
|
||||
// to give that field a genuine, safe, callable vtable whose slots all
|
||||
// just return 0 (same generic pattern rtti_shims.cpp's ctype<char>/
|
||||
// num_put<char> facets already use for library objects this project
|
||||
// doesn't fully reimplement), instead of leaving it null. Placed AFTER
|
||||
// BuildGuestJavaVM/BuildGuestJNIEnv's own AllocPermanent/AllocCodeStub
|
||||
// calls (moved 2026-09-06): confirmed live that placing this block
|
||||
// BEFORE them let its allocations shift the control/misc-stub arena
|
||||
// layout under the VM/JNIEnv function tables built later, corrupting a
|
||||
// JavaVM slot (AttachCurrentThread) that a background pthread_create'd
|
||||
// thread then jumped through - moving this fix to run strictly after
|
||||
// every other subsystem's own one-time setup calls avoids stepping on
|
||||
// arena layout anything else still needs to allocate.
|
||||
{
|
||||
// sub_3EA194 (called with a1 = dword_ADCAA0+224, the embedded
|
||||
// streambuf sub-object) computes its crashing pointer as:
|
||||
// innerVtable = *(a1) // the streambuf's OWN vtable ptr
|
||||
// off = *(innerVtable - 12) // real Itanium offset-to-top-style value baked into that vtable
|
||||
// v17 = *(a1 + off + 24) // THIS is the null field - not a fixed "+248" offset
|
||||
// Confirmed live this session that assuming off==0 (a naive
|
||||
// "224+24=248" guess) was WRONG - that write didn't reach the real
|
||||
// v17 storage location and the crash persisted identically. Read
|
||||
// `off` from the real, already-constructed vtable instead of
|
||||
// assuming it, so the fix lands on the actual field regardless of
|
||||
// this class's real (compiler-chosen) layout.
|
||||
constexpr GuestAddr kAdcaa0Addr = 0xADCAA0u;
|
||||
constexpr GuestAddr kStreamBufAddr = kAdcaa0Addr + 224u;
|
||||
uint32_t innerVtable = 0;
|
||||
memcpy(&innerVtable, engine.G2H(kStreamBufAddr), 4);
|
||||
uint32_t off = 0;
|
||||
if (innerVtable >= 12) memcpy(&off, engine.G2H(innerVtable - 12), 4);
|
||||
GuestAddr v17Addr = kStreamBufAddr + off + 24u;
|
||||
Log("LoadEmulatedLibapp: dword_ADCAA0 streambuf vtable=0x%x off=0x%x -> v17 storage at 0x%x "
|
||||
"(current value 0x%x)",
|
||||
innerVtable, off, v17Addr, *(uint32_t*)engine.G2H(v17Addr));
|
||||
|
||||
constexpr int kNoOpVtableSlots = 16; // generous - the confirmed-needed slot is #12 (byte offset 48)
|
||||
GuestAddr vtable = engine.AllocPermanent((uint32_t)kNoOpVtableSlots * 4);
|
||||
for (int i = 0; i < kNoOpVtableSlots; i++) {
|
||||
GuestAddr stub = engine.AllocCodeStub(
|
||||
[](uc_engine* uc, uint64_t, uint32_t, void*) {
|
||||
uint32_t zero = 0;
|
||||
uc_reg_write(uc, UC_ARM_REG_R0, &zero);
|
||||
},
|
||||
nullptr);
|
||||
if (vtable && stub) memcpy(engine.G2H(vtable + (uint32_t)i * 4), &stub, 4);
|
||||
}
|
||||
GuestAddr obj = engine.AllocPermanent(4);
|
||||
if (obj && vtable) memcpy(engine.G2H(obj), &vtable, 4);
|
||||
if (obj) {
|
||||
memcpy(engine.G2H(v17Addr), &obj, 4);
|
||||
Log("LoadEmulatedLibapp: pre-seeded dword_ADCAA0's real v17 field (0x%x) with a safe "
|
||||
"no-op vtable object (0x%x) - see this call site's own comment", v17Addr, obj);
|
||||
} else {
|
||||
Log("LoadEmulatedLibapp: failed to allocate the dword_ADCAA0 v17 no-op object");
|
||||
}
|
||||
}
|
||||
|
||||
// Temporarily wired in (2026-09-17, ARM64_TRANSLATION_LAYER.md - the
|
||||
// 0x3d3d3d3d heap-overflow chase). Tests whether "long first write
|
||||
// forcing SSO->heap transition, then a real nested function call
|
||||
// writing more into the same stream" alone reproduces the corruption in
|
||||
// total isolation from the real game. Remove once this question is
|
||||
// answered, same discipline as every other one-shot test call in this
|
||||
// function.
|
||||
RunOstreamAssemblyNestedReproTest(engine);
|
||||
|
||||
// RunOstreamAssemblyReproTest call removed 2026-09-16 - its question is
|
||||
// answered (see ARM64_TRANSLATION_LAYER.md's "isolated
|
||||
// std::ostringstream repro" entry): a real, standalone armeabi-v7a
|
||||
// artifact (ostream_repro/, GuestEngine::LoadSecondaryImage) exercising
|
||||
// the exact same write-then-extract std::ostringstream pattern as the
|
||||
// real game's sub_4702D8/sub_27160C came back PASS, twice, with zero
|
||||
// unresolved-import noise in either run - extracted content matched
|
||||
// exactly what was written (len=49, retVal=49). This rules OUT a
|
||||
// general ostringstream/basic_stringbuf<char>::str()-extraction bug in
|
||||
// GuestEngine itself as the cause of the real game's empty-shader-
|
||||
// source symptom; whatever's actually wrong is specific to libapp.so's
|
||||
// own state/control flow reaching sub_4702D8, not this engine's
|
||||
// translation of the C++ runtime mechanism in general. Same "remove
|
||||
// spent diagnostics once their question is answered" discipline as the
|
||||
// RunCalleeSavedRegisterTest/RunReentrantCallRegisterTest removal note
|
||||
// above - the infrastructure (GuestEngine::LoadSecondaryImage,
|
||||
// emu/ostream_repro_test.{h,cpp}, ostream_repro/) stays in the tree,
|
||||
// only this one-shot call site is gone.
|
||||
|
||||
// RunStackArgMarshalingTest/RunSequentialCallStateLeakTest calls
|
||||
// removed 2026-09-16 - both questions answered PASS (see
|
||||
// ARM64_TRANSLATION_LAYER.md): argCount>4 stack-marshaled arguments
|
||||
// arrive correctly (all 8 of 8 args, register- and stack-passed alike),
|
||||
// and three sequential (non-reentrant) calls through the same
|
||||
// AllocCodeStub-dispatched stub show zero cross-call state leakage.
|
||||
// Same "remove spent diagnostics once their question is answered"
|
||||
// discipline as every other synthetic-test removal note in this
|
||||
// function - the test functions themselves stay in tcg_bench.h/.cpp
|
||||
// for reuse if this area is ever suspected again.
|
||||
|
||||
return true;
|
||||
}
|
||||
int (*sub_4087CC)() = nullptr;
|
||||
@@ -82,8 +370,7 @@ using namespace std;
|
||||
// position-independent, safe to relocate into the trampoline as-is).
|
||||
#define BUILDTRACKSCENEPATH_OFFSET 0x2a8424
|
||||
|
||||
typedef int (*BuildTrackScenePathFn)(void* raceLoaderTask);
|
||||
static BuildTrackScenePathFn orig_BuildTrackScenePath = nullptr;
|
||||
static GuestFn<int, void*> orig_BuildTrackScenePath;
|
||||
|
||||
// Deliberately different from any real event's track, so a successful
|
||||
// override is visually unmistakable. region3/colorado was tried first and
|
||||
@@ -135,43 +422,18 @@ int Hook_BuildTrackScenePath(void* a1) {
|
||||
return orig_BuildTrackScenePath(a1);
|
||||
}
|
||||
|
||||
// Ported onto GuestEngine (2026-08-19): same target offset, same
|
||||
// precondition (position-independent 2-instruction prologue, already
|
||||
// verified live-byte-matched against the real libapp.so this session - see
|
||||
// scratchpad/spike_load.py), same trampoline TECHNIQUE (verbatim copy of
|
||||
// the displaced instructions + jump back to target+8) - it now just builds
|
||||
// that trampoline in Unicorn-backed guest memory and is invoked via
|
||||
// CallGuestFunction instead of live byte-patching a real dlopen'd library.
|
||||
// See guest_engine.h/guest_fn.h for the mechanism.
|
||||
static bool InstallBuildTrackScenePathHook() {
|
||||
uintptr_t target = (uintptr_t)libapp_base + BUILDTRACKSCENEPATH_OFFSET;
|
||||
uint32_t* target32 = (uint32_t*)target;
|
||||
|
||||
void* tramp = mmap(nullptr, (size_t)getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (tramp == MAP_FAILED) {
|
||||
Log("BuildTrackScenePath hook: mmap trampoline failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t* tramp32 = (uint32_t*)tramp;
|
||||
// Relocate the 2 displaced original ARM instructions verbatim (both
|
||||
// confirmed position-independent: plain PUSH and ADD, no PC-relative
|
||||
// addressing), then jump back into the function body past them.
|
||||
tramp32[0] = target32[0];
|
||||
tramp32[1] = target32[1];
|
||||
tramp32[2] = 0xE51FF004; // LDR PC, [PC, #-4]
|
||||
tramp32[3] = (uint32_t)(target + 8);
|
||||
orig_BuildTrackScenePath = (BuildTrackScenePathFn)tramp;
|
||||
|
||||
uintptr_t page = target & ~((uintptr_t)getpagesize() - 1);
|
||||
if (mprotect((void*)page, (size_t)getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC) != 0) {
|
||||
Log("BuildTrackScenePath hook: mprotect target failed: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
target32[0] = 0xE51FF004; // LDR PC, [PC, #-4]
|
||||
// &Hook_BuildTrackScenePath has bit0 set (Thumb-compiled mpcore code),
|
||||
// triggering the ARM->Thumb interworking switch on load into PC.
|
||||
target32[1] = (uint32_t)(uintptr_t)&Hook_BuildTrackScenePath;
|
||||
|
||||
__builtin___clear_cache((char*)target, (char*)(target + 8));
|
||||
__builtin___clear_cache((char*)tramp, (char*)tramp + 16);
|
||||
|
||||
Log("Installed RaceLoaderTask_BuildTrackScenePath hook at %p, trampoline=%p", (void*)target, tramp);
|
||||
return true;
|
||||
orig_BuildTrackScenePath = InstallTrampolineHook(
|
||||
BUILDTRACKSCENEPATH_OFFSET, &Hook_BuildTrackScenePath, "RaceLoaderTask_BuildTrackScenePath hook");
|
||||
return (bool)orig_BuildTrackScenePath;
|
||||
}
|
||||
|
||||
// ---- MapScreen constructor trace hook (temporary, RE discovery only) ----
|
||||
@@ -184,8 +446,7 @@ static bool InstallBuildTrackScenePathHook() {
|
||||
// (found earlier, offsets +0x44.."+0x50") into real screen pixels.
|
||||
#define MAPSCREEN_CTOR_OFFSET 0x1781BC
|
||||
|
||||
typedef void* (*MapScreenCtorFn)(void* a1);
|
||||
static MapScreenCtorFn orig_MapScreenCtor = nullptr;
|
||||
static GuestFn<void*, void*> orig_MapScreenCtor;
|
||||
void* g_mapScreenInstance = nullptr;
|
||||
|
||||
void* Hook_MapScreenCtor(void* a1) {
|
||||
@@ -195,41 +456,12 @@ void* Hook_MapScreenCtor(void* a1) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Ported onto GuestEngine (2026-08-19) - see InstallBuildTrackScenePathHook's
|
||||
// own comment just above for the mechanism; identical technique.
|
||||
static bool InstallMapScreenCtorTraceHook() {
|
||||
uintptr_t target = (uintptr_t)libapp_base + MAPSCREEN_CTOR_OFFSET;
|
||||
uint32_t* target32 = (uint32_t*)target;
|
||||
|
||||
// Confirmed ARM-mode, position-independent prologue this session
|
||||
// (PUSH {R4-R11,LR}; ADD R11,SP,#0x1C), same trampoline pattern as the
|
||||
// other hooks in this file.
|
||||
void* tramp = mmap(nullptr, (size_t)getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (tramp == MAP_FAILED) {
|
||||
Log("MapScreen ctor hook: mmap trampoline failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t* tramp32 = (uint32_t*)tramp;
|
||||
tramp32[0] = target32[0];
|
||||
tramp32[1] = target32[1];
|
||||
tramp32[2] = 0xE51FF004; // LDR PC, [PC, #-4]
|
||||
tramp32[3] = (uint32_t)(target + 8);
|
||||
orig_MapScreenCtor = (MapScreenCtorFn)tramp;
|
||||
|
||||
uintptr_t page = target & ~((uintptr_t)getpagesize() - 1);
|
||||
if (mprotect((void*)page, (size_t)getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC) != 0) {
|
||||
Log("MapScreen ctor hook: mprotect target failed: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
target32[0] = 0xE51FF004; // LDR PC, [PC, #-4]
|
||||
target32[1] = (uint32_t)(uintptr_t)&Hook_MapScreenCtor;
|
||||
|
||||
__builtin___clear_cache((char*)target, (char*)(target + 8));
|
||||
__builtin___clear_cache((char*)tramp, (char*)tramp + 16);
|
||||
|
||||
Log("Installed MapScreen ctor trace hook at %p, trampoline=%p", (void*)target, tramp);
|
||||
return true;
|
||||
orig_MapScreenCtor = InstallTrampolineHook(
|
||||
MAPSCREEN_CTOR_OFFSET, &Hook_MapScreenCtor, "MapScreen ctor trace hook");
|
||||
return (bool)orig_MapScreenCtor;
|
||||
}
|
||||
|
||||
// Flip to false to run the game completely unmodified (e.g. to capture a
|
||||
@@ -253,14 +485,14 @@ static bool InstallMapScreenCtorTraceHook() {
|
||||
// this PoC toggle should only be flipped on deliberately, for a specific
|
||||
// track-substitution test, not left on as the default running state.
|
||||
static constexpr bool kEnableTrackSubstitutionHook = false;
|
||||
static constexpr bool kEnableLanEventInjectionHook = true;
|
||||
static constexpr bool kEnableMapScreenCtorTraceHook = false; // TEMP: isolating a reproducible SIGSEGV, see PROGRESS.md
|
||||
// See ANALYSIS.md §6ff/§6gg: prevents a QA-only "Soak Test" auto-race feature
|
||||
// from eventually crashing the process on entries our injection adds to the
|
||||
// prefab cache. Independent of kEnableLanEventInjectionHook so it can be kept
|
||||
// on even if injection itself is toggled off for testing.
|
||||
static constexpr bool kEnableSoakTestDisableHook = true;
|
||||
|
||||
// JNI_OnLoad only sets up the (unaffected, host-side-only) GameEvents JNI
|
||||
// bridge now - loading libapp.so and installing hooks against it needs a
|
||||
// real filesystem path to the extracted asset (see LoadEmulatedLibapp's own
|
||||
// comment), which JNI_OnLoad has no reliable way to obtain on its own
|
||||
// (no Context/AssetManager access at this point). See
|
||||
// Java_..._MultiplayerCore_loadEmulatedLibapp below for where that now happens.
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
JNIEnv* env = nullptr;
|
||||
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_OK) {
|
||||
@@ -268,94 +500,52 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
} else {
|
||||
Log("JNI_OnLoad: GetEnv failed, GameEvents bridge not initialised");
|
||||
}
|
||||
|
||||
if (get_libapp_base()) {
|
||||
if (kEnableTrackSubstitutionHook) {
|
||||
InstallBuildTrackScenePathHook();
|
||||
}
|
||||
if (kEnableLanEventInjectionHook) {
|
||||
InstallMapTrackHandleEventHook();
|
||||
}
|
||||
if (kEnableSoakTestDisableHook) {
|
||||
InstallSoakTestDisableHook();
|
||||
}
|
||||
if (kEnableMapScreenCtorTraceHook) {
|
||||
InstallMapScreenCtorTraceHook();
|
||||
}
|
||||
InstallCopSoundsTickSkipHook();
|
||||
// cont.67 CONFIRMED ROOT CAUSE, permanently disabled: this hook made
|
||||
// GetComponentName (sub_240548) unconditionally return an empty-string
|
||||
// sentinel for EVERY call, not just the null-input crash case its own
|
||||
// original comment described (cont.29/30 - a narrow SIGSEGV while
|
||||
// triggering our synthetic LAN test event's own car-select flow).
|
||||
// Live-bisected this session against a real, user-reported regression:
|
||||
// real races completed and granted cash/SP correctly, but the
|
||||
// per-event "medal earned" persistent record (read by
|
||||
// MapTrack::AddEvent/sub_369AB0 via sub_77B70/sub_251188/sub_2515D0/
|
||||
// sub_250F34 and a reflective "medal" property - feeds the
|
||||
// street-completion-% badge on the map) was never written, on the
|
||||
// player's very first tutorial/career race. Decompiling this hook's
|
||||
// only relevant caller chain found sub_240294 does NOT just log
|
||||
// GetComponentName's result for a debug label as originally assumed -
|
||||
// it WRITES it into a named-cache-context object's own +8 field (the
|
||||
// same kind of cache-context, via sub_7566C, that the medal-lookup
|
||||
// chain also resolves through), and sub_240294 is itself called from
|
||||
// sub_17A99C, the same real map-event-processing function this
|
||||
// project's own FireEventOutput uses. With every hook EXCEPT this one
|
||||
// installed, medal recording worked correctly live (confirmed twice);
|
||||
// with this one also installed, it silently failed every time -
|
||||
// isolating it as the sole cause. Left declared/installable below in
|
||||
// case the original narrow crash needs revisiting, but must not ship
|
||||
// installed - it silently corrupts real save progression for every
|
||||
// player, not just the one synthetic test scenario it was written for.
|
||||
// InstallGetComponentNameSkipHook();
|
||||
InstallStrlenNullGuardHook();
|
||||
InstallInternStringDiagHook();
|
||||
InstallFatalLogCallerTraceHook();
|
||||
InstallResolveDisplayTextHook();
|
||||
InstallResolveDisplayTextWrapperDiagHook();
|
||||
InstallLayoutScreenCtorHook();
|
||||
InstallModSlotSelectedHook();
|
||||
InstallFireOutputDiagHook();
|
||||
// NOT installed (cont.43): live-tested and found to break touch
|
||||
// responsiveness on car_select once installed, for reasons not yet
|
||||
// understood (sub_16C660 itself runs fine every frame through the
|
||||
// hook per its own diagnostics - "returned 0" every ~16ms, no hang
|
||||
// - yet taps stop registering; reproduced 5/5 tries with the hook
|
||||
// installed vs 1/1 without). sub_16C660 is called at a much higher,
|
||||
// more global frequency (~60/sec, from app boot onward) than any
|
||||
// other function hooked in this project - too risky to keep
|
||||
// chasing blind. See lan_event_injection.h for the full writeup;
|
||||
// the FireOutput-level interception was widened instead (does not
|
||||
// need this hook).
|
||||
// InstallConfirmCarSelectionHook();
|
||||
InstallFlowNodeTickHook();
|
||||
}
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
// Called from Kotlin once it has extracted the bundled armeabi-v7a
|
||||
// libapp.so asset to a real file (see GameActivityMain.kt) - loads it
|
||||
// through the embedded ARM32 emulation core and installs whichever hooks
|
||||
// are enabled. Returns true on success. See ARM64_TRANSLATION_LAYER.md and
|
||||
// this file's own comment above the (currently excluded)
|
||||
// "#include lan_event_injection.h" line for what is and isn't wired up yet.
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_nfs_mod_mpcore_MultiplayerCore_loadEmulatedLibapp(JNIEnv* env, jobject thiz, jstring path) {
|
||||
const char* pathUtf8 = env->GetStringUTFChars(path, nullptr);
|
||||
bool ok = LoadEmulatedLibapp(pathUtf8, env, thiz);
|
||||
env->ReleaseStringUTFChars(path, pathUtf8);
|
||||
if (!ok) return JNI_FALSE;
|
||||
|
||||
if (kEnableTrackSubstitutionHook) {
|
||||
InstallBuildTrackScenePathHook();
|
||||
}
|
||||
if (kEnableMapScreenCtorTraceHook) {
|
||||
InstallMapScreenCtorTraceHook();
|
||||
}
|
||||
Log("loadEmulatedLibapp: libapp.so loaded into the emulation core, host base=%p", libapp_base);
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_nfs_mod_mpcore_MultiplayerCore_bumpBackTraceToLogcat(JNIEnv *env, jobject thiz) {
|
||||
//backtraceToLogcat();
|
||||
}
|
||||
|
||||
// cont.44: lets Kotlin (eventually a real lobby-overlay button, for now a
|
||||
// debug broadcast receiver - see GameActivityMain.kt) open car_select on
|
||||
// demand instead of only automatically at boot. See
|
||||
// TriggerOpenCarSelectOnDemand in lan_event_injection.h for the details.
|
||||
// cont.44/48's on-demand car_select triggers depend on the LAN
|
||||
// event-injection subsystem, which isn't wired into this emulated build yet
|
||||
// (see the comment above this file's excluded lan_event_injection.h
|
||||
// include). Kept as no-op JNI stubs, not removed outright, so
|
||||
// MultiplayerCore.kt's existing `external fun` declarations still link.
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_nfs_mod_mpcore_MultiplayerCore_triggerCarSelectTest(JNIEnv *env, jobject thiz) {
|
||||
TriggerOpenCarSelectOnDemand();
|
||||
Log("triggerCarSelectTest: not available yet in the ARM64 emulated-core build (see main.cpp)");
|
||||
}
|
||||
|
||||
// cont.48: experimental TRUE direct jump to car_select, bypassing
|
||||
// EventDetails entirely - see TriggerTrueDirectCarSelectJump in
|
||||
// lan_event_injection.h for the details and the real risk involved.
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_nfs_mod_mpcore_MultiplayerCore_triggerTrueDirectCarSelectJump(JNIEnv *env, jobject thiz) {
|
||||
TriggerTrueDirectCarSelectJump();
|
||||
Log("triggerTrueDirectCarSelectJump: not available yet in the ARM64 emulated-core build (see main.cpp)");
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
// Shared helper for the JNIEXPORT wrappers in game_lifecycle_stubs*.cpp:
|
||||
// calls one of libapp.so's own real native functions (real_native_offsets.h)
|
||||
// through the emulation core, marshaling `env`/`thiz` into the guest JNIEnv/
|
||||
// jobject handle scheme (emu/jni_shim.h) first.
|
||||
#include <jni.h>
|
||||
#include <vector>
|
||||
#include "emu/guest_engine.h"
|
||||
#include "emu/jni_shim.h"
|
||||
|
||||
inline uint32_t CallRealNative(JNIEnv* env, jobject thiz, GuestAddr offset,
|
||||
std::initializer_list<uint32_t> extraArgs = {}) {
|
||||
JniShim::Instance().SetRealEnv(env);
|
||||
GuestAddr guestEnv = JniShim::Instance().BuildGuestJNIEnv(GuestEngine::Instance());
|
||||
uint32_t guestThiz = JniShim::Instance().handles().Alloc(thiz);
|
||||
std::vector<uint32_t> args = {guestEnv, guestThiz};
|
||||
args.insert(args.end(), extraArgs);
|
||||
return GuestEngine::Instance().CallGuestFunction(offset, args.data(), (int)args.size());
|
||||
}
|
||||
|
||||
inline uint32_t GuestHandleFor(jobject obj) {
|
||||
return JniShim::Instance().handles().Alloc(obj);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
// Real guest addresses of libapp.so's own JNI-exported native functions -
|
||||
// found this session via a plain .dynsym dump (readelf/pyelftools), NOT IDA
|
||||
// RE: even though the file is stripped, JNI symbols stay in .dynsym so the
|
||||
// dynamic linker/dlsym can find them, which meant every one of these was
|
||||
// available for free. See ARM64_TRANSLATION_LAYER.md's "boot the game"
|
||||
// follow-up notes for context. All are ARM-mode entry points (standard for
|
||||
// this NDK/compiler combination, consistent with every other hooked offset
|
||||
// in this codebase already being ARM-mode).
|
||||
//
|
||||
// Verify with: readelf -sW native_lib/libapp.so | grep 'Java_\|JNI_OnLoad'
|
||||
|
||||
#define REAL_JNI_ONLOAD_OFFSET 0x54e124
|
||||
|
||||
#define REAL_EAIO_STARTUP_OFFSET 0x6bc7d4
|
||||
#define REAL_EAIO_SHUTDOWN_OFFSET 0x6bc804
|
||||
#define REAL_STORAGEDIR_STARTUP_OFFSET 0x764264
|
||||
#define REAL_STORAGEDIR_SHUTDOWN_OFFSET 0x764378
|
||||
#define REAL_EATHREAD_INIT_OFFSET 0x643328
|
||||
|
||||
#define REAL_NIMBLE_ON_APP_LAUNCH_OFFSET 0x96c781
|
||||
#define REAL_NIMBLE_ON_APP_QUIT_OFFSET 0x96c8c9
|
||||
#define REAL_NIMBLE_ON_APP_RESUME_OFFSET 0x96c855
|
||||
#define REAL_NIMBLE_ON_APP_SUSPEND_OFFSET 0x96c7f5
|
||||
#define REAL_NIMBLE_ON_UPDATE_LAUNCH_METHOD_OFFSET 0x96c925
|
||||
|
||||
#define REAL_GAMEACTIVITY_ONCREATE_OFFSET 0x54c6e0
|
||||
#define REAL_GAMEACTIVITY_ONDESTROY_OFFSET 0x54cb20
|
||||
#define REAL_GAMEACTIVITY_ONMUSICPLAYERSTATECHANGED_OFFSET 0x54cfc8
|
||||
#define REAL_GAMEACTIVITY_ONORIENTATIONCHANGE_OFFSET 0x54cfe4
|
||||
#define REAL_GAMEACTIVITY_ONPAUSE_OFFSET 0x54c904
|
||||
#define REAL_GAMEACTIVITY_ONPHYSICALKEYDOWN_OFFSET 0x54cb98
|
||||
#define REAL_GAMEACTIVITY_ONPHYSICALKEYUP_OFFSET 0x54cc88
|
||||
#define REAL_GAMEACTIVITY_ONPHYSICALNAVIGATIONVISIBILITYCHANGED_OFFSET 0x54cd84
|
||||
#define REAL_GAMEACTIVITY_ONRESTART_OFFSET 0x54c900
|
||||
#define REAL_GAMEACTIVITY_ONRESUME_OFFSET 0x54c920
|
||||
#define REAL_GAMEACTIVITY_ONSTART_OFFSET 0x54c8e4
|
||||
#define REAL_GAMEACTIVITY_ONSTOP_OFFSET 0x54c93c
|
||||
#define REAL_GAMEACTIVITY_RESTORECONTEXT_OFFSET 0x54cb70
|
||||
#define REAL_GAMEACTIVITY_SURFACECHANGED_OFFSET 0x54cb50
|
||||
#define REAL_GAMEACTIVITY_SURFACECREATED_OFFSET 0x54cb48
|
||||
|
||||
#define REAL_RUNLOOP_TICK_OFFSET 0x54e100
|
||||
|
||||
#define REAL_MOGA_ONKEYEVENT_OFFSET 0x265ea0
|
||||
#define REAL_MOGA_ONMOTIONEVENT_OFFSET 0x2667a0
|
||||
#define REAL_MOGA_ONSTATEEVENT_OFFSET 0x266cdc
|
||||
|
||||
#define REAL_GLSURFACEVIEW_TOUCHPAD_OFFSET 0x54d9d4
|
||||
#define REAL_GLSURFACEVIEW_TOUCHSCREEN_OFFSET 0x54d764
|
||||
@@ -0,0 +1,16 @@
|
||||
BasedOnStyle: LLVM
|
||||
IndentWidth: 4
|
||||
UseTab: Never
|
||||
BreakBeforeBraces: Linux
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
IndentCaseLabels: false
|
||||
ColumnLimit: 80
|
||||
SortIncludes: false
|
||||
AllowShortLambdasOnASingleLine: Inline
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
BreakStringLiterals: true
|
||||
PointerAlignment: Right
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "docs/Unicorn_Engine_Documentation"]
|
||||
path = docs/Unicorn_Engine_Documentation
|
||||
url = https://github.com/kabeor/Unicorn-Engine-Documentation
|
||||
@@ -0,0 +1,2 @@
|
||||
Nguyen Anh Quynh <aquynh -at- gmail.com>
|
||||
Dang Hoang Vu <dang.hvu -at- gmail.com>
|
||||
+1552
File diff suppressed because it is too large
Load Diff
+339
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -0,0 +1,481 @@
|
||||
GNU LIBRARY GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1991 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the library GPL. It is
|
||||
numbered 2 because it goes with version 2 of the ordinary GPL.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Library General Public License, applies to some
|
||||
specially designated Free Software Foundation software, and to any
|
||||
other libraries whose authors decide to use it. You can use it for
|
||||
your libraries, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if
|
||||
you distribute copies of the library, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link a program with the library, you must provide
|
||||
complete object files to the recipients so that they can relink them
|
||||
with the library, after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
Our method of protecting your rights has two steps: (1) copyright
|
||||
the library, and (2) offer you this license which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
Also, for each distributor's protection, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
library. If the library is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original
|
||||
version, so that any problems introduced by others will not reflect on
|
||||
the original authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that companies distributing free
|
||||
software will individually obtain patent licenses, thus in effect
|
||||
transforming the program into proprietary software. To prevent this,
|
||||
we have made it clear that any patent must be licensed for everyone's
|
||||
free use or not licensed at all.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the ordinary
|
||||
GNU General Public License, which was designed for utility programs. This
|
||||
license, the GNU Library General Public License, applies to certain
|
||||
designated libraries. This license is quite different from the ordinary
|
||||
one; be sure to read it in full, and don't assume that anything in it is
|
||||
the same as in the ordinary license.
|
||||
|
||||
The reason we have a separate public license for some libraries is that
|
||||
they blur the distinction we usually make between modifying or adding to a
|
||||
program and simply using it. Linking a program with a library, without
|
||||
changing the library, is in some sense simply using the library, and is
|
||||
analogous to running a utility program or application program. However, in
|
||||
a textual and legal sense, the linked executable is a combined work, a
|
||||
derivative of the original library, and the ordinary General Public License
|
||||
treats it as such.
|
||||
|
||||
Because of this blurred distinction, using the ordinary General
|
||||
Public License for libraries did not effectively promote software
|
||||
sharing, because most developers did not use the libraries. We
|
||||
concluded that weaker conditions might promote sharing better.
|
||||
|
||||
However, unrestricted linking of non-free programs would deprive the
|
||||
users of those programs of all benefit from the free status of the
|
||||
libraries themselves. This Library General Public License is intended to
|
||||
permit developers of non-free programs to use free libraries, while
|
||||
preserving your freedom as a user of such programs to change the free
|
||||
libraries that are incorporated in them. (We have not seen how to achieve
|
||||
this as regards changes in header files, but we have achieved it as regards
|
||||
changes in the actual functions of the Library.) The hope is that this
|
||||
will lead to faster development of free libraries.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, while the latter only
|
||||
works together with the library.
|
||||
|
||||
Note that it is possible for a library to be covered by the ordinary
|
||||
General Public License rather than by this special one.
|
||||
|
||||
GNU LIBRARY GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library which
|
||||
contains a notice placed by the copyright holder or other authorized
|
||||
party saying it may be distributed under the terms of this Library
|
||||
General Public License (also called "this License"). Each licensee is
|
||||
addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also compile or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
c) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
d) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the source code distributed need not include anything that is normally
|
||||
distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Library General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
@@ -0,0 +1,482 @@
|
||||
GNU LIBRARY GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the library GPL. It is
|
||||
numbered 2 because it goes with version 2 of the ordinary GPL.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Library General Public License, applies to some
|
||||
specially designated Free Software Foundation software, and to any
|
||||
other libraries whose authors decide to use it. You can use it for
|
||||
your libraries, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if
|
||||
you distribute copies of the library, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link a program with the library, you must provide
|
||||
complete object files to the recipients so that they can relink them
|
||||
with the library, after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
Our method of protecting your rights has two steps: (1) copyright
|
||||
the library, and (2) offer you this license which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
Also, for each distributor's protection, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
library. If the library is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original
|
||||
version, so that any problems introduced by others will not reflect on
|
||||
the original authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that companies distributing free
|
||||
software will individually obtain patent licenses, thus in effect
|
||||
transforming the program into proprietary software. To prevent this,
|
||||
we have made it clear that any patent must be licensed for everyone's
|
||||
free use or not licensed at all.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the ordinary
|
||||
GNU General Public License, which was designed for utility programs. This
|
||||
license, the GNU Library General Public License, applies to certain
|
||||
designated libraries. This license is quite different from the ordinary
|
||||
one; be sure to read it in full, and don't assume that anything in it is
|
||||
the same as in the ordinary license.
|
||||
|
||||
The reason we have a separate public license for some libraries is that
|
||||
they blur the distinction we usually make between modifying or adding to a
|
||||
program and simply using it. Linking a program with a library, without
|
||||
changing the library, is in some sense simply using the library, and is
|
||||
analogous to running a utility program or application program. However, in
|
||||
a textual and legal sense, the linked executable is a combined work, a
|
||||
derivative of the original library, and the ordinary General Public License
|
||||
treats it as such.
|
||||
|
||||
Because of this blurred distinction, using the ordinary General
|
||||
Public License for libraries did not effectively promote software
|
||||
sharing, because most developers did not use the libraries. We
|
||||
concluded that weaker conditions might promote sharing better.
|
||||
|
||||
However, unrestricted linking of non-free programs would deprive the
|
||||
users of those programs of all benefit from the free status of the
|
||||
libraries themselves. This Library General Public License is intended to
|
||||
permit developers of non-free programs to use free libraries, while
|
||||
preserving your freedom as a user of such programs to change the free
|
||||
libraries that are incorporated in them. (We have not seen how to achieve
|
||||
this as regards changes in header files, but we have achieved it as regards
|
||||
changes in the actual functions of the Library.) The hope is that this
|
||||
will lead to faster development of free libraries.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, while the latter only
|
||||
works together with the library.
|
||||
|
||||
Note that it is possible for a library to be covered by the ordinary
|
||||
General Public License rather than by this special one.
|
||||
|
||||
GNU LIBRARY GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library which
|
||||
contains a notice placed by the copyright holder or other authorized
|
||||
party saying it may be distributed under the terms of this Library
|
||||
General Public License (also called "this License"). Each licensee is
|
||||
addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also compile or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
c) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
d) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the source code distributed need not include anything that is normally
|
||||
distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Library General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the
|
||||
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
Boston, MA 02111-1307 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
@@ -0,0 +1,83 @@
|
||||
This file credits all the contributors of the Unicorn engine project.
|
||||
|
||||
|
||||
Key developers
|
||||
==============
|
||||
Nguyen Anh Quynh <aquynh -at- gmail.com>
|
||||
Dang Hoang Vu <dang.hvu -at- gmail.com>
|
||||
Huitao Chen (chenhuitao)
|
||||
Ziqiao Kong (lazymio)
|
||||
KaiJernLau (xwings)
|
||||
|
||||
|
||||
Beta testers (in no particular order)
|
||||
==============================
|
||||
Nguyen Tan Cong
|
||||
Loi Anh Tuan
|
||||
Edgar Barbosa
|
||||
Joxean Koret
|
||||
Chris Eagle
|
||||
Jay Little, Trail of Bits
|
||||
Jeong Wook Oh
|
||||
Luis Miras
|
||||
Yan Shoshitaishvili, Shellphish & UC Santa Barbara
|
||||
Erik Fischer
|
||||
Darel Griffin, NCC Group
|
||||
Anton Cherepanov
|
||||
Mohamed Saher (halsten)
|
||||
Tyler Colgan
|
||||
Jonathon Reinhart
|
||||
Blue Skeye
|
||||
Chris Maixner
|
||||
Sergi Alvarez, aka pancake (author of radare)
|
||||
Ryan Hileman
|
||||
Tim "diff" Strazzere
|
||||
WanderingGlitch of the Zero Day Initiative
|
||||
Sascha Schirra
|
||||
François Serman
|
||||
Sean Heelan
|
||||
Luke Burnett
|
||||
Parker Thompson
|
||||
Daniel Godas-Lopez
|
||||
Antonio "s4tan" Parata
|
||||
Corey Kallenberg
|
||||
Shift
|
||||
Gabriel Quadros
|
||||
Fabian Yamaguchi
|
||||
Ralf-Philipp Weinmann
|
||||
Mike Guidry
|
||||
Joshua "posixninja" Hill
|
||||
|
||||
|
||||
Contributors (in no particular order)
|
||||
=====================================
|
||||
(Please let us know if you want to have your name here)
|
||||
|
||||
Nguyen Tan Cong
|
||||
Loi Anh Tuan
|
||||
Shaun Wheelhouse: Homebrew package
|
||||
Kamil Rytarowski: Pkgsrc package
|
||||
Zak Escano: MSVC support.
|
||||
Chris Eagle: Java binding
|
||||
Ryan Hileman: Go binding
|
||||
Antonio Parata: .NET binding
|
||||
Jonathon Reinhart: C unit test
|
||||
Sascha Schirra: Ruby binding
|
||||
Adrian Herrera: Haskell binding
|
||||
practicalswift: Various cool bugs found by fuzzing
|
||||
farmdve: Memory leaking fix
|
||||
Andrew Dutcher: uc_context_{save, restore} API.
|
||||
Stephen Groat: improved CI setup.
|
||||
David Zimmer: VB6 binding.
|
||||
zhangwm: ARM & ARM64 big endian.
|
||||
Mohamed Osama: FreePascal/Delphi binding.
|
||||
Philippe Antoine (Catena cyber): fuzzing
|
||||
Huitao Chen (chenhuitao) & KaiJern Lau (xwings): Cmake support
|
||||
Huitao Chen (chenhuitao) & KaiJern Lau (xwings): Python3 support for building
|
||||
Kevin Foo (chfl4gs): Travis-CI migration
|
||||
Simon Gorchakov: PowerPC target
|
||||
Stuart Dootson (studoot): MSVC compatibility with PowerPC target support
|
||||
Ziqiao Kong (lazymio): uc_context_free() API and various bug fix & improvement.
|
||||
Sven Almgren (blindmatrix): bug fix
|
||||
Chenxu Wu (kabeor): Documentation
|
||||
Philipp Takacs: virtual tlb, memory snapshots
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
[workspace]
|
||||
members = ["bindings/rust/unicorn-engine"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
rust-version = "1.85.0"
|
||||
version = "2.1.5"
|
||||
authors = ["Ziqiao Kong <mio@lazym.io>", "Lukas Seidel", "Amaan Qureshi <amaanq12@gmail.com>"]
|
||||
keywords = ["unicorn", "cpu", "emulator", "bindings"]
|
||||
categories = ["api-bindings", "emulators", "no-std", "virtualization"]
|
||||
documentation = "https://github.com/unicorn-engine/unicorn/wiki"
|
||||
edition = "2024"
|
||||
license = "GPL-2.0"
|
||||
readme = "./bindings/rust/sys/README.md"
|
||||
repository = "https://github.com/unicorn-engine/unicorn"
|
||||
description = "Rust bindings for the Unicorn emulator with utility functions"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
cast_lossless = "allow"
|
||||
cast_possible_truncation = "allow"
|
||||
cast_possible_wrap = "allow"
|
||||
cast_sign_loss = "allow"
|
||||
missing_errors_doc = "allow"
|
||||
missing_panics_doc = "allow"
|
||||
similar_names = "allow"
|
||||
unreadable_literal = "allow"
|
||||
use_self = "allow"
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
nursery = { level = "warn", priority = -1 }
|
||||
cargo = { level = "warn", priority = -1 }
|
||||
|
||||
# Root package
|
||||
[package]
|
||||
name = "unicorn-engine-sys"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
keywords.workspace = true
|
||||
categories = [
|
||||
"api-bindings",
|
||||
"emulators",
|
||||
"external-ffi-bindings",
|
||||
"no-std",
|
||||
"virtualization",
|
||||
]
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme = "README.md"
|
||||
repository.workspace = true
|
||||
description.workspace = true
|
||||
links = "unicorn"
|
||||
# Cargo package --list
|
||||
exclude = [
|
||||
"/docs",
|
||||
"/bindings/dotnet",
|
||||
"/bindings/go",
|
||||
"/bindings/haskell",
|
||||
"/bindings/java",
|
||||
"/bindings/pascal",
|
||||
"/bindings/python",
|
||||
"/bindings/ruby",
|
||||
"/bindings/vb6",
|
||||
"/bindings/zig",
|
||||
"/samples",
|
||||
"/tests",
|
||||
]
|
||||
|
||||
[lib]
|
||||
path = "bindings/rust/sys/src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.71.1"
|
||||
cc = { version = "1.2.17" }
|
||||
cmake = { version = "0.1.54" }
|
||||
heck = "0.5.0"
|
||||
pkg-config = { version = "0.3.32" }
|
||||
|
||||
[features]
|
||||
default = ["arch_all"]
|
||||
dynamic_linkage = []
|
||||
arch_all = [
|
||||
"arch_x86",
|
||||
"arch_arm",
|
||||
"arch_aarch64",
|
||||
"arch_riscv",
|
||||
"arch_mips",
|
||||
"arch_sparc",
|
||||
"arch_m68k",
|
||||
"arch_ppc",
|
||||
"arch_s390x",
|
||||
"arch_tricore",
|
||||
]
|
||||
arch_x86 = []
|
||||
arch_arm = []
|
||||
arch_aarch64 = ["arch_arm"]
|
||||
arch_riscv = []
|
||||
arch_mips = []
|
||||
arch_sparc = []
|
||||
arch_m68k = []
|
||||
arch_ppc = []
|
||||
arch_s390x = []
|
||||
arch_tricore = []
|
||||
+550
@@ -0,0 +1,550 @@
|
||||
This file details the changelog of Unicorn Engine.
|
||||
|
||||
-------------------------------
|
||||
[Version 2.1.4]: September 9th, 2025
|
||||
|
||||
Hello everyone. It has been a while since last release and we are releasing Unicorn 2.1.4. Though it is a patch release, there are some highlights worth mentioning.
|
||||
|
||||
The very first thing is that, Unicorn (finally!) offers consistent PC guarantee in all cases. Well, it might sound weird that why it was not. Generally QEMU is not designed to keep PC synced in all cases for performance and Unicorn once put necessary hacks but those hacks were too hard to maintain. Now we have architecture agnostic mechanism to offer the PC guarantee.
|
||||
|
||||
In addition, we offer a standalone unicorn Rust crate `unicorn-engine-sys` to allows users to build their own bindings since v2.1.4. There are also lots of bindings improvements contained in this release.
|
||||
|
||||
We also fix the building and distribution issues for macos ARM and distribute wheels again. Our friend @patryk4815 from pwndbg helps sort this out. Kudos to him!
|
||||
|
||||
For 2.2.0, personally I wish I could work it out before the end of this year, by merging lots of pending big PRs but my time budget is relatively limited recently. Please aware that 2.2.0 would probably bump QEMU version to 5.1.0 or even higher so semantics could be changed. Any help is highly welcome and please contact @wtdcode if you are keen.
|
||||
|
||||
Lastly, I would like to express my deep gratitude to all contributors that help make this release happen, specifically @Evian-Zhang for Rust improvements and unicornafl integration, @Antelox for consistent contributions on our workflows, @amaanq for various huge work, reviews and Rust bindings split, @PhilippTakacs for PR reviews and fix. Again, thanks for every contributor!
|
||||
|
||||
Below goes the auto generated release notes. Starting from this tag, the release note will switch to a short passage plus Github generated contents because this makes it easier to follow big changes.
|
||||
|
||||
## What's Changed
|
||||
* bindings/zig: Fix sample_riscv_zig partial writes and logging by @fervagar in https://github.com/unicorn-engine/unicorn/pull/2133
|
||||
* Fix physical address truncation on 32-bit systems with addressing extensions by @ExhoAR22 in https://github.com/unicorn-engine/unicorn/pull/2139
|
||||
* refactor(lib): mark pointers as const where possible by @amaanq in https://github.com/unicorn-engine/unicorn/pull/2140
|
||||
* bindings: ruby: fix unexpected uc_query result pointer type by @anthraxx in https://github.com/unicorn-engine/unicorn/pull/1962
|
||||
* bindings: ruby: fix version identifier to 2.1.3 by @anthraxx in https://github.com/unicorn-engine/unicorn/pull/2142
|
||||
* feat(arm): add an `ESR` register by @amaanq in https://github.com/unicorn-engine/unicorn/pull/2155
|
||||
* fix(rust): watch all source and header files by @amaanq in https://github.com/unicorn-engine/unicorn/pull/2159
|
||||
* feat(rust): improve ARM CP register ergonomics by @amaanq in https://github.com/unicorn-engine/unicorn/pull/2160
|
||||
* fix(m68k): correct SR register read by @amaanq in https://github.com/unicorn-engine/unicorn/pull/2161
|
||||
* fix(python): catch `BaseException` in wrappers instead of `Exception` by @amaanq in https://github.com/unicorn-engine/unicorn/pull/2163
|
||||
* Remove the size limit for memory read and write (revamped) by @rliebig in https://github.com/unicorn-engine/unicorn/pull/2144
|
||||
* Loongarch port by @wtdcode in https://github.com/unicorn-engine/unicorn/pull/2164
|
||||
* S390x registers by @wtdcode in https://github.com/unicorn-engine/unicorn/pull/2167
|
||||
* Add from_handle_with_data method by @Evian-Zhang in https://github.com/unicorn-engine/unicorn/pull/2169
|
||||
* Make Unicorn struct clone-able by @Evian-Zhang in https://github.com/unicorn-engine/unicorn/pull/2170
|
||||
* Add Display and Error impl for uc_error for Rust bindings by @Evian-Zhang in https://github.com/unicorn-engine/unicorn/pull/2171
|
||||
* Add edge generated callback by @Evian-Zhang in https://github.com/unicorn-engine/unicorn/pull/2176
|
||||
* Correctly restore skip_sync_pc_on_exit by @Evian-Zhang in https://github.com/unicorn-engine/unicorn/pull/2181
|
||||
* Added CFG check and standalone flag for .NET binding by @AdvDebug in https://github.com/unicorn-engine/unicorn/pull/2182
|
||||
* qemu/configure: make strings command can be redefined by ${STRINGS} by @clan in https://github.com/unicorn-engine/unicorn/pull/2186
|
||||
* fix x86 pc by @wtdcode in https://github.com/unicorn-engine/unicorn/pull/2187
|
||||
* Patch from Saferewrite by @wtdcode in https://github.com/unicorn-engine/unicorn/pull/2188
|
||||
* fix incorrect ret of trampoline `closure` in `alloc_code_gen_buffer` by @HyperSine in https://github.com/unicorn-engine/unicorn/pull/2197
|
||||
* reset invalid_error before ram_block_add by @PhilippTakacs in https://github.com/unicorn-engine/unicorn/pull/2189
|
||||
* add uc_mem_read_virtual by @PhilippTakacs in https://github.com/unicorn-engine/unicorn/pull/2121
|
||||
* Minor fixes for vmem apis by @wtdcode in https://github.com/unicorn-engine/unicorn/pull/2199
|
||||
* glib_compat/glib_compact: Clear the buffer in g_hash_table_resize by @MarsDoge in https://github.com/unicorn-engine/unicorn/pull/2219
|
||||
* Enable install for rust bindings by @wtdcode in https://github.com/unicorn-engine/unicorn/pull/2224
|
||||
* Remove ninja installation from macos runners by @scribam in https://github.com/unicorn-engine/unicorn/pull/2225
|
||||
* Fix `read_sprr_perm` for Apple real CPUs and GitHub Actions; enable Apple ARM64 wheel builds on PyPI. Fixes #2033. by @patryk4815 in https://github.com/unicorn-engine/unicorn/pull/2227
|
||||
* Bump cmake minimum required version to 3.10 by @scribam in https://github.com/unicorn-engine/unicorn/pull/2226
|
||||
* Add support for clangarm64 by @scribam in https://github.com/unicorn-engine/unicorn/pull/2228
|
||||
* Revert "glib_compat/glib_compact: Clear the buffer in g_hash_table_resize" by @wtdcode in https://github.com/unicorn-engine/unicorn/pull/2220
|
||||
* Python bindings: Use ABI3 wheels by @Antelox in https://github.com/unicorn-engine/unicorn/pull/2223
|
||||
* concurrent control by @wtdcode in https://github.com/unicorn-engine/unicorn/pull/2235
|
||||
|
||||
## New Contributors
|
||||
* @fervagar made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2133
|
||||
* @ExhoAR22 made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2139
|
||||
* @anthraxx made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1962
|
||||
* @rliebig made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2144
|
||||
* @AdvDebug made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2182
|
||||
* @HyperSine made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2197
|
||||
* @MarsDoge made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2219
|
||||
* @patryk4815 made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2227
|
||||
|
||||
**Full Changelog**: https://github.com/unicorn-engine/unicorn/compare/v2.1.3...v2.1.4
|
||||
|
||||
-------------------------------
|
||||
[Version 2.1.3]: March 7th, 2025
|
||||
|
||||
2.1.3 includes a few fixes for distribution and stability issues. We urge users to update their versions as previous version contains security vulnerabilities.
|
||||
|
||||
Fix & Improvements
|
||||
|
||||
- Several security issues fixes. @PhilippTakacs
|
||||
- Add alpine workflow and fix several distribution issues. @Antelox
|
||||
- Introduce importlib_resources for py3.8 (EOL already) @Arusekk @Antelox
|
||||
- Mips64 improvemtns. @OBarronCS
|
||||
- mips related fixes and implement a few registers.
|
||||
- x86 default cpu model is changed to `UC_CPU_X86_HASWELL` and remove a few quirks.
|
||||
- Fix CR4 implementation.
|
||||
- Fix python bindings break changes by adding back `ctl_tlb_mode`. @Antelox
|
||||
|
||||
New Contributors
|
||||
* @OBarronCS made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2111
|
||||
|
||||
Full Changelog: https://github.com/unicorn-engine/unicorn/compare/2.1.2...2.1.3
|
||||
|
||||
-------------------------------
|
||||
[Version 2.1.2]: Feb 13rd, 2025
|
||||
2.1.2 is a patch release to mainly resolve the stability issue of the 2.1.0 release series and fix a few distribution issues. All users of Unicorn are expected to upgrade.
|
||||
|
||||
Highlights
|
||||
|
||||
- macOS arm64 no longer provides binary wheels due to a feature missing from Github Action runners.
|
||||
- py3.8 support is brought back, python2 wheels are built and test as well and we migrate to cibuildwheel! @Antelox
|
||||
- Several polish to python bindings and implement new API. @elicn @droe
|
||||
- Revert previous break changes that return UC_ERR_ARG for non-existing registers. Now this prints a warning. We urge all users relying on this behavior to fix code as soon as possible.
|
||||
- Fix several segment faults, including a few long-standing random segfault issues.
|
||||
- Revive previous unicorn 1 test suite and related refactoring. @elicn @Antelox
|
||||
- Optimize memory handling and notdirty writes for faster memory access and snapshots, especially useful for fuzzing. @PhilippTakacs
|
||||
- RISC-V API updates. @apparentlymart
|
||||
- fix UC_HOOK_MEM_READ on aarch64. @glennsec
|
||||
- Support UC_TCG_OP_FLAG_CMP for ARM @dotCirill
|
||||
- An alternative tag prefixed with "v" is added for golang compatibility.
|
||||
|
||||
Fix & Improvements
|
||||
|
||||
- Implement UC_ARM64_REG_WSP
|
||||
- Several fixes on arm64 macos @tbodt
|
||||
- reg_read_batch and reg_write_batch @hsa1as
|
||||
- Fix pc sync issue for SPARC, MIPS, x86. This also fixes PC sync issue within UC_HOOK_BLOCK hooks.
|
||||
- Allow cmake to install files on Windows and allow cmake not to generate Unicorn 1 style all-in-one objects archive
|
||||
- Make i386 instructions RDTSC and RDTSCP hookable @t0rr3sp3dr0
|
||||
- Allow Statically Linking in Go @t0rr3sp3dr0
|
||||
- Rust bindings improvements @Sanae6
|
||||
- Patch multiple UC_HOOK_MEM callbacks for unaligned access @Michael-c0de
|
||||
- Fix UC_MEM_FETCH_PROT for data read
|
||||
- Remove more Unicorn 1 hacks to improve performance.
|
||||
- Docs & unit test updates. @saicao
|
||||
- Allow uc_ctl_set_page_size() for arm64 @droe
|
||||
- Musl builds @clan
|
||||
- mips16 fix @ZakDanger
|
||||
- Fix UC_HOOK_MEM on arm32 @xndcn
|
||||
- Fix heap buffer overflow in op_cksm function @Shivam7-1
|
||||
|
||||
|
||||
New Contributors
|
||||
* @droe made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2023
|
||||
* @Antelox made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2026
|
||||
* @tbodt made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2049
|
||||
* @hsa1as made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2060
|
||||
* @t0rr3sp3dr0 made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2066
|
||||
* @Sanae6 made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2069
|
||||
* @Michael-c0de made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2063
|
||||
* @Evian-Zhang made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2064
|
||||
* @glennsec made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2028
|
||||
* @clan made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2085
|
||||
* @xndcn made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2091
|
||||
* @Shivam7-1 made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2096
|
||||
* @amaanq made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2099
|
||||
|
||||
Full Changelog: https://github.com/unicorn-engine/unicorn/compare/2.1.1...2.1.2
|
||||
|
||||
As usual, thanks to all contributors and sorry if I missed your name here (please tell me @wtdcode !).
|
||||
|
||||
Lastly and personally, I would like to express my big thanks to @Antelox @elicn and @PhilippTakacs for spending lots of time improving Unicorn Engine. Also, there are a few big pull requests adding new architectures (RH850, TCI, LoongArch64, AVR) and I will actively push them to 2.2.0.
|
||||
|
||||
-------------------------------
|
||||
[Version 2.1.1]: Sept 26th, 2024
|
||||
|
||||
This is a small release to fix a few urgent issues.
|
||||
|
||||
- Remove pkg_resources usage
|
||||
- Fix wheels distribution for x86_64 macos
|
||||
- Fix redundant wheel hacks
|
||||
- Support musllinux distribution
|
||||
|
||||
-------------------------------
|
||||
[Version 2.1.0]: Sept 22nd, 2024
|
||||
|
||||
It has been a while since the last release, and 2.1.0 brings several exciting features. Below is the changelog from the latest to the oldest (though not strictly).
|
||||
|
||||
Highlights
|
||||
|
||||
- Revive QEMU logs, now we have `-DUNICORN_LOGGING=yes` to enable all qemu logs. @BitMaskMixer
|
||||
- Faster (up to 40x) write performance by not always doing `store_helper` and cleaning page locks. @tunz @boborjan2
|
||||
- Brand new python bindings, with strongly typed and many improvements. @elicn
|
||||
- Fix to a long-standing MinGW random segfault bug.
|
||||
- We bring python2 compatibility back.
|
||||
- We now fully support M1, both building and a pre-built wheel.
|
||||
- We support snapshot memory now, with a very low overhead copy-on-write fashion. @PhilippTakacs
|
||||
- An option to bypass MMU is also added, check our FAQ. @PhilippTakacs
|
||||
- A brand new (and modern) java bindings. We are also working to publish it to maven. @nneonneo
|
||||
- We have zig integrated. @kassane @atipls
|
||||
- Now Unicorn no longer allocates 2GB memory for every instance. The memory will be only committed once used and the upper limit can be adjusted with `uc_ctl`.
|
||||
- New DotNet binding, with published to both Github and Nuget. @TSRBerry
|
||||
- The release will attach all binaries, thanks to @marameref
|
||||
|
||||
Fixes & Improvements
|
||||
|
||||
- RISCV improvements, but we still have a long way to go. @apparentlymart @ks0777
|
||||
- cmake improvements @scribam @es3n1n
|
||||
- Various python bindings fix and improvements @bet4it @rhelmot
|
||||
- Docs. @gerph @BitMaskMixer
|
||||
- Rust bindings. @lockbox @mlgiraud @deadash
|
||||
- TCG backend fixes. @redoste @StalkR @dglynos
|
||||
- PPC32 fixes. @dotCirill
|
||||
- Haiku fixes. @kallisti5
|
||||
- Improvements to avoid simulator detection. @mrexodia
|
||||
|
||||
New Contributors
|
||||
|
||||
* @ks0777 made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1736
|
||||
* @LG3696 made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1764
|
||||
* @PhilippTakacs made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1765
|
||||
* @edsky made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1754
|
||||
* @tunz made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1790
|
||||
* @kassane made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1809
|
||||
* @Xeonacid made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1807
|
||||
* @nneonneo made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1830
|
||||
* @lockbox made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1847
|
||||
* @mlgiraud made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1849
|
||||
* @basavesh made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1861
|
||||
* @hamarituc made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1886
|
||||
* @StalkR made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1907
|
||||
* @dotCirill made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1910
|
||||
* @marameref made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1897
|
||||
* @redoste made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1922
|
||||
* @xclusivor made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1929
|
||||
* @elicn made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1629
|
||||
* @nganhkhoa made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1942
|
||||
* @es3n1n made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1960
|
||||
* @BitMaskMixer made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1977
|
||||
* @apparentlymart made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1995
|
||||
* @atipls made their first contribution in https://github.com/unicorn-engine/unicorn/pull/1985
|
||||
* @omer54463 made their first contribution in https://github.com/unicorn-engine/unicorn/pull/2005
|
||||
|
||||
Full Changelog: https://github.com/unicorn-engine/unicorn/compare/2.0.1...2.1.0
|
||||
|
||||
|
||||
Again, thanks for all contributors and sorry if I missed your name here (please tell me @wtdcode !). 2.1.1 is also coming because we expect some minor break changes to fix.
|
||||
|
||||
-------------------------------
|
||||
[Version 2.0.1.post1]: Nov 22nd, 2022
|
||||
|
||||
This is a small release to complement the previous 2.0.1 release.
|
||||
|
||||
Fix:
|
||||
|
||||
- Fix the endianness detection in tests.
|
||||
- Fix the version number in CMakeLists.txt.
|
||||
|
||||
-------------------------------
|
||||
[Version 2.0.1]: Nov 1st, 2022
|
||||
|
||||
Unicorn2 makes the first step to [Debian packages](https://tracker.debian.org/pkg/unicorn-engine) and [vcpkg](https://github.com/microsoft/vcpkg/pull/26101)! Thanks @roehling and @LilyWangL !
|
||||
|
||||
Features:
|
||||
|
||||
- Support building & running on BE hosts. #1710
|
||||
- Fix and support `clang-cl` on Windows. #1687
|
||||
- Fix python `sdist` and add aarch64 Linux wheels. Note `pip` can build Unicorn2 on M1 now!
|
||||
- C# binding is refined and upgraded to .Net 6. #1723
|
||||
|
||||
Fix/Improvements:
|
||||
|
||||
- Various bindings improvements. #1723
|
||||
- Improvements for tests. #1684 #1683 #1691 #1711
|
||||
- Fail explicitly when VEX.L is set. #1658
|
||||
- Fix endianness when writing PPC32 CR register. #1659
|
||||
- Fix a bug in `uc_ctl_set_cpu_model` check.
|
||||
- Fix Tricore PC not updating. #1668
|
||||
- Fix the mapping not updated if users modify the mappings in the hooks.
|
||||
- Handle pathological cases consistently. #1651
|
||||
- Fix memory leaks in PPC target. #1680
|
||||
- Fix memory leaks in Tricore target. #1681
|
||||
- Fix MSVC handling in cmake. #1693
|
||||
- Fix PC sync-ing problems for `UC_HOOK_BLOCK` hooks.
|
||||
- Fix PC sync-ed twice when users request a soft restart.
|
||||
- Prevent overflow with pre-allocated RAM blocks. #1712
|
||||
- Add FPCR and FPSR registers #1722
|
||||
- Fix ARM CPU state not deep copied.
|
||||
- Fix PC not sync-ed for memory operation on aarch64.
|
||||
- Exit invalid store operations early to avoid the target registers being overwritten.
|
||||
- Improve the support for ARM BE32.
|
||||
|
||||
Thanks:
|
||||
|
||||
@roehling @LilyWangL @mrexodia @zachriggle @Yu3H0 @rhelmot @relapids @sh4w1 @TSRBerry
|
||||
|
||||
-------------------------------
|
||||
[Version 2.0.0]: July 7th, 2022
|
||||
|
||||
Features:
|
||||
|
||||
- TriCore Support (#1568)
|
||||
|
||||
Fixes/Improvements:
|
||||
|
||||
- Build both shared library and static archive as unicorn1 does.
|
||||
- Misc bindings improvements. #1569 #1600 #1609 #1613 #1616
|
||||
- Make sure setjmp-setjmp-wrapper-win32 participates in the build. #1604
|
||||
- Improve Rust bindings build logic.
|
||||
- Fix wrong python binding for UC_CTL_TB_REMOVE_CACHE
|
||||
- Flush translation blocks when the count hook is removed.
|
||||
- Fix unicorn crash when nested `uc_emu_start` deletes a hook
|
||||
- Fix CPU not fully resumed when writing PC.
|
||||
- Don't quit TB if `uc_mem_protect` doesn't change the protection of current TB memory.
|
||||
- Add type annotations for python bindings.
|
||||
- Add CPUID hook for python bindings. #1618
|
||||
- Don't repeat memory hooks if there is already an unhandled error. #1618
|
||||
- Support reads and writes over all Arm SIMD registers #1621
|
||||
- Fix wrong registers range in python bindings.
|
||||
- Fix uc_mem_protect on mmio regions
|
||||
- Fix a UAF caused by hook cache.
|
||||
- Fix the value collision between UC_MODE_ARMBE8 and UC_MODE_ARM926
|
||||
|
||||
Thanks:
|
||||
|
||||
@AfoHT @mrexodia @bet4it @lowlyw @ekilmer @ondryaso @QDucasse @PalumboN @uberwoozle
|
||||
|
||||
----------------------------------
|
||||
[Version 2.0.0 rc7]: April 17, 2022
|
||||
|
||||
This release is expected to be the real last RC release of Unicorn2. ;)
|
||||
|
||||
Features:
|
||||
|
||||
- Correctly generate static archives for the static build and have CI auto-tested.
|
||||
- Rust bindings revised. #1584
|
||||
- Compatible with clang-cl compiler. #1581
|
||||
- Implement UC_HOOK_INSN for aarch64 MRS/MSR/SYS/SYSL
|
||||
|
||||
Fixes/Improvements:
|
||||
|
||||
- Several corner cases on our API. #1587 #1595
|
||||
- Fix the codegen buffer leak.
|
||||
- Rust bindins improvements. #1574 #1575
|
||||
- Add "holes" to allow unicorn lib as a drop-in replacement for older ones. #1572
|
||||
- s390x backports. #1570
|
||||
- Fix exits wrongly removed in nested uc_emu_start
|
||||
- Fix a possible endless loop for only one translation block in a multithreaded environment.
|
||||
- Fix wrong PC without `UC_HOOK_CODE` installed.
|
||||
- Update vb6 bindings license. #1563
|
||||
- Fix buffer allocation failure on M1. #1559
|
||||
- Fix wrong EFLAGS on startup.
|
||||
- Fix wrong internal states on nested uc_emu_start.
|
||||
- Remove armeb-softmmu and aarcheb-softmmu which are usermode targets.
|
||||
- Advance PPC32 PC. #1558
|
||||
- Support UC_PPC_REG_CR.
|
||||
- Update CI to windows-2019
|
||||
|
||||
Thanks:
|
||||
|
||||
@shuffle2 @liyansong2018 @rose4096 @nviennot @n1tram1 @iii-i @dzzie @yrashk @bet4it
|
||||
|
||||
----------------------------------
|
||||
[Version 2.0.0 rc6]: Feburary 13, 2022
|
||||
|
||||
This release is expected to be the last RC release of Unicorn2.
|
||||
|
||||
Features:
|
||||
|
||||
- SystemZ (aka. s390x) support. #1521 #1547
|
||||
- CPUID hook now may return a bool to indicate whether skipping the CPUID instruction.
|
||||
- ARM/AARCH64 coprocessor registers read/write support. #889
|
||||
|
||||
Fixes/Improvements:
|
||||
|
||||
- Rust improvements. More registers enums #1504 Easier to use #1543 #1545
|
||||
- M68k improvements. #1507
|
||||
- Golang improvements. Enable `uc_ctl_set_model` #1506
|
||||
- Unit tests improvements. #1512
|
||||
- Various ARM system mode fixes. #1500 #1520 #1525 #1531
|
||||
- Read/write arm FPSCR and FPSID. #1453
|
||||
- Fix the support for ARMv8
|
||||
- Fix a large number of memory leaks and unicorn2 now goes with google/oss-fuzz!
|
||||
- Add more X87 registers. #1524
|
||||
- Add more PPC registers.
|
||||
- Fix the exception not cleared in python bindings. #1537
|
||||
- Correctly support ARM big endian and drops `armeb-softmmu` and `aarch64eb-softmmu`
|
||||
- Fix ARM CPSR.E not reflected during runtime.
|
||||
- Resolve fuzzing speed problem on macOS.
|
||||
- Modernize CmakeFileLists.txt. #1544
|
||||
- Fix an issue in nested `uc_emu_start`
|
||||
|
||||
Thanks:
|
||||
|
||||
@Kritzefitz @zznop @QDucasse @gerph @bet4it @mrexodia @iii-i @jbcayrou @scribam
|
||||
|
||||
----------------------------------
|
||||
[Version 2.0.0 rc5]: November 25, 2021
|
||||
|
||||
This release fixes a few urgent bugs and improves performance.
|
||||
|
||||
Fixes/Improvements:
|
||||
|
||||
- Rust bindings improvements. #1480 #1483
|
||||
- Allow R/W to cp15 registers. #1481
|
||||
- Fix `UC_HOOK_EDGE_GENERATED` not calling for indirect jumps.
|
||||
- Python bindings build improvements. #1486
|
||||
- Fix bindings on m1 macOS.
|
||||
- Support nested `uc_emu_start` calls without context save/restore
|
||||
- Fix wrong MMIO offset for 32bit targets.
|
||||
- Fix wrong `uc_mem_unmap` logic for both ram and mmio memory.
|
||||
- Inline `uc_trace_code` and PC sync to improve performance.
|
||||
- Various fixes in tests.
|
||||
- Allow writing to CPSR to switch bank registers.
|
||||
- Implement MMIO in rust bindings. #1499
|
||||
|
||||
Thanks:
|
||||
|
||||
- @domenukk
|
||||
- @bet4it
|
||||
- @mid-kid
|
||||
- @Kritzefitz
|
||||
|
||||
----------------------------------
|
||||
[Version 2.0.0 rc4]: November 09, 2021
|
||||
|
||||
This is a big release of Unicorn and introduces a few powerful new features and a bunch of fixes.
|
||||
|
||||
New Features:
|
||||
|
||||
- New API: uc_ctl, by which you could control CPU models, TB caches or multiple exits etc.
|
||||
- New Hook: UC_HOOK_EDGE_GENERATED, UC_HOOK_TCG_OPCODE
|
||||
- RISCV CSR read/write.
|
||||
- Support reading MIPS hi/lo regs. 7268c2a19bce2db72b90e3ea3b133482c3ff4e58
|
||||
- OSS Fuzzing building support.
|
||||
- MSVC 32bit and Android build support.
|
||||
- Introduce clang-format.
|
||||
|
||||
Fixes/Improvements:
|
||||
|
||||
- Java bindings improvements. unicorn-engine/unicorn#1461
|
||||
- API Documents updates. unicorn-engine/unicorn#1459
|
||||
- Rust bindings improvements. unicorn-engine/unicorn#1462
|
||||
- Add a go.mod for go bindings.
|
||||
- CMakeLists.txt improvements as a subproject. #1373
|
||||
- Fix rust bindings build script and add CI.
|
||||
- Use binary search to find mappings. unicorn-engine/unicorn#1414
|
||||
- RISCV:
|
||||
- Update pc when exiting execution. unicorn-engine/unicorn#1465
|
||||
- Add RISCV control status registers to enable floating. unicorn-engine/unicorn#1469 unicorn-engine/unicorn#1478
|
||||
- After `ecall`, pc not advanced. unicorn-engine/unicorn#1477
|
||||
- Fix tb not invalidated when exiting.
|
||||
- Fix bindings makefile.
|
||||
- Fix uc_mem_protect not working. unicorn-engine/unicorn#1468
|
||||
|
||||
Thanks:
|
||||
|
||||
- @bet4it
|
||||
- @kabeor
|
||||
- @chfl4gs
|
||||
- @QDucasse
|
||||
- @h33p
|
||||
- @geohot
|
||||
- @cla7aye15I4nd
|
||||
- @jcalabres
|
||||
|
||||
----------------------------------
|
||||
[Version 2.0.0 rc3]: October 06, 2021
|
||||
|
||||
This is an urgent pre-release regarding python bindings on older Linux systems.
|
||||
|
||||
- Support older Linux distribution, e.g. prior to Ubuntu 19.04
|
||||
- Fix a memory leak in `uc_close`
|
||||
- Support building on Android
|
||||
- Support hooking CPUID instruction.
|
||||
|
||||
Enjoy.
|
||||
|
||||
----------------------------------
|
||||
[Version 2.0.0 rc2]: October 05, 2021
|
||||
|
||||
This is an urgent pre-release regarding the packaging problem of python bindings.
|
||||
|
||||
- Set `zip_false` and `is_pure` to `False` to avoid issues on some Linux distributions.
|
||||
- Link to `libm` to make sure our libraries work.
|
||||
- Support to read ST registers in rust bindings.
|
||||
- Fix #1450
|
||||
|
||||
Enjoy.
|
||||
|
||||
----------------------------------
|
||||
[Version 2.0.0 rc1]: October 04, 2021
|
||||
|
||||
Unicorn2 first release candidate!
|
||||
|
||||
- Based on Qemu 5.0.1
|
||||
- Remain backward compatible with Unicorn 1.x
|
||||
- Update ISA of all existing architectures
|
||||
- Support 2 new architectures in PowerPC & RISCV
|
||||
|
||||
----------------------------------
|
||||
[Unicorn2-beta]: October 3rd, 2021
|
||||
|
||||
- Release Unicorn2 beta to public.
|
||||
- New logo to celebrate this important milestone!
|
||||
|
||||
----------------------------------
|
||||
[Version 1.0.1]: April 20th, 2017
|
||||
|
||||
- Properly handle selected-architecture build.
|
||||
- Fix compilation issues on PPC & S390x.
|
||||
- Fix a memory leak on uc_mem_protect().
|
||||
- ARM:
|
||||
- Support big-endian mode.
|
||||
- Correct instruction size of Thumb/Thumb2 code.
|
||||
- Support read/write APSR register.
|
||||
- ARM64:
|
||||
- Support read/write NEON registers.
|
||||
- Support read/write NZCV registers.
|
||||
- Mips: Support read/write Mips64 registers.
|
||||
- X86: Support read/write MSR.
|
||||
- Haskell binding: update to the latest API.
|
||||
- Python: allow not having PATH setup.
|
||||
|
||||
----------------------------------
|
||||
[Version 1.0]: February 23rd, 2017
|
||||
|
||||
- Fix build script for BSD host.
|
||||
- Fix building Unicorn on Arm/PPC/Sparc/S390 hosts.
|
||||
- X86:
|
||||
- Fix 16bit address computation.
|
||||
- Fix initial state of segment registers.
|
||||
|
||||
----------------------------------
|
||||
[Version 1.0-rc3]: January 25th, 2017
|
||||
|
||||
- Rename API uc_context_free() to uc_free().
|
||||
- ARM:
|
||||
- uc_reg_write() now can modify CPSR register.
|
||||
- Add some ARM coproc registers.
|
||||
- ARM64: uc_reg_read|write() now handles W0-W31 registers.
|
||||
- Windows: fix a double free bug in uc_close().
|
||||
- New VB6 binding.
|
||||
- Java: update to support new APIs from v1.0-rc1.
|
||||
- Python:
|
||||
- Fix memory leaking that prevents UC instances from being GC.
|
||||
- Remove some dependencies leftover from glib time.
|
||||
- Add new method mem_regions() (linked to uc_mem_regions() API)
|
||||
|
||||
----------------------------------
|
||||
[Version 1.0-rc2]: January 4th, 2017
|
||||
|
||||
- Remove glib & pkconfig dependency.
|
||||
- Python: fix an issue to restore support for FreeBSD (and other *BSD Unix).
|
||||
- ARM: support MCLASS cpu (Cortex-M3).
|
||||
- Windows: export a static lib that can be used outside of Mingw
|
||||
|
||||
----------------------------------
|
||||
[Version 1.0-rc1]: December 22nd, 2016
|
||||
|
||||
- Lots of bugfixes in all architectures.
|
||||
- Better support for ARM Thumb.
|
||||
- Fix many memory leaking issues.
|
||||
- New bindings: Haskell, MSVC.
|
||||
- Better support for Python3.
|
||||
- New APIs: uc_query, uc_reg_write_batch, uc_reg_read_batch, uc_mem_map_ptr, uc_mem_regions, uc_context_alloc, uc_context_save & uc_context_restore.
|
||||
- New memory hook type: UC_HOOK_MEM_READ_AFTER.
|
||||
- Add new version macros UC_VERSION_{MAJOR, MINOR, EXTRA}
|
||||
|
||||
----------------------------------
|
||||
[Version 0.9]: October 15th, 2015
|
||||
|
||||
- Initial public release.
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
Unicorn Engine
|
||||
==============
|
||||
|
||||
[](https://pepy.tech/project/unicorn)
|
||||
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:unicorn)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<img width="250" src="docs/unicorn-logo.png">
|
||||
</p>
|
||||
|
||||
Unicorn is a lightweight, multi-platform, multi-architecture CPU emulator framework, based on [QEMU](http://qemu.org).
|
||||
|
||||
Unicorn offers some unparalleled features:
|
||||
|
||||
- Multi-architecture: ARM, ARM64 (ARMv8), M68K, MIPS, PowerPC, RISCV, SPARC, S390X, TriCore and X86 (16, 32, 64-bit)
|
||||
- Clean/simple/lightweight/intuitive architecture-neutral API
|
||||
- Implemented in pure C language, with bindings for Crystal, Clojure, Visual Basic, Perl, Rust, Ruby, Python, Java, .NET, Go, Delphi/Free Pascal, Haskell, Pharo, Lua and Zig.
|
||||
- Native support for Windows & *nix (with Mac OSX, Linux, Android, *BSD & Solaris confirmed)
|
||||
- High performance via Just-In-Time compilation
|
||||
- Support for fine-grained instrumentation at various levels
|
||||
- Thread-safety by design
|
||||
- Distributed under free software license GPLv2
|
||||
|
||||
Further information is available at http://www.unicorn-engine.org
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
This project is released under the [GPL license](COPYING).
|
||||
|
||||
|
||||
Compilation & Docs
|
||||
------------------
|
||||
|
||||
See [docs/COMPILE.md](docs/COMPILE.md) file for how to compile and install Unicorn.
|
||||
|
||||
More documentation is available in [docs/README.md](docs/README.md).
|
||||
|
||||
For common questions, read [docs/FAQ.md](docs/FAQ.md) before raising an issue.
|
||||
|
||||
Contact
|
||||
-------
|
||||
|
||||
[Contact us](http://www.unicorn-engine.org/contact/) via mailing list, email or twitter for any questions.
|
||||
|
||||
|
||||
Join [our group](https://t.me/+lnNl0fPpyCYzZmVh) for instant feedback.
|
||||
|
||||
Contribute
|
||||
----------
|
||||
|
||||
If you want to contribute, please pick up something from our [Github issues](https://github.com/unicorn-engine/unicorn/issues).
|
||||
|
||||
We also maintain a list of more challenged problems in [milestones](https://github.com/unicorn-engine/unicorn/milestones) for our regular release.
|
||||
|
||||
Please send pull request to our [dev branch](https://github.com/unicorn-engine/unicorn/tree/dev).
|
||||
|
||||
[CREDITS.TXT](CREDITS.TXT) records important contributors of our project.
|
||||
@@ -0,0 +1,3 @@
|
||||
aquynh -at- gmail.com
|
||||
|
||||
mio -at- lazym.io
|
||||
@@ -0,0 +1 @@
|
||||
Moved to https://github.com/unicorn-engine/unicorn/milestones
|
||||
+390
@@ -0,0 +1,390 @@
|
||||
use std::{env, path::PathBuf, process::Command};
|
||||
|
||||
use bindgen::callbacks::{EnumVariantValue, ParseCallbacks};
|
||||
use heck::ToUpperCamelCase;
|
||||
|
||||
fn ninja_available() -> bool {
|
||||
Command::new("ninja").arg("--version").spawn().is_ok()
|
||||
}
|
||||
|
||||
fn msvc_cmake_tools_available() -> bool {
|
||||
Command::new("cmake").arg("--version").spawn().is_ok() && ninja_available()
|
||||
}
|
||||
|
||||
fn get_tool_paths_msvc(compiler: &cc::Tool) -> Option<(PathBuf, PathBuf)> {
|
||||
// If tools are already available, don't need to find them
|
||||
if msvc_cmake_tools_available() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let target = env::var("TARGET").unwrap();
|
||||
let devenv = cc::windows_registry::find_tool(target.as_str(), "devenv");
|
||||
let tool_root = devenv.map_or_else(
|
||||
|| {
|
||||
// if devenv (i.e. Visual Studio) was not found, assume compiler is
|
||||
// from standalone Build Tools and look there instead.
|
||||
let tools_name = std::ffi::OsStr::new("BuildTools");
|
||||
let compiler_path = compiler.path().to_path_buf();
|
||||
compiler_path
|
||||
.iter()
|
||||
.find(|x| *x == tools_name)
|
||||
.expect("Failed to find devenv or Build Tools");
|
||||
compiler_path
|
||||
.iter()
|
||||
.take_while(|x| *x != tools_name)
|
||||
.collect::<PathBuf>()
|
||||
.join(tools_name)
|
||||
.join(r"Common7\IDE")
|
||||
},
|
||||
|devenv_tool| devenv_tool.path().parent().unwrap().to_path_buf(),
|
||||
);
|
||||
let cmake_pkg_dir = tool_root.join(r"CommonExtensions\Microsoft\CMake");
|
||||
let cmake_path = cmake_pkg_dir.join(r"CMake\bin\cmake.exe");
|
||||
let ninja_path = cmake_pkg_dir.join(r"Ninja\ninja.exe");
|
||||
|
||||
assert!(cmake_path.is_file(), "missing cmake");
|
||||
assert!(ninja_path.is_file(), "missing ninja");
|
||||
|
||||
Some((cmake_path, ninja_path))
|
||||
}
|
||||
|
||||
fn build_with_cmake() {
|
||||
let current_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let uc_dir = current_dir;
|
||||
let compiler = cc::Build::new().get_compiler();
|
||||
|
||||
// Initialize configuration
|
||||
let mut config = cmake::Config::new(uc_dir);
|
||||
|
||||
// Check for tools and set up configuration
|
||||
let has_ninja = if compiler.is_like_msvc() {
|
||||
// MSVC-specific setup
|
||||
if let Some((cmake_path, ninja_path)) = get_tool_paths_msvc(&compiler) {
|
||||
// Tell Cargo where to find the tools instead of modifying PATH
|
||||
println!("cargo:rustc-env=CMAKE_PATH={}", cmake_path.display());
|
||||
println!("cargo:rustc-env=NINJA_PATH={}", ninja_path.display());
|
||||
|
||||
// Set cmake path for the cmake crate
|
||||
config.define("CMAKE_PROGRAM", cmake_path.to_str().unwrap());
|
||||
}
|
||||
true
|
||||
} else {
|
||||
// Non-MSVC setup
|
||||
ninja_available()
|
||||
};
|
||||
|
||||
// Configure build generator
|
||||
if has_ninja {
|
||||
config.generator("Ninja");
|
||||
}
|
||||
|
||||
let mut archs = String::new();
|
||||
|
||||
if std::env::var("CARGO_FEATURE_ARCH_X86").is_ok() {
|
||||
archs.push_str("x86;");
|
||||
}
|
||||
if std::env::var("CARGO_FEATURE_ARCH_ARM").is_ok() {
|
||||
archs.push_str("arm;");
|
||||
}
|
||||
if std::env::var("CARGO_FEATURE_ARCH_AARCH64").is_ok() {
|
||||
archs.push_str("aarch64;");
|
||||
}
|
||||
if std::env::var("CARGO_FEATURE_ARCH_RISCV").is_ok() {
|
||||
archs.push_str("riscv;");
|
||||
}
|
||||
if std::env::var("CARGO_FEATURE_ARCH_MIPS").is_ok() {
|
||||
archs.push_str("mips;");
|
||||
}
|
||||
if std::env::var("CARGO_FEATURE_ARCH_SPARC").is_ok() {
|
||||
archs.push_str("sparc;");
|
||||
}
|
||||
if std::env::var("CARGO_FEATURE_ARCH_M68K").is_ok() {
|
||||
archs.push_str("m68k;");
|
||||
}
|
||||
if std::env::var("CARGO_FEATURE_ARCH_PPC").is_ok() {
|
||||
archs.push_str("ppc;");
|
||||
}
|
||||
if std::env::var("CARGO_FEATURE_ARCH_S390X").is_ok() {
|
||||
archs.push_str("s390x;");
|
||||
}
|
||||
if std::env::var("CARGO_FEATURE_ARCH_TRICORE").is_ok() {
|
||||
archs.push_str("tricore;");
|
||||
}
|
||||
|
||||
if !archs.is_empty() {
|
||||
archs.pop();
|
||||
}
|
||||
|
||||
if config.get_profile() == "Debug" {
|
||||
config.define("UNICORN_LOGGING", "ON");
|
||||
}
|
||||
|
||||
let dst = config
|
||||
.define("UNICORN_BUILD_TESTS", "OFF")
|
||||
.define("UNICORN_INSTALL", "ON")
|
||||
.define("UNICORN_ARCH", archs)
|
||||
.build();
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
dst.join("lib").display()
|
||||
);
|
||||
// rhel
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
dst.join("lib64").display()
|
||||
);
|
||||
|
||||
// Lazymio(@wtdcode): Dynamic link may break. See: https://github.com/rust-lang/cargo/issues/5077
|
||||
if cfg!(feature = "dynamic_linkage") {
|
||||
if compiler.is_like_msvc() {
|
||||
println!("cargo:rustc-link-lib=dylib=unicorn-import");
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=dylib=unicorn");
|
||||
}
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=static=unicorn");
|
||||
}
|
||||
if !compiler.is_like_msvc() {
|
||||
println!("cargo:rustc-link-lib=pthread");
|
||||
println!("cargo:rustc-link-lib=m");
|
||||
}
|
||||
}
|
||||
|
||||
fn watch_source_files() {
|
||||
let current_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let project_root = std::path::Path::new(¤t_dir)
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap();
|
||||
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
project_root.join("uc.c").display()
|
||||
);
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
project_root.join("list.c").display()
|
||||
);
|
||||
|
||||
// Directories to watch for changes
|
||||
let watch_dirs = vec!["qemu", "include", "bindings", "glib_compat"];
|
||||
|
||||
let watch_extensions = vec![".c", ".h"];
|
||||
|
||||
for dir in watch_dirs {
|
||||
let dir_path = project_root.join(dir);
|
||||
if dir_path.exists() {
|
||||
register_dir_files(&dir_path, &watch_extensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn register_dir_files(dir: &std::path::Path, extensions: &[&str]) {
|
||||
if let Ok(entries) = std::fs::read_dir(dir) {
|
||||
for entry in entries.filter_map(Result::ok) {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
register_dir_files(&path, extensions);
|
||||
} else if let Some(ext) = path.extension() {
|
||||
if extensions
|
||||
.iter()
|
||||
.any(|&e| e == format!(".{}", ext.to_string_lossy()))
|
||||
{
|
||||
println!("cargo:rerun-if-changed={}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Renamer;
|
||||
|
||||
impl ParseCallbacks for Renamer {
|
||||
fn item_name(&self, original_item_name: &str) -> Option<String> {
|
||||
// Special case for error type
|
||||
if original_item_name == "uc_err" {
|
||||
return Some(String::from("uc_error"));
|
||||
}
|
||||
|
||||
if original_item_name.contains("_cpu_") {
|
||||
return original_item_name
|
||||
.strip_prefix("uc_cpu_")
|
||||
.map(|suffix| format!("{}CpuModel", suffix.to_upper_camel_case()));
|
||||
}
|
||||
|
||||
if original_item_name.ends_with("_reg") {
|
||||
return original_item_name
|
||||
.strip_prefix("uc_")
|
||||
.and_then(|suffix| suffix.strip_suffix("_reg"))
|
||||
.map(|suffix| format!("Register{}", suffix.replace('_', "").to_uppercase()));
|
||||
}
|
||||
|
||||
if original_item_name.ends_with("_insn") {
|
||||
return original_item_name
|
||||
.strip_prefix("uc_")
|
||||
.and_then(|suffix| suffix.strip_suffix("_insn"))
|
||||
.map(|suffix| format!("{}Insn", suffix.to_upper_camel_case()));
|
||||
}
|
||||
|
||||
if original_item_name.contains("_mode_") {
|
||||
return original_item_name
|
||||
.strip_prefix("uc_mode_")
|
||||
.map(|suffix| format!("{}Mode", suffix.to_upper_camel_case()));
|
||||
}
|
||||
|
||||
// Map various specific types to more idiomatic Rust names
|
||||
match original_item_name {
|
||||
"uc_query_type" => Some(String::from("Query")),
|
||||
"uc_tlb_type" => Some(String::from("TlbType")),
|
||||
"uc_mem_type" => Some(String::from("MemType")),
|
||||
"uc_tb" => Some(String::from("TranslationBlock")),
|
||||
"uc_arch" => Some(String::from("Arch")),
|
||||
"uc_mode" => Some(String::from("Mode")),
|
||||
"uc_mem_region" => Some(String::from("MemRegion")),
|
||||
"uc_prot" => Some(String::from("Prot")),
|
||||
"uc_hook_type" => Some(String::from("HookType")),
|
||||
"uc_tlb_entry" => Some(String::from("TlbEntry")),
|
||||
"uc_control_type" => Some(String::from("ControlType")),
|
||||
"uc_context_content" => Some(String::from("ContextMode")),
|
||||
"uc_tcg_op_code" => Some(String::from("TcgOpCode")),
|
||||
"uc_tcg_op_flag" => Some(String::from("TcgOpFlag")),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn enum_variant_name(
|
||||
&self,
|
||||
enum_name: Option<&str>,
|
||||
original_variant_name: &str,
|
||||
_variant_value: EnumVariantValue,
|
||||
) -> Option<String> {
|
||||
if let Some(enum_name) = enum_name {
|
||||
if enum_name.starts_with("enum uc_") {
|
||||
// Prefix to strip from enum variant names
|
||||
let prefix = match enum_name.strip_prefix("enum uc_").unwrap() {
|
||||
"query_type" => "UC_QUERY",
|
||||
"tlb_type" => "UC_TLB",
|
||||
"control_type" => "UC_CTL",
|
||||
"context_content" => "UC_CTL_CONTEXT",
|
||||
"err" => "UC_ERR",
|
||||
"mem_type" | "mem_region" => "UC_MEM",
|
||||
"arch" => "UC_ARCH",
|
||||
"mode" => "UC_MODE",
|
||||
"prot" => "UC_PROT",
|
||||
"hook_type" => "UC_HOOK",
|
||||
"x86_insn" => "UC_X86_INS",
|
||||
"tcg_op_code" => "UC_TCG_OP",
|
||||
"tcg_op_flag" => "UC_TCG_OP_FLAG",
|
||||
other => format!("UC_{}", other.to_uppercase()).leak(),
|
||||
}
|
||||
.to_string()
|
||||
+ "_";
|
||||
|
||||
// Strip prefix
|
||||
let mut fixed = original_variant_name
|
||||
.strip_prefix(&prefix)
|
||||
.map(str::to_uppercase);
|
||||
|
||||
// Special handling for numeric register names in PPC and MIPS
|
||||
if (enum_name == "enum uc_ppc_reg" || enum_name == "enum uc_mips_reg")
|
||||
&& fixed.as_ref().is_some_and(|s| s.parse::<u32>().is_ok())
|
||||
{
|
||||
fixed = fixed.map(|s| format!("R{s}"));
|
||||
}
|
||||
|
||||
// Special handling for CPU variants that start with a number
|
||||
if enum_name.contains("cpu")
|
||||
&& fixed
|
||||
.as_ref()
|
||||
.is_some_and(|s| s.chars().next().unwrap().is_ascii_digit())
|
||||
{
|
||||
fixed = fixed.map(|s| format!("Model_{s}"));
|
||||
}
|
||||
|
||||
// Special handling for mode values
|
||||
if enum_name == "enum uc_mode" {
|
||||
fixed = fixed.map(|s| match s.as_str() {
|
||||
"16" | "32" | "64" => format!("MODE_{s}"),
|
||||
_ => s,
|
||||
});
|
||||
}
|
||||
|
||||
return fixed;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_bindings() {
|
||||
const HEADER_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/include/unicorn/unicorn.h");
|
||||
|
||||
let bitflag_enums = [
|
||||
"uc_hook_type",
|
||||
"uc_tcg_op_flag",
|
||||
"uc_prot",
|
||||
"uc_mode",
|
||||
"uc_context_content",
|
||||
"uc_control_type",
|
||||
];
|
||||
|
||||
let bindings = bindgen::Builder::default()
|
||||
.header(HEADER_PATH)
|
||||
.layout_tests(false)
|
||||
.allowlist_type("^uc.*")
|
||||
.allowlist_function("^uc_.*")
|
||||
.allowlist_var("^uc.*")
|
||||
.rustified_enum("^uc.*")
|
||||
.prepend_enum_name(false)
|
||||
.parse_callbacks(Box::new(Renamer))
|
||||
.bitfield_enum(bitflag_enums.join("|"))
|
||||
.derive_ord(true)
|
||||
.derive_eq(true)
|
||||
.use_core()
|
||||
.generate()
|
||||
.expect("Failed to generate bindings");
|
||||
|
||||
let bindings_rs = PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("bindings.rs");
|
||||
bindings
|
||||
.write_to_file(&bindings_rs)
|
||||
.unwrap_or_else(|_| panic!("Failed to write bindings into path: {bindings_rs:?}"));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
watch_source_files();
|
||||
|
||||
generate_bindings();
|
||||
|
||||
match pkg_config::Config::new()
|
||||
.atleast_version("2")
|
||||
.cargo_metadata(false)
|
||||
.probe("unicorn")
|
||||
{
|
||||
Ok(lib) => {
|
||||
for dir in lib.link_paths {
|
||||
println!("cargo:rustc-link-search=native={}", dir.to_str().unwrap());
|
||||
}
|
||||
if cfg!(feature = "dynamic_linkage") {
|
||||
if cc::Build::new().get_compiler().is_like_msvc() {
|
||||
println!("cargo:rustc-link-lib=dylib=unicorn-import");
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=dylib=unicorn");
|
||||
}
|
||||
} else {
|
||||
println!("cargo:rustc-link-arg=-Wl,-allow-multiple-definition");
|
||||
println!("cargo:rustc-link-lib=static=unicorn");
|
||||
println!("cargo:rustc-link-lib=pthread");
|
||||
println!("cargo:rustc-link-lib=m");
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
build_with_cmake();
|
||||
}
|
||||
}
|
||||
}
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
//! License: GNU GENERAL PUBLIC LICENSE Version 2
|
||||
|
||||
const std = @import("std");
|
||||
const MIN_ZIG_VERSION: []const u8 = "0.13.0";
|
||||
const MIN_ZIG_VERSION_ERR_MSG = "Please! Update zig toolchain to >= v" ++ MIN_ZIG_VERSION;
|
||||
|
||||
const SampleFileTypes = enum {
|
||||
c,
|
||||
cpp,
|
||||
zig,
|
||||
};
|
||||
|
||||
const SampleDescripton = struct {
|
||||
file_type: SampleFileTypes,
|
||||
root_file_path: []const u8,
|
||||
};
|
||||
|
||||
/// Create a module for the Zig Bindings
|
||||
///
|
||||
/// This will also get exported as a library that other zig projects can use
|
||||
/// as a dependency via the zig build system.
|
||||
fn create_unicorn_sys(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *std.Build.Module {
|
||||
const unicorn_sys = b.addModule("unicorn-sys", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_source_file = b.path("bindings/zig/unicorn/unicorn.zig"),
|
||||
});
|
||||
|
||||
// link libc
|
||||
unicorn_sys.link_libc = true;
|
||||
|
||||
// we need the c header for the zig-bindings
|
||||
unicorn_sys.addIncludePath(b.path("include"));
|
||||
unicorn_sys.addLibraryPath(b.path("build"));
|
||||
|
||||
// Linking to the Unicorn library
|
||||
if (target.result.abi == .msvc and target.result.os.tag == .windows) {
|
||||
unicorn_sys.linkSystemLibrary("unicorn.dll", .{});
|
||||
} else {
|
||||
unicorn_sys.linkSystemLibrary("unicorn", .{});
|
||||
}
|
||||
|
||||
return unicorn_sys;
|
||||
}
|
||||
|
||||
// Although this function looks imperative, note that its job is to
|
||||
// declaratively construct a build graph that will be executed by an external
|
||||
// runner.
|
||||
pub fn build(b: *std.Build) void {
|
||||
if (comptime !checkVersion())
|
||||
@compileError(MIN_ZIG_VERSION_ERR_MSG);
|
||||
|
||||
// Standard target options allows the person running `zig build` to choose
|
||||
// what target to build for. Here we do not override the defaults, which
|
||||
// means any target is allowed, and the default is native. Other options
|
||||
// for restricting supported target set are available.
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
// Standard optimization options allow the person running `zig build` to select
|
||||
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
|
||||
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
// Give the user the options to perform the cmake build in parallel or not
|
||||
// (eg. ci on macos will fail if parallel is enabled)
|
||||
//
|
||||
// flag: -Dparallel=true/false
|
||||
const parallel_cmake = b.option(bool, "parallel", "Enable parallel cmake build") orelse true;
|
||||
|
||||
// flag: -DSamples=True/False
|
||||
const samples = b.option(bool, "Samples", "Build all Samples [default: true]") orelse true;
|
||||
|
||||
const sample_bins = [_]SampleDescripton{
|
||||
.{ .file_type = .zig, .root_file_path = "bindings/zig/sample/sample_riscv_zig.zig" },
|
||||
.{ .file_type = .c, .root_file_path = "samples/sample_arm.c" },
|
||||
.{ .file_type = .c, .root_file_path = "samples/sample_arm64.c" },
|
||||
.{ .file_type = .c, .root_file_path = "samples/sample_ctl.c" },
|
||||
.{ .file_type = .c, .root_file_path = "samples/sample_batch_reg.c" },
|
||||
.{ .file_type = .c, .root_file_path = "samples/sample_m68k.c" },
|
||||
.{ .file_type = .c, .root_file_path = "samples/sample_riscv.c" },
|
||||
.{ .file_type = .c, .root_file_path = "samples/sample_sparc.c" },
|
||||
.{ .file_type = .c, .root_file_path = "samples/sample_s390x.c" },
|
||||
.{ .file_type = .c, .root_file_path = "samples/shellcode.c" },
|
||||
.{ .file_type = .c, .root_file_path = "samples/sample_tricore.c" },
|
||||
.{ .file_type = .c, .root_file_path = "samples/sample_x86.c" },
|
||||
.{ .file_type = .c, .root_file_path = "samples/sample_x86_32_gdt_and_seg_regs.c" },
|
||||
};
|
||||
|
||||
// make a module for Zig Bindings
|
||||
const unicorn_sys = create_unicorn_sys(b, target, optimize);
|
||||
|
||||
// Build Samples
|
||||
if (samples) {
|
||||
for (sample_bins) |sample| {
|
||||
const sample_bin = buildExe(b, .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.filetype = sample.file_type,
|
||||
.filepath = sample.root_file_path,
|
||||
});
|
||||
|
||||
// import the unicorn sys module if this is a zig build
|
||||
if (sample.file_type == .zig) {
|
||||
sample_bin.root_module.addImport("unicorn", unicorn_sys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CMake Build
|
||||
const cmake = cmakeBuild(b, parallel_cmake);
|
||||
const cmake_step = b.step("cmake", "Run cmake build");
|
||||
cmake_step.dependOn(&cmake.step);
|
||||
}
|
||||
|
||||
fn buildExe(b: *std.Build, info: BuildInfo) *std.Build.Step.Compile {
|
||||
const target = info.stdTarget();
|
||||
|
||||
const execonfig: std.Build.ExecutableOptions = switch (info.filetype) {
|
||||
.c, .cpp => .{
|
||||
.name = info.filename(),
|
||||
.target = info.target,
|
||||
.optimize = info.optimize,
|
||||
},
|
||||
else => .{
|
||||
.name = info.filename(),
|
||||
.target = info.target,
|
||||
.optimize = info.optimize,
|
||||
.root_source_file = b.path(info.filepath),
|
||||
},
|
||||
};
|
||||
const exe = b.addExecutable(execonfig);
|
||||
|
||||
if (info.filetype != .zig) {
|
||||
exe.addCSourceFile(.{
|
||||
.file = b.path(info.filepath),
|
||||
.flags = &.{
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
"-fno-sanitize=all",
|
||||
"-Wshadow",
|
||||
},
|
||||
});
|
||||
|
||||
// Ensure the C headers are available
|
||||
exe.addIncludePath(b.path("include"));
|
||||
|
||||
// Ensure the C library is available
|
||||
exe.addLibraryPath(b.path("build"));
|
||||
|
||||
// linking to OS-LibC or static-linking for:
|
||||
// Musl(Linux) [e.g: -Dtarget=native-linux-musl]
|
||||
// MinGW(Windows) [e.g: -Dtarget=native-windows-gnu (default)]
|
||||
if (info.filetype == .cpp and target.abi != .msvc)
|
||||
exe.linkLibCpp() // static-linking LLVM-libcxx (all targets) + libC
|
||||
else
|
||||
exe.linkLibC();
|
||||
|
||||
// Now link the C library
|
||||
if (target.abi == .msvc and target.os.tag == .windows) {
|
||||
exe.linkSystemLibrary("unicorn.dll");
|
||||
} else exe.linkSystemLibrary("unicorn");
|
||||
}
|
||||
|
||||
// Linking to the Unicorn library
|
||||
if (target.abi == .msvc and target.os.tag == .windows) {
|
||||
exe.want_lto = false;
|
||||
}
|
||||
|
||||
// This declares intent for the executable to be installed into the
|
||||
// standard location when the user invokes the "install" step (the default
|
||||
// step when running `zig build`).
|
||||
b.installArtifact(exe);
|
||||
|
||||
// This *creates* a RunStep in the build graph, to be executed when another
|
||||
// step is evaluated that depends on it. The next line below will establish
|
||||
// such a dependency.
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
|
||||
// By making the run step depend on the install step, it will be run from the
|
||||
// installation directory rather than directly from within the cache directory.
|
||||
// This is not necessary, however, if the application depends on other installed
|
||||
// files, this ensures they will be present and in the expected location.
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
|
||||
// This allows the user to pass arguments to the application in the build
|
||||
// command itself, like this: `zig build run -- arg1 arg2 etc`
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
// This creates a build step. It will be visible in the `zig build --help` menu,
|
||||
// and can be selected like this: `zig build run`
|
||||
// This will evaluate the `run` step rather than the default, which is "install".
|
||||
const run_step = b.step(info.filename(), b.fmt("Run the {s}.", .{info.filename()}));
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
|
||||
return exe;
|
||||
}
|
||||
|
||||
const PARALLEL_CMAKE_COMMAND = [_][]const u8{
|
||||
"cmake",
|
||||
"--build",
|
||||
"build",
|
||||
"--config",
|
||||
"release",
|
||||
"--parallel",
|
||||
};
|
||||
|
||||
const SINGLE_CMAKE_COMMAND = [_][]const u8{
|
||||
"cmake",
|
||||
"--build",
|
||||
"build",
|
||||
"--config",
|
||||
"release",
|
||||
};
|
||||
fn cmakeBuild(b: *std.Build, parallel_cmake: bool) *std.Build.Step.Run {
|
||||
const preconf = b.addSystemCommand(&.{
|
||||
"cmake",
|
||||
"-B",
|
||||
"build",
|
||||
"-DZIG_BUILD=ON",
|
||||
"-DUNICORN_BUILD_TESTS=OFF",
|
||||
"-DUNICORN_INSTALL=OFF",
|
||||
"-DCMAKE_BUILD_TYPE=Release",
|
||||
});
|
||||
|
||||
// build in parallel if requested
|
||||
const cmakebuild = b.addSystemCommand(blk: {
|
||||
if (parallel_cmake) {
|
||||
break :blk &PARALLEL_CMAKE_COMMAND;
|
||||
} else {
|
||||
break :blk &SINGLE_CMAKE_COMMAND;
|
||||
}
|
||||
});
|
||||
cmakebuild.step.dependOn(&preconf.step);
|
||||
return cmakebuild;
|
||||
}
|
||||
|
||||
// ensures the currently in-use zig version is at least the minimum required
|
||||
fn checkVersion() bool {
|
||||
const builtin = @import("builtin");
|
||||
if (!@hasDecl(builtin, "zig_version")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const needed_version = std.SemanticVersion.parse(MIN_ZIG_VERSION) catch unreachable;
|
||||
const version = builtin.zig_version;
|
||||
const order = version.order(needed_version);
|
||||
return order != .lt;
|
||||
}
|
||||
|
||||
const BuildInfo = struct {
|
||||
filepath: []const u8,
|
||||
filetype: SampleFileTypes,
|
||||
target: std.Build.ResolvedTarget,
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
|
||||
fn filename(self: BuildInfo) []const u8 {
|
||||
var split = std.mem.splitSequence(u8, std.fs.path.basename(self.filepath), ".");
|
||||
return split.first();
|
||||
}
|
||||
|
||||
fn stdTarget(self: *const BuildInfo) std.Target {
|
||||
return self.target.result;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
.{
|
||||
.name = .unicorn,
|
||||
.version = "2.1.4",
|
||||
.minimum_zig_version = "0.14.0-dev.3445+6c3cbb0c8",
|
||||
.fingerprint = 0x58fbd83f3bf465b6,
|
||||
.paths = .{""},
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
# https://cristianadam.eu/20190501/bundling-together-static-libraries-with-cmake/
|
||||
function(bundle_static_library tgt_name bundled_tgt_name library_name)
|
||||
list(APPEND static_libs ${tgt_name})
|
||||
set(dep_libs "")
|
||||
|
||||
function(_recursively_collect_dependencies input_target)
|
||||
set(_input_link_libraries LINK_LIBRARIES)
|
||||
get_target_property(_input_type ${input_target} TYPE)
|
||||
if (${_input_type} STREQUAL "INTERFACE_LIBRARY")
|
||||
set(_input_link_libraries INTERFACE_LINK_LIBRARIES)
|
||||
endif()
|
||||
get_target_property(public_dependencies ${input_target} ${_input_link_libraries})
|
||||
foreach(dependency IN LISTS public_dependencies)
|
||||
if(TARGET ${dependency})
|
||||
get_target_property(alias ${dependency} ALIASED_TARGET)
|
||||
if (TARGET ${alias})
|
||||
set(dependency ${alias})
|
||||
endif()
|
||||
get_target_property(_type ${dependency} TYPE)
|
||||
if (${_type} STREQUAL "STATIC_LIBRARY")
|
||||
list(APPEND static_libs ${dependency})
|
||||
endif()
|
||||
|
||||
get_property(library_already_added
|
||||
GLOBAL PROPERTY _${tgt_name}_static_bundle_${dependency})
|
||||
if (NOT library_already_added)
|
||||
set_property(GLOBAL PROPERTY _${tgt_name}_static_bundle_${dependency} ON)
|
||||
_recursively_collect_dependencies(${dependency})
|
||||
endif()
|
||||
elseif(dependency)
|
||||
list(APPEND dep_libs ${dependency})
|
||||
endif()
|
||||
endforeach()
|
||||
set(static_libs ${static_libs} PARENT_SCOPE)
|
||||
set(dep_libs ${dep_libs} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
_recursively_collect_dependencies(${tgt_name})
|
||||
|
||||
list(REMOVE_DUPLICATES static_libs)
|
||||
list(REMOVE_DUPLICATES dep_libs)
|
||||
|
||||
foreach(tgt IN LISTS static_libs)
|
||||
list(APPEND static_libs_objects $<TARGET_OBJECTS:${tgt}>)
|
||||
endforeach()
|
||||
|
||||
add_library(${bundled_tgt_name} STATIC ${static_libs_objects})
|
||||
set_target_properties(${bundled_tgt_name} PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES $<TARGET_PROPERTY:${tgt_name},INTERFACE_INCLUDE_DIRECTORIES>
|
||||
INTERFACE_LINK_LIBRARIES "${dep_libs}"
|
||||
OUTPUT_NAME "${library_name}"
|
||||
SYMLINK_NAME "${library_name}.o"
|
||||
)
|
||||
add_custom_command(TARGET ${bundled_tgt_name} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink "$<TARGET_FILE_NAME:${bundled_tgt_name}>" "$<TARGET_FILE_DIR:${bundled_tgt_name}>/$<TARGET_PROPERTY:${bundled_tgt_name},SYMLINK_NAME>"
|
||||
)
|
||||
endfunction()
|
||||
@@ -0,0 +1,17 @@
|
||||
# cross compile
|
||||
SET(CMAKE_SYSTEM_NAME Windows)
|
||||
|
||||
# set the compiler
|
||||
SET(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
|
||||
SET(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
|
||||
SET(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
|
||||
|
||||
# set the compiler search path
|
||||
SET(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
|
||||
|
||||
# adjust the default behaviour of the FIND_XXX() commands:
|
||||
# search headers and libraries in the target environment, search
|
||||
# programs in the host environment
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
@@ -0,0 +1,9 @@
|
||||
set(CMAKE_CROSSCOMPILING TRUE)
|
||||
# set the compiler
|
||||
if(WIN32)
|
||||
SET(ZIG_CC ${CMAKE_SOURCE_DIR}/bindings/zig/tools/zigcc.cmd)
|
||||
else()
|
||||
SET(ZIG_CC ${CMAKE_SOURCE_DIR}/bindings/zig/tools/zigcc.sh)
|
||||
endif()
|
||||
SET(CMAKE_C_COMPILER_ID ${ZIG_CC})
|
||||
SET(CMAKE_C_COMPILER ${ZIG_CC})
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
find . -maxdepth 1 "(" -name "*.c" -or -name "*.h" ")" -exec clang-format -i -style=file "{}" ";"
|
||||
find ./msvc -maxdepth 1 "(" -name "*.c" -or -name "*.h" ")" -exec clang-format -i -style=file "{}" ";"
|
||||
find ./include -maxdepth 2 "(" -name "*.c" -or -name "*.h" ")" -exec clang-format -i -style=file "{}" ";"
|
||||
find ./tests/unit -maxdepth 1 "(" -name "*.c" -or -name "*.h" ")" -exec clang-format -i -style=file "{}" ";"
|
||||
find ./samples -maxdepth 1 "(" -name "*.c" -or -name "*.h" ")" -exec clang-format -i -style=file "{}" ";"
|
||||
find ./qemu "(" -name "unicorn.c" -or -name "unicorn.h" -or -name "unicorn_arm.c" -or -name "unicorn_aarch64.c" ")" -exec clang-format -i -style=file "{}" ";"
|
||||
@@ -0,0 +1,2 @@
|
||||
This is a compatible glib library, customized for Unicorn.
|
||||
Based on glib 2.64.4.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,99 @@
|
||||
/* GLIB - Library of useful routines for C programming
|
||||
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Modified by the GLib Team and others 1997-2000. See the AUTHORS
|
||||
* file for a list of people on the GLib Team. See the ChangeLog
|
||||
* files for a list of changes. These files are distributed with
|
||||
* GLib at ftp://ftp.gtk.org/pub/gtk/.
|
||||
*/
|
||||
|
||||
#ifndef __G_ARRAY_H__
|
||||
#define __G_ARRAY_H__
|
||||
|
||||
#include "gtypes.h"
|
||||
|
||||
typedef struct _GBytes GBytes;
|
||||
typedef struct _GArray GArray;
|
||||
typedef struct _GByteArray GByteArray;
|
||||
typedef struct _GPtrArray GPtrArray;
|
||||
|
||||
struct _GArray
|
||||
{
|
||||
gchar *data;
|
||||
guint len;
|
||||
};
|
||||
|
||||
struct _GByteArray
|
||||
{
|
||||
guint8 *data;
|
||||
guint len;
|
||||
};
|
||||
|
||||
struct _GPtrArray
|
||||
{
|
||||
gpointer *pdata;
|
||||
guint len;
|
||||
};
|
||||
|
||||
/* Resizable arrays. remove fills any cleared spot and shortens the
|
||||
* array, while preserving the order. remove_fast will distort the
|
||||
* order by moving the last element to the position of the removed.
|
||||
*/
|
||||
|
||||
#define g_array_append_val(a,v) g_array_append_vals (a, &(v), 1)
|
||||
#define g_array_index(a,t,i) (((t*) (void *) (a)->data) [(i)])
|
||||
|
||||
GArray* g_array_append_vals (GArray *array,
|
||||
gconstpointer data,
|
||||
guint len);
|
||||
|
||||
GArray* g_array_new (gboolean zero_terminated, gboolean clear_, guint element_size);
|
||||
GArray* g_array_sized_new (gboolean zero_terminated,
|
||||
gboolean clear_,
|
||||
guint element_size,
|
||||
guint reserved_size);
|
||||
|
||||
gchar* g_array_free(GArray *array, gboolean free_segment);
|
||||
GArray* g_array_set_size(GArray *array, guint length);
|
||||
GArray*
|
||||
g_array_remove_range (GArray *farray,
|
||||
guint index_,
|
||||
guint length);
|
||||
|
||||
void g_ptr_array_set_free_func (GPtrArray *array,
|
||||
GDestroyNotify element_free_func);
|
||||
|
||||
/* Resizable pointer array. This interface is much less complicated
|
||||
* than the above. Add appends a pointer. Remove fills any cleared
|
||||
* spot and shortens the array. remove_fast will again distort order.
|
||||
*/
|
||||
#define g_ptr_array_index(array,index_) ((array)->pdata)[index_]
|
||||
GPtrArray* g_ptr_array_new_with_free_func (GDestroyNotify element_free_func);
|
||||
void g_ptr_array_add(GPtrArray *array, gpointer data);
|
||||
GPtrArray* g_ptr_array_sized_new (guint reserved_size);
|
||||
GPtrArray* g_ptr_array_remove_range (GPtrArray *array, guint index_, guint length);
|
||||
|
||||
/* Byte arrays, an array of guint8. Implemented as a GArray,
|
||||
* but type-safe.
|
||||
*/
|
||||
GByteArray* g_byte_array_sized_new(guint reserved_size);
|
||||
guint8* g_byte_array_free(GByteArray *array, gboolean free_segment);
|
||||
GByteArray* g_byte_array_append(GByteArray *array, const guint8 *data, guint len);
|
||||
GByteArray* g_byte_array_set_size(GByteArray *array, guint length);
|
||||
|
||||
#endif /* __G_ARRAY_H__ */
|
||||
@@ -0,0 +1,77 @@
|
||||
/* GLIB - Library of useful routines for C programming
|
||||
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Modified by the GLib Team and others 1997-2000. See the AUTHORS
|
||||
* file for a list of people on the GLib Team. See the ChangeLog
|
||||
* files for a list of changes. These files are distributed with
|
||||
* GLib at ftp://ftp.gtk.org/pub/gtk/.
|
||||
*/
|
||||
|
||||
#ifndef __G_HASH_H__
|
||||
#define __G_HASH_H__
|
||||
|
||||
#include "gtypes.h"
|
||||
|
||||
typedef struct _GHashTable GHashTable;
|
||||
|
||||
typedef gboolean (*GHRFunc) (gpointer key, gpointer value, gpointer user_data);
|
||||
|
||||
struct _GHashTableIter
|
||||
{
|
||||
/*< private >*/
|
||||
gpointer dummy1;
|
||||
gpointer dummy2;
|
||||
gpointer dummy3;
|
||||
int dummy4;
|
||||
gboolean dummy5;
|
||||
gpointer dummy6;
|
||||
};
|
||||
|
||||
GHashTable* g_hash_table_new (GHashFunc hash_func, GEqualFunc key_equal_func);
|
||||
|
||||
GHashTable* g_hash_table_new_full (GHashFunc hash_func,
|
||||
GEqualFunc key_equal_func,
|
||||
GDestroyNotify key_destroy_func,
|
||||
GDestroyNotify value_destroy_func);
|
||||
|
||||
void g_hash_table_destroy (GHashTable *hash_table);
|
||||
|
||||
gboolean g_hash_table_insert (GHashTable *hash_table, gpointer key, gpointer value);
|
||||
|
||||
void g_hash_table_replace (GHashTable *hash_table, gpointer key, gpointer value);
|
||||
|
||||
gboolean g_hash_table_remove (GHashTable *hash_table, gconstpointer key);
|
||||
|
||||
void g_hash_table_remove_all (GHashTable *hash_table);
|
||||
|
||||
gpointer g_hash_table_lookup (GHashTable *hash_table, gconstpointer key);
|
||||
|
||||
void g_hash_table_foreach (GHashTable *hash_table, GHFunc func, gpointer user_data);
|
||||
|
||||
guint g_hash_table_size (GHashTable *hash_table);
|
||||
|
||||
GHashTable* g_hash_table_ref (GHashTable *hash_table);
|
||||
|
||||
void g_hash_table_unref (GHashTable *hash_table);
|
||||
|
||||
/* Hash Functions
|
||||
*/
|
||||
gboolean g_int_equal (gconstpointer v1, gconstpointer v2);
|
||||
guint g_int_hash (gconstpointer v);
|
||||
|
||||
#endif /* __G_HASH_H__ */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
glib_compat.h replacement functionality for glib code used in qemu
|
||||
Copyright (C) 2016 Chris Eagle cseagle at gmail dot com
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GLIB_COMPAT_H
|
||||
#define __GLIB_COMPAT_H
|
||||
|
||||
#include "unicorn/platform.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define G_MAXUINT UINT_MAX
|
||||
#define G_MAXINT INT_MAX
|
||||
|
||||
#include "gtestutils.h"
|
||||
#include "gtypes.h"
|
||||
#include "garray.h"
|
||||
#include "gtree.h"
|
||||
#include "ghash.h"
|
||||
#include "gmem.h"
|
||||
#include "gslice.h"
|
||||
#include "gmessages.h"
|
||||
#include "gpattern.h"
|
||||
#include "grand.h"
|
||||
#include "glist.h"
|
||||
#include "gnode.h"
|
||||
|
||||
typedef gint (*GCompareDataFunc)(gconstpointer a,
|
||||
gconstpointer b,
|
||||
gpointer user_data);
|
||||
typedef void (*GFunc)(gpointer data, gpointer user_data);
|
||||
typedef gint (*GCompareFunc)(gconstpointer v1, gconstpointer v2);
|
||||
|
||||
guint g_str_hash(gconstpointer v);
|
||||
gboolean g_str_equal(gconstpointer v1, gconstpointer v2);
|
||||
guint g_int_hash(gconstpointer v);
|
||||
|
||||
gboolean g_int_equal(gconstpointer v1, gconstpointer v2);
|
||||
|
||||
int g_strcmp0(const char *str1, const char *str2);
|
||||
|
||||
GList *g_list_first(GList *list);
|
||||
void g_list_foreach(GList *list, GFunc func, gpointer user_data);
|
||||
void g_list_free(GList *list);
|
||||
GList *g_list_insert_sorted(GList *list, gpointer data, GCompareFunc compare);
|
||||
#define g_list_next(list) (list->next)
|
||||
GList *g_list_prepend(GList *list, gpointer data);
|
||||
GList *g_list_remove_link(GList *list, GList *llink);
|
||||
GList *g_list_sort(GList *list, GCompareFunc compare);
|
||||
|
||||
typedef struct _GSList {
|
||||
gpointer data;
|
||||
struct _GSList *next;
|
||||
} GSList;
|
||||
|
||||
GSList *g_slist_append(GSList *list, gpointer data);
|
||||
void g_slist_foreach(GSList *list, GFunc func, gpointer user_data);
|
||||
void g_slist_free(GSList *list);
|
||||
GSList *g_slist_prepend(GSList *list, gpointer data);
|
||||
GSList *g_slist_sort(GSList *list, GCompareFunc compare);
|
||||
GSList *g_slist_find_custom(GSList *list, gconstpointer data, GCompareFunc func);
|
||||
|
||||
/* replacement for g_malloc dependency */
|
||||
void g_free(gpointer ptr);
|
||||
gpointer g_realloc(gpointer ptr, size_t size);
|
||||
|
||||
char *g_strdup(const char *str);
|
||||
char *g_strdup_printf(const char *format, ...);
|
||||
char *g_strdup_vprintf(const char *format, va_list ap);
|
||||
char *g_strndup(const char *str, size_t n);
|
||||
void g_strfreev(char **v);
|
||||
gpointer g_memdup(gconstpointer mem, size_t byte_size);
|
||||
gpointer g_new_(size_t sz, size_t n_structs);
|
||||
gpointer g_new0_(size_t sz, size_t n_structs);
|
||||
gpointer g_renew_(size_t sz, gpointer mem, size_t n_structs);
|
||||
|
||||
gchar** g_strsplit (const gchar *string,
|
||||
const gchar *delimiter,
|
||||
gint max_tokens);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,154 @@
|
||||
/* GLIB - Library of useful routines for C programming
|
||||
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Modified by the GLib Team and others 1997-2000. See the AUTHORS
|
||||
* file for a list of people on the GLib Team. See the ChangeLog
|
||||
* files for a list of changes. These files are distributed with
|
||||
* GLib at ftp://ftp.gtk.org/pub/gtk/.
|
||||
*/
|
||||
|
||||
/*
|
||||
* MT safe
|
||||
*/
|
||||
|
||||
#include "gtypes.h"
|
||||
#include "glist.h"
|
||||
#include "gslice.h"
|
||||
#include "gmessages.h"
|
||||
|
||||
#define _g_list_alloc() g_slice_new (GList)
|
||||
#define _g_list_alloc0() g_slice_new0 (GList)
|
||||
#define _g_list_free1(list) g_slice_free (GList, list)
|
||||
|
||||
/**
|
||||
* g_list_alloc:
|
||||
*
|
||||
* Allocates space for one #GList element. It is called by
|
||||
* g_list_append(), g_list_prepend(), g_list_insert() and
|
||||
* g_list_insert_sorted() and so is rarely used on its own.
|
||||
*
|
||||
* Returns: a pointer to the newly-allocated #GList element
|
||||
**/
|
||||
GList *g_list_alloc (void)
|
||||
{
|
||||
return _g_list_alloc0 ();
|
||||
}
|
||||
|
||||
static inline GList *_g_list_remove_link (GList *list, GList *link)
|
||||
{
|
||||
if (link == NULL)
|
||||
return list;
|
||||
|
||||
if (link->prev)
|
||||
{
|
||||
if (link->prev->next == link)
|
||||
link->prev->next = link->next;
|
||||
//else
|
||||
// g_warning ("corrupted double-linked list detected");
|
||||
}
|
||||
if (link->next)
|
||||
{
|
||||
if (link->next->prev == link)
|
||||
link->next->prev = link->prev;
|
||||
//else
|
||||
// g_warning ("corrupted double-linked list detected");
|
||||
}
|
||||
|
||||
if (link == list)
|
||||
list = list->next;
|
||||
|
||||
link->next = NULL;
|
||||
link->prev = NULL;
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_list_delete_link:
|
||||
* @list: a #GList, this must point to the top of the list
|
||||
* @link_: node to delete from @list
|
||||
*
|
||||
* Removes the node link_ from the list and frees it.
|
||||
* Compare this to g_list_remove_link() which removes the node
|
||||
* without freeing it.
|
||||
*
|
||||
* Returns: the (possibly changed) start of the #GList
|
||||
*/
|
||||
GList *g_list_delete_link (GList *list, GList *link_)
|
||||
{
|
||||
list = _g_list_remove_link (list, link_);
|
||||
_g_list_free1 (link_);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_list_insert_before:
|
||||
* @list: a pointer to a #GList, this must point to the top of the list
|
||||
* @sibling: the list element before which the new element
|
||||
* is inserted or %NULL to insert at the end of the list
|
||||
* @data: the data for the new element
|
||||
*
|
||||
* Inserts a new element into the list before the given position.
|
||||
*
|
||||
* Returns: the (possibly changed) start of the #GList
|
||||
*/
|
||||
GList *g_list_insert_before (GList *list, GList *sibling, gpointer data)
|
||||
{
|
||||
if (list == NULL)
|
||||
{
|
||||
list = g_list_alloc ();
|
||||
list->data = data;
|
||||
g_return_val_if_fail (sibling == NULL, list);
|
||||
return list;
|
||||
}
|
||||
else if (sibling != NULL)
|
||||
{
|
||||
GList *node;
|
||||
|
||||
node = _g_list_alloc ();
|
||||
node->data = data;
|
||||
node->prev = sibling->prev;
|
||||
node->next = sibling;
|
||||
sibling->prev = node;
|
||||
if (node->prev != NULL)
|
||||
{
|
||||
node->prev->next = node;
|
||||
return list;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_return_val_if_fail (sibling == list, node);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GList *last;
|
||||
|
||||
for (last = list; last->next != NULL; last = last->next) {}
|
||||
|
||||
last->next = _g_list_alloc ();
|
||||
last->next->data = data;
|
||||
last->next->prev = last;
|
||||
last->next->next = NULL;
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/* GLIB - Library of useful routines for C programming
|
||||
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Modified by the GLib Team and others 1997-2000. See the AUTHORS
|
||||
* file for a list of people on the GLib Team. See the ChangeLog
|
||||
* files for a list of changes. These files are distributed with
|
||||
* GLib at ftp://ftp.gtk.org/pub/gtk/.
|
||||
*/
|
||||
|
||||
#ifndef __G_LIST_H__
|
||||
#define __G_LIST_H__
|
||||
|
||||
#include "gmem.h"
|
||||
|
||||
typedef struct _GList GList;
|
||||
|
||||
struct _GList
|
||||
{
|
||||
gpointer data;
|
||||
GList *next;
|
||||
GList *prev;
|
||||
};
|
||||
|
||||
|
||||
GList* g_list_insert_before (GList *list, GList *sibling, gpointer data);
|
||||
|
||||
GList* g_list_delete_link (GList *list, GList *link_);
|
||||
|
||||
#endif /* __G_LIST_H__ */
|
||||
@@ -0,0 +1,59 @@
|
||||
/* GLIB - Library of useful routines for C programming
|
||||
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Modified by the GLib Team and others 1997-2000. See the AUTHORS
|
||||
* file for a list of people on the GLib Team. See the ChangeLog
|
||||
* files for a list of changes. These files are distributed with
|
||||
* GLib at ftp://ftp.gtk.org/pub/gtk/.
|
||||
*/
|
||||
|
||||
/* This file must not include any other glib header file and must thus
|
||||
* not refer to variables from glibconfig.h
|
||||
*/
|
||||
|
||||
#ifndef __G_MACROS_H__
|
||||
#define __G_MACROS_H__
|
||||
|
||||
/* We include stddef.h to get the system's definition of NULL
|
||||
*/
|
||||
#include <stddef.h>
|
||||
|
||||
/* Here we provide G_GNUC_EXTENSION as an alias for __extension__,
|
||||
* where this is valid. This allows for warningless compilation of
|
||||
* "long long" types even in the presence of '-ansi -pedantic'.
|
||||
*/
|
||||
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 8)
|
||||
#define G_GNUC_EXTENSION __extension__
|
||||
#else
|
||||
#define G_GNUC_EXTENSION
|
||||
#endif
|
||||
|
||||
#if !(defined (G_STMT_START) && defined (G_STMT_END))
|
||||
#define G_STMT_START do
|
||||
#if defined (_MSC_VER) && (_MSC_VER >= 1500)
|
||||
#define G_STMT_END \
|
||||
__pragma(warning(push)) \
|
||||
__pragma(warning(disable:4127)) \
|
||||
while(0) \
|
||||
__pragma(warning(pop))
|
||||
#else
|
||||
#define G_STMT_END while (0)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif /* __G_MACROS_H__ */
|
||||
@@ -0,0 +1,257 @@
|
||||
/* GLIB - Library of useful routines for C programming
|
||||
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Modified by the GLib Team and others 1997-2000. See the AUTHORS
|
||||
* file for a list of people on the GLib Team. See the ChangeLog
|
||||
* files for a list of changes. These files are distributed with
|
||||
* GLib at ftp://ftp.gtk.org/pub/gtk/.
|
||||
*/
|
||||
|
||||
/*
|
||||
* MT safe
|
||||
*/
|
||||
|
||||
#include "gtypes.h"
|
||||
#include "gmem.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "gslice.h"
|
||||
|
||||
#define SIZE_OVERFLOWS(a,b) (((b) > 0 && (a) > G_MAXSIZE / (b)))
|
||||
|
||||
|
||||
/**
|
||||
* g_try_malloc:
|
||||
* @n_bytes: number of bytes to allocate.
|
||||
*
|
||||
* Attempts to allocate @n_bytes, and returns %NULL on failure.
|
||||
* Contrast with g_malloc(), which aborts the program on failure.
|
||||
*
|
||||
* Returns: the allocated memory, or %NULL.
|
||||
*/
|
||||
gpointer g_try_malloc (gsize n_bytes)
|
||||
{
|
||||
gpointer mem;
|
||||
|
||||
if (n_bytes)
|
||||
mem = malloc (n_bytes);
|
||||
else
|
||||
mem = NULL;
|
||||
|
||||
return mem;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_try_malloc_n:
|
||||
* @n_blocks: the number of blocks to allocate
|
||||
* @n_block_bytes: the size of each block in bytes
|
||||
*
|
||||
* This function is similar to g_try_malloc(), allocating (@n_blocks * @n_block_bytes) bytes,
|
||||
* but care is taken to detect possible overflow during multiplication.
|
||||
*
|
||||
* Since: 2.24
|
||||
* Returns: the allocated memory, or %NULL.
|
||||
*/
|
||||
gpointer g_try_malloc_n (gsize n_blocks, gsize n_block_bytes)
|
||||
{
|
||||
if (SIZE_OVERFLOWS (n_blocks, n_block_bytes))
|
||||
return NULL;
|
||||
|
||||
return g_try_malloc (n_blocks * n_block_bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_malloc:
|
||||
* @n_bytes: the number of bytes to allocate
|
||||
*
|
||||
* Allocates @n_bytes bytes of memory.
|
||||
* If @n_bytes is 0 it returns %NULL.
|
||||
*
|
||||
* Returns: a pointer to the allocated memory
|
||||
*/
|
||||
gpointer g_malloc (gsize n_bytes)
|
||||
{
|
||||
if (n_bytes) {
|
||||
gpointer mem;
|
||||
|
||||
mem = malloc (n_bytes);
|
||||
if (mem)
|
||||
return mem;
|
||||
|
||||
//g_error ("%s: failed to allocate %"G_GSIZE_FORMAT" bytes",
|
||||
// G_STRLOC, n_bytes);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_malloc_n:
|
||||
* @n_blocks: the number of blocks to allocate
|
||||
* @n_block_bytes: the size of each block in bytes
|
||||
*
|
||||
* This function is similar to g_malloc(), allocating (@n_blocks * @n_block_bytes) bytes,
|
||||
* but care is taken to detect possible overflow during multiplication.
|
||||
*
|
||||
* Since: 2.24
|
||||
* Returns: a pointer to the allocated memory
|
||||
*/
|
||||
gpointer g_malloc_n (gsize n_blocks, gsize n_block_bytes)
|
||||
{
|
||||
if (SIZE_OVERFLOWS (n_blocks, n_block_bytes)) {
|
||||
//g_error ("%s: overflow allocating %"G_GSIZE_FORMAT"*%"G_GSIZE_FORMAT" bytes",
|
||||
// G_STRLOC, n_blocks, n_block_bytes);
|
||||
}
|
||||
|
||||
return g_malloc (n_blocks * n_block_bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_malloc0:
|
||||
* @n_bytes: the number of bytes to allocate
|
||||
*
|
||||
* Allocates @n_bytes bytes of memory, initialized to 0's.
|
||||
* If @n_bytes is 0 it returns %NULL.
|
||||
*
|
||||
* Returns: a pointer to the allocated memory
|
||||
*/
|
||||
gpointer g_malloc0 (gsize n_bytes)
|
||||
{
|
||||
if (n_bytes) {
|
||||
gpointer mem;
|
||||
|
||||
mem = calloc (1, n_bytes);
|
||||
if (mem)
|
||||
return mem;
|
||||
|
||||
//g_error ("%s: failed to allocate %"G_GSIZE_FORMAT" bytes",
|
||||
// G_STRLOC, n_bytes);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_malloc0_n:
|
||||
* @n_blocks: the number of blocks to allocate
|
||||
* @n_block_bytes: the size of each block in bytes
|
||||
*
|
||||
* This function is similar to g_malloc0(), allocating (@n_blocks * @n_block_bytes) bytes,
|
||||
* but care is taken to detect possible overflow during multiplication.
|
||||
*
|
||||
* Since: 2.24
|
||||
* Returns: a pointer to the allocated memory
|
||||
*/
|
||||
gpointer g_malloc0_n (gsize n_blocks, gsize n_block_bytes)
|
||||
{
|
||||
if (SIZE_OVERFLOWS (n_blocks, n_block_bytes)) {
|
||||
//g_error ("%s: overflow allocating %"G_GSIZE_FORMAT"*%"G_GSIZE_FORMAT" bytes",
|
||||
// G_STRLOC, n_blocks, n_block_bytes);
|
||||
}
|
||||
|
||||
return g_malloc0 (n_blocks * n_block_bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_try_malloc0:
|
||||
* @n_bytes: number of bytes to allocate
|
||||
*
|
||||
* Attempts to allocate @n_bytes, initialized to 0's, and returns %NULL on
|
||||
* failure. Contrast with g_malloc0(), which aborts the program on failure.
|
||||
*
|
||||
* Since: 2.8
|
||||
* Returns: the allocated memory, or %NULL
|
||||
*/
|
||||
gpointer g_try_malloc0 (gsize n_bytes)
|
||||
{
|
||||
gpointer mem;
|
||||
|
||||
if (n_bytes)
|
||||
mem = calloc (1, n_bytes);
|
||||
else
|
||||
mem = NULL;
|
||||
|
||||
return mem;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_realloc:
|
||||
* @mem: (nullable): the memory to reallocate
|
||||
* @n_bytes: new size of the memory in bytes
|
||||
*
|
||||
* Reallocates the memory pointed to by @mem, so that it now has space for
|
||||
* @n_bytes bytes of memory. It returns the new address of the memory, which may
|
||||
* have been moved. @mem may be %NULL, in which case it's considered to
|
||||
* have zero-length. @n_bytes may be 0, in which case %NULL will be returned
|
||||
* and @mem will be freed unless it is %NULL.
|
||||
*
|
||||
* Returns: the new address of the allocated memory
|
||||
*/
|
||||
gpointer g_realloc (gpointer mem, gsize n_bytes)
|
||||
{
|
||||
gpointer newmem;
|
||||
|
||||
if (n_bytes) {
|
||||
newmem = realloc (mem, n_bytes);
|
||||
if (newmem)
|
||||
return newmem;
|
||||
|
||||
//g_error("%s: failed to allocate %"G_GSIZE_FORMAT" bytes", G_STRLOC, n_bytes);
|
||||
}
|
||||
|
||||
free (mem);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_realloc_n:
|
||||
* @mem: (nullable): the memory to reallocate
|
||||
* @n_blocks: the number of blocks to allocate
|
||||
* @n_block_bytes: the size of each block in bytes
|
||||
*
|
||||
* This function is similar to g_realloc(), allocating (@n_blocks * @n_block_bytes) bytes,
|
||||
* but care is taken to detect possible overflow during multiplication.
|
||||
*
|
||||
* Since: 2.24
|
||||
* Returns: the new address of the allocated memory
|
||||
*/
|
||||
gpointer g_realloc_n (gpointer mem, gsize n_blocks, gsize n_block_bytes)
|
||||
{
|
||||
if (SIZE_OVERFLOWS (n_blocks, n_block_bytes)) {
|
||||
//g_error ("%s: overflow allocating %"G_GSIZE_FORMAT"*%"G_GSIZE_FORMAT" bytes",
|
||||
// G_STRLOC, n_blocks, n_block_bytes);
|
||||
}
|
||||
|
||||
return g_realloc (mem, n_blocks * n_block_bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_free:
|
||||
* @mem: (nullable): the memory to free
|
||||
*
|
||||
* Frees the memory pointed to by @mem.
|
||||
*
|
||||
* If @mem is %NULL it simply returns, so there is no need to check @mem
|
||||
* against %NULL before calling this function.
|
||||
*/
|
||||
void g_free (gpointer mem)
|
||||
{
|
||||
free (mem);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/* GLIB - Library of useful routines for C programming
|
||||
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Modified by the GLib Team and others 1997-2000. See the AUTHORS
|
||||
* file for a list of people on the GLib Team. See the ChangeLog
|
||||
* files for a list of changes. These files are distributed with
|
||||
* GLib at ftp://ftp.gtk.org/pub/gtk/.
|
||||
*/
|
||||
|
||||
#ifndef __G_MEM_H__
|
||||
#define __G_MEM_H__
|
||||
|
||||
#include <limits.h>
|
||||
#include "gmacros.h"
|
||||
|
||||
#define G_MAXSIZE ULONG_MAX
|
||||
|
||||
/* Optimise: avoid the call to the (slower) _n function if we can
|
||||
* determine at compile-time that no overflow happens.
|
||||
*/
|
||||
#if defined (__GNUC__) && (__GNUC__ >= 2) && defined (__OPTIMIZE__)
|
||||
# define _G_NEW(struct_type, n_structs, func) \
|
||||
(struct_type *) (G_GNUC_EXTENSION ({ \
|
||||
gsize __n = (gsize) (n_structs); \
|
||||
gsize __s = sizeof (struct_type); \
|
||||
gpointer __p; \
|
||||
if (__s == 1) \
|
||||
__p = g_##func (__n); \
|
||||
else if (__builtin_constant_p (__n) && \
|
||||
(__s == 0 || __n <= G_MAXSIZE / __s)) \
|
||||
__p = g_##func (__n * __s); \
|
||||
else \
|
||||
__p = g_##func##_n (__n, __s); \
|
||||
__p; \
|
||||
}))
|
||||
# define _G_RENEW(struct_type, mem, n_structs, func) \
|
||||
(struct_type *) (G_GNUC_EXTENSION ({ \
|
||||
gsize __n = (gsize) (n_structs); \
|
||||
gsize __s = sizeof (struct_type); \
|
||||
gpointer __p = (gpointer) (mem); \
|
||||
if (__s == 1) \
|
||||
__p = g_##func (__p, __n); \
|
||||
else if (__builtin_constant_p (__n) && \
|
||||
(__s == 0 || __n <= G_MAXSIZE / __s)) \
|
||||
__p = g_##func (__p, __n * __s); \
|
||||
else \
|
||||
__p = g_##func##_n (__p, __n, __s); \
|
||||
__p; \
|
||||
}))
|
||||
|
||||
#else
|
||||
/* Unoptimised version: always call the _n() function. */
|
||||
#define _G_NEW(struct_type, n_structs, func) \
|
||||
((struct_type *) g_##func##_n ((n_structs), sizeof (struct_type)))
|
||||
#define _G_RENEW(struct_type, mem, n_structs, func) \
|
||||
((struct_type *) g_##func##_n (mem, (n_structs), sizeof (struct_type)))
|
||||
|
||||
#endif
|
||||
|
||||
gpointer g_try_malloc (gsize n_bytes);
|
||||
|
||||
gpointer g_try_malloc0 (gsize n_bytes);
|
||||
|
||||
gpointer g_try_malloc_n (gsize n_blocks, gsize n_block_bytes);
|
||||
|
||||
gpointer g_malloc0_n (gsize n_blocks, gsize n_block_bytes);
|
||||
|
||||
gpointer g_realloc_n (gpointer mem, gsize n_blocks, gsize n_block_bytes);
|
||||
|
||||
gpointer g_malloc_n (gsize n_blocks, gsize n_block_bytes);
|
||||
|
||||
gpointer g_malloc0 (gsize n_bytes);
|
||||
|
||||
gpointer g_malloc (gsize n_bytes);
|
||||
|
||||
void g_free (gpointer mem);
|
||||
|
||||
/**
|
||||
* g_try_new:
|
||||
* @struct_type: the type of the elements to allocate
|
||||
* @n_structs: the number of elements to allocate
|
||||
*
|
||||
* Attempts to allocate @n_structs elements of type @struct_type, and returns
|
||||
* %NULL on failure. Contrast with g_new(), which aborts the program on failure.
|
||||
* The returned pointer is cast to a pointer to the given type.
|
||||
* The function returns %NULL when @n_structs is 0 of if an overflow occurs.
|
||||
*
|
||||
* Since: 2.8
|
||||
* Returns: a pointer to the allocated memory, cast to a pointer to @struct_type
|
||||
*/
|
||||
#define g_try_new(struct_type, n_structs) _G_NEW (struct_type, n_structs, try_malloc)
|
||||
#define g_new0(struct_type, n_structs) _G_NEW (struct_type, n_structs, malloc0)
|
||||
#define g_new(struct_type, n_structs) _G_NEW (struct_type, n_structs, malloc)
|
||||
#define g_renew(struct_type, mem, n_structs) _G_RENEW (struct_type, mem, n_structs, realloc)
|
||||
|
||||
#endif /* __G_MEM_H__ */
|
||||
@@ -0,0 +1,35 @@
|
||||
/* GLIB - Library of useful routines for C programming
|
||||
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Modified by the GLib Team and others 1997-2000. See the AUTHORS
|
||||
* file for a list of people on the GLib Team. See the ChangeLog
|
||||
* files for a list of changes. These files are distributed with
|
||||
* GLib at ftp://ftp.gtk.org/pub/gtk/.
|
||||
*/
|
||||
|
||||
#ifndef __G_MESSAGES_H__
|
||||
#define __G_MESSAGES_H__
|
||||
|
||||
#include "gmacros.h"
|
||||
|
||||
#define g_return_val_if_fail(expr,val) G_STMT_START{ (void)0; }G_STMT_END
|
||||
#define g_return_if_fail(expr) G_STMT_START{ (void)0; }G_STMT_END
|
||||
#define g_return_if_reached() G_STMT_START{ return; }G_STMT_END
|
||||
#define g_return_val_if_reached(val) G_STMT_START{ return (val); }G_STMT_END
|
||||
|
||||
#endif /* __G_MESSAGES_H__ */
|
||||
@@ -0,0 +1,39 @@
|
||||
/* GLIB - Library of useful routines for C programming
|
||||
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Modified by the GLib Team and others 1997-2000. See the AUTHORS
|
||||
* file for a list of people on the GLib Team. See the ChangeLog
|
||||
* files for a list of changes. These files are distributed with
|
||||
* GLib at ftp://ftp.gtk.org/pub/gtk/.
|
||||
*/
|
||||
|
||||
#ifndef __G_NODE_H__
|
||||
#define __G_NODE_H__
|
||||
|
||||
#include "gmem.h"
|
||||
|
||||
/* Tree traverse orders */
|
||||
typedef enum
|
||||
{
|
||||
G_IN_ORDER,
|
||||
G_PRE_ORDER,
|
||||
G_POST_ORDER,
|
||||
G_LEVEL_ORDER
|
||||
} GTraverseType;
|
||||
|
||||
#endif /* __G_NODE_H__ */
|
||||
@@ -0,0 +1,400 @@
|
||||
/* GLIB - Library of useful routines for C programming
|
||||
* Copyright (C) 1995-1997, 1999 Peter Mattis, Red Hat, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "gpattern.h"
|
||||
|
||||
#include "gmacros.h"
|
||||
#include "gmessages.h"
|
||||
#include "gmem.h"
|
||||
|
||||
/**
|
||||
* SECTION:patterns
|
||||
* @title: Glob-style pattern matching
|
||||
* @short_description: matches strings against patterns containing '*'
|
||||
* (wildcard) and '?' (joker)
|
||||
*
|
||||
* The g_pattern_match* functions match a string
|
||||
* against a pattern containing '*' and '?' wildcards with similar
|
||||
* semantics as the standard glob() function: '*' matches an arbitrary,
|
||||
* possibly empty, string, '?' matches an arbitrary character.
|
||||
*
|
||||
* Note that in contrast to glob(), the '/' character can be matched by
|
||||
* the wildcards, there are no '[...]' character ranges and '*' and '?'
|
||||
* can not be escaped to include them literally in a pattern.
|
||||
*
|
||||
* When multiple strings must be matched against the same pattern, it
|
||||
* is better to compile the pattern to a #GPatternSpec using
|
||||
* g_pattern_spec_new() and use g_pattern_match_string() instead of
|
||||
* g_pattern_match_simple(). This avoids the overhead of repeated
|
||||
* pattern compilation.
|
||||
**/
|
||||
|
||||
/**
|
||||
* GPatternSpec:
|
||||
*
|
||||
* A GPatternSpec struct is the 'compiled' form of a pattern. This
|
||||
* structure is opaque and its fields cannot be accessed directly.
|
||||
*/
|
||||
|
||||
/* keep enum and structure of gpattern.c and patterntest.c in sync */
|
||||
typedef enum
|
||||
{
|
||||
G_MATCH_ALL, /* "*A?A*" */
|
||||
G_MATCH_ALL_TAIL, /* "*A?AA" */
|
||||
G_MATCH_HEAD, /* "AAAA*" */
|
||||
G_MATCH_TAIL, /* "*AAAA" */
|
||||
G_MATCH_EXACT, /* "AAAAA" */
|
||||
G_MATCH_LAST
|
||||
} GMatchType;
|
||||
|
||||
struct _GPatternSpec
|
||||
{
|
||||
GMatchType match_type;
|
||||
guint pattern_length;
|
||||
guint min_length;
|
||||
guint max_length;
|
||||
gchar *pattern;
|
||||
};
|
||||
|
||||
|
||||
/* --- functions --- */
|
||||
static inline gboolean g_pattern_ph_match (const gchar *match_pattern,
|
||||
const gchar *match_string,
|
||||
gboolean *wildcard_reached_p)
|
||||
{
|
||||
const gchar *pattern, *string;
|
||||
gchar ch;
|
||||
|
||||
pattern = match_pattern;
|
||||
string = match_string;
|
||||
|
||||
ch = *pattern;
|
||||
pattern++;
|
||||
while (ch)
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case '?':
|
||||
if (!*string)
|
||||
return FALSE;
|
||||
string = string + 1;
|
||||
break;
|
||||
|
||||
case '*':
|
||||
*wildcard_reached_p = TRUE;
|
||||
do
|
||||
{
|
||||
ch = *pattern;
|
||||
pattern++;
|
||||
if (ch == '?')
|
||||
{
|
||||
if (!*string)
|
||||
return FALSE;
|
||||
string = string + 1;
|
||||
}
|
||||
}
|
||||
while (ch == '*' || ch == '?');
|
||||
if (!ch)
|
||||
return TRUE;
|
||||
do
|
||||
{
|
||||
gboolean next_wildcard_reached = FALSE;
|
||||
while (ch != *string)
|
||||
{
|
||||
if (!*string)
|
||||
return FALSE;
|
||||
string = string + 1;
|
||||
}
|
||||
string++;
|
||||
if (g_pattern_ph_match (pattern, string, &next_wildcard_reached))
|
||||
return TRUE;
|
||||
if (next_wildcard_reached)
|
||||
/* the forthcoming pattern substring up to the next wildcard has
|
||||
* been matched, but a mismatch occurred for the rest of the
|
||||
* pattern, following the next wildcard.
|
||||
* there's no need to advance the current match position any
|
||||
* further if the rest pattern will not match.
|
||||
*/
|
||||
return FALSE;
|
||||
}
|
||||
while (*string);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (ch == *string)
|
||||
string++;
|
||||
else
|
||||
return FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
ch = *pattern;
|
||||
pattern++;
|
||||
}
|
||||
|
||||
return *string == 0;
|
||||
}
|
||||
|
||||
static gchar *string_reverse(const gchar *string, gint string_length)
|
||||
{
|
||||
gchar *new_string;
|
||||
gint i, j;
|
||||
if (string == NULL || string_length <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
new_string = g_new(gchar, string_length + 1);
|
||||
if (new_string) {
|
||||
for (i = 0; i < string_length; i++) {
|
||||
j = string_length - i - 1;
|
||||
new_string[j] = string[i];
|
||||
}
|
||||
new_string[string_length] = 0;
|
||||
}
|
||||
|
||||
return new_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_pattern_match:
|
||||
* @pspec: a #GPatternSpec
|
||||
* @string_length: the length of @string (in bytes, i.e. strlen(),
|
||||
* not g_utf8_strlen())
|
||||
* @string: the UTF-8 encoded string to match
|
||||
* @string_reversed: (nullable): the reverse of @string or %NULL
|
||||
*
|
||||
* Matches a string against a compiled pattern. Passing the correct
|
||||
* length of the string given is mandatory. The reversed string can be
|
||||
* omitted by passing %NULL, this is more efficient if the reversed
|
||||
* version of the string to be matched is not at hand, as
|
||||
* g_pattern_match() will only construct it if the compiled pattern
|
||||
* requires reverse matches.
|
||||
*
|
||||
* Note that, if the user code will (possibly) match a string against a
|
||||
* multitude of patterns containing wildcards, chances are high that
|
||||
* some patterns will require a reversed string. In this case, it's
|
||||
* more efficient to provide the reversed string to avoid multiple
|
||||
* constructions thereof in the various calls to g_pattern_match().
|
||||
*
|
||||
* Note also that the reverse of a UTF-8 encoded string can in general
|
||||
* not be obtained by g_strreverse(). This works only if the string
|
||||
* does not contain any multibyte characters. GLib offers the
|
||||
* g_utf8_strreverse() function to reverse UTF-8 encoded strings.
|
||||
*
|
||||
* Returns: %TRUE if @string matches @pspec
|
||||
**/
|
||||
gboolean g_pattern_match (GPatternSpec *pspec,
|
||||
guint string_length,
|
||||
const gchar *string,
|
||||
const gchar *string_reversed)
|
||||
{
|
||||
g_return_val_if_fail (pspec != NULL, FALSE);
|
||||
g_return_val_if_fail (string != NULL, FALSE);
|
||||
|
||||
if (string_length < pspec->min_length ||
|
||||
string_length > pspec->max_length)
|
||||
return FALSE;
|
||||
|
||||
switch (pspec->match_type)
|
||||
{
|
||||
gboolean dummy;
|
||||
case G_MATCH_ALL:
|
||||
return g_pattern_ph_match (pspec->pattern, string, &dummy);
|
||||
case G_MATCH_ALL_TAIL:
|
||||
if (string_reversed)
|
||||
return g_pattern_ph_match (pspec->pattern, string_reversed, &dummy);
|
||||
else
|
||||
{
|
||||
gboolean result;
|
||||
gchar *tmp;
|
||||
tmp = string_reverse (string, string_length);
|
||||
result = g_pattern_ph_match (pspec->pattern, tmp, &dummy);
|
||||
g_free (tmp);
|
||||
return result;
|
||||
}
|
||||
case G_MATCH_HEAD:
|
||||
if (pspec->pattern_length == string_length)
|
||||
return strcmp (pspec->pattern, string) == 0;
|
||||
else if (pspec->pattern_length)
|
||||
return strncmp (pspec->pattern, string, pspec->pattern_length) == 0;
|
||||
else
|
||||
return TRUE;
|
||||
case G_MATCH_TAIL:
|
||||
if (pspec->pattern_length)
|
||||
return strcmp (pspec->pattern, string + (string_length - pspec->pattern_length)) == 0;
|
||||
else
|
||||
return TRUE;
|
||||
case G_MATCH_EXACT:
|
||||
if (pspec->pattern_length != string_length)
|
||||
return FALSE;
|
||||
else
|
||||
return strcmp (pspec->pattern, string) == 0;
|
||||
default:
|
||||
g_return_val_if_fail (pspec->match_type < G_MATCH_LAST, FALSE);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* g_pattern_spec_new:
|
||||
* @pattern: a zero-terminated UTF-8 encoded string
|
||||
*
|
||||
* Compiles a pattern to a #GPatternSpec.
|
||||
*
|
||||
* Returns: a newly-allocated #GPatternSpec
|
||||
**/
|
||||
GPatternSpec* g_pattern_spec_new (const gchar *pattern)
|
||||
{
|
||||
GPatternSpec *pspec;
|
||||
gboolean seen_joker = FALSE, seen_wildcard = FALSE, more_wildcards = FALSE;
|
||||
gint hw_pos = -1, tw_pos = -1, hj_pos = -1, tj_pos = -1;
|
||||
gboolean follows_wildcard = FALSE;
|
||||
guint pending_jokers = 0;
|
||||
const gchar *s;
|
||||
gchar *d;
|
||||
guint i;
|
||||
|
||||
g_return_val_if_fail (pattern != NULL, NULL);
|
||||
|
||||
/* canonicalize pattern and collect necessary stats */
|
||||
pspec = g_new (GPatternSpec, 1);
|
||||
pspec->pattern_length = strlen (pattern);
|
||||
pspec->min_length = 0;
|
||||
pspec->max_length = 0;
|
||||
pspec->pattern = g_new (gchar, pspec->pattern_length + 1);
|
||||
d = pspec->pattern;
|
||||
for (i = 0, s = pattern; *s != 0; s++)
|
||||
{
|
||||
switch (*s)
|
||||
{
|
||||
case '*':
|
||||
if (follows_wildcard) /* compress multiple wildcards */
|
||||
{
|
||||
pspec->pattern_length--;
|
||||
continue;
|
||||
}
|
||||
follows_wildcard = TRUE;
|
||||
if (hw_pos < 0)
|
||||
hw_pos = i;
|
||||
tw_pos = i;
|
||||
break;
|
||||
case '?':
|
||||
pending_jokers++;
|
||||
pspec->min_length++;
|
||||
pspec->max_length += 4; /* maximum UTF-8 character length */
|
||||
continue;
|
||||
default:
|
||||
for (; pending_jokers; pending_jokers--, i++) {
|
||||
*d++ = '?';
|
||||
if (hj_pos < 0)
|
||||
hj_pos = i;
|
||||
tj_pos = i;
|
||||
}
|
||||
follows_wildcard = FALSE;
|
||||
pspec->min_length++;
|
||||
pspec->max_length++;
|
||||
break;
|
||||
}
|
||||
*d++ = *s;
|
||||
i++;
|
||||
}
|
||||
for (; pending_jokers; pending_jokers--) {
|
||||
*d++ = '?';
|
||||
if (hj_pos < 0)
|
||||
hj_pos = i;
|
||||
tj_pos = i;
|
||||
}
|
||||
*d++ = 0;
|
||||
seen_joker = hj_pos >= 0;
|
||||
seen_wildcard = hw_pos >= 0;
|
||||
more_wildcards = seen_wildcard && hw_pos != tw_pos;
|
||||
if (seen_wildcard)
|
||||
pspec->max_length = UINT_MAX;
|
||||
|
||||
/* special case sole head/tail wildcard or exact matches */
|
||||
if (!seen_joker && !more_wildcards)
|
||||
{
|
||||
if (pspec->pattern[0] == '*')
|
||||
{
|
||||
pspec->match_type = G_MATCH_TAIL;
|
||||
memmove (pspec->pattern, pspec->pattern + 1, --pspec->pattern_length);
|
||||
pspec->pattern[pspec->pattern_length] = 0;
|
||||
return pspec;
|
||||
}
|
||||
if (pspec->pattern_length > 0 &&
|
||||
pspec->pattern[pspec->pattern_length - 1] == '*')
|
||||
{
|
||||
pspec->match_type = G_MATCH_HEAD;
|
||||
pspec->pattern[--pspec->pattern_length] = 0;
|
||||
return pspec;
|
||||
}
|
||||
if (!seen_wildcard)
|
||||
{
|
||||
pspec->match_type = G_MATCH_EXACT;
|
||||
return pspec;
|
||||
}
|
||||
}
|
||||
|
||||
/* now just need to distinguish between head or tail match start */
|
||||
tw_pos = pspec->pattern_length - 1 - tw_pos; /* last pos to tail distance */
|
||||
tj_pos = pspec->pattern_length - 1 - tj_pos; /* last pos to tail distance */
|
||||
if (seen_wildcard)
|
||||
pspec->match_type = tw_pos > hw_pos ? G_MATCH_ALL_TAIL : G_MATCH_ALL;
|
||||
else /* seen_joker */
|
||||
pspec->match_type = tj_pos > hj_pos ? G_MATCH_ALL_TAIL : G_MATCH_ALL;
|
||||
if (pspec->match_type == G_MATCH_ALL_TAIL) {
|
||||
gchar *tmp = pspec->pattern;
|
||||
pspec->pattern = string_reverse (pspec->pattern, pspec->pattern_length);
|
||||
g_free (tmp);
|
||||
}
|
||||
return pspec;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_pattern_spec_free:
|
||||
* @pspec: a #GPatternSpec
|
||||
*
|
||||
* Frees the memory allocated for the #GPatternSpec.
|
||||
**/
|
||||
void g_pattern_spec_free (GPatternSpec *pspec)
|
||||
{
|
||||
g_return_if_fail (pspec != NULL);
|
||||
|
||||
g_free (pspec->pattern);
|
||||
g_free (pspec);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_pattern_match_string:
|
||||
* @pspec: a #GPatternSpec
|
||||
* @string: the UTF-8 encoded string to match
|
||||
*
|
||||
* Matches a string against a compiled pattern. If the string is to be
|
||||
* matched against more than one pattern, consider using
|
||||
* g_pattern_match() instead while supplying the reversed string.
|
||||
*
|
||||
* Returns: %TRUE if @string matches @pspec
|
||||
**/
|
||||
gboolean g_pattern_match_string (GPatternSpec *pspec, const gchar *string)
|
||||
{
|
||||
g_return_val_if_fail (pspec != NULL, FALSE);
|
||||
g_return_val_if_fail (string != NULL, FALSE);
|
||||
|
||||
return g_pattern_match (pspec, strlen (string), string, NULL);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/* GLIB - Library of useful routines for C programming
|
||||
* Copyright (C) 1995-1997, 1999 Peter Mattis, Red Hat, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __G_PATTERN_H__
|
||||
#define __G_PATTERN_H__
|
||||
|
||||
#include "gtypes.h"
|
||||
|
||||
typedef struct _GPatternSpec GPatternSpec;
|
||||
|
||||
GPatternSpec* g_pattern_spec_new (const gchar *pattern);
|
||||
void g_pattern_spec_free (GPatternSpec *pspec);
|
||||
gboolean g_pattern_match (GPatternSpec *pspec,
|
||||
guint string_length,
|
||||
const gchar *string,
|
||||
const gchar *string_reversed);
|
||||
gboolean g_pattern_match_string (GPatternSpec *pspec,
|
||||
const gchar *string);
|
||||
|
||||
#endif /* __G_PATTERN_H__ */
|
||||
@@ -0,0 +1,384 @@
|
||||
/* GLIB - Library of useful routines for C programming
|
||||
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* Originally developed and coded by Makoto Matsumoto and Takuji
|
||||
* Nishimura. Please mail <matumoto@math.keio.ac.jp>, if you're using
|
||||
* code from this file in your own programs or libraries.
|
||||
* Further information on the Mersenne Twister can be found at
|
||||
* http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
|
||||
* This code was adapted to glib by Sebastian Wilhelmi.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Modified by the GLib Team and others 1997-2000. See the AUTHORS
|
||||
* file for a list of people on the GLib Team. See the ChangeLog
|
||||
* files for a list of changes. These files are distributed with
|
||||
* GLib at ftp://ftp.gtk.org/pub/gtk/.
|
||||
*/
|
||||
|
||||
/*
|
||||
* MT safe
|
||||
*/
|
||||
|
||||
#define _CRT_RAND_S
|
||||
|
||||
#include <math.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#ifndef _MSC_VER
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#else
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "grand.h"
|
||||
#include "gmem.h"
|
||||
#include "gmessages.h"
|
||||
|
||||
#define G_USEC_PER_SEC 1000000
|
||||
|
||||
#if defined(__MINGW64_VERSION_MAJOR) || defined(_WIN32)
|
||||
errno_t rand_s(unsigned int* randomValue);
|
||||
#endif
|
||||
|
||||
#define G_GINT64_CONSTANT(val) (val##L)
|
||||
|
||||
/* Period parameters */
|
||||
#define N 624
|
||||
#define M 397
|
||||
#define MATRIX_A 0x9908b0df /* constant vector a */
|
||||
#define UPPER_MASK 0x80000000 /* most significant w-r bits */
|
||||
#define LOWER_MASK 0x7fffffff /* least significant r bits */
|
||||
|
||||
/* Tempering parameters */
|
||||
#define TEMPERING_MASK_B 0x9d2c5680
|
||||
#define TEMPERING_MASK_C 0xefc60000
|
||||
#define TEMPERING_SHIFT_U(y) (y >> 11)
|
||||
#define TEMPERING_SHIFT_S(y) (y << 7)
|
||||
#define TEMPERING_SHIFT_T(y) (y << 15)
|
||||
#define TEMPERING_SHIFT_L(y) (y >> 18)
|
||||
|
||||
struct _GRand
|
||||
{
|
||||
guint32 mt[N]; /* the array for the state vector */
|
||||
guint mti;
|
||||
};
|
||||
|
||||
static guint get_random_version (void)
|
||||
{
|
||||
static gsize initialized = FALSE;
|
||||
static guint random_version;
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
// g_warning ("Unknown G_RANDOM_VERSION \"%s\". Using version 2.2.", version_string);
|
||||
random_version = 22;
|
||||
initialized = TRUE;
|
||||
}
|
||||
|
||||
return random_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_rand_set_seed:
|
||||
* @rand_: a #GRand
|
||||
* @seed: a value to reinitialize the random number generator
|
||||
*
|
||||
* Sets the seed for the random number generator #GRand to @seed.
|
||||
*/
|
||||
void g_rand_set_seed (GRand *rand, guint32 seed)
|
||||
{
|
||||
g_return_if_fail (rand != NULL);
|
||||
|
||||
switch (get_random_version ())
|
||||
{
|
||||
case 20:
|
||||
/* setting initial seeds to mt[N] using */
|
||||
/* the generator Line 25 of Table 1 in */
|
||||
/* [KNUTH 1981, The Art of Computer Programming */
|
||||
/* Vol. 2 (2nd Ed.), pp102] */
|
||||
|
||||
if (seed == 0) /* This would make the PRNG produce only zeros */
|
||||
seed = 0x6b842128; /* Just set it to another number */
|
||||
|
||||
rand->mt[0]= seed;
|
||||
for (rand->mti=1; rand->mti<N; rand->mti++)
|
||||
rand->mt[rand->mti] = (69069 * rand->mt[rand->mti-1]);
|
||||
|
||||
break;
|
||||
case 22:
|
||||
/* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
|
||||
/* In the previous version (see above), MSBs of the */
|
||||
/* seed affect only MSBs of the array mt[]. */
|
||||
|
||||
rand->mt[0]= seed;
|
||||
for (rand->mti=1; rand->mti<N; rand->mti++)
|
||||
rand->mt[rand->mti] = 1812433253UL *
|
||||
(rand->mt[rand->mti-1] ^ (rand->mt[rand->mti-1] >> 30)) + rand->mti;
|
||||
break;
|
||||
default:
|
||||
// g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* g_rand_new_with_seed:
|
||||
* @seed: a value to initialize the random number generator
|
||||
*
|
||||
* Creates a new random number generator initialized with @seed.
|
||||
*
|
||||
* Returns: the new #GRand
|
||||
**/
|
||||
GRand* g_rand_new_with_seed (guint32 seed)
|
||||
{
|
||||
GRand *rand = g_new0 (GRand, 1);
|
||||
g_rand_set_seed (rand, seed);
|
||||
return rand;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_rand_set_seed_array:
|
||||
* @rand_: a #GRand
|
||||
* @seed: array to initialize with
|
||||
* @seed_length: length of array
|
||||
*
|
||||
* Initializes the random number generator by an array of longs.
|
||||
* Array can be of arbitrary size, though only the first 624 values
|
||||
* are taken. This function is useful if you have many low entropy
|
||||
* seeds, or if you require more then 32 bits of actual entropy for
|
||||
* your application.
|
||||
*
|
||||
* Since: 2.4
|
||||
*/
|
||||
void g_rand_set_seed_array (GRand *rand, const guint32 *seed, guint seed_length)
|
||||
{
|
||||
guint i, j, k;
|
||||
|
||||
g_return_if_fail (rand != NULL);
|
||||
g_return_if_fail (seed_length >= 1);
|
||||
|
||||
g_rand_set_seed (rand, 19650218UL);
|
||||
|
||||
i=1; j=0;
|
||||
k = (N>seed_length ? N : seed_length);
|
||||
for (; k; k--)
|
||||
{
|
||||
rand->mt[i] = (rand->mt[i] ^
|
||||
((rand->mt[i-1] ^ (rand->mt[i-1] >> 30)) * 1664525UL))
|
||||
+ seed[j] + j; /* non linear */
|
||||
rand->mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */
|
||||
i++; j++;
|
||||
if (i>=N)
|
||||
{
|
||||
rand->mt[0] = rand->mt[N-1];
|
||||
i=1;
|
||||
}
|
||||
if (j>=seed_length)
|
||||
j=0;
|
||||
}
|
||||
for (k=N-1; k; k--)
|
||||
{
|
||||
rand->mt[i] = (rand->mt[i] ^
|
||||
((rand->mt[i-1] ^ (rand->mt[i-1] >> 30)) * 1566083941UL))
|
||||
- i; /* non linear */
|
||||
rand->mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */
|
||||
i++;
|
||||
if (i>=N)
|
||||
{
|
||||
rand->mt[0] = rand->mt[N-1];
|
||||
i=1;
|
||||
}
|
||||
}
|
||||
|
||||
rand->mt[0] = 0x80000000UL; /* MSB is 1; assuring non-zero initial array */
|
||||
}
|
||||
|
||||
/**
|
||||
* g_rand_new_with_seed_array:
|
||||
* @seed: an array of seeds to initialize the random number generator
|
||||
* @seed_length: an array of seeds to initialize the random number
|
||||
* generator
|
||||
*
|
||||
* Creates a new random number generator initialized with @seed.
|
||||
*
|
||||
* Returns: the new #GRand
|
||||
*
|
||||
* Since: 2.4
|
||||
*/
|
||||
GRand *g_rand_new_with_seed_array (const guint32 *seed, guint seed_length)
|
||||
{
|
||||
GRand *rand = g_new0 (GRand, 1);
|
||||
g_rand_set_seed_array (rand, seed, seed_length);
|
||||
return rand;
|
||||
}
|
||||
|
||||
gint64 g_get_real_time (void)
|
||||
{
|
||||
#if defined(unix) || defined(__unix__) || defined(__unix) || defined (__MINGW32__) || defined(__APPLE__) || defined(__HAIKU__)
|
||||
struct timeval r;
|
||||
|
||||
/* this is required on alpha, there the timeval structs are ints
|
||||
* not longs and a cast only would fail horribly */
|
||||
gettimeofday (&r, NULL);
|
||||
|
||||
return (((gint64) r.tv_sec) * 1000000) + r.tv_usec;
|
||||
#else
|
||||
FILETIME ft;
|
||||
guint64 time64;
|
||||
|
||||
GetSystemTimeAsFileTime (&ft);
|
||||
memmove (&time64, &ft, sizeof (FILETIME));
|
||||
|
||||
/* Convert from 100s of nanoseconds since 1601-01-01
|
||||
* to Unix epoch. This is Y2038 safe.
|
||||
*/
|
||||
time64 -= G_GINT64_CONSTANT (116444736000000000);
|
||||
time64 /= 10;
|
||||
|
||||
return time64;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* g_rand_new:
|
||||
*
|
||||
* Creates a new random number generator initialized with a seed taken
|
||||
* either from `/dev/urandom` (if existing) or from the current time
|
||||
* (as a fallback).
|
||||
*
|
||||
* On Windows, the seed is taken from rand_s().
|
||||
*
|
||||
* Returns: the new #GRand
|
||||
*/
|
||||
GRand *g_rand_new (void)
|
||||
{
|
||||
guint32 seed[4];
|
||||
#if defined(unix) || defined(__unix__) || defined(__unix) || defined(__APPLE__) || defined(__HAIKU__)
|
||||
static gboolean dev_urandom_exists = TRUE;
|
||||
|
||||
if (dev_urandom_exists)
|
||||
{
|
||||
FILE* dev_urandom;
|
||||
|
||||
do
|
||||
{
|
||||
dev_urandom = fopen("/dev/urandom", "rb");
|
||||
}
|
||||
while (dev_urandom == NULL && errno == EINTR);
|
||||
|
||||
if (dev_urandom)
|
||||
{
|
||||
int r;
|
||||
|
||||
setvbuf (dev_urandom, NULL, _IONBF, 0);
|
||||
do
|
||||
{
|
||||
errno = 0;
|
||||
r = fread (seed, sizeof (seed), 1, dev_urandom);
|
||||
}
|
||||
while (errno == EINTR);
|
||||
|
||||
if (r != 1)
|
||||
dev_urandom_exists = FALSE;
|
||||
|
||||
fclose (dev_urandom);
|
||||
}
|
||||
else
|
||||
dev_urandom_exists = FALSE;
|
||||
}
|
||||
|
||||
if (!dev_urandom_exists)
|
||||
{
|
||||
gint64 now_us = g_get_real_time ();
|
||||
seed[0] = now_us / G_USEC_PER_SEC;
|
||||
seed[1] = now_us % G_USEC_PER_SEC;
|
||||
seed[2] = getpid ();
|
||||
seed[3] = getppid ();
|
||||
}
|
||||
#else /* G_OS_WIN32 */
|
||||
/* rand_s() is only available since Visual Studio 2005 and
|
||||
* MinGW-w64 has a wrapper that will emulate rand_s() if it's not in msvcrt
|
||||
*/
|
||||
#if (defined(_MSC_VER) && _MSC_VER >= 1400) || defined(__MINGW64_VERSION_MAJOR)
|
||||
gint i;
|
||||
|
||||
for (i = 0; i < 4;/* array size of seed */ i++) {
|
||||
rand_s(&seed[i]);
|
||||
}
|
||||
#else
|
||||
#warning Using insecure seed for random number generation because of missing rand_s() in Windows XP
|
||||
GTimeVal now;
|
||||
|
||||
g_get_current_time (&now);
|
||||
seed[0] = now.tv_sec;
|
||||
seed[1] = now.tv_usec;
|
||||
seed[2] = getpid ();
|
||||
seed[3] = 0;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
return g_rand_new_with_seed_array (seed, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_rand_int:
|
||||
* @rand_: a #GRand
|
||||
*
|
||||
* Returns the next random #guint32 from @rand_ equally distributed over
|
||||
* the range [0..2^32-1].
|
||||
*
|
||||
* Returns: a random number
|
||||
*/
|
||||
guint32 g_rand_int (GRand *rand)
|
||||
{
|
||||
guint32 y;
|
||||
static const guint32 mag01[2]={0x0, MATRIX_A};
|
||||
/* mag01[x] = x * MATRIX_A for x=0,1 */
|
||||
|
||||
g_return_val_if_fail (rand != NULL, 0);
|
||||
|
||||
if (rand->mti >= N) { /* generate N words at one time */
|
||||
int kk;
|
||||
|
||||
for (kk = 0; kk < N - M; kk++) {
|
||||
y = (rand->mt[kk]&UPPER_MASK)|(rand->mt[kk+1]&LOWER_MASK);
|
||||
rand->mt[kk] = rand->mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1];
|
||||
}
|
||||
for (; kk < N - 1; kk++) {
|
||||
y = (rand->mt[kk]&UPPER_MASK)|(rand->mt[kk+1]&LOWER_MASK);
|
||||
rand->mt[kk] = rand->mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1];
|
||||
}
|
||||
y = (rand->mt[N-1]&UPPER_MASK)|(rand->mt[0]&LOWER_MASK);
|
||||
rand->mt[N-1] = rand->mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1];
|
||||
|
||||
rand->mti = 0;
|
||||
}
|
||||
|
||||
y = rand->mt[rand->mti++];
|
||||
y ^= TEMPERING_SHIFT_U(y);
|
||||
y ^= TEMPERING_SHIFT_S(y) & TEMPERING_MASK_B;
|
||||
y ^= TEMPERING_SHIFT_T(y) & TEMPERING_MASK_C;
|
||||
y ^= TEMPERING_SHIFT_L(y);
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/* GLIB - Library of useful routines for C programming
|
||||
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Modified by the GLib Team and others 1997-2000. See the AUTHORS
|
||||
* file for a list of people on the GLib Team. See the ChangeLog
|
||||
* files for a list of changes. These files are distributed with
|
||||
* GLib at ftp://ftp.gtk.org/pub/gtk/.
|
||||
*/
|
||||
|
||||
#ifndef __G_RAND_H__
|
||||
#define __G_RAND_H__
|
||||
|
||||
#include "gtypes.h"
|
||||
|
||||
typedef struct _GRand GRand;
|
||||
|
||||
GRand *g_rand_new_with_seed(guint32 seed);
|
||||
GRand *g_rand_new_with_seed_array (const guint32 *seed, guint seed_length);
|
||||
GRand *g_rand_new(void);
|
||||
guint32 g_rand_int(GRand *rand_);
|
||||
|
||||
#endif /* __G_RAND_H__ */
|
||||
@@ -0,0 +1,91 @@
|
||||
/* GLIB sliced memory - fast concurrent memory chunk allocator
|
||||
* Copyright (C) 2005 Tim Janik
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/* MT safe */
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "gtypes.h"
|
||||
#include "gslice.h"
|
||||
#include "gmem.h" /* gslice.h */
|
||||
|
||||
/**
|
||||
* g_slice_alloc:
|
||||
* @block_size: the number of bytes to allocate
|
||||
*
|
||||
* Allocates a block of memory from the slice allocator.
|
||||
* The block address handed out can be expected to be aligned
|
||||
* to at least 1 * sizeof (void*),
|
||||
* though in general slices are 2 * sizeof (void*) bytes aligned,
|
||||
* if a malloc() fallback implementation is used instead,
|
||||
* the alignment may be reduced in a libc dependent fashion.
|
||||
* Note that the underlying slice allocation mechanism can
|
||||
* be changed with the [`G_SLICE=always-malloc`][G_SLICE]
|
||||
* environment variable.
|
||||
*
|
||||
* Returns: a pointer to the allocated memory block, which will be %NULL if and
|
||||
* only if @mem_size is 0
|
||||
*
|
||||
* Since: 2.10
|
||||
*/
|
||||
gpointer g_slice_alloc (gsize mem_size)
|
||||
{
|
||||
return g_malloc (mem_size);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_slice_alloc0:
|
||||
* @block_size: the number of bytes to allocate
|
||||
*
|
||||
* Allocates a block of memory via g_slice_alloc() and initializes
|
||||
* the returned memory to 0. Note that the underlying slice allocation
|
||||
* mechanism can be changed with the [`G_SLICE=always-malloc`][G_SLICE]
|
||||
* environment variable.
|
||||
*
|
||||
* Returns: a pointer to the allocated block, which will be %NULL if and only
|
||||
* if @mem_size is 0
|
||||
*
|
||||
* Since: 2.10
|
||||
*/
|
||||
gpointer g_slice_alloc0 (gsize mem_size)
|
||||
{
|
||||
gpointer mem = g_slice_alloc (mem_size);
|
||||
if (mem)
|
||||
memset (mem, 0, mem_size);
|
||||
return mem;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_slice_free1:
|
||||
* @block_size: the size of the block
|
||||
* @mem_block: a pointer to the block to free
|
||||
*
|
||||
* Frees a block of memory.
|
||||
*
|
||||
* The memory must have been allocated via g_slice_alloc() or
|
||||
* g_slice_alloc0() and the @block_size has to match the size
|
||||
* specified upon allocation. Note that the exact release behaviour
|
||||
* can be changed with the [`G_DEBUG=gc-friendly`][G_DEBUG] environment
|
||||
* variable, also see [`G_SLICE`][G_SLICE] for related debugging options.
|
||||
*
|
||||
* If @mem_block is %NULL, this function does nothing.
|
||||
*
|
||||
* Since: 2.10
|
||||
*/
|
||||
void g_slice_free1 (gsize mem_size, gpointer mem_block)
|
||||
{
|
||||
g_free (mem_block);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/* GLIB sliced memory - fast threaded memory chunk allocator
|
||||
* Copyright (C) 2005 Tim Janik
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __G_SLICE_H__
|
||||
#define __G_SLICE_H__
|
||||
|
||||
#include "gtypes.h"
|
||||
|
||||
#define g_slice_new(type) ((type*) g_slice_alloc (sizeof (type)))
|
||||
#define g_slice_new0(type) ((type*) g_slice_alloc0 (sizeof (type)))
|
||||
|
||||
gpointer g_slice_alloc0 (gsize block_size);
|
||||
gpointer g_slice_alloc (gsize block_size);
|
||||
void g_slice_free1 (gsize block_size, gpointer mem_block);
|
||||
|
||||
#define g_slice_free(type, mem) \
|
||||
G_STMT_START { \
|
||||
if (1) g_slice_free1 (sizeof (type), (mem)); \
|
||||
else (void) ((type*) 0 == (mem)); \
|
||||
} G_STMT_END
|
||||
|
||||
#endif /* __G_SLICE_H__ */
|
||||
@@ -0,0 +1,34 @@
|
||||
/* GLib testing utilities
|
||||
* Copyright (C) 2007 Imendio AB
|
||||
* Authors: Tim Janik, Sven Herzberg
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "gtestutils.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void
|
||||
g_assertion_message_expr (const char *file,
|
||||
int line,
|
||||
const char *expr)
|
||||
{
|
||||
if (!expr)
|
||||
printf("%s:%d code should not be reached", file, line);
|
||||
else
|
||||
printf("%s:%d assertion failed: %s", file, line, expr);
|
||||
|
||||
abort();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/* GLib testing utilities
|
||||
* Copyright (C) 2007 Imendio AB
|
||||
* Authors: Tim Janik
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __G_TEST_UTILS_H__
|
||||
#define __G_TEST_UTILS_H__
|
||||
|
||||
|
||||
#if !(defined (G_STMT_START) && defined (G_STMT_END))
|
||||
#define G_STMT_START do
|
||||
#if defined (_MSC_VER) && (_MSC_VER >= 1500)
|
||||
#define G_STMT_END \
|
||||
__pragma(warning(push)) \
|
||||
__pragma(warning(disable:4127)) \
|
||||
while(0) \
|
||||
__pragma(warning(pop))
|
||||
#else
|
||||
#define G_STMT_END while (0)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
|
||||
#define G_GNUC_NORETURN \
|
||||
__attribute__((__noreturn__))
|
||||
#else /* !__GNUC__ */
|
||||
/* NOTE: MSVC has __declspec(noreturn) but unlike GCC __attribute__,
|
||||
* __declspec can only be placed at the start of the function prototype
|
||||
* and not at the end, so we can't use it without breaking API.
|
||||
*/
|
||||
#define G_GNUC_NORETURN
|
||||
#endif /* !__GNUC__ */
|
||||
|
||||
void g_assertion_message_expr (const char *file,
|
||||
int line,
|
||||
const char *expr) G_GNUC_NORETURN;
|
||||
|
||||
#define g_assert_not_reached() G_STMT_START { g_assertion_message_expr (__FILE__, __LINE__, NULL); } G_STMT_END
|
||||
#define g_assert(expr) G_STMT_START { \
|
||||
if (expr) ; else \
|
||||
g_assertion_message_expr (__FILE__, __LINE__, #expr); \
|
||||
} G_STMT_END
|
||||
|
||||
#endif /* __G_TEST_UTILS_H__ */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
/* GLIB - Library of useful routines for C programming
|
||||
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Modified by the GLib Team and others 1997-2000. See the AUTHORS
|
||||
* file for a list of people on the GLib Team. See the ChangeLog
|
||||
* files for a list of changes. These files are distributed with
|
||||
* GLib at ftp://ftp.gtk.org/pub/gtk/.
|
||||
*/
|
||||
|
||||
#ifndef __G_TREE_H__
|
||||
#define __G_TREE_H__
|
||||
|
||||
typedef struct _GTree GTree;
|
||||
|
||||
typedef gboolean (*GTraverseFunc) (gpointer key, gpointer value, gpointer data);
|
||||
|
||||
/* Balanced binary trees
|
||||
*/
|
||||
GTree* g_tree_new (GCompareFunc key_compare_func);
|
||||
|
||||
GTree* g_tree_new_full (GCompareDataFunc key_compare_func,
|
||||
gpointer key_compare_data,
|
||||
GDestroyNotify key_destroy_func,
|
||||
GDestroyNotify value_destroy_func);
|
||||
|
||||
GTree* g_tree_ref (GTree *tree);
|
||||
|
||||
void g_tree_destroy (GTree *tree);
|
||||
|
||||
void g_tree_insert (GTree *tree, gpointer key, gpointer value);
|
||||
|
||||
void g_tree_remove_all (GTree *tree);
|
||||
|
||||
gboolean g_tree_remove (GTree *tree, gconstpointer key);
|
||||
|
||||
gpointer g_tree_lookup (GTree *tree, gconstpointer key);
|
||||
|
||||
void g_tree_foreach (GTree *tree, GTraverseFunc func, gpointer user_data);
|
||||
|
||||
gint g_tree_nnodes (GTree *tree);
|
||||
|
||||
#endif /* __G_TREE_H__ */
|
||||
@@ -0,0 +1,80 @@
|
||||
/* GLIB - Library of useful routines for C programming
|
||||
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Modified by the GLib Team and others 1997-2000. See the AUTHORS
|
||||
* file for a list of people on the GLib Team. See the ChangeLog
|
||||
* files for a list of changes. These files are distributed with
|
||||
* GLib at ftp://ftp.gtk.org/pub/gtk/.
|
||||
*/
|
||||
|
||||
#ifndef __G_TYPES_H__
|
||||
#define __G_TYPES_H__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <float.h>
|
||||
|
||||
#ifndef TRUE
|
||||
#define TRUE 1
|
||||
#endif
|
||||
|
||||
#ifndef FALSE
|
||||
#define FALSE 0
|
||||
#endif
|
||||
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
/* typedefs for glib related types that may still be referenced */
|
||||
typedef void* gpointer;
|
||||
|
||||
typedef const void *gconstpointer;
|
||||
|
||||
typedef int gint;
|
||||
typedef uint8_t guint8;
|
||||
typedef int8_t gint8;
|
||||
typedef uint16_t guint16;
|
||||
typedef int16_t gint16;
|
||||
typedef uint32_t guint32;
|
||||
typedef int32_t gint32;
|
||||
typedef uint64_t guint64;
|
||||
typedef int64_t gint64;
|
||||
typedef unsigned int guint;
|
||||
typedef char gchar;
|
||||
typedef int gboolean;
|
||||
typedef unsigned long gulong;
|
||||
typedef unsigned long gsize;
|
||||
|
||||
typedef gint grefcount;
|
||||
|
||||
typedef volatile gint gatomicrefcount;
|
||||
|
||||
typedef void (*GDestroyNotify) (gpointer data);
|
||||
|
||||
typedef gint (*GCompareFunc) (gconstpointer a, gconstpointer b);
|
||||
|
||||
typedef gint (*GCompareDataFunc) (gconstpointer a, gconstpointer b, gpointer user_data);
|
||||
|
||||
typedef guint (*GHashFunc) (gconstpointer key);
|
||||
|
||||
typedef gboolean (*GEqualFunc) (gconstpointer a, gconstpointer b);
|
||||
|
||||
typedef void (*GHFunc) (gpointer key, gpointer value, gpointer user_data);
|
||||
|
||||
typedef gpointer (*GCopyFunc) (gconstpointer src, gpointer data);
|
||||
|
||||
#endif /* __G_TYPES_H__ */
|
||||
@@ -0,0 +1,4 @@
|
||||
module github.com/unicorn-engine/unicorn
|
||||
|
||||
go 1.17
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef UC_LLIST_H
|
||||
#define UC_LLIST_H
|
||||
|
||||
#include "unicorn/platform.h"
|
||||
|
||||
typedef void (*delete_fn)(void *data);
|
||||
|
||||
struct list_item {
|
||||
struct list_item *next;
|
||||
void *data;
|
||||
};
|
||||
|
||||
struct list {
|
||||
struct list_item *head, *tail;
|
||||
delete_fn delete_fn;
|
||||
};
|
||||
|
||||
// create a new list
|
||||
struct list *list_new(void);
|
||||
|
||||
// removed linked list nodes but does not free their content
|
||||
void list_clear(struct list *list);
|
||||
|
||||
// insert a new item at the begin of the list.
|
||||
void *list_insert(struct list *list, void *data);
|
||||
|
||||
// append a new item at the end of the list.
|
||||
void *list_append(struct list *list, void *data);
|
||||
|
||||
// returns true if entry was removed, false otherwise
|
||||
bool list_remove(struct list *list, void *data);
|
||||
|
||||
// returns true if the data exists in the list
|
||||
bool list_exists(struct list *list, void *data);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,50 @@
|
||||
/* By Dang Hoang Vu <dang.hvu -at- gmail.com>, 2015 */
|
||||
/* Modified for Unicorn Engine by Chen Huitao<chenhuitao@hfmrit.com>, 2020 */
|
||||
|
||||
#ifndef UC_QEMU_H
|
||||
#define UC_QEMU_H
|
||||
|
||||
struct uc_struct;
|
||||
|
||||
#define OPC_BUF_SIZE 640
|
||||
|
||||
#include "sysemu/sysemu.h"
|
||||
#include "sysemu/cpus.h"
|
||||
#include "exec/cpu-common.h"
|
||||
#include "exec/memory.h"
|
||||
|
||||
#include "qemu/thread.h"
|
||||
#include "hw/core/cpu.h"
|
||||
|
||||
#include "vl.h"
|
||||
|
||||
// This struct is originally from qemu/include/exec/ramblock.h
|
||||
// Temporarily moved here since there is circular inclusion.
|
||||
struct RAMBlock {
|
||||
struct MemoryRegion *mr;
|
||||
uint8_t *host;
|
||||
ram_addr_t offset;
|
||||
ram_addr_t used_length;
|
||||
ram_addr_t max_length;
|
||||
uint32_t flags;
|
||||
/* RCU-enabled, writes protected by the ramlist lock */
|
||||
QLIST_ENTRY(RAMBlock) next;
|
||||
size_t page_size;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
MemoryRegion *mr;
|
||||
void *buffer;
|
||||
hwaddr addr;
|
||||
hwaddr len;
|
||||
} BounceBuffer;
|
||||
|
||||
// This struct is originally from qemu/include/exec/ramlist.h
|
||||
typedef struct RAMList {
|
||||
bool freed;
|
||||
RAMBlock *mru_block;
|
||||
RAMBlock *last_block;
|
||||
QLIST_HEAD(, RAMBlock) blocks;
|
||||
} RAMList;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,581 @@
|
||||
/* Unicorn Emulator Engine */
|
||||
/* By Nguyen Anh Quynh <aquynh@gmail.com>, 2015 */
|
||||
/* Modified for Unicorn Engine by Chen Huitao<chenhuitao@hfmrit.com>, 2020 */
|
||||
|
||||
#ifndef UC_PRIV_H
|
||||
#define UC_PRIV_H
|
||||
|
||||
#include "unicorn/platform.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#include "qemu.h"
|
||||
#include "qemu/xxhash.h"
|
||||
#include "unicorn/unicorn.h"
|
||||
#include "list.h"
|
||||
|
||||
// The max recursive nested uc_emu_start levels
|
||||
#define UC_MAX_NESTED_LEVEL (64)
|
||||
|
||||
// These are masks of supported modes for each cpu/arch.
|
||||
// They should be updated when changes are made to the uc_mode enum typedef.
|
||||
#define UC_MODE_ARM_MASK \
|
||||
(UC_MODE_ARM | UC_MODE_THUMB | UC_MODE_LITTLE_ENDIAN | UC_MODE_MCLASS | \
|
||||
UC_MODE_ARM926 | UC_MODE_ARM946 | UC_MODE_ARM1176 | UC_MODE_BIG_ENDIAN | \
|
||||
UC_MODE_ARMBE8)
|
||||
#define UC_MODE_MIPS_MASK \
|
||||
(UC_MODE_MIPS32 | UC_MODE_MIPS64 | UC_MODE_LITTLE_ENDIAN | \
|
||||
UC_MODE_BIG_ENDIAN)
|
||||
#define UC_MODE_X86_MASK \
|
||||
(UC_MODE_16 | UC_MODE_32 | UC_MODE_64 | UC_MODE_LITTLE_ENDIAN)
|
||||
#define UC_MODE_PPC_MASK (UC_MODE_PPC32 | UC_MODE_PPC64 | UC_MODE_BIG_ENDIAN)
|
||||
#define UC_MODE_SPARC_MASK \
|
||||
(UC_MODE_SPARC32 | UC_MODE_SPARC64 | UC_MODE_BIG_ENDIAN)
|
||||
#define UC_MODE_M68K_MASK (UC_MODE_BIG_ENDIAN)
|
||||
#define UC_MODE_RISCV_MASK \
|
||||
(UC_MODE_RISCV32 | UC_MODE_RISCV64 | UC_MODE_LITTLE_ENDIAN)
|
||||
#define UC_MODE_S390X_MASK (UC_MODE_BIG_ENDIAN)
|
||||
#define UC_MODE_TRICORE_MASK (UC_MODE_LITTLE_ENDIAN)
|
||||
|
||||
#define ARR_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
||||
|
||||
#define READ_QWORD(x) ((uint64_t)x)
|
||||
#define READ_DWORD(x) (x & 0xffffffff)
|
||||
#define READ_WORD(x) (x & 0xffff)
|
||||
#define READ_BYTE_H(x) ((x & 0xffff) >> 8)
|
||||
#define READ_BYTE_L(x) (x & 0xff)
|
||||
#define WRITE_DWORD(x, w) (x = (x & ~0xffffffffLL) | (w & 0xffffffff))
|
||||
#define WRITE_WORD(x, w) (x = (x & ~0xffff) | (w & 0xffff))
|
||||
#define WRITE_BYTE_H(x, b) (x = (x & ~0xff00) | ((b & 0xff) << 8))
|
||||
#define WRITE_BYTE_L(x, b) (x = (x & ~0xff) | (b & 0xff))
|
||||
|
||||
struct TranslationBlock;
|
||||
|
||||
// Place the struct here since we need it in uc.c
|
||||
typedef struct _mmio_cbs {
|
||||
uc_cb_mmio_read_t read;
|
||||
void *user_data_read;
|
||||
uc_cb_mmio_write_t write;
|
||||
void *user_data_write;
|
||||
MemoryRegionOps ops;
|
||||
} mmio_cbs;
|
||||
|
||||
typedef uc_err (*query_t)(struct uc_struct *uc, uc_query_type type,
|
||||
size_t *result);
|
||||
|
||||
typedef uc_err (*reg_read_t)(void *env, int mode, unsigned int regid,
|
||||
void *value, size_t *size);
|
||||
typedef uc_err (*reg_write_t)(void *env, int mode, unsigned int regid,
|
||||
const void *value, size_t *size, int *setpc);
|
||||
|
||||
typedef struct {
|
||||
reg_read_t read;
|
||||
reg_write_t write;
|
||||
} context_reg_rw_t;
|
||||
|
||||
typedef void (*reg_reset_t)(struct uc_struct *uc);
|
||||
|
||||
typedef bool (*uc_write_mem_t)(AddressSpace *as, hwaddr addr,
|
||||
const uint8_t *buf, hwaddr len);
|
||||
|
||||
typedef bool (*uc_read_mem_t)(AddressSpace *as, hwaddr addr, uint8_t *buf,
|
||||
hwaddr len);
|
||||
|
||||
typedef bool (*uc_read_mem_virtual_t)(struct uc_struct *uc, vaddr addr,
|
||||
uint32_t prot, uint8_t *buf, int len);
|
||||
|
||||
typedef bool (*uc_virtual_to_physical_t)(struct uc_struct *uc, vaddr addr,
|
||||
uint32_t prot, uint64_t *res);
|
||||
|
||||
typedef MemoryRegion *(*uc_mem_cow_t)(struct uc_struct *uc,
|
||||
MemoryRegion *current, hwaddr begin,
|
||||
size_t size);
|
||||
|
||||
typedef void (*uc_args_void_t)(void *);
|
||||
|
||||
typedef void (*uc_args_uc_t)(struct uc_struct *);
|
||||
typedef void (*uc_args_int_uc_t)(struct uc_struct *);
|
||||
|
||||
typedef void (*uc_args_uc_long_t)(struct uc_struct *, uint32_t);
|
||||
|
||||
typedef void (*uc_args_uc_u64_t)(struct uc_struct *, uint64_t addr);
|
||||
|
||||
typedef uint64_t (*uc_get_pc_t)(struct uc_struct *);
|
||||
|
||||
typedef MemoryRegion *(*uc_args_uc_ram_size_t)(struct uc_struct *, hwaddr begin,
|
||||
size_t size, uint32_t perms);
|
||||
|
||||
typedef MemoryRegion *(*uc_args_uc_ram_size_ptr_t)(struct uc_struct *,
|
||||
hwaddr begin, size_t size,
|
||||
uint32_t perms, void *ptr);
|
||||
|
||||
typedef void (*uc_mem_unmap_t)(struct uc_struct *, MemoryRegion *mr);
|
||||
|
||||
typedef MemoryRegion *(*uc_memory_mapping_t)(struct uc_struct *, hwaddr addr);
|
||||
|
||||
typedef void (*uc_memory_filter_t)(MemoryRegion *, int32_t);
|
||||
|
||||
typedef bool (*uc_flatview_copy_t)(struct uc_struct *, FlatView *, FlatView *,
|
||||
bool);
|
||||
|
||||
typedef void (*uc_readonly_mem_t)(MemoryRegion *mr, bool readonly);
|
||||
|
||||
typedef int (*uc_cpus_init)(struct uc_struct *, const char *);
|
||||
|
||||
typedef MemoryRegion *(*uc_memory_map_io_t)(struct uc_struct *uc,
|
||||
ram_addr_t begin, size_t size,
|
||||
uc_cb_mmio_read_t read_cb,
|
||||
uc_cb_mmio_write_t write_cb,
|
||||
void *user_data_read,
|
||||
void *user_data_write);
|
||||
|
||||
// which interrupt should make emulation stop?
|
||||
typedef bool (*uc_args_int_t)(struct uc_struct *uc, int intno);
|
||||
|
||||
// validate if Unicorn supports hooking a given instruction
|
||||
typedef bool (*uc_insn_hook_validate)(uint32_t insn_enum);
|
||||
|
||||
typedef bool (*uc_opcode_hook_validate_t)(uint32_t op, uint32_t flags);
|
||||
|
||||
// init target page
|
||||
typedef void (*uc_target_page_init)(struct uc_struct *);
|
||||
|
||||
// soft float init
|
||||
typedef void (*uc_softfloat_initialize)(void);
|
||||
|
||||
// tcg flush softmmu tlb
|
||||
typedef void (*uc_tcg_flush_tlb)(struct uc_struct *uc);
|
||||
|
||||
// Invalidate the TB at given address
|
||||
typedef void (*uc_invalidate_tb_t)(struct uc_struct *uc, uint64_t start,
|
||||
size_t len);
|
||||
|
||||
// Request generating TB at given address
|
||||
typedef uc_err (*uc_gen_tb_t)(struct uc_struct *uc, uint64_t pc, uc_tb *out_tb);
|
||||
|
||||
// tb flush
|
||||
typedef uc_tcg_flush_tlb uc_tb_flush_t;
|
||||
|
||||
typedef uc_err (*uc_set_tlb_t)(struct uc_struct *uc, int mode);
|
||||
|
||||
struct hook {
|
||||
int type; // UC_HOOK_*
|
||||
int insn; // instruction for HOOK_INSN
|
||||
int refs; // reference count to free hook stored in multiple lists
|
||||
int op; // opcode for HOOK_TCG_OPCODE
|
||||
int op_flags; // opcode flags for HOOK_TCG_OPCODE
|
||||
bool to_delete; // set to true when the hook is deleted by the user. The
|
||||
// destruction of the hook is delayed.
|
||||
uint64_t begin, end; // only trigger if PC or memory access is in this
|
||||
// address (depends on hook type)
|
||||
void *callback; // a uc_cb_* type
|
||||
void *user_data;
|
||||
GHashTable *hooked_regions; // The regions this hook instrumented on
|
||||
};
|
||||
|
||||
// Add an inline hook to helper_table
|
||||
typedef void (*uc_add_inline_hook_t)(struct uc_struct *uc, struct hook *hk,
|
||||
void **args, int args_len);
|
||||
|
||||
// Delete a hook from helper_table
|
||||
typedef void (*uc_del_inline_hook_t)(struct uc_struct *uc, struct hook *hk);
|
||||
|
||||
// Return the size of a CPU context
|
||||
typedef size_t (*uc_context_size_t)(struct uc_struct *uc);
|
||||
|
||||
// Generate a CPU context
|
||||
typedef uc_err (*uc_context_save_t)(struct uc_struct *uc, uc_context *context);
|
||||
|
||||
// Restore a CPU context
|
||||
typedef uc_err (*uc_context_restore_t)(struct uc_struct *uc,
|
||||
uc_context *context);
|
||||
|
||||
// hook list offsets
|
||||
//
|
||||
// The lowest 6 bits are used for hook type index while the others
|
||||
// are used for hook flags.
|
||||
//
|
||||
// mirrors the order of uc_hook_type from include/unicorn/unicorn.h
|
||||
typedef enum uc_hook_idx {
|
||||
UC_HOOK_INTR_IDX,
|
||||
UC_HOOK_INSN_IDX,
|
||||
UC_HOOK_CODE_IDX,
|
||||
UC_HOOK_BLOCK_IDX,
|
||||
UC_HOOK_MEM_READ_UNMAPPED_IDX,
|
||||
UC_HOOK_MEM_WRITE_UNMAPPED_IDX,
|
||||
UC_HOOK_MEM_FETCH_UNMAPPED_IDX,
|
||||
UC_HOOK_MEM_READ_PROT_IDX,
|
||||
UC_HOOK_MEM_WRITE_PROT_IDX,
|
||||
UC_HOOK_MEM_FETCH_PROT_IDX,
|
||||
UC_HOOK_MEM_READ_IDX,
|
||||
UC_HOOK_MEM_WRITE_IDX,
|
||||
UC_HOOK_MEM_FETCH_IDX,
|
||||
UC_HOOK_MEM_READ_AFTER_IDX,
|
||||
UC_HOOK_INSN_INVALID_IDX,
|
||||
UC_HOOK_EDGE_GENERATED_IDX,
|
||||
UC_HOOK_TCG_OPCODE_IDX,
|
||||
UC_HOOK_TLB_FILL_IDX,
|
||||
|
||||
UC_HOOK_MAX,
|
||||
} uc_hook_idx;
|
||||
|
||||
// Copy the essential information from TranslationBlock
|
||||
#define UC_TB_COPY(uc_tb, tb) \
|
||||
do { \
|
||||
(uc_tb)->pc = tb->pc; \
|
||||
(uc_tb)->icount = tb->icount; \
|
||||
(uc_tb)->size = tb->size; \
|
||||
} while (0)
|
||||
|
||||
// The lowest 6 bits are used for hook type index.
|
||||
#define UC_HOOK_IDX_MASK ((1 << 6) - 1)
|
||||
|
||||
// hook flags
|
||||
#define UC_HOOK_FLAG_NO_STOP \
|
||||
(1 << 6) // Don't stop emulation in this uc_tracecode.
|
||||
|
||||
// The rest of bits are reserved for hook flags.
|
||||
#define UC_HOOK_FLAG_MASK (~(UC_HOOK_IDX_MASK))
|
||||
|
||||
#define HOOK_FOREACH_VAR_DECLARE struct list_item *cur
|
||||
|
||||
// for loop macro to loop over hook lists
|
||||
#define HOOK_FOREACH(uc, hh, idx) \
|
||||
for (cur = (uc)->hook[idx##_IDX].head; \
|
||||
cur != NULL && ((hh) = (struct hook *)cur->data); cur = cur->next)
|
||||
|
||||
// if statement to check hook bounds
|
||||
#define HOOK_BOUND_CHECK(hh, addr) \
|
||||
((((addr) >= (hh)->begin && (addr) <= (hh)->end) || \
|
||||
(hh)->begin > (hh)->end) && \
|
||||
!((hh)->to_delete))
|
||||
|
||||
#define HOOK_EXISTS(uc, idx) ((uc)->hook[idx##_IDX].head != NULL)
|
||||
#define HOOK_EXISTS_BOUNDED(uc, idx, addr) \
|
||||
_hook_exists_bounded((uc)->hook[idx##_IDX].head, addr)
|
||||
|
||||
static inline bool _hook_exists_bounded(struct list_item *cur, uint64_t addr)
|
||||
{
|
||||
while (cur != NULL) {
|
||||
if (HOOK_BOUND_CHECK((struct hook *)cur->data, addr))
|
||||
return true;
|
||||
cur = cur->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// relloc increment, KEEP THIS A POWER OF 2!
|
||||
#define MEM_BLOCK_INCR 32
|
||||
|
||||
typedef struct TargetPageBits TargetPageBits;
|
||||
typedef struct TCGContext TCGContext;
|
||||
|
||||
struct uc_struct {
|
||||
uc_arch arch;
|
||||
uc_mode mode;
|
||||
uc_err errnum; // qemu/cpu-exec.c
|
||||
AddressSpace address_space_memory;
|
||||
AddressSpace address_space_io;
|
||||
query_t query;
|
||||
reg_read_t reg_read;
|
||||
reg_write_t reg_write;
|
||||
reg_reset_t reg_reset;
|
||||
|
||||
uc_write_mem_t write_mem;
|
||||
uc_read_mem_t read_mem;
|
||||
uc_read_mem_virtual_t read_mem_virtual;
|
||||
uc_virtual_to_physical_t virtual_to_physical;
|
||||
uc_mem_cow_t memory_cow;
|
||||
uc_args_void_t release; // release resource when uc_close()
|
||||
uc_args_uc_u64_t set_pc; // set PC for tracecode
|
||||
uc_get_pc_t get_pc;
|
||||
uc_args_int_t
|
||||
stop_interrupt; // check if the interrupt should stop emulation
|
||||
uc_memory_map_io_t memory_map_io;
|
||||
|
||||
uc_args_uc_t init_arch, cpu_exec_init_all;
|
||||
uc_args_int_uc_t vm_start;
|
||||
uc_args_uc_long_t tcg_exec_init;
|
||||
uc_args_uc_ram_size_t memory_map;
|
||||
uc_args_uc_ram_size_ptr_t memory_map_ptr;
|
||||
uc_memory_mapping_t memory_mapping;
|
||||
uc_memory_filter_t memory_filter_subregions;
|
||||
uc_flatview_copy_t flatview_copy;
|
||||
uc_mem_unmap_t memory_unmap;
|
||||
uc_mem_unmap_t memory_moveout;
|
||||
uc_mem_unmap_t memory_movein;
|
||||
uc_readonly_mem_t readonly_mem;
|
||||
uc_cpus_init cpus_init;
|
||||
uc_target_page_init target_page;
|
||||
uc_softfloat_initialize softfloat_initialize;
|
||||
uc_tcg_flush_tlb tcg_flush_tlb;
|
||||
uc_invalidate_tb_t uc_invalidate_tb;
|
||||
uc_gen_tb_t uc_gen_tb;
|
||||
uc_tb_flush_t tb_flush;
|
||||
uc_add_inline_hook_t add_inline_hook;
|
||||
uc_del_inline_hook_t del_inline_hook;
|
||||
|
||||
uc_context_size_t context_size;
|
||||
uc_context_save_t context_save;
|
||||
uc_context_restore_t context_restore;
|
||||
|
||||
/* only 1 cpu in unicorn,
|
||||
do not need current_cpu to handle current running cpu. */
|
||||
CPUState *cpu;
|
||||
|
||||
uc_insn_hook_validate insn_hook_validate;
|
||||
uc_opcode_hook_validate_t opcode_hook_invalidate;
|
||||
|
||||
MemoryRegion *system_memory; // qemu/exec.c
|
||||
MemoryRegion *system_io; // qemu/exec.c
|
||||
MemoryRegion io_mem_unassigned; // qemu/exec.c
|
||||
RAMList ram_list; // qemu/exec.c
|
||||
/* qemu/exec.c */
|
||||
unsigned int alloc_hint;
|
||||
/* qemu/exec-vary.c */
|
||||
TargetPageBits *init_target_page;
|
||||
int target_bits; // User defined page bits by uc_ctl
|
||||
int cpu_model;
|
||||
BounceBuffer bounce; // qemu/cpu-exec.c
|
||||
volatile sig_atomic_t exit_request; // qemu/cpu-exec.c
|
||||
/* qemu/accel/tcg/cpu-exec-common.c */
|
||||
/* always be true after call tcg_exec_init(). */
|
||||
bool tcg_allowed;
|
||||
/* This is a multi-level map on the virtual address space.
|
||||
The bottom level has pointers to PageDesc. */
|
||||
void **l1_map; // qemu/accel/tcg/translate-all.c
|
||||
size_t l1_map_size;
|
||||
/* qemu/accel/tcg/translate-all.c */
|
||||
int v_l1_size;
|
||||
int v_l1_shift;
|
||||
int v_l2_levels;
|
||||
/* code generation context */
|
||||
TCGContext *tcg_ctx;
|
||||
/* memory.c */
|
||||
QTAILQ_HEAD(memory_listeners, MemoryListener) memory_listeners;
|
||||
QTAILQ_HEAD(, AddressSpace) address_spaces;
|
||||
GHashTable *flat_views;
|
||||
bool memory_region_update_pending;
|
||||
|
||||
uc_set_tlb_t set_tlb;
|
||||
|
||||
// linked lists containing hooks per type
|
||||
struct list hook[UC_HOOK_MAX];
|
||||
struct list hooks_to_del;
|
||||
int hooks_count[UC_HOOK_MAX];
|
||||
|
||||
// hook to count number of instructions for uc_emu_start()
|
||||
uc_hook count_hook;
|
||||
|
||||
size_t emu_counter; // current counter of uc_emu_start()
|
||||
size_t emu_count; // save counter of uc_emu_start()
|
||||
|
||||
int size_recur_mem; // size for mem access when in a recursive call
|
||||
|
||||
bool init_tcg; // already initialized local TCGv variables?
|
||||
bool stop_request; // request to immediately stop emulation - for
|
||||
// uc_emu_stop()
|
||||
bool quit_request; // request to quit the current TB, but continue to
|
||||
// emulate - for uc_mem_protect()
|
||||
bool emulation_done; // emulation is done by uc_emu_start()
|
||||
bool timed_out; // emulation timed out, that can retrieve via
|
||||
// uc_query(UC_QUERY_TIMEOUT)
|
||||
QemuThread timer; // timer for emulation timeout
|
||||
uint64_t timeout; // timeout for uc_emu_start()
|
||||
|
||||
uint64_t invalid_addr; // invalid address to be accessed
|
||||
int invalid_error; // invalid memory code: 1 = READ, 2 = WRITE, 3 = CODE
|
||||
|
||||
int use_exits;
|
||||
uint64_t exits[UC_MAX_NESTED_LEVEL]; // When multiple exits is not enabled.
|
||||
GTree *ctl_exits; // addresses where emulation stops (@until param of
|
||||
// uc_emu_start()) Also see UC_CTL_USE_EXITS for more
|
||||
// details.
|
||||
|
||||
int thumb; // thumb mode for ARM
|
||||
MemoryRegion **mapped_blocks;
|
||||
uint32_t mapped_block_count;
|
||||
uint32_t mapped_block_cache_index;
|
||||
void *qemu_thread_data; // to support cross compile to Windows
|
||||
// (qemu-thread-win32.c)
|
||||
uint32_t target_page_size;
|
||||
uint32_t target_page_align;
|
||||
uint64_t qemu_host_page_size;
|
||||
uint64_t qemu_real_host_page_size;
|
||||
int qemu_icache_linesize;
|
||||
/* ARCH_REGS_STORAGE_SIZE */
|
||||
uc_context_content context_content;
|
||||
int cpu_context_size;
|
||||
uint64_t next_pc; // save next PC for some special cases
|
||||
bool hook_insert; // insert new hook at begin of the hook list (append by
|
||||
// default)
|
||||
bool first_tb; // is this the first Translation-Block ever generated since
|
||||
// uc_emu_start()?
|
||||
bool no_exit_request; // Disable check_exit_request temporarily. A
|
||||
// workaround to treat the IT block as a whole block.
|
||||
bool init_done; // Whether the initialization is done.
|
||||
|
||||
sigjmp_buf jmp_bufs[UC_MAX_NESTED_LEVEL]; // To support nested uc_emu_start
|
||||
int nested_level; // Current nested_level
|
||||
|
||||
struct TranslationBlock *last_tb; // The real last tb we executed.
|
||||
|
||||
FlatView *empty_view; // Static function variable moved from flatviews_init
|
||||
|
||||
uint32_t tcg_buffer_size; // The buffer size we are going to use
|
||||
#ifdef WIN32
|
||||
PVOID seh_handle;
|
||||
void *seh_closure;
|
||||
#endif
|
||||
GArray *unmapped_regions;
|
||||
int32_t snapshot_level;
|
||||
uint64_t nested; // the nested level of all exposed API
|
||||
bool thread_executable_entry;
|
||||
bool current_executable;
|
||||
bool skip_sync_pc_on_exit;
|
||||
};
|
||||
|
||||
// Metadata stub for the variable-size cpu context used with uc_context_*()
|
||||
struct uc_context {
|
||||
size_t context_size; // size of the real internal context structure
|
||||
uc_mode mode; // the mode of this context
|
||||
uc_arch arch; // the arch of this context
|
||||
int snapshot_level; // the memory snapshot level to restore
|
||||
bool ramblock_freed; // wheter there was a some ramblock freed
|
||||
RAMBlock *last_block; // The last element of the ramblock list
|
||||
FlatView *fv; // The current flatview of the memory
|
||||
char data[0]; // context
|
||||
};
|
||||
|
||||
// We have to support 32bit system so we can't hold uint64_t on void*
|
||||
static inline void uc_add_exit(uc_engine *uc, uint64_t addr)
|
||||
{
|
||||
uint64_t *new_exit = g_malloc(sizeof(uint64_t));
|
||||
*new_exit = addr;
|
||||
g_tree_insert(uc->ctl_exits, (gpointer)new_exit, (gpointer)1);
|
||||
}
|
||||
|
||||
// This function has to exist since we would like to accept uint32_t or
|
||||
// it's complex to achieve so.
|
||||
static inline int uc_addr_is_exit(uc_engine *uc, uint64_t addr)
|
||||
{
|
||||
if (uc->use_exits) {
|
||||
return g_tree_lookup(uc->ctl_exits, (gpointer)(&addr)) == (gpointer)1;
|
||||
} else {
|
||||
return uc->exits[uc->nested_level - 1] == addr;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct HookedRegion {
|
||||
uint64_t start;
|
||||
uint64_t length;
|
||||
} HookedRegion;
|
||||
|
||||
// hooked_regions related functions
|
||||
static inline guint hooked_regions_hash(const void *p)
|
||||
{
|
||||
HookedRegion *region = (HookedRegion *)p;
|
||||
|
||||
return qemu_xxhash4(region->start, region->length);
|
||||
}
|
||||
|
||||
static inline gboolean hooked_regions_equal(const void *lhs, const void *rhs)
|
||||
{
|
||||
HookedRegion *l = (HookedRegion *)lhs;
|
||||
HookedRegion *r = (HookedRegion *)rhs;
|
||||
|
||||
return l->start == r->start && l->length == r->length;
|
||||
}
|
||||
|
||||
static inline void hooked_regions_add(struct hook *h, uint64_t start,
|
||||
uint64_t length)
|
||||
{
|
||||
HookedRegion tmp;
|
||||
tmp.start = start;
|
||||
tmp.length = length;
|
||||
|
||||
if (!g_hash_table_lookup(h->hooked_regions, (void *)&tmp)) {
|
||||
HookedRegion *r = malloc(sizeof(HookedRegion));
|
||||
r->start = start;
|
||||
r->length = length;
|
||||
g_hash_table_insert(h->hooked_regions, (void *)r, (void *)1);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hooked_regions_check_single(struct list_item *cur,
|
||||
uint64_t start, uint64_t length)
|
||||
{
|
||||
while (cur != NULL) {
|
||||
if (HOOK_BOUND_CHECK((struct hook *)cur->data, start)) {
|
||||
hooked_regions_add((struct hook *)cur->data, start, length);
|
||||
}
|
||||
cur = cur->next;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hooked_regions_check(uc_engine *uc, uint64_t start,
|
||||
uint64_t length)
|
||||
{
|
||||
// Only UC_HOOK_BLOCK and UC_HOOK_CODE might be wrongle cached!
|
||||
hooked_regions_check_single(uc->hook[UC_HOOK_CODE_IDX].head, start, length);
|
||||
hooked_regions_check_single(uc->hook[UC_HOOK_BLOCK_IDX].head, start,
|
||||
length);
|
||||
}
|
||||
|
||||
/*
|
||||
break translation loop:
|
||||
This is done in two cases:
|
||||
1. the user wants to stop the emulation.
|
||||
2. the user has set it IP. This requires to restart the internal
|
||||
CPU emulation and rebuild some translation blocks
|
||||
*/
|
||||
static inline uc_err break_translation_loop(uc_engine *uc)
|
||||
{
|
||||
if (uc->emulation_done) {
|
||||
return UC_ERR_OK;
|
||||
}
|
||||
|
||||
// TODO: make this atomic somehow?
|
||||
if (uc->cpu) {
|
||||
// exit the current TB
|
||||
cpu_exit(uc->cpu);
|
||||
}
|
||||
|
||||
return UC_ERR_OK;
|
||||
}
|
||||
|
||||
static inline void revert_uc_emu_stop(uc_engine *uc)
|
||||
{
|
||||
uc->stop_request = 0;
|
||||
uc->cpu->exit_request = 0;
|
||||
uc->cpu->tcg_exit_req = 0;
|
||||
uc->cpu->icount_decr_ptr->u16.high = 0;
|
||||
}
|
||||
|
||||
#ifdef UNICORN_TRACER
|
||||
#define UC_TRACE_START(loc) trace_start(get_tracer(), loc)
|
||||
#define UC_TRACE_END(loc, fmt, ...) \
|
||||
trace_end(get_tracer(), loc, fmt, __VA_ARGS__)
|
||||
|
||||
typedef enum trace_loc {
|
||||
UC_TRACE_TB_EXEC = 0,
|
||||
UC_TRACE_TB_TRANS,
|
||||
UC_TRACER_MAX
|
||||
} trace_loc;
|
||||
|
||||
typedef struct uc_tracer {
|
||||
int64_t starts[UC_TRACER_MAX];
|
||||
} uc_tracer;
|
||||
|
||||
uc_tracer *get_tracer();
|
||||
|
||||
void trace_start(uc_tracer *tracer, trace_loc loc);
|
||||
|
||||
void trace_end(uc_tracer *tracer, trace_loc loc, const char *fmt, ...);
|
||||
|
||||
#else
|
||||
#define UC_TRACE_START(loc)
|
||||
#define UC_TRACE_END(loc, fmt, ...)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
/* vim: set ts=4 noet: */
|
||||
@@ -0,0 +1,235 @@
|
||||
/* Unicorn Engine */
|
||||
/* By Nguyen Anh Quynh <aquynh@gmail.com>, 2015-2017 */
|
||||
/* This file is released under LGPL2.
|
||||
See COPYING.LGPL2 in root directory for more details
|
||||
*/
|
||||
|
||||
#ifndef UNICORN_ARM_H
|
||||
#define UNICORN_ARM_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4201)
|
||||
#endif
|
||||
|
||||
//> ARM CPU
|
||||
typedef enum uc_cpu_arm {
|
||||
UC_CPU_ARM_926 = 0,
|
||||
UC_CPU_ARM_946,
|
||||
UC_CPU_ARM_1026,
|
||||
UC_CPU_ARM_1136_R2,
|
||||
UC_CPU_ARM_1136,
|
||||
UC_CPU_ARM_1176,
|
||||
UC_CPU_ARM_11MPCORE,
|
||||
UC_CPU_ARM_CORTEX_M0,
|
||||
UC_CPU_ARM_CORTEX_M3,
|
||||
UC_CPU_ARM_CORTEX_M4,
|
||||
UC_CPU_ARM_CORTEX_M7,
|
||||
UC_CPU_ARM_CORTEX_M33,
|
||||
UC_CPU_ARM_CORTEX_R5,
|
||||
UC_CPU_ARM_CORTEX_R5F,
|
||||
UC_CPU_ARM_CORTEX_A7,
|
||||
UC_CPU_ARM_CORTEX_A8,
|
||||
UC_CPU_ARM_CORTEX_A9,
|
||||
UC_CPU_ARM_CORTEX_A15,
|
||||
UC_CPU_ARM_TI925T,
|
||||
UC_CPU_ARM_SA1100,
|
||||
UC_CPU_ARM_SA1110,
|
||||
UC_CPU_ARM_PXA250,
|
||||
UC_CPU_ARM_PXA255,
|
||||
UC_CPU_ARM_PXA260,
|
||||
UC_CPU_ARM_PXA261,
|
||||
UC_CPU_ARM_PXA262,
|
||||
UC_CPU_ARM_PXA270,
|
||||
UC_CPU_ARM_PXA270A0,
|
||||
UC_CPU_ARM_PXA270A1,
|
||||
UC_CPU_ARM_PXA270B0,
|
||||
UC_CPU_ARM_PXA270B1,
|
||||
UC_CPU_ARM_PXA270C0,
|
||||
UC_CPU_ARM_PXA270C5,
|
||||
UC_CPU_ARM_MAX,
|
||||
|
||||
UC_CPU_ARM_ENDING
|
||||
} uc_cpu_arm;
|
||||
|
||||
// ARM coprocessor registers, use this with UC_ARM_REG_CP_REG to
|
||||
// in call to uc_reg_write/read() to access the registers.
|
||||
typedef struct uc_arm_cp_reg {
|
||||
uint32_t cp; // The coprocessor identifier
|
||||
uint32_t is64; // Is it a 64 bit control register
|
||||
uint32_t sec; // Security state
|
||||
uint32_t crn; // Coprocessor register number
|
||||
uint32_t crm; // Coprocessor register number
|
||||
uint32_t opc1; // Opcode1
|
||||
uint32_t opc2; // Opcode2
|
||||
uint64_t val; // The value to read/write
|
||||
} uc_arm_cp_reg;
|
||||
|
||||
//> ARM registers
|
||||
typedef enum uc_arm_reg {
|
||||
UC_ARM_REG_INVALID = 0,
|
||||
UC_ARM_REG_APSR,
|
||||
UC_ARM_REG_APSR_NZCV,
|
||||
UC_ARM_REG_CPSR,
|
||||
UC_ARM_REG_FPEXC,
|
||||
UC_ARM_REG_FPINST,
|
||||
UC_ARM_REG_FPSCR,
|
||||
UC_ARM_REG_FPSCR_NZCV,
|
||||
UC_ARM_REG_FPSID,
|
||||
UC_ARM_REG_ITSTATE,
|
||||
UC_ARM_REG_LR,
|
||||
UC_ARM_REG_PC,
|
||||
UC_ARM_REG_SP,
|
||||
UC_ARM_REG_SPSR,
|
||||
UC_ARM_REG_D0,
|
||||
UC_ARM_REG_D1,
|
||||
UC_ARM_REG_D2,
|
||||
UC_ARM_REG_D3,
|
||||
UC_ARM_REG_D4,
|
||||
UC_ARM_REG_D5,
|
||||
UC_ARM_REG_D6,
|
||||
UC_ARM_REG_D7,
|
||||
UC_ARM_REG_D8,
|
||||
UC_ARM_REG_D9,
|
||||
UC_ARM_REG_D10,
|
||||
UC_ARM_REG_D11,
|
||||
UC_ARM_REG_D12,
|
||||
UC_ARM_REG_D13,
|
||||
UC_ARM_REG_D14,
|
||||
UC_ARM_REG_D15,
|
||||
UC_ARM_REG_D16,
|
||||
UC_ARM_REG_D17,
|
||||
UC_ARM_REG_D18,
|
||||
UC_ARM_REG_D19,
|
||||
UC_ARM_REG_D20,
|
||||
UC_ARM_REG_D21,
|
||||
UC_ARM_REG_D22,
|
||||
UC_ARM_REG_D23,
|
||||
UC_ARM_REG_D24,
|
||||
UC_ARM_REG_D25,
|
||||
UC_ARM_REG_D26,
|
||||
UC_ARM_REG_D27,
|
||||
UC_ARM_REG_D28,
|
||||
UC_ARM_REG_D29,
|
||||
UC_ARM_REG_D30,
|
||||
UC_ARM_REG_D31,
|
||||
UC_ARM_REG_FPINST2,
|
||||
UC_ARM_REG_MVFR0,
|
||||
UC_ARM_REG_MVFR1,
|
||||
UC_ARM_REG_MVFR2,
|
||||
UC_ARM_REG_Q0,
|
||||
UC_ARM_REG_Q1,
|
||||
UC_ARM_REG_Q2,
|
||||
UC_ARM_REG_Q3,
|
||||
UC_ARM_REG_Q4,
|
||||
UC_ARM_REG_Q5,
|
||||
UC_ARM_REG_Q6,
|
||||
UC_ARM_REG_Q7,
|
||||
UC_ARM_REG_Q8,
|
||||
UC_ARM_REG_Q9,
|
||||
UC_ARM_REG_Q10,
|
||||
UC_ARM_REG_Q11,
|
||||
UC_ARM_REG_Q12,
|
||||
UC_ARM_REG_Q13,
|
||||
UC_ARM_REG_Q14,
|
||||
UC_ARM_REG_Q15,
|
||||
UC_ARM_REG_R0,
|
||||
UC_ARM_REG_R1,
|
||||
UC_ARM_REG_R2,
|
||||
UC_ARM_REG_R3,
|
||||
UC_ARM_REG_R4,
|
||||
UC_ARM_REG_R5,
|
||||
UC_ARM_REG_R6,
|
||||
UC_ARM_REG_R7,
|
||||
UC_ARM_REG_R8,
|
||||
UC_ARM_REG_R9,
|
||||
UC_ARM_REG_R10,
|
||||
UC_ARM_REG_R11,
|
||||
UC_ARM_REG_R12,
|
||||
UC_ARM_REG_S0,
|
||||
UC_ARM_REG_S1,
|
||||
UC_ARM_REG_S2,
|
||||
UC_ARM_REG_S3,
|
||||
UC_ARM_REG_S4,
|
||||
UC_ARM_REG_S5,
|
||||
UC_ARM_REG_S6,
|
||||
UC_ARM_REG_S7,
|
||||
UC_ARM_REG_S8,
|
||||
UC_ARM_REG_S9,
|
||||
UC_ARM_REG_S10,
|
||||
UC_ARM_REG_S11,
|
||||
UC_ARM_REG_S12,
|
||||
UC_ARM_REG_S13,
|
||||
UC_ARM_REG_S14,
|
||||
UC_ARM_REG_S15,
|
||||
UC_ARM_REG_S16,
|
||||
UC_ARM_REG_S17,
|
||||
UC_ARM_REG_S18,
|
||||
UC_ARM_REG_S19,
|
||||
UC_ARM_REG_S20,
|
||||
UC_ARM_REG_S21,
|
||||
UC_ARM_REG_S22,
|
||||
UC_ARM_REG_S23,
|
||||
UC_ARM_REG_S24,
|
||||
UC_ARM_REG_S25,
|
||||
UC_ARM_REG_S26,
|
||||
UC_ARM_REG_S27,
|
||||
UC_ARM_REG_S28,
|
||||
UC_ARM_REG_S29,
|
||||
UC_ARM_REG_S30,
|
||||
UC_ARM_REG_S31,
|
||||
|
||||
UC_ARM_REG_C1_C0_2, // Depreciated, use UC_ARM_REG_CP_REG instead
|
||||
UC_ARM_REG_C13_C0_2, // Depreciated, use UC_ARM_REG_CP_REG instead
|
||||
UC_ARM_REG_C13_C0_3, // Depreciated, use UC_ARM_REG_CP_REG instead
|
||||
|
||||
UC_ARM_REG_IPSR,
|
||||
UC_ARM_REG_MSP,
|
||||
UC_ARM_REG_PSP,
|
||||
UC_ARM_REG_CONTROL,
|
||||
UC_ARM_REG_IAPSR,
|
||||
UC_ARM_REG_EAPSR,
|
||||
UC_ARM_REG_XPSR,
|
||||
UC_ARM_REG_EPSR,
|
||||
UC_ARM_REG_IEPSR,
|
||||
UC_ARM_REG_PRIMASK,
|
||||
UC_ARM_REG_BASEPRI,
|
||||
UC_ARM_REG_BASEPRI_MAX,
|
||||
UC_ARM_REG_FAULTMASK,
|
||||
UC_ARM_REG_APSR_NZCVQ,
|
||||
UC_ARM_REG_APSR_G,
|
||||
UC_ARM_REG_APSR_NZCVQG,
|
||||
UC_ARM_REG_IAPSR_NZCVQ,
|
||||
UC_ARM_REG_IAPSR_G,
|
||||
UC_ARM_REG_IAPSR_NZCVQG,
|
||||
UC_ARM_REG_EAPSR_NZCVQ,
|
||||
UC_ARM_REG_EAPSR_G,
|
||||
UC_ARM_REG_EAPSR_NZCVQG,
|
||||
UC_ARM_REG_XPSR_NZCVQ,
|
||||
UC_ARM_REG_XPSR_G,
|
||||
UC_ARM_REG_XPSR_NZCVQG,
|
||||
UC_ARM_REG_CP_REG,
|
||||
// A pseudo-register for fetching the exception syndrome
|
||||
// from the CPU state. This is not a real register.
|
||||
UC_ARM_REG_ESR,
|
||||
UC_ARM_REG_ENDING, // <-- mark the end of the list or registers
|
||||
|
||||
//> alias registers
|
||||
UC_ARM_REG_R13 = UC_ARM_REG_SP,
|
||||
UC_ARM_REG_R14 = UC_ARM_REG_LR,
|
||||
UC_ARM_REG_R15 = UC_ARM_REG_PC,
|
||||
|
||||
UC_ARM_REG_SB = UC_ARM_REG_R9,
|
||||
UC_ARM_REG_SL = UC_ARM_REG_R10,
|
||||
UC_ARM_REG_FP = UC_ARM_REG_R11,
|
||||
UC_ARM_REG_IP = UC_ARM_REG_R12,
|
||||
} uc_arm_reg;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,393 @@
|
||||
/* Unicorn Emulator Engine */
|
||||
/* By Nguyen Anh Quynh <aquynh@gmail.com>, 2015-2017 */
|
||||
/* This file is released under LGPL2.
|
||||
See COPYING.LGPL2 in root directory for more details
|
||||
*/
|
||||
|
||||
#ifndef UNICORN_ARM64_H
|
||||
#define UNICORN_ARM64_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4201)
|
||||
#endif
|
||||
|
||||
//> ARM64 CPU
|
||||
typedef enum uc_cpu_arm64 {
|
||||
UC_CPU_ARM64_A57 = 0,
|
||||
UC_CPU_ARM64_A53,
|
||||
UC_CPU_ARM64_A72,
|
||||
UC_CPU_ARM64_MAX,
|
||||
|
||||
UC_CPU_ARM64_ENDING
|
||||
} uc_cpu_arm64;
|
||||
|
||||
// ARM64 coprocessor registers, use this with UC_ARM64_REG_CP_REG to
|
||||
// in call to uc_reg_write/read() to access the registers.
|
||||
typedef struct uc_arm64_cp_reg {
|
||||
uint32_t crn; // Coprocessor register number
|
||||
uint32_t crm; // Coprocessor register number
|
||||
uint32_t op0; // Opcode0
|
||||
uint32_t op1; // Opcode1
|
||||
uint32_t op2; // Opcode2
|
||||
uint64_t val; // The value to read/write
|
||||
} uc_arm64_cp_reg;
|
||||
|
||||
//> ARM64 registers
|
||||
typedef enum uc_arm64_reg {
|
||||
UC_ARM64_REG_INVALID = 0,
|
||||
|
||||
UC_ARM64_REG_X29,
|
||||
UC_ARM64_REG_X30,
|
||||
UC_ARM64_REG_NZCV,
|
||||
UC_ARM64_REG_SP,
|
||||
UC_ARM64_REG_WSP,
|
||||
UC_ARM64_REG_WZR,
|
||||
UC_ARM64_REG_XZR,
|
||||
UC_ARM64_REG_B0,
|
||||
UC_ARM64_REG_B1,
|
||||
UC_ARM64_REG_B2,
|
||||
UC_ARM64_REG_B3,
|
||||
UC_ARM64_REG_B4,
|
||||
UC_ARM64_REG_B5,
|
||||
UC_ARM64_REG_B6,
|
||||
UC_ARM64_REG_B7,
|
||||
UC_ARM64_REG_B8,
|
||||
UC_ARM64_REG_B9,
|
||||
UC_ARM64_REG_B10,
|
||||
UC_ARM64_REG_B11,
|
||||
UC_ARM64_REG_B12,
|
||||
UC_ARM64_REG_B13,
|
||||
UC_ARM64_REG_B14,
|
||||
UC_ARM64_REG_B15,
|
||||
UC_ARM64_REG_B16,
|
||||
UC_ARM64_REG_B17,
|
||||
UC_ARM64_REG_B18,
|
||||
UC_ARM64_REG_B19,
|
||||
UC_ARM64_REG_B20,
|
||||
UC_ARM64_REG_B21,
|
||||
UC_ARM64_REG_B22,
|
||||
UC_ARM64_REG_B23,
|
||||
UC_ARM64_REG_B24,
|
||||
UC_ARM64_REG_B25,
|
||||
UC_ARM64_REG_B26,
|
||||
UC_ARM64_REG_B27,
|
||||
UC_ARM64_REG_B28,
|
||||
UC_ARM64_REG_B29,
|
||||
UC_ARM64_REG_B30,
|
||||
UC_ARM64_REG_B31,
|
||||
UC_ARM64_REG_D0,
|
||||
UC_ARM64_REG_D1,
|
||||
UC_ARM64_REG_D2,
|
||||
UC_ARM64_REG_D3,
|
||||
UC_ARM64_REG_D4,
|
||||
UC_ARM64_REG_D5,
|
||||
UC_ARM64_REG_D6,
|
||||
UC_ARM64_REG_D7,
|
||||
UC_ARM64_REG_D8,
|
||||
UC_ARM64_REG_D9,
|
||||
UC_ARM64_REG_D10,
|
||||
UC_ARM64_REG_D11,
|
||||
UC_ARM64_REG_D12,
|
||||
UC_ARM64_REG_D13,
|
||||
UC_ARM64_REG_D14,
|
||||
UC_ARM64_REG_D15,
|
||||
UC_ARM64_REG_D16,
|
||||
UC_ARM64_REG_D17,
|
||||
UC_ARM64_REG_D18,
|
||||
UC_ARM64_REG_D19,
|
||||
UC_ARM64_REG_D20,
|
||||
UC_ARM64_REG_D21,
|
||||
UC_ARM64_REG_D22,
|
||||
UC_ARM64_REG_D23,
|
||||
UC_ARM64_REG_D24,
|
||||
UC_ARM64_REG_D25,
|
||||
UC_ARM64_REG_D26,
|
||||
UC_ARM64_REG_D27,
|
||||
UC_ARM64_REG_D28,
|
||||
UC_ARM64_REG_D29,
|
||||
UC_ARM64_REG_D30,
|
||||
UC_ARM64_REG_D31,
|
||||
UC_ARM64_REG_H0,
|
||||
UC_ARM64_REG_H1,
|
||||
UC_ARM64_REG_H2,
|
||||
UC_ARM64_REG_H3,
|
||||
UC_ARM64_REG_H4,
|
||||
UC_ARM64_REG_H5,
|
||||
UC_ARM64_REG_H6,
|
||||
UC_ARM64_REG_H7,
|
||||
UC_ARM64_REG_H8,
|
||||
UC_ARM64_REG_H9,
|
||||
UC_ARM64_REG_H10,
|
||||
UC_ARM64_REG_H11,
|
||||
UC_ARM64_REG_H12,
|
||||
UC_ARM64_REG_H13,
|
||||
UC_ARM64_REG_H14,
|
||||
UC_ARM64_REG_H15,
|
||||
UC_ARM64_REG_H16,
|
||||
UC_ARM64_REG_H17,
|
||||
UC_ARM64_REG_H18,
|
||||
UC_ARM64_REG_H19,
|
||||
UC_ARM64_REG_H20,
|
||||
UC_ARM64_REG_H21,
|
||||
UC_ARM64_REG_H22,
|
||||
UC_ARM64_REG_H23,
|
||||
UC_ARM64_REG_H24,
|
||||
UC_ARM64_REG_H25,
|
||||
UC_ARM64_REG_H26,
|
||||
UC_ARM64_REG_H27,
|
||||
UC_ARM64_REG_H28,
|
||||
UC_ARM64_REG_H29,
|
||||
UC_ARM64_REG_H30,
|
||||
UC_ARM64_REG_H31,
|
||||
UC_ARM64_REG_Q0,
|
||||
UC_ARM64_REG_Q1,
|
||||
UC_ARM64_REG_Q2,
|
||||
UC_ARM64_REG_Q3,
|
||||
UC_ARM64_REG_Q4,
|
||||
UC_ARM64_REG_Q5,
|
||||
UC_ARM64_REG_Q6,
|
||||
UC_ARM64_REG_Q7,
|
||||
UC_ARM64_REG_Q8,
|
||||
UC_ARM64_REG_Q9,
|
||||
UC_ARM64_REG_Q10,
|
||||
UC_ARM64_REG_Q11,
|
||||
UC_ARM64_REG_Q12,
|
||||
UC_ARM64_REG_Q13,
|
||||
UC_ARM64_REG_Q14,
|
||||
UC_ARM64_REG_Q15,
|
||||
UC_ARM64_REG_Q16,
|
||||
UC_ARM64_REG_Q17,
|
||||
UC_ARM64_REG_Q18,
|
||||
UC_ARM64_REG_Q19,
|
||||
UC_ARM64_REG_Q20,
|
||||
UC_ARM64_REG_Q21,
|
||||
UC_ARM64_REG_Q22,
|
||||
UC_ARM64_REG_Q23,
|
||||
UC_ARM64_REG_Q24,
|
||||
UC_ARM64_REG_Q25,
|
||||
UC_ARM64_REG_Q26,
|
||||
UC_ARM64_REG_Q27,
|
||||
UC_ARM64_REG_Q28,
|
||||
UC_ARM64_REG_Q29,
|
||||
UC_ARM64_REG_Q30,
|
||||
UC_ARM64_REG_Q31,
|
||||
UC_ARM64_REG_S0,
|
||||
UC_ARM64_REG_S1,
|
||||
UC_ARM64_REG_S2,
|
||||
UC_ARM64_REG_S3,
|
||||
UC_ARM64_REG_S4,
|
||||
UC_ARM64_REG_S5,
|
||||
UC_ARM64_REG_S6,
|
||||
UC_ARM64_REG_S7,
|
||||
UC_ARM64_REG_S8,
|
||||
UC_ARM64_REG_S9,
|
||||
UC_ARM64_REG_S10,
|
||||
UC_ARM64_REG_S11,
|
||||
UC_ARM64_REG_S12,
|
||||
UC_ARM64_REG_S13,
|
||||
UC_ARM64_REG_S14,
|
||||
UC_ARM64_REG_S15,
|
||||
UC_ARM64_REG_S16,
|
||||
UC_ARM64_REG_S17,
|
||||
UC_ARM64_REG_S18,
|
||||
UC_ARM64_REG_S19,
|
||||
UC_ARM64_REG_S20,
|
||||
UC_ARM64_REG_S21,
|
||||
UC_ARM64_REG_S22,
|
||||
UC_ARM64_REG_S23,
|
||||
UC_ARM64_REG_S24,
|
||||
UC_ARM64_REG_S25,
|
||||
UC_ARM64_REG_S26,
|
||||
UC_ARM64_REG_S27,
|
||||
UC_ARM64_REG_S28,
|
||||
UC_ARM64_REG_S29,
|
||||
UC_ARM64_REG_S30,
|
||||
UC_ARM64_REG_S31,
|
||||
UC_ARM64_REG_W0,
|
||||
UC_ARM64_REG_W1,
|
||||
UC_ARM64_REG_W2,
|
||||
UC_ARM64_REG_W3,
|
||||
UC_ARM64_REG_W4,
|
||||
UC_ARM64_REG_W5,
|
||||
UC_ARM64_REG_W6,
|
||||
UC_ARM64_REG_W7,
|
||||
UC_ARM64_REG_W8,
|
||||
UC_ARM64_REG_W9,
|
||||
UC_ARM64_REG_W10,
|
||||
UC_ARM64_REG_W11,
|
||||
UC_ARM64_REG_W12,
|
||||
UC_ARM64_REG_W13,
|
||||
UC_ARM64_REG_W14,
|
||||
UC_ARM64_REG_W15,
|
||||
UC_ARM64_REG_W16,
|
||||
UC_ARM64_REG_W17,
|
||||
UC_ARM64_REG_W18,
|
||||
UC_ARM64_REG_W19,
|
||||
UC_ARM64_REG_W20,
|
||||
UC_ARM64_REG_W21,
|
||||
UC_ARM64_REG_W22,
|
||||
UC_ARM64_REG_W23,
|
||||
UC_ARM64_REG_W24,
|
||||
UC_ARM64_REG_W25,
|
||||
UC_ARM64_REG_W26,
|
||||
UC_ARM64_REG_W27,
|
||||
UC_ARM64_REG_W28,
|
||||
UC_ARM64_REG_W29,
|
||||
UC_ARM64_REG_W30,
|
||||
UC_ARM64_REG_X0,
|
||||
UC_ARM64_REG_X1,
|
||||
UC_ARM64_REG_X2,
|
||||
UC_ARM64_REG_X3,
|
||||
UC_ARM64_REG_X4,
|
||||
UC_ARM64_REG_X5,
|
||||
UC_ARM64_REG_X6,
|
||||
UC_ARM64_REG_X7,
|
||||
UC_ARM64_REG_X8,
|
||||
UC_ARM64_REG_X9,
|
||||
UC_ARM64_REG_X10,
|
||||
UC_ARM64_REG_X11,
|
||||
UC_ARM64_REG_X12,
|
||||
UC_ARM64_REG_X13,
|
||||
UC_ARM64_REG_X14,
|
||||
UC_ARM64_REG_X15,
|
||||
UC_ARM64_REG_X16,
|
||||
UC_ARM64_REG_X17,
|
||||
UC_ARM64_REG_X18,
|
||||
UC_ARM64_REG_X19,
|
||||
UC_ARM64_REG_X20,
|
||||
UC_ARM64_REG_X21,
|
||||
UC_ARM64_REG_X22,
|
||||
UC_ARM64_REG_X23,
|
||||
UC_ARM64_REG_X24,
|
||||
UC_ARM64_REG_X25,
|
||||
UC_ARM64_REG_X26,
|
||||
UC_ARM64_REG_X27,
|
||||
UC_ARM64_REG_X28,
|
||||
|
||||
UC_ARM64_REG_V0,
|
||||
UC_ARM64_REG_V1,
|
||||
UC_ARM64_REG_V2,
|
||||
UC_ARM64_REG_V3,
|
||||
UC_ARM64_REG_V4,
|
||||
UC_ARM64_REG_V5,
|
||||
UC_ARM64_REG_V6,
|
||||
UC_ARM64_REG_V7,
|
||||
UC_ARM64_REG_V8,
|
||||
UC_ARM64_REG_V9,
|
||||
UC_ARM64_REG_V10,
|
||||
UC_ARM64_REG_V11,
|
||||
UC_ARM64_REG_V12,
|
||||
UC_ARM64_REG_V13,
|
||||
UC_ARM64_REG_V14,
|
||||
UC_ARM64_REG_V15,
|
||||
UC_ARM64_REG_V16,
|
||||
UC_ARM64_REG_V17,
|
||||
UC_ARM64_REG_V18,
|
||||
UC_ARM64_REG_V19,
|
||||
UC_ARM64_REG_V20,
|
||||
UC_ARM64_REG_V21,
|
||||
UC_ARM64_REG_V22,
|
||||
UC_ARM64_REG_V23,
|
||||
UC_ARM64_REG_V24,
|
||||
UC_ARM64_REG_V25,
|
||||
UC_ARM64_REG_V26,
|
||||
UC_ARM64_REG_V27,
|
||||
UC_ARM64_REG_V28,
|
||||
UC_ARM64_REG_V29,
|
||||
UC_ARM64_REG_V30,
|
||||
UC_ARM64_REG_V31,
|
||||
|
||||
//> pseudo registers
|
||||
UC_ARM64_REG_PC, // program counter register
|
||||
|
||||
UC_ARM64_REG_CPACR_EL1,
|
||||
|
||||
//> thread registers, depreciated, use UC_ARM64_REG_CP_REG instead
|
||||
UC_ARM64_REG_TPIDR_EL0,
|
||||
UC_ARM64_REG_TPIDRRO_EL0,
|
||||
UC_ARM64_REG_TPIDR_EL1,
|
||||
|
||||
UC_ARM64_REG_PSTATE,
|
||||
|
||||
//> exception link registers, depreciated, use UC_ARM64_REG_CP_REG instead
|
||||
UC_ARM64_REG_ELR_EL0,
|
||||
UC_ARM64_REG_ELR_EL1,
|
||||
UC_ARM64_REG_ELR_EL2,
|
||||
UC_ARM64_REG_ELR_EL3,
|
||||
|
||||
//> stack pointers registers, depreciated, use UC_ARM64_REG_CP_REG instead
|
||||
UC_ARM64_REG_SP_EL0,
|
||||
UC_ARM64_REG_SP_EL1,
|
||||
UC_ARM64_REG_SP_EL2,
|
||||
UC_ARM64_REG_SP_EL3,
|
||||
|
||||
//> other CP15 registers, depreciated, use UC_ARM64_REG_CP_REG instead
|
||||
UC_ARM64_REG_TTBR0_EL1,
|
||||
UC_ARM64_REG_TTBR1_EL1,
|
||||
|
||||
UC_ARM64_REG_ESR_EL0,
|
||||
UC_ARM64_REG_ESR_EL1,
|
||||
UC_ARM64_REG_ESR_EL2,
|
||||
UC_ARM64_REG_ESR_EL3,
|
||||
|
||||
UC_ARM64_REG_FAR_EL0,
|
||||
UC_ARM64_REG_FAR_EL1,
|
||||
UC_ARM64_REG_FAR_EL2,
|
||||
UC_ARM64_REG_FAR_EL3,
|
||||
|
||||
UC_ARM64_REG_PAR_EL1,
|
||||
|
||||
UC_ARM64_REG_MAIR_EL1,
|
||||
|
||||
UC_ARM64_REG_VBAR_EL0,
|
||||
UC_ARM64_REG_VBAR_EL1,
|
||||
UC_ARM64_REG_VBAR_EL2,
|
||||
UC_ARM64_REG_VBAR_EL3,
|
||||
|
||||
UC_ARM64_REG_CP_REG,
|
||||
|
||||
//> floating point control and status registers
|
||||
UC_ARM64_REG_FPCR,
|
||||
UC_ARM64_REG_FPSR,
|
||||
|
||||
UC_ARM64_REG_ENDING, // <-- mark the end of the list of registers
|
||||
|
||||
//> alias registers
|
||||
|
||||
UC_ARM64_REG_IP0 = UC_ARM64_REG_X16,
|
||||
UC_ARM64_REG_IP1 = UC_ARM64_REG_X17,
|
||||
UC_ARM64_REG_FP = UC_ARM64_REG_X29,
|
||||
UC_ARM64_REG_LR = UC_ARM64_REG_X30,
|
||||
} uc_arm64_reg;
|
||||
|
||||
// Callback function for tracing MRS/MSR/SYS/SYSL. If this callback returns
|
||||
// true, the read/write to system registers would be skipped (even though it may
|
||||
// cause exceptions!). Note one callback per instruction is allowed.
|
||||
// @reg: The source/destination register.
|
||||
// @cp_reg: The source/destincation system register.
|
||||
// @user_data: The user data.
|
||||
typedef uint32_t (*uc_cb_insn_sys_t)(uc_engine *uc, uc_arm64_reg reg,
|
||||
const uc_arm64_cp_reg *cp_reg,
|
||||
void *user_data);
|
||||
|
||||
//> ARM64 instructions
|
||||
typedef enum uc_arm64_insn {
|
||||
UC_ARM64_INS_INVALID = 0,
|
||||
|
||||
UC_ARM64_INS_MRS,
|
||||
UC_ARM64_INS_MSR,
|
||||
UC_ARM64_INS_SYS,
|
||||
UC_ARM64_INS_SYSL,
|
||||
|
||||
UC_ARM64_INS_ENDING
|
||||
} uc_arm64_insn;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,81 @@
|
||||
/* Unicorn Emulator Engine */
|
||||
/* By Nguyen Anh Quynh <aquynh@gmail.com>, 2014-2017 */
|
||||
/* This file is released under LGPL2.
|
||||
See COPYING.LGPL2 in root directory for more details
|
||||
*/
|
||||
|
||||
#ifndef UNICORN_M68K_H
|
||||
#define UNICORN_M68K_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4201)
|
||||
#endif
|
||||
|
||||
//> M68K CPU
|
||||
typedef enum uc_cpu_m68k {
|
||||
UC_CPU_M68K_M5206 = 0,
|
||||
UC_CPU_M68K_M68000,
|
||||
UC_CPU_M68K_M68020,
|
||||
UC_CPU_M68K_M68030,
|
||||
UC_CPU_M68K_M68040,
|
||||
UC_CPU_M68K_M68060,
|
||||
UC_CPU_M68K_M5208,
|
||||
UC_CPU_M68K_CFV4E,
|
||||
UC_CPU_M68K_ANY,
|
||||
|
||||
UC_CPU_M68K_ENDING
|
||||
} uc_cpu_m68k;
|
||||
|
||||
//> M68K registers
|
||||
typedef enum uc_m68k_reg {
|
||||
UC_M68K_REG_INVALID = 0,
|
||||
|
||||
UC_M68K_REG_A0,
|
||||
UC_M68K_REG_A1,
|
||||
UC_M68K_REG_A2,
|
||||
UC_M68K_REG_A3,
|
||||
UC_M68K_REG_A4,
|
||||
UC_M68K_REG_A5,
|
||||
UC_M68K_REG_A6,
|
||||
UC_M68K_REG_A7,
|
||||
|
||||
UC_M68K_REG_D0,
|
||||
UC_M68K_REG_D1,
|
||||
UC_M68K_REG_D2,
|
||||
UC_M68K_REG_D3,
|
||||
UC_M68K_REG_D4,
|
||||
UC_M68K_REG_D5,
|
||||
UC_M68K_REG_D6,
|
||||
UC_M68K_REG_D7,
|
||||
|
||||
UC_M68K_REG_SR,
|
||||
UC_M68K_REG_PC,
|
||||
|
||||
UC_M68K_REG_CR_SFC,
|
||||
UC_M68K_REG_CR_DFC,
|
||||
UC_M68K_REG_CR_VBR,
|
||||
UC_M68K_REG_CR_CACR,
|
||||
UC_M68K_REG_CR_TC,
|
||||
UC_M68K_REG_CR_MMUSR,
|
||||
UC_M68K_REG_CR_SRP,
|
||||
UC_M68K_REG_CR_USP,
|
||||
UC_M68K_REG_CR_MSP,
|
||||
UC_M68K_REG_CR_ISP,
|
||||
UC_M68K_REG_CR_URP,
|
||||
UC_M68K_REG_CR_ITT0,
|
||||
UC_M68K_REG_CR_ITT1,
|
||||
UC_M68K_REG_CR_DTT0,
|
||||
UC_M68K_REG_CR_DTT1,
|
||||
|
||||
UC_M68K_REG_ENDING, // <-- mark the end of the list of registers
|
||||
} uc_m68k_reg;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,283 @@
|
||||
/* Unicorn Emulator Engine */
|
||||
/* By Nguyen Anh Quynh <aquynh@gmail.com>, 2015-2017 */
|
||||
/* This file is released under LGPL2.
|
||||
See COPYING.LGPL2 in root directory for more details
|
||||
*/
|
||||
|
||||
#ifndef UNICORN_MIPS_H
|
||||
#define UNICORN_MIPS_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// GCC MIPS toolchain has a default macro called "mips" which breaks
|
||||
// compilation
|
||||
#undef mips
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4201)
|
||||
#endif
|
||||
|
||||
//> MIPS32 CPUS
|
||||
typedef enum uc_cpu_mips32 {
|
||||
UC_CPU_MIPS32_4KC = 0,
|
||||
UC_CPU_MIPS32_4KM,
|
||||
UC_CPU_MIPS32_4KECR1,
|
||||
UC_CPU_MIPS32_4KEMR1,
|
||||
UC_CPU_MIPS32_4KEC,
|
||||
UC_CPU_MIPS32_4KEM,
|
||||
UC_CPU_MIPS32_24KC,
|
||||
UC_CPU_MIPS32_24KEC,
|
||||
UC_CPU_MIPS32_24KF,
|
||||
UC_CPU_MIPS32_34KF,
|
||||
UC_CPU_MIPS32_74KF,
|
||||
UC_CPU_MIPS32_M14K,
|
||||
UC_CPU_MIPS32_M14KC,
|
||||
UC_CPU_MIPS32_P5600,
|
||||
UC_CPU_MIPS32_MIPS32R6_GENERIC,
|
||||
UC_CPU_MIPS32_I7200,
|
||||
|
||||
UC_CPU_MIPS32_ENDING
|
||||
} uc_cpu_mips32;
|
||||
|
||||
//> MIPS64 CPUS
|
||||
typedef enum uc_cpu_mips64 {
|
||||
UC_CPU_MIPS64_R4000 = 0,
|
||||
UC_CPU_MIPS64_VR5432,
|
||||
UC_CPU_MIPS64_5KC,
|
||||
UC_CPU_MIPS64_5KF,
|
||||
UC_CPU_MIPS64_20KC,
|
||||
UC_CPU_MIPS64_MIPS64R2_GENERIC,
|
||||
UC_CPU_MIPS64_5KEC,
|
||||
UC_CPU_MIPS64_5KEF,
|
||||
UC_CPU_MIPS64_I6400,
|
||||
UC_CPU_MIPS64_I6500,
|
||||
UC_CPU_MIPS64_LOONGSON_2E,
|
||||
UC_CPU_MIPS64_LOONGSON_2F,
|
||||
UC_CPU_MIPS64_MIPS64DSPR2,
|
||||
|
||||
UC_CPU_MIPS64_ENDING
|
||||
} uc_cpu_mips64;
|
||||
|
||||
//> MIPS registers
|
||||
typedef enum uc_mips_reg {
|
||||
UC_MIPS_REG_INVALID = 0,
|
||||
//> General purpose registers
|
||||
UC_MIPS_REG_PC,
|
||||
|
||||
UC_MIPS_REG_0,
|
||||
UC_MIPS_REG_1,
|
||||
UC_MIPS_REG_2,
|
||||
UC_MIPS_REG_3,
|
||||
UC_MIPS_REG_4,
|
||||
UC_MIPS_REG_5,
|
||||
UC_MIPS_REG_6,
|
||||
UC_MIPS_REG_7,
|
||||
UC_MIPS_REG_8,
|
||||
UC_MIPS_REG_9,
|
||||
UC_MIPS_REG_10,
|
||||
UC_MIPS_REG_11,
|
||||
UC_MIPS_REG_12,
|
||||
UC_MIPS_REG_13,
|
||||
UC_MIPS_REG_14,
|
||||
UC_MIPS_REG_15,
|
||||
UC_MIPS_REG_16,
|
||||
UC_MIPS_REG_17,
|
||||
UC_MIPS_REG_18,
|
||||
UC_MIPS_REG_19,
|
||||
UC_MIPS_REG_20,
|
||||
UC_MIPS_REG_21,
|
||||
UC_MIPS_REG_22,
|
||||
UC_MIPS_REG_23,
|
||||
UC_MIPS_REG_24,
|
||||
UC_MIPS_REG_25,
|
||||
UC_MIPS_REG_26,
|
||||
UC_MIPS_REG_27,
|
||||
UC_MIPS_REG_28,
|
||||
UC_MIPS_REG_29,
|
||||
UC_MIPS_REG_30,
|
||||
UC_MIPS_REG_31,
|
||||
|
||||
//> DSP registers
|
||||
UC_MIPS_REG_DSPCCOND,
|
||||
UC_MIPS_REG_DSPCARRY,
|
||||
UC_MIPS_REG_DSPEFI,
|
||||
UC_MIPS_REG_DSPOUTFLAG,
|
||||
UC_MIPS_REG_DSPOUTFLAG16_19,
|
||||
UC_MIPS_REG_DSPOUTFLAG20,
|
||||
UC_MIPS_REG_DSPOUTFLAG21,
|
||||
UC_MIPS_REG_DSPOUTFLAG22,
|
||||
UC_MIPS_REG_DSPOUTFLAG23,
|
||||
UC_MIPS_REG_DSPPOS,
|
||||
UC_MIPS_REG_DSPSCOUNT,
|
||||
|
||||
//> ACC registers
|
||||
UC_MIPS_REG_AC0,
|
||||
UC_MIPS_REG_AC1,
|
||||
UC_MIPS_REG_AC2,
|
||||
UC_MIPS_REG_AC3,
|
||||
|
||||
//> COP registers
|
||||
UC_MIPS_REG_CC0,
|
||||
UC_MIPS_REG_CC1,
|
||||
UC_MIPS_REG_CC2,
|
||||
UC_MIPS_REG_CC3,
|
||||
UC_MIPS_REG_CC4,
|
||||
UC_MIPS_REG_CC5,
|
||||
UC_MIPS_REG_CC6,
|
||||
UC_MIPS_REG_CC7,
|
||||
|
||||
//> FPU registers
|
||||
UC_MIPS_REG_F0,
|
||||
UC_MIPS_REG_F1,
|
||||
UC_MIPS_REG_F2,
|
||||
UC_MIPS_REG_F3,
|
||||
UC_MIPS_REG_F4,
|
||||
UC_MIPS_REG_F5,
|
||||
UC_MIPS_REG_F6,
|
||||
UC_MIPS_REG_F7,
|
||||
UC_MIPS_REG_F8,
|
||||
UC_MIPS_REG_F9,
|
||||
UC_MIPS_REG_F10,
|
||||
UC_MIPS_REG_F11,
|
||||
UC_MIPS_REG_F12,
|
||||
UC_MIPS_REG_F13,
|
||||
UC_MIPS_REG_F14,
|
||||
UC_MIPS_REG_F15,
|
||||
UC_MIPS_REG_F16,
|
||||
UC_MIPS_REG_F17,
|
||||
UC_MIPS_REG_F18,
|
||||
UC_MIPS_REG_F19,
|
||||
UC_MIPS_REG_F20,
|
||||
UC_MIPS_REG_F21,
|
||||
UC_MIPS_REG_F22,
|
||||
UC_MIPS_REG_F23,
|
||||
UC_MIPS_REG_F24,
|
||||
UC_MIPS_REG_F25,
|
||||
UC_MIPS_REG_F26,
|
||||
UC_MIPS_REG_F27,
|
||||
UC_MIPS_REG_F28,
|
||||
UC_MIPS_REG_F29,
|
||||
UC_MIPS_REG_F30,
|
||||
UC_MIPS_REG_F31,
|
||||
|
||||
UC_MIPS_REG_FCC0,
|
||||
UC_MIPS_REG_FCC1,
|
||||
UC_MIPS_REG_FCC2,
|
||||
UC_MIPS_REG_FCC3,
|
||||
UC_MIPS_REG_FCC4,
|
||||
UC_MIPS_REG_FCC5,
|
||||
UC_MIPS_REG_FCC6,
|
||||
UC_MIPS_REG_FCC7,
|
||||
|
||||
//> AFPR128
|
||||
UC_MIPS_REG_W0,
|
||||
UC_MIPS_REG_W1,
|
||||
UC_MIPS_REG_W2,
|
||||
UC_MIPS_REG_W3,
|
||||
UC_MIPS_REG_W4,
|
||||
UC_MIPS_REG_W5,
|
||||
UC_MIPS_REG_W6,
|
||||
UC_MIPS_REG_W7,
|
||||
UC_MIPS_REG_W8,
|
||||
UC_MIPS_REG_W9,
|
||||
UC_MIPS_REG_W10,
|
||||
UC_MIPS_REG_W11,
|
||||
UC_MIPS_REG_W12,
|
||||
UC_MIPS_REG_W13,
|
||||
UC_MIPS_REG_W14,
|
||||
UC_MIPS_REG_W15,
|
||||
UC_MIPS_REG_W16,
|
||||
UC_MIPS_REG_W17,
|
||||
UC_MIPS_REG_W18,
|
||||
UC_MIPS_REG_W19,
|
||||
UC_MIPS_REG_W20,
|
||||
UC_MIPS_REG_W21,
|
||||
UC_MIPS_REG_W22,
|
||||
UC_MIPS_REG_W23,
|
||||
UC_MIPS_REG_W24,
|
||||
UC_MIPS_REG_W25,
|
||||
UC_MIPS_REG_W26,
|
||||
UC_MIPS_REG_W27,
|
||||
UC_MIPS_REG_W28,
|
||||
UC_MIPS_REG_W29,
|
||||
UC_MIPS_REG_W30,
|
||||
UC_MIPS_REG_W31,
|
||||
|
||||
UC_MIPS_REG_HI,
|
||||
UC_MIPS_REG_LO,
|
||||
|
||||
UC_MIPS_REG_P0,
|
||||
UC_MIPS_REG_P1,
|
||||
UC_MIPS_REG_P2,
|
||||
|
||||
UC_MIPS_REG_MPL0,
|
||||
UC_MIPS_REG_MPL1,
|
||||
UC_MIPS_REG_MPL2,
|
||||
|
||||
UC_MIPS_REG_CP0_CONFIG3,
|
||||
UC_MIPS_REG_CP0_USERLOCAL,
|
||||
UC_MIPS_REG_CP0_STATUS,
|
||||
|
||||
// FCR(s) Ref:
|
||||
// https://s3-eu-west-1.amazonaws.com/downloads-mips/documents/MD00083-2B-MIPS64INT-AFP-06.01.pdf
|
||||
UC_MIPS_REG_FIR,
|
||||
UC_MIPS_REG_FCSR,
|
||||
|
||||
UC_MIPS_REG_ENDING, // <-- mark the end of the list or registers
|
||||
|
||||
// alias registers
|
||||
UC_MIPS_REG_ZERO = UC_MIPS_REG_0,
|
||||
UC_MIPS_REG_AT = UC_MIPS_REG_1,
|
||||
UC_MIPS_REG_V0 = UC_MIPS_REG_2,
|
||||
UC_MIPS_REG_V1 = UC_MIPS_REG_3,
|
||||
UC_MIPS_REG_A0 = UC_MIPS_REG_4,
|
||||
UC_MIPS_REG_A1 = UC_MIPS_REG_5,
|
||||
UC_MIPS_REG_A2 = UC_MIPS_REG_6,
|
||||
UC_MIPS_REG_A3 = UC_MIPS_REG_7,
|
||||
UC_MIPS_REG_T0 = UC_MIPS_REG_8,
|
||||
UC_MIPS_REG_T1 = UC_MIPS_REG_9,
|
||||
UC_MIPS_REG_T2 = UC_MIPS_REG_10,
|
||||
UC_MIPS_REG_T3 = UC_MIPS_REG_11,
|
||||
UC_MIPS_REG_T4 = UC_MIPS_REG_12,
|
||||
UC_MIPS_REG_T5 = UC_MIPS_REG_13,
|
||||
UC_MIPS_REG_T6 = UC_MIPS_REG_14,
|
||||
UC_MIPS_REG_T7 = UC_MIPS_REG_15,
|
||||
UC_MIPS_REG_S0 = UC_MIPS_REG_16,
|
||||
UC_MIPS_REG_S1 = UC_MIPS_REG_17,
|
||||
UC_MIPS_REG_S2 = UC_MIPS_REG_18,
|
||||
UC_MIPS_REG_S3 = UC_MIPS_REG_19,
|
||||
UC_MIPS_REG_S4 = UC_MIPS_REG_20,
|
||||
UC_MIPS_REG_S5 = UC_MIPS_REG_21,
|
||||
UC_MIPS_REG_S6 = UC_MIPS_REG_22,
|
||||
UC_MIPS_REG_S7 = UC_MIPS_REG_23,
|
||||
UC_MIPS_REG_T8 = UC_MIPS_REG_24,
|
||||
UC_MIPS_REG_T9 = UC_MIPS_REG_25,
|
||||
UC_MIPS_REG_K0 = UC_MIPS_REG_26,
|
||||
UC_MIPS_REG_K1 = UC_MIPS_REG_27,
|
||||
UC_MIPS_REG_GP = UC_MIPS_REG_28,
|
||||
UC_MIPS_REG_SP = UC_MIPS_REG_29,
|
||||
UC_MIPS_REG_FP = UC_MIPS_REG_30,
|
||||
UC_MIPS_REG_S8 = UC_MIPS_REG_30,
|
||||
UC_MIPS_REG_RA = UC_MIPS_REG_31,
|
||||
|
||||
UC_MIPS_REG_HI0 = UC_MIPS_REG_AC0,
|
||||
UC_MIPS_REG_HI1 = UC_MIPS_REG_AC1,
|
||||
UC_MIPS_REG_HI2 = UC_MIPS_REG_AC2,
|
||||
UC_MIPS_REG_HI3 = UC_MIPS_REG_AC3,
|
||||
|
||||
UC_MIPS_REG_LO0 = UC_MIPS_REG_HI0,
|
||||
UC_MIPS_REG_LO1 = UC_MIPS_REG_HI1,
|
||||
UC_MIPS_REG_LO2 = UC_MIPS_REG_HI2,
|
||||
UC_MIPS_REG_LO3 = UC_MIPS_REG_HI3,
|
||||
} uc_mips_reg;
|
||||
|
||||
// This is only for backwards compatibility
|
||||
typedef uc_mips_reg UC_MIPS_REG;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,263 @@
|
||||
/* This file is released under LGPL2.
|
||||
See COPYING.LGPL2 in root directory for more details
|
||||
*/
|
||||
|
||||
/*
|
||||
This file is to support header files that are missing in MSVC and
|
||||
other non-standard compilers.
|
||||
*/
|
||||
#ifndef UNICORN_PLATFORM_H
|
||||
#define UNICORN_PLATFORM_H
|
||||
|
||||
/*
|
||||
These are the various MSVC versions as given by _MSC_VER:
|
||||
MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015)
|
||||
MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013)
|
||||
MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012)
|
||||
MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010)
|
||||
MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008)
|
||||
MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005)
|
||||
MSVC++ 7.1 _MSC_VER == 1310 (Visual Studio 2003)
|
||||
MSVC++ 7.0 _MSC_VER == 1300
|
||||
MSVC++ 6.0 _MSC_VER == 1200
|
||||
MSVC++ 5.0 _MSC_VER == 1100
|
||||
*/
|
||||
#define MSC_VER_VS2003 1310
|
||||
#define MSC_VER_VS2005 1400
|
||||
#define MSC_VER_VS2008 1500
|
||||
#define MSC_VER_VS2010 1600
|
||||
#define MSC_VER_VS2012 1700
|
||||
#define MSC_VER_VS2013 1800
|
||||
#define MSC_VER_VS2015 1900
|
||||
|
||||
// handle stdbool.h compatibility
|
||||
#if !defined(__CYGWIN__) && !defined(__MINGW32__) && !defined(__MINGW64__) && \
|
||||
(defined(WIN32) || defined(WIN64) || defined(_WIN32) || defined(_WIN64))
|
||||
// MSVC
|
||||
|
||||
// stdbool.h
|
||||
#if (_MSC_VER < MSC_VER_VS2013) || defined(_KERNEL_MODE)
|
||||
// this system does not have stdbool.h
|
||||
#ifndef __cplusplus
|
||||
typedef unsigned char bool;
|
||||
#define false 0
|
||||
#define true 1
|
||||
#endif // __cplusplus
|
||||
|
||||
#else
|
||||
// VisualStudio 2013+ -> C99 is supported
|
||||
#include <stdbool.h>
|
||||
#endif // (_MSC_VER < MSC_VER_VS2013) || defined(_KERNEL_MODE)
|
||||
|
||||
#else
|
||||
// not MSVC -> C99 is supported
|
||||
#include <stdbool.h>
|
||||
#endif // !defined(__CYGWIN__) && !defined(__MINGW32__) && !defined(__MINGW64__)
|
||||
// && (defined (WIN32) || defined (WIN64) || defined (_WIN32) || defined
|
||||
// (_WIN64))
|
||||
|
||||
#if (defined(_MSC_VER) && (_MSC_VER < MSC_VER_VS2010)) || defined(_KERNEL_MODE)
|
||||
// this system does not have stdint.h
|
||||
typedef signed char int8_t;
|
||||
typedef signed short int16_t;
|
||||
typedef signed int int32_t;
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned int uint32_t;
|
||||
typedef signed long long int64_t;
|
||||
typedef unsigned long long uint64_t;
|
||||
|
||||
typedef signed char int_fast8_t;
|
||||
typedef int int_fast16_t;
|
||||
typedef int int_fast32_t;
|
||||
typedef long long int_fast64_t;
|
||||
typedef unsigned char uint_fast8_t;
|
||||
typedef unsigned int uint_fast16_t;
|
||||
typedef unsigned int uint_fast32_t;
|
||||
typedef unsigned long long uint_fast64_t;
|
||||
|
||||
#if !defined(_W64)
|
||||
#if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300
|
||||
#define _W64 __w64
|
||||
#else
|
||||
#define _W64
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef _INTPTR_T_DEFINED
|
||||
#define _INTPTR_T_DEFINED
|
||||
#ifdef _WIN64
|
||||
typedef long long intptr_t;
|
||||
#else /* _WIN64 */
|
||||
typedef _W64 int intptr_t;
|
||||
#endif /* _WIN64 */
|
||||
#endif /* _INTPTR_T_DEFINED */
|
||||
|
||||
#ifndef _UINTPTR_T_DEFINED
|
||||
#define _UINTPTR_T_DEFINED
|
||||
#ifdef _WIN64
|
||||
typedef unsigned long long uintptr_t;
|
||||
#else /* _WIN64 */
|
||||
typedef _W64 unsigned int uintptr_t;
|
||||
#endif /* _WIN64 */
|
||||
#endif /* _UINTPTR_T_DEFINED */
|
||||
|
||||
#define INT8_MIN (-127i8 - 1)
|
||||
#define INT16_MIN (-32767i16 - 1)
|
||||
#define INT32_MIN (-2147483647i32 - 1)
|
||||
#define INT64_MIN (-9223372036854775807i64 - 1)
|
||||
#define INT8_MAX 127i8
|
||||
#define INT16_MAX 32767i16
|
||||
#define INT32_MAX 2147483647i32
|
||||
#define INT64_MAX 9223372036854775807i64
|
||||
#define UINT8_MAX 0xffui8
|
||||
#define UINT16_MAX 0xffffui16
|
||||
#define UINT32_MAX 0xffffffffui32
|
||||
#define UINT64_MAX 0xffffffffffffffffui64
|
||||
|
||||
#define INT_FAST8_MIN INT8_MIN
|
||||
#define INT_FAST16_MIN INT32_MIN
|
||||
#define INT_FAST32_MIN INT32_MIN
|
||||
#define INT_FAST64_MIN INT64_MIN
|
||||
#define INT_FAST8_MAX INT8_MAX
|
||||
#define INT_FAST16_MAX INT32_MAX
|
||||
#define INT_FAST32_MAX INT32_MAX
|
||||
#define INT_FAST64_MAX INT64_MAX
|
||||
#define UINT_FAST8_MAX UINT8_MAX
|
||||
#define UINT_FAST16_MAX UINT32_MAX
|
||||
#define UINT_FAST32_MAX UINT32_MAX
|
||||
#define UINT_FAST64_MAX UINT64_MAX
|
||||
|
||||
#ifdef _WIN64
|
||||
#define INTPTR_MIN INT64_MIN
|
||||
#define INTPTR_MAX INT64_MAX
|
||||
#define UINTPTR_MAX UINT64_MAX
|
||||
#else /* _WIN64 */
|
||||
#define INTPTR_MIN INT32_MIN
|
||||
#define INTPTR_MAX INT32_MAX
|
||||
#define UINTPTR_MAX UINT32_MAX
|
||||
#endif /* _WIN64 */
|
||||
|
||||
#else // this system has stdint.h
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER == MSC_VER_VS2010)
|
||||
#define _INTPTR 2
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#endif // (defined(_MSC_VER) && (_MSC_VER < MSC_VER_VS2010)) ||
|
||||
// defined(_KERNEL_MODE)
|
||||
|
||||
// handle inttypes.h compatibility
|
||||
#if (defined(_MSC_VER) && (_MSC_VER < MSC_VER_VS2013)) || defined(_KERNEL_MODE)
|
||||
// this system does not have inttypes.h
|
||||
|
||||
#define __PRI_8_LENGTH_MODIFIER__ "hh"
|
||||
#define __PRI_64_LENGTH_MODIFIER__ "ll"
|
||||
|
||||
#define PRId8 __PRI_8_LENGTH_MODIFIER__ "d"
|
||||
#define PRIi8 __PRI_8_LENGTH_MODIFIER__ "i"
|
||||
#define PRIo8 __PRI_8_LENGTH_MODIFIER__ "o"
|
||||
#define PRIu8 __PRI_8_LENGTH_MODIFIER__ "u"
|
||||
#define PRIx8 __PRI_8_LENGTH_MODIFIER__ "x"
|
||||
#define PRIX8 __PRI_8_LENGTH_MODIFIER__ "X"
|
||||
|
||||
#define PRId16 "hd"
|
||||
#define PRIi16 "hi"
|
||||
#define PRIo16 "ho"
|
||||
#define PRIu16 "hu"
|
||||
#define PRIx16 "hx"
|
||||
#define PRIX16 "hX"
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER <= MSC_VER_VS2012)
|
||||
#define PRId32 "ld"
|
||||
#define PRIi32 "li"
|
||||
#define PRIo32 "lo"
|
||||
#define PRIu32 "lu"
|
||||
#define PRIx32 "lx"
|
||||
#define PRIX32 "lX"
|
||||
#else // OSX
|
||||
#define PRId32 "d"
|
||||
#define PRIi32 "i"
|
||||
#define PRIo32 "o"
|
||||
#define PRIu32 "u"
|
||||
#define PRIx32 "x"
|
||||
#define PRIX32 "X"
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER <= MSC_VER_VS2012)
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER <= MSC_VER_VS2012)
|
||||
// redefine functions from inttypes.h used in cstool
|
||||
#define strtoull _strtoui64
|
||||
#endif
|
||||
|
||||
#define PRId64 __PRI_64_LENGTH_MODIFIER__ "d"
|
||||
#define PRIi64 __PRI_64_LENGTH_MODIFIER__ "i"
|
||||
#define PRIo64 __PRI_64_LENGTH_MODIFIER__ "o"
|
||||
#define PRIu64 __PRI_64_LENGTH_MODIFIER__ "u"
|
||||
#define PRIx64 __PRI_64_LENGTH_MODIFIER__ "x"
|
||||
#define PRIX64 __PRI_64_LENGTH_MODIFIER__ "X"
|
||||
|
||||
#else
|
||||
// this system has inttypes.h by default
|
||||
#include <inttypes.h>
|
||||
#endif // #if defined(_MSC_VER) && (_MSC_VER < MSC_VER_VS2013) ||
|
||||
// defined(_KERNEL_MODE)
|
||||
|
||||
// sys/time.h compatibility
|
||||
#if defined(_MSC_VER)
|
||||
#include <sys/types.h>
|
||||
#include <sys/timeb.h>
|
||||
#include <windows.h>
|
||||
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
// unistd.h compatibility
|
||||
#if defined(_MSC_VER)
|
||||
|
||||
static int usleep(uint32_t usec)
|
||||
{
|
||||
HANDLE timer;
|
||||
LARGE_INTEGER due;
|
||||
|
||||
timer = CreateWaitableTimer(NULL, TRUE, NULL);
|
||||
if (!timer)
|
||||
return -1;
|
||||
|
||||
due.QuadPart = (-((int64_t)usec)) * 10LL;
|
||||
if (!SetWaitableTimer(timer, &due, 0, NULL, NULL, 0)) {
|
||||
CloseHandle(timer);
|
||||
return -1;
|
||||
}
|
||||
WaitForSingleObject(timer, INFINITE);
|
||||
CloseHandle(timer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
// misc support
|
||||
#if defined(_MSC_VER)
|
||||
#ifdef _WIN64
|
||||
typedef signed __int64 ssize_t;
|
||||
#else
|
||||
typedef _W64 signed int ssize_t;
|
||||
#endif
|
||||
|
||||
#ifndef va_copy
|
||||
#define va_copy(d, s) ((d) = (s))
|
||||
#endif
|
||||
#define strcasecmp _stricmp
|
||||
#if (_MSC_VER < MSC_VER_VS2015)
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
#if (_MSC_VER <= MSC_VER_VS2013)
|
||||
#define strtoll _strtoi64
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // UNICORN_PLATFORM_H
|
||||
@@ -0,0 +1,434 @@
|
||||
/* Unicorn Engine */
|
||||
/* By Nguyen Anh Quynh <aquynh@gmail.com>, 2015-2017 */
|
||||
/* This file is released under LGPL2.
|
||||
See COPYING.LGPL2 in root directory for more details
|
||||
*/
|
||||
|
||||
#ifndef UNICORN_PPC_H
|
||||
#define UNICORN_PPC_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4201)
|
||||
#endif
|
||||
|
||||
//> PPC CPU
|
||||
typedef enum uc_cpu_ppc {
|
||||
UC_CPU_PPC32_401 = 0,
|
||||
UC_CPU_PPC32_401A1,
|
||||
UC_CPU_PPC32_401B2,
|
||||
UC_CPU_PPC32_401C2,
|
||||
UC_CPU_PPC32_401D2,
|
||||
UC_CPU_PPC32_401E2,
|
||||
UC_CPU_PPC32_401F2,
|
||||
UC_CPU_PPC32_401G2,
|
||||
UC_CPU_PPC32_IOP480,
|
||||
UC_CPU_PPC32_COBRA,
|
||||
UC_CPU_PPC32_403GA,
|
||||
UC_CPU_PPC32_403GB,
|
||||
UC_CPU_PPC32_403GC,
|
||||
UC_CPU_PPC32_403GCX,
|
||||
UC_CPU_PPC32_405D2,
|
||||
UC_CPU_PPC32_405D4,
|
||||
UC_CPU_PPC32_405CRA,
|
||||
UC_CPU_PPC32_405CRB,
|
||||
UC_CPU_PPC32_405CRC,
|
||||
UC_CPU_PPC32_405EP,
|
||||
UC_CPU_PPC32_405EZ,
|
||||
UC_CPU_PPC32_405GPA,
|
||||
UC_CPU_PPC32_405GPB,
|
||||
UC_CPU_PPC32_405GPC,
|
||||
UC_CPU_PPC32_405GPD,
|
||||
UC_CPU_PPC32_405GPR,
|
||||
UC_CPU_PPC32_405LP,
|
||||
UC_CPU_PPC32_NPE405H,
|
||||
UC_CPU_PPC32_NPE405H2,
|
||||
UC_CPU_PPC32_NPE405L,
|
||||
UC_CPU_PPC32_NPE4GS3,
|
||||
UC_CPU_PPC32_STB03,
|
||||
UC_CPU_PPC32_STB04,
|
||||
UC_CPU_PPC32_STB25,
|
||||
UC_CPU_PPC32_X2VP4,
|
||||
UC_CPU_PPC32_X2VP20,
|
||||
UC_CPU_PPC32_440_XILINX,
|
||||
UC_CPU_PPC32_440_XILINX_W_DFPU,
|
||||
UC_CPU_PPC32_440EPA,
|
||||
UC_CPU_PPC32_440EPB,
|
||||
UC_CPU_PPC32_440EPX,
|
||||
UC_CPU_PPC32_460EXB,
|
||||
UC_CPU_PPC32_G2,
|
||||
UC_CPU_PPC32_G2H4,
|
||||
UC_CPU_PPC32_G2GP,
|
||||
UC_CPU_PPC32_G2LS,
|
||||
UC_CPU_PPC32_G2HIP3,
|
||||
UC_CPU_PPC32_G2HIP4,
|
||||
UC_CPU_PPC32_MPC603,
|
||||
UC_CPU_PPC32_G2LE,
|
||||
UC_CPU_PPC32_G2LEGP,
|
||||
UC_CPU_PPC32_G2LELS,
|
||||
UC_CPU_PPC32_G2LEGP1,
|
||||
UC_CPU_PPC32_G2LEGP3,
|
||||
UC_CPU_PPC32_MPC5200_V10,
|
||||
UC_CPU_PPC32_MPC5200_V11,
|
||||
UC_CPU_PPC32_MPC5200_V12,
|
||||
UC_CPU_PPC32_MPC5200B_V20,
|
||||
UC_CPU_PPC32_MPC5200B_V21,
|
||||
UC_CPU_PPC32_E200Z5,
|
||||
UC_CPU_PPC32_E200Z6,
|
||||
UC_CPU_PPC32_E300C1,
|
||||
UC_CPU_PPC32_E300C2,
|
||||
UC_CPU_PPC32_E300C3,
|
||||
UC_CPU_PPC32_E300C4,
|
||||
UC_CPU_PPC32_MPC8343,
|
||||
UC_CPU_PPC32_MPC8343A,
|
||||
UC_CPU_PPC32_MPC8343E,
|
||||
UC_CPU_PPC32_MPC8343EA,
|
||||
UC_CPU_PPC32_MPC8347T,
|
||||
UC_CPU_PPC32_MPC8347P,
|
||||
UC_CPU_PPC32_MPC8347AT,
|
||||
UC_CPU_PPC32_MPC8347AP,
|
||||
UC_CPU_PPC32_MPC8347ET,
|
||||
UC_CPU_PPC32_MPC8347EP,
|
||||
UC_CPU_PPC32_MPC8347EAT,
|
||||
UC_CPU_PPC32_MPC8347EAP,
|
||||
UC_CPU_PPC32_MPC8349,
|
||||
UC_CPU_PPC32_MPC8349A,
|
||||
UC_CPU_PPC32_MPC8349E,
|
||||
UC_CPU_PPC32_MPC8349EA,
|
||||
UC_CPU_PPC32_MPC8377,
|
||||
UC_CPU_PPC32_MPC8377E,
|
||||
UC_CPU_PPC32_MPC8378,
|
||||
UC_CPU_PPC32_MPC8378E,
|
||||
UC_CPU_PPC32_MPC8379,
|
||||
UC_CPU_PPC32_MPC8379E,
|
||||
UC_CPU_PPC32_E500_V10,
|
||||
UC_CPU_PPC32_E500_V20,
|
||||
UC_CPU_PPC32_E500V2_V10,
|
||||
UC_CPU_PPC32_E500V2_V20,
|
||||
UC_CPU_PPC32_E500V2_V21,
|
||||
UC_CPU_PPC32_E500V2_V22,
|
||||
UC_CPU_PPC32_E500V2_V30,
|
||||
UC_CPU_PPC32_E500MC,
|
||||
UC_CPU_PPC32_MPC8533_V10,
|
||||
UC_CPU_PPC32_MPC8533_V11,
|
||||
UC_CPU_PPC32_MPC8533E_V10,
|
||||
UC_CPU_PPC32_MPC8533E_V11,
|
||||
UC_CPU_PPC32_MPC8540_V10,
|
||||
UC_CPU_PPC32_MPC8540_V20,
|
||||
UC_CPU_PPC32_MPC8540_V21,
|
||||
UC_CPU_PPC32_MPC8541_V10,
|
||||
UC_CPU_PPC32_MPC8541_V11,
|
||||
UC_CPU_PPC32_MPC8541E_V10,
|
||||
UC_CPU_PPC32_MPC8541E_V11,
|
||||
UC_CPU_PPC32_MPC8543_V10,
|
||||
UC_CPU_PPC32_MPC8543_V11,
|
||||
UC_CPU_PPC32_MPC8543_V20,
|
||||
UC_CPU_PPC32_MPC8543_V21,
|
||||
UC_CPU_PPC32_MPC8543E_V10,
|
||||
UC_CPU_PPC32_MPC8543E_V11,
|
||||
UC_CPU_PPC32_MPC8543E_V20,
|
||||
UC_CPU_PPC32_MPC8543E_V21,
|
||||
UC_CPU_PPC32_MPC8544_V10,
|
||||
UC_CPU_PPC32_MPC8544_V11,
|
||||
UC_CPU_PPC32_MPC8544E_V10,
|
||||
UC_CPU_PPC32_MPC8544E_V11,
|
||||
UC_CPU_PPC32_MPC8545_V20,
|
||||
UC_CPU_PPC32_MPC8545_V21,
|
||||
UC_CPU_PPC32_MPC8545E_V20,
|
||||
UC_CPU_PPC32_MPC8545E_V21,
|
||||
UC_CPU_PPC32_MPC8547E_V20,
|
||||
UC_CPU_PPC32_MPC8547E_V21,
|
||||
UC_CPU_PPC32_MPC8548_V10,
|
||||
UC_CPU_PPC32_MPC8548_V11,
|
||||
UC_CPU_PPC32_MPC8548_V20,
|
||||
UC_CPU_PPC32_MPC8548_V21,
|
||||
UC_CPU_PPC32_MPC8548E_V10,
|
||||
UC_CPU_PPC32_MPC8548E_V11,
|
||||
UC_CPU_PPC32_MPC8548E_V20,
|
||||
UC_CPU_PPC32_MPC8548E_V21,
|
||||
UC_CPU_PPC32_MPC8555_V10,
|
||||
UC_CPU_PPC32_MPC8555_V11,
|
||||
UC_CPU_PPC32_MPC8555E_V10,
|
||||
UC_CPU_PPC32_MPC8555E_V11,
|
||||
UC_CPU_PPC32_MPC8560_V10,
|
||||
UC_CPU_PPC32_MPC8560_V20,
|
||||
UC_CPU_PPC32_MPC8560_V21,
|
||||
UC_CPU_PPC32_MPC8567,
|
||||
UC_CPU_PPC32_MPC8567E,
|
||||
UC_CPU_PPC32_MPC8568,
|
||||
UC_CPU_PPC32_MPC8568E,
|
||||
UC_CPU_PPC32_MPC8572,
|
||||
UC_CPU_PPC32_MPC8572E,
|
||||
UC_CPU_PPC32_E600,
|
||||
UC_CPU_PPC32_MPC8610,
|
||||
UC_CPU_PPC32_MPC8641,
|
||||
UC_CPU_PPC32_MPC8641D,
|
||||
UC_CPU_PPC32_601_V0,
|
||||
UC_CPU_PPC32_601_V1,
|
||||
UC_CPU_PPC32_601_V2,
|
||||
UC_CPU_PPC32_602,
|
||||
UC_CPU_PPC32_603,
|
||||
UC_CPU_PPC32_603E_V1_1,
|
||||
UC_CPU_PPC32_603E_V1_2,
|
||||
UC_CPU_PPC32_603E_V1_3,
|
||||
UC_CPU_PPC32_603E_V1_4,
|
||||
UC_CPU_PPC32_603E_V2_2,
|
||||
UC_CPU_PPC32_603E_V3,
|
||||
UC_CPU_PPC32_603E_V4,
|
||||
UC_CPU_PPC32_603E_V4_1,
|
||||
UC_CPU_PPC32_603E7,
|
||||
UC_CPU_PPC32_603E7T,
|
||||
UC_CPU_PPC32_603E7V,
|
||||
UC_CPU_PPC32_603E7V1,
|
||||
UC_CPU_PPC32_603E7V2,
|
||||
UC_CPU_PPC32_603P,
|
||||
UC_CPU_PPC32_604,
|
||||
UC_CPU_PPC32_604E_V1_0,
|
||||
UC_CPU_PPC32_604E_V2_2,
|
||||
UC_CPU_PPC32_604E_V2_4,
|
||||
UC_CPU_PPC32_604R,
|
||||
UC_CPU_PPC32_740_V1_0,
|
||||
UC_CPU_PPC32_750_V1_0,
|
||||
UC_CPU_PPC32_740_V2_0,
|
||||
UC_CPU_PPC32_750_V2_0,
|
||||
UC_CPU_PPC32_740_V2_1,
|
||||
UC_CPU_PPC32_750_V2_1,
|
||||
UC_CPU_PPC32_740_V2_2,
|
||||
UC_CPU_PPC32_750_V2_2,
|
||||
UC_CPU_PPC32_740_V3_0,
|
||||
UC_CPU_PPC32_750_V3_0,
|
||||
UC_CPU_PPC32_740_V3_1,
|
||||
UC_CPU_PPC32_750_V3_1,
|
||||
UC_CPU_PPC32_740E,
|
||||
UC_CPU_PPC32_750E,
|
||||
UC_CPU_PPC32_740P,
|
||||
UC_CPU_PPC32_750P,
|
||||
UC_CPU_PPC32_750CL_V1_0,
|
||||
UC_CPU_PPC32_750CL_V2_0,
|
||||
UC_CPU_PPC32_750CX_V1_0,
|
||||
UC_CPU_PPC32_750CX_V2_0,
|
||||
UC_CPU_PPC32_750CX_V2_1,
|
||||
UC_CPU_PPC32_750CX_V2_2,
|
||||
UC_CPU_PPC32_750CXE_V2_1,
|
||||
UC_CPU_PPC32_750CXE_V2_2,
|
||||
UC_CPU_PPC32_750CXE_V2_3,
|
||||
UC_CPU_PPC32_750CXE_V2_4,
|
||||
UC_CPU_PPC32_750CXE_V2_4B,
|
||||
UC_CPU_PPC32_750CXE_V3_0,
|
||||
UC_CPU_PPC32_750CXE_V3_1,
|
||||
UC_CPU_PPC32_750CXE_V3_1B,
|
||||
UC_CPU_PPC32_750CXR,
|
||||
UC_CPU_PPC32_750FL,
|
||||
UC_CPU_PPC32_750FX_V1_0,
|
||||
UC_CPU_PPC32_750FX_V2_0,
|
||||
UC_CPU_PPC32_750FX_V2_1,
|
||||
UC_CPU_PPC32_750FX_V2_2,
|
||||
UC_CPU_PPC32_750FX_V2_3,
|
||||
UC_CPU_PPC32_750GL,
|
||||
UC_CPU_PPC32_750GX_V1_0,
|
||||
UC_CPU_PPC32_750GX_V1_1,
|
||||
UC_CPU_PPC32_750GX_V1_2,
|
||||
UC_CPU_PPC32_750L_V2_0,
|
||||
UC_CPU_PPC32_750L_V2_1,
|
||||
UC_CPU_PPC32_750L_V2_2,
|
||||
UC_CPU_PPC32_750L_V3_0,
|
||||
UC_CPU_PPC32_750L_V3_2,
|
||||
UC_CPU_PPC32_745_V1_0,
|
||||
UC_CPU_PPC32_755_V1_0,
|
||||
UC_CPU_PPC32_745_V1_1,
|
||||
UC_CPU_PPC32_755_V1_1,
|
||||
UC_CPU_PPC32_745_V2_0,
|
||||
UC_CPU_PPC32_755_V2_0,
|
||||
UC_CPU_PPC32_745_V2_1,
|
||||
UC_CPU_PPC32_755_V2_1,
|
||||
UC_CPU_PPC32_745_V2_2,
|
||||
UC_CPU_PPC32_755_V2_2,
|
||||
UC_CPU_PPC32_745_V2_3,
|
||||
UC_CPU_PPC32_755_V2_3,
|
||||
UC_CPU_PPC32_745_V2_4,
|
||||
UC_CPU_PPC32_755_V2_4,
|
||||
UC_CPU_PPC32_745_V2_5,
|
||||
UC_CPU_PPC32_755_V2_5,
|
||||
UC_CPU_PPC32_745_V2_6,
|
||||
UC_CPU_PPC32_755_V2_6,
|
||||
UC_CPU_PPC32_745_V2_7,
|
||||
UC_CPU_PPC32_755_V2_7,
|
||||
UC_CPU_PPC32_745_V2_8,
|
||||
UC_CPU_PPC32_755_V2_8,
|
||||
UC_CPU_PPC32_7400_V1_0,
|
||||
UC_CPU_PPC32_7400_V1_1,
|
||||
UC_CPU_PPC32_7400_V2_0,
|
||||
UC_CPU_PPC32_7400_V2_1,
|
||||
UC_CPU_PPC32_7400_V2_2,
|
||||
UC_CPU_PPC32_7400_V2_6,
|
||||
UC_CPU_PPC32_7400_V2_7,
|
||||
UC_CPU_PPC32_7400_V2_8,
|
||||
UC_CPU_PPC32_7400_V2_9,
|
||||
UC_CPU_PPC32_7410_V1_0,
|
||||
UC_CPU_PPC32_7410_V1_1,
|
||||
UC_CPU_PPC32_7410_V1_2,
|
||||
UC_CPU_PPC32_7410_V1_3,
|
||||
UC_CPU_PPC32_7410_V1_4,
|
||||
UC_CPU_PPC32_7448_V1_0,
|
||||
UC_CPU_PPC32_7448_V1_1,
|
||||
UC_CPU_PPC32_7448_V2_0,
|
||||
UC_CPU_PPC32_7448_V2_1,
|
||||
UC_CPU_PPC32_7450_V1_0,
|
||||
UC_CPU_PPC32_7450_V1_1,
|
||||
UC_CPU_PPC32_7450_V1_2,
|
||||
UC_CPU_PPC32_7450_V2_0,
|
||||
UC_CPU_PPC32_7450_V2_1,
|
||||
UC_CPU_PPC32_7441_V2_1,
|
||||
UC_CPU_PPC32_7441_V2_3,
|
||||
UC_CPU_PPC32_7451_V2_3,
|
||||
UC_CPU_PPC32_7441_V2_10,
|
||||
UC_CPU_PPC32_7451_V2_10,
|
||||
UC_CPU_PPC32_7445_V1_0,
|
||||
UC_CPU_PPC32_7455_V1_0,
|
||||
UC_CPU_PPC32_7445_V2_1,
|
||||
UC_CPU_PPC32_7455_V2_1,
|
||||
UC_CPU_PPC32_7445_V3_2,
|
||||
UC_CPU_PPC32_7455_V3_2,
|
||||
UC_CPU_PPC32_7445_V3_3,
|
||||
UC_CPU_PPC32_7455_V3_3,
|
||||
UC_CPU_PPC32_7445_V3_4,
|
||||
UC_CPU_PPC32_7455_V3_4,
|
||||
UC_CPU_PPC32_7447_V1_0,
|
||||
UC_CPU_PPC32_7457_V1_0,
|
||||
UC_CPU_PPC32_7447_V1_1,
|
||||
UC_CPU_PPC32_7457_V1_1,
|
||||
UC_CPU_PPC32_7457_V1_2,
|
||||
UC_CPU_PPC32_7447A_V1_0,
|
||||
UC_CPU_PPC32_7457A_V1_0,
|
||||
UC_CPU_PPC32_7447A_V1_1,
|
||||
UC_CPU_PPC32_7457A_V1_1,
|
||||
UC_CPU_PPC32_7447A_V1_2,
|
||||
UC_CPU_PPC32_7457A_V1_2,
|
||||
|
||||
UC_CPU_PPC32_ENDING
|
||||
} uc_cpu_ppc;
|
||||
|
||||
//> PPC64 CPU
|
||||
typedef enum uc_cpu_ppc64 {
|
||||
UC_CPU_PPC64_E5500 = 0,
|
||||
UC_CPU_PPC64_E6500,
|
||||
UC_CPU_PPC64_970_V2_2,
|
||||
UC_CPU_PPC64_970FX_V1_0,
|
||||
UC_CPU_PPC64_970FX_V2_0,
|
||||
UC_CPU_PPC64_970FX_V2_1,
|
||||
UC_CPU_PPC64_970FX_V3_0,
|
||||
UC_CPU_PPC64_970FX_V3_1,
|
||||
UC_CPU_PPC64_970MP_V1_0,
|
||||
UC_CPU_PPC64_970MP_V1_1,
|
||||
UC_CPU_PPC64_POWER5_V2_1,
|
||||
UC_CPU_PPC64_POWER7_V2_3,
|
||||
UC_CPU_PPC64_POWER7_V2_1,
|
||||
UC_CPU_PPC64_POWER8E_V2_1,
|
||||
UC_CPU_PPC64_POWER8_V2_0,
|
||||
UC_CPU_PPC64_POWER8NVL_V1_0,
|
||||
UC_CPU_PPC64_POWER9_V1_0,
|
||||
UC_CPU_PPC64_POWER9_V2_0,
|
||||
UC_CPU_PPC64_POWER10_V1_0,
|
||||
|
||||
UC_CPU_PPC64_ENDING
|
||||
} uc_cpu_ppc64;
|
||||
|
||||
//> PPC registers
|
||||
typedef enum uc_ppc_reg {
|
||||
UC_PPC_REG_INVALID = 0,
|
||||
//> General purpose registers
|
||||
UC_PPC_REG_PC,
|
||||
|
||||
UC_PPC_REG_0,
|
||||
UC_PPC_REG_1,
|
||||
UC_PPC_REG_2,
|
||||
UC_PPC_REG_3,
|
||||
UC_PPC_REG_4,
|
||||
UC_PPC_REG_5,
|
||||
UC_PPC_REG_6,
|
||||
UC_PPC_REG_7,
|
||||
UC_PPC_REG_8,
|
||||
UC_PPC_REG_9,
|
||||
UC_PPC_REG_10,
|
||||
UC_PPC_REG_11,
|
||||
UC_PPC_REG_12,
|
||||
UC_PPC_REG_13,
|
||||
UC_PPC_REG_14,
|
||||
UC_PPC_REG_15,
|
||||
UC_PPC_REG_16,
|
||||
UC_PPC_REG_17,
|
||||
UC_PPC_REG_18,
|
||||
UC_PPC_REG_19,
|
||||
UC_PPC_REG_20,
|
||||
UC_PPC_REG_21,
|
||||
UC_PPC_REG_22,
|
||||
UC_PPC_REG_23,
|
||||
UC_PPC_REG_24,
|
||||
UC_PPC_REG_25,
|
||||
UC_PPC_REG_26,
|
||||
UC_PPC_REG_27,
|
||||
UC_PPC_REG_28,
|
||||
UC_PPC_REG_29,
|
||||
UC_PPC_REG_30,
|
||||
UC_PPC_REG_31,
|
||||
|
||||
UC_PPC_REG_CR0,
|
||||
UC_PPC_REG_CR1,
|
||||
UC_PPC_REG_CR2,
|
||||
UC_PPC_REG_CR3,
|
||||
UC_PPC_REG_CR4,
|
||||
UC_PPC_REG_CR5,
|
||||
UC_PPC_REG_CR6,
|
||||
UC_PPC_REG_CR7,
|
||||
|
||||
UC_PPC_REG_FPR0,
|
||||
UC_PPC_REG_FPR1,
|
||||
UC_PPC_REG_FPR2,
|
||||
UC_PPC_REG_FPR3,
|
||||
UC_PPC_REG_FPR4,
|
||||
UC_PPC_REG_FPR5,
|
||||
UC_PPC_REG_FPR6,
|
||||
UC_PPC_REG_FPR7,
|
||||
UC_PPC_REG_FPR8,
|
||||
UC_PPC_REG_FPR9,
|
||||
UC_PPC_REG_FPR10,
|
||||
UC_PPC_REG_FPR11,
|
||||
UC_PPC_REG_FPR12,
|
||||
UC_PPC_REG_FPR13,
|
||||
UC_PPC_REG_FPR14,
|
||||
UC_PPC_REG_FPR15,
|
||||
UC_PPC_REG_FPR16,
|
||||
UC_PPC_REG_FPR17,
|
||||
UC_PPC_REG_FPR18,
|
||||
UC_PPC_REG_FPR19,
|
||||
UC_PPC_REG_FPR20,
|
||||
UC_PPC_REG_FPR21,
|
||||
UC_PPC_REG_FPR22,
|
||||
UC_PPC_REG_FPR23,
|
||||
UC_PPC_REG_FPR24,
|
||||
UC_PPC_REG_FPR25,
|
||||
UC_PPC_REG_FPR26,
|
||||
UC_PPC_REG_FPR27,
|
||||
UC_PPC_REG_FPR28,
|
||||
UC_PPC_REG_FPR29,
|
||||
UC_PPC_REG_FPR30,
|
||||
UC_PPC_REG_FPR31,
|
||||
|
||||
UC_PPC_REG_LR,
|
||||
UC_PPC_REG_XER,
|
||||
UC_PPC_REG_CTR,
|
||||
UC_PPC_REG_MSR,
|
||||
UC_PPC_REG_FPSCR,
|
||||
UC_PPC_REG_CR,
|
||||
|
||||
UC_PPC_REG_ENDING, // <-- mark the end of the list or registers
|
||||
} uc_ppc_reg;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,316 @@
|
||||
/* Unicorn Engine */
|
||||
/* By Nguyen Anh Quynh <aquynh@gmail.com>, 2015-2020 */
|
||||
/* This file is released under LGPL2.
|
||||
See COPYING.LGPL2 in root directory for more details
|
||||
*/
|
||||
|
||||
#ifndef UNICORN_RISCV_H
|
||||
#define UNICORN_RISCV_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4201)
|
||||
#endif
|
||||
|
||||
//> RISCV32 CPU
|
||||
typedef enum uc_cpu_riscv32 {
|
||||
UC_CPU_RISCV32_ANY = 0,
|
||||
UC_CPU_RISCV32_BASE32,
|
||||
UC_CPU_RISCV32_SIFIVE_E31,
|
||||
UC_CPU_RISCV32_SIFIVE_U34,
|
||||
|
||||
UC_CPU_RISCV32_ENDING
|
||||
} uc_cpu_riscv32;
|
||||
|
||||
//> RISCV64 CPU
|
||||
typedef enum uc_cpu_riscv64 {
|
||||
UC_CPU_RISCV64_ANY = 0,
|
||||
UC_CPU_RISCV64_BASE64,
|
||||
UC_CPU_RISCV64_SIFIVE_E51,
|
||||
UC_CPU_RISCV64_SIFIVE_U54,
|
||||
|
||||
UC_CPU_RISCV64_ENDING
|
||||
} uc_cpu_riscv64;
|
||||
|
||||
//> RISCV registers
|
||||
typedef enum uc_riscv_reg {
|
||||
UC_RISCV_REG_INVALID = 0,
|
||||
//> General purpose registers
|
||||
UC_RISCV_REG_X0,
|
||||
UC_RISCV_REG_X1,
|
||||
UC_RISCV_REG_X2,
|
||||
UC_RISCV_REG_X3,
|
||||
UC_RISCV_REG_X4,
|
||||
UC_RISCV_REG_X5,
|
||||
UC_RISCV_REG_X6,
|
||||
UC_RISCV_REG_X7,
|
||||
UC_RISCV_REG_X8,
|
||||
UC_RISCV_REG_X9,
|
||||
UC_RISCV_REG_X10,
|
||||
UC_RISCV_REG_X11,
|
||||
UC_RISCV_REG_X12,
|
||||
UC_RISCV_REG_X13,
|
||||
UC_RISCV_REG_X14,
|
||||
UC_RISCV_REG_X15,
|
||||
UC_RISCV_REG_X16,
|
||||
UC_RISCV_REG_X17,
|
||||
UC_RISCV_REG_X18,
|
||||
UC_RISCV_REG_X19,
|
||||
UC_RISCV_REG_X20,
|
||||
UC_RISCV_REG_X21,
|
||||
UC_RISCV_REG_X22,
|
||||
UC_RISCV_REG_X23,
|
||||
UC_RISCV_REG_X24,
|
||||
UC_RISCV_REG_X25,
|
||||
UC_RISCV_REG_X26,
|
||||
UC_RISCV_REG_X27,
|
||||
UC_RISCV_REG_X28,
|
||||
UC_RISCV_REG_X29,
|
||||
UC_RISCV_REG_X30,
|
||||
UC_RISCV_REG_X31,
|
||||
|
||||
//> RISCV CSR
|
||||
UC_RISCV_REG_USTATUS,
|
||||
UC_RISCV_REG_UIE,
|
||||
UC_RISCV_REG_UTVEC,
|
||||
UC_RISCV_REG_USCRATCH,
|
||||
UC_RISCV_REG_UEPC,
|
||||
UC_RISCV_REG_UCAUSE,
|
||||
UC_RISCV_REG_UTVAL,
|
||||
UC_RISCV_REG_UIP,
|
||||
UC_RISCV_REG_FFLAGS,
|
||||
UC_RISCV_REG_FRM,
|
||||
UC_RISCV_REG_FCSR,
|
||||
UC_RISCV_REG_CYCLE,
|
||||
UC_RISCV_REG_TIME,
|
||||
UC_RISCV_REG_INSTRET,
|
||||
UC_RISCV_REG_HPMCOUNTER3,
|
||||
UC_RISCV_REG_HPMCOUNTER4,
|
||||
UC_RISCV_REG_HPMCOUNTER5,
|
||||
UC_RISCV_REG_HPMCOUNTER6,
|
||||
UC_RISCV_REG_HPMCOUNTER7,
|
||||
UC_RISCV_REG_HPMCOUNTER8,
|
||||
UC_RISCV_REG_HPMCOUNTER9,
|
||||
UC_RISCV_REG_HPMCOUNTER10,
|
||||
UC_RISCV_REG_HPMCOUNTER11,
|
||||
UC_RISCV_REG_HPMCOUNTER12,
|
||||
UC_RISCV_REG_HPMCOUNTER13,
|
||||
UC_RISCV_REG_HPMCOUNTER14,
|
||||
UC_RISCV_REG_HPMCOUNTER15,
|
||||
UC_RISCV_REG_HPMCOUNTER16,
|
||||
UC_RISCV_REG_HPMCOUNTER17,
|
||||
UC_RISCV_REG_HPMCOUNTER18,
|
||||
UC_RISCV_REG_HPMCOUNTER19,
|
||||
UC_RISCV_REG_HPMCOUNTER20,
|
||||
UC_RISCV_REG_HPMCOUNTER21,
|
||||
UC_RISCV_REG_HPMCOUNTER22,
|
||||
UC_RISCV_REG_HPMCOUNTER23,
|
||||
UC_RISCV_REG_HPMCOUNTER24,
|
||||
UC_RISCV_REG_HPMCOUNTER25,
|
||||
UC_RISCV_REG_HPMCOUNTER26,
|
||||
UC_RISCV_REG_HPMCOUNTER27,
|
||||
UC_RISCV_REG_HPMCOUNTER28,
|
||||
UC_RISCV_REG_HPMCOUNTER29,
|
||||
UC_RISCV_REG_HPMCOUNTER30,
|
||||
UC_RISCV_REG_HPMCOUNTER31,
|
||||
UC_RISCV_REG_CYCLEH,
|
||||
UC_RISCV_REG_TIMEH,
|
||||
UC_RISCV_REG_INSTRETH,
|
||||
UC_RISCV_REG_HPMCOUNTER3H,
|
||||
UC_RISCV_REG_HPMCOUNTER4H,
|
||||
UC_RISCV_REG_HPMCOUNTER5H,
|
||||
UC_RISCV_REG_HPMCOUNTER6H,
|
||||
UC_RISCV_REG_HPMCOUNTER7H,
|
||||
UC_RISCV_REG_HPMCOUNTER8H,
|
||||
UC_RISCV_REG_HPMCOUNTER9H,
|
||||
UC_RISCV_REG_HPMCOUNTER10H,
|
||||
UC_RISCV_REG_HPMCOUNTER11H,
|
||||
UC_RISCV_REG_HPMCOUNTER12H,
|
||||
UC_RISCV_REG_HPMCOUNTER13H,
|
||||
UC_RISCV_REG_HPMCOUNTER14H,
|
||||
UC_RISCV_REG_HPMCOUNTER15H,
|
||||
UC_RISCV_REG_HPMCOUNTER16H,
|
||||
UC_RISCV_REG_HPMCOUNTER17H,
|
||||
UC_RISCV_REG_HPMCOUNTER18H,
|
||||
UC_RISCV_REG_HPMCOUNTER19H,
|
||||
UC_RISCV_REG_HPMCOUNTER20H,
|
||||
UC_RISCV_REG_HPMCOUNTER21H,
|
||||
UC_RISCV_REG_HPMCOUNTER22H,
|
||||
UC_RISCV_REG_HPMCOUNTER23H,
|
||||
UC_RISCV_REG_HPMCOUNTER24H,
|
||||
UC_RISCV_REG_HPMCOUNTER25H,
|
||||
UC_RISCV_REG_HPMCOUNTER26H,
|
||||
UC_RISCV_REG_HPMCOUNTER27H,
|
||||
UC_RISCV_REG_HPMCOUNTER28H,
|
||||
UC_RISCV_REG_HPMCOUNTER29H,
|
||||
UC_RISCV_REG_HPMCOUNTER30H,
|
||||
UC_RISCV_REG_HPMCOUNTER31H,
|
||||
UC_RISCV_REG_MCYCLE,
|
||||
UC_RISCV_REG_MINSTRET,
|
||||
UC_RISCV_REG_MCYCLEH,
|
||||
UC_RISCV_REG_MINSTRETH,
|
||||
UC_RISCV_REG_MVENDORID,
|
||||
UC_RISCV_REG_MARCHID,
|
||||
UC_RISCV_REG_MIMPID,
|
||||
UC_RISCV_REG_MHARTID,
|
||||
UC_RISCV_REG_MSTATUS,
|
||||
UC_RISCV_REG_MISA,
|
||||
UC_RISCV_REG_MEDELEG,
|
||||
UC_RISCV_REG_MIDELEG,
|
||||
UC_RISCV_REG_MIE,
|
||||
UC_RISCV_REG_MTVEC,
|
||||
UC_RISCV_REG_MCOUNTEREN,
|
||||
UC_RISCV_REG_MSTATUSH,
|
||||
UC_RISCV_REG_MUCOUNTEREN,
|
||||
UC_RISCV_REG_MSCOUNTEREN,
|
||||
UC_RISCV_REG_MHCOUNTEREN,
|
||||
UC_RISCV_REG_MSCRATCH,
|
||||
UC_RISCV_REG_MEPC,
|
||||
UC_RISCV_REG_MCAUSE,
|
||||
UC_RISCV_REG_MTVAL,
|
||||
UC_RISCV_REG_MIP,
|
||||
UC_RISCV_REG_MBADADDR,
|
||||
UC_RISCV_REG_SSTATUS,
|
||||
UC_RISCV_REG_SEDELEG,
|
||||
UC_RISCV_REG_SIDELEG,
|
||||
UC_RISCV_REG_SIE,
|
||||
UC_RISCV_REG_STVEC,
|
||||
UC_RISCV_REG_SCOUNTEREN,
|
||||
UC_RISCV_REG_SSCRATCH,
|
||||
UC_RISCV_REG_SEPC,
|
||||
UC_RISCV_REG_SCAUSE,
|
||||
UC_RISCV_REG_STVAL,
|
||||
UC_RISCV_REG_SIP,
|
||||
UC_RISCV_REG_SBADADDR,
|
||||
UC_RISCV_REG_SPTBR,
|
||||
UC_RISCV_REG_SATP,
|
||||
UC_RISCV_REG_HSTATUS,
|
||||
UC_RISCV_REG_HEDELEG,
|
||||
UC_RISCV_REG_HIDELEG,
|
||||
UC_RISCV_REG_HIE,
|
||||
UC_RISCV_REG_HCOUNTEREN,
|
||||
UC_RISCV_REG_HTVAL,
|
||||
UC_RISCV_REG_HIP,
|
||||
UC_RISCV_REG_HTINST,
|
||||
UC_RISCV_REG_HGATP,
|
||||
UC_RISCV_REG_HTIMEDELTA,
|
||||
UC_RISCV_REG_HTIMEDELTAH,
|
||||
|
||||
//> Floating-point registers
|
||||
UC_RISCV_REG_F0, // "ft0"
|
||||
UC_RISCV_REG_F1, // "ft1"
|
||||
UC_RISCV_REG_F2, // "ft2"
|
||||
UC_RISCV_REG_F3, // "ft3"
|
||||
UC_RISCV_REG_F4, // "ft4"
|
||||
UC_RISCV_REG_F5, // "ft5"
|
||||
UC_RISCV_REG_F6, // "ft6"
|
||||
UC_RISCV_REG_F7, // "ft7"
|
||||
UC_RISCV_REG_F8, // "fs0"
|
||||
UC_RISCV_REG_F9, // "fs1"
|
||||
UC_RISCV_REG_F10, // "fa0"
|
||||
UC_RISCV_REG_F11, // "fa1"
|
||||
UC_RISCV_REG_F12, // "fa2"
|
||||
UC_RISCV_REG_F13, // "fa3"
|
||||
UC_RISCV_REG_F14, // "fa4"
|
||||
UC_RISCV_REG_F15, // "fa5"
|
||||
UC_RISCV_REG_F16, // "fa6"
|
||||
UC_RISCV_REG_F17, // "fa7"
|
||||
UC_RISCV_REG_F18, // "fs2"
|
||||
UC_RISCV_REG_F19, // "fs3"
|
||||
UC_RISCV_REG_F20, // "fs4"
|
||||
UC_RISCV_REG_F21, // "fs5"
|
||||
UC_RISCV_REG_F22, // "fs6"
|
||||
UC_RISCV_REG_F23, // "fs7"
|
||||
UC_RISCV_REG_F24, // "fs8"
|
||||
UC_RISCV_REG_F25, // "fs9"
|
||||
UC_RISCV_REG_F26, // "fs10"
|
||||
UC_RISCV_REG_F27, // "fs11"
|
||||
UC_RISCV_REG_F28, // "ft8"
|
||||
UC_RISCV_REG_F29, // "ft9"
|
||||
UC_RISCV_REG_F30, // "ft10"
|
||||
UC_RISCV_REG_F31, // "ft11"
|
||||
|
||||
UC_RISCV_REG_PC, // PC register
|
||||
|
||||
UC_RISCV_REG_PRIV, // Virtual register for the current privilege level
|
||||
|
||||
UC_RISCV_REG_ENDING, // <-- mark the end of the list or registers
|
||||
|
||||
//> Alias registers
|
||||
UC_RISCV_REG_ZERO = UC_RISCV_REG_X0, // "zero"
|
||||
UC_RISCV_REG_RA = UC_RISCV_REG_X1, // "ra"
|
||||
UC_RISCV_REG_SP = UC_RISCV_REG_X2, // "sp"
|
||||
UC_RISCV_REG_GP = UC_RISCV_REG_X3, // "gp"
|
||||
UC_RISCV_REG_TP = UC_RISCV_REG_X4, // "tp"
|
||||
UC_RISCV_REG_T0 = UC_RISCV_REG_X5, // "t0"
|
||||
UC_RISCV_REG_T1 = UC_RISCV_REG_X6, // "t1"
|
||||
UC_RISCV_REG_T2 = UC_RISCV_REG_X7, // "t2"
|
||||
UC_RISCV_REG_S0 = UC_RISCV_REG_X8, // "s0"
|
||||
UC_RISCV_REG_FP = UC_RISCV_REG_X8, // "fp"
|
||||
UC_RISCV_REG_S1 = UC_RISCV_REG_X9, // "s1"
|
||||
UC_RISCV_REG_A0 = UC_RISCV_REG_X10, // "a0"
|
||||
UC_RISCV_REG_A1 = UC_RISCV_REG_X11, // "a1"
|
||||
UC_RISCV_REG_A2 = UC_RISCV_REG_X12, // "a2"
|
||||
UC_RISCV_REG_A3 = UC_RISCV_REG_X13, // "a3"
|
||||
UC_RISCV_REG_A4 = UC_RISCV_REG_X14, // "a4"
|
||||
UC_RISCV_REG_A5 = UC_RISCV_REG_X15, // "a5"
|
||||
UC_RISCV_REG_A6 = UC_RISCV_REG_X16, // "a6"
|
||||
UC_RISCV_REG_A7 = UC_RISCV_REG_X17, // "a7"
|
||||
UC_RISCV_REG_S2 = UC_RISCV_REG_X18, // "s2"
|
||||
UC_RISCV_REG_S3 = UC_RISCV_REG_X19, // "s3"
|
||||
UC_RISCV_REG_S4 = UC_RISCV_REG_X20, // "s4"
|
||||
UC_RISCV_REG_S5 = UC_RISCV_REG_X21, // "s5"
|
||||
UC_RISCV_REG_S6 = UC_RISCV_REG_X22, // "s6"
|
||||
UC_RISCV_REG_S7 = UC_RISCV_REG_X23, // "s7"
|
||||
UC_RISCV_REG_S8 = UC_RISCV_REG_X24, // "s8"
|
||||
UC_RISCV_REG_S9 = UC_RISCV_REG_X25, // "s9"
|
||||
UC_RISCV_REG_S10 = UC_RISCV_REG_X26, // "s10"
|
||||
UC_RISCV_REG_S11 = UC_RISCV_REG_X27, // "s11"
|
||||
UC_RISCV_REG_T3 = UC_RISCV_REG_X28, // "t3"
|
||||
UC_RISCV_REG_T4 = UC_RISCV_REG_X29, // "t4"
|
||||
UC_RISCV_REG_T5 = UC_RISCV_REG_X30, // "t5"
|
||||
UC_RISCV_REG_T6 = UC_RISCV_REG_X31, // "t6"
|
||||
|
||||
UC_RISCV_REG_FT0 = UC_RISCV_REG_F0, // "ft0"
|
||||
UC_RISCV_REG_FT1 = UC_RISCV_REG_F1, // "ft1"
|
||||
UC_RISCV_REG_FT2 = UC_RISCV_REG_F2, // "ft2"
|
||||
UC_RISCV_REG_FT3 = UC_RISCV_REG_F3, // "ft3"
|
||||
UC_RISCV_REG_FT4 = UC_RISCV_REG_F4, // "ft4"
|
||||
UC_RISCV_REG_FT5 = UC_RISCV_REG_F5, // "ft5"
|
||||
UC_RISCV_REG_FT6 = UC_RISCV_REG_F6, // "ft6"
|
||||
UC_RISCV_REG_FT7 = UC_RISCV_REG_F7, // "ft7"
|
||||
UC_RISCV_REG_FS0 = UC_RISCV_REG_F8, // "fs0"
|
||||
UC_RISCV_REG_FS1 = UC_RISCV_REG_F9, // "fs1"
|
||||
|
||||
UC_RISCV_REG_FA0 = UC_RISCV_REG_F10, // "fa0"
|
||||
UC_RISCV_REG_FA1 = UC_RISCV_REG_F11, // "fa1"
|
||||
UC_RISCV_REG_FA2 = UC_RISCV_REG_F12, // "fa2"
|
||||
UC_RISCV_REG_FA3 = UC_RISCV_REG_F13, // "fa3"
|
||||
UC_RISCV_REG_FA4 = UC_RISCV_REG_F14, // "fa4"
|
||||
UC_RISCV_REG_FA5 = UC_RISCV_REG_F15, // "fa5"
|
||||
UC_RISCV_REG_FA6 = UC_RISCV_REG_F16, // "fa6"
|
||||
UC_RISCV_REG_FA7 = UC_RISCV_REG_F17, // "fa7"
|
||||
UC_RISCV_REG_FS2 = UC_RISCV_REG_F18, // "fs2"
|
||||
UC_RISCV_REG_FS3 = UC_RISCV_REG_F19, // "fs3"
|
||||
UC_RISCV_REG_FS4 = UC_RISCV_REG_F20, // "fs4"
|
||||
UC_RISCV_REG_FS5 = UC_RISCV_REG_F21, // "fs5"
|
||||
UC_RISCV_REG_FS6 = UC_RISCV_REG_F22, // "fs6"
|
||||
UC_RISCV_REG_FS7 = UC_RISCV_REG_F23, // "fs7"
|
||||
UC_RISCV_REG_FS8 = UC_RISCV_REG_F24, // "fs8"
|
||||
UC_RISCV_REG_FS9 = UC_RISCV_REG_F25, // "fs9"
|
||||
UC_RISCV_REG_FS10 = UC_RISCV_REG_F26, // "fs10"
|
||||
UC_RISCV_REG_FS11 = UC_RISCV_REG_F27, // "fs11"
|
||||
UC_RISCV_REG_FT8 = UC_RISCV_REG_F28, // "ft8"
|
||||
UC_RISCV_REG_FT9 = UC_RISCV_REG_F29, // "ft9"
|
||||
UC_RISCV_REG_FT10 = UC_RISCV_REG_F30, // "ft10"
|
||||
UC_RISCV_REG_FT11 = UC_RISCV_REG_F31, // "ft11"
|
||||
} uc_riscv_reg;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,200 @@
|
||||
/* Unicorn Engine */
|
||||
/* By Nguyen Anh Quynh <aquynh@gmail.com>, 2015-2021 */
|
||||
|
||||
#ifndef UNICORN_S390X_H
|
||||
#define UNICORN_S390X_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4201)
|
||||
#endif
|
||||
|
||||
//> S390X CPU
|
||||
typedef enum uc_cpu_s390x {
|
||||
UC_CPU_S390X_Z900 = 0,
|
||||
UC_CPU_S390X_Z900_2,
|
||||
UC_CPU_S390X_Z900_3,
|
||||
UC_CPU_S390X_Z800,
|
||||
UC_CPU_S390X_Z990,
|
||||
UC_CPU_S390X_Z990_2,
|
||||
UC_CPU_S390X_Z990_3,
|
||||
UC_CPU_S390X_Z890,
|
||||
UC_CPU_S390X_Z990_4,
|
||||
UC_CPU_S390X_Z890_2,
|
||||
UC_CPU_S390X_Z990_5,
|
||||
UC_CPU_S390X_Z890_3,
|
||||
UC_CPU_S390X_Z9EC,
|
||||
UC_CPU_S390X_Z9EC_2,
|
||||
UC_CPU_S390X_Z9BC,
|
||||
UC_CPU_S390X_Z9EC_3,
|
||||
UC_CPU_S390X_Z9BC_2,
|
||||
UC_CPU_S390X_Z10EC,
|
||||
UC_CPU_S390X_Z10EC_2,
|
||||
UC_CPU_S390X_Z10BC,
|
||||
UC_CPU_S390X_Z10EC_3,
|
||||
UC_CPU_S390X_Z10BC_2,
|
||||
UC_CPU_S390X_Z196,
|
||||
UC_CPU_S390X_Z196_2,
|
||||
UC_CPU_S390X_Z114,
|
||||
UC_CPU_S390X_ZEC12,
|
||||
UC_CPU_S390X_ZEC12_2,
|
||||
UC_CPU_S390X_ZBC12,
|
||||
UC_CPU_S390X_Z13,
|
||||
UC_CPU_S390X_Z13_2,
|
||||
UC_CPU_S390X_Z13S,
|
||||
UC_CPU_S390X_Z14,
|
||||
UC_CPU_S390X_Z14_2,
|
||||
UC_CPU_S390X_Z14ZR1,
|
||||
UC_CPU_S390X_GEN15A,
|
||||
UC_CPU_S390X_GEN15B,
|
||||
UC_CPU_S390X_QEMU,
|
||||
UC_CPU_S390X_MAX,
|
||||
|
||||
UC_CPU_S390X_ENDING
|
||||
} uc_cpu_s390x;
|
||||
|
||||
//> S390X registers
|
||||
typedef enum uc_s390x_reg {
|
||||
UC_S390X_REG_INVALID = 0,
|
||||
//> General purpose registers
|
||||
UC_S390X_REG_R0,
|
||||
UC_S390X_REG_R1,
|
||||
UC_S390X_REG_R2,
|
||||
UC_S390X_REG_R3,
|
||||
UC_S390X_REG_R4,
|
||||
UC_S390X_REG_R5,
|
||||
UC_S390X_REG_R6,
|
||||
UC_S390X_REG_R7,
|
||||
UC_S390X_REG_R8,
|
||||
UC_S390X_REG_R9,
|
||||
UC_S390X_REG_R10,
|
||||
UC_S390X_REG_R11,
|
||||
UC_S390X_REG_R12,
|
||||
UC_S390X_REG_R13,
|
||||
UC_S390X_REG_R14,
|
||||
UC_S390X_REG_R15,
|
||||
|
||||
//> Floating point registers
|
||||
UC_S390X_REG_F0,
|
||||
UC_S390X_REG_F1,
|
||||
UC_S390X_REG_F2,
|
||||
UC_S390X_REG_F3,
|
||||
UC_S390X_REG_F4,
|
||||
UC_S390X_REG_F5,
|
||||
UC_S390X_REG_F6,
|
||||
UC_S390X_REG_F7,
|
||||
UC_S390X_REG_F8,
|
||||
UC_S390X_REG_F9,
|
||||
UC_S390X_REG_F10,
|
||||
UC_S390X_REG_F11,
|
||||
UC_S390X_REG_F12,
|
||||
UC_S390X_REG_F13,
|
||||
UC_S390X_REG_F14,
|
||||
UC_S390X_REG_F15,
|
||||
//> Not real registers, low half of vr16-vr31
|
||||
UC_S390X_REG_F16,
|
||||
UC_S390X_REG_F17,
|
||||
UC_S390X_REG_F18,
|
||||
UC_S390X_REG_F19,
|
||||
UC_S390X_REG_F20,
|
||||
UC_S390X_REG_F21,
|
||||
UC_S390X_REG_F22,
|
||||
UC_S390X_REG_F23,
|
||||
UC_S390X_REG_F24,
|
||||
UC_S390X_REG_F25,
|
||||
UC_S390X_REG_F26,
|
||||
UC_S390X_REG_F27,
|
||||
UC_S390X_REG_F28,
|
||||
UC_S390X_REG_F29,
|
||||
UC_S390X_REG_F30,
|
||||
UC_S390X_REG_F31,
|
||||
|
||||
//> Access registers
|
||||
UC_S390X_REG_A0,
|
||||
UC_S390X_REG_A1,
|
||||
UC_S390X_REG_A2,
|
||||
UC_S390X_REG_A3,
|
||||
UC_S390X_REG_A4,
|
||||
UC_S390X_REG_A5,
|
||||
UC_S390X_REG_A6,
|
||||
UC_S390X_REG_A7,
|
||||
UC_S390X_REG_A8,
|
||||
UC_S390X_REG_A9,
|
||||
UC_S390X_REG_A10,
|
||||
UC_S390X_REG_A11,
|
||||
UC_S390X_REG_A12,
|
||||
UC_S390X_REG_A13,
|
||||
UC_S390X_REG_A14,
|
||||
UC_S390X_REG_A15,
|
||||
|
||||
UC_S390X_REG_PC, // PC register
|
||||
UC_S390X_REG_PSWM,
|
||||
|
||||
//> pseudo registers, high half of vr16-vr31
|
||||
UC_S390X_REG_F0_HI,
|
||||
UC_S390X_REG_F1_HI,
|
||||
UC_S390X_REG_F2_HI,
|
||||
UC_S390X_REG_F3_HI,
|
||||
UC_S390X_REG_F4_HI,
|
||||
UC_S390X_REG_F5_HI,
|
||||
UC_S390X_REG_F6_HI,
|
||||
UC_S390X_REG_F7_HI,
|
||||
UC_S390X_REG_F8_HI,
|
||||
UC_S390X_REG_F9_HI,
|
||||
UC_S390X_REG_F10_HI,
|
||||
UC_S390X_REG_F11_HI,
|
||||
UC_S390X_REG_F12_HI,
|
||||
UC_S390X_REG_F13_HI,
|
||||
UC_S390X_REG_F14_HI,
|
||||
UC_S390X_REG_F15_HI,
|
||||
UC_S390X_REG_F16_HI,
|
||||
UC_S390X_REG_F17_HI,
|
||||
UC_S390X_REG_F18_HI,
|
||||
UC_S390X_REG_F19_HI,
|
||||
UC_S390X_REG_F20_HI,
|
||||
UC_S390X_REG_F21_HI,
|
||||
UC_S390X_REG_F22_HI,
|
||||
UC_S390X_REG_F23_HI,
|
||||
UC_S390X_REG_F24_HI,
|
||||
UC_S390X_REG_F25_HI,
|
||||
UC_S390X_REG_F26_HI,
|
||||
UC_S390X_REG_F27_HI,
|
||||
UC_S390X_REG_F28_HI,
|
||||
UC_S390X_REG_F29_HI,
|
||||
UC_S390X_REG_F30_HI,
|
||||
UC_S390X_REG_F31_HI,
|
||||
|
||||
//> float control register
|
||||
UC_S390X_REG_FPC,
|
||||
|
||||
//> control registers
|
||||
UC_S390X_REG_CR0,
|
||||
UC_S390X_REG_CR1,
|
||||
UC_S390X_REG_CR2,
|
||||
UC_S390X_REG_CR3,
|
||||
UC_S390X_REG_CR4,
|
||||
UC_S390X_REG_CR5,
|
||||
UC_S390X_REG_CR6,
|
||||
UC_S390X_REG_CR7,
|
||||
UC_S390X_REG_CR8,
|
||||
UC_S390X_REG_CR9,
|
||||
UC_S390X_REG_CR10,
|
||||
UC_S390X_REG_CR11,
|
||||
UC_S390X_REG_CR12,
|
||||
UC_S390X_REG_CR13,
|
||||
UC_S390X_REG_CR14,
|
||||
UC_S390X_REG_CR15,
|
||||
|
||||
UC_S390X_REG_ENDING, // <-- mark the end of the list or registers
|
||||
|
||||
//> Alias registers
|
||||
} uc_s390x_reg;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,173 @@
|
||||
/* Unicorn Emulator Engine */
|
||||
/* By Nguyen Anh Quynh <aquynh@gmail.com>, 2014-2017 */
|
||||
/* This file is released under LGPL2.
|
||||
See COPYING.LGPL2 in root directory for more details
|
||||
*/
|
||||
|
||||
#ifndef UNICORN_SPARC_H
|
||||
#define UNICORN_SPARC_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// GCC SPARC toolchain has a default macro called "sparc" which breaks
|
||||
// compilation
|
||||
#undef sparc
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4201)
|
||||
#endif
|
||||
|
||||
//> SPARC32 CPU
|
||||
typedef enum uc_cpu_sparc32 {
|
||||
UC_CPU_SPARC32_FUJITSU_MB86904 = 0,
|
||||
UC_CPU_SPARC32_FUJITSU_MB86907,
|
||||
UC_CPU_SPARC32_TI_MICROSPARC_I,
|
||||
UC_CPU_SPARC32_TI_MICROSPARC_II,
|
||||
UC_CPU_SPARC32_TI_MICROSPARC_IIEP,
|
||||
UC_CPU_SPARC32_TI_SUPERSPARC_40,
|
||||
UC_CPU_SPARC32_TI_SUPERSPARC_50,
|
||||
UC_CPU_SPARC32_TI_SUPERSPARC_51,
|
||||
UC_CPU_SPARC32_TI_SUPERSPARC_60,
|
||||
UC_CPU_SPARC32_TI_SUPERSPARC_61,
|
||||
UC_CPU_SPARC32_TI_SUPERSPARC_II,
|
||||
UC_CPU_SPARC32_LEON2,
|
||||
UC_CPU_SPARC32_LEON3,
|
||||
|
||||
UC_CPU_SPARC32_ENDING
|
||||
} uc_cpu_sparc32;
|
||||
|
||||
//> SPARC64 CPU
|
||||
typedef enum uc_cpu_sparc64 {
|
||||
UC_CPU_SPARC64_FUJITSU = 0,
|
||||
UC_CPU_SPARC64_FUJITSU_III,
|
||||
UC_CPU_SPARC64_FUJITSU_IV,
|
||||
UC_CPU_SPARC64_FUJITSU_V,
|
||||
UC_CPU_SPARC64_TI_ULTRASPARC_I,
|
||||
UC_CPU_SPARC64_TI_ULTRASPARC_II,
|
||||
UC_CPU_SPARC64_TI_ULTRASPARC_III,
|
||||
UC_CPU_SPARC64_TI_ULTRASPARC_IIE,
|
||||
UC_CPU_SPARC64_SUN_ULTRASPARC_III,
|
||||
UC_CPU_SPARC64_SUN_ULTRASPARC_III_CU,
|
||||
UC_CPU_SPARC64_SUN_ULTRASPARC_IIII,
|
||||
UC_CPU_SPARC64_SUN_ULTRASPARC_IV,
|
||||
UC_CPU_SPARC64_SUN_ULTRASPARC_IV_PLUS,
|
||||
UC_CPU_SPARC64_SUN_ULTRASPARC_IIII_PLUS,
|
||||
UC_CPU_SPARC64_SUN_ULTRASPARC_T1,
|
||||
UC_CPU_SPARC64_SUN_ULTRASPARC_T2,
|
||||
UC_CPU_SPARC64_NEC_ULTRASPARC_I,
|
||||
|
||||
UC_CPU_SPARC64_ENDING
|
||||
} uc_cpu_sparc64;
|
||||
|
||||
//> SPARC registers
|
||||
typedef enum uc_sparc_reg {
|
||||
UC_SPARC_REG_INVALID = 0,
|
||||
|
||||
UC_SPARC_REG_F0,
|
||||
UC_SPARC_REG_F1,
|
||||
UC_SPARC_REG_F2,
|
||||
UC_SPARC_REG_F3,
|
||||
UC_SPARC_REG_F4,
|
||||
UC_SPARC_REG_F5,
|
||||
UC_SPARC_REG_F6,
|
||||
UC_SPARC_REG_F7,
|
||||
UC_SPARC_REG_F8,
|
||||
UC_SPARC_REG_F9,
|
||||
UC_SPARC_REG_F10,
|
||||
UC_SPARC_REG_F11,
|
||||
UC_SPARC_REG_F12,
|
||||
UC_SPARC_REG_F13,
|
||||
UC_SPARC_REG_F14,
|
||||
UC_SPARC_REG_F15,
|
||||
UC_SPARC_REG_F16,
|
||||
UC_SPARC_REG_F17,
|
||||
UC_SPARC_REG_F18,
|
||||
UC_SPARC_REG_F19,
|
||||
UC_SPARC_REG_F20,
|
||||
UC_SPARC_REG_F21,
|
||||
UC_SPARC_REG_F22,
|
||||
UC_SPARC_REG_F23,
|
||||
UC_SPARC_REG_F24,
|
||||
UC_SPARC_REG_F25,
|
||||
UC_SPARC_REG_F26,
|
||||
UC_SPARC_REG_F27,
|
||||
UC_SPARC_REG_F28,
|
||||
UC_SPARC_REG_F29,
|
||||
UC_SPARC_REG_F30,
|
||||
UC_SPARC_REG_F31,
|
||||
UC_SPARC_REG_F32,
|
||||
UC_SPARC_REG_F34,
|
||||
UC_SPARC_REG_F36,
|
||||
UC_SPARC_REG_F38,
|
||||
UC_SPARC_REG_F40,
|
||||
UC_SPARC_REG_F42,
|
||||
UC_SPARC_REG_F44,
|
||||
UC_SPARC_REG_F46,
|
||||
UC_SPARC_REG_F48,
|
||||
UC_SPARC_REG_F50,
|
||||
UC_SPARC_REG_F52,
|
||||
UC_SPARC_REG_F54,
|
||||
UC_SPARC_REG_F56,
|
||||
UC_SPARC_REG_F58,
|
||||
UC_SPARC_REG_F60,
|
||||
UC_SPARC_REG_F62,
|
||||
UC_SPARC_REG_FCC0, // Floating condition codes
|
||||
UC_SPARC_REG_FCC1,
|
||||
UC_SPARC_REG_FCC2,
|
||||
UC_SPARC_REG_FCC3,
|
||||
UC_SPARC_REG_G0,
|
||||
UC_SPARC_REG_G1,
|
||||
UC_SPARC_REG_G2,
|
||||
UC_SPARC_REG_G3,
|
||||
UC_SPARC_REG_G4,
|
||||
UC_SPARC_REG_G5,
|
||||
UC_SPARC_REG_G6,
|
||||
UC_SPARC_REG_G7,
|
||||
UC_SPARC_REG_I0,
|
||||
UC_SPARC_REG_I1,
|
||||
UC_SPARC_REG_I2,
|
||||
UC_SPARC_REG_I3,
|
||||
UC_SPARC_REG_I4,
|
||||
UC_SPARC_REG_I5,
|
||||
UC_SPARC_REG_FP,
|
||||
UC_SPARC_REG_I7,
|
||||
UC_SPARC_REG_ICC, // Integer condition codes
|
||||
UC_SPARC_REG_L0,
|
||||
UC_SPARC_REG_L1,
|
||||
UC_SPARC_REG_L2,
|
||||
UC_SPARC_REG_L3,
|
||||
UC_SPARC_REG_L4,
|
||||
UC_SPARC_REG_L5,
|
||||
UC_SPARC_REG_L6,
|
||||
UC_SPARC_REG_L7,
|
||||
UC_SPARC_REG_O0,
|
||||
UC_SPARC_REG_O1,
|
||||
UC_SPARC_REG_O2,
|
||||
UC_SPARC_REG_O3,
|
||||
UC_SPARC_REG_O4,
|
||||
UC_SPARC_REG_O5,
|
||||
UC_SPARC_REG_SP,
|
||||
UC_SPARC_REG_O7,
|
||||
UC_SPARC_REG_Y,
|
||||
|
||||
// special register
|
||||
UC_SPARC_REG_XCC,
|
||||
|
||||
// pseudo register
|
||||
UC_SPARC_REG_PC, // program counter register
|
||||
UC_SPARC_REG_PSR,
|
||||
|
||||
UC_SPARC_REG_ENDING, // <-- mark the end of the list of registers
|
||||
|
||||
// extras
|
||||
UC_SPARC_REG_O6 = UC_SPARC_REG_SP,
|
||||
UC_SPARC_REG_I6 = UC_SPARC_REG_FP,
|
||||
} uc_sparc_reg;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,174 @@
|
||||
/* This file is released under LGPL2.
|
||||
See COPYING.LGPL2 in root directory for more details
|
||||
*/
|
||||
|
||||
/*
|
||||
Created for Unicorn Engine by Eric Poole <eric.poole@aptiv.com>, 2022
|
||||
Copyright 2022 Aptiv
|
||||
*/
|
||||
|
||||
#ifndef UNICORN_TRICORE_H
|
||||
#define UNICORN_TRICORE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4201)
|
||||
#endif
|
||||
|
||||
//> TRICORE CPU
|
||||
typedef enum uc_cpu_tricore {
|
||||
UC_CPU_TRICORE_TC1796,
|
||||
UC_CPU_TRICORE_TC1797,
|
||||
UC_CPU_TRICORE_TC27X,
|
||||
|
||||
UC_CPU_TRICORE_ENDING
|
||||
} uc_cpu_tricore;
|
||||
|
||||
//> TRICORE registers
|
||||
typedef enum uc_tricore_reg {
|
||||
UC_TRICORE_REG_INVALID = 0,
|
||||
|
||||
// General purpose registers (GPR)
|
||||
// Address GPR
|
||||
UC_TRICORE_REG_A0,
|
||||
UC_TRICORE_REG_A1,
|
||||
UC_TRICORE_REG_A2,
|
||||
UC_TRICORE_REG_A3,
|
||||
UC_TRICORE_REG_A4,
|
||||
UC_TRICORE_REG_A5,
|
||||
UC_TRICORE_REG_A6,
|
||||
UC_TRICORE_REG_A7,
|
||||
UC_TRICORE_REG_A8,
|
||||
UC_TRICORE_REG_A9,
|
||||
UC_TRICORE_REG_A10,
|
||||
UC_TRICORE_REG_A11,
|
||||
UC_TRICORE_REG_A12,
|
||||
UC_TRICORE_REG_A13,
|
||||
UC_TRICORE_REG_A14,
|
||||
UC_TRICORE_REG_A15,
|
||||
// Data GPR
|
||||
UC_TRICORE_REG_D0,
|
||||
UC_TRICORE_REG_D1,
|
||||
UC_TRICORE_REG_D2,
|
||||
UC_TRICORE_REG_D3,
|
||||
UC_TRICORE_REG_D4,
|
||||
UC_TRICORE_REG_D5,
|
||||
UC_TRICORE_REG_D6,
|
||||
UC_TRICORE_REG_D7,
|
||||
UC_TRICORE_REG_D8,
|
||||
UC_TRICORE_REG_D9,
|
||||
UC_TRICORE_REG_D10,
|
||||
UC_TRICORE_REG_D11,
|
||||
UC_TRICORE_REG_D12,
|
||||
UC_TRICORE_REG_D13,
|
||||
UC_TRICORE_REG_D14,
|
||||
UC_TRICORE_REG_D15,
|
||||
|
||||
/* CSFR Register */
|
||||
UC_TRICORE_REG_PCXI,
|
||||
|
||||
UC_TRICORE_REG_PSW,
|
||||
|
||||
/* PSW flag cache for faster execution */
|
||||
UC_TRICORE_REG_PSW_USB_C,
|
||||
UC_TRICORE_REG_PSW_USB_V,
|
||||
UC_TRICORE_REG_PSW_USB_SV,
|
||||
UC_TRICORE_REG_PSW_USB_AV,
|
||||
UC_TRICORE_REG_PSW_USB_SAV,
|
||||
|
||||
UC_TRICORE_REG_PC,
|
||||
UC_TRICORE_REG_SYSCON,
|
||||
UC_TRICORE_REG_CPU_ID,
|
||||
UC_TRICORE_REG_BIV,
|
||||
UC_TRICORE_REG_BTV,
|
||||
UC_TRICORE_REG_ISP,
|
||||
UC_TRICORE_REG_ICR,
|
||||
UC_TRICORE_REG_FCX,
|
||||
UC_TRICORE_REG_LCX,
|
||||
UC_TRICORE_REG_COMPAT,
|
||||
|
||||
UC_TRICORE_REG_DPR0_U,
|
||||
UC_TRICORE_REG_DPR1_U,
|
||||
UC_TRICORE_REG_DPR2_U,
|
||||
UC_TRICORE_REG_DPR3_U,
|
||||
UC_TRICORE_REG_DPR0_L,
|
||||
UC_TRICORE_REG_DPR1_L,
|
||||
UC_TRICORE_REG_DPR2_L,
|
||||
UC_TRICORE_REG_DPR3_L,
|
||||
|
||||
UC_TRICORE_REG_CPR0_U,
|
||||
UC_TRICORE_REG_CPR1_U,
|
||||
UC_TRICORE_REG_CPR2_U,
|
||||
UC_TRICORE_REG_CPR3_U,
|
||||
UC_TRICORE_REG_CPR0_L,
|
||||
UC_TRICORE_REG_CPR1_L,
|
||||
UC_TRICORE_REG_CPR2_L,
|
||||
UC_TRICORE_REG_CPR3_L,
|
||||
|
||||
UC_TRICORE_REG_DPM0,
|
||||
UC_TRICORE_REG_DPM1,
|
||||
UC_TRICORE_REG_DPM2,
|
||||
UC_TRICORE_REG_DPM3,
|
||||
|
||||
UC_TRICORE_REG_CPM0,
|
||||
UC_TRICORE_REG_CPM1,
|
||||
UC_TRICORE_REG_CPM2,
|
||||
UC_TRICORE_REG_CPM3,
|
||||
|
||||
/* Memory Management Registers */
|
||||
UC_TRICORE_REG_MMU_CON,
|
||||
UC_TRICORE_REG_MMU_ASI,
|
||||
UC_TRICORE_REG_MMU_TVA,
|
||||
UC_TRICORE_REG_MMU_TPA,
|
||||
UC_TRICORE_REG_MMU_TPX,
|
||||
UC_TRICORE_REG_MMU_TFA,
|
||||
|
||||
// 1.3.1 Only
|
||||
UC_TRICORE_REG_BMACON,
|
||||
UC_TRICORE_REG_SMACON,
|
||||
UC_TRICORE_REG_DIEAR,
|
||||
UC_TRICORE_REG_DIETR,
|
||||
UC_TRICORE_REG_CCDIER,
|
||||
UC_TRICORE_REG_MIECON,
|
||||
UC_TRICORE_REG_PIEAR,
|
||||
UC_TRICORE_REG_PIETR,
|
||||
UC_TRICORE_REG_CCPIER,
|
||||
|
||||
/* Debug Registers */
|
||||
UC_TRICORE_REG_DBGSR,
|
||||
UC_TRICORE_REG_EXEVT,
|
||||
UC_TRICORE_REG_CREVT,
|
||||
UC_TRICORE_REG_SWEVT,
|
||||
UC_TRICORE_REG_TR0EVT,
|
||||
UC_TRICORE_REG_TR1EVT,
|
||||
UC_TRICORE_REG_DMS,
|
||||
UC_TRICORE_REG_DCX,
|
||||
UC_TRICORE_REG_DBGTCR,
|
||||
UC_TRICORE_REG_CCTRL,
|
||||
UC_TRICORE_REG_CCNT,
|
||||
UC_TRICORE_REG_ICNT,
|
||||
UC_TRICORE_REG_M1CNT,
|
||||
UC_TRICORE_REG_M2CNT,
|
||||
UC_TRICORE_REG_M3CNT,
|
||||
|
||||
UC_TRICORE_REG_ENDING, // <-- mark the end of the list of registers
|
||||
|
||||
// alias registers
|
||||
UC_TRICORE_REG_GA0 = UC_TRICORE_REG_A0,
|
||||
UC_TRICORE_REG_GA1 = UC_TRICORE_REG_A1,
|
||||
UC_TRICORE_REG_GA8 = UC_TRICORE_REG_A8,
|
||||
UC_TRICORE_REG_GA9 = UC_TRICORE_REG_A9,
|
||||
UC_TRICORE_REG_SP = UC_TRICORE_REG_A10,
|
||||
UC_TRICORE_REG_LR = UC_TRICORE_REG_A11,
|
||||
UC_TRICORE_REG_IA = UC_TRICORE_REG_A15,
|
||||
UC_TRICORE_REG_ID = UC_TRICORE_REG_D15,
|
||||
} uc_tricore_reg;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user