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