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