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>
502 lines
22 KiB
C++
502 lines
22 KiB
C++
// JNI tracing for the native ARM32 investigation (see
|
|
// ARM64_TRANSLATION_LAYER.md's "Native ARM32 tracing harness" plan).
|
|
//
|
|
// JNI calls go through the JNIEnv->functions function-pointer table, not
|
|
// the dynamic symbol table - LD_PRELOAD interposition (libc_gles_trace.cpp)
|
|
// can't see them. Real Android ART shares ONE JNINativeInterface table
|
|
// across every thread's JNIEnv (confirmed by design, not assumed - this is
|
|
// standard JVM/ART behavior), so this patches the table'S CONTENTS in
|
|
// place (not swapping which struct `functions` points to) - covers every
|
|
// thread automatically, including whichever one actually drives the game's
|
|
// real onDrawFrame/RunLoop-tick GL thread, not just whichever thread
|
|
// happens to call the installer below.
|
|
//
|
|
// 2026-09-06: widened from the original curated dozen to a much broader
|
|
// call-flow set (method/field access, object lifecycle, exceptions, local
|
|
// frames) - the point of a "full" trace is reconstructing execution order
|
|
// on real hardware, and the original subset was too narrow for that. Still
|
|
// deliberately skips a few genuinely high-frequency, low-information calls
|
|
// (DeleteLocalRef, array element accessors) - see their own comments below.
|
|
#include <jni.h>
|
|
#include <cstring>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
#include "trace_log.h"
|
|
|
|
using trace_agent::LookupName;
|
|
using trace_agent::RememberName;
|
|
using trace_agent::TraceLog;
|
|
|
|
namespace {
|
|
|
|
JNINativeInterface g_orig{}; // real function pointers, saved before patching, so wrappers can call through
|
|
|
|
jclass Wrap_FindClass(JNIEnv* env, const char* name) {
|
|
jclass r = g_orig.FindClass(env, name);
|
|
TraceLog("JNI FindClass(\"%s\") -> %p%s", name ? name : "(null)", (void*)r, r == nullptr ? " [FAILED]" : "");
|
|
return r;
|
|
}
|
|
|
|
// Diagnostic for the ARM64_TRANSLATION_LAYER.md "external-storage overlay
|
|
// at VFS root" investigation - is GameActivityMain.useAssetsFileSystem()
|
|
// even called on real native ARM32, and what does it actually return?
|
|
// The Java-side implementation is this project's own (native32 shares
|
|
// GameActivityMain.kt with the translated/emulated flavor), so a
|
|
// same-vs-different result here isn't about different source code - it's
|
|
// about whether the same code produces a different real answer natively.
|
|
jmethodID g_useAssetsFileSystemId = nullptr;
|
|
|
|
jmethodID Wrap_GetMethodID(JNIEnv* env, jclass clazz, const char* name, const char* sig) {
|
|
jmethodID r = g_orig.GetMethodID(env, clazz, name, sig);
|
|
TraceLog("JNI GetMethodID(\"%s\", \"%s\") -> %p%s", name ? name : "(null)", sig ? sig : "(null)",
|
|
(void*)r, r == nullptr ? " [FAILED]" : "");
|
|
if (r) RememberName(r, name, sig);
|
|
if (name && r && strcmp(name, "useAssetsFileSystem") == 0) {
|
|
g_useAssetsFileSystemId = r;
|
|
TraceLog("JNI GetMethodID: cached useAssetsFileSystem jmethodID=%p for CallBooleanMethod tracing", (void*)r);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
jboolean Wrap_CallBooleanMethod(JNIEnv* env, jobject obj, jmethodID id, ...) {
|
|
va_list ap;
|
|
va_start(ap, id);
|
|
jboolean r = g_orig.CallBooleanMethodV(env, obj, id, ap);
|
|
va_end(ap);
|
|
TraceLog("JNI CallBooleanMethod: %s(obj=%p) -> %s", LookupName(id).c_str(), (void*)obj, r ? "true" : "false");
|
|
return r;
|
|
}
|
|
|
|
jboolean Wrap_CallBooleanMethodV(JNIEnv* env, jobject obj, jmethodID id, va_list args) {
|
|
// va_list can only be consumed once - copy before forwarding so the
|
|
// real call still gets a fresh, valid list.
|
|
va_list copy;
|
|
va_copy(copy, args);
|
|
jboolean r = g_orig.CallBooleanMethodV(env, obj, id, copy);
|
|
va_end(copy);
|
|
TraceLog("JNI CallBooleanMethodV: %s(obj=%p) -> %s", LookupName(id).c_str(), (void*)obj, r ? "true" : "false");
|
|
return r;
|
|
}
|
|
|
|
jboolean Wrap_CallBooleanMethodA(JNIEnv* env, jobject obj, jmethodID id, const jvalue* args) {
|
|
jboolean r = g_orig.CallBooleanMethodA(env, obj, id, args);
|
|
TraceLog("JNI CallBooleanMethodA: %s(obj=%p) -> %s", LookupName(id).c_str(), (void*)obj, r ? "true" : "false");
|
|
return r;
|
|
}
|
|
|
|
jmethodID Wrap_GetStaticMethodID(JNIEnv* env, jclass clazz, const char* name, const char* sig) {
|
|
jmethodID r = g_orig.GetStaticMethodID(env, clazz, name, sig);
|
|
TraceLog("JNI GetStaticMethodID(\"%s\", \"%s\") -> %p%s", name ? name : "(null)", sig ? sig : "(null)",
|
|
(void*)r, r == nullptr ? " [FAILED]" : "");
|
|
if (r) RememberName(r, name, sig);
|
|
return r;
|
|
}
|
|
|
|
jfieldID Wrap_GetFieldID(JNIEnv* env, jclass clazz, const char* name, const char* sig) {
|
|
jfieldID r = g_orig.GetFieldID(env, clazz, name, sig);
|
|
TraceLog("JNI GetFieldID(\"%s\", \"%s\") -> %p%s", name ? name : "(null)", sig ? sig : "(null)",
|
|
(void*)r, r == nullptr ? " [FAILED]" : "");
|
|
if (r) RememberName(r, name, sig);
|
|
return r;
|
|
}
|
|
|
|
jfieldID Wrap_GetStaticFieldID(JNIEnv* env, jclass clazz, const char* name, const char* sig) {
|
|
jfieldID r = g_orig.GetStaticFieldID(env, clazz, name, sig);
|
|
TraceLog("JNI GetStaticFieldID(\"%s\", \"%s\") -> %p%s", name ? name : "(null)", sig ? sig : "(null)",
|
|
(void*)r, r == nullptr ? " [FAILED]" : "");
|
|
if (r) RememberName(r, name, sig);
|
|
return r;
|
|
}
|
|
|
|
jint Wrap_RegisterNatives(JNIEnv* env, jclass clazz, const JNINativeMethod* methods, jint n) {
|
|
jint r = g_orig.RegisterNatives(env, clazz, methods, n);
|
|
TraceLog("JNI RegisterNatives(count=%d) -> %d%s", n, r, r != 0 ? " [FAILED]" : "");
|
|
for (jint i = 0; i < n; i++) {
|
|
TraceLog("JNI RegisterNatives: %s %s", methods[i].name, methods[i].signature);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
jstring Wrap_NewStringUTF(JNIEnv* env, const char* bytes) {
|
|
jstring r = g_orig.NewStringUTF(env, bytes);
|
|
TraceLog("JNI NewStringUTF(\"%s\") -> %p", bytes ? bytes : "(null)", (void*)r);
|
|
return r;
|
|
}
|
|
|
|
const char* Wrap_GetStringUTFChars(JNIEnv* env, jstring str, jboolean* isCopy) {
|
|
const char* r = g_orig.GetStringUTFChars(env, str, isCopy);
|
|
TraceLog("JNI GetStringUTFChars() -> \"%s\"", r ? r : "(null)");
|
|
return r;
|
|
}
|
|
|
|
jboolean Wrap_ExceptionCheck(JNIEnv* env) {
|
|
jboolean r = g_orig.ExceptionCheck(env);
|
|
if (r) TraceLog("JNI ExceptionCheck() -> TRUE");
|
|
return r;
|
|
}
|
|
|
|
void Wrap_ExceptionDescribe(JNIEnv* env) {
|
|
TraceLog("JNI ExceptionDescribe() called");
|
|
g_orig.ExceptionDescribe(env);
|
|
}
|
|
|
|
// ---- object lifecycle / class checks ----
|
|
|
|
jclass Wrap_GetObjectClass(JNIEnv* env, jobject obj) {
|
|
jclass r = g_orig.GetObjectClass(env, obj);
|
|
TraceLog("JNI GetObjectClass(obj=%p) -> %p", (void*)obj, (void*)r);
|
|
return r;
|
|
}
|
|
|
|
jboolean Wrap_IsInstanceOf(JNIEnv* env, jobject obj, jclass clazz) {
|
|
jboolean r = g_orig.IsInstanceOf(env, obj, clazz);
|
|
TraceLog("JNI IsInstanceOf(obj=%p, class=%p) -> %s", (void*)obj, (void*)clazz, r ? "true" : "false");
|
|
return r;
|
|
}
|
|
|
|
jboolean Wrap_IsSameObject(JNIEnv* env, jobject a, jobject b) {
|
|
jboolean r = g_orig.IsSameObject(env, a, b);
|
|
TraceLog("JNI IsSameObject(%p, %p) -> %s", (void*)a, (void*)b, r ? "true" : "false");
|
|
return r;
|
|
}
|
|
|
|
jobject Wrap_NewGlobalRef(JNIEnv* env, jobject obj) {
|
|
jobject r = g_orig.NewGlobalRef(env, obj);
|
|
TraceLog("JNI NewGlobalRef(%p) -> %p", (void*)obj, (void*)r);
|
|
return r;
|
|
}
|
|
|
|
void Wrap_DeleteGlobalRef(JNIEnv* env, jobject obj) {
|
|
TraceLog("JNI DeleteGlobalRef(%p)", (void*)obj);
|
|
g_orig.DeleteGlobalRef(env, obj);
|
|
}
|
|
|
|
// DeleteLocalRef is deliberately NOT wrapped - by far the highest-frequency
|
|
// JNI call in any real run (every local ref a native function creates gets
|
|
// cleaned up through it), and it carries no name/signature information of
|
|
// its own. Logging it would reintroduce the exact volume problem
|
|
// libc_gles_trace.cpp's IsInteresting() was added to avoid, for a line that
|
|
// never says which method/field it's tied to.
|
|
|
|
jint Wrap_PushLocalFrame(JNIEnv* env, jint cap) {
|
|
TraceLog("JNI PushLocalFrame(%d)", (int)cap);
|
|
return g_orig.PushLocalFrame(env, cap);
|
|
}
|
|
|
|
jobject Wrap_PopLocalFrame(JNIEnv* env, jobject result) {
|
|
TraceLog("JNI PopLocalFrame()");
|
|
return g_orig.PopLocalFrame(env, result);
|
|
}
|
|
|
|
// ---- exceptions ----
|
|
|
|
jint Wrap_Throw(JNIEnv* env, jthrowable obj) {
|
|
TraceLog("JNI Throw(%p)", (void*)obj);
|
|
return g_orig.Throw(env, obj);
|
|
}
|
|
|
|
jint Wrap_ThrowNew(JNIEnv* env, jclass clazz, const char* msg) {
|
|
TraceLog("JNI ThrowNew(class=%p, \"%s\")", (void*)clazz, msg ? msg : "(null)");
|
|
return g_orig.ThrowNew(env, clazz, msg);
|
|
}
|
|
|
|
jthrowable Wrap_ExceptionOccurred(JNIEnv* env) {
|
|
jthrowable r = g_orig.ExceptionOccurred(env);
|
|
if (r) TraceLog("JNI ExceptionOccurred() -> %p", (void*)r);
|
|
return r;
|
|
}
|
|
|
|
void Wrap_ExceptionClear(JNIEnv* env) {
|
|
TraceLog("JNI ExceptionClear()");
|
|
g_orig.ExceptionClear(env);
|
|
}
|
|
|
|
// ---- object construction / arrays ----
|
|
|
|
jobject Wrap_NewObject(JNIEnv* env, jclass clazz, jmethodID id, ...) {
|
|
va_list ap;
|
|
va_start(ap, id);
|
|
jobject r = g_orig.NewObjectV(env, clazz, id, ap);
|
|
va_end(ap);
|
|
TraceLog("JNI NewObject: %s -> %p", LookupName(id).c_str(), (void*)r);
|
|
return r;
|
|
}
|
|
|
|
jobject Wrap_NewObjectV(JNIEnv* env, jclass clazz, jmethodID id, va_list args) {
|
|
va_list copy;
|
|
va_copy(copy, args);
|
|
jobject r = g_orig.NewObjectV(env, clazz, id, copy);
|
|
va_end(copy);
|
|
TraceLog("JNI NewObjectV: %s -> %p", LookupName(id).c_str(), (void*)r);
|
|
return r;
|
|
}
|
|
|
|
jsize Wrap_GetArrayLength(JNIEnv* env, jarray arr) {
|
|
jsize r = g_orig.GetArrayLength(env, arr);
|
|
TraceLog("JNI GetArrayLength(%p) -> %d", (void*)arr, (int)r);
|
|
return r;
|
|
}
|
|
|
|
jobjectArray Wrap_NewObjectArray(JNIEnv* env, jsize len, jclass clazz, jobject init) {
|
|
jobjectArray r = g_orig.NewObjectArray(env, len, clazz, init);
|
|
TraceLog("JNI NewObjectArray(len=%d, class=%p) -> %p", (int)len, (void*)clazz, (void*)r);
|
|
return r;
|
|
}
|
|
|
|
// ---- instance Call*Method / Call*MethodV families (mechanical - same
|
|
// shape as jni_shim.cpp's own DoCall/DoCallV on the emulated side) ----
|
|
|
|
#define DEF_CALL_METHOD(RetType, Name) \
|
|
RetType Wrap_Call##Name##Method(JNIEnv* env, jobject obj, jmethodID id, ...) { \
|
|
va_list ap; \
|
|
va_start(ap, id); \
|
|
RetType r = g_orig.Call##Name##MethodV(env, obj, id, ap); \
|
|
va_end(ap); \
|
|
TraceLog("JNI Call" #Name "Method: %s(obj=%p)", LookupName(id).c_str(), (void*)obj); \
|
|
return r; \
|
|
} \
|
|
RetType Wrap_Call##Name##MethodV(JNIEnv* env, jobject obj, jmethodID id, va_list args) { \
|
|
va_list copy; \
|
|
va_copy(copy, args); \
|
|
RetType r = g_orig.Call##Name##MethodV(env, obj, id, copy); \
|
|
va_end(copy); \
|
|
TraceLog("JNI Call" #Name "MethodV: %s(obj=%p)", LookupName(id).c_str(), (void*)obj); \
|
|
return r; \
|
|
}
|
|
|
|
// void has no return value to capture, so it can't share DEF_CALL_METHOD's
|
|
// RetType-returning shape.
|
|
void Wrap_CallVoidMethod(JNIEnv* env, jobject obj, jmethodID id, ...) {
|
|
va_list ap;
|
|
va_start(ap, id);
|
|
g_orig.CallVoidMethodV(env, obj, id, ap);
|
|
va_end(ap);
|
|
TraceLog("JNI CallVoidMethod: %s(obj=%p)", LookupName(id).c_str(), (void*)obj);
|
|
}
|
|
void Wrap_CallVoidMethodV(JNIEnv* env, jobject obj, jmethodID id, va_list args) {
|
|
va_list copy;
|
|
va_copy(copy, args);
|
|
g_orig.CallVoidMethodV(env, obj, id, copy);
|
|
va_end(copy);
|
|
TraceLog("JNI CallVoidMethodV: %s(obj=%p)", LookupName(id).c_str(), (void*)obj);
|
|
}
|
|
|
|
DEF_CALL_METHOD(jobject, Object)
|
|
DEF_CALL_METHOD(jint, Int)
|
|
DEF_CALL_METHOD(jlong, Long)
|
|
DEF_CALL_METHOD(jfloat, Float)
|
|
DEF_CALL_METHOD(jdouble, Double)
|
|
|
|
#undef DEF_CALL_METHOD
|
|
|
|
// ---- static Call*Method / Call*MethodV families ----
|
|
|
|
#define DEF_CALL_STATIC_METHOD(RetType, Name) \
|
|
RetType Wrap_CallStatic##Name##Method(JNIEnv* env, jclass clazz, jmethodID id, ...) { \
|
|
va_list ap; \
|
|
va_start(ap, id); \
|
|
RetType r = g_orig.CallStatic##Name##MethodV(env, clazz, id, ap); \
|
|
va_end(ap); \
|
|
TraceLog("JNI CallStatic" #Name "Method: %s", LookupName(id).c_str()); \
|
|
return r; \
|
|
} \
|
|
RetType Wrap_CallStatic##Name##MethodV(JNIEnv* env, jclass clazz, jmethodID id, va_list args) { \
|
|
va_list copy; \
|
|
va_copy(copy, args); \
|
|
RetType r = g_orig.CallStatic##Name##MethodV(env, clazz, id, copy); \
|
|
va_end(copy); \
|
|
TraceLog("JNI CallStatic" #Name "MethodV: %s", LookupName(id).c_str()); \
|
|
return r; \
|
|
}
|
|
|
|
void Wrap_CallStaticVoidMethod(JNIEnv* env, jclass clazz, jmethodID id, ...) {
|
|
va_list ap;
|
|
va_start(ap, id);
|
|
g_orig.CallStaticVoidMethodV(env, clazz, id, ap);
|
|
va_end(ap);
|
|
TraceLog("JNI CallStaticVoidMethod: %s", LookupName(id).c_str());
|
|
}
|
|
void Wrap_CallStaticVoidMethodV(JNIEnv* env, jclass clazz, jmethodID id, va_list args) {
|
|
va_list copy;
|
|
va_copy(copy, args);
|
|
g_orig.CallStaticVoidMethodV(env, clazz, id, copy);
|
|
va_end(copy);
|
|
TraceLog("JNI CallStaticVoidMethodV: %s", LookupName(id).c_str());
|
|
}
|
|
|
|
DEF_CALL_STATIC_METHOD(jobject, Object)
|
|
DEF_CALL_STATIC_METHOD(jboolean, Boolean)
|
|
DEF_CALL_STATIC_METHOD(jint, Int)
|
|
DEF_CALL_STATIC_METHOD(jlong, Long)
|
|
DEF_CALL_STATIC_METHOD(jfloat, Float)
|
|
DEF_CALL_STATIC_METHOD(jdouble, Double)
|
|
|
|
#undef DEF_CALL_STATIC_METHOD
|
|
|
|
// ---- instance Get*Field / Set*Field families ----
|
|
|
|
#define DEF_GET_FIELD(RetType, Name) \
|
|
RetType Wrap_Get##Name##Field(JNIEnv* env, jobject obj, jfieldID id) { \
|
|
RetType r = g_orig.Get##Name##Field(env, obj, id); \
|
|
TraceLog("JNI Get" #Name "Field: %s(obj=%p)", LookupName(id).c_str(), (void*)obj); \
|
|
return r; \
|
|
}
|
|
#define DEF_SET_FIELD(ValType, Name) \
|
|
void Wrap_Set##Name##Field(JNIEnv* env, jobject obj, jfieldID id, ValType val) { \
|
|
TraceLog("JNI Set" #Name "Field: %s(obj=%p)", LookupName(id).c_str(), (void*)obj); \
|
|
g_orig.Set##Name##Field(env, obj, id, val); \
|
|
}
|
|
|
|
DEF_GET_FIELD(jobject, Object)
|
|
DEF_GET_FIELD(jboolean, Boolean)
|
|
DEF_GET_FIELD(jint, Int)
|
|
DEF_GET_FIELD(jlong, Long)
|
|
DEF_GET_FIELD(jfloat, Float)
|
|
DEF_GET_FIELD(jdouble, Double)
|
|
|
|
DEF_SET_FIELD(jobject, Object)
|
|
DEF_SET_FIELD(jboolean, Boolean)
|
|
DEF_SET_FIELD(jint, Int)
|
|
DEF_SET_FIELD(jlong, Long)
|
|
DEF_SET_FIELD(jfloat, Float)
|
|
DEF_SET_FIELD(jdouble, Double)
|
|
|
|
#undef DEF_GET_FIELD
|
|
#undef DEF_SET_FIELD
|
|
|
|
// ---- static Get*Field / Set*Field families ----
|
|
|
|
#define DEF_GET_STATIC_FIELD(RetType, Name) \
|
|
RetType Wrap_GetStatic##Name##Field(JNIEnv* env, jclass clazz, jfieldID id) { \
|
|
RetType r = g_orig.GetStatic##Name##Field(env, clazz, id); \
|
|
TraceLog("JNI GetStatic" #Name "Field: %s", LookupName(id).c_str()); \
|
|
return r; \
|
|
}
|
|
#define DEF_SET_STATIC_FIELD(ValType, Name) \
|
|
void Wrap_SetStatic##Name##Field(JNIEnv* env, jclass clazz, jfieldID id, ValType val) { \
|
|
TraceLog("JNI SetStatic" #Name "Field: %s", LookupName(id).c_str()); \
|
|
g_orig.SetStatic##Name##Field(env, clazz, id, val); \
|
|
}
|
|
|
|
DEF_GET_STATIC_FIELD(jobject, Object)
|
|
DEF_GET_STATIC_FIELD(jboolean, Boolean)
|
|
DEF_GET_STATIC_FIELD(jint, Int)
|
|
|
|
DEF_SET_STATIC_FIELD(jobject, Object)
|
|
DEF_SET_STATIC_FIELD(jboolean, Boolean)
|
|
DEF_SET_STATIC_FIELD(jint, Int)
|
|
|
|
#undef DEF_GET_STATIC_FIELD
|
|
#undef DEF_SET_STATIC_FIELD
|
|
|
|
} // namespace
|
|
|
|
// Kotlin side: nfs.mod.traceagent.TraceAgentBridge.install(outputPath) - a
|
|
// tiny new class added purely for this investigation (see the plan's Phase
|
|
// 3 file list), called from a new minimal Application.attachBaseContext, as
|
|
// early as possible, before any real game library gets a chance to grab its
|
|
// own JNIEnv reference. outputPath is the app's own private files dir (see
|
|
// TraceApplication.kt) - trace_log.h opens it for the whole process's life.
|
|
extern "C" JNIEXPORT void JNICALL
|
|
Java_nfs_mod_traceagent_TraceAgentBridge_install(JNIEnv* env, jclass, jstring outputPath) {
|
|
if (outputPath) {
|
|
const char* path = env->GetStringUTFChars(outputPath, nullptr);
|
|
trace_agent::InitFileLog(path);
|
|
env->ReleaseStringUTFChars(outputPath, path);
|
|
}
|
|
|
|
auto* table = const_cast<JNINativeInterface*>(env->functions);
|
|
g_orig = *table;
|
|
|
|
long pageSize = sysconf(_SC_PAGESIZE);
|
|
uintptr_t base = (uintptr_t)table & ~(uintptr_t)(pageSize - 1);
|
|
size_t span = ((uintptr_t)table + sizeof(*table)) - base;
|
|
if (mprotect((void*)base, span, PROT_READ | PROT_WRITE) != 0) {
|
|
TraceLog("InstallJniTraceHooks: mprotect failed - JNI tracing disabled, libc/GLES tracing (if LD_PRELOAD'd) still active");
|
|
return;
|
|
}
|
|
|
|
table->FindClass = Wrap_FindClass;
|
|
table->GetMethodID = Wrap_GetMethodID;
|
|
table->CallBooleanMethod = Wrap_CallBooleanMethod;
|
|
table->CallBooleanMethodV = Wrap_CallBooleanMethodV;
|
|
table->CallBooleanMethodA = Wrap_CallBooleanMethodA;
|
|
table->GetStaticMethodID = Wrap_GetStaticMethodID;
|
|
table->GetFieldID = Wrap_GetFieldID;
|
|
table->GetStaticFieldID = Wrap_GetStaticFieldID;
|
|
table->RegisterNatives = Wrap_RegisterNatives;
|
|
table->NewStringUTF = Wrap_NewStringUTF;
|
|
table->GetStringUTFChars = Wrap_GetStringUTFChars;
|
|
table->ExceptionCheck = Wrap_ExceptionCheck;
|
|
table->ExceptionDescribe = Wrap_ExceptionDescribe;
|
|
|
|
table->GetObjectClass = Wrap_GetObjectClass;
|
|
table->IsInstanceOf = Wrap_IsInstanceOf;
|
|
table->IsSameObject = Wrap_IsSameObject;
|
|
table->NewGlobalRef = Wrap_NewGlobalRef;
|
|
table->DeleteGlobalRef = Wrap_DeleteGlobalRef;
|
|
table->PushLocalFrame = Wrap_PushLocalFrame;
|
|
table->PopLocalFrame = Wrap_PopLocalFrame;
|
|
|
|
table->Throw = Wrap_Throw;
|
|
table->ThrowNew = Wrap_ThrowNew;
|
|
table->ExceptionOccurred = Wrap_ExceptionOccurred;
|
|
table->ExceptionClear = Wrap_ExceptionClear;
|
|
|
|
table->NewObject = Wrap_NewObject;
|
|
table->NewObjectV = Wrap_NewObjectV;
|
|
table->GetArrayLength = Wrap_GetArrayLength;
|
|
table->NewObjectArray = Wrap_NewObjectArray;
|
|
|
|
table->CallVoidMethod = Wrap_CallVoidMethod;
|
|
table->CallVoidMethodV = Wrap_CallVoidMethodV;
|
|
table->CallObjectMethod = Wrap_CallObjectMethod;
|
|
table->CallObjectMethodV = Wrap_CallObjectMethodV;
|
|
table->CallIntMethod = Wrap_CallIntMethod;
|
|
table->CallIntMethodV = Wrap_CallIntMethodV;
|
|
table->CallLongMethod = Wrap_CallLongMethod;
|
|
table->CallLongMethodV = Wrap_CallLongMethodV;
|
|
table->CallFloatMethod = Wrap_CallFloatMethod;
|
|
table->CallFloatMethodV = Wrap_CallFloatMethodV;
|
|
table->CallDoubleMethod = Wrap_CallDoubleMethod;
|
|
table->CallDoubleMethodV = Wrap_CallDoubleMethodV;
|
|
|
|
table->CallStaticVoidMethod = Wrap_CallStaticVoidMethod;
|
|
table->CallStaticVoidMethodV = Wrap_CallStaticVoidMethodV;
|
|
table->CallStaticObjectMethod = Wrap_CallStaticObjectMethod;
|
|
table->CallStaticObjectMethodV = Wrap_CallStaticObjectMethodV;
|
|
table->CallStaticBooleanMethod = Wrap_CallStaticBooleanMethod;
|
|
table->CallStaticBooleanMethodV = Wrap_CallStaticBooleanMethodV;
|
|
table->CallStaticIntMethod = Wrap_CallStaticIntMethod;
|
|
table->CallStaticIntMethodV = Wrap_CallStaticIntMethodV;
|
|
table->CallStaticLongMethod = Wrap_CallStaticLongMethod;
|
|
table->CallStaticLongMethodV = Wrap_CallStaticLongMethodV;
|
|
table->CallStaticFloatMethod = Wrap_CallStaticFloatMethod;
|
|
table->CallStaticFloatMethodV = Wrap_CallStaticFloatMethodV;
|
|
table->CallStaticDoubleMethod = Wrap_CallStaticDoubleMethod;
|
|
table->CallStaticDoubleMethodV = Wrap_CallStaticDoubleMethodV;
|
|
|
|
table->GetObjectField = Wrap_GetObjectField;
|
|
table->GetBooleanField = Wrap_GetBooleanField;
|
|
table->GetIntField = Wrap_GetIntField;
|
|
table->GetLongField = Wrap_GetLongField;
|
|
table->GetFloatField = Wrap_GetFloatField;
|
|
table->GetDoubleField = Wrap_GetDoubleField;
|
|
table->SetObjectField = Wrap_SetObjectField;
|
|
table->SetBooleanField = Wrap_SetBooleanField;
|
|
table->SetIntField = Wrap_SetIntField;
|
|
table->SetLongField = Wrap_SetLongField;
|
|
table->SetFloatField = Wrap_SetFloatField;
|
|
table->SetDoubleField = Wrap_SetDoubleField;
|
|
|
|
table->GetStaticObjectField = Wrap_GetStaticObjectField;
|
|
table->GetStaticBooleanField = Wrap_GetStaticBooleanField;
|
|
table->GetStaticIntField = Wrap_GetStaticIntField;
|
|
table->SetStaticObjectField = Wrap_SetStaticObjectField;
|
|
table->SetStaticBooleanField = Wrap_SetStaticBooleanField;
|
|
table->SetStaticIntField = Wrap_SetStaticIntField;
|
|
|
|
mprotect((void*)base, span, PROT_READ); // restore - real ART keeps this read-only
|
|
TraceLog("InstallJniTraceHooks: installed (full call/field/exception/lifecycle set, file+logcat)");
|
|
}
|