UNPKG

frida-java-bridge

Version:
1,464 lines (1,229 loc) 154 kB
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); }