Create Tweak::ArrayParams for EntryHook
am: fd1dbb6a9c
Change-Id: I31bbb43db7bab380d3b9a3d603cb47ac0e7236c4
diff --git a/dexter/dexter_tests.py b/dexter/dexter_tests.py
index 7a47c01..5e5fe75 100644
--- a/dexter/dexter_tests.py
+++ b/dexter/dexter_tests.py
@@ -26,26 +26,27 @@
# list of test cases
# ( <test_name> : { <test_case_config> } )
test_cases = {
- 'map' : { 'args' : '-m', 'input' : ['*.dex'] },
- 'stats' : { 'args' : '-s', 'input' : ['*.dex'] },
- 'asm' : { 'args' : '-d', 'input' : ['*.dex'] },
- 'hello_stats' : { 'args' : '-s -e Hello', 'input' : ['hello.dex'] },
- 'am_stats' : { 'args' : '-s -e android.app.ActivityManager', 'input' : ['large.dex'] },
- 'rewrite' : { 'args' : '-d -x full_rewrite', 'input' : ['*.dex'] },
- 'entry_hook' : { 'args' : '-d -x stress_entry_hook', 'input' : [
+ 'map' : { 'args' : '-m', 'input' : ['*.dex'] },
+ 'stats' : { 'args' : '-s', 'input' : ['*.dex'] },
+ 'asm' : { 'args' : '-d', 'input' : ['*.dex'] },
+ 'hello_stats' : { 'args' : '-s -e Hello', 'input' : ['hello.dex'] },
+ 'am_stats' : { 'args' : '-s -e android.app.ActivityManager', 'input' : ['large.dex'] },
+ 'rewrite' : { 'args' : '-d -x full_rewrite', 'input' : ['*.dex'] },
+ 'entry_hook' : { 'args' : '-d -x stress_entry_hook', 'input' : [
'entry_hooks.dex', 'hello.dex', 'medium.dex', 'min.dex' ] },
- 'exit_hook' : { 'args' : '-d -x stress_exit_hook', 'input' : [
+ 'exit_hook' : { 'args' : '-d -x stress_exit_hook', 'input' : [
'exit_hooks.dex', 'medium.dex', 'try_catch.dex' ] },
- 'wrap_invoke' : { 'args' : '-d -x stress_wrap_invoke', 'input' : [
+ 'wrap_invoke' : { 'args' : '-d -x stress_wrap_invoke', 'input' : [
'hello.dex', 'hello_nodebug.dex', 'medium.dex' ] },
- 'mi' : { 'args' : '-d -x test_method_instrumenter', 'input' : ['mi.dex'] },
- 'find_method' : { 'args' : '-x stress_find_method', 'input' : [
+ 'mi' : { 'args' : '-d -x test_method_instrumenter', 'input' : ['mi.dex'] },
+ 'find_method' : { 'args' : '-x stress_find_method', 'input' : [
'hello.dex', 'entry_hooks.dex', 'medium.dex', 'large.dex', 'try_catch.dex' ] },
- 'verbose_cfg' : { 'args' : '-d --cfg=verbose', 'input' : ['*.dex'] },
- 'compact_cfg' : { 'args' : '-d --cfg=compact', 'input' : ['*.dex'] },
- 'scratch_regs' : { 'args' : '-d -x stress_scratch_regs', 'input' : ['*.dex'] },
- 'regs_usage' : { 'args' : '-x regs_histogram', 'input' : ['*.dex'] },
- 'code_coverage' : { 'args' : '-d -x code_coverage', 'input' : ['*.dex'] },
+ 'verbose_cfg' : { 'args' : '-d --cfg=verbose', 'input' : ['*.dex'] },
+ 'compact_cfg' : { 'args' : '-d --cfg=compact', 'input' : ['*.dex'] },
+ 'scratch_regs' : { 'args' : '-d -x stress_scratch_regs', 'input' : ['*.dex'] },
+ 'regs_usage' : { 'args' : '-x regs_histogram', 'input' : ['*.dex'] },
+ 'code_coverage' : { 'args' : '-d -x code_coverage', 'input' : ['*.dex'] },
+ 'array_entry_hook' : { 'args' : '-d -x array_param_entry_hook', 'input' : ['mi.dex'] },
}
# run a shell command and returns the stdout content
diff --git a/dexter/experimental.cc b/dexter/experimental.cc
index 545a340..b721883 100644
--- a/dexter/experimental.cc
+++ b/dexter/experimental.cc
@@ -491,6 +491,19 @@
PrintHistogram(regs_histogram, "Method extra registers (total - parameters)");
}
+// Test slicer::MethodInstrumenter + Tweak::ArrayParams
+void TestArrayParamsEntryHook(std::shared_ptr<ir::DexFile> dex_ir) {
+ slicer::MethodInstrumenter mi(dex_ir);
+ mi.AddTransformation<slicer::EntryHook>(ir::MethodId("LTracer;", "onFooEntry"),
+ slicer::EntryHook::Tweak::ArrayParams);
+
+ auto method1 = ir::MethodId("LTarget;", "foo", "(ILjava/lang/String;)I");
+ SLICER_CHECK(mi.InstrumentMethod(method1));
+
+ auto method2 = ir::MethodId("LTarget;", "foo", "(I[[Ljava/lang/String;)Ljava/lang/Integer;");
+ SLICER_CHECK(mi.InstrumentMethod(method2));
+}
+
void ListExperiments(std::shared_ptr<ir::DexFile> dex_ir);
using Experiment = void (*)(std::shared_ptr<ir::DexFile>);
@@ -507,6 +520,7 @@
{ "stress_scratch_regs", &StressScratchRegs },
{ "regs_histogram", &RegsHistogram },
{ "code_coverage", &CodeCoverage },
+ { "array_param_entry_hook", &TestArrayParamsEntryHook },
};
// Lists all the registered experiments
diff --git a/slicer/export/slicer/instrumentation.h b/slicer/export/slicer/instrumentation.h
index b9fda8c..bdc04c7 100644
--- a/slicer/export/slicer/instrumentation.h
+++ b/slicer/export/slicer/instrumentation.h
@@ -45,7 +45,12 @@
// Expose the "this" argument of non-static methods as the "Object" type.
// This can be helpful when the code you want to handle the hook doesn't
// have access to the actual type in its classpath.
- ThisAsObject
+ ThisAsObject,
+ // Forward incoming arguments as an array. Zero-th element of the array is
+ // "this" object if instrumented method isn't static.
+ // It is helpul, when you inject the same hook into the different
+ // methods.
+ ArrayParams,
};
explicit EntryHook(const ir::MethodId& hook_method_id, Tweak tweak)
@@ -68,6 +73,8 @@
private:
ir::MethodId hook_method_id_;
Tweak tweak_;
+
+ bool InjectArrayParamsHook(lir::CodeIr* code_ir, lir::Bytecode* bytecode);
};
// Insert a call to the "exit hook" method before every return
diff --git a/slicer/instrumentation.cc b/slicer/instrumentation.cc
index 83a524d..9eaf5da 100644
--- a/slicer/instrumentation.cc
+++ b/slicer/instrumentation.cc
@@ -29,9 +29,88 @@
}
};
+void BoxValue(lir::Bytecode* bytecode,
+ lir::CodeIr* code_ir,
+ ir::Type* type,
+ dex::u4 src_reg,
+ dex::u4 dst_reg) {
+ bool is_wide = false;
+ const char* boxed_type_name = nullptr;
+ switch (*(type->descriptor)->c_str()) {
+ case 'Z':
+ boxed_type_name = "Ljava/lang/Boolean;";
+ break;
+ case 'B':
+ boxed_type_name = "Ljava/lang/Byte;";
+ break;
+ case 'C':
+ boxed_type_name = "Ljava/lang/Character;";
+ break;
+ case 'S':
+ boxed_type_name = "Ljava/lang/Short;";
+ break;
+ case 'I':
+ boxed_type_name = "Ljava/lang/Integer;";
+ break;
+ case 'J':
+ is_wide = true;
+ boxed_type_name = "Ljava/lang/Long;";
+ break;
+ case 'F':
+ boxed_type_name = "Ljava/lang/Float;";
+ break;
+ case 'D':
+ is_wide = true;
+ boxed_type_name = "Ljava/lang/Double;";
+ break;
+ }
+ SLICER_CHECK(boxed_type_name != nullptr);
+
+ ir::Builder builder(code_ir->dex_ir);
+ std::vector<ir::Type*> param_types;
+ param_types.push_back(type);
+
+ auto boxed_type = builder.GetType(boxed_type_name);
+ auto ir_proto = builder.GetProto(boxed_type, builder.GetTypeList(param_types));
+
+ auto ir_method_decl = builder.GetMethodDecl(
+ builder.GetAsciiString("valueOf"), ir_proto, boxed_type);
+
+ auto boxing_method = code_ir->Alloc<lir::Method>(ir_method_decl, ir_method_decl->orig_index);
+
+ auto args = code_ir->Alloc<lir::VRegRange>(src_reg, 1 + is_wide);
+ auto boxing_invoke = code_ir->Alloc<lir::Bytecode>();
+ boxing_invoke->opcode = dex::OP_INVOKE_STATIC_RANGE;
+ boxing_invoke->operands.push_back(args);
+ boxing_invoke->operands.push_back(boxing_method);
+ code_ir->instructions.InsertBefore(bytecode, boxing_invoke);
+
+ auto move_result = code_ir->Alloc<lir::Bytecode>();
+ move_result->opcode = dex::OP_MOVE_RESULT_OBJECT;
+ move_result->operands.push_back(code_ir->Alloc<lir::VReg>(dst_reg));
+ code_ir->instructions.InsertBefore(bytecode, move_result);
+}
+
} // namespace
bool EntryHook::Apply(lir::CodeIr* code_ir) {
+ lir::Bytecode* bytecode = nullptr;
+ // find the first bytecode in the method body to insert the hook before it
+ for (auto instr : code_ir->instructions) {
+ BytecodeConvertingVisitor visitor;
+ instr->Accept(&visitor);
+ bytecode = visitor.out;
+ if (bytecode != nullptr) {
+ break;
+ }
+ }
+ if (bytecode == nullptr) {
+ return false;
+ }
+ if (tweak_ == Tweak::ArrayParams) {
+ return InjectArrayParamsHook(code_ir, bytecode);
+ }
+
ir::Builder builder(code_ir->dex_ir);
const auto ir_method = code_ir->ir_method;
@@ -75,17 +154,116 @@
hook_invoke->operands.push_back(hook_method);
// insert the hook before the first bytecode in the method body
- for (auto instr : code_ir->instructions) {
- BytecodeConvertingVisitor visitor;
- instr->Accept(&visitor);
- auto bytecode = visitor.out;
- if (bytecode == nullptr) {
- continue;
- }
- code_ir->instructions.InsertBefore(bytecode, hook_invoke);
- break;
+ code_ir->instructions.InsertBefore(bytecode, hook_invoke);
+ return true;
+}
+
+bool EntryHook::InjectArrayParamsHook(lir::CodeIr* code_ir, lir::Bytecode* bytecode) {
+ ir::Builder builder(code_ir->dex_ir);
+ const auto ir_method = code_ir->ir_method;
+ auto param_types = ir_method->decl->prototype->param_types->types;
+ bool is_static = (ir_method->access_flags & dex::kAccStatic) != 0;
+
+ bool needsBoxingReg = false;
+ for (auto type: param_types) {
+ needsBoxingReg |= type->GetCategory() != ir::Type::Category::Reference;
}
+ // allocate scract registers
+ slicer::AllocateScratchRegs alloc_regs(2 + needsBoxingReg);
+ alloc_regs.Apply(code_ir);
+ auto reg_iterator = alloc_regs.ScratchRegs().begin();
+ // register that will store size of during allocation
+ // later will be reused to store index when do "aput"
+ dex::u4 array_size_reg = *(reg_iterator);
+ // register that will store an array that will be passed
+ // as a parameter in entry hook
+ dex::u4 array_reg = *(++reg_iterator);
+ // if we need to boxing, this register stores result of boxing
+ dex::u4 boxing_reg = needsBoxingReg ? *(++reg_iterator) : 0;
+
+ // TODO: handle very "high" registers
+ if (boxing_reg > 0xff) {
+ printf("WARNING: can't instrument method %s.%s%s\n",
+ ir_method->decl->parent->Decl().c_str(),
+ ir_method->decl->name->c_str(),
+ ir_method->decl->prototype->Signature().c_str());
+ return false;
+ }
+
+ // array size bytecode
+ auto const_size_op = code_ir->Alloc<lir::Bytecode>();
+ const_size_op->opcode = dex::OP_CONST;
+ const_size_op->operands.push_back(code_ir->Alloc<lir::VReg>(array_size_reg));
+ const_size_op->operands.push_back(code_ir->Alloc<lir::Const32>(param_types.size() + !is_static));
+ code_ir->instructions.InsertBefore(bytecode, const_size_op);
+
+ // allocate array
+ const auto obj_array_type = builder.GetType("[Ljava/lang/Object;");
+ auto allocate_array_op = code_ir->Alloc<lir::Bytecode>();
+ allocate_array_op->opcode = dex::OP_NEW_ARRAY;
+ allocate_array_op->operands.push_back(code_ir->Alloc<lir::VReg>(array_reg));
+ allocate_array_op->operands.push_back(code_ir->Alloc<lir::VReg>(array_size_reg));
+ allocate_array_op->operands.push_back(
+ code_ir->Alloc<lir::Type>(obj_array_type, obj_array_type->orig_index));
+ code_ir->instructions.InsertBefore(bytecode, allocate_array_op);
+
+ // fill the array with parameters passed into function
+
+ std::vector<ir::Type*> types;
+ if (!is_static) {
+ types.push_back(ir_method->decl->parent);
+ }
+
+ types.insert(types.end(), param_types.begin(), param_types.end());
+
+ // register where params start
+ dex::u4 current_reg = ir_method->code->registers - ir_method->code->ins_count;
+ // reuse not needed anymore register to store indexes
+ dex::u4 array_index_reg = array_size_reg;
+ int i = 0;
+ for (auto type: types) {
+ dex::u4 src_reg = 0;
+ if (type->GetCategory() != ir::Type::Category::Reference) {
+ BoxValue(bytecode, code_ir, type, current_reg, boxing_reg);
+ src_reg = boxing_reg;
+ current_reg += 1 + (type->GetCategory() == ir::Type::Category::WideScalar);
+ } else {
+ src_reg = current_reg;
+ current_reg++;
+ }
+
+ auto index_const_op = code_ir->Alloc<lir::Bytecode>();
+ index_const_op->opcode = dex::OP_CONST;
+ index_const_op->operands.push_back(code_ir->Alloc<lir::VReg>(array_index_reg));
+ index_const_op->operands.push_back(code_ir->Alloc<lir::Const32>(i++));
+ code_ir->instructions.InsertBefore(bytecode, index_const_op);
+
+ auto aput_op = code_ir->Alloc<lir::Bytecode>();
+ aput_op->opcode = dex::OP_APUT_OBJECT;
+ aput_op->operands.push_back(code_ir->Alloc<lir::VReg>(src_reg));
+ aput_op->operands.push_back(code_ir->Alloc<lir::VReg>(array_reg));
+ aput_op->operands.push_back(code_ir->Alloc<lir::VReg>(array_index_reg));
+ code_ir->instructions.InsertBefore(bytecode, aput_op);
+ }
+
+ std::vector<ir::Type*> hook_param_types;
+ hook_param_types.push_back(obj_array_type);
+
+ auto ir_proto = builder.GetProto(builder.GetType("V"),
+ builder.GetTypeList(hook_param_types));
+
+ auto ir_method_decl = builder.GetMethodDecl(
+ builder.GetAsciiString(hook_method_id_.method_name), ir_proto,
+ builder.GetType(hook_method_id_.class_descriptor));
+
+ auto hook_method = code_ir->Alloc<lir::Method>(ir_method_decl, ir_method_decl->orig_index);
+ auto args = code_ir->Alloc<lir::VRegRange>(array_reg, 1);
+ auto hook_invoke = code_ir->Alloc<lir::Bytecode>();
+ hook_invoke->opcode = dex::OP_INVOKE_STATIC_RANGE;
+ hook_invoke->operands.push_back(args);
+ hook_invoke->operands.push_back(hook_method);
+ code_ir->instructions.InsertBefore(bytecode, hook_invoke);
return true;
}
diff --git a/testdata/expected/mi.array_entry_hook b/testdata/expected/mi.array_entry_hook
new file mode 100644
index 0000000..8f2a5e3
--- /dev/null
+++ b/testdata/expected/mi.array_entry_hook
@@ -0,0 +1,99 @@
+
+method Base.<init>():void
+{
+ .src "Target.java"
+ .line 1
+ .prologue_end
+ .line 1
+ 0| invoke-direct {v0}, java.lang.Object.<init>():void
+ 3| return-void
+}
+
+method Base.foo(int, java.lang.String):int
+{
+ .params "?", "?"
+ .src "Target.java"
+ .line 3
+ .prologue_end
+ .line 3
+ 0| const/4 v0, #+0 (0x00000000 | 0.00000)
+ 1| return v0
+}
+
+method IBase.bar(java.lang.String):void
+{
+}
+
+method Target.<init>(Base, IBase):void
+{
+ .params "?", "?"
+ .src "Target.java"
+ .line 15
+ .prologue_end
+ .line 15
+ 0| invoke-direct {v0}, java.lang.Object.<init>():void
+ .line 16
+ 3| iput-object v1, v0, Target.base
+ .line 17
+ 5| iput-object v2, v0, Target.iBase
+ .line 18
+ 7| return-void
+}
+
+method Target.foo(int, java.lang.String):int
+{
+ .params "?", "?"
+ .src "Target.java"
+ .line 21
+ .prologue_end
+ .line 21
+ 0| const v0, #+3 (0x00000003 | 4.20390e-45)
+ 3| new-array v1, v0, java.lang.Object[]
+ 5| const v0, #+0 (0x00000000 | 0.00000)
+ 8| aput-object v5, v1, v0
+ 10| invoke-static/range {v6..v6}, java.lang.Integer.valueOf(int):java.lang.Integer
+ 13| move-result-object v2
+ 14| const v0, #+1 (0x00000001 | 1.40130e-45)
+ 17| aput-object v2, v1, v0
+ 19| const v0, #+2 (0x00000002 | 2.80260e-45)
+ 22| aput-object v7, v1, v0
+ 24| invoke-static/range {v1..v1}, Tracer.onFooEntry(java.lang.Object[]):void
+ 27| iget-object v3, v5, Target.base
+ 29| invoke-virtual {v3,v6,v7}, Base.foo(int, java.lang.String):int
+ 32| move-result v3
+ .line 22
+ 33| iget-object v4, v5, Target.iBase
+ 35| invoke-interface {v4,v7}, IBase.bar(java.lang.String):void
+ .line 23
+ 38| return v3
+}
+
+method Target.foo(int, java.lang.String[][]):java.lang.Integer
+{
+ .params "?", "?"
+ .src "Target.java"
+ .line 27
+ .prologue_end
+ .line 27
+ 0| const v0, #+3 (0x00000003 | 4.20390e-45)
+ 3| new-array v1, v0, java.lang.Object[]
+ 5| const v0, #+0 (0x00000000 | 0.00000)
+ 8| aput-object v5, v1, v0
+ 10| invoke-static/range {v6..v6}, java.lang.Integer.valueOf(int):java.lang.Integer
+ 13| move-result-object v2
+ 14| const v0, #+1 (0x00000001 | 1.40130e-45)
+ 17| aput-object v2, v1, v0
+ 19| const v0, #+2 (0x00000002 | 2.80260e-45)
+ 22| aput-object v7, v1, v0
+ 24| invoke-static/range {v1..v1}, Tracer.onFooEntry(java.lang.Object[]):void
+ 27| iget-object v3, v5, Target.base
+ 29| const-string v4, "foo"
+ 31| invoke-virtual {v3,v6,v4}, Base.foo(int, java.lang.String):int
+ .line 28
+ 34| iget-object v3, v5, Target.iBase
+ 36| const-string v4, "bar"
+ 38| invoke-interface {v3,v4}, IBase.bar(java.lang.String):void
+ .line 29
+ 41| const/4 v3, #+0 (0x00000000 | 0.00000)
+ 42| return-object v3
+}