Merge "Pass method signature in exit and entry hooks" am: c927914ee5 am: d12e952462 am: 2b79f52c3e Original change: https://android-review.googlesource.com/c/platform/tools/dexter/+/1609217 MUST ONLY BE SUBMITTED BY AUTOMERGER Change-Id: Ie6ba6bb66f282326648311fdd5edbbf0e5292076
diff --git a/dexter/dexter_tests.py b/dexter/dexter_tests.py index 5b639ee..0e7bc10 100644 --- a/dexter/dexter_tests.py +++ b/dexter/dexter_tests.py
@@ -48,6 +48,7 @@ 'code_coverage' : { 'args' : '-d -x code_coverage', 'input' : ['*.dex'] }, 'array_entry_hook' : { 'args' : '-d -x array_param_entry_hook', 'input' : ['mi.dex'] }, 'object_exit_hook' : { 'args' : '-d -x return_obj_exit_hook', 'input' : ['mi.dex'] }, + 'sign_exit_hook' : { 'args' : '-d -x pass_sign_exit_hook', 'input' : ['mi.dex'] }, } # run a shell command and returns the stdout content
diff --git a/dexter/experimental.cc b/dexter/experimental.cc index 47ae29e..114980a 100644 --- a/dexter/experimental.cc +++ b/dexter/experimental.cc
@@ -514,6 +514,16 @@ SLICER_CHECK(mi.InstrumentMethod(method)); } +// Test slicer::MethodInstrumenter + Tweak::ReturnAsObject + Tweak::PassMethodSignature +void TestPassMethodSignatureExitHook(std::shared_ptr<ir::DexFile> dex_ir) { + slicer::MethodInstrumenter mi(dex_ir); + mi.AddTransformation<slicer::ExitHook>(ir::MethodId("LTracer;", "onFooExit"), + slicer::ExitHook::Tweak::ReturnAsObject | + slicer::ExitHook::Tweak::PassMethodSignature); + + auto method = ir::MethodId("LTarget;", "foo", "(I[[Ljava/lang/String;)Ljava/lang/Integer;"); + SLICER_CHECK(mi.InstrumentMethod(method)); +} void ListExperiments(std::shared_ptr<ir::DexFile> dex_ir); @@ -533,7 +543,7 @@ { "code_coverage", &CodeCoverage }, { "array_param_entry_hook", &TestArrayParamsEntryHook }, { "return_obj_exit_hook", &TestReturnAsObjectExitHook }, - + { "pass_sign_exit_hook", &TestPassMethodSignatureExitHook }, }; // Lists all the registered experiments
diff --git a/slicer/export/slicer/instrumentation.h b/slicer/export/slicer/instrumentation.h index 0cb2cc1..eca18c0 100644 --- a/slicer/export/slicer/instrumentation.h +++ b/slicer/export/slicer/instrumentation.h
@@ -47,6 +47,7 @@ // have access to the actual type in its classpath. ThisAsObject, // Forward incoming arguments as an array. Zero-th element of the array is + // the method signature. First 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. @@ -83,12 +84,14 @@ class ExitHook : public Transformation { public: enum class Tweak { - None, + None = 0, // return value will be passed as "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 or when you want to inject // the same hook in multiple methods. - ReturnAsObject, + ReturnAsObject = 1 << 0, + // Pass method signature as the first parameter of the hook method. + PassMethodSignature = 1 << 1, }; explicit ExitHook(const ir::MethodId& hook_method_id, Tweak tweak) @@ -106,6 +109,14 @@ Tweak tweak_; }; +inline ExitHook::Tweak operator|(ExitHook::Tweak a, ExitHook::Tweak b) { + return static_cast<ExitHook::Tweak>(static_cast<int>(a) | static_cast<int>(b)); +} + +inline int operator&(ExitHook::Tweak a, ExitHook::Tweak b) { + return static_cast<int>(a) & static_cast<int>(b); +} + // Base class for detour hooks. Replace every occurrence of specific opcode with // something else. The detour is a static method which takes the same arguments // as the original method plus an explicit "this" argument and returns the same
diff --git a/slicer/instrumentation.cc b/slicer/instrumentation.cc index f0dfbdf..5c51741 100644 --- a/slicer/instrumentation.cc +++ b/slicer/instrumentation.cc
@@ -91,6 +91,11 @@ code_ir->instructions.InsertBefore(bytecode, move_result); } +std::string MethodLabel(ir::EncodedMethod* ir_method) { + auto signature_str = ir_method->decl->prototype->Signature(); + return ir_method->decl->parent->Decl() + "->" + ir_method->decl->name->c_str() + signature_str; +} + } // namespace bool EntryHook::Apply(lir::CodeIr* code_ir) { @@ -213,13 +218,8 @@ auto param_types = param_types_list != nullptr ? param_types_list->types : std::vector<ir::Type*>(); 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; - } - // number of registers that we need to operate - dex::u2 regs_count = 2 + needsBoxingReg; + dex::u2 regs_count = 3; auto non_param_regs = ir_method->code->registers - ir_method->code->ins_count; // do we have enough registers to operate? @@ -230,21 +230,23 @@ code_ir->ir_method->code->registers += regs_count - non_param_regs; } - // simply use three first registry now - + // use three first registers: + // all three are needed when we "aput" a string/boxed-value (1) into an array (2) at an index (3) + // register that will store size of during allocation // later will be reused to store index when do "aput" dex::u4 array_size_reg = 0; // register that will store an array that will be passed // as a parameter in entry hook dex::u4 array_reg = 1; - // if we need to boxing, this register stores result of boxing - dex::u4 boxing_reg = needsBoxingReg ? 2 : 0; + // stores result of boxing (if it's needed); also stores the method signature string + dex::u4 value_reg = 2; // 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)); + const_size_op->operands.push_back(code_ir->Alloc<lir::Const32>( + 2 + param_types.size())); // method signature + params + "this" object code_ir->instructions.InsertBefore(bytecode, const_size_op); // allocate array @@ -260,11 +262,12 @@ // fill the array with parameters passed into function std::vector<ir::Type*> types; + types.push_back(builder.GetType("Ljava/lang/String;")); // method signature string if (!is_static) { - types.push_back(ir_method->decl->parent); + types.push_back(ir_method->decl->parent); // "this" object } - types.insert(types.end(), param_types.begin(), param_types.end()); + types.insert(types.end(), param_types.begin(), param_types.end()); // parameters // register where params start dex::u4 current_reg = ir_method->code->registers - ir_method->code->ins_count; @@ -273,9 +276,20 @@ 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; + if (i == 0) { // method signature string + // e.g. const-string v2, "(I[Ljava/lang/String;)Ljava/lang/String;" + // for (int, String[]) -> String + auto const_str_op = code_ir->Alloc<lir::Bytecode>(); + const_str_op->opcode = dex::OP_CONST_STRING; + const_str_op->operands.push_back(code_ir->Alloc<lir::VReg>(value_reg)); // dst + auto method_label = builder.GetAsciiString(MethodLabel(ir_method).c_str()); + const_str_op->operands.push_back( + code_ir->Alloc<lir::String>(method_label, method_label->orig_index)); // src + code_ir->instructions.InsertBefore(bytecode, const_str_op); + src_reg = value_reg; + } else if (type->GetCategory() != ir::Type::Category::Reference) { + BoxValue(bytecode, code_ir, type, current_reg, value_reg); + src_reg = value_reg; current_reg += 1 + (type->GetCategory() == ir::Type::Category::WideScalar); } else { src_reg = current_reg; @@ -294,6 +308,10 @@ 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); + + // if function is static, then jumping over index 1 + // since null should be be passed in this case + if (i == 1 && is_static) i++; } std::vector<ir::Type*> hook_param_types; @@ -336,7 +354,7 @@ ir::Builder builder(code_ir->dex_ir); const auto ir_method = code_ir->ir_method; const auto declared_return_type = ir_method->decl->prototype->return_type; - bool return_as_object = tweak_ == Tweak::ReturnAsObject; + bool return_as_object = (tweak_ & Tweak::ReturnAsObject) != 0; // do we have a void-return method? bool return_void = (::strcmp(declared_return_type->descriptor->c_str(), "V") == 0); // returnAsObject supports only object return type; @@ -345,8 +363,12 @@ const auto return_type = return_as_object ? builder.GetType("Ljava/lang/Object;") : declared_return_type; + bool pass_method_signature = (tweak_ & Tweak::PassMethodSignature) != 0; // construct the hook method declaration std::vector<ir::Type*> param_types; + if (pass_method_signature) { + param_types.push_back(builder.GetType("Ljava/lang/String;")); + } if (!return_void) { param_types.push_back(return_type); } @@ -371,7 +393,6 @@ dex::Opcode move_result_opcode = dex::OP_NOP; dex::u4 reg = 0; int reg_count = 0; - switch (bytecode->opcode) { case dex::OP_RETURN_VOID: SLICER_CHECK(return_void); @@ -399,8 +420,62 @@ continue; } - // invoke hook bytecode - auto args = code_ir->Alloc<lir::VRegRange>(reg, reg_count); + dex::u4 scratch_reg = 0; + // load method signature into scratch_reg + if (pass_method_signature) { + // is there a register that can be overtaken + bool needsScratchReg = ir_method->code->registers < reg_count + 1; + if (needsScratchReg) { + // don't renumber registers underneath us + slicer::AllocateScratchRegs alloc_regs(1, false); + alloc_regs.Apply(code_ir); + } + + // we need use one register before results to put signature there + // however result starts in register 0, thefore it is shifted + // to register 1 + if (reg == 0 && bytecode->opcode != dex::OP_RETURN_VOID) { + auto move_op = code_ir->Alloc<lir::Bytecode>(); + switch (bytecode->opcode) { + case dex::OP_RETURN_OBJECT: + move_op->opcode = dex::OP_MOVE_OBJECT_16; + move_op->operands.push_back(code_ir->Alloc<lir::VReg>(reg + 1)); + move_op->operands.push_back(code_ir->Alloc<lir::VReg>(reg)); + break; + case dex::OP_RETURN: + move_op->opcode = dex::OP_MOVE_16; + move_op->operands.push_back(code_ir->Alloc<lir::VReg>(reg + 1)); + move_op->operands.push_back(code_ir->Alloc<lir::VReg>(reg)); + break; + case dex::OP_RETURN_WIDE: + move_op->opcode = dex::OP_MOVE_WIDE_16; + move_op->operands.push_back(code_ir->Alloc<lir::VRegPair>(reg + 1)); + move_op->operands.push_back(code_ir->Alloc<lir::VRegPair>(reg)); + break; + default: + SLICER_FATAL("Unexpected opcode %d", bytecode->opcode); + } + code_ir->instructions.InsertBefore(bytecode, move_op); + // return is the last call, return is shifted to one, so taking over 0 registry + scratch_reg = 0; + } else { + // return is the last call, so we're taking over previous registry + scratch_reg = bytecode->opcode == dex::OP_RETURN_VOID ? 0 : reg - 1; + } + + + // return is the last call, so we're taking over previous registry + auto method_label = builder.GetAsciiString(MethodLabel(ir_method).c_str()); + auto const_str_op = code_ir->Alloc<lir::Bytecode>(); + const_str_op->opcode = dex::OP_CONST_STRING; + const_str_op->operands.push_back(code_ir->Alloc<lir::VReg>(scratch_reg)); // dst + const_str_op->operands.push_back(code_ir->Alloc<lir::String>(method_label, method_label->orig_index)); // src + code_ir->instructions.InsertBefore(bytecode, const_str_op); + } + + auto args = pass_method_signature + ? code_ir->Alloc<lir::VRegRange>(scratch_reg, reg_count + 1) + : code_ir->Alloc<lir::VRegRange>(reg, reg_count); auto hook_invoke = code_ir->Alloc<lir::Bytecode>(); hook_invoke->opcode = dex::OP_INVOKE_STATIC_RANGE; hook_invoke->operands.push_back(args); @@ -420,7 +495,7 @@ move_result->operands.push_back(bytecode->operands[0]); code_ir->instructions.InsertBefore(bytecode, move_result); - if (tweak_ == Tweak::ReturnAsObject) { + if ((tweak_ & Tweak::ReturnAsObject) != 0) { auto check_cast = code_ir->Alloc<lir::Bytecode>(); check_cast->opcode = dex::OP_CHECK_CAST; check_cast->operands.push_back(code_ir->Alloc<lir::VReg>(reg));
diff --git a/testdata/expected/mi.array_entry_hook b/testdata/expected/mi.array_entry_hook index 48eea67..5d134e2 100644 --- a/testdata/expected/mi.array_entry_hook +++ b/testdata/expected/mi.array_entry_hook
@@ -47,31 +47,34 @@ .line 21 .prologue_end .line 21 - 0| const v0, #+3 (0x00000003 | 4.20390e-45) + 0| const v0, #+4 (0x00000004 | 5.60519e-45) 3| new-array v1, v0, java.lang.Object[] - 5| const v0, #+0 (0x00000000 | 0.00000) - 8| aput-object v3, v1, v0 - 10| invoke-static/range {v4..v4}, 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 v5, v1, v0 - 24| invoke-static/range {v1..v1}, Tracer.onFooEntry(java.lang.Object[]):void - 27| const v0, #-16843010 (0xfefefefe | -1.69474e+38) - 30| const v1, #-16843010 (0xfefefefe | -1.69474e+38) - 33| const v2, #-16843010 (0xfefefefe | -1.69474e+38) - 36| move-object/16 v2, v3 - 39| move/16 v3, v4 - 42| move-object/16 v4, v5 - 45| iget-object v0, v2, Target.base - 47| invoke-virtual {v0,v3,v4}, Base.foo(int, java.lang.String):int - 50| move-result v0 + 5| const-string v2, "Target->foo(ILjava/lang/String;)I" + 7| const v0, #+0 (0x00000000 | 0.00000) + 10| aput-object v2, v1, v0 + 12| const v0, #+1 (0x00000001 | 1.40130e-45) + 15| aput-object v3, v1, v0 + 17| invoke-static/range {v4..v4}, java.lang.Integer.valueOf(int):java.lang.Integer + 20| move-result-object v2 + 21| const v0, #+2 (0x00000002 | 2.80260e-45) + 24| aput-object v2, v1, v0 + 26| const v0, #+3 (0x00000003 | 4.20390e-45) + 29| aput-object v5, v1, v0 + 31| invoke-static/range {v1..v1}, Tracer.onFooEntry(java.lang.Object[]):void + 34| const v0, #-16843010 (0xfefefefe | -1.69474e+38) + 37| const v1, #-16843010 (0xfefefefe | -1.69474e+38) + 40| const v2, #-16843010 (0xfefefefe | -1.69474e+38) + 43| move-object/16 v2, v3 + 46| move/16 v3, v4 + 49| move-object/16 v4, v5 + 52| iget-object v0, v2, Target.base + 54| invoke-virtual {v0,v3,v4}, Base.foo(int, java.lang.String):int + 57| move-result v0 .line 22 - 51| iget-object v1, v2, Target.iBase - 53| invoke-interface {v1,v4}, IBase.bar(java.lang.String):void + 58| iget-object v1, v2, Target.iBase + 60| invoke-interface {v1,v4}, IBase.bar(java.lang.String):void .line 23 - 56| return v0 + 63| return v0 } method Target.foo(int, java.lang.String[][]):java.lang.Integer @@ -81,31 +84,34 @@ .line 27 .prologue_end .line 27 - 0| const v0, #+3 (0x00000003 | 4.20390e-45) + 0| const v0, #+4 (0x00000004 | 5.60519e-45) 3| new-array v1, v0, java.lang.Object[] - 5| const v0, #+0 (0x00000000 | 0.00000) - 8| aput-object v3, v1, v0 - 10| invoke-static/range {v4..v4}, 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 v5, v1, v0 - 24| invoke-static/range {v1..v1}, Tracer.onFooEntry(java.lang.Object[]):void - 27| const v0, #-16843010 (0xfefefefe | -1.69474e+38) - 30| const v1, #-16843010 (0xfefefefe | -1.69474e+38) - 33| const v2, #-16843010 (0xfefefefe | -1.69474e+38) - 36| move-object/16 v2, v3 - 39| move/16 v3, v4 - 42| move-object/16 v4, v5 - 45| iget-object v0, v2, Target.base - 47| const-string v1, "foo" - 49| invoke-virtual {v0,v3,v1}, Base.foo(int, java.lang.String):int + 5| const-string v2, "Target->foo(I[[Ljava/lang/String;)Ljava/lang/Integer;" + 7| const v0, #+0 (0x00000000 | 0.00000) + 10| aput-object v2, v1, v0 + 12| const v0, #+1 (0x00000001 | 1.40130e-45) + 15| aput-object v3, v1, v0 + 17| invoke-static/range {v4..v4}, java.lang.Integer.valueOf(int):java.lang.Integer + 20| move-result-object v2 + 21| const v0, #+2 (0x00000002 | 2.80260e-45) + 24| aput-object v2, v1, v0 + 26| const v0, #+3 (0x00000003 | 4.20390e-45) + 29| aput-object v5, v1, v0 + 31| invoke-static/range {v1..v1}, Tracer.onFooEntry(java.lang.Object[]):void + 34| const v0, #-16843010 (0xfefefefe | -1.69474e+38) + 37| const v1, #-16843010 (0xfefefefe | -1.69474e+38) + 40| const v2, #-16843010 (0xfefefefe | -1.69474e+38) + 43| move-object/16 v2, v3 + 46| move/16 v3, v4 + 49| move-object/16 v4, v5 + 52| iget-object v0, v2, Target.base + 54| const-string v1, "foo" + 56| invoke-virtual {v0,v3,v1}, Base.foo(int, java.lang.String):int .line 28 - 52| iget-object v0, v2, Target.iBase - 54| const-string v1, "bar" - 56| invoke-interface {v0,v1}, IBase.bar(java.lang.String):void + 59| iget-object v0, v2, Target.iBase + 61| const-string v1, "bar" + 63| invoke-interface {v0,v1}, IBase.bar(java.lang.String):void .line 29 - 59| const/4 v0, #+0 (0x00000000 | 0.00000) - 60| return-object v0 + 66| const/4 v0, #+0 (0x00000000 | 0.00000) + 67| return-object v0 }
diff --git a/testdata/expected/mi.sign_exit_hook b/testdata/expected/mi.sign_exit_hook new file mode 100644 index 0000000..b6d06a0 --- /dev/null +++ b/testdata/expected/mi.sign_exit_hook
@@ -0,0 +1,82 @@ + +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| iget-object v0, v2, Target.base + 2| invoke-virtual {v0,v3,v4}, Base.foo(int, java.lang.String):int + 5| move-result v0 + .line 22 + 6| iget-object v1, v2, Target.iBase + 8| invoke-interface {v1,v4}, IBase.bar(java.lang.String):void + .line 23 + 11| return v0 +} + +method Target.foo(int, java.lang.String[][]):java.lang.Integer +{ + .params "?", "?" + .src "Target.java" + .line 27 + .prologue_end + .line 27 + 0| iget-object v0, v2, Target.base + 2| const-string v1, "foo" + 4| invoke-virtual {v0,v3,v1}, Base.foo(int, java.lang.String):int + .line 28 + 7| iget-object v0, v2, Target.iBase + 9| const-string v1, "bar" + 11| invoke-interface {v0,v1}, IBase.bar(java.lang.String):void + .line 29 + 14| const/4 v0, #+0 (0x00000000 | 0.00000) + 15| move-object/16 v1, v0 + 18| const-string v0, "Target->foo(I[[Ljava/lang/String;)Ljava/lang/Integer;" + 20| invoke-static/range {v0..v1}, Tracer.onFooExit(java.lang.String, java.lang.Object):java.lang.Object + 23| move-result-object v0 + 24| check-cast v0, java.lang.Integer + 26| return-object v0 +}