frida-java-bridge 6.0.1 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +1 -1
- package/lib/android.js +496 -71
- package/lib/class-factory.js +10 -3
- package/lib/class-model.js +1 -1
- package/lib/jvm.js +156 -92
- package/lib/jvmti.js +62 -0
- package/lib/machine-code.js +22 -0
- package/lib/vm.js +10 -2
- package/package.json +2 -1
package/index.js
CHANGED
|
@@ -233,7 +233,7 @@ class Runtime {
|
|
|
233
233
|
|
|
234
234
|
const visitClassLoaders = api['art::ClassLinker::VisitClassLoaders'];
|
|
235
235
|
if (visitClassLoaders === undefined) {
|
|
236
|
-
throw new Error('This API is only available on
|
|
236
|
+
throw new Error('This API is only available on Android >= 7.0');
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
const ClassLoader = factory.use('java.lang.ClassLoader');
|
package/lib/android.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
const makeCodeAllocator = require('./alloc');
|
|
2
|
+
const {
|
|
3
|
+
jvmtiVersion,
|
|
4
|
+
jvmtiCapabilities,
|
|
5
|
+
EnvJvmti
|
|
6
|
+
} = require('./jvmti');
|
|
7
|
+
const { parseInstructionsAt } = require('./machine-code');
|
|
2
8
|
const memoize = require('./memoize');
|
|
3
|
-
const { checkJniResult } = require('./result');
|
|
9
|
+
const { checkJniResult, JNI_OK } = require('./result');
|
|
4
10
|
const VM = require('./vm');
|
|
5
11
|
|
|
6
12
|
const jsizeSize = 4;
|
|
@@ -99,6 +105,7 @@ const artThreadStateTransitions = {};
|
|
|
99
105
|
let cachedApi = null;
|
|
100
106
|
let MethodMangler = null;
|
|
101
107
|
let artController = null;
|
|
108
|
+
const inlineHooks = [];
|
|
102
109
|
const patchedClasses = new Map();
|
|
103
110
|
const artQuickInterceptors = [];
|
|
104
111
|
let thunkPage = null;
|
|
@@ -330,6 +337,7 @@ function _getApi () {
|
|
|
330
337
|
'_ZN3art3Dbg13ConfigureJdwpERKNS_4JDWP11JdwpOptionsE',
|
|
331
338
|
'_ZN3art31InternalDebuggerControlCallback13StartDebuggerEv',
|
|
332
339
|
'_ZN3art3Dbg15gDebuggerActiveE',
|
|
340
|
+
'_ZN3art15instrumentation15Instrumentation20EnableDeoptimizationEv',
|
|
333
341
|
'_ZN3art15instrumentation15Instrumentation20DeoptimizeEverythingEPKc',
|
|
334
342
|
'_ZN3art15instrumentation15Instrumentation20DeoptimizeEverythingEv',
|
|
335
343
|
'_ZN3art7Runtime19DeoptimizeBootImageEv',
|
|
@@ -474,6 +482,16 @@ function _getApi () {
|
|
|
474
482
|
artController = makeArtController(vm);
|
|
475
483
|
|
|
476
484
|
fixupArtQuickDeliverExceptionBug(temporaryApi);
|
|
485
|
+
|
|
486
|
+
let cachedJvmti = null;
|
|
487
|
+
Object.defineProperty(temporaryApi, 'jvmti', {
|
|
488
|
+
get () {
|
|
489
|
+
if (cachedJvmti === null) {
|
|
490
|
+
cachedJvmti = [tryGetEnvJvmti(vm, this.artRuntime)];
|
|
491
|
+
}
|
|
492
|
+
return cachedJvmti[0];
|
|
493
|
+
}
|
|
494
|
+
});
|
|
477
495
|
}
|
|
478
496
|
|
|
479
497
|
const cxxImports = Module.enumerateImports(vmModule.path)
|
|
@@ -490,6 +508,39 @@ function _getApi () {
|
|
|
490
508
|
return temporaryApi;
|
|
491
509
|
}
|
|
492
510
|
|
|
511
|
+
function tryGetEnvJvmti (vm, runtime) {
|
|
512
|
+
let env = null;
|
|
513
|
+
|
|
514
|
+
vm.perform(() => {
|
|
515
|
+
const ensurePluginLoaded = new NativeFunction(
|
|
516
|
+
Module.getExportByName('libart.so', '_ZN3art7Runtime18EnsurePluginLoadedEPKcPNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEE'),
|
|
517
|
+
'bool',
|
|
518
|
+
['pointer', 'pointer', 'pointer']);
|
|
519
|
+
const errorPtr = Memory.alloc(pointerSize);
|
|
520
|
+
const success = ensurePluginLoaded(runtime, Memory.allocUtf8String('libopenjdkjvmti.so'), errorPtr);
|
|
521
|
+
if (!success) {
|
|
522
|
+
// FIXME: Avoid leaking error
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const kArtTiVersion = jvmtiVersion.v1_2 | 0x40000000;
|
|
527
|
+
const handle = vm.tryGetEnvHandle(kArtTiVersion);
|
|
528
|
+
if (handle === null) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
env = new EnvJvmti(handle, vm);
|
|
532
|
+
|
|
533
|
+
const capaBuf = Memory.alloc(8);
|
|
534
|
+
capaBuf.writeU64(jvmtiCapabilities.canTagObjects);
|
|
535
|
+
const result = env.addCapabilities(capaBuf);
|
|
536
|
+
if (result !== JNI_OK) {
|
|
537
|
+
env = null;
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
return env;
|
|
542
|
+
}
|
|
543
|
+
|
|
493
544
|
function ensureClassInitialized (env, classRef) {
|
|
494
545
|
const api = getApi();
|
|
495
546
|
if (api.flavor !== 'art') {
|
|
@@ -530,6 +581,7 @@ function _getArtRuntimeSpec (api) {
|
|
|
530
581
|
* InternTable* intern_table_; <--/
|
|
531
582
|
* ClassLinker* class_linker_; <-/
|
|
532
583
|
* SignalCatcher* signal_catcher_;
|
|
584
|
+
* SmallIrtAllocator* small_irt_allocator_; <------------ API level >= 33 or Android Tiramisu Developer Preview
|
|
533
585
|
* std::unique_ptr<jni::JniIdManager> jni_id_manager_; <- API level >= 30 or Android R Developer Preview
|
|
534
586
|
* bool use_tombstoned_traces_; <-------------------- API level 27/28
|
|
535
587
|
* std::string stack_trace_file_; <-------------------- API level <= 28
|
|
@@ -553,7 +605,10 @@ function _getArtRuntimeSpec (api) {
|
|
|
553
605
|
if (value.equals(vm)) {
|
|
554
606
|
let classLinkerOffset = null;
|
|
555
607
|
let jniIdManagerOffset = null;
|
|
556
|
-
if (apiLevel >=
|
|
608
|
+
if (apiLevel >= 33 || getAndroidCodename() === 'Tiramisu') {
|
|
609
|
+
classLinkerOffset = offset - (4 * pointerSize);
|
|
610
|
+
jniIdManagerOffset = offset - pointerSize;
|
|
611
|
+
} else if (apiLevel >= 30 || getAndroidCodename() === 'R') {
|
|
557
612
|
classLinkerOffset = offset - (3 * pointerSize);
|
|
558
613
|
jniIdManagerOffset = offset - pointerSize;
|
|
559
614
|
} else if (apiLevel >= 29) {
|
|
@@ -593,7 +648,7 @@ function _getArtRuntimeSpec (api) {
|
|
|
593
648
|
throw new Error('Unable to determine Runtime field offsets');
|
|
594
649
|
}
|
|
595
650
|
|
|
596
|
-
spec.offset.instrumentation = tryDetectInstrumentationOffset();
|
|
651
|
+
spec.offset.instrumentation = tryDetectInstrumentationOffset(api);
|
|
597
652
|
spec.offset.jniIdsIndirection = tryDetectJniIdsIndirectionOffset();
|
|
598
653
|
|
|
599
654
|
return spec;
|
|
@@ -606,43 +661,26 @@ const instrumentationOffsetParsers = {
|
|
|
606
661
|
arm64: parseArm64InstrumentationOffset
|
|
607
662
|
};
|
|
608
663
|
|
|
609
|
-
function tryDetectInstrumentationOffset () {
|
|
610
|
-
|
|
611
|
-
if (
|
|
664
|
+
function tryDetectInstrumentationOffset (api) {
|
|
665
|
+
const impl = api['art::Runtime::DeoptimizeBootImage'];
|
|
666
|
+
if (impl === undefined) {
|
|
612
667
|
return null;
|
|
613
668
|
}
|
|
614
669
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
for (let i = 0; i !== 20; i++) {
|
|
618
|
-
const insn = Instruction.parse(cur);
|
|
619
|
-
|
|
620
|
-
const offset = tryParse(insn);
|
|
621
|
-
if (offset !== null) {
|
|
622
|
-
return offset;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
cur = insn.next;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
return null;
|
|
670
|
+
return parseInstructionsAt(impl, instrumentationOffsetParsers[Process.arch], { limit: 30 });
|
|
629
671
|
}
|
|
630
672
|
|
|
631
673
|
function parsex86InstrumentationOffset (insn) {
|
|
632
|
-
|
|
633
|
-
if (mnemonic !== 'mov' && mnemonic !== 'add') {
|
|
674
|
+
if (insn.mnemonic !== 'lea') {
|
|
634
675
|
return null;
|
|
635
676
|
}
|
|
636
677
|
|
|
637
|
-
const
|
|
638
|
-
if (
|
|
639
|
-
return null;
|
|
640
|
-
}
|
|
641
|
-
if (op1.value < 0x100 || op1.value > 0x400) {
|
|
678
|
+
const offset = insn.operands[1].value.disp;
|
|
679
|
+
if (offset < 0x100 || offset > 0x400) {
|
|
642
680
|
return null;
|
|
643
681
|
}
|
|
644
682
|
|
|
645
|
-
return
|
|
683
|
+
return offset;
|
|
646
684
|
}
|
|
647
685
|
|
|
648
686
|
function parseArmInstrumentationOffset (insn) {
|
|
@@ -673,12 +711,21 @@ function parseArm64InstrumentationOffset (insn) {
|
|
|
673
711
|
return null;
|
|
674
712
|
}
|
|
675
713
|
|
|
714
|
+
if (ops[0].value === 'sp' || ops[1].value === 'sp') {
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
|
|
676
718
|
const op2 = ops[2];
|
|
677
719
|
if (op2.type !== 'imm') {
|
|
678
720
|
return null;
|
|
679
721
|
}
|
|
680
722
|
|
|
681
|
-
|
|
723
|
+
const offset = op2.value.valueOf();
|
|
724
|
+
if (offset < 0x100 || offset > 0x400) {
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return offset;
|
|
682
729
|
}
|
|
683
730
|
|
|
684
731
|
const jniIdsIndirectionOffsetParsers = {
|
|
@@ -689,27 +736,17 @@ const jniIdsIndirectionOffsetParsers = {
|
|
|
689
736
|
};
|
|
690
737
|
|
|
691
738
|
function tryDetectJniIdsIndirectionOffset () {
|
|
692
|
-
|
|
693
|
-
if (
|
|
739
|
+
const impl = Module.findExportByName('libart.so', '_ZN3art7Runtime12SetJniIdTypeENS_9JniIdTypeE');
|
|
740
|
+
if (impl === null) {
|
|
694
741
|
return null;
|
|
695
742
|
}
|
|
696
743
|
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
for (let i = 0; i !== 20; i++) {
|
|
701
|
-
const insn = Instruction.parse(cur);
|
|
702
|
-
|
|
703
|
-
const offset = tryParse(insn, prevInsn);
|
|
704
|
-
if (offset !== null) {
|
|
705
|
-
return offset;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
cur = insn.next;
|
|
709
|
-
prevInsn = insn;
|
|
744
|
+
const offset = parseInstructionsAt(impl, jniIdsIndirectionOffsetParsers[Process.arch], { limit: 20 });
|
|
745
|
+
if (offset === null) {
|
|
746
|
+
throw new Error('Unable to determine Runtime.jni_ids_indirection_ offset');
|
|
710
747
|
}
|
|
711
748
|
|
|
712
|
-
|
|
749
|
+
return offset;
|
|
713
750
|
}
|
|
714
751
|
|
|
715
752
|
function parsex86JniIdsIndirectionOffset (insn) {
|
|
@@ -902,7 +939,7 @@ function _getArtMethodSpec (vm) {
|
|
|
902
939
|
|
|
903
940
|
vm.perform(env => {
|
|
904
941
|
const process = env.findClass('android/os/Process');
|
|
905
|
-
const
|
|
942
|
+
const getElapsedCpuTime = unwrapMethodId(env.getStaticMethodId(process, 'getElapsedCpuTime', '()J'));
|
|
906
943
|
env.deleteLocalRef(process);
|
|
907
944
|
|
|
908
945
|
const runtimeModule = Process.getModuleByName('libandroid_runtime.so');
|
|
@@ -914,13 +951,13 @@ function _getArtMethodSpec (vm) {
|
|
|
914
951
|
const entrypointFieldSize = (apiLevel <= 21) ? 8 : pointerSize;
|
|
915
952
|
|
|
916
953
|
const expectedAccessFlags = kAccPublic | kAccStatic | kAccFinal | kAccNative;
|
|
917
|
-
const relevantAccessFlagsMask = ~(kAccPublicApi | kAccNterpInvokeFastPathFlag) >>> 0;
|
|
954
|
+
const relevantAccessFlagsMask = ~(kAccFastInterpreterToInterpreterInvoke | kAccPublicApi | kAccNterpInvokeFastPathFlag) >>> 0;
|
|
918
955
|
|
|
919
956
|
let jniCodeOffset = null;
|
|
920
957
|
let accessFlagsOffset = null;
|
|
921
958
|
let remaining = 2;
|
|
922
959
|
for (let offset = 0; offset !== 64 && remaining !== 0; offset += 4) {
|
|
923
|
-
const field =
|
|
960
|
+
const field = getElapsedCpuTime.add(offset);
|
|
924
961
|
|
|
925
962
|
if (jniCodeOffset === null) {
|
|
926
963
|
const address = field.readPointer();
|
|
@@ -1711,6 +1748,7 @@ on_leave_gc_concurrent_copying_copying_phase (GumInvocationContext * ic)
|
|
|
1711
1748
|
return {
|
|
1712
1749
|
handle: cm,
|
|
1713
1750
|
replacedMethods: {
|
|
1751
|
+
isReplacement: new NativeFunction(cm.is_replacement_method, 'bool', ['pointer'], fastOptions),
|
|
1714
1752
|
get: new NativeFunction(cm.get_replacement_method, 'pointer', ['pointer'], fastOptions),
|
|
1715
1753
|
set: new NativeFunction(cm.set_replacement_method, 'void', ['pointer', 'pointer'], fastOptions),
|
|
1716
1754
|
delete: new NativeFunction(cm.delete_replacement_method, 'void', ['pointer'], fastOptions),
|
|
@@ -1785,18 +1823,20 @@ function ensureArtKnowsHowToHandleReplacementMethods (vm) {
|
|
|
1785
1823
|
}
|
|
1786
1824
|
taughtArtAboutReplacementMethods = true;
|
|
1787
1825
|
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1826
|
+
if (!maybeInstrumentGetOatQuickMethodHeaderInlineCopies()) {
|
|
1827
|
+
const { getOatQuickMethodHeaderImpl } = artController;
|
|
1828
|
+
if (getOatQuickMethodHeaderImpl === null) {
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1792
1831
|
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1832
|
+
try {
|
|
1833
|
+
Interceptor.replace(getOatQuickMethodHeaderImpl, artController.hooks.ArtMethod.getOatQuickMethodHeader);
|
|
1834
|
+
} catch (e) {
|
|
1835
|
+
/*
|
|
1836
|
+
* Already replaced by another script. For now we don't support replacing methods from multiple scripts,
|
|
1837
|
+
* but we'll allow users to try it if they're feeling adventurous.
|
|
1838
|
+
*/
|
|
1839
|
+
}
|
|
1800
1840
|
}
|
|
1801
1841
|
|
|
1802
1842
|
const apiLevel = getAndroidApiLevel();
|
|
@@ -1813,6 +1853,381 @@ function ensureArtKnowsHowToHandleReplacementMethods (vm) {
|
|
|
1813
1853
|
}
|
|
1814
1854
|
}
|
|
1815
1855
|
|
|
1856
|
+
const artGetOatQuickMethodHeaderInlinedCopyHandler = {
|
|
1857
|
+
arm: {
|
|
1858
|
+
signatures: [
|
|
1859
|
+
{
|
|
1860
|
+
pattern: [
|
|
1861
|
+
'b0 68', // ldr r0, [r6, #8]
|
|
1862
|
+
'01 30', // adds r0, #1
|
|
1863
|
+
'0c d0', // beq #0x16fcd4
|
|
1864
|
+
'1b 98', // ldr r0, [sp, #0x6c]
|
|
1865
|
+
':',
|
|
1866
|
+
'c0 ff',
|
|
1867
|
+
'c0 ff',
|
|
1868
|
+
'00 ff',
|
|
1869
|
+
'00 2f'
|
|
1870
|
+
],
|
|
1871
|
+
validateMatch: validateGetOatQuickMethodHeaderInlinedMatchArm
|
|
1872
|
+
},
|
|
1873
|
+
{
|
|
1874
|
+
pattern: [
|
|
1875
|
+
'd8 f8 08 00', // ldr r0, [r8, #8]
|
|
1876
|
+
'01 30', // adds r0, #1
|
|
1877
|
+
'0c d0', // beq #0x16fcd4
|
|
1878
|
+
'1b 98', // ldr r0, [sp, #0x6c]
|
|
1879
|
+
':',
|
|
1880
|
+
'f0 ff ff 0f',
|
|
1881
|
+
'ff ff',
|
|
1882
|
+
'00 ff',
|
|
1883
|
+
'00 2f'
|
|
1884
|
+
],
|
|
1885
|
+
validateMatch: validateGetOatQuickMethodHeaderInlinedMatchArm
|
|
1886
|
+
},
|
|
1887
|
+
{
|
|
1888
|
+
pattern: [
|
|
1889
|
+
'b0 68', // ldr r0, [r6, #8]
|
|
1890
|
+
'01 30', // adds r0, #1
|
|
1891
|
+
'40 f0 c3 80', // bne #0x203bf0
|
|
1892
|
+
'00 25', // movs r5, #0
|
|
1893
|
+
':',
|
|
1894
|
+
'c0 ff',
|
|
1895
|
+
'c0 ff',
|
|
1896
|
+
'c0 fb 00 d0',
|
|
1897
|
+
'ff f8'
|
|
1898
|
+
],
|
|
1899
|
+
validateMatch: validateGetOatQuickMethodHeaderInlinedMatchArm
|
|
1900
|
+
}
|
|
1901
|
+
],
|
|
1902
|
+
instrument: instrumentGetOatQuickMethodHeaderInlinedCopyArm
|
|
1903
|
+
},
|
|
1904
|
+
arm64: {
|
|
1905
|
+
signatures: [
|
|
1906
|
+
{
|
|
1907
|
+
pattern: [
|
|
1908
|
+
/* e8 */ '0a 40 b9', // ldr w8, [x23, #0x8]
|
|
1909
|
+
'1f 05 00 31', // cmn w8, #0x1
|
|
1910
|
+
'40 01 00 54', // b.eq 0x2e4204
|
|
1911
|
+
'88 39 00 f0', // adrp x8, 0xa17000
|
|
1912
|
+
':',
|
|
1913
|
+
/* 00 */ 'fc ff ff',
|
|
1914
|
+
'1f fc ff ff',
|
|
1915
|
+
'1f 00 00 ff',
|
|
1916
|
+
'00 00 00 9f'
|
|
1917
|
+
],
|
|
1918
|
+
offset: 1,
|
|
1919
|
+
validateMatch: validateGetOatQuickMethodHeaderInlinedMatchArm64
|
|
1920
|
+
},
|
|
1921
|
+
{
|
|
1922
|
+
pattern: [
|
|
1923
|
+
/* e8 */ '0a 40 b9', // ldr w8, [x23, #0x8]
|
|
1924
|
+
'1f 05 00 31', // cmn w8, #0x1
|
|
1925
|
+
'01 34 00 54', // b.ne 0x3d8e50
|
|
1926
|
+
'e0 03 1f aa', // mov x0, xzr
|
|
1927
|
+
':',
|
|
1928
|
+
/* 00 */ 'fc ff ff',
|
|
1929
|
+
'1f fc ff ff',
|
|
1930
|
+
'1f 00 00 ff',
|
|
1931
|
+
'e0 ff ff ff'
|
|
1932
|
+
],
|
|
1933
|
+
offset: 1,
|
|
1934
|
+
validateMatch: validateGetOatQuickMethodHeaderInlinedMatchArm64
|
|
1935
|
+
}
|
|
1936
|
+
],
|
|
1937
|
+
instrument: instrumentGetOatQuickMethodHeaderInlinedCopyArm64
|
|
1938
|
+
}
|
|
1939
|
+
};
|
|
1940
|
+
|
|
1941
|
+
function validateGetOatQuickMethodHeaderInlinedMatchArm ({ address, size }) {
|
|
1942
|
+
const ldr = Instruction.parse(address.or(1));
|
|
1943
|
+
const [ldrDst, ldrSrc] = ldr.operands;
|
|
1944
|
+
const methodReg = ldrSrc.value.base;
|
|
1945
|
+
const scratchReg = ldrDst.value;
|
|
1946
|
+
|
|
1947
|
+
const branch = Instruction.parse(ldr.next.add(2));
|
|
1948
|
+
const targetWhenTrue = ptr(branch.operands[0].value);
|
|
1949
|
+
const targetWhenFalse = branch.address.add(branch.size);
|
|
1950
|
+
|
|
1951
|
+
let targetWhenRegularMethod, targetWhenRuntimeMethod;
|
|
1952
|
+
if (branch.mnemonic === 'beq') {
|
|
1953
|
+
targetWhenRegularMethod = targetWhenFalse;
|
|
1954
|
+
targetWhenRuntimeMethod = targetWhenTrue;
|
|
1955
|
+
} else {
|
|
1956
|
+
targetWhenRegularMethod = targetWhenTrue;
|
|
1957
|
+
targetWhenRuntimeMethod = targetWhenFalse;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
return parseInstructionsAt(targetWhenRegularMethod.or(1), tryParse, { limit: 3 });
|
|
1961
|
+
|
|
1962
|
+
function tryParse (insn) {
|
|
1963
|
+
const { mnemonic } = insn;
|
|
1964
|
+
if (!(mnemonic === 'ldr' || mnemonic === 'ldr.w')) {
|
|
1965
|
+
return null;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
const { base, disp } = insn.operands[1].value;
|
|
1969
|
+
if (!(base === methodReg && disp === 0x14)) {
|
|
1970
|
+
return null;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
return {
|
|
1974
|
+
methodReg,
|
|
1975
|
+
scratchReg,
|
|
1976
|
+
target: {
|
|
1977
|
+
whenTrue: targetWhenTrue,
|
|
1978
|
+
whenRegularMethod: targetWhenRegularMethod,
|
|
1979
|
+
whenRuntimeMethod: targetWhenRuntimeMethod
|
|
1980
|
+
}
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
function validateGetOatQuickMethodHeaderInlinedMatchArm64 ({ address, size }) {
|
|
1986
|
+
const [ldrDst, ldrSrc] = Instruction.parse(address).operands;
|
|
1987
|
+
const methodReg = ldrSrc.value.base;
|
|
1988
|
+
const scratchReg = 'x' + ldrDst.value.substring(1);
|
|
1989
|
+
|
|
1990
|
+
const branch = Instruction.parse(address.add(8));
|
|
1991
|
+
const targetWhenTrue = ptr(branch.operands[0].value);
|
|
1992
|
+
const targetWhenFalse = address.add(12);
|
|
1993
|
+
|
|
1994
|
+
let targetWhenRegularMethod, targetWhenRuntimeMethod;
|
|
1995
|
+
if (branch.mnemonic === 'b.eq') {
|
|
1996
|
+
targetWhenRegularMethod = targetWhenFalse;
|
|
1997
|
+
targetWhenRuntimeMethod = targetWhenTrue;
|
|
1998
|
+
} else {
|
|
1999
|
+
targetWhenRegularMethod = targetWhenTrue;
|
|
2000
|
+
targetWhenRuntimeMethod = targetWhenFalse;
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
return parseInstructionsAt(targetWhenRegularMethod, tryParse, { limit: 3 });
|
|
2004
|
+
|
|
2005
|
+
function tryParse (insn) {
|
|
2006
|
+
if (insn.mnemonic !== 'ldr') {
|
|
2007
|
+
return null;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
const { base, disp } = insn.operands[1].value;
|
|
2011
|
+
if (!(base === methodReg && disp === 0x18)) {
|
|
2012
|
+
return null;
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
return {
|
|
2016
|
+
methodReg,
|
|
2017
|
+
scratchReg,
|
|
2018
|
+
target: {
|
|
2019
|
+
whenTrue: targetWhenTrue,
|
|
2020
|
+
whenRegularMethod: targetWhenRegularMethod,
|
|
2021
|
+
whenRuntimeMethod: targetWhenRuntimeMethod
|
|
2022
|
+
}
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
function maybeInstrumentGetOatQuickMethodHeaderInlineCopies () {
|
|
2028
|
+
if (getAndroidApiLevel() < 31) {
|
|
2029
|
+
return false;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
const handler = artGetOatQuickMethodHeaderInlinedCopyHandler[Process.arch];
|
|
2033
|
+
if (handler === undefined) {
|
|
2034
|
+
// Not needed on x86 and x64, at least not for now...
|
|
2035
|
+
return false;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
const signatures = handler.signatures.map(({ pattern, offset = 0, validateMatch = returnEmptyObject }) => {
|
|
2039
|
+
return {
|
|
2040
|
+
pattern: new MatchPattern(pattern.join('')),
|
|
2041
|
+
offset,
|
|
2042
|
+
validateMatch
|
|
2043
|
+
};
|
|
2044
|
+
});
|
|
2045
|
+
|
|
2046
|
+
const impls = [];
|
|
2047
|
+
for (const { base, size } of getApi().module.enumerateRanges('--x')) {
|
|
2048
|
+
for (const { pattern, offset, validateMatch } of signatures) {
|
|
2049
|
+
const matches = Memory.scanSync(base, size, pattern)
|
|
2050
|
+
.map(({ address, size }) => {
|
|
2051
|
+
return { address: address.sub(offset), size: size + offset };
|
|
2052
|
+
})
|
|
2053
|
+
.filter(match => {
|
|
2054
|
+
const validationResult = validateMatch(match);
|
|
2055
|
+
if (validationResult === null) {
|
|
2056
|
+
return false;
|
|
2057
|
+
}
|
|
2058
|
+
match.validationResult = validationResult;
|
|
2059
|
+
return true;
|
|
2060
|
+
});
|
|
2061
|
+
impls.push(...matches);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
impls.forEach(handler.instrument);
|
|
2066
|
+
|
|
2067
|
+
return true;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
function returnEmptyObject () {
|
|
2071
|
+
return {};
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
class InlineHook {
|
|
2075
|
+
constructor (address, size, trampoline) {
|
|
2076
|
+
this.address = address;
|
|
2077
|
+
this.size = size;
|
|
2078
|
+
this.originalCode = address.readByteArray(size);
|
|
2079
|
+
this.trampoline = trampoline;
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
revert () {
|
|
2083
|
+
Memory.patchCode(this.address, this.size, code => {
|
|
2084
|
+
code.writeByteArray(this.originalCode);
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
function instrumentGetOatQuickMethodHeaderInlinedCopyArm ({ address, size, validationResult }) {
|
|
2090
|
+
const { methodReg, target } = validationResult;
|
|
2091
|
+
|
|
2092
|
+
const trampoline = Memory.alloc(Process.pageSize);
|
|
2093
|
+
let redirectCapacity = size;
|
|
2094
|
+
|
|
2095
|
+
Memory.patchCode(trampoline, 256, code => {
|
|
2096
|
+
const writer = new ThumbWriter(code, { pc: trampoline });
|
|
2097
|
+
|
|
2098
|
+
const relocator = new ThumbRelocator(address, writer);
|
|
2099
|
+
for (let i = 0; i !== 2; i++) {
|
|
2100
|
+
relocator.readOne();
|
|
2101
|
+
}
|
|
2102
|
+
relocator.writeAll();
|
|
2103
|
+
|
|
2104
|
+
relocator.readOne();
|
|
2105
|
+
relocator.skipOne();
|
|
2106
|
+
writer.putBCondLabel('eq', 'runtime_or_replacement_method');
|
|
2107
|
+
|
|
2108
|
+
const vpushFpRegs = [0x2d, 0xed, 0x10, 0x0a]; /* vpush {s0-s15} */
|
|
2109
|
+
writer.putBytes(vpushFpRegs);
|
|
2110
|
+
|
|
2111
|
+
const savedRegs = ['r0', 'r1', 'r2', 'r3'];
|
|
2112
|
+
writer.putPushRegs(savedRegs);
|
|
2113
|
+
|
|
2114
|
+
writer.putCallAddressWithArguments(artController.replacedMethods.isReplacement, [methodReg]);
|
|
2115
|
+
writer.putCmpRegImm('r0', 0);
|
|
2116
|
+
|
|
2117
|
+
writer.putPopRegs(savedRegs);
|
|
2118
|
+
|
|
2119
|
+
const vpopFpRegs = [0xbd, 0xec, 0x10, 0x0a]; /* vpop {s0-s15} */
|
|
2120
|
+
writer.putBytes(vpopFpRegs);
|
|
2121
|
+
|
|
2122
|
+
writer.putBCondLabel('ne', 'runtime_or_replacement_method');
|
|
2123
|
+
writer.putBLabel('regular_method');
|
|
2124
|
+
|
|
2125
|
+
relocator.readOne();
|
|
2126
|
+
|
|
2127
|
+
const tailIsRegular = relocator.input.address.equals(target.whenRegularMethod);
|
|
2128
|
+
|
|
2129
|
+
writer.putLabel(tailIsRegular ? 'regular_method' : 'runtime_or_replacement_method');
|
|
2130
|
+
relocator.writeOne();
|
|
2131
|
+
while (redirectCapacity < 10) {
|
|
2132
|
+
const offset = relocator.readOne();
|
|
2133
|
+
if (offset === 0) {
|
|
2134
|
+
redirectCapacity = 10;
|
|
2135
|
+
break;
|
|
2136
|
+
}
|
|
2137
|
+
redirectCapacity = offset;
|
|
2138
|
+
}
|
|
2139
|
+
relocator.writeAll();
|
|
2140
|
+
writer.putBranchAddress(address.add(redirectCapacity + 1));
|
|
2141
|
+
|
|
2142
|
+
writer.putLabel(tailIsRegular ? 'runtime_or_replacement_method' : 'regular_method');
|
|
2143
|
+
writer.putBranchAddress(target.whenTrue);
|
|
2144
|
+
|
|
2145
|
+
writer.flush();
|
|
2146
|
+
});
|
|
2147
|
+
|
|
2148
|
+
inlineHooks.push(new InlineHook(address, redirectCapacity, trampoline));
|
|
2149
|
+
|
|
2150
|
+
Memory.patchCode(address, redirectCapacity, code => {
|
|
2151
|
+
const writer = new ThumbWriter(code, { pc: address });
|
|
2152
|
+
writer.putLdrRegAddress('pc', trampoline.or(1));
|
|
2153
|
+
writer.flush();
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
function instrumentGetOatQuickMethodHeaderInlinedCopyArm64 ({ address, size, validationResult }) {
|
|
2158
|
+
const { methodReg, scratchReg, target } = validationResult;
|
|
2159
|
+
|
|
2160
|
+
const trampoline = Memory.alloc(Process.pageSize);
|
|
2161
|
+
|
|
2162
|
+
Memory.patchCode(trampoline, 256, code => {
|
|
2163
|
+
const writer = new Arm64Writer(code, { pc: trampoline });
|
|
2164
|
+
|
|
2165
|
+
const relocator = new Arm64Relocator(address, writer);
|
|
2166
|
+
for (let i = 0; i !== 2; i++) {
|
|
2167
|
+
relocator.readOne();
|
|
2168
|
+
}
|
|
2169
|
+
relocator.writeAll();
|
|
2170
|
+
|
|
2171
|
+
relocator.readOne();
|
|
2172
|
+
relocator.skipOne();
|
|
2173
|
+
writer.putBCondLabel('eq', 'runtime_or_replacement_method');
|
|
2174
|
+
|
|
2175
|
+
const savedRegs = [
|
|
2176
|
+
'd0', 'd1',
|
|
2177
|
+
'd2', 'd3',
|
|
2178
|
+
'd4', 'd5',
|
|
2179
|
+
'd6', 'd7',
|
|
2180
|
+
'x0', 'x1',
|
|
2181
|
+
'x2', 'x3',
|
|
2182
|
+
'x4', 'x5',
|
|
2183
|
+
'x6', 'x7',
|
|
2184
|
+
'x8', 'x9',
|
|
2185
|
+
'x10', 'x11',
|
|
2186
|
+
'x12', 'x13',
|
|
2187
|
+
'x14', 'x15',
|
|
2188
|
+
'x16', 'x17'
|
|
2189
|
+
];
|
|
2190
|
+
const numSavedRegs = savedRegs.length;
|
|
2191
|
+
|
|
2192
|
+
for (let i = 0; i !== numSavedRegs; i += 2) {
|
|
2193
|
+
writer.putPushRegReg(savedRegs[i], savedRegs[i + 1]);
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
writer.putCallAddressWithArguments(artController.replacedMethods.isReplacement, [methodReg]);
|
|
2197
|
+
writer.putCmpRegReg('x0', 'xzr');
|
|
2198
|
+
|
|
2199
|
+
for (let i = numSavedRegs - 2; i >= 0; i -= 2) {
|
|
2200
|
+
writer.putPopRegReg(savedRegs[i], savedRegs[i + 1]);
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
writer.putBCondLabel('ne', 'runtime_or_replacement_method');
|
|
2204
|
+
writer.putBLabel('regular_method');
|
|
2205
|
+
|
|
2206
|
+
relocator.readOne();
|
|
2207
|
+
const tailInstruction = relocator.input;
|
|
2208
|
+
|
|
2209
|
+
const tailIsRegular = tailInstruction.address.equals(target.whenRegularMethod);
|
|
2210
|
+
|
|
2211
|
+
writer.putLabel(tailIsRegular ? 'regular_method' : 'runtime_or_replacement_method');
|
|
2212
|
+
relocator.writeOne();
|
|
2213
|
+
writer.putBranchAddress(tailInstruction.next);
|
|
2214
|
+
|
|
2215
|
+
writer.putLabel(tailIsRegular ? 'runtime_or_replacement_method' : 'regular_method');
|
|
2216
|
+
writer.putBranchAddress(target.whenTrue);
|
|
2217
|
+
|
|
2218
|
+
writer.flush();
|
|
2219
|
+
});
|
|
2220
|
+
|
|
2221
|
+
inlineHooks.push(new InlineHook(address, size, trampoline));
|
|
2222
|
+
|
|
2223
|
+
Memory.patchCode(address, size, code => {
|
|
2224
|
+
const writer = new Arm64Writer(code, { pc: address });
|
|
2225
|
+
writer.putLdrRegAddress(scratchReg, trampoline);
|
|
2226
|
+
writer.putBrReg(scratchReg);
|
|
2227
|
+
writer.flush();
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
2230
|
+
|
|
1816
2231
|
function makeMethodMangler (methodId) {
|
|
1817
2232
|
return new MethodMangler(methodId);
|
|
1818
2233
|
}
|
|
@@ -1937,8 +2352,6 @@ extern void translate_location (ArtMethod * method, guint32 pc, const gchar ** s
|
|
|
1937
2352
|
extern void cxx_delete (void * mem);
|
|
1938
2353
|
extern unsigned long strtoul (const char * str, char ** endptr, int base);
|
|
1939
2354
|
|
|
1940
|
-
extern void _frida_log (const gchar * message);
|
|
1941
|
-
|
|
1942
2355
|
static bool visit_frame (ArtStackVisitor * visitor);
|
|
1943
2356
|
static void art_stack_frame_destroy (ArtStackFrame * frame);
|
|
1944
2357
|
|
|
@@ -1947,8 +2360,6 @@ static void append_jni_type_name (GString * s, const gchar * name, gsize length)
|
|
|
1947
2360
|
static void std_string_destroy (StdString * str);
|
|
1948
2361
|
static gchar * std_string_get_data (StdString * str);
|
|
1949
2362
|
|
|
1950
|
-
static void frida_log (const char * format, ...);
|
|
1951
|
-
|
|
1952
2363
|
void
|
|
1953
2364
|
init (void)
|
|
1954
2365
|
{
|
|
@@ -2334,7 +2745,13 @@ function revertGlobalPatches () {
|
|
|
2334
2745
|
});
|
|
2335
2746
|
patchedClasses.clear();
|
|
2336
2747
|
|
|
2337
|
-
|
|
2748
|
+
for (const interceptor of artQuickInterceptors.splice(0)) {
|
|
2749
|
+
interceptor.deactivate();
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
for (const hook of inlineHooks.splice(0)) {
|
|
2753
|
+
hook.revert();
|
|
2754
|
+
}
|
|
2338
2755
|
}
|
|
2339
2756
|
|
|
2340
2757
|
function unwrapMethodId (methodId) {
|
|
@@ -2707,7 +3124,7 @@ function writeArtQuickCodePrologueArm64 (target, trampoline, redirectSize) {
|
|
|
2707
3124
|
|
|
2708
3125
|
const artQuickCodeHookRedirectSize = {
|
|
2709
3126
|
ia32: 5,
|
|
2710
|
-
x64:
|
|
3127
|
+
x64: 16,
|
|
2711
3128
|
arm: 8,
|
|
2712
3129
|
arm64: 16
|
|
2713
3130
|
};
|
|
@@ -3116,6 +3533,10 @@ function deoptimizeEverything (vm, env) {
|
|
|
3116
3533
|
function deoptimizeBootImage (vm, env) {
|
|
3117
3534
|
const api = getApi();
|
|
3118
3535
|
|
|
3536
|
+
if (getAndroidApiLevel() < 26) {
|
|
3537
|
+
throw new Error('This API is only available on Android >= 8.0');
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3119
3540
|
withRunnableArtThread(vm, env, thread => {
|
|
3120
3541
|
api['art::Runtime::DeoptimizeBootImage'](api.artRuntime);
|
|
3121
3542
|
});
|
|
@@ -3125,7 +3546,7 @@ function requestDeoptimization (vm, env, kind, method) {
|
|
|
3125
3546
|
const api = getApi();
|
|
3126
3547
|
|
|
3127
3548
|
if (getAndroidApiLevel() < 24) {
|
|
3128
|
-
throw new Error('This API is only available on
|
|
3549
|
+
throw new Error('This API is only available on Android >= 7.0');
|
|
3129
3550
|
}
|
|
3130
3551
|
|
|
3131
3552
|
withRunnableArtThread(vm, env, thread => {
|
|
@@ -3161,9 +3582,12 @@ function requestDeoptimization (vm, env, kind, method) {
|
|
|
3161
3582
|
throw new Error('Unable to find Instrumentation class in ART; please file a bug');
|
|
3162
3583
|
}
|
|
3163
3584
|
|
|
3164
|
-
const
|
|
3165
|
-
if (
|
|
3166
|
-
|
|
3585
|
+
const enableDeopt = api['art::Instrumentation::EnableDeoptimization'];
|
|
3586
|
+
if (enableDeopt !== undefined) {
|
|
3587
|
+
const deoptimizationEnabled = !!instrumentation.add(getArtInstrumentationSpec().offset.deoptimizationEnabled).readU8();
|
|
3588
|
+
if (!deoptimizationEnabled) {
|
|
3589
|
+
enableDeopt(instrumentation);
|
|
3590
|
+
}
|
|
3167
3591
|
}
|
|
3168
3592
|
|
|
3169
3593
|
switch (kind) {
|
|
@@ -3544,8 +3968,9 @@ function recompileExceptionClearForX86 (buffer, pc, exceptionClearImpl, nextFunc
|
|
|
3544
3968
|
if (pointerSize === 4) {
|
|
3545
3969
|
writer.putAndRegU32('esp', 0xfffffff0);
|
|
3546
3970
|
} else {
|
|
3547
|
-
|
|
3548
|
-
writer.
|
|
3971
|
+
const scratchReg = (threadReg !== 'rdi') ? 'rdi' : 'rsi';
|
|
3972
|
+
writer.putMovRegU64(scratchReg, uint64('0xfffffffffffffff0'));
|
|
3973
|
+
writer.putAndRegReg('rsp', scratchReg);
|
|
3549
3974
|
}
|
|
3550
3975
|
writer.putCallAddressWithAlignedArguments(callback, [threadReg]);
|
|
3551
3976
|
writer.putMovRegReg('xsp', 'xbp');
|
package/lib/class-factory.js
CHANGED
|
@@ -485,9 +485,16 @@ class ClassFactory {
|
|
|
485
485
|
if (flavor === 'jvm') {
|
|
486
486
|
this._chooseObjectsJvm(specifier, env, callbacks);
|
|
487
487
|
} else if (flavor === 'art') {
|
|
488
|
+
const legacyApiMissing = api['art::gc::Heap::VisitObjects'] === undefined;
|
|
489
|
+
if (legacyApiMissing) {
|
|
490
|
+
const preA12ApiMissing = api['art::gc::Heap::GetInstances'] === undefined;
|
|
491
|
+
if (preA12ApiMissing) {
|
|
492
|
+
return this._chooseObjectsJvm(specifier, env, callbacks);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
488
495
|
android.withRunnableArtThread(vm, env, thread => {
|
|
489
|
-
if (
|
|
490
|
-
this.
|
|
496
|
+
if (legacyApiMissing) {
|
|
497
|
+
this._chooseObjectsArtPreA12(specifier, env, thread, callbacks);
|
|
491
498
|
} else {
|
|
492
499
|
this._chooseObjectsArtLegacy(specifier, env, thread, callbacks);
|
|
493
500
|
}
|
|
@@ -545,7 +552,7 @@ class ClassFactory {
|
|
|
545
552
|
}
|
|
546
553
|
}
|
|
547
554
|
|
|
548
|
-
|
|
555
|
+
_chooseObjectsArtPreA12 (className, env, thread, callbacks) {
|
|
549
556
|
const classWrapper = this.use(className);
|
|
550
557
|
|
|
551
558
|
const scope = android.VariableSizedHandleScope.$new(thread, vm);
|
package/lib/class-model.js
CHANGED
|
@@ -1176,7 +1176,7 @@ class Model {
|
|
|
1176
1176
|
|
|
1177
1177
|
const params = query.match(methodQueryPattern);
|
|
1178
1178
|
if (params === null) {
|
|
1179
|
-
throw new Error('Invalid method query');
|
|
1179
|
+
throw new Error('Invalid query; format is: class!method -- see documentation of Java.enumerateMethods(query) for details');
|
|
1180
1180
|
}
|
|
1181
1181
|
|
|
1182
1182
|
const classQuery = Memory.allocUtf8String(params[1]);
|
package/lib/jvm.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
const {
|
|
2
|
+
jvmtiVersion,
|
|
3
|
+
jvmtiCapabilities,
|
|
4
|
+
EnvJvmti
|
|
5
|
+
} = require('./jvmti');
|
|
6
|
+
const { parseInstructionsAt } = require('./machine-code');
|
|
1
7
|
const memoize = require('./memoize');
|
|
2
8
|
const { checkJniResult } = require('./result');
|
|
3
9
|
const VM = require('./vm');
|
|
@@ -11,17 +17,13 @@ const JVM_ACC_IS_OBSOLETE = 0x00020000;
|
|
|
11
17
|
const JVM_ACC_NOT_C2_COMPILABLE = 0x02000000;
|
|
12
18
|
const JVM_ACC_NOT_C1_COMPILABLE = 0x04000000;
|
|
13
19
|
const JVM_ACC_NOT_C2_OSR_COMPILABLE = 0x08000000;
|
|
14
|
-
const JVMTI_VERSION_1_0 = 0x30010000;
|
|
15
|
-
|
|
16
|
-
const jvmtiCapabilities = {
|
|
17
|
-
canTagObjects: 1
|
|
18
|
-
};
|
|
19
20
|
|
|
20
21
|
const nativeFunctionOptions = {
|
|
21
22
|
exceptions: 'propagate'
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
const getJvmMethodSpec = memoize(_getJvmMethodSpec);
|
|
26
|
+
const getJvmInstanceKlassSpec = memoize(_getJvmInstanceKlassSpec);
|
|
25
27
|
const getJvmThreadSpec = memoize(_getJvmThreadSpec);
|
|
26
28
|
|
|
27
29
|
let cachedApi = null;
|
|
@@ -57,6 +59,9 @@ function _getApi () {
|
|
|
57
59
|
_ZN6Method4sizeEb: ['Method::size', 'int', ['int']],
|
|
58
60
|
_ZN6Method19set_native_functionEPhb: ['Method::set_native_function', 'void', ['pointer', 'pointer', 'int']],
|
|
59
61
|
_ZN6Method21clear_native_functionEv: ['Method::clear_native_function', 'void', ['pointer']],
|
|
62
|
+
// JDK >= 17
|
|
63
|
+
_ZN6Method24restore_unshareable_infoEP10JavaThread: ['Method::restore_unshareable_info', 'void', ['pointer', 'pointer']],
|
|
64
|
+
// JDK < 17
|
|
60
65
|
_ZN6Method24restore_unshareable_infoEP6Thread: ['Method::restore_unshareable_info', 'void', ['pointer', 'pointer']],
|
|
61
66
|
_ZN6Method10jmethod_idEv: ['Method::jmethod_id', 'pointer', ['pointer']],
|
|
62
67
|
_ZN6Method10clear_codeEv: function (address) {
|
|
@@ -73,8 +78,6 @@ function _getApi () {
|
|
|
73
78
|
};
|
|
74
79
|
},
|
|
75
80
|
|
|
76
|
-
_ZNK5Klass15start_of_vtableEv: ['Klass::start_of_vtable', 'pointer', ['pointer']],
|
|
77
|
-
_ZNK13InstanceKlass6vtableEv: ['InstanceKlass::vtable', 'pointer', ['pointer']],
|
|
78
81
|
// JDK >= 13
|
|
79
82
|
_ZN18VM_RedefineClasses19mark_dependent_codeEP13InstanceKlass: ['VM_RedefineClasses::mark_dependent_code', 'void', ['pointer', 'pointer']],
|
|
80
83
|
_ZN18VM_RedefineClasses20flush_dependent_codeEv: ['VM_RedefineClasses::flush_dependent_code', 'void', []],
|
|
@@ -157,6 +160,16 @@ function _getApi () {
|
|
|
157
160
|
_ZNK18VM_RedefineClasses14print_on_errorEP12outputStream: function (address) {
|
|
158
161
|
this.redefineClassesOnError = address;
|
|
159
162
|
},
|
|
163
|
+
|
|
164
|
+
// JDK >= 17
|
|
165
|
+
_ZN13InstanceKlass33create_new_default_vtable_indicesEiP10JavaThread: function (address) {
|
|
166
|
+
this.createNewDefaultVtableIndices = address;
|
|
167
|
+
},
|
|
168
|
+
// JDK < 17
|
|
169
|
+
_ZN13InstanceKlass33create_new_default_vtable_indicesEiP6Thread: function (address) {
|
|
170
|
+
this.createNewDefaultVtableIndices = address;
|
|
171
|
+
},
|
|
172
|
+
|
|
160
173
|
_ZN19Abstract_VM_Version19jre_release_versionEv: function (address) {
|
|
161
174
|
const getVersion = new NativeFunction(address, 'pointer', [], nativeFunctionOptions);
|
|
162
175
|
const versionS = getVersion().readCString();
|
|
@@ -167,6 +180,7 @@ function _getApi () {
|
|
|
167
180
|
: parseInt(versionS.slice(0, 2), 10);
|
|
168
181
|
this.versionS = versionS;
|
|
169
182
|
},
|
|
183
|
+
|
|
170
184
|
_ZN14NMethodSweeper11_traversalsE: function (address) {
|
|
171
185
|
this.traversals = address;
|
|
172
186
|
},
|
|
@@ -178,36 +192,41 @@ function _getApi () {
|
|
|
178
192
|
}
|
|
179
193
|
},
|
|
180
194
|
optionals: [
|
|
195
|
+
'_ZN6Method24restore_unshareable_infoEP10JavaThread',
|
|
196
|
+
'_ZN6Method24restore_unshareable_infoEP6Thread',
|
|
197
|
+
'_ZN6Method10clear_codeEv',
|
|
198
|
+
'_ZN6Method10clear_codeEb',
|
|
199
|
+
|
|
181
200
|
'_ZN18VM_RedefineClasses19mark_dependent_codeEP13InstanceKlass',
|
|
182
201
|
'_ZN18VM_RedefineClasses20flush_dependent_codeEv',
|
|
183
202
|
'_ZN18VM_RedefineClasses20flush_dependent_codeEP13InstanceKlassP6Thread',
|
|
184
203
|
'_ZN18VM_RedefineClasses20flush_dependent_codeE19instanceKlassHandleP6Thread',
|
|
185
204
|
|
|
186
|
-
'_ZNK5Klass15start_of_vtableEv',
|
|
187
|
-
'_ZNK13InstanceKlass6vtableEv',
|
|
188
|
-
|
|
189
205
|
'_ZN19ResolvedMethodTable21adjust_method_entriesEPb',
|
|
190
206
|
'_ZN15MemberNameTable21adjust_method_entriesEP13InstanceKlassPb',
|
|
191
207
|
|
|
192
208
|
'_ZN17ConstantPoolCache21adjust_method_entriesEPb',
|
|
193
209
|
'_ZN17ConstantPoolCache21adjust_method_entriesEP13InstanceKlassPb',
|
|
194
210
|
|
|
211
|
+
'_ZN20ClassLoaderDataGraph22clean_deallocate_listsEb',
|
|
212
|
+
|
|
213
|
+
'_ZN10JavaThread27thread_from_jni_environmentEP7JNIEnv_',
|
|
214
|
+
|
|
215
|
+
'_ZN14NMethodSweeper11force_sweepEv',
|
|
216
|
+
'_ZN14NMethodSweeper17sweep_in_progressEv',
|
|
217
|
+
|
|
195
218
|
'_ZN18VM_RedefineClasses14_the_class_oopE',
|
|
196
219
|
'_ZN18VM_RedefineClasses10_the_classE',
|
|
197
220
|
'_ZN18VM_RedefineClasses25AdjustCpoolCacheAndVtable8do_klassEP5Klass',
|
|
198
221
|
'_ZN18VM_RedefineClasses22AdjustAndCleanMetadata8do_klassEP5Klass',
|
|
199
|
-
|
|
200
222
|
'_ZN18VM_RedefineClassesD0Ev',
|
|
201
223
|
'_ZN18VM_RedefineClassesD1Ev',
|
|
202
224
|
'_ZNK18VM_RedefineClasses14print_on_errorEP12outputStream',
|
|
203
225
|
|
|
204
|
-
'
|
|
205
|
-
'
|
|
226
|
+
'_ZN13InstanceKlass33create_new_default_vtable_indicesEiP10JavaThread',
|
|
227
|
+
'_ZN13InstanceKlass33create_new_default_vtable_indicesEiP6Thread',
|
|
206
228
|
|
|
207
|
-
'
|
|
208
|
-
'_ZN14NMethodSweeper11force_sweepEv',
|
|
209
|
-
'_ZN14NMethodSweeper21_sweep_fractions_leftE',
|
|
210
|
-
'_ZN14NMethodSweeper17sweep_in_progressEv'
|
|
229
|
+
'_ZN14NMethodSweeper21_sweep_fractions_leftE'
|
|
211
230
|
]
|
|
212
231
|
}];
|
|
213
232
|
|
|
@@ -291,6 +310,11 @@ function _getApi () {
|
|
|
291
310
|
}
|
|
292
311
|
|
|
293
312
|
temporaryApi.jvmti = getEnvJvmti(temporaryApi);
|
|
313
|
+
|
|
314
|
+
if (temporaryApi['JavaThread::thread_from_jni_environment'] === undefined) {
|
|
315
|
+
temporaryApi['JavaThread::thread_from_jni_environment'] = makeThreadFromJniHelper(temporaryApi);
|
|
316
|
+
}
|
|
317
|
+
|
|
294
318
|
return temporaryApi;
|
|
295
319
|
}
|
|
296
320
|
|
|
@@ -299,63 +323,59 @@ function getEnvJvmti (api) {
|
|
|
299
323
|
|
|
300
324
|
let env;
|
|
301
325
|
vm.perform(() => {
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
env = new EnvJvmti(envBuf.readPointer(), vm);
|
|
326
|
+
const handle = vm.tryGetEnvHandle(jvmtiVersion.v1_0);
|
|
327
|
+
if (handle === null) {
|
|
328
|
+
throw new Error('JVMTI not available');
|
|
329
|
+
}
|
|
330
|
+
env = new EnvJvmti(handle, vm);
|
|
308
331
|
|
|
309
332
|
const capaBuf = Memory.alloc(8);
|
|
310
333
|
capaBuf.writeU64(jvmtiCapabilities.canTagObjects);
|
|
311
|
-
result = env.addCapabilities(capaBuf);
|
|
334
|
+
const result = env.addCapabilities(capaBuf);
|
|
312
335
|
checkJniResult('getEnvJvmti::AddCapabilities', result);
|
|
313
336
|
});
|
|
314
337
|
|
|
315
338
|
return env;
|
|
316
339
|
}
|
|
317
340
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
this.vtable = handle.readPointer();
|
|
322
|
-
}
|
|
341
|
+
const threadOffsetParsers = {
|
|
342
|
+
x64: parseX64ThreadOffset
|
|
343
|
+
};
|
|
323
344
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
EnvJvmti.prototype.addCapabilities = proxy(142, 'int32', ['pointer', 'pointer'], function (impl, capabilitiesPtr) {
|
|
344
|
-
return impl(this.handle, capabilitiesPtr);
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
function proxy (offset, retType, argTypes, wrapper) {
|
|
348
|
-
let impl = null;
|
|
349
|
-
return function () {
|
|
350
|
-
if (impl === null) {
|
|
351
|
-
impl = new NativeFunction(this.vtable.add((offset - 1) * pointerSize).readPointer(), retType, argTypes, nativeFunctionOptions);
|
|
352
|
-
}
|
|
353
|
-
let args = [impl];
|
|
354
|
-
args = args.concat.apply(args, arguments);
|
|
355
|
-
return wrapper.apply(this, args);
|
|
345
|
+
function makeThreadFromJniHelper (api) {
|
|
346
|
+
let offset = null;
|
|
347
|
+
|
|
348
|
+
const tryParse = threadOffsetParsers[Process.arch];
|
|
349
|
+
if (tryParse !== undefined) {
|
|
350
|
+
const vm = new VM(api);
|
|
351
|
+
const findClassImpl = vm.perform(env => env.handle.readPointer().add(6 * pointerSize).readPointer());
|
|
352
|
+
offset = parseInstructionsAt(findClassImpl, tryParse, { limit: 10 });
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (offset === null) {
|
|
356
|
+
return () => {
|
|
357
|
+
throw new Error('Unable to make thread_from_jni_environment() helper for the current architecture');
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return env => {
|
|
362
|
+
return env.add(offset);
|
|
356
363
|
};
|
|
357
364
|
}
|
|
358
365
|
|
|
366
|
+
function parseX64ThreadOffset (insn) {
|
|
367
|
+
if (insn.mnemonic !== 'lea') {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const { base, disp } = insn.operands[1].value;
|
|
372
|
+
if (!(base === 'rdi' && disp < 0)) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return disp;
|
|
377
|
+
}
|
|
378
|
+
|
|
359
379
|
function ensureClassInitialized (env, classRef) {
|
|
360
380
|
}
|
|
361
381
|
|
|
@@ -530,13 +550,15 @@ function withJvmThread (fn, fnPrologue, fnEpilogue) {
|
|
|
530
550
|
const doIt = new NativeCallback(fn, 'void', ['pointer']);
|
|
531
551
|
vtableDup.add(doItOffset).writePointer(doIt);
|
|
532
552
|
|
|
553
|
+
let prologue = null;
|
|
533
554
|
if (fnPrologue !== undefined) {
|
|
534
|
-
|
|
555
|
+
prologue = new NativeCallback(fnPrologue, 'int', ['pointer']);
|
|
535
556
|
vtableDup.add(prologueOffset).writePointer(prologue);
|
|
536
557
|
}
|
|
537
558
|
|
|
559
|
+
let epilogue = null;
|
|
538
560
|
if (fnEpilogue !== undefined) {
|
|
539
|
-
|
|
561
|
+
epilogue = new NativeCallback(fnEpilogue, 'void', ['pointer']);
|
|
540
562
|
vtableDup.add(epilogueOffset).writePointer(epilogue);
|
|
541
563
|
}
|
|
542
564
|
|
|
@@ -727,24 +749,19 @@ function readJvmMethod (method, constMethod, constMethodSize) {
|
|
|
727
749
|
const instanceKlass = constantPool.add(spec.constantPool.instanceKlassOffset).readPointer();
|
|
728
750
|
const cache = constantPool.add(spec.constantPool.cacheOffset).readPointer();
|
|
729
751
|
|
|
730
|
-
|
|
731
|
-
const klassVtable = api['InstanceKlass::vtable'](instanceKlass);
|
|
732
|
-
const vtableOffset = klassVtable.add(pointerSize).readS32();
|
|
733
|
-
const klassSpec = getJvmKlassSpec(vtableOffset);
|
|
734
|
-
spec.instanceKlass = klassSpec;
|
|
735
|
-
}
|
|
752
|
+
const instanceKlassSpec = getJvmInstanceKlassSpec();
|
|
736
753
|
|
|
737
|
-
const methods = instanceKlass.add(
|
|
754
|
+
const methods = instanceKlass.add(instanceKlassSpec.methodsOffset).readPointer();
|
|
738
755
|
const methodsCount = methods.readS32();
|
|
739
756
|
const methodsArray = methods.add(pointerSize);
|
|
740
757
|
const methodIndex = constMethod.add(spec.constMethod.methodIdnumOffset).readU16();
|
|
741
758
|
const vtableIndexPtr = method.add(spec.method.vtableIndexOffset);
|
|
742
759
|
const vtableIndex = vtableIndexPtr.readS32();
|
|
743
|
-
const vtable = instanceKlass.add(
|
|
744
|
-
const oopMapCache = instanceKlass.add(
|
|
760
|
+
const vtable = instanceKlass.add(instanceKlassSpec.vtableOffset);
|
|
761
|
+
const oopMapCache = instanceKlass.add(instanceKlassSpec.oopMapCacheOffset).readPointer();
|
|
745
762
|
|
|
746
763
|
const memberNames = (api.version >= 10)
|
|
747
|
-
? instanceKlass.add(
|
|
764
|
+
? instanceKlass.add(instanceKlassSpec.memberNamesOffset).readPointer()
|
|
748
765
|
: NULL;
|
|
749
766
|
|
|
750
767
|
return {
|
|
@@ -782,39 +799,48 @@ function revertJvmMethod (method) {
|
|
|
782
799
|
|
|
783
800
|
function _getJvmMethodSpec () {
|
|
784
801
|
const api = getApi();
|
|
802
|
+
const { version } = api;
|
|
785
803
|
|
|
786
|
-
|
|
804
|
+
let adapterHandlerLocation;
|
|
805
|
+
if (version >= 17) {
|
|
806
|
+
adapterHandlerLocation = 'method:early';
|
|
807
|
+
} else if (version >= 9 && version <= 16) {
|
|
808
|
+
adapterHandlerLocation = 'const-method';
|
|
809
|
+
} else {
|
|
810
|
+
adapterHandlerLocation = 'method:late';
|
|
811
|
+
}
|
|
787
812
|
|
|
788
813
|
const isNative = 1;
|
|
789
814
|
const methodSize = api['Method::size'](isNative) * pointerSize;
|
|
790
815
|
const constMethodOffset = pointerSize;
|
|
791
816
|
const methodDataOffset = 2 * pointerSize;
|
|
792
817
|
const methodCountersOffset = 3 * pointerSize;
|
|
793
|
-
const
|
|
818
|
+
const adapterInMethodEarlyOffset = 4 * pointerSize;
|
|
819
|
+
const adapterInMethodEarlySize = (adapterHandlerLocation === 'method:early') ? pointerSize : 0;
|
|
820
|
+
const accessFlagsOffset = adapterInMethodEarlyOffset + adapterInMethodEarlySize;
|
|
794
821
|
const vtableIndexOffset = accessFlagsOffset + 4;
|
|
795
|
-
const i2iEntryOffset = vtableIndexOffset + 4 +
|
|
822
|
+
const i2iEntryOffset = vtableIndexOffset + 4 + 8;
|
|
823
|
+
const adapterInMethodLateOffset = i2iEntryOffset + pointerSize;
|
|
824
|
+
const adapterInMethodOffset = (adapterInMethodEarlySize !== 0) ? adapterInMethodEarlyOffset : adapterInMethodLateOffset;
|
|
796
825
|
const nativeFunctionOffset = methodSize - 2 * pointerSize;
|
|
797
826
|
const signatureHandlerOffset = methodSize - pointerSize;
|
|
798
827
|
|
|
799
|
-
const constantPoolOffset =
|
|
800
|
-
const stackmapDataOffset =
|
|
801
|
-
const
|
|
828
|
+
const constantPoolOffset = 8;
|
|
829
|
+
const stackmapDataOffset = constantPoolOffset + pointerSize;
|
|
830
|
+
const adapterInConstMethodOffset = stackmapDataOffset + pointerSize;
|
|
831
|
+
const adapterInConstMethodSize = (adapterHandlerLocation === 'const-method') ? pointerSize : 0;
|
|
832
|
+
const constMethodSizeOffset = adapterInConstMethodOffset + adapterInConstMethodSize;
|
|
802
833
|
const methodIdnumOffset = constMethodSizeOffset + 0xe;
|
|
803
834
|
|
|
804
835
|
const cacheOffset = 2 * pointerSize;
|
|
805
836
|
const instanceKlassOffset = 3 * pointerSize;
|
|
806
|
-
let klassSpec;
|
|
807
|
-
if ('Klass::start_of_vtable' in api) {
|
|
808
|
-
const vtableOffset = api['Klass::start_of_vtable'](NULL).toInt32();
|
|
809
|
-
klassSpec = getJvmKlassSpec(vtableOffset);
|
|
810
|
-
}
|
|
811
837
|
|
|
812
|
-
const getAdapterPointer =
|
|
838
|
+
const getAdapterPointer = (adapterInConstMethodSize !== 0)
|
|
813
839
|
? function (method, constMethod) {
|
|
814
|
-
return constMethod.add(
|
|
840
|
+
return constMethod.add(adapterInConstMethodOffset);
|
|
815
841
|
}
|
|
816
842
|
: function (method, constMethod) {
|
|
817
|
-
return method.add(
|
|
843
|
+
return method.add(adapterInMethodOffset);
|
|
818
844
|
};
|
|
819
845
|
|
|
820
846
|
return {
|
|
@@ -839,15 +865,28 @@ function _getJvmMethodSpec () {
|
|
|
839
865
|
constantPool: {
|
|
840
866
|
cacheOffset,
|
|
841
867
|
instanceKlassOffset
|
|
842
|
-
}
|
|
843
|
-
instanceKlass: klassSpec
|
|
868
|
+
}
|
|
844
869
|
};
|
|
845
870
|
}
|
|
846
871
|
|
|
847
|
-
|
|
848
|
-
|
|
872
|
+
const vtableOffsetParsers = {
|
|
873
|
+
x64: parseX64VTableOffset
|
|
874
|
+
};
|
|
875
|
+
|
|
876
|
+
function _getJvmInstanceKlassSpec () {
|
|
877
|
+
const { version: jvmVersion, createNewDefaultVtableIndices } = getApi();
|
|
878
|
+
|
|
879
|
+
const tryParse = vtableOffsetParsers[Process.arch];
|
|
880
|
+
if (tryParse === undefined) {
|
|
881
|
+
throw new Error(`Missing vtable offset parser for ${Process.arch}`);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const vtableOffset = parseInstructionsAt(createNewDefaultVtableIndices, tryParse, { limit: 32 });
|
|
885
|
+
if (vtableOffset === null) {
|
|
886
|
+
throw new Error('Unable to deduce vtable offset');
|
|
887
|
+
}
|
|
849
888
|
|
|
850
|
-
const oopMultiplier = (jvmVersion >= 10 && jvmVersion <= 11) ? 17 : 18;
|
|
889
|
+
const oopMultiplier = ((jvmVersion >= 10 && jvmVersion <= 11) || jvmVersion >= 15) ? 17 : 18;
|
|
851
890
|
|
|
852
891
|
const methodsOffset = vtableOffset - (7 * pointerSize);
|
|
853
892
|
const memberNamesOffset = vtableOffset - (17 * pointerSize);
|
|
@@ -861,6 +900,31 @@ function getJvmKlassSpec (vtableOffset) {
|
|
|
861
900
|
};
|
|
862
901
|
}
|
|
863
902
|
|
|
903
|
+
function parseX64VTableOffset (insn) {
|
|
904
|
+
if (insn.mnemonic !== 'mov') {
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const dst = insn.operands[0];
|
|
909
|
+
if (dst.type !== 'mem') {
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
const { value: dstValue } = dst;
|
|
914
|
+
if (dstValue.scale !== 1) {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const { disp } = dstValue;
|
|
919
|
+
if (disp < 0x100) {
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const defaultVtableIndicesOffset = disp;
|
|
924
|
+
|
|
925
|
+
return defaultVtableIndicesOffset + 16;
|
|
926
|
+
}
|
|
927
|
+
|
|
864
928
|
function deoptimizeEverything (vm, env) {
|
|
865
929
|
}
|
|
866
930
|
|
package/lib/jvmti.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const { checkJniResult } = require('./result');
|
|
2
|
+
|
|
3
|
+
const jvmtiVersion = {
|
|
4
|
+
v1_0: 0x30010000,
|
|
5
|
+
v1_2: 0x30010200
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const jvmtiCapabilities = {
|
|
9
|
+
canTagObjects: 1
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const { pointerSize } = Process;
|
|
13
|
+
const nativeFunctionOptions = {
|
|
14
|
+
exceptions: 'propagate'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function EnvJvmti (handle, vm) {
|
|
18
|
+
this.handle = handle;
|
|
19
|
+
this.vm = vm;
|
|
20
|
+
this.vtable = handle.readPointer();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
EnvJvmti.prototype.deallocate = proxy(47, 'int32', ['pointer', 'pointer'], function (impl, mem) {
|
|
24
|
+
return impl(this.handle, mem);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
EnvJvmti.prototype.getLoadedClasses = proxy(78, 'int32', ['pointer', 'pointer', 'pointer'], function (impl, classCountPtr, classesPtr) {
|
|
28
|
+
const result = impl(this.handle, classCountPtr, classesPtr);
|
|
29
|
+
checkJniResult('EnvJvmti::getLoadedClasses', result);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
EnvJvmti.prototype.iterateOverInstancesOfClass = proxy(112, 'int32', ['pointer', 'pointer', 'int', 'pointer', 'pointer'], function (impl, klass, objectFilter, heapObjectCallback, userData) {
|
|
33
|
+
const result = impl(this.handle, klass, objectFilter, heapObjectCallback, userData);
|
|
34
|
+
checkJniResult('EnvJvmti::iterateOverInstancesOfClass', result);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
EnvJvmti.prototype.getObjectsWithTags = proxy(114, 'int32', ['pointer', 'int', 'pointer', 'pointer', 'pointer', 'pointer'], function (impl, tagCount, tags, countPtr, objectResultPtr, tagResultPtr) {
|
|
38
|
+
const result = impl(this.handle, tagCount, tags, countPtr, objectResultPtr, tagResultPtr);
|
|
39
|
+
checkJniResult('EnvJvmti::getObjectsWithTags', result);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
EnvJvmti.prototype.addCapabilities = proxy(142, 'int32', ['pointer', 'pointer'], function (impl, capabilitiesPtr) {
|
|
43
|
+
return impl(this.handle, capabilitiesPtr);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
function proxy (offset, retType, argTypes, wrapper) {
|
|
47
|
+
let impl = null;
|
|
48
|
+
return function () {
|
|
49
|
+
if (impl === null) {
|
|
50
|
+
impl = new NativeFunction(this.vtable.add((offset - 1) * pointerSize).readPointer(), retType, argTypes, nativeFunctionOptions);
|
|
51
|
+
}
|
|
52
|
+
let args = [impl];
|
|
53
|
+
args = args.concat.apply(args, arguments);
|
|
54
|
+
return wrapper.apply(this, args);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
jvmtiVersion,
|
|
60
|
+
jvmtiCapabilities,
|
|
61
|
+
EnvJvmti
|
|
62
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function parseInstructionsAt (address, tryParse, { limit }) {
|
|
2
|
+
let cursor = address;
|
|
3
|
+
let prevInsn = null;
|
|
4
|
+
|
|
5
|
+
for (let i = 0; i !== limit; i++) {
|
|
6
|
+
const insn = Instruction.parse(cursor);
|
|
7
|
+
|
|
8
|
+
const value = tryParse(insn, prevInsn);
|
|
9
|
+
if (value !== null) {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
cursor = insn.next;
|
|
14
|
+
prevInsn = insn;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
parseInstructionsAt
|
|
22
|
+
};
|
package/lib/vm.js
CHANGED
|
@@ -107,12 +107,20 @@ function VM (api) {
|
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
this._tryGetEnv = function () {
|
|
110
|
+
const h = this.tryGetEnvHandle(JNI_VERSION_1_6);
|
|
111
|
+
if (h === null) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
return new Env(h, this);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
this.tryGetEnvHandle = function (version) {
|
|
110
118
|
const envBuf = Memory.alloc(pointerSize);
|
|
111
|
-
const result = getEnv(handle, envBuf,
|
|
119
|
+
const result = getEnv(handle, envBuf, version);
|
|
112
120
|
if (result !== JNI_OK) {
|
|
113
121
|
return null;
|
|
114
122
|
}
|
|
115
|
-
return
|
|
123
|
+
return envBuf.readPointer();
|
|
116
124
|
};
|
|
117
125
|
|
|
118
126
|
this.makeHandleDestructor = function (handle) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frida-java-bridge",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.1.1",
|
|
4
4
|
"description": "Java runtime interop from Frida",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"Instruction",
|
|
37
37
|
"Int64",
|
|
38
38
|
"Interceptor",
|
|
39
|
+
"MatchPattern",
|
|
39
40
|
"Memory",
|
|
40
41
|
"Module",
|
|
41
42
|
"NULL",
|