xref: /aosp_15_r20/tools/dexter/dexter/experimental.cc (revision f0dffb02cdb5c647d21204e89a92a1ffae2dad87)
1*f0dffb02SXin Li /*
2*f0dffb02SXin Li  * Copyright (C) 2017 The Android Open Source Project
3*f0dffb02SXin Li  *
4*f0dffb02SXin Li  * Licensed under the Apache License, Version 2.0 (the "License");
5*f0dffb02SXin Li  * you may not use this file except in compliance with the License.
6*f0dffb02SXin Li  * You may obtain a copy of the License at
7*f0dffb02SXin Li  *
8*f0dffb02SXin Li  *      http://www.apache.org/licenses/LICENSE-2.0
9*f0dffb02SXin Li  *
10*f0dffb02SXin Li  * Unless required by applicable law or agreed to in writing, software
11*f0dffb02SXin Li  * distributed under the License is distributed on an "AS IS" BASIS,
12*f0dffb02SXin Li  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*f0dffb02SXin Li  * See the License for the specific language governing permissions and
14*f0dffb02SXin Li  * limitations under the License.
15*f0dffb02SXin Li  */
16*f0dffb02SXin Li 
17*f0dffb02SXin Li #include "experimental.h"
18*f0dffb02SXin Li 
19*f0dffb02SXin Li #include "slicer/code_ir.h"
20*f0dffb02SXin Li #include "slicer/control_flow_graph.h"
21*f0dffb02SXin Li #include "slicer/dex_ir.h"
22*f0dffb02SXin Li #include "slicer/dex_ir_builder.h"
23*f0dffb02SXin Li #include "slicer/instrumentation.h"
24*f0dffb02SXin Li 
25*f0dffb02SXin Li #include <string.h>
26*f0dffb02SXin Li #include <map>
27*f0dffb02SXin Li #include <memory>
28*f0dffb02SXin Li #include <vector>
29*f0dffb02SXin Li 
30*f0dffb02SXin Li namespace experimental {
31*f0dffb02SXin Li 
32*f0dffb02SXin Li // Rewrites every method through raising to code IR -> back to bytecode
33*f0dffb02SXin Li // (also stress the CFG creation)
FullRewrite(std::shared_ptr<ir::DexFile> dex_ir)34*f0dffb02SXin Li void FullRewrite(std::shared_ptr<ir::DexFile> dex_ir) {
35*f0dffb02SXin Li   for (auto& ir_method : dex_ir->encoded_methods) {
36*f0dffb02SXin Li     if (ir_method->code != nullptr) {
37*f0dffb02SXin Li       lir::CodeIr code_ir(ir_method.get(), dex_ir);
38*f0dffb02SXin Li       lir::ControlFlowGraph cfg_compact(&code_ir, false);
39*f0dffb02SXin Li       lir::ControlFlowGraph cfg_verbose(&code_ir, true);
40*f0dffb02SXin Li       code_ir.Assemble();
41*f0dffb02SXin Li     }
42*f0dffb02SXin Li   }
43*f0dffb02SXin Li }
44*f0dffb02SXin Li 
45*f0dffb02SXin Li // For every method body in the .dex image, replace invoke-virtual[/range]
46*f0dffb02SXin Li // instances with a invoke-static[/range] to a fictitious Tracer.WrapInvoke(<args...>)
47*f0dffb02SXin Li // WrapInvoke() is a static method which takes the same arguments as the
48*f0dffb02SXin Li // original method plus an explicit "this" argument, and returns the same
49*f0dffb02SXin Li // type as the original method.
StressWrapInvoke(std::shared_ptr<ir::DexFile> dex_ir)50*f0dffb02SXin Li void StressWrapInvoke(std::shared_ptr<ir::DexFile> dex_ir) {
51*f0dffb02SXin Li   for (auto& ir_method : dex_ir->encoded_methods) {
52*f0dffb02SXin Li     if (ir_method->code == nullptr) {
53*f0dffb02SXin Li       continue;
54*f0dffb02SXin Li     }
55*f0dffb02SXin Li 
56*f0dffb02SXin Li     lir::CodeIr code_ir(ir_method.get(), dex_ir);
57*f0dffb02SXin Li     ir::Builder builder(dex_ir);
58*f0dffb02SXin Li 
59*f0dffb02SXin Li     // search for invoke-virtual[/range] bytecodes
60*f0dffb02SXin Li     //
61*f0dffb02SXin Li     // NOTE: we may be removing the current bytecode
62*f0dffb02SXin Li     //   from the instructions list so we must use a
63*f0dffb02SXin Li     //   different iteration style (it++ is done before
64*f0dffb02SXin Li     //   handling *it, not after as in a normal iteration)
65*f0dffb02SXin Li     //
66*f0dffb02SXin Li     auto it = code_ir.instructions.begin();
67*f0dffb02SXin Li     while (it != code_ir.instructions.end()) {
68*f0dffb02SXin Li       auto instr = *it++;
69*f0dffb02SXin Li       auto bytecode = dynamic_cast<lir::Bytecode*>(instr);
70*f0dffb02SXin Li       if (bytecode == nullptr) {
71*f0dffb02SXin Li         continue;
72*f0dffb02SXin Li       }
73*f0dffb02SXin Li 
74*f0dffb02SXin Li       dex::Opcode new_call_opcode = dex::OP_NOP;
75*f0dffb02SXin Li       switch (bytecode->opcode) {
76*f0dffb02SXin Li         case dex::OP_INVOKE_VIRTUAL:
77*f0dffb02SXin Li           new_call_opcode = dex::OP_INVOKE_STATIC;
78*f0dffb02SXin Li           break;
79*f0dffb02SXin Li         case dex::OP_INVOKE_VIRTUAL_RANGE:
80*f0dffb02SXin Li           new_call_opcode = dex::OP_INVOKE_STATIC_RANGE;
81*f0dffb02SXin Li           break;
82*f0dffb02SXin Li         default:
83*f0dffb02SXin Li           // skip instruction ...
84*f0dffb02SXin Li           continue;
85*f0dffb02SXin Li       }
86*f0dffb02SXin Li       assert(new_call_opcode != dex::OP_NOP);
87*f0dffb02SXin Li 
88*f0dffb02SXin Li       auto orig_method = bytecode->CastOperand<lir::Method>(1)->ir_method;
89*f0dffb02SXin Li 
90*f0dffb02SXin Li       // construct the wrapper method declaration
91*f0dffb02SXin Li       std::vector<ir::Type*> param_types;
92*f0dffb02SXin Li       param_types.push_back(orig_method->parent);
93*f0dffb02SXin Li       if (orig_method->prototype->param_types != nullptr) {
94*f0dffb02SXin Li         const auto& orig_param_types = orig_method->prototype->param_types->types;
95*f0dffb02SXin Li         param_types.insert(param_types.end(), orig_param_types.begin(), orig_param_types.end());
96*f0dffb02SXin Li       }
97*f0dffb02SXin Li 
98*f0dffb02SXin Li       auto ir_proto = builder.GetProto(orig_method->prototype->return_type,
99*f0dffb02SXin Li                                        builder.GetTypeList(param_types));
100*f0dffb02SXin Li 
101*f0dffb02SXin Li       auto ir_method_decl = builder.GetMethodDecl(builder.GetAsciiString("WrapInvoke"),
102*f0dffb02SXin Li                                                   ir_proto,
103*f0dffb02SXin Li                                                   builder.GetType("LTracer;"));
104*f0dffb02SXin Li 
105*f0dffb02SXin Li       auto wraper_method = code_ir.Alloc<lir::Method>(ir_method_decl, ir_method_decl->orig_index);
106*f0dffb02SXin Li 
107*f0dffb02SXin Li       // new call bytecode
108*f0dffb02SXin Li       auto new_call = code_ir.Alloc<lir::Bytecode>();
109*f0dffb02SXin Li       new_call->opcode = new_call_opcode;
110*f0dffb02SXin Li       new_call->operands.push_back(bytecode->operands[0]);
111*f0dffb02SXin Li       new_call->operands.push_back(wraper_method);
112*f0dffb02SXin Li       code_ir.instructions.InsertBefore(bytecode, new_call);
113*f0dffb02SXin Li 
114*f0dffb02SXin Li       // remove the old call bytecode
115*f0dffb02SXin Li       //
116*f0dffb02SXin Li       // NOTE: we can mutate the original bytecode directly
117*f0dffb02SXin Li       //  since the instructions can't have multiple references
118*f0dffb02SXin Li       //  in the code IR, but for testing purposes we'll do it
119*f0dffb02SXin Li       //  the hard way here
120*f0dffb02SXin Li       //
121*f0dffb02SXin Li       code_ir.instructions.Remove(bytecode);
122*f0dffb02SXin Li     }
123*f0dffb02SXin Li 
124*f0dffb02SXin Li     code_ir.Assemble();
125*f0dffb02SXin Li   }
126*f0dffb02SXin Li }
127*f0dffb02SXin Li 
128*f0dffb02SXin Li // For every method in the .dex image, insert an "entry hook" call
129*f0dffb02SXin Li // to a fictitious method: Tracer.OnEntry(<args...>). OnEntry() has the
130*f0dffb02SXin Li // same argument types as the instrumented method plus an explicit
131*f0dffb02SXin Li // "this" for non-static methods. On entry to the instumented method
132*f0dffb02SXin Li // we'll call OnEntry() with the values of the incoming arguments.
133*f0dffb02SXin Li //
134*f0dffb02SXin Li // NOTE: the entry hook will forward all the incoming arguments
135*f0dffb02SXin Li //   so we need to define an Tracer.OnEntry overload for every method
136*f0dffb02SXin Li //   signature. This means that for very large .dex images, approaching
137*f0dffb02SXin Li //   the 64k method limit, we might not be able to allocate new method declarations.
138*f0dffb02SXin Li //   (which is ok, and a good test case, since this is a stress scenario)
139*f0dffb02SXin Li //
StressEntryHook(std::shared_ptr<ir::DexFile> dex_ir)140*f0dffb02SXin Li void StressEntryHook(std::shared_ptr<ir::DexFile> dex_ir) {
141*f0dffb02SXin Li   for (auto& ir_method : dex_ir->encoded_methods) {
142*f0dffb02SXin Li     if (ir_method->code == nullptr) {
143*f0dffb02SXin Li       continue;
144*f0dffb02SXin Li     }
145*f0dffb02SXin Li 
146*f0dffb02SXin Li     lir::CodeIr code_ir(ir_method.get(), dex_ir);
147*f0dffb02SXin Li     ir::Builder builder(dex_ir);
148*f0dffb02SXin Li 
149*f0dffb02SXin Li     // 1. construct call target
150*f0dffb02SXin Li     std::vector<ir::Type*> param_types;
151*f0dffb02SXin Li     if ((ir_method->access_flags & dex::kAccStatic) == 0) {
152*f0dffb02SXin Li       param_types.push_back(ir_method->decl->parent);
153*f0dffb02SXin Li     }
154*f0dffb02SXin Li     if (ir_method->decl->prototype->param_types != nullptr) {
155*f0dffb02SXin Li       const auto& orig_param_types = ir_method->decl->prototype->param_types->types;
156*f0dffb02SXin Li       param_types.insert(param_types.end(), orig_param_types.begin(), orig_param_types.end());
157*f0dffb02SXin Li     }
158*f0dffb02SXin Li 
159*f0dffb02SXin Li     auto ir_proto = builder.GetProto(builder.GetType("V"),
160*f0dffb02SXin Li                                      builder.GetTypeList(param_types));
161*f0dffb02SXin Li 
162*f0dffb02SXin Li     auto ir_method_decl = builder.GetMethodDecl(builder.GetAsciiString("OnEntry"),
163*f0dffb02SXin Li                                                 ir_proto,
164*f0dffb02SXin Li                                                 builder.GetType("LTracer;"));
165*f0dffb02SXin Li 
166*f0dffb02SXin Li     auto target_method = code_ir.Alloc<lir::Method>(ir_method_decl, ir_method_decl->orig_index);
167*f0dffb02SXin Li 
168*f0dffb02SXin Li     // 2. argument registers
169*f0dffb02SXin Li     auto regs = ir_method->code->registers;
170*f0dffb02SXin Li     auto args_count = ir_method->code->ins_count;
171*f0dffb02SXin Li     auto args = code_ir.Alloc<lir::VRegRange>(regs - args_count, args_count);
172*f0dffb02SXin Li 
173*f0dffb02SXin Li     // 3. call bytecode
174*f0dffb02SXin Li     auto call = code_ir.Alloc<lir::Bytecode>();
175*f0dffb02SXin Li     call->opcode = dex::OP_INVOKE_STATIC_RANGE;
176*f0dffb02SXin Li     call->operands.push_back(args);
177*f0dffb02SXin Li     call->operands.push_back(target_method);
178*f0dffb02SXin Li 
179*f0dffb02SXin Li     // 4. insert the hook before the first bytecode
180*f0dffb02SXin Li     for (auto instr : code_ir.instructions) {
181*f0dffb02SXin Li       auto bytecode = dynamic_cast<lir::Bytecode*>(instr);
182*f0dffb02SXin Li       if (bytecode == nullptr) {
183*f0dffb02SXin Li         continue;
184*f0dffb02SXin Li       }
185*f0dffb02SXin Li       code_ir.instructions.InsertBefore(bytecode, call);
186*f0dffb02SXin Li       break;
187*f0dffb02SXin Li     }
188*f0dffb02SXin Li 
189*f0dffb02SXin Li     code_ir.Assemble();
190*f0dffb02SXin Li   }
191*f0dffb02SXin Li }
192*f0dffb02SXin Li 
193*f0dffb02SXin Li // For every method in the .dex image, insert an "exit hook" call
194*f0dffb02SXin Li // to a fictitious method: Tracer.OnExit(<return value...>).
195*f0dffb02SXin Li // OnExit() is called right before returning from the instrumented
196*f0dffb02SXin Li // method (on the non-exceptional path) and it will be passed the return
197*f0dffb02SXin Li // value, if any. For non-void return types, the return value from OnExit()
198*f0dffb02SXin Li // will also be used as the return value of the instrumented method.
StressExitHook(std::shared_ptr<ir::DexFile> dex_ir)199*f0dffb02SXin Li void StressExitHook(std::shared_ptr<ir::DexFile> dex_ir) {
200*f0dffb02SXin Li   for (auto& ir_method : dex_ir->encoded_methods) {
201*f0dffb02SXin Li     if (ir_method->code == nullptr) {
202*f0dffb02SXin Li       continue;
203*f0dffb02SXin Li     }
204*f0dffb02SXin Li 
205*f0dffb02SXin Li     lir::CodeIr code_ir(ir_method.get(), dex_ir);
206*f0dffb02SXin Li     ir::Builder builder(dex_ir);
207*f0dffb02SXin Li 
208*f0dffb02SXin Li     // do we have a void-return method?
209*f0dffb02SXin Li     bool return_void =
210*f0dffb02SXin Li         ::strcmp(ir_method->decl->prototype->return_type->descriptor->c_str(), "V") == 0;
211*f0dffb02SXin Li 
212*f0dffb02SXin Li     // 1. construct call target
213*f0dffb02SXin Li     std::vector<ir::Type*> param_types;
214*f0dffb02SXin Li     if (!return_void) {
215*f0dffb02SXin Li       param_types.push_back(ir_method->decl->prototype->return_type);
216*f0dffb02SXin Li     }
217*f0dffb02SXin Li 
218*f0dffb02SXin Li     auto ir_proto = builder.GetProto(ir_method->decl->prototype->return_type,
219*f0dffb02SXin Li                                      builder.GetTypeList(param_types));
220*f0dffb02SXin Li 
221*f0dffb02SXin Li     auto ir_method_decl = builder.GetMethodDecl(builder.GetAsciiString("OnExit"),
222*f0dffb02SXin Li                                                 ir_proto,
223*f0dffb02SXin Li                                                 builder.GetType("LTracer;"));
224*f0dffb02SXin Li 
225*f0dffb02SXin Li     auto target_method = code_ir.Alloc<lir::Method>(ir_method_decl, ir_method_decl->orig_index);
226*f0dffb02SXin Li 
227*f0dffb02SXin Li     // 2. find and instrument the return instructions
228*f0dffb02SXin Li     for (auto instr : code_ir.instructions) {
229*f0dffb02SXin Li       auto bytecode = dynamic_cast<lir::Bytecode*>(instr);
230*f0dffb02SXin Li       if (bytecode == nullptr) {
231*f0dffb02SXin Li         continue;
232*f0dffb02SXin Li       }
233*f0dffb02SXin Li 
234*f0dffb02SXin Li       dex::Opcode move_result_opcode = dex::OP_NOP;
235*f0dffb02SXin Li       dex::u4 reg = 0;
236*f0dffb02SXin Li       int reg_count = 0;
237*f0dffb02SXin Li 
238*f0dffb02SXin Li       switch (bytecode->opcode) {
239*f0dffb02SXin Li         case dex::OP_RETURN_VOID:
240*f0dffb02SXin Li           SLICER_CHECK(return_void);
241*f0dffb02SXin Li           break;
242*f0dffb02SXin Li         case dex::OP_RETURN:
243*f0dffb02SXin Li           SLICER_CHECK(!return_void);
244*f0dffb02SXin Li           move_result_opcode = dex::OP_MOVE_RESULT;
245*f0dffb02SXin Li           reg = bytecode->CastOperand<lir::VReg>(0)->reg;
246*f0dffb02SXin Li           reg_count = 1;
247*f0dffb02SXin Li           break;
248*f0dffb02SXin Li         case dex::OP_RETURN_OBJECT:
249*f0dffb02SXin Li           SLICER_CHECK(!return_void);
250*f0dffb02SXin Li           move_result_opcode = dex::OP_MOVE_RESULT_OBJECT;
251*f0dffb02SXin Li           reg = bytecode->CastOperand<lir::VReg>(0)->reg;
252*f0dffb02SXin Li           reg_count = 1;
253*f0dffb02SXin Li           break;
254*f0dffb02SXin Li         case dex::OP_RETURN_WIDE:
255*f0dffb02SXin Li           SLICER_CHECK(!return_void);
256*f0dffb02SXin Li           move_result_opcode = dex::OP_MOVE_RESULT_WIDE;
257*f0dffb02SXin Li           reg = bytecode->CastOperand<lir::VRegPair>(0)->base_reg;
258*f0dffb02SXin Li           reg_count = 2;
259*f0dffb02SXin Li           break;
260*f0dffb02SXin Li         default:
261*f0dffb02SXin Li           // skip the bytecode...
262*f0dffb02SXin Li           continue;
263*f0dffb02SXin Li       }
264*f0dffb02SXin Li 
265*f0dffb02SXin Li       // the call bytecode
266*f0dffb02SXin Li       auto args = code_ir.Alloc<lir::VRegRange>(reg, reg_count);
267*f0dffb02SXin Li       auto call = code_ir.Alloc<lir::Bytecode>();
268*f0dffb02SXin Li       call->opcode = dex::OP_INVOKE_STATIC_RANGE;
269*f0dffb02SXin Li       call->operands.push_back(args);
270*f0dffb02SXin Li       call->operands.push_back(target_method);
271*f0dffb02SXin Li       code_ir.instructions.InsertBefore(bytecode, call);
272*f0dffb02SXin Li 
273*f0dffb02SXin Li       // move result back to the right register
274*f0dffb02SXin Li       //
275*f0dffb02SXin Li       // NOTE: we're reusing the original return's operand,
276*f0dffb02SXin Li       //   which is valid and more efficient than allocating
277*f0dffb02SXin Li       //   a new LIR node, but it's also fragile: we need to be
278*f0dffb02SXin Li       //   very careful about mutating shared nodes.
279*f0dffb02SXin Li       //
280*f0dffb02SXin Li       if (move_result_opcode != dex::OP_NOP) {
281*f0dffb02SXin Li         auto move_result = code_ir.Alloc<lir::Bytecode>();
282*f0dffb02SXin Li         move_result->opcode = move_result_opcode;
283*f0dffb02SXin Li         move_result->operands.push_back(bytecode->operands[0]);
284*f0dffb02SXin Li         code_ir.instructions.InsertBefore(bytecode, move_result);
285*f0dffb02SXin Li       }
286*f0dffb02SXin Li     }
287*f0dffb02SXin Li 
288*f0dffb02SXin Li     code_ir.Assemble();
289*f0dffb02SXin Li   }
290*f0dffb02SXin Li }
291*f0dffb02SXin Li 
292*f0dffb02SXin Li // Test slicer::MethodInstrumenter
TestMethodInstrumenter(std::shared_ptr<ir::DexFile> dex_ir)293*f0dffb02SXin Li void TestMethodInstrumenter(std::shared_ptr<ir::DexFile> dex_ir) {
294*f0dffb02SXin Li   slicer::MethodInstrumenter mi(dex_ir);
295*f0dffb02SXin Li   mi.AddTransformation<slicer::EntryHook>(
296*f0dffb02SXin Li       ir::MethodId("LTracer;", "onFooEntry"),
297*f0dffb02SXin Li       slicer::EntryHook::Tweak::ThisAsObject);
298*f0dffb02SXin Li   mi.AddTransformation<slicer::EntryHook>(
299*f0dffb02SXin Li       ir::MethodId("LTracer;", "onFooEntry"));
300*f0dffb02SXin Li   mi.AddTransformation<slicer::ExitHook>(ir::MethodId("LTracer;", "onFooExit"));
301*f0dffb02SXin Li   mi.AddTransformation<slicer::DetourVirtualInvoke>(
302*f0dffb02SXin Li       ir::MethodId("LBase;", "foo", "(ILjava/lang/String;)I"),
303*f0dffb02SXin Li       ir::MethodId("LTracer;", "wrapFoo"));
304*f0dffb02SXin Li   mi.AddTransformation<slicer::DetourInterfaceInvoke>(
305*f0dffb02SXin Li       ir::MethodId("LIBase;", "bar", "(Ljava/lang/String;)V"),
306*f0dffb02SXin Li       ir::MethodId("LTracer;", "wrapBar"));
307*f0dffb02SXin Li 
308*f0dffb02SXin Li   auto method1 = ir::MethodId("LTarget;", "foo", "(ILjava/lang/String;)I");
309*f0dffb02SXin Li   SLICER_CHECK(mi.InstrumentMethod(method1));
310*f0dffb02SXin Li 
311*f0dffb02SXin Li   auto method2 = ir::MethodId("LTarget;", "foo", "(I[[Ljava/lang/String;)Ljava/lang/Integer;");
312*f0dffb02SXin Li   SLICER_CHECK(mi.InstrumentMethod(method2));
313*f0dffb02SXin Li }
314*f0dffb02SXin Li 
315*f0dffb02SXin Li // Stress scratch register allocation
StressScratchRegs(std::shared_ptr<ir::DexFile> dex_ir)316*f0dffb02SXin Li void StressScratchRegs(std::shared_ptr<ir::DexFile> dex_ir) {
317*f0dffb02SXin Li   slicer::MethodInstrumenter mi(dex_ir);
318*f0dffb02SXin Li 
319*f0dffb02SXin Li   // queue multiple allocations to stress corner cases (various counts and alignments)
320*f0dffb02SXin Li   auto t1 = mi.AddTransformation<slicer::AllocateScratchRegs>(1, false);
321*f0dffb02SXin Li   auto t2 = mi.AddTransformation<slicer::AllocateScratchRegs>(1, false);
322*f0dffb02SXin Li   auto t3 = mi.AddTransformation<slicer::AllocateScratchRegs>(1);
323*f0dffb02SXin Li   auto t4 = mi.AddTransformation<slicer::AllocateScratchRegs>(20);
324*f0dffb02SXin Li 
325*f0dffb02SXin Li   // apply the transformations to every single method
326*f0dffb02SXin Li   for (auto& ir_method : dex_ir->encoded_methods) {
327*f0dffb02SXin Li     if (ir_method->code != nullptr) {
328*f0dffb02SXin Li       SLICER_CHECK(mi.InstrumentMethod(ir_method.get()));
329*f0dffb02SXin Li       SLICER_CHECK_EQ(t1->ScratchRegs().size(), 1);
330*f0dffb02SXin Li       SLICER_CHECK_EQ(t2->ScratchRegs().size(), 1);
331*f0dffb02SXin Li       SLICER_CHECK_EQ(t3->ScratchRegs().size(), 1);
332*f0dffb02SXin Li       SLICER_CHECK_EQ(t4->ScratchRegs().size(), 20);
333*f0dffb02SXin Li     }
334*f0dffb02SXin Li   }
335*f0dffb02SXin Li }
336*f0dffb02SXin Li 
337*f0dffb02SXin Li // Sample code coverage instrumentation: on the entry of every
338*f0dffb02SXin Li // basic block, inject a call to a tracing method:
339*f0dffb02SXin Li //
340*f0dffb02SXin Li //   CodeCoverage.TraceBasicBlock(block_id)
341*f0dffb02SXin Li //
CodeCoverage(std::shared_ptr<ir::DexFile> dex_ir)342*f0dffb02SXin Li void CodeCoverage(std::shared_ptr<ir::DexFile> dex_ir) {
343*f0dffb02SXin Li   ir::Builder builder(dex_ir);
344*f0dffb02SXin Li   slicer::AllocateScratchRegs alloc_regs(1);
345*f0dffb02SXin Li   int basic_block_id = 1;
346*f0dffb02SXin Li 
347*f0dffb02SXin Li   constexpr const char* kTracerClass = "LCodeCoverage;";
348*f0dffb02SXin Li 
349*f0dffb02SXin Li   // create the tracing method declaration
350*f0dffb02SXin Li   std::vector<ir::Type*> param_types { builder.GetType("I") };
351*f0dffb02SXin Li   auto ir_proto =
352*f0dffb02SXin Li       builder.GetProto(builder.GetType("V"),
353*f0dffb02SXin Li                        builder.GetTypeList(param_types));
354*f0dffb02SXin Li   auto ir_method_decl =
355*f0dffb02SXin Li       builder.GetMethodDecl(builder.GetAsciiString("TraceBasicBlock"),
356*f0dffb02SXin Li                             ir_proto,
357*f0dffb02SXin Li                             builder.GetType(kTracerClass));
358*f0dffb02SXin Li 
359*f0dffb02SXin Li   // instrument every method (except for the tracer class methods)
360*f0dffb02SXin Li   for (auto& ir_method : dex_ir->encoded_methods) {
361*f0dffb02SXin Li     if (ir_method->code == nullptr) {
362*f0dffb02SXin Li       continue;
363*f0dffb02SXin Li     }
364*f0dffb02SXin Li 
365*f0dffb02SXin Li     // don't instrument the methods of the tracer class
366*f0dffb02SXin Li     if (std::strcmp(ir_method->decl->parent->descriptor->c_str(), kTracerClass) == 0) {
367*f0dffb02SXin Li       continue;
368*f0dffb02SXin Li     }
369*f0dffb02SXin Li 
370*f0dffb02SXin Li     lir::CodeIr code_ir(ir_method.get(), dex_ir);
371*f0dffb02SXin Li     lir::ControlFlowGraph cfg(&code_ir, true);
372*f0dffb02SXin Li 
373*f0dffb02SXin Li     // allocate a scratch register
374*f0dffb02SXin Li     //
375*f0dffb02SXin Li     // NOTE: we're assuming this does not change the CFG!
376*f0dffb02SXin Li     //   (this is the case here, but transformations which
377*f0dffb02SXin Li     //    alter the basic blocks boundaries or the code flow
378*f0dffb02SXin Li     //    would invalidate existing CFGs)
379*f0dffb02SXin Li     //
380*f0dffb02SXin Li     alloc_regs.Apply(&code_ir);
381*f0dffb02SXin Li     dex::u4 scratch_reg = *alloc_regs.ScratchRegs().begin();
382*f0dffb02SXin Li 
383*f0dffb02SXin Li     // TODO: handle very "high" registers
384*f0dffb02SXin Li     if (scratch_reg > 0xff) {
385*f0dffb02SXin Li       printf("WARNING: can't instrument method %s.%s%s\n",
386*f0dffb02SXin Li              ir_method->decl->parent->Decl().c_str(),
387*f0dffb02SXin Li              ir_method->decl->name->c_str(),
388*f0dffb02SXin Li              ir_method->decl->prototype->Signature().c_str());
389*f0dffb02SXin Li       continue;
390*f0dffb02SXin Li     }
391*f0dffb02SXin Li 
392*f0dffb02SXin Li     auto tracing_method = code_ir.Alloc<lir::Method>(ir_method_decl, ir_method_decl->orig_index);
393*f0dffb02SXin Li 
394*f0dffb02SXin Li     // instrument each basic block entry point
395*f0dffb02SXin Li     for (const auto& block : cfg.basic_blocks) {
396*f0dffb02SXin Li       // generate the map of basic blocks
397*f0dffb02SXin Li       printf("%8u: mi=%u s=%u e=%u\n",
398*f0dffb02SXin Li              static_cast<dex::u4>(basic_block_id),
399*f0dffb02SXin Li              ir_method->decl->orig_index,
400*f0dffb02SXin Li              block.region.first->offset,
401*f0dffb02SXin Li              block.region.last->offset);
402*f0dffb02SXin Li 
403*f0dffb02SXin Li       // find first bytecode in the basic block
404*f0dffb02SXin Li       lir::Instruction* trace_point = nullptr;
405*f0dffb02SXin Li       for (auto instr = block.region.first; instr != nullptr; instr = instr->next) {
406*f0dffb02SXin Li         trace_point = dynamic_cast<lir::Bytecode*>(instr);
407*f0dffb02SXin Li         if (trace_point != nullptr || instr == block.region.last) {
408*f0dffb02SXin Li           break;
409*f0dffb02SXin Li         }
410*f0dffb02SXin Li       }
411*f0dffb02SXin Li       SLICER_CHECK_NE(trace_point, nullptr);
412*f0dffb02SXin Li 
413*f0dffb02SXin Li       // special case: don't separate 'move-result-<kind>' from the preceding invoke
414*f0dffb02SXin Li       auto opcode = static_cast<lir::Bytecode*>(trace_point)->opcode;
415*f0dffb02SXin Li       if (opcode == dex::OP_MOVE_RESULT ||
416*f0dffb02SXin Li           opcode == dex::OP_MOVE_RESULT_WIDE ||
417*f0dffb02SXin Li           opcode == dex::OP_MOVE_RESULT_OBJECT) {
418*f0dffb02SXin Li         trace_point = trace_point->next;
419*f0dffb02SXin Li       }
420*f0dffb02SXin Li 
421*f0dffb02SXin Li       // arg_reg = block_id
422*f0dffb02SXin Li       auto load_block_id = code_ir.Alloc<lir::Bytecode>();
423*f0dffb02SXin Li       load_block_id->opcode = dex::OP_CONST;
424*f0dffb02SXin Li       load_block_id->operands.push_back(code_ir.Alloc<lir::VReg>(scratch_reg));
425*f0dffb02SXin Li       load_block_id->operands.push_back(code_ir.Alloc<lir::Const32>(basic_block_id));
426*f0dffb02SXin Li       code_ir.instructions.InsertBefore(trace_point, load_block_id);
427*f0dffb02SXin Li 
428*f0dffb02SXin Li       // call the tracing method
429*f0dffb02SXin Li       auto trace_call = code_ir.Alloc<lir::Bytecode>();
430*f0dffb02SXin Li       trace_call->opcode = dex::OP_INVOKE_STATIC_RANGE;
431*f0dffb02SXin Li       trace_call->operands.push_back(code_ir.Alloc<lir::VRegRange>(scratch_reg, 1));
432*f0dffb02SXin Li       trace_call->operands.push_back(tracing_method);
433*f0dffb02SXin Li       code_ir.instructions.InsertBefore(trace_point, trace_call);
434*f0dffb02SXin Li 
435*f0dffb02SXin Li       ++basic_block_id;
436*f0dffb02SXin Li     }
437*f0dffb02SXin Li 
438*f0dffb02SXin Li     code_ir.Assemble();
439*f0dffb02SXin Li   }
440*f0dffb02SXin Li }
441*f0dffb02SXin Li 
442*f0dffb02SXin Li // Stress the roundtrip: EncodedMethod -> MethodId -> FindMethod -> EncodedMethod
443*f0dffb02SXin Li // NOTE: until we start indexing methods this test is slow on debug builds + large .dex images
StressFindMethod(std::shared_ptr<ir::DexFile> dex_ir)444*f0dffb02SXin Li void StressFindMethod(std::shared_ptr<ir::DexFile> dex_ir) {
445*f0dffb02SXin Li   ir::Builder builder(dex_ir);
446*f0dffb02SXin Li   int method_count = 0;
447*f0dffb02SXin Li   for (auto& ir_method : dex_ir->encoded_methods) {
448*f0dffb02SXin Li     auto decl = ir_method->decl;
449*f0dffb02SXin Li     auto signature = decl->prototype->Signature();
450*f0dffb02SXin Li     auto class_descriptor = decl->parent->descriptor;
451*f0dffb02SXin Li     ir::MethodId method_id(class_descriptor->c_str(), decl->name->c_str(), signature.c_str());
452*f0dffb02SXin Li     SLICER_CHECK_EQ(builder.FindMethod(method_id), ir_method.get());
453*f0dffb02SXin Li     ++method_count;
454*f0dffb02SXin Li   }
455*f0dffb02SXin Li   printf("Everything looks fine, found %d methods.\n", method_count);
456*f0dffb02SXin Li }
457*f0dffb02SXin Li 
PrintHistogram(const std::map<int,int> histogram,const char * name)458*f0dffb02SXin Li static void PrintHistogram(const std::map<int, int> histogram, const char* name) {
459*f0dffb02SXin Li   constexpr int kHistogramWidth = 100;
460*f0dffb02SXin Li   int max_count = 0;
461*f0dffb02SXin Li   for (const auto& kv : histogram) {
462*f0dffb02SXin Li     max_count = std::max(max_count, kv.second);
463*f0dffb02SXin Li   }
464*f0dffb02SXin Li   printf("\nHistogram: %s [max_count=%d]\n\n", name, max_count);
465*f0dffb02SXin Li   for (const auto& kv : histogram) {
466*f0dffb02SXin Li     printf("%6d [ %3d ] ", kv.second, kv.first);
467*f0dffb02SXin Li     int hist_len = static_cast<int>(static_cast<double>(kv.second) / max_count * kHistogramWidth);
468*f0dffb02SXin Li     for (int i = 0; i <= hist_len; ++i) {
469*f0dffb02SXin Li       printf("*");
470*f0dffb02SXin Li     }
471*f0dffb02SXin Li     printf("\n");
472*f0dffb02SXin Li   }
473*f0dffb02SXin Li }
474*f0dffb02SXin Li 
475*f0dffb02SXin Li // Builds a histogram of registers count per method
RegsHistogram(std::shared_ptr<ir::DexFile> dex_ir)476*f0dffb02SXin Li void RegsHistogram(std::shared_ptr<ir::DexFile> dex_ir) {
477*f0dffb02SXin Li   std::map<int, int> regs_histogram;
478*f0dffb02SXin Li   std::map<int, int> param_histogram;
479*f0dffb02SXin Li   std::map<int, int> extra_histogram;
480*f0dffb02SXin Li   for (auto& ir_method : dex_ir->encoded_methods) {
481*f0dffb02SXin Li     if (ir_method->code != nullptr) {
482*f0dffb02SXin Li       const int regs = ir_method->code->registers;
483*f0dffb02SXin Li       const int ins =  ir_method->code->ins_count;
484*f0dffb02SXin Li       SLICER_CHECK_GE(regs, ins);
485*f0dffb02SXin Li       regs_histogram[regs]++;
486*f0dffb02SXin Li       param_histogram[ins]++;
487*f0dffb02SXin Li       extra_histogram[regs - ins]++;
488*f0dffb02SXin Li     }
489*f0dffb02SXin Li   }
490*f0dffb02SXin Li   PrintHistogram(regs_histogram, "Method registers");
491*f0dffb02SXin Li   PrintHistogram(param_histogram, "Method parameter registers");
492*f0dffb02SXin Li   PrintHistogram(regs_histogram, "Method extra registers (total - parameters)");
493*f0dffb02SXin Li }
494*f0dffb02SXin Li 
495*f0dffb02SXin Li // Test slicer::MethodInstrumenter + Tweak::ArrayParams
TestArrayParamsEntryHook(std::shared_ptr<ir::DexFile> dex_ir)496*f0dffb02SXin Li void TestArrayParamsEntryHook(std::shared_ptr<ir::DexFile> dex_ir) {
497*f0dffb02SXin Li   slicer::MethodInstrumenter mi(dex_ir);
498*f0dffb02SXin Li   mi.AddTransformation<slicer::EntryHook>(ir::MethodId("LTracer;", "onFooEntry"),
499*f0dffb02SXin Li                                           slicer::EntryHook::Tweak::ArrayParams);
500*f0dffb02SXin Li 
501*f0dffb02SXin Li   auto method1 = ir::MethodId("LTarget;", "foo", "(ILjava/lang/String;)I");
502*f0dffb02SXin Li   SLICER_CHECK(mi.InstrumentMethod(method1));
503*f0dffb02SXin Li 
504*f0dffb02SXin Li   auto method2 = ir::MethodId("LTarget;", "foo", "(I[[Ljava/lang/String;)Ljava/lang/Integer;");
505*f0dffb02SXin Li   SLICER_CHECK(mi.InstrumentMethod(method2));
506*f0dffb02SXin Li }
507*f0dffb02SXin Li 
508*f0dffb02SXin Li // Test slicer::MethodInstrumenter + Tweak::ReturnAsObject
TestReturnAsObjectExitHook(std::shared_ptr<ir::DexFile> dex_ir)509*f0dffb02SXin Li void TestReturnAsObjectExitHook(std::shared_ptr<ir::DexFile> dex_ir) {
510*f0dffb02SXin Li   slicer::MethodInstrumenter mi(dex_ir);
511*f0dffb02SXin Li   mi.AddTransformation<slicer::ExitHook>(ir::MethodId("LTracer;", "onFooExit"),
512*f0dffb02SXin Li                                           slicer::ExitHook::Tweak::ReturnAsObject);
513*f0dffb02SXin Li 
514*f0dffb02SXin Li   auto method = ir::MethodId("LTarget;", "foo", "(I[[Ljava/lang/String;)Ljava/lang/Integer;");
515*f0dffb02SXin Li   SLICER_CHECK(mi.InstrumentMethod(method));
516*f0dffb02SXin Li }
517*f0dffb02SXin Li 
518*f0dffb02SXin Li // Test slicer::MethodInstrumenter + Tweak::ReturnAsObject + Tweak::PassMethodSignature
TestPassMethodSignatureExitHook(std::shared_ptr<ir::DexFile> dex_ir)519*f0dffb02SXin Li void TestPassMethodSignatureExitHook(std::shared_ptr<ir::DexFile> dex_ir) {
520*f0dffb02SXin Li   slicer::MethodInstrumenter mi(dex_ir);
521*f0dffb02SXin Li   mi.AddTransformation<slicer::ExitHook>(ir::MethodId("LTracer;", "onFooExit"),
522*f0dffb02SXin Li                                           slicer::ExitHook::Tweak::ReturnAsObject |
523*f0dffb02SXin Li                                           slicer::ExitHook::Tweak::PassMethodSignature);
524*f0dffb02SXin Li 
525*f0dffb02SXin Li   auto method = ir::MethodId("LTarget;", "foo", "(I[[Ljava/lang/String;)Ljava/lang/Integer;");
526*f0dffb02SXin Li   SLICER_CHECK(mi.InstrumentMethod(method));
527*f0dffb02SXin Li }
528*f0dffb02SXin Li 
529*f0dffb02SXin Li void ListExperiments(std::shared_ptr<ir::DexFile> dex_ir);
530*f0dffb02SXin Li 
531*f0dffb02SXin Li using Experiment = void (*)(std::shared_ptr<ir::DexFile>);
532*f0dffb02SXin Li 
533*f0dffb02SXin Li // the registry of available experiments
534*f0dffb02SXin Li std::map<std::string, Experiment> experiments_registry = {
535*f0dffb02SXin Li     { "list_experiments", &ListExperiments },
536*f0dffb02SXin Li     { "full_rewrite", &FullRewrite },
537*f0dffb02SXin Li     { "stress_entry_hook", &StressEntryHook },
538*f0dffb02SXin Li     { "stress_exit_hook", &StressExitHook },
539*f0dffb02SXin Li     { "stress_wrap_invoke", &StressWrapInvoke },
540*f0dffb02SXin Li     { "test_method_instrumenter", &TestMethodInstrumenter },
541*f0dffb02SXin Li     { "stress_find_method", &StressFindMethod },
542*f0dffb02SXin Li     { "stress_scratch_regs", &StressScratchRegs },
543*f0dffb02SXin Li     { "regs_histogram", &RegsHistogram },
544*f0dffb02SXin Li     { "code_coverage", &CodeCoverage },
545*f0dffb02SXin Li     { "array_param_entry_hook", &TestArrayParamsEntryHook },
546*f0dffb02SXin Li     { "return_obj_exit_hook", &TestReturnAsObjectExitHook },
547*f0dffb02SXin Li     { "pass_sign_exit_hook", &TestPassMethodSignatureExitHook },
548*f0dffb02SXin Li };
549*f0dffb02SXin Li 
550*f0dffb02SXin Li // Lists all the registered experiments
ListExperiments(std::shared_ptr<ir::DexFile> dex_ir)551*f0dffb02SXin Li void ListExperiments(std::shared_ptr<ir::DexFile> dex_ir) {
552*f0dffb02SXin Li   printf("\nAvailable experiments:\n");
553*f0dffb02SXin Li   printf("-------------------------\n");
554*f0dffb02SXin Li   for (auto& e : experiments_registry) {
555*f0dffb02SXin Li     printf("  %s\n", e.first.c_str());
556*f0dffb02SXin Li   }
557*f0dffb02SXin Li   printf("-------------------------\n\n");
558*f0dffb02SXin Li }
559*f0dffb02SXin Li 
560*f0dffb02SXin Li // Driver for running experiments
Run(const char * experiment,std::shared_ptr<ir::DexFile> dex_ir)561*f0dffb02SXin Li void Run(const char* experiment, std::shared_ptr<ir::DexFile> dex_ir) {
562*f0dffb02SXin Li   auto it = experiments_registry.find(experiment);
563*f0dffb02SXin Li   if (it == experiments_registry.end()) {
564*f0dffb02SXin Li     printf("\nUnknown experiment '%s'\n", experiment);
565*f0dffb02SXin Li     ListExperiments(dex_ir);
566*f0dffb02SXin Li     exit(1);
567*f0dffb02SXin Li   }
568*f0dffb02SXin Li 
569*f0dffb02SXin Li   // running the experiment entry point
570*f0dffb02SXin Li   (*it->second)(dex_ir);
571*f0dffb02SXin Li }
572*f0dffb02SXin Li 
573*f0dffb02SXin Li }  // namespace experimental
574