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