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