• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #pragma once
18 
19 #include "code_ir.h"
20 #include "common.h"
21 #include "dex_ir.h"
22 #include "dex_ir_builder.h"
23 
24 #include <memory>
25 #include <vector>
26 #include <utility>
27 #include <set>
28 
29 namespace slicer {
30 
31 // Interface for a single transformation operation
32 class Transformation {
33  public:
34   virtual ~Transformation() = default;
35   virtual bool Apply(lir::CodeIr* code_ir) = 0;
36 };
37 
38 // Insert a call to the "entry hook" at the start of the instrumented method:
39 // The "entry hook" will be forwarded the original incoming arguments plus
40 // an explicit "this" argument for non-static methods.
41 class EntryHook : public Transformation {
42  public:
43   explicit EntryHook(
44       const ir::MethodId& hook_method_id,
45       bool use_object_type_for_this_argument = false)
hook_method_id_(hook_method_id)46       : hook_method_id_(hook_method_id),
47         use_object_type_for_this_argument_(use_object_type_for_this_argument) {
48     // hook method signature is generated automatically
49     SLICER_CHECK(hook_method_id_.signature == nullptr);
50   }
51 
52   virtual bool Apply(lir::CodeIr* code_ir) override;
53 
54  private:
55   ir::MethodId hook_method_id_;
56   // If true, "this" argument of non-static methods is forwarded as Object type.
57   // For example "this" argument of OkHttpClient type is forwared as Object and
58   // is used to get OkHttp class loader.
59   bool use_object_type_for_this_argument_;
60 };
61 
62 // Insert a call to the "exit hook" method before every return
63 // in the instrumented method. The "exit hook" will be passed the
64 // original return value and it may return a new return value.
65 class ExitHook : public Transformation {
66  public:
ExitHook(const ir::MethodId & hook_method_id)67   explicit ExitHook(const ir::MethodId& hook_method_id) : hook_method_id_(hook_method_id) {
68     // hook method signature is generated automatically
69     SLICER_CHECK(hook_method_id_.signature == nullptr);
70   }
71 
72   virtual bool Apply(lir::CodeIr* code_ir) override;
73 
74  private:
75   ir::MethodId hook_method_id_;
76 };
77 
78 // Base class for detour hooks. Replace every occurrence of specific opcode with
79 // something else. The detour is a static method which takes the same arguments
80 // as the original method plus an explicit "this" argument and returns the same
81 // type as the original method. Derived classes must implement GetNewOpcode.
82 class DetourHook : public Transformation {
83  public:
DetourHook(const ir::MethodId & orig_method_id,const ir::MethodId & detour_method_id)84   DetourHook(const ir::MethodId& orig_method_id,
85              const ir::MethodId& detour_method_id)
86       : orig_method_id_(orig_method_id), detour_method_id_(detour_method_id) {
87     // detour method signature is automatically created
88     // to match the original method and must not be explicitly specified
89     SLICER_CHECK(detour_method_id_.signature == nullptr);
90   }
91 
92   virtual bool Apply(lir::CodeIr* code_ir) override;
93 
94  protected:
95   ir::MethodId orig_method_id_;
96   ir::MethodId detour_method_id_;
97 
98   // Returns a new opcode to replace the desired opcode or OP_NOP otherwise.
99   virtual dex::Opcode GetNewOpcode(dex::Opcode opcode) = 0;
100 };
101 
102 // Replace every invoke-virtual[/range] to the a specified method with
103 // a invoke-static[/range] to the detour method.
104 class DetourVirtualInvoke : public DetourHook {
105  public:
DetourVirtualInvoke(const ir::MethodId & orig_method_id,const ir::MethodId & detour_method_id)106   DetourVirtualInvoke(const ir::MethodId& orig_method_id,
107                       const ir::MethodId& detour_method_id)
108       : DetourHook(orig_method_id, detour_method_id) {}
109 
110  protected:
111   virtual dex::Opcode GetNewOpcode(dex::Opcode opcode) override;
112 };
113 
114 // Replace every invoke-interface[/range] to the a specified method with
115 // a invoke-static[/range] to the detour method.
116 class DetourInterfaceInvoke : public DetourHook {
117  public:
DetourInterfaceInvoke(const ir::MethodId & orig_method_id,const ir::MethodId & detour_method_id)118   DetourInterfaceInvoke(const ir::MethodId& orig_method_id,
119                         const ir::MethodId& detour_method_id)
120       : DetourHook(orig_method_id, detour_method_id) {}
121 
122  protected:
123   virtual dex::Opcode GetNewOpcode(dex::Opcode opcode) override;
124 };
125 
126 // Allocates scratch registers without doing a full register allocation
127 class AllocateScratchRegs : public Transformation {
128  public:
129   explicit AllocateScratchRegs(int allocate_count, bool allow_renumbering = true)
allocate_count_(allocate_count)130     : allocate_count_(allocate_count), allow_renumbering_(allow_renumbering) {
131     SLICER_CHECK(allocate_count > 0);
132   }
133 
134   virtual bool Apply(lir::CodeIr* code_ir) override;
135 
ScratchRegs()136   const std::set<dex::u4>& ScratchRegs() const {
137     SLICER_CHECK(scratch_regs_.size() == static_cast<size_t>(allocate_count_));
138     return scratch_regs_;
139   }
140 
141  private:
142   void RegsRenumbering(lir::CodeIr* code_ir);
143   void ShiftParams(lir::CodeIr* code_ir);
144   void Allocate(lir::CodeIr* code_ir, dex::u4 first_reg, int count);
145 
146  private:
147   const int allocate_count_;
148   const bool allow_renumbering_;
149   int left_to_allocate_ = 0;
150   std::set<dex::u4> scratch_regs_;
151 };
152 
153 // A friendly helper for instrumenting existing methods: it allows batching
154 // a set of transformations to be applied to method (the batching allow it
155 // to build and encode the code IR once per method regardless of how many
156 // transformation are applied)
157 //
158 // For example, if we want to add both entry and exit hooks to a
159 // Hello.Test(int) method, the code would look like this:
160 //
161 //    ...
162 //    slicer::MethodInstrumenter mi(dex_ir);
163 //    mi.AddTransformation<slicer::EntryHook>(ir::MethodId("LTracer;", "OnEntry"));
164 //    mi.AddTransformation<slicer::ExitHook>(ir::MethodId("LTracer;", "OnExit"));
165 //    SLICER_CHECK(mi.InstrumentMethod(ir::MethodId("LHello;", "Test", "(I)I")));
166 //    ...
167 //
168 class MethodInstrumenter {
169  public:
MethodInstrumenter(std::shared_ptr<ir::DexFile> dex_ir)170   explicit MethodInstrumenter(std::shared_ptr<ir::DexFile> dex_ir) : dex_ir_(dex_ir) {}
171 
172   // No copy/move semantics
173   MethodInstrumenter(const MethodInstrumenter&) = delete;
174   MethodInstrumenter& operator=(const MethodInstrumenter&) = delete;
175 
176   // Queue a transformation
177   // (T is a class derived from Transformation)
178   template<class T, class... Args>
AddTransformation(Args &&...args)179   T* AddTransformation(Args&&... args) {
180     T* transformation = new T(std::forward<Args>(args)...);
181     transformations_.emplace_back(transformation);
182     return transformation;
183   }
184 
185   // Apply all the queued transformations to the specified method
186   bool InstrumentMethod(ir::EncodedMethod* ir_method);
187   bool InstrumentMethod(const ir::MethodId& method_id);
188 
189  private:
190   std::shared_ptr<ir::DexFile> dex_ir_;
191   std::vector<std::unique_ptr<Transformation>> transformations_;
192 };
193 
194 }  // namespace slicer
195