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 // Replace every invoke-virtual[/range] to the a specified method with
79 // a invoke-static[/range] to the detour method. The detour is a static
80 // method which takes the same arguments as the original method plus
81 // an explicit "this" argument, and returns the same type as the original method
82 class DetourVirtualInvoke : public Transformation {
83  public:
DetourVirtualInvoke(const ir::MethodId & orig_method_id,const ir::MethodId & detour_method_id)84   DetourVirtualInvoke(const ir::MethodId& orig_method_id, const ir::MethodId& detour_method_id)
85     : orig_method_id_(orig_method_id), detour_method_id_(detour_method_id) {
86     // detour method signature is automatically created
87     // to match the original method and must not be explicitly specified
88     SLICER_CHECK(detour_method_id_.signature == nullptr);
89   }
90 
91   virtual bool Apply(lir::CodeIr* code_ir) override;
92 
93  private:
94   ir::MethodId orig_method_id_;
95   ir::MethodId detour_method_id_;
96 };
97 
98 // Allocates scratch registers without doing a full register allocation
99 class AllocateScratchRegs : public Transformation {
100  public:
101   explicit AllocateScratchRegs(int allocate_count, bool allow_renumbering = true)
allocate_count_(allocate_count)102     : allocate_count_(allocate_count), allow_renumbering_(allow_renumbering) {
103     SLICER_CHECK(allocate_count > 0);
104   }
105 
106   virtual bool Apply(lir::CodeIr* code_ir) override;
107 
ScratchRegs()108   const std::set<dex::u4>& ScratchRegs() const {
109     SLICER_CHECK(scratch_regs_.size() == static_cast<size_t>(allocate_count_));
110     return scratch_regs_;
111   }
112 
113  private:
114   void RegsRenumbering(lir::CodeIr* code_ir);
115   void ShiftParams(lir::CodeIr* code_ir);
116   void Allocate(lir::CodeIr* code_ir, dex::u4 first_reg, int count);
117 
118  private:
119   const int allocate_count_;
120   const bool allow_renumbering_;
121   int left_to_allocate_ = 0;
122   std::set<dex::u4> scratch_regs_;
123 };
124 
125 // A friendly helper for instrumenting existing methods: it allows batching
126 // a set of transformations to be applied to method (the batching allow it
127 // to build and encode the code IR once per method regardless of how many
128 // transformation are applied)
129 //
130 // For example, if we want to add both entry and exit hooks to a
131 // Hello.Test(int) method, the code would look like this:
132 //
133 //    ...
134 //    slicer::MethodInstrumenter mi(dex_ir);
135 //    mi.AddTransformation<slicer::EntryHook>(ir::MethodId("LTracer;", "OnEntry"));
136 //    mi.AddTransformation<slicer::ExitHook>(ir::MethodId("LTracer;", "OnExit"));
137 //    SLICER_CHECK(mi.InstrumentMethod(ir::MethodId("LHello;", "Test", "(I)I")));
138 //    ...
139 //
140 class MethodInstrumenter {
141  public:
MethodInstrumenter(std::shared_ptr<ir::DexFile> dex_ir)142   explicit MethodInstrumenter(std::shared_ptr<ir::DexFile> dex_ir) : dex_ir_(dex_ir) {}
143 
144   // No copy/move semantics
145   MethodInstrumenter(const MethodInstrumenter&) = delete;
146   MethodInstrumenter& operator=(const MethodInstrumenter&) = delete;
147 
148   // Queue a transformation
149   // (T is a class derived from Transformation)
150   template<class T, class... Args>
AddTransformation(Args &&...args)151   T* AddTransformation(Args&&... args) {
152     T* transformation = new T(std::forward<Args>(args)...);
153     transformations_.emplace_back(transformation);
154     return transformation;
155   }
156 
157   // Apply all the queued transformations to the specified method
158   bool InstrumentMethod(ir::EncodedMethod* ir_method);
159   bool InstrumentMethod(const ir::MethodId& method_id);
160 
161  private:
162   std::shared_ptr<ir::DexFile> dex_ir_;
163   std::vector<std::unique_ptr<Transformation>> transformations_;
164 };
165 
166 }  // namespace slicer
167