frida-java-bridge
Version:
Java runtime interop from Frida
989 lines (842 loc) • 33.8 kB
JavaScript
import {
jvmtiVersion,
jvmtiCapabilities,
EnvJvmti
} from './jvmti.js';
import { parseInstructionsAt } from './machine-code.js';
import memoize from './memoize.js';
import { checkJniResult } from './result.js';
import VM from './vm.js';
const jsizeSize = 4;
const { pointerSize } = Process;
const JVM_ACC_NATIVE = 0x0100;
const JVM_ACC_IS_OLD = 0x00010000;
const JVM_ACC_IS_OBSOLETE = 0x00020000;
const JVM_ACC_NOT_C2_COMPILABLE = 0x02000000;
const JVM_ACC_NOT_C1_COMPILABLE = 0x04000000;
const JVM_ACC_NOT_C2_OSR_COMPILABLE = 0x08000000;
const nativeFunctionOptions = {
exceptions: 'propagate'
};
const getJvmMethodSpec = memoize(_getJvmMethodSpec);
const getJvmInstanceKlassSpec = memoize(_getJvmInstanceKlassSpec);
const getJvmThreadSpec = memoize(_getJvmThreadSpec);
let cachedApi = null;
let manglersScheduled = false;
const replaceManglers = new Map();
const revertManglers = new Map();
export function getApi () {
if (cachedApi === null) {
cachedApi = _getApi();
}
return cachedApi;
}
function _getApi () {
const vmModules = Process.enumerateModules()
.filter(m => /jvm.(dll|dylib|so)$/.test(m.name));
if (vmModules.length === 0) {
return null;
}
const vmModule = vmModules[0];
const temporaryApi = {
flavor: 'jvm'
};
const pending = Process.platform === 'windows'
? [{
module: vmModule,
functions: {
JNI_GetCreatedJavaVMs: ['JNI_GetCreatedJavaVMs', 'int', ['pointer', 'int', 'pointer']],
JVM_Sleep: ['JVM_Sleep', 'void', ['pointer', 'pointer', 'long']],
'VMThread::execute': ['VMThread::execute', 'void', ['pointer']],
'Method::size': ['Method::size', 'int', ['int']],
'Method::set_native_function': ['Method::set_native_function', 'void', ['pointer', 'pointer', 'int']],
'Method::clear_native_function': ['Method::clear_native_function', 'void', ['pointer']],
'Method::jmethod_id': ['Method::jmethod_id', 'pointer', ['pointer']],
'ClassLoaderDataGraph::classes_do': ['ClassLoaderDataGraph::classes_do', 'void', ['pointer']],
'NMethodSweeper::sweep_code_cache': ['NMethodSweeper::sweep_code_cache', 'void', []],
'OopMapCache::flush_obsolete_entries': ['OopMapCache::flush_obsolete_entries', 'void', ['pointer']]
},
variables: {
'VM_RedefineClasses::`vftable\'': function (address) {
this.vtableRedefineClasses = address;
},
'VM_RedefineClasses::doit': function (address) {
this.redefineClassesDoIt = address;
},
'VM_RedefineClasses::doit_prologue': function (address) {
this.redefineClassesDoItPrologue = address;
},
'VM_RedefineClasses::doit_epilogue': function (address) {
this.redefineClassesDoItEpilogue = address;
},
'VM_RedefineClasses::allow_nested_vm_operations': function (address) {
this.redefineClassesAllow = address;
},
'NMethodSweeper::_traversals': function (address) {
this.traversals = address;
},
'NMethodSweeper::_should_sweep': function (address) {
this.shouldSweep = address;
}
},
optionals: [
]
}]
// If platform is not Windows
: [{
module: vmModule,
functions: {
JNI_GetCreatedJavaVMs: ['JNI_GetCreatedJavaVMs', 'int', ['pointer', 'int', 'pointer']],
_ZN6Method4sizeEb: ['Method::size', 'int', ['int']],
_ZN6Method19set_native_functionEPhb: ['Method::set_native_function', 'void', ['pointer', 'pointer', 'int']],
_ZN6Method21clear_native_functionEv: ['Method::clear_native_function', 'void', ['pointer']],
// JDK >= 17
_ZN6Method24restore_unshareable_infoEP10JavaThread: ['Method::restore_unshareable_info', 'void', ['pointer', 'pointer']],
// JDK < 17
_ZN6Method24restore_unshareable_infoEP6Thread: ['Method::restore_unshareable_info', 'void', ['pointer', 'pointer']],
_ZN6Method11link_methodERK12methodHandleP10JavaThread: ['Method::link_method', 'void', ['pointer', 'pointer', 'pointer']],
_ZN6Method10jmethod_idEv: ['Method::jmethod_id', 'pointer', ['pointer']],
_ZN6Method10clear_codeEv: function (address) {
const clearCode = new NativeFunction(address, 'void', ['pointer'], nativeFunctionOptions);
this['Method::clear_code'] = function (thisPtr) {
clearCode(thisPtr);
};
},
_ZN6Method10clear_codeEb: function (address) {
const clearCode = new NativeFunction(address, 'void', ['pointer', 'int'], nativeFunctionOptions);
const lock = 0;
this['Method::clear_code'] = function (thisPtr) {
clearCode(thisPtr, lock);
};
},
// JDK >= 13
_ZN18VM_RedefineClasses19mark_dependent_codeEP13InstanceKlass: ['VM_RedefineClasses::mark_dependent_code', 'void', ['pointer', 'pointer']],
_ZN18VM_RedefineClasses20flush_dependent_codeEv: ['VM_RedefineClasses::flush_dependent_code', 'void', []],
// JDK < 13
_ZN18VM_RedefineClasses20flush_dependent_codeEP13InstanceKlassP6Thread: ['VM_RedefineClasses::flush_dependent_code', 'void', ['pointer', 'pointer', 'pointer']],
// JDK < 10
_ZN18VM_RedefineClasses20flush_dependent_codeE19instanceKlassHandleP6Thread: ['VM_RedefineClasses::flush_dependent_code', 'void', ['pointer', 'pointer', 'pointer']],
_ZN19ResolvedMethodTable21adjust_method_entriesEPb: ['ResolvedMethodTable::adjust_method_entries', 'void', ['pointer']],
// JDK < 10
_ZN15MemberNameTable21adjust_method_entriesEP13InstanceKlassPb: ['MemberNameTable::adjust_method_entries', 'void', ['pointer', 'pointer', 'pointer']],
_ZN17ConstantPoolCache21adjust_method_entriesEPb: function (address) {
const adjustMethod = new NativeFunction(address, 'void', ['pointer', 'pointer'], nativeFunctionOptions);
this['ConstantPoolCache::adjust_method_entries'] = function (thisPtr, holderPtr, tracePtr) {
adjustMethod(thisPtr, tracePtr);
};
},
// JDK < 13
_ZN17ConstantPoolCache21adjust_method_entriesEP13InstanceKlassPb: function (address) {
const adjustMethod = new NativeFunction(address, 'void', ['pointer', 'pointer', 'pointer'], nativeFunctionOptions);
this['ConstantPoolCache::adjust_method_entries'] = function (thisPtr, holderPtr, tracePtr) {
adjustMethod(thisPtr, holderPtr, tracePtr);
};
},
_ZN20ClassLoaderDataGraph10classes_doEP12KlassClosure: ['ClassLoaderDataGraph::classes_do', 'void', ['pointer']],
_ZN20ClassLoaderDataGraph22clean_deallocate_listsEb: ['ClassLoaderDataGraph::clean_deallocate_lists', 'void', ['int']],
_ZN10JavaThread27thread_from_jni_environmentEP7JNIEnv_: ['JavaThread::thread_from_jni_environment', 'pointer', ['pointer']],
_ZN8VMThread7executeEP12VM_Operation: ['VMThread::execute', 'void', ['pointer']],
_ZN11OopMapCache22flush_obsolete_entriesEv: ['OopMapCache::flush_obsolete_entries', 'void', ['pointer']],
_ZN14NMethodSweeper11force_sweepEv: ['NMethodSweeper::force_sweep', 'void', []],
_ZN14NMethodSweeper16sweep_code_cacheEv: ['NMethodSweeper::sweep_code_cache', 'void', []],
_ZN14NMethodSweeper17sweep_in_progressEv: ['NMethodSweeper::sweep_in_progress', 'bool', []],
JVM_Sleep: ['JVM_Sleep', 'void', ['pointer', 'pointer', 'long']]
},
variables: {
// JDK <= 9
_ZN18VM_RedefineClasses14_the_class_oopE: function (address) {
this.redefineClass = address;
},
// 9 < JDK < 13
_ZN18VM_RedefineClasses10_the_classE: function (address) {
this.redefineClass = address;
},
// JDK < 13
_ZN18VM_RedefineClasses25AdjustCpoolCacheAndVtable8do_klassEP5Klass: function (address) {
this.doKlass = address;
},
// JDK >= 13
_ZN18VM_RedefineClasses22AdjustAndCleanMetadata8do_klassEP5Klass: function (address) {
this.doKlass = address;
},
_ZTV18VM_RedefineClasses: function (address) {
this.vtableRedefineClasses = address;
},
_ZN18VM_RedefineClasses4doitEv: function (address) {
this.redefineClassesDoIt = address;
},
_ZN18VM_RedefineClasses13doit_prologueEv: function (address) {
this.redefineClassesDoItPrologue = address;
},
_ZN18VM_RedefineClasses13doit_epilogueEv: function (address) {
this.redefineClassesDoItEpilogue = address;
},
_ZN18VM_RedefineClassesD0Ev: function (address) {
this.redefineClassesDispose0 = address;
},
_ZN18VM_RedefineClassesD1Ev: function (address) {
this.redefineClassesDispose1 = address;
},
_ZNK18VM_RedefineClasses26allow_nested_vm_operationsEv: function (address) {
this.redefineClassesAllow = address;
},
_ZNK18VM_RedefineClasses14print_on_errorEP12outputStream: function (address) {
this.redefineClassesOnError = address;
},
// JDK >= 17
_ZN13InstanceKlass33create_new_default_vtable_indicesEiP10JavaThread: function (address) {
this.createNewDefaultVtableIndices = address;
},
// JDK < 17
_ZN13InstanceKlass33create_new_default_vtable_indicesEiP6Thread: function (address) {
this.createNewDefaultVtableIndices = address;
},
_ZN19Abstract_VM_Version19jre_release_versionEv: function (address) {
const getVersion = new NativeFunction(address, 'pointer', [], nativeFunctionOptions);
const versionS = getVersion().readCString();
this.version = versionS.startsWith('1.8')
? 8
: versionS.startsWith('9.')
? 9
: parseInt(versionS.slice(0, 2), 10);
this.versionS = versionS;
},
_ZN14NMethodSweeper11_traversalsE: function (address) {
this.traversals = address;
},
_ZN14NMethodSweeper21_sweep_fractions_leftE: function (address) {
this.fractions = address;
},
_ZN14NMethodSweeper13_should_sweepE: function (address) {
this.shouldSweep = address;
}
},
optionals: [
'_ZN6Method24restore_unshareable_infoEP10JavaThread',
'_ZN6Method24restore_unshareable_infoEP6Thread',
'_ZN6Method11link_methodERK12methodHandleP10JavaThread',
'_ZN6Method10clear_codeEv',
'_ZN6Method10clear_codeEb',
'_ZN18VM_RedefineClasses19mark_dependent_codeEP13InstanceKlass',
'_ZN18VM_RedefineClasses20flush_dependent_codeEv',
'_ZN18VM_RedefineClasses20flush_dependent_codeEP13InstanceKlassP6Thread',
'_ZN18VM_RedefineClasses20flush_dependent_codeE19instanceKlassHandleP6Thread',
'_ZN19ResolvedMethodTable21adjust_method_entriesEPb',
'_ZN15MemberNameTable21adjust_method_entriesEP13InstanceKlassPb',
'_ZN17ConstantPoolCache21adjust_method_entriesEPb',
'_ZN17ConstantPoolCache21adjust_method_entriesEP13InstanceKlassPb',
'_ZN20ClassLoaderDataGraph22clean_deallocate_listsEb',
'_ZN10JavaThread27thread_from_jni_environmentEP7JNIEnv_',
'_ZN14NMethodSweeper11force_sweepEv',
'_ZN14NMethodSweeper17sweep_in_progressEv',
'_ZN18VM_RedefineClasses14_the_class_oopE',
'_ZN18VM_RedefineClasses10_the_classE',
'_ZN18VM_RedefineClasses25AdjustCpoolCacheAndVtable8do_klassEP5Klass',
'_ZN18VM_RedefineClasses22AdjustAndCleanMetadata8do_klassEP5Klass',
'_ZN18VM_RedefineClassesD0Ev',
'_ZN18VM_RedefineClassesD1Ev',
'_ZNK18VM_RedefineClasses14print_on_errorEP12outputStream',
'_ZN13InstanceKlass33create_new_default_vtable_indicesEiP10JavaThread',
'_ZN13InstanceKlass33create_new_default_vtable_indicesEiP6Thread',
'_ZN14NMethodSweeper21_sweep_fractions_leftE'
]
}];
const missing = [];
pending.forEach(function (api) {
const module = api.module;
const functions = api.functions || {};
const variables = api.variables || {};
const optionals = new Set(api.optionals || []);
const tmp = module.enumerateExports()
.reduce(function (result, exp) {
result[exp.name] = exp;
return result;
}, {});
const exportByName = module.enumerateSymbols()
.reduce(function (result, exp) {
result[exp.name] = exp;
return result;
}, tmp);
Object.keys(functions)
.forEach(function (name) {
const exp = exportByName[name];
if (exp !== undefined) {
const signature = functions[name];
if (typeof signature === 'function') {
signature.call(temporaryApi, exp.address);
} else {
temporaryApi[signature[0]] = new NativeFunction(exp.address, signature[1], signature[2], nativeFunctionOptions);
}
} else {
if (!optionals.has(name)) {
missing.push(name);
}
}
});
Object.keys(variables)
.forEach(function (name) {
const exp = exportByName[name];
if (exp !== undefined) {
const handler = variables[name];
handler.call(temporaryApi, exp.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();
const allocatorFunctions = Process.platform === 'windows'
? {
$new: ['??2@YAPEAX_K@Z', 'pointer', ['ulong']],
$delete: ['??3@YAXPEAX@Z', 'void', ['pointer']]
}
// If platform is not Windows
: {
$new: ['_Znwm', 'pointer', ['ulong']],
$delete: ['_ZdlPv', 'void', ['pointer']]
};
for (const [name, [rawName, retType, argTypes]] of Object.entries(allocatorFunctions)) {
let address = Module.findGlobalExportByName(rawName);
if (address === null) {
address = DebugSymbol.fromName(rawName).address;
if (address.isNull()) {
throw new Error(`unable to find C++ allocator API, missing: '${rawName}'`);
}
}
temporaryApi[name] = new NativeFunction(address, retType, argTypes, nativeFunctionOptions);
}
temporaryApi.jvmti = getEnvJvmti(temporaryApi);
if (temporaryApi['JavaThread::thread_from_jni_environment'] === undefined) {
temporaryApi['JavaThread::thread_from_jni_environment'] = makeThreadFromJniHelper(temporaryApi);
}
return temporaryApi;
}
function getEnvJvmti (api) {
const vm = new VM(api);
let env;
vm.perform(() => {
const handle = vm.tryGetEnvHandle(jvmtiVersion.v1_0);
if (handle === null) {
throw new Error('JVMTI not available');
}
env = new EnvJvmti(handle, vm);
const capaBuf = Memory.alloc(8);
capaBuf.writeU64(jvmtiCapabilities.canTagObjects);
const result = env.addCapabilities(capaBuf);
checkJniResult('getEnvJvmti::AddCapabilities', result);
});
return env;
}
const threadOffsetParsers = {
x64: parseX64ThreadOffset
};
function makeThreadFromJniHelper (api) {
let offset = null;
const tryParse = threadOffsetParsers[Process.arch];
if (tryParse !== undefined) {
const vm = new VM(api);
const findClassImpl = vm.perform(env => env.handle.readPointer().add(6 * pointerSize).readPointer());
offset = parseInstructionsAt(findClassImpl, tryParse, { limit: 11 });
}
if (offset === null) {
return () => {
throw new Error('Unable to make thread_from_jni_environment() helper for the current architecture');
};
}
return env => {
return env.add(offset);
};
}
function parseX64ThreadOffset (insn) {
if (insn.mnemonic !== 'lea') {
return null;
}
const { base, disp } = insn.operands[1].value;
if (!(base === 'rdi' && disp < 0)) {
return null;
}
return disp;
}
export function ensureClassInitialized (env, classRef) {
}
class JvmMethodMangler {
constructor (methodId) {
this.methodId = methodId;
this.method = methodId.readPointer();
this.originalMethod = null;
this.newMethod = null;
this.resolved = null;
this.impl = null;
this.key = methodId.toString(16);
}
replace (impl, isInstanceMethod, argTypes, vm, api) {
const { key } = this;
const mangler = revertManglers.get(key);
if (mangler !== undefined) {
revertManglers.delete(key);
this.method = mangler.method;
this.originalMethod = mangler.originalMethod;
this.newMethod = mangler.newMethod;
this.resolved = mangler.resolved;
}
this.impl = impl;
replaceManglers.set(key, this);
ensureManglersScheduled(vm);
}
revert (vm) {
const { key } = this;
replaceManglers.delete(key);
revertManglers.set(key, this);
ensureManglersScheduled(vm);
}
resolveTarget (wrapper, isInstanceMethod, env, api) {
const { resolved, originalMethod, methodId } = this;
if (resolved !== null) {
return resolved;
}
if (originalMethod === null) {
return methodId;
}
const vip = originalMethod.oldMethod.vtableIndexPtr;
// Make old method final with nonvirtual_vtable_index = -2
// so that we don't need a vtable entry when calling old method.
vip.writeS32(-2);
const jmethodID = Memory.alloc(pointerSize);
jmethodID.writePointer(this.method);
this.resolved = jmethodID;
return jmethodID;
}
}
function ensureManglersScheduled (vm) {
if (!manglersScheduled) {
manglersScheduled = true;
Script.nextTick(doManglers, vm);
}
}
function doManglers (vm) {
const localReplaceManglers = new Map(replaceManglers);
const localRevertManglers = new Map(revertManglers);
replaceManglers.clear();
revertManglers.clear();
manglersScheduled = false;
vm.perform(env => {
const api = getApi();
const thread = api['JavaThread::thread_from_jni_environment'](env.handle);
let force = false;
withJvmThread(() => {
localReplaceManglers.forEach(mangler => {
const { method, originalMethod, impl, methodId, newMethod } = mangler;
if (originalMethod === null) {
mangler.originalMethod = fetchJvmMethod(method);
mangler.newMethod = nativeJvmMethod(method, impl, thread);
installJvmMethod(mangler.newMethod, methodId, thread);
} else {
api['Method::set_native_function'](newMethod.method, impl, 0);
}
});
localRevertManglers.forEach(mangler => {
const { originalMethod, methodId, newMethod } = mangler;
if (originalMethod !== null) {
revertJvmMethod(originalMethod);
const revert = originalMethod.oldMethod;
revert.oldMethod = newMethod;
installJvmMethod(revert, methodId, thread);
force = true;
}
});
});
if (force) {
forceSweep(env.handle);
}
});
}
function forceSweep (env) {
const {
fractions,
shouldSweep,
traversals,
'NMethodSweeper::sweep_code_cache': sweep,
'NMethodSweeper::sweep_in_progress': inProgress,
'NMethodSweeper::force_sweep': force,
JVM_Sleep: sleep
} = getApi();
if (force !== undefined) {
Thread.sleep(0.05);
force();
Thread.sleep(0.05);
force();
} else {
let trav = traversals.readS64();
const endTrav = trav + 2;
while (endTrav > trav) {
// Force a full sweep if already in progress.
fractions.writeS32(1);
sleep(env, NULL, 50);
// Check if current nmethod is set.
if (!inProgress()) {
// Force mark_active_nmethods on exit from safepoint.
withJvmThread(() => {
Thread.sleep(0.05);
});
}
const sweepNotAlreadyInProgress = shouldSweep.readU8() === 0;
if (sweepNotAlreadyInProgress) {
// Sanity check to not divide by 0.
fractions.writeS32(1);
sweep();
}
trav = traversals.readS64();
}
}
}
function withJvmThread (fn, fnPrologue, fnEpilogue) {
const {
execute,
vtable,
vtableSize,
doItOffset,
prologueOffset,
epilogueOffset
} = getJvmThreadSpec();
const vtableDup = Memory.dup(vtable, vtableSize);
const vmOperation = Memory.alloc(pointerSize * 25);
vmOperation.writePointer(vtableDup);
const doIt = new NativeCallback(fn, 'void', ['pointer']);
vtableDup.add(doItOffset).writePointer(doIt);
let prologue = null;
if (fnPrologue !== undefined) {
prologue = new NativeCallback(fnPrologue, 'int', ['pointer']);
vtableDup.add(prologueOffset).writePointer(prologue);
}
let epilogue = null;
if (fnEpilogue !== undefined) {
epilogue = new NativeCallback(fnEpilogue, 'void', ['pointer']);
vtableDup.add(epilogueOffset).writePointer(epilogue);
}
execute(vmOperation);
}
function _getJvmThreadSpec () {
const {
vtableRedefineClasses,
redefineClassesDoIt,
redefineClassesDoItPrologue,
redefineClassesDoItEpilogue,
redefineClassesOnError,
redefineClassesAllow,
redefineClassesDispose0,
redefineClassesDispose1,
'VMThread::execute': execute
} = getApi();
const vtablePtr = vtableRedefineClasses.add(2 * pointerSize);
const vtableSize = 15 * pointerSize;
const vtable = Memory.dup(vtablePtr, vtableSize);
const emptyCallback = new NativeCallback(() => {}, 'void', ['pointer']);
let doItOffset, prologueOffset, epilogueOffset;
for (let offset = 0; offset !== vtableSize; offset += pointerSize) {
const element = vtable.add(offset);
const value = element.readPointer();
if ((redefineClassesOnError !== undefined && value.equals(redefineClassesOnError)) ||
(redefineClassesDispose0 !== undefined && value.equals(redefineClassesDispose0)) ||
(redefineClassesDispose1 !== undefined && value.equals(redefineClassesDispose1))) {
element.writePointer(emptyCallback);
} else if (value.equals(redefineClassesDoIt)) {
doItOffset = offset;
} else if (value.equals(redefineClassesDoItPrologue)) {
prologueOffset = offset;
element.writePointer(redefineClassesAllow);
} else if (value.equals(redefineClassesDoItEpilogue)) {
epilogueOffset = offset;
element.writePointer(emptyCallback);
}
}
return {
execute,
emptyCallback,
vtable,
vtableSize,
doItOffset,
prologueOffset,
epilogueOffset
};
}
export function makeMethodMangler (methodId) {
return new JvmMethodMangler(methodId);
}
function installJvmMethod (method, methodId, thread) {
const { method: handle, oldMethod: old } = method;
const api = getApi();
// Replace position in methodsArray with new method.
method.methodsArray.add(method.methodIndex * pointerSize).writePointer(handle);
// Replace method handle in vtable
if (method.vtableIndex >= 0) {
method.vtable.add(method.vtableIndex * pointerSize).writePointer(handle);
}
// Replace jmethodID with new method.
methodId.writePointer(handle);
old.accessFlagsPtr.writeU32((old.accessFlags | JVM_ACC_IS_OLD | JVM_ACC_IS_OBSOLETE) >>> 0);
// Deoptimize dependent code.
const flushObs = api['OopMapCache::flush_obsolete_entries'];
if (flushObs !== undefined) {
const { oopMapCache } = method;
if (!oopMapCache.isNull()) {
flushObs(oopMapCache);
}
}
const mark = api['VM_RedefineClasses::mark_dependent_code'];
const flush = api['VM_RedefineClasses::flush_dependent_code'];
if (mark !== undefined) {
mark(NULL, method.instanceKlass);
flush();
} else {
flush(NULL, method.instanceKlass, thread);
}
const traceNamePrinted = Memory.alloc(1);
traceNamePrinted.writeU8(1);
api['ConstantPoolCache::adjust_method_entries'](method.cache, method.instanceKlass, traceNamePrinted);
const klassClosure = Memory.alloc(3 * pointerSize);
const doKlassPtr = Memory.alloc(pointerSize);
doKlassPtr.writePointer(api.doKlass);
klassClosure.writePointer(doKlassPtr);
klassClosure.add(pointerSize).writePointer(thread);
klassClosure.add(2 * pointerSize).writePointer(thread);
if (api.redefineClass !== undefined) {
api.redefineClass.writePointer(method.instanceKlass);
}
api['ClassLoaderDataGraph::classes_do'](klassClosure);
const rmtAdjustMethodEntries = api['ResolvedMethodTable::adjust_method_entries'];
if (rmtAdjustMethodEntries !== undefined) {
rmtAdjustMethodEntries(traceNamePrinted);
} else {
const { memberNames } = method;
if (!memberNames.isNull()) {
const mntAdjustMethodEntries = api['MemberNameTable::adjust_method_entries'];
if (mntAdjustMethodEntries !== undefined) {
mntAdjustMethodEntries(memberNames, method.instanceKlass, traceNamePrinted);
}
}
}
const clean = api['ClassLoaderDataGraph::clean_deallocate_lists'];
if (clean !== undefined) {
clean(0);
}
}
function nativeJvmMethod (method, impl, thread) {
const api = getApi();
const newMethod = fetchJvmMethod(method);
newMethod.constPtr.writePointer(newMethod.const);
const flags = (newMethod.accessFlags | JVM_ACC_NATIVE |
JVM_ACC_NOT_C2_COMPILABLE | JVM_ACC_NOT_C1_COMPILABLE |
JVM_ACC_NOT_C2_OSR_COMPILABLE) >>> 0;
newMethod.accessFlagsPtr.writeU32(flags);
newMethod.signatureHandler.writePointer(NULL);
newMethod.adapter.writePointer(NULL);
newMethod.i2iEntry.writePointer(NULL);
api['Method::clear_code'](newMethod.method);
newMethod.dataPtr.writePointer(NULL);
newMethod.countersPtr.writePointer(NULL);
newMethod.stackmapPtr.writePointer(NULL);
api['Method::clear_native_function'](newMethod.method);
api['Method::set_native_function'](newMethod.method, impl, 0);
api['Method::restore_unshareable_info'](newMethod.method, thread);
if (api.version >= 17) {
// Only certain JDK versions of restore_unshareable_info() call
// link_method(). Manually call if necessary.
const methodHandle = Memory.alloc(2 * pointerSize);
methodHandle.writePointer(newMethod.method);
methodHandle.add(pointerSize).writePointer(thread);
api['Method::link_method'](newMethod.method, methodHandle, thread);
}
return newMethod;
}
function fetchJvmMethod (method) {
const spec = getJvmMethodSpec();
const constMethod = method.add(spec.method.constMethodOffset).readPointer();
const constMethodSize = constMethod.add(spec.constMethod.sizeOffset).readS32() * pointerSize;
const newConstMethod = Memory.alloc(constMethodSize + spec.method.size);
Memory.copy(newConstMethod, constMethod, constMethodSize);
const newMethod = newConstMethod.add(constMethodSize);
Memory.copy(newMethod, method, spec.method.size);
const result = readJvmMethod(newMethod, newConstMethod, constMethodSize);
const oldMethod = readJvmMethod(method, constMethod, constMethodSize);
result.oldMethod = oldMethod;
return result;
}
function readJvmMethod (method, constMethod, constMethodSize) {
const api = getApi();
const spec = getJvmMethodSpec();
const constPtr = method.add(spec.method.constMethodOffset);
const dataPtr = method.add(spec.method.methodDataOffset);
const countersPtr = method.add(spec.method.methodCountersOffset);
const accessFlagsPtr = method.add(spec.method.accessFlagsOffset);
const accessFlags = accessFlagsPtr.readU32();
const adapter = spec.getAdapterPointer(method, constMethod);
const i2iEntry = method.add(spec.method.i2iEntryOffset);
const signatureHandler = method.add(spec.method.signatureHandlerOffset);
const constantPool = constMethod.add(spec.constMethod.constantPoolOffset).readPointer();
const stackmapPtr = constMethod.add(spec.constMethod.stackmapDataOffset);
const instanceKlass = constantPool.add(spec.constantPool.instanceKlassOffset).readPointer();
const cache = constantPool.add(spec.constantPool.cacheOffset).readPointer();
const instanceKlassSpec = getJvmInstanceKlassSpec();
const methods = instanceKlass.add(instanceKlassSpec.methodsOffset).readPointer();
const methodsCount = methods.readS32();
const methodsArray = methods.add(pointerSize);
const methodIndex = constMethod.add(spec.constMethod.methodIdnumOffset).readU16();
const vtableIndexPtr = method.add(spec.method.vtableIndexOffset);
const vtableIndex = vtableIndexPtr.readS32();
const vtable = instanceKlass.add(instanceKlassSpec.vtableOffset);
const oopMapCache = instanceKlass.add(instanceKlassSpec.oopMapCacheOffset).readPointer();
const memberNames = (api.version >= 10)
? instanceKlass.add(instanceKlassSpec.memberNamesOffset).readPointer()
: NULL;
return {
method,
methodSize: spec.method.size,
const: constMethod,
constSize: constMethodSize,
constPtr,
dataPtr,
countersPtr,
stackmapPtr,
instanceKlass,
methodsArray,
methodsCount,
methodIndex,
vtableIndex,
vtableIndexPtr,
vtable,
accessFlags,
accessFlagsPtr,
adapter,
i2iEntry,
signatureHandler,
memberNames,
cache,
oopMapCache
};
}
function revertJvmMethod (method) {
const { oldMethod: old } = method;
old.accessFlagsPtr.writeU32(old.accessFlags);
old.vtableIndexPtr.writeS32(old.vtableIndex);
}
function _getJvmMethodSpec () {
const api = getApi();
const { version } = api;
let adapterHandlerLocation;
if (version >= 17) {
adapterHandlerLocation = 'method:early';
} else if (version >= 9 && version <= 16) {
adapterHandlerLocation = 'const-method';
} else {
adapterHandlerLocation = 'method:late';
}
const isNative = 1;
const methodSize = api['Method::size'](isNative) * pointerSize;
const constMethodOffset = pointerSize;
const methodDataOffset = 2 * pointerSize;
const methodCountersOffset = 3 * pointerSize;
const adapterInMethodEarlyOffset = 4 * pointerSize;
const adapterInMethodEarlySize = (adapterHandlerLocation === 'method:early') ? pointerSize : 0;
const accessFlagsOffset = adapterInMethodEarlyOffset + adapterInMethodEarlySize;
const vtableIndexOffset = accessFlagsOffset + 4;
const i2iEntryOffset = vtableIndexOffset + 4 + 8;
const adapterInMethodLateOffset = i2iEntryOffset + pointerSize;
const adapterInMethodOffset = (adapterInMethodEarlySize !== 0) ? adapterInMethodEarlyOffset : adapterInMethodLateOffset;
const nativeFunctionOffset = methodSize - 2 * pointerSize;
const signatureHandlerOffset = methodSize - pointerSize;
const constantPoolOffset = 8;
const stackmapDataOffset = constantPoolOffset + pointerSize;
const adapterInConstMethodOffset = stackmapDataOffset + pointerSize;
const adapterInConstMethodSize = (adapterHandlerLocation === 'const-method') ? pointerSize : 0;
const constMethodSizeOffset = adapterInConstMethodOffset + adapterInConstMethodSize;
const methodIdnumOffset = constMethodSizeOffset + 0xe;
const cacheOffset = 2 * pointerSize;
const instanceKlassOffset = 3 * pointerSize;
const getAdapterPointer = (adapterInConstMethodSize !== 0)
? function (method, constMethod) {
return constMethod.add(adapterInConstMethodOffset);
}
: function (method, constMethod) {
return method.add(adapterInMethodOffset);
};
return {
getAdapterPointer,
method: {
size: methodSize,
constMethodOffset,
methodDataOffset,
methodCountersOffset,
accessFlagsOffset,
vtableIndexOffset,
i2iEntryOffset,
nativeFunctionOffset,
signatureHandlerOffset
},
constMethod: {
constantPoolOffset,
stackmapDataOffset,
sizeOffset: constMethodSizeOffset,
methodIdnumOffset
},
constantPool: {
cacheOffset,
instanceKlassOffset
}
};
}
const vtableOffsetParsers = {
x64: parseX64VTableOffset
};
function _getJvmInstanceKlassSpec () {
const { version: jvmVersion, createNewDefaultVtableIndices } = getApi();
const tryParse = vtableOffsetParsers[Process.arch];
if (tryParse === undefined) {
throw new Error(`Missing vtable offset parser for ${Process.arch}`);
}
const vtableOffset = parseInstructionsAt(createNewDefaultVtableIndices, tryParse, { limit: 32 });
if (vtableOffset === null) {
throw new Error('Unable to deduce vtable offset');
}
const oopMultiplier = ((jvmVersion >= 10 && jvmVersion <= 11) || jvmVersion >= 15) ? 17 : 18;
const methodsOffset = vtableOffset - (7 * pointerSize);
const memberNamesOffset = vtableOffset - (17 * pointerSize);
const oopMapCacheOffset = vtableOffset - (oopMultiplier * pointerSize);
return {
vtableOffset,
methodsOffset,
memberNamesOffset,
oopMapCacheOffset
};
}
function parseX64VTableOffset (insn) {
if (insn.mnemonic !== 'mov') {
return null;
}
const dst = insn.operands[0];
if (dst.type !== 'mem') {
return null;
}
const { value: dstValue } = dst;
if (dstValue.scale !== 1) {
return null;
}
const { disp } = dstValue;
if (disp < 0x100) {
return null;
}
const defaultVtableIndicesOffset = disp;
return defaultVtableIndicesOffset + 16;
}
export function deoptimizeEverything (vm, env) {
}