frida-java-bridge
Version:
Java runtime interop from Frida
1,464 lines (1,229 loc) • 154 kB
JavaScript
import makeCodeAllocator from './alloc.js';
import {
jvmtiVersion,
jvmtiCapabilities,
EnvJvmti
} from './jvmti.js';
import { parseInstructionsAt } from './machine-code.js';
import memoize from './memoize.js';
import { checkJniResult, JNI_OK } from './result.js';
import VM from './vm.js';
const jsizeSize = 4;
const pointerSize = Process.pointerSize;
const {
readU32,
readPointer,
writeU32,
writePointer
} = NativePointer.prototype;
const kAccPublic = 0x0001;
const kAccStatic = 0x0008;
const kAccFinal = 0x0010;
const kAccNative = 0x0100;
const kAccFastNative = 0x00080000;
const kAccCriticalNative = 0x00200000;
const kAccFastInterpreterToInterpreterInvoke = 0x40000000;
const kAccSkipAccessChecks = 0x00080000;
const kAccSingleImplementation = 0x08000000;
const kAccNterpEntryPointFastPathFlag = 0x00100000;
const kAccNterpInvokeFastPathFlag = 0x00200000;
const kAccPublicApi = 0x10000000;
const kAccXposedHookedMethod = 0x10000000;
const kPointer = 0x0;
const kFullDeoptimization = 3;
const kSelectiveDeoptimization = 5;
const THUMB_BIT_REMOVAL_MASK = ptr(1).not();
const X86_JMP_MAX_DISTANCE = 0x7fffbfff;
const ARM64_ADRP_MAX_DISTANCE = 0xfffff000;
const ENV_VTABLE_OFFSET_EXCEPTION_CLEAR = 17 * pointerSize;
const ENV_VTABLE_OFFSET_FATAL_ERROR = 18 * pointerSize;
export const DVM_JNI_ENV_OFFSET_SELF = 12;
const DVM_CLASS_OBJECT_OFFSET_VTABLE_COUNT = 112;
const DVM_CLASS_OBJECT_OFFSET_VTABLE = 116;
const DVM_OBJECT_OFFSET_CLAZZ = 0;
const DVM_METHOD_SIZE = 56;
const DVM_METHOD_OFFSET_ACCESS_FLAGS = 4;
const DVM_METHOD_OFFSET_METHOD_INDEX = 8;
const DVM_METHOD_OFFSET_REGISTERS_SIZE = 10;
const DVM_METHOD_OFFSET_OUTS_SIZE = 12;
const DVM_METHOD_OFFSET_INS_SIZE = 14;
const DVM_METHOD_OFFSET_SHORTY = 28;
const DVM_METHOD_OFFSET_JNI_ARG_INFO = 36;
const DALVIK_JNI_RETURN_VOID = 0;
const DALVIK_JNI_RETURN_FLOAT = 1;
const DALVIK_JNI_RETURN_DOUBLE = 2;
const DALVIK_JNI_RETURN_S8 = 3;
const DALVIK_JNI_RETURN_S4 = 4;
const DALVIK_JNI_RETURN_S2 = 5;
const DALVIK_JNI_RETURN_U2 = 6;
const DALVIK_JNI_RETURN_S1 = 7;
const DALVIK_JNI_NO_ARG_INFO = 0x80000000;
const DALVIK_JNI_RETURN_SHIFT = 28;
const STD_STRING_SIZE = 3 * pointerSize;
const STD_VECTOR_SIZE = 3 * pointerSize;
const AF_UNIX = 1;
const SOCK_STREAM = 1;
const getArtRuntimeSpec = memoize(_getArtRuntimeSpec);
const getArtInstrumentationSpec = memoize(_getArtInstrumentationSpec);
export const getArtMethodSpec = memoize(_getArtMethodSpec);
export const getArtThreadSpec = memoize(_getArtThreadSpec);
const getArtManagedStackSpec = memoize(_getArtManagedStackSpec);
const getArtThreadStateTransitionImpl = memoize(_getArtThreadStateTransitionImpl);
export const getAndroidVersion = memoize(_getAndroidVersion);
const getAndroidCodename = memoize(_getAndroidCodename);
export const getAndroidApiLevel = memoize(_getAndroidApiLevel);
const getArtQuickFrameInfoGetterThunk = memoize(_getArtQuickFrameInfoGetterThunk);
const makeCxxMethodWrapperReturningPointerByValue =
(Process.arch === 'ia32')
? makeCxxMethodWrapperReturningPointerByValueInFirstArg
: makeCxxMethodWrapperReturningPointerByValueGeneric;
const nativeFunctionOptions = {
exceptions: 'propagate'
};
const artThreadStateTransitions = {};
let cachedApi = null;
let cachedArtClassLinkerSpec = null;
let MethodMangler = null;
let artController = null;
const inlineHooks = [];
const patchedClasses = new Map();
const artQuickInterceptors = [];
let thunkPage = null;
let thunkOffset = 0;
let taughtArtAboutReplacementMethods = false;
let taughtArtAboutMethodInstrumentation = false;
let backtraceModule = null;
const jdwpSessions = [];
let socketpair = null;
let trampolineAllocator = null;
export function getApi () {
if (cachedApi === null) {
cachedApi = _getApi();
}
return cachedApi;
}
function _getApi () {
const vmModules = Process.enumerateModules()
.filter(m => /^lib(art|dvm).so$/.test(m.name))
.filter(m => !/\/system\/fake-libs/.test(m.path));
if (vmModules.length === 0) {
return null;
}
const vmModule = vmModules[0];
const flavor = (vmModule.name.indexOf('art') !== -1) ? 'art' : 'dalvik';
const isArt = flavor === 'art';
const temporaryApi = {
module: vmModule,
find (name) {
const { module } = this;
let address = module.findExportByName(name);
if (address === null) {
address = module.findSymbolByName(name);
}
return address;
},
flavor,
addLocalReference: null
};
temporaryApi.isApiLevel34OrApexEquivalent = isArt && (
temporaryApi.find('_ZN3art7AppInfo29GetPrimaryApkReferenceProfileEv') !== null ||
temporaryApi.find('_ZN3art6Thread15RunFlipFunctionEPS0_') !== null
);
const pending = isArt
? {
functions: {
JNI_GetCreatedJavaVMs: ['JNI_GetCreatedJavaVMs', 'int', ['pointer', 'int', 'pointer']],
// Android < 7
artInterpreterToCompiledCodeBridge: function (address) {
this.artInterpreterToCompiledCodeBridge = address;
},
// Android >= 8
_ZN3art9JavaVMExt12AddGlobalRefEPNS_6ThreadENS_6ObjPtrINS_6mirror6ObjectEEE: ['art::JavaVMExt::AddGlobalRef', 'pointer', ['pointer', 'pointer', 'pointer']],
// Android >= 6
_ZN3art9JavaVMExt12AddGlobalRefEPNS_6ThreadEPNS_6mirror6ObjectE: ['art::JavaVMExt::AddGlobalRef', 'pointer', ['pointer', 'pointer', 'pointer']],
// Android < 6: makeAddGlobalRefFallbackForAndroid5() needs these:
_ZN3art17ReaderWriterMutex13ExclusiveLockEPNS_6ThreadE: ['art::ReaderWriterMutex::ExclusiveLock', 'void', ['pointer', 'pointer']],
_ZN3art17ReaderWriterMutex15ExclusiveUnlockEPNS_6ThreadE: ['art::ReaderWriterMutex::ExclusiveUnlock', 'void', ['pointer', 'pointer']],
// Android <= 7
_ZN3art22IndirectReferenceTable3AddEjPNS_6mirror6ObjectE: function (address) {
this['art::IndirectReferenceTable::Add'] = new NativeFunction(address, 'pointer', ['pointer', 'uint', 'pointer'], nativeFunctionOptions);
},
// Android > 7
_ZN3art22IndirectReferenceTable3AddENS_15IRTSegmentStateENS_6ObjPtrINS_6mirror6ObjectEEE: function (address) {
this['art::IndirectReferenceTable::Add'] = new NativeFunction(address, 'pointer', ['pointer', 'uint', 'pointer'], nativeFunctionOptions);
},
// Android >= 7
_ZN3art9JavaVMExt12DecodeGlobalEPv: function (address) {
let decodeGlobal;
if (getAndroidApiLevel() >= 26) {
// Returns ObjPtr<mirror::Object>
decodeGlobal = makeCxxMethodWrapperReturningPointerByValue(address, ['pointer', 'pointer']);
} else {
// Returns mirror::Object *
decodeGlobal = new NativeFunction(address, 'pointer', ['pointer', 'pointer'], nativeFunctionOptions);
}
this['art::JavaVMExt::DecodeGlobal'] = function (vm, thread, ref) {
return decodeGlobal(vm, ref);
};
},
// Android >= 6
_ZN3art9JavaVMExt12DecodeGlobalEPNS_6ThreadEPv: ['art::JavaVMExt::DecodeGlobal', 'pointer', ['pointer', 'pointer', 'pointer']],
// makeDecodeGlobalFallback() uses:
// Android >= 15
_ZNK3art6Thread19DecodeGlobalJObjectEP8_jobject: ['art::Thread::DecodeJObject', 'pointer', ['pointer', 'pointer']],
// Android < 6
_ZNK3art6Thread13DecodeJObjectEP8_jobject: ['art::Thread::DecodeJObject', 'pointer', ['pointer', 'pointer']],
// Android >= 6
_ZN3art10ThreadList10SuspendAllEPKcb: ['art::ThreadList::SuspendAll', 'void', ['pointer', 'pointer', 'bool']],
// or fallback:
_ZN3art10ThreadList10SuspendAllEv: function (address) {
const suspendAll = new NativeFunction(address, 'void', ['pointer'], nativeFunctionOptions);
this['art::ThreadList::SuspendAll'] = function (threadList, cause, longSuspend) {
return suspendAll(threadList);
};
},
_ZN3art10ThreadList9ResumeAllEv: ['art::ThreadList::ResumeAll', 'void', ['pointer']],
// Android >= 7
_ZN3art11ClassLinker12VisitClassesEPNS_12ClassVisitorE: ['art::ClassLinker::VisitClasses', 'void', ['pointer', 'pointer']],
// Android < 7
_ZN3art11ClassLinker12VisitClassesEPFbPNS_6mirror5ClassEPvES4_: function (address) {
const visitClasses = new NativeFunction(address, 'void', ['pointer', 'pointer', 'pointer'], nativeFunctionOptions);
this['art::ClassLinker::VisitClasses'] = function (classLinker, visitor) {
visitClasses(classLinker, visitor, NULL);
};
},
_ZNK3art11ClassLinker17VisitClassLoadersEPNS_18ClassLoaderVisitorE: ['art::ClassLinker::VisitClassLoaders', 'void', ['pointer', 'pointer']],
_ZN3art2gc4Heap12VisitObjectsEPFvPNS_6mirror6ObjectEPvES5_: ['art::gc::Heap::VisitObjects', 'void', ['pointer', 'pointer', 'pointer']],
_ZN3art2gc4Heap12GetInstancesERNS_24VariableSizedHandleScopeENS_6HandleINS_6mirror5ClassEEEiRNSt3__16vectorINS4_INS5_6ObjectEEENS8_9allocatorISB_EEEE: ['art::gc::Heap::GetInstances', 'void', ['pointer', 'pointer', 'pointer', 'int', 'pointer']],
// Android >= 9
_ZN3art2gc4Heap12GetInstancesERNS_24VariableSizedHandleScopeENS_6HandleINS_6mirror5ClassEEEbiRNSt3__16vectorINS4_INS5_6ObjectEEENS8_9allocatorISB_EEEE: function (address) {
const getInstances = new NativeFunction(address, 'void', ['pointer', 'pointer', 'pointer', 'bool', 'int', 'pointer'], nativeFunctionOptions);
this['art::gc::Heap::GetInstances'] = function (instance, scope, hClass, maxCount, instances) {
const useIsAssignableFrom = 0;
getInstances(instance, scope, hClass, useIsAssignableFrom, maxCount, instances);
};
},
_ZN3art12StackVisitorC2EPNS_6ThreadEPNS_7ContextENS0_13StackWalkKindEjb: ['art::StackVisitor::StackVisitor', 'void', ['pointer', 'pointer', 'pointer', 'uint', 'uint', 'bool']],
_ZN3art12StackVisitorC2EPNS_6ThreadEPNS_7ContextENS0_13StackWalkKindEmb: ['art::StackVisitor::StackVisitor', 'void', ['pointer', 'pointer', 'pointer', 'uint', 'size_t', 'bool']],
_ZN3art12StackVisitor9WalkStackILNS0_16CountTransitionsE0EEEvb: ['art::StackVisitor::WalkStack', 'void', ['pointer', 'bool']],
_ZNK3art12StackVisitor9GetMethodEv: ['art::StackVisitor::GetMethod', 'pointer', ['pointer']],
_ZNK3art12StackVisitor16DescribeLocationEv: function (address) {
this['art::StackVisitor::DescribeLocation'] = makeCxxMethodWrapperReturningStdStringByValue(address, ['pointer']);
},
_ZNK3art12StackVisitor24GetCurrentQuickFrameInfoEv: function (address) {
this['art::StackVisitor::GetCurrentQuickFrameInfo'] = makeArtQuickFrameInfoGetter(address);
},
_ZN3art6Thread18GetLongJumpContextEv: ['art::Thread::GetLongJumpContext', 'pointer', ['pointer']],
_ZN3art6mirror5Class13GetDescriptorEPNSt3__112basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEE: function (address) {
this['art::mirror::Class::GetDescriptor'] = address;
},
_ZN3art6mirror5Class11GetLocationEv: function (address) {
this['art::mirror::Class::GetLocation'] = makeCxxMethodWrapperReturningStdStringByValue(address, ['pointer']);
},
_ZN3art9ArtMethod12PrettyMethodEb: function (address) {
this['art::ArtMethod::PrettyMethod'] = makeCxxMethodWrapperReturningStdStringByValue(address, ['pointer', 'bool']);
},
_ZN3art12PrettyMethodEPNS_9ArtMethodEb: function (address) {
this['art::ArtMethod::PrettyMethodNullSafe'] = makeCxxMethodWrapperReturningStdStringByValue(address, ['pointer', 'bool']);
},
// Android < 6 for cloneArtMethod()
_ZN3art6Thread14CurrentFromGdbEv: ['art::Thread::CurrentFromGdb', 'pointer', []],
_ZN3art6mirror6Object5CloneEPNS_6ThreadE: function (address) {
this['art::mirror::Object::Clone'] = new NativeFunction(address, 'pointer', ['pointer', 'pointer'], nativeFunctionOptions);
},
_ZN3art6mirror6Object5CloneEPNS_6ThreadEm: function (address) {
const clone = new NativeFunction(address, 'pointer', ['pointer', 'pointer', 'pointer'], nativeFunctionOptions);
this['art::mirror::Object::Clone'] = function (thisPtr, threadPtr) {
const numTargetBytes = NULL;
return clone(thisPtr, threadPtr, numTargetBytes);
};
},
_ZN3art6mirror6Object5CloneEPNS_6ThreadEj: function (address) {
const clone = new NativeFunction(address, 'pointer', ['pointer', 'pointer', 'uint'], nativeFunctionOptions);
this['art::mirror::Object::Clone'] = function (thisPtr, threadPtr) {
const numTargetBytes = 0;
return clone(thisPtr, threadPtr, numTargetBytes);
};
},
_ZN3art3Dbg14SetJdwpAllowedEb: ['art::Dbg::SetJdwpAllowed', 'void', ['bool']],
_ZN3art3Dbg13ConfigureJdwpERKNS_4JDWP11JdwpOptionsE: ['art::Dbg::ConfigureJdwp', 'void', ['pointer']],
_ZN3art31InternalDebuggerControlCallback13StartDebuggerEv: ['art::InternalDebuggerControlCallback::StartDebugger', 'void', ['pointer']],
_ZN3art3Dbg9StartJdwpEv: ['art::Dbg::StartJdwp', 'void', []],
_ZN3art3Dbg8GoActiveEv: ['art::Dbg::GoActive', 'void', []],
_ZN3art3Dbg21RequestDeoptimizationERKNS_21DeoptimizationRequestE: ['art::Dbg::RequestDeoptimization', 'void', ['pointer']],
_ZN3art3Dbg20ManageDeoptimizationEv: ['art::Dbg::ManageDeoptimization', 'void', []],
_ZN3art15instrumentation15Instrumentation20EnableDeoptimizationEv: ['art::Instrumentation::EnableDeoptimization', 'void', ['pointer']],
// Android >= 6
_ZN3art15instrumentation15Instrumentation20DeoptimizeEverythingEPKc: ['art::Instrumentation::DeoptimizeEverything', 'void', ['pointer', 'pointer']],
// Android < 6
_ZN3art15instrumentation15Instrumentation20DeoptimizeEverythingEv: function (address) {
const deoptimize = new NativeFunction(address, 'void', ['pointer'], nativeFunctionOptions);
this['art::Instrumentation::DeoptimizeEverything'] = function (instrumentation, key) {
deoptimize(instrumentation);
};
},
_ZN3art7Runtime19DeoptimizeBootImageEv: ['art::Runtime::DeoptimizeBootImage', 'void', ['pointer']],
_ZN3art15instrumentation15Instrumentation10DeoptimizeEPNS_9ArtMethodE: ['art::Instrumentation::Deoptimize', 'void', ['pointer', 'pointer']],
// Android >= 11
_ZN3art3jni12JniIdManager14DecodeMethodIdEP10_jmethodID: ['art::jni::JniIdManager::DecodeMethodId', 'pointer', ['pointer', 'pointer']],
_ZN3art11interpreter18GetNterpEntryPointEv: ['art::interpreter::GetNterpEntryPoint', 'pointer', []],
_ZN3art7Monitor17TranslateLocationEPNS_9ArtMethodEjPPKcPi: ['art::Monitor::TranslateLocation', 'void', ['pointer', 'uint32', 'pointer', 'pointer']]
},
variables: {
_ZN3art3Dbg9gRegistryE: function (address) {
this.isJdwpStarted = () => !address.readPointer().isNull();
},
_ZN3art3Dbg15gDebuggerActiveE: function (address) {
this.isDebuggerActive = () => !!address.readU8();
}
},
optionals: new Set([
'artInterpreterToCompiledCodeBridge',
'_ZN3art9JavaVMExt12AddGlobalRefEPNS_6ThreadENS_6ObjPtrINS_6mirror6ObjectEEE',
'_ZN3art9JavaVMExt12AddGlobalRefEPNS_6ThreadEPNS_6mirror6ObjectE',
'_ZN3art9JavaVMExt12DecodeGlobalEPv',
'_ZN3art9JavaVMExt12DecodeGlobalEPNS_6ThreadEPv',
'_ZNK3art6Thread19DecodeGlobalJObjectEP8_jobject',
'_ZNK3art6Thread13DecodeJObjectEP8_jobject',
'_ZN3art10ThreadList10SuspendAllEPKcb',
'_ZN3art10ThreadList10SuspendAllEv',
'_ZN3art11ClassLinker12VisitClassesEPNS_12ClassVisitorE',
'_ZN3art11ClassLinker12VisitClassesEPFbPNS_6mirror5ClassEPvES4_',
'_ZNK3art11ClassLinker17VisitClassLoadersEPNS_18ClassLoaderVisitorE',
'_ZN3art6mirror6Object5CloneEPNS_6ThreadE',
'_ZN3art6mirror6Object5CloneEPNS_6ThreadEm',
'_ZN3art6mirror6Object5CloneEPNS_6ThreadEj',
'_ZN3art22IndirectReferenceTable3AddEjPNS_6mirror6ObjectE',
'_ZN3art22IndirectReferenceTable3AddENS_15IRTSegmentStateENS_6ObjPtrINS_6mirror6ObjectEEE',
'_ZN3art2gc4Heap12VisitObjectsEPFvPNS_6mirror6ObjectEPvES5_',
'_ZN3art2gc4Heap12GetInstancesERNS_24VariableSizedHandleScopeENS_6HandleINS_6mirror5ClassEEEiRNSt3__16vectorINS4_INS5_6ObjectEEENS8_9allocatorISB_EEEE',
'_ZN3art2gc4Heap12GetInstancesERNS_24VariableSizedHandleScopeENS_6HandleINS_6mirror5ClassEEEbiRNSt3__16vectorINS4_INS5_6ObjectEEENS8_9allocatorISB_EEEE',
'_ZN3art12StackVisitorC2EPNS_6ThreadEPNS_7ContextENS0_13StackWalkKindEjb',
'_ZN3art12StackVisitorC2EPNS_6ThreadEPNS_7ContextENS0_13StackWalkKindEmb',
'_ZN3art12StackVisitor9WalkStackILNS0_16CountTransitionsE0EEEvb',
'_ZNK3art12StackVisitor9GetMethodEv',
'_ZNK3art12StackVisitor16DescribeLocationEv',
'_ZNK3art12StackVisitor24GetCurrentQuickFrameInfoEv',
'_ZN3art6Thread18GetLongJumpContextEv',
'_ZN3art6mirror5Class13GetDescriptorEPNSt3__112basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEE',
'_ZN3art6mirror5Class11GetLocationEv',
'_ZN3art9ArtMethod12PrettyMethodEb',
'_ZN3art12PrettyMethodEPNS_9ArtMethodEb',
'_ZN3art3Dbg13ConfigureJdwpERKNS_4JDWP11JdwpOptionsE',
'_ZN3art31InternalDebuggerControlCallback13StartDebuggerEv',
'_ZN3art3Dbg15gDebuggerActiveE',
'_ZN3art15instrumentation15Instrumentation20EnableDeoptimizationEv',
'_ZN3art15instrumentation15Instrumentation20DeoptimizeEverythingEPKc',
'_ZN3art15instrumentation15Instrumentation20DeoptimizeEverythingEv',
'_ZN3art7Runtime19DeoptimizeBootImageEv',
'_ZN3art15instrumentation15Instrumentation10DeoptimizeEPNS_9ArtMethodE',
'_ZN3art3Dbg9StartJdwpEv',
'_ZN3art3Dbg8GoActiveEv',
'_ZN3art3Dbg21RequestDeoptimizationERKNS_21DeoptimizationRequestE',
'_ZN3art3Dbg20ManageDeoptimizationEv',
'_ZN3art3Dbg9gRegistryE',
'_ZN3art3jni12JniIdManager14DecodeMethodIdEP10_jmethodID',
'_ZN3art11interpreter18GetNterpEntryPointEv',
'_ZN3art7Monitor17TranslateLocationEPNS_9ArtMethodEjPPKcPi'
])
}
: {
functions: {
_Z20dvmDecodeIndirectRefP6ThreadP8_jobject: ['dvmDecodeIndirectRef', 'pointer', ['pointer', 'pointer']],
_Z15dvmUseJNIBridgeP6MethodPv: ['dvmUseJNIBridge', 'void', ['pointer', 'pointer']],
_Z20dvmHeapSourceGetBasev: ['dvmHeapSourceGetBase', 'pointer', []],
_Z21dvmHeapSourceGetLimitv: ['dvmHeapSourceGetLimit', 'pointer', []],
_Z16dvmIsValidObjectPK6Object: ['dvmIsValidObject', 'uint8', ['pointer']],
JNI_GetCreatedJavaVMs: ['JNI_GetCreatedJavaVMs', 'int', ['pointer', 'int', 'pointer']]
},
variables: {
gDvmJni: function (address) {
this.gDvmJni = address;
},
gDvm: function (address) {
this.gDvm = address;
}
}
};
const {
functions = {},
variables = {},
optionals = new Set()
} = pending;
const missing = [];
for (const [name, signature] of Object.entries(functions)) {
const address = temporaryApi.find(name);
if (address !== null) {
if (typeof signature === 'function') {
signature.call(temporaryApi, address);
} else {
temporaryApi[signature[0]] = new NativeFunction(address, signature[1], signature[2], nativeFunctionOptions);
}
} else {
if (!optionals.has(name)) {
missing.push(name);
}
}
}
for (const [name, handler] of Object.entries(variables)) {
const address = temporaryApi.find(name);
if (address !== null) {
handler.call(temporaryApi, address);
} else {
if (!optionals.has(name)) {
missing.push(name);
}
}
}
if (missing.length > 0) {
throw new Error('Java API only partially available; please file a bug. Missing: ' + missing.join(', '));
}
const vms = Memory.alloc(pointerSize);
const vmCount = Memory.alloc(jsizeSize);
checkJniResult('JNI_GetCreatedJavaVMs', temporaryApi.JNI_GetCreatedJavaVMs(vms, 1, vmCount));
if (vmCount.readInt() === 0) {
return null;
}
temporaryApi.vm = vms.readPointer();
if (isArt) {
const apiLevel = getAndroidApiLevel();
let kAccCompileDontBother;
if (apiLevel >= 27) {
kAccCompileDontBother = 0x02000000;
} else if (apiLevel >= 24) {
kAccCompileDontBother = 0x01000000;
} else {
kAccCompileDontBother = 0;
}
temporaryApi.kAccCompileDontBother = kAccCompileDontBother;
const artRuntime = temporaryApi.vm.add(pointerSize).readPointer();
temporaryApi.artRuntime = artRuntime;
const runtimeSpec = getArtRuntimeSpec(temporaryApi);
const runtimeOffset = runtimeSpec.offset;
const instrumentationOffset = runtimeOffset.instrumentation;
temporaryApi.artInstrumentation = (instrumentationOffset !== null) ? artRuntime.add(instrumentationOffset) : null;
temporaryApi.artHeap = artRuntime.add(runtimeOffset.heap).readPointer();
temporaryApi.artThreadList = artRuntime.add(runtimeOffset.threadList).readPointer();
/*
* We must use the *correct* copy (or address) of art_quick_generic_jni_trampoline
* in order for the stack trace to recognize the JNI stub quick frame.
*
* For ARTs for Android 6.x we can just use the JNI trampoline built into ART.
*/
const classLinker = artRuntime.add(runtimeOffset.classLinker).readPointer();
const classLinkerOffsets = getArtClassLinkerSpec(artRuntime, runtimeSpec).offset;
const quickResolutionTrampoline = classLinker.add(classLinkerOffsets.quickResolutionTrampoline).readPointer();
const quickImtConflictTrampoline = classLinker.add(classLinkerOffsets.quickImtConflictTrampoline).readPointer();
const quickGenericJniTrampoline = classLinker.add(classLinkerOffsets.quickGenericJniTrampoline).readPointer();
const quickToInterpreterBridgeTrampoline = classLinker.add(classLinkerOffsets.quickToInterpreterBridgeTrampoline).readPointer();
temporaryApi.artClassLinker = {
address: classLinker,
quickResolutionTrampoline,
quickImtConflictTrampoline,
quickGenericJniTrampoline,
quickToInterpreterBridgeTrampoline
};
const vm = new VM(temporaryApi);
temporaryApi.artQuickGenericJniTrampoline = getArtQuickEntrypointFromTrampoline(quickGenericJniTrampoline, vm);
temporaryApi.artQuickToInterpreterBridge = getArtQuickEntrypointFromTrampoline(quickToInterpreterBridgeTrampoline, vm);
temporaryApi.artQuickResolutionTrampoline = getArtQuickEntrypointFromTrampoline(quickResolutionTrampoline, vm);
if (temporaryApi['art::JavaVMExt::AddGlobalRef'] === undefined) {
temporaryApi['art::JavaVMExt::AddGlobalRef'] = makeAddGlobalRefFallbackForAndroid5(temporaryApi);
}
if (temporaryApi['art::JavaVMExt::DecodeGlobal'] === undefined) {
temporaryApi['art::JavaVMExt::DecodeGlobal'] = makeDecodeGlobalFallback(temporaryApi);
}
if (temporaryApi['art::ArtMethod::PrettyMethod'] === undefined) {
temporaryApi['art::ArtMethod::PrettyMethod'] = temporaryApi['art::ArtMethod::PrettyMethodNullSafe'];
}
if (temporaryApi['art::interpreter::GetNterpEntryPoint'] !== undefined) {
temporaryApi.artNterpEntryPoint = temporaryApi['art::interpreter::GetNterpEntryPoint']();
} else {
temporaryApi.artNterpEntryPoint = temporaryApi.find('ExecuteNterpImpl');
}
artController = makeArtController(temporaryApi, vm);
fixupArtQuickDeliverExceptionBug(temporaryApi);
let cachedJvmti = null;
Object.defineProperty(temporaryApi, 'jvmti', {
get () {
if (cachedJvmti === null) {
cachedJvmti = [tryGetEnvJvmti(vm, this.artRuntime)];
}
return cachedJvmti[0];
}
});
}
const cxxImports = vmModule.enumerateImports()
.filter(imp => imp.name.indexOf('_Z') === 0)
.reduce((result, imp) => {
result[imp.name] = imp.address;
return result;
}, {});
temporaryApi.$new = new NativeFunction(cxxImports._Znwm || cxxImports._Znwj, 'pointer', ['ulong'], nativeFunctionOptions);
temporaryApi.$delete = new NativeFunction(cxxImports._ZdlPv, 'void', ['pointer'], nativeFunctionOptions);
MethodMangler = isArt ? ArtMethodMangler : DalvikMethodMangler;
return temporaryApi;
}
function tryGetEnvJvmti (vm, runtime) {
let env = null;
vm.perform(() => {
const ensurePluginLoadedAddr = getApi().find('_ZN3art7Runtime18EnsurePluginLoadedEPKcPNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEE');
if (ensurePluginLoadedAddr === null) {
return;
}
const ensurePluginLoaded = new NativeFunction(ensurePluginLoadedAddr,
'bool',
['pointer', 'pointer', 'pointer']);
const errorPtr = Memory.alloc(pointerSize);
const success = ensurePluginLoaded(runtime, Memory.allocUtf8String('libopenjdkjvmti.so'), errorPtr);
if (!success) {
// FIXME: Avoid leaking error
return;
}
const kArtTiVersion = jvmtiVersion.v1_2 | 0x40000000;
const handle = vm.tryGetEnvHandle(kArtTiVersion);
if (handle === null) {
return;
}
env = new EnvJvmti(handle, vm);
const capaBuf = Memory.alloc(8);
capaBuf.writeU64(jvmtiCapabilities.canTagObjects);
const result = env.addCapabilities(capaBuf);
if (result !== JNI_OK) {
env = null;
}
});
return env;
}
export function ensureClassInitialized (env, classRef) {
const api = getApi();
if (api.flavor !== 'art') {
return;
}
env.getFieldId(classRef, 'x', 'Z');
env.exceptionClear();
}
function getArtVMSpec (api) {
return {
offset: (pointerSize === 4)
? {
globalsLock: 32,
globals: 72
}
: {
globalsLock: 64,
globals: 112
}
};
}
function _getArtRuntimeSpec (api) {
/*
* class Runtime {
* ...
* gc::Heap* heap_; <-- we need to find this
* std::unique_ptr<ArenaPool> jit_arena_pool_; <----- API level >= 24
* std::unique_ptr<ArenaPool> arena_pool_; __
* std::unique_ptr<ArenaPool> low_4gb_arena_pool_/linear_alloc_arena_pool_; <--|__ API level >= 23
* std::unique_ptr<LinearAlloc> linear_alloc_; \_
* std::atomic<LinearAlloc*> startup_linear_alloc_;<----- API level >= 34
* size_t max_spins_before_thin_lock_inflation_;
* MonitorList* monitor_list_;
* MonitorPool* monitor_pool_;
* ThreadList* thread_list_; <--- and these
* InternTable* intern_table_; <--/
* ClassLinker* class_linker_; <-/
* SignalCatcher* signal_catcher_;
* SmallIrtAllocator* small_irt_allocator_; <------------ API level >= 33 or Android Tiramisu Developer Preview
* std::unique_ptr<jni::JniIdManager> jni_id_manager_; <- API level >= 30 or Android R Developer Preview
* bool use_tombstoned_traces_; <-------------------- API level 27/28
* std::string stack_trace_file_; <-------------------- API level <= 28
* JavaVMExt* java_vm_; <-- so we find this then calculate our way backwards
* ...
* }
*/
const vm = api.vm;
const runtime = api.artRuntime;
const startOffset = (pointerSize === 4) ? 200 : 384;
const endOffset = startOffset + (100 * pointerSize);
const apiLevel = getAndroidApiLevel();
const codename = getAndroidCodename();
const { isApiLevel34OrApexEquivalent } = api;
let spec = null;
for (let offset = startOffset; offset !== endOffset; offset += pointerSize) {
const value = runtime.add(offset).readPointer();
if (value.equals(vm)) {
let classLinkerOffsets;
let jniIdManagerOffset = null;
if (apiLevel >= 33 || codename === 'Tiramisu' || isApiLevel34OrApexEquivalent) {
classLinkerOffsets = [offset - (4 * pointerSize)];
jniIdManagerOffset = offset - pointerSize;
} else if (apiLevel >= 30 || codename === 'R') {
classLinkerOffsets = [offset - (3 * pointerSize), offset - (4 * pointerSize)];
jniIdManagerOffset = offset - pointerSize;
} else if (apiLevel >= 29) {
classLinkerOffsets = [offset - (2 * pointerSize)];
} else if (apiLevel >= 27) {
classLinkerOffsets = [offset - STD_STRING_SIZE - (3 * pointerSize)];
} else {
classLinkerOffsets = [offset - STD_STRING_SIZE - (2 * pointerSize)];
}
for (const classLinkerOffset of classLinkerOffsets) {
const internTableOffset = classLinkerOffset - pointerSize;
const threadListOffset = internTableOffset - pointerSize;
let heapOffset;
if (isApiLevel34OrApexEquivalent) {
heapOffset = threadListOffset - (9 * pointerSize);
} else if (apiLevel >= 24) {
heapOffset = threadListOffset - (8 * pointerSize);
} else if (apiLevel >= 23) {
heapOffset = threadListOffset - (7 * pointerSize);
} else {
heapOffset = threadListOffset - (4 * pointerSize);
}
const candidate = {
offset: {
heap: heapOffset,
threadList: threadListOffset,
internTable: internTableOffset,
classLinker: classLinkerOffset,
jniIdManager: jniIdManagerOffset
}
};
if (tryGetArtClassLinkerSpec(runtime, candidate) !== null) {
spec = candidate;
break;
}
}
break;
}
}
if (spec === null) {
throw new Error('Unable to determine Runtime field offsets');
}
spec.offset.instrumentation = tryDetectInstrumentationOffset(api);
spec.offset.jniIdsIndirection = tryDetectJniIdsIndirectionOffset(api);
return spec;
}
const instrumentationOffsetParsers = {
ia32: parsex86InstrumentationOffset,
x64: parsex86InstrumentationOffset,
arm: parseArmInstrumentationOffset,
arm64: parseArm64InstrumentationOffset
};
function tryDetectInstrumentationOffset (api) {
const impl = api['art::Runtime::DeoptimizeBootImage'];
if (impl === undefined) {
return null;
}
return parseInstructionsAt(impl, instrumentationOffsetParsers[Process.arch], { limit: 30 });
}
function parsex86InstrumentationOffset (insn) {
if (insn.mnemonic !== 'lea') {
return null;
}
const offset = insn.operands[1].value.disp;
if (offset < 0x100 || offset > 0x400) {
return null;
}
return offset;
}
function parseArmInstrumentationOffset (insn) {
if (insn.mnemonic !== 'add.w') {
return null;
}
const ops = insn.operands;
if (ops.length !== 3) {
return null;
}
const op2 = ops[2];
if (op2.type !== 'imm') {
return null;
}
return op2.value;
}
function parseArm64InstrumentationOffset (insn) {
if (insn.mnemonic !== 'add') {
return null;
}
const ops = insn.operands;
if (ops.length !== 3) {
return null;
}
if (ops[0].value === 'sp' || ops[1].value === 'sp') {
return null;
}
const op2 = ops[2];
if (op2.type !== 'imm') {
return null;
}
const offset = op2.value.valueOf();
if (offset < 0x100 || offset > 0x400) {
return null;
}
return offset;
}
const jniIdsIndirectionOffsetParsers = {
ia32: parsex86JniIdsIndirectionOffset,
x64: parsex86JniIdsIndirectionOffset,
arm: parseArmJniIdsIndirectionOffset,
arm64: parseArm64JniIdsIndirectionOffset
};
function tryDetectJniIdsIndirectionOffset (api) {
const impl = api.find('_ZN3art7Runtime12SetJniIdTypeENS_9JniIdTypeE');
if (impl === null) {
return null;
}
const offset = parseInstructionsAt(impl, jniIdsIndirectionOffsetParsers[Process.arch], { limit: 20 });
if (offset === null) {
throw new Error('Unable to determine Runtime.jni_ids_indirection_ offset');
}
return offset;
}
function parsex86JniIdsIndirectionOffset (insn) {
if (insn.mnemonic === 'cmp') {
return insn.operands[0].value.disp;
}
return null;
}
function parseArmJniIdsIndirectionOffset (insn) {
if (insn.mnemonic === 'ldr.w') {
return insn.operands[1].value.disp;
}
return null;
}
function parseArm64JniIdsIndirectionOffset (insn, prevInsn) {
if (prevInsn === null) {
return null;
}
const { mnemonic } = insn;
const { mnemonic: prevMnemonic } = prevInsn;
if ((mnemonic === 'cmp' && prevMnemonic === 'ldr') || (mnemonic === 'bl' && prevMnemonic === 'str')) {
return prevInsn.operands[1].value.disp;
}
return null;
}
function _getArtInstrumentationSpec () {
const deoptimizationEnabledOffsets = {
'4-21': 136,
'4-22': 136,
'4-23': 172,
'4-24': 196,
'4-25': 196,
'4-26': 196,
'4-27': 196,
'4-28': 212,
'4-29': 172,
'4-30': 180,
'4-31': 180,
'8-21': 224,
'8-22': 224,
'8-23': 296,
'8-24': 344,
'8-25': 344,
'8-26': 352,
'8-27': 352,
'8-28': 392,
'8-29': 328,
'8-30': 336,
'8-31': 336
};
const deoptEnabledOffset = deoptimizationEnabledOffsets[`${pointerSize}-${getAndroidApiLevel()}`];
if (deoptEnabledOffset === undefined) {
throw new Error('Unable to determine Instrumentation field offsets');
}
return {
offset: {
forcedInterpretOnly: 4,
deoptimizationEnabled: deoptEnabledOffset
}
};
}
function getArtClassLinkerSpec (runtime, runtimeSpec) {
const spec = tryGetArtClassLinkerSpec(runtime, runtimeSpec);
if (spec === null) {
throw new Error('Unable to determine ClassLinker field offsets');
}
return spec;
}
function tryGetArtClassLinkerSpec (runtime, runtimeSpec) {
if (cachedArtClassLinkerSpec !== null) {
return cachedArtClassLinkerSpec;
}
/*
* On Android 5.x:
*
* class ClassLinker {
* ...
* InternTable* intern_table_; <-- We find this then calculate our way forwards
* const void* portable_resolution_trampoline_;
* const void* quick_resolution_trampoline_;
* const void* portable_imt_conflict_trampoline_;
* const void* quick_imt_conflict_trampoline_;
* const void* quick_generic_jni_trampoline_; <-- ...to this
* const void* quick_to_interpreter_bridge_trampoline_;
* ...
* }
*
* On Android 6.x and above:
*
* class ClassLinker {
* ...
* InternTable* intern_table_; <-- We find this then calculate our way forwards
* const void* quick_resolution_trampoline_;
* const void* quick_imt_conflict_trampoline_;
* const void* quick_generic_jni_trampoline_; <-- ...to this
* const void* quick_to_interpreter_bridge_trampoline_;
* ...
* }
*/
const { classLinker: classLinkerOffset, internTable: internTableOffset } = runtimeSpec.offset;
const classLinker = runtime.add(classLinkerOffset).readPointer();
const internTable = runtime.add(internTableOffset).readPointer();
const startOffset = (pointerSize === 4) ? 100 : 200;
const endOffset = startOffset + (100 * pointerSize);
const apiLevel = getAndroidApiLevel();
let spec = null;
for (let offset = startOffset; offset !== endOffset; offset += pointerSize) {
const value = classLinker.add(offset).readPointer();
if (value.equals(internTable)) {
let delta;
if (apiLevel >= 30 || getAndroidCodename() === 'R') {
delta = 6;
} else if (apiLevel >= 29) {
delta = 4;
} else if (apiLevel >= 23) {
delta = 3;
} else {
delta = 5;
}
const quickGenericJniTrampolineOffset = offset + (delta * pointerSize);
let quickResolutionTrampolineOffset;
if (apiLevel >= 23) {
quickResolutionTrampolineOffset = quickGenericJniTrampolineOffset - (2 * pointerSize);
} else {
quickResolutionTrampolineOffset = quickGenericJniTrampolineOffset - (3 * pointerSize);
}
spec = {
offset: {
quickResolutionTrampoline: quickResolutionTrampolineOffset,
quickImtConflictTrampoline: quickGenericJniTrampolineOffset - pointerSize,
quickGenericJniTrampoline: quickGenericJniTrampolineOffset,
quickToInterpreterBridgeTrampoline: quickGenericJniTrampolineOffset + pointerSize
}
};
break;
}
}
if (spec !== null) {
cachedArtClassLinkerSpec = spec;
}
return spec;
}
export function getArtClassSpec (vm) {
let apiLevel;
try {
apiLevel = getAndroidApiLevel();
} catch (e) {
return null;
}
if (apiLevel >= 36) {
return {
offset: {
ifields: 0x28,
methods: 0x28 + 0x8,
sfields: 0,
copiedMethodsOffset: 0x6c,
}
};
} else if (apiLevel >= 26) {
return {
offset: {
ifields: 0x28,
methods: 0x28 + 0x8,
sfields: 0x28 + 0x10,
copiedMethodsOffset: 0x74,
}
};
} else if (apiLevel >= 24) {
return {
offset: {
ifields: 0x38,
methods: 0x38 + 0x8,
sfields: 0x38 + 0x10,
copiedMethodsOffset: 0x7c,
}
};
} else {
return null;
}
}
function _getArtMethodSpec (vm) {
const api = getApi();
let spec;
vm.perform(env => {
const process = env.findClass('android/os/Process');
const getElapsedCpuTime = unwrapMethodId(env.getStaticMethodId(process, 'getElapsedCpuTime', '()J'));
env.deleteLocalRef(process);
const runtimeModule = Process.getModuleByName('libandroid_runtime.so');
const runtimeStart = runtimeModule.base;
const runtimeEnd = runtimeStart.add(runtimeModule.size);
const apiLevel = getAndroidApiLevel();
const entrypointFieldSize = (apiLevel <= 21) ? 8 : pointerSize;
const expectedAccessFlags = kAccPublic | kAccStatic | kAccFinal | kAccNative;
const relevantAccessFlagsMask = ~(kAccFastInterpreterToInterpreterInvoke | kAccPublicApi | kAccNterpInvokeFastPathFlag) >>> 0;
let jniCodeOffset = null;
let accessFlagsOffset = null;
let remaining = 2;
for (let offset = 0; offset !== 64 && remaining !== 0; offset += 4) {
const field = getElapsedCpuTime.add(offset);
if (jniCodeOffset === null) {
const address = field.readPointer();
if (address.compare(runtimeStart) >= 0 && address.compare(runtimeEnd) < 0) {
jniCodeOffset = offset;
remaining--;
}
}
if (accessFlagsOffset === null) {
const flags = field.readU32();
if ((flags & relevantAccessFlagsMask) === expectedAccessFlags) {
accessFlagsOffset = offset;
remaining--;
}
}
}
if (remaining !== 0) {
throw new Error('Unable to determine ArtMethod field offsets');
}
const quickCodeOffset = jniCodeOffset + entrypointFieldSize;
const size = (apiLevel <= 21) ? (quickCodeOffset + 32) : (quickCodeOffset + pointerSize);
spec = {
size,
offset: {
jniCode: jniCodeOffset,
quickCode: quickCodeOffset,
accessFlags: accessFlagsOffset
}
};
if ('artInterpreterToCompiledCodeBridge' in api) {
spec.offset.interpreterCode = jniCodeOffset - entrypointFieldSize;
}
});
return spec;
}
export function getArtFieldSpec (vm) {
const apiLevel = getAndroidApiLevel();
if (apiLevel >= 23) {
return {
size: 16,
offset: {
accessFlags: 4
}
};
}
if (apiLevel >= 21) {
return {
size: 24,
offset: {
accessFlags: 12
}
};
}
return null;
}
function _getArtThreadSpec (vm) {
/*
* bool32_t is_exception_reported_to_instrumentation_; <-- We need this on API level <= 22
* ...
* mirror::Throwable* exception; <-- ...and this on all versions
* uint8_t* stack_end;
* ManagedStack managed_stack;
* uintptr_t* suspend_trigger;
* JNIEnvExt* jni_env; <-- We find this then calculate our way backwards/forwards
* JNIEnvExt* tmp_jni_env; <-- API level >= 23
* Thread* self;
* mirror::Object* opeer;
* jobject jpeer;
* uint8_t* stack_begin;
* size_t stack_size;
* ThrowLocation throw_location; <-- ...and this on API level <= 22
* union DepsOrStackTraceSample {
* DepsOrStackTraceSample() {
* verifier_deps = nullptr;
* stack_trace_sample = nullptr;
* }
* std::vector<ArtMethod*>* stack_trace_sample;
* verifier::VerifierDeps* verifier_deps;
* } deps_or_stack_trace_sample;
* Thread* wait_next;
* mirror::Object* monitor_enter_object;
* BaseHandleScope* top_handle_scope; <-- ...and to this on all versions
*/
const apiLevel = getAndroidApiLevel();
let spec;
vm.perform(env => {
const threadHandle = getArtThreadFromEnv(env);
const envHandle = env.handle;
let isExceptionReportedOffset = null;
let exceptionOffset = null;
let throwLocationOffset = null;
let topHandleScopeOffset = null;
let managedStackOffset = null;
let selfOffset = null;
for (let offset = 144; offset !== 256; offset += pointerSize) {
const field = threadHandle.add(offset);
const value = field.readPointer();
if (value.equals(envHandle)) {
exceptionOffset = offset - (6 * pointerSize);
managedStackOffset = offset - (4 * pointerSize);
selfOffset = offset + (2 * pointerSize);
if (apiLevel <= 22) {
exceptionOffset -= pointerSize;
isExceptionReportedOffset = exceptionOffset - pointerSize - (9 * 8) - (3 * 4);
throwLocationOffset = offset + (6 * pointerSize);
managedStackOffset -= pointerSize;
selfOffset -= pointerSize;
}
topHandleScopeOffset = offset + (9 * pointerSize);
if (apiLevel <= 22) {
topHandleScopeOffset += (2 * pointerSize) + 4;
if (pointerSize === 8) {
topHandleScopeOffset += 4;
}
}
if (apiLevel >= 23) {
topHandleScopeOffset += pointerSize;
}
break;
}
}
if (topHandleScopeOffset === null) {
throw new Error('Unable to determine ArtThread field offsets');
}
spec = {
offset: {
isExceptionReportedToInstrumentation: isExceptionReportedOffset,
exception: exceptionOffset,
throwLocation: throwLocationOffset,
topHandleScope: topHandleScopeOffset,
managedStack: managedStackOffset,
self: selfOffset
}
};
});
return spec;
}
function _getArtManagedStackSpec () {
const apiLevel = getAndroidApiLevel();
if (apiLevel >= 23) {
return {
offset: {
topQuickFrame: 0,
link: pointerSize
}
};
} else {
return {
offset: {
topQuickFrame: 2 * pointerSize,
link: 0
}
};
}
}
const artQuickTrampolineParsers = {
ia32: parseArtQuickTrampolineX86,
x64: parseArtQuickTrampolineX86,
arm: parseArtQuickTrampolineArm,
arm64: parseArtQuickTrampolineArm64
};
function getArtQuickEntrypointFromTrampoline (trampoline, vm) {
let address;
vm.perform(env => {
const thread = getArtThreadFromEnv(env);
const tryParse = artQuickTrampolineParsers[Process.arch];
const insn = Instruction.parse(trampoline);
const offset = tryParse(insn);
if (offset !== null) {
address = thread.add(offset).readPointer();
} else {
address = trampoline;
}
});
return address;
}
function parseArtQuickTrampolineX86 (insn) {
if (insn.mnemonic === 'jmp') {
return insn.operands[0].value.disp;
}
return null;
}
function parseArtQuickTrampolineArm (insn) {
if (insn.mnemonic === 'ldr.w') {
return insn.operands[1].value.disp;
}
return null;
}
function parseArtQuickTrampolineArm64 (insn) {
if (insn.mnemonic === 'ldr') {
return insn.operands[1].value.disp;
}
return null;
}
export function getArtThreadFromEnv (env) {
return env.handle.add(pointerSize).readPointer();
}
function _getAndroidVersion () {
return getAndroidSystemProperty('ro.build.version.release');
}
function _getAndroidCodename () {
return getAndroidSystemProperty('ro.build.version.codename');
}
function _getAndroidApiLevel () {
return parseInt(getAndroidSystemProperty('ro.build.version.sdk'), 10);
}
let systemPropertyGet = null;
const PROP_VALUE_MAX = 92;
function getAndroidSystemProperty (name) {
if (systemPropertyGet === null) {
systemPropertyGet = new NativeFunction(
Process.getModuleByName('libc.so').getExportByName('__system_property_get'),
'int',
['pointer', 'pointer'],
nativeFunctionOptions);
}
const buf = Memory.alloc(PROP_VALUE_MAX);
systemPropertyGet(Memory.allocUtf8String(name), buf);
return buf.readUtf8String();
}
export function withRunnableArtThread (vm, env, fn) {
const perform = getArtThreadStateTransitionImpl(vm, env);
const id = getArtThreadFromEnv(env).toString();
artThreadStateTransitions[id] = fn;
perform(env.handle);
if (artThreadStateTransitions[id] !== undefined) {
delete artThreadStateTransitions[id];
throw new Error('Unable to perform state transition; please file a bug');
}
}
function _getArtThreadStateTransitionImpl (vm, env) {
const callback = new NativeCallback(onThreadStateTransitionComplete, 'void', ['pointer']);
return makeArtThreadStateTransitionImpl(vm, env, callback);
}
function onThreadStateTransitionComplete (thread) {
const id = thread.toString();
const fn = artThreadStateTransitions[id];
delete artThreadStateTransitions[id];
fn(thread);
}
export function withAllArtThreadsSuspended (fn) {
const api = getApi();
const threadList = api.artThreadList;
const longSuspend = false;
api['art::ThreadList::SuspendAll'](threadList, Memory.allocUtf8String('frida'), longSuspend ? 1 : 0);
try {
fn();
} finally {
api['art::ThreadList::ResumeAll'](threadList);
}
}
class ArtClassVisitor {
constructor (visit) {
const visitor = Memory.alloc(4 * pointerSize);
const vtable = visitor.add(pointerSize);
visitor.writePointer(vtable);
const onVisit = new NativeCallback((self, klass) => {
return visit(klass) === true ? 1 : 0;
}, 'bool', ['pointer', 'pointer']);
vtable.add(2 * pointerSize).writePointer(onVisit);
this.handle = visitor;
this._onVisit = onVisit;
}
}
export function makeArtClassVisitor (visit) {
const api = getApi();
if (api['art::ClassLinker::VisitClasses'] instanceof NativeFunction) {
return new ArtClassVisitor(visit);
}
return new NativeCallback(klass => {
return visit(klass) === true ? 1 : 0;
}, 'bool', ['pointer', 'pointer']);
}
class ArtClassLoaderVisitor {
constructor (visit) {
const visitor = Memory.alloc(4 * pointerSize);
const vtable = visitor.add(pointerSize);
visitor.writePointer(vtable);
const onVisit = new NativeCallback((self, klass) => {
visit(klass);
}, 'void', ['pointer', 'pointer']);
vtable.add(2 * pointerSize).writePointer(onVisit);
this.handle = visitor;
this._onVisit = onVisit;
}
}
export function makeArtClassLoaderVisitor (visit) {
return new ArtClassLoaderVisitor(visit);
}
const WalkKind = {
'include-inlined-frames': 0,
'skip-inlined-frames': 1
};
export class ArtStackVisitor {
constructor (thread, context, walkKind, numFrames = 0, checkSuspended = true) {
const api = getApi();
const baseSize = 512; /* Up to 488 bytes on 64-bit Android Q. */
const vtableSize = 3 * pointerSize;
const visitor = Memory.alloc(baseSize + vtableSize);
api['art::StackVisitor::StackVisitor'](visitor, thread, context, WalkKind[walkKind], numFrames,
checkSuspended ? 1 : 0);
const vtable = visitor.add(baseSize);
visitor.writePointer(vtable);
const onVisitFrame = new NativeCallback(this._visitFrame.bind(this), 'bool', ['pointer']);
vtable.add(2 * pointerSize).writePointer(onVisitFrame);
this.handle = visitor;
this._onVisitFrame = onVisitFrame;
const curShadowFrame = visitor.add((pointerSize === 4) ? 12 : 24);
this._curShadowFrame = curShadowFrame;
this._curQuickFrame = curShadowFrame.add(pointerSize);
this._curQuickFramePc = curShadowFrame.add(2 * pointerSize);
this._curOatQuickMethodHeader = curShadowFrame.add(3 * pointerSize);
this._getMethodImpl = api['art::StackVisitor::GetMethod'];
this._descLocImpl = api['art::StackVisitor::DescribeLocation'];
this._getCQFIImpl = api['art::StackVisitor::GetCurrentQuickFrameInfo'];
}
walkStack (includeTransitions = false) {
getApi()['art::StackVisitor::WalkStack'](this.handle, includeTransitions ? 1 : 0);
}
_visitFrame () {
return this.visitFrame() ? 1 : 0;
}
visitFrame () {
throw new Error('Subclass must implement visitFrame');
}
getMethod () {
const methodHandle = this._getMethodImpl(this.handle);
if (methodHandle.isNull()) {
return null;
}
return new ArtMethod(methodHandle);
}
getCurrentQuickFramePc () {
return this._curQuickFramePc.readPointer();
}
getCurrentQuickFrame () {
return this._curQuickFrame.readPointer();
}
getCurrentShadowFrame () {
return this._curShadowFrame.readPointer();
}
describeLocation () {
const result = new StdString();
this._descLocImpl(result, this.handle);
return result.disposeToString();
}
getCurrentOatQuickMethodHeader () {
return this._curOatQuickMethodHeader.readPointer();
}
getCurrentQuickFrameInfo () {
return this._getCQFIImpl(this.handle);
}