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