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