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