• 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   enum class Tweak {
44     None,
45     // Expose the "this" argument of non-static methods as the "Object" type.
46     // This can be helpful when the code you want to handle the hook doesn't
47     // have access to the actual type in its classpath.
48     ThisAsObject,
49     // Forward incoming arguments as an array. Zero-th element of the array is
50     // the method signature. First element of the array is
51     // "this" object if instrumented method isn't static.
52     // It is helpul, when you inject the same hook into the different
53     // methods.
54     ArrayParams,
55   };
56 
EntryHook(const ir::MethodId & hook_method_id,Tweak tweak)57   explicit EntryHook(const ir::MethodId& hook_method_id, Tweak tweak)
58       : hook_method_id_(hook_method_id), tweak_(tweak) {
59     // hook method signature is generated automatically
60     SLICER_CHECK(hook_method_id_.signature == nullptr);
61   }
62 
63   // TODO: Delete this legacy constrcutor.
64   // It is left in temporarily so we can move callers away from it to the new
65   // `tweak` constructor.
66   explicit EntryHook(const ir::MethodId& hook_method_id,
67                      bool use_object_type_for_this_argument = false)
68       : EntryHook(hook_method_id, use_object_type_for_this_argument
69                                       ? Tweak::ThisAsObject
70                                       : Tweak::None) {}
71 
72   virtual bool Apply(lir::CodeIr* code_ir) override;
73 
74  private:
75   ir::MethodId hook_method_id_;
76   Tweak tweak_;
77 
78   bool InjectArrayParamsHook(lir::CodeIr* code_ir, lir::Bytecode* bytecode);
79 };
80 
81 // Insert a call to the "exit hook" method before every return
82 // in the instrumented method. The "exit hook" will be passed the
83 // original return value and it may return a new return value.
84 class ExitHook : public Transformation {
85  public:
86   enum class Tweak {
87     None = 0,
88     // return value will be passed as "Object" type.
89     // This can be helpful when the code you want to handle the hook doesn't
90     // have access to the actual type in its classpath or when you want to inject
91     // the same hook in multiple methods.
92     ReturnAsObject = 1 << 0,
93     // Pass method signature as the first parameter of the hook method.
94     PassMethodSignature = 1 << 1,
95   };
96 
ExitHook(const ir::MethodId & hook_method_id,Tweak tweak)97    explicit ExitHook(const ir::MethodId& hook_method_id, Tweak tweak)
98       : hook_method_id_(hook_method_id), tweak_(tweak) {
99     // hook method signature is generated automatically
100     SLICER_CHECK(hook_method_id_.signature == nullptr);
101   }
102 
ExitHook(const ir::MethodId & hook_method_id)103   explicit ExitHook(const ir::MethodId& hook_method_id) : ExitHook(hook_method_id, Tweak::None) {}
104 
105   virtual bool Apply(lir::CodeIr* code_ir) override;
106 
107  private:
108   ir::MethodId hook_method_id_;
109   Tweak tweak_;
110 };
111 
112 inline ExitHook::Tweak operator|(ExitHook::Tweak a, ExitHook::Tweak b) {
113   return static_cast<ExitHook::Tweak>(static_cast<int>(a) | static_cast<int>(b));
114 }
115 
116 inline int operator&(ExitHook::Tweak a, ExitHook::Tweak b) {
117   return static_cast<int>(a) & static_cast<int>(b);
118 }
119 
120 // Base class for detour hooks. Replace every occurrence of specific opcode with
121 // something else. The detour is a static method which takes the same arguments
122 // as the original method plus an explicit "this" argument and returns the same
123 // type as the original method. Derived classes must implement GetNewOpcode.
124 class DetourHook : public Transformation {
125  public:
DetourHook(const ir::MethodId & orig_method_id,const ir::MethodId & detour_method_id)126   DetourHook(const ir::MethodId& orig_method_id,
127              const ir::MethodId& detour_method_id)
128       : orig_method_id_(orig_method_id), detour_method_id_(detour_method_id) {
129     // detour method signature is automatically created
130     // to match the original method and must not be explicitly specified
131     SLICER_CHECK(detour_method_id_.signature == nullptr);
132   }
133 
134   virtual bool Apply(lir::CodeIr* code_ir) override;
135 
136  protected:
137   ir::MethodId orig_method_id_;
138   ir::MethodId detour_method_id_;
139 
140   // Returns a new opcode to replace the desired opcode or OP_NOP otherwise.
141   virtual dex::Opcode GetNewOpcode(dex::Opcode opcode) = 0;
142 };
143 
144 // Replace every invoke-virtual[/range] to the a specified method with
145 // a invoke-static[/range] to the detour method.
146 class DetourVirtualInvoke : public DetourHook {
147  public:
DetourVirtualInvoke(const ir::MethodId & orig_method_id,const ir::MethodId & detour_method_id)148   DetourVirtualInvoke(const ir::MethodId& orig_method_id,
149                       const ir::MethodId& detour_method_id)
150       : DetourHook(orig_method_id, detour_method_id) {}
151 
152  protected:
153   virtual dex::Opcode GetNewOpcode(dex::Opcode opcode) override;
154 };
155 
156 // Replace every invoke-interface[/range] to the a specified method with
157 // a invoke-static[/range] to the detour method.
158 class DetourInterfaceInvoke : public DetourHook {
159  public:
DetourInterfaceInvoke(const ir::MethodId & orig_method_id,const ir::MethodId & detour_method_id)160   DetourInterfaceInvoke(const ir::MethodId& orig_method_id,
161                         const ir::MethodId& detour_method_id)
162       : DetourHook(orig_method_id, detour_method_id) {}
163 
164  protected:
165   virtual dex::Opcode GetNewOpcode(dex::Opcode opcode) override;
166 };
167 
168 // Allocates scratch registers without doing a full register allocation
169 class AllocateScratchRegs : public Transformation {
170  public:
171   explicit AllocateScratchRegs(int allocate_count, bool allow_renumbering = true)
allocate_count_(allocate_count)172     : allocate_count_(allocate_count), allow_renumbering_(allow_renumbering) {
173     SLICER_CHECK(allocate_count > 0);
174   }
175 
176   virtual bool Apply(lir::CodeIr* code_ir) override;
177 
ScratchRegs()178   const std::set<dex::u4>& ScratchRegs() const {
179     SLICER_CHECK(scratch_regs_.size() == static_cast<size_t>(allocate_count_));
180     return scratch_regs_;
181   }
182 
183  private:
184   void RegsRenumbering(lir::CodeIr* code_ir);
185   void ShiftParams(lir::CodeIr* code_ir);
186   void Allocate(lir::CodeIr* code_ir, dex::u4 first_reg, int count);
187 
188  private:
189   const int allocate_count_;
190   const bool allow_renumbering_;
191   int left_to_allocate_ = 0;
192   std::set<dex::u4> scratch_regs_;
193 };
194 
195 // A friendly helper for instrumenting existing methods: it allows batching
196 // a set of transformations to be applied to method (the batching allow it
197 // to build and encode the code IR once per method regardless of how many
198 // transformation are applied)
199 //
200 // For example, if we want to add both entry and exit hooks to a
201 // Hello.Test(int) method, the code would look like this:
202 //
203 //    ...
204 //    slicer::MethodInstrumenter mi(dex_ir);
205 //    mi.AddTransformation<slicer::EntryHook>(ir::MethodId("LTracer;", "OnEntry"));
206 //    mi.AddTransformation<slicer::ExitHook>(ir::MethodId("LTracer;", "OnExit"));
207 //    SLICER_CHECK(mi.InstrumentMethod(ir::MethodId("LHello;", "Test", "(I)I")));
208 //    ...
209 //
210 class MethodInstrumenter {
211  public:
MethodInstrumenter(std::shared_ptr<ir::DexFile> dex_ir)212   explicit MethodInstrumenter(std::shared_ptr<ir::DexFile> dex_ir) : dex_ir_(dex_ir) {}
213 
214   // No copy/move semantics
215   MethodInstrumenter(const MethodInstrumenter&) = delete;
216   MethodInstrumenter& operator=(const MethodInstrumenter&) = delete;
217 
218   // Queue a transformation
219   // (T is a class derived from Transformation)
220   template<class T, class... Args>
AddTransformation(Args &&...args)221   T* AddTransformation(Args&&... args) {
222     T* transformation = new T(std::forward<Args>(args)...);
223     transformations_.emplace_back(transformation);
224     return transformation;
225   }
226 
227   // Apply all the queued transformations to the specified method
228   bool InstrumentMethod(ir::EncodedMethod* ir_method);
229   bool InstrumentMethod(const ir::MethodId& method_id);
230 
231  private:
232   std::shared_ptr<ir::DexFile> dex_ir_;
233   std::vector<std::unique_ptr<Transformation>> transformations_;
234 };
235 
236 }  // namespace slicer
237