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
+}