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 "dexter.h"
18
19 #include "experimental.h"
20 #include "slicer/common.h"
21 #include "slicer/scopeguard.h"
22 #include "slicer/reader.h"
23 #include "slicer/writer.h"
24 #include "slicer/chronometer.h"
25
26 #include <getopt.h>
27 #include <stdio.h>
28 #include <memory>
29 #include <sstream>
30 #include <map>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33
34 // Converts a class name to a type descriptor
35 // (ex. "java.lang.String" to "Ljava/lang/String;")
ClassNameToDescriptor(const char * class_name)36 static std::string ClassNameToDescriptor(const char* class_name) {
37 std::stringstream ss;
38 ss << "L";
39 for (auto p = class_name; *p != '\0'; ++p) {
40 ss << (*p == '.' ? '/' : *p);
41 }
42 ss << ";";
43 return ss.str();
44 }
45
PrintHelp()46 void Dexter::PrintHelp() {
47 printf("\nDex manipulation tool %s\n\n", VERSION);
48 printf("dexter [flags...] [-e classname] [-o outfile] <dexfile>\n");
49 printf(" -h : help\n");
50 printf(" -s : print stats\n");
51 printf(" -e : extract a single class\n");
52 printf(" -l : list the classes defined in the dex file\n");
53 printf(" -v : verbose output\n");
54 printf(" -o : output a new .dex file\n");
55 printf(" -d : disassemble method bodies\n");
56 printf(" -m : print .dex layout map\n");
57 printf(" --cfg : generate control flow graph (compact|verbose)\n");
58 printf("\n");
59 }
60
Run()61 int Dexter::Run() {
62 // names for the CFG options
63 const std::map<std::string, DexDisassembler::CfgType> cfg_type_names = {
64 { "none", DexDisassembler::CfgType::None },
65 { "compact", DexDisassembler::CfgType::Compact },
66 { "verbose", DexDisassembler::CfgType::Verbose },
67 };
68
69 // long cmdline options
70 enum { kBuildCFG = 1 };
71 const option long_options[] = {
72 { "cfg", required_argument, 0, kBuildCFG },
73 { 0, 0, 0, 0}
74 };
75
76 bool show_help = false;
77 int opt = 0;
78 int opt_index = 0;
79 while ((opt = ::getopt_long(argc_, argv_, "hlsvdmo:e:x:", long_options, &opt_index)) != -1) {
80 switch (opt) {
81 case kBuildCFG: {
82 auto it = cfg_type_names.find(::optarg);
83 if (it == cfg_type_names.end()) {
84 printf("Invalid --cfg type\n");
85 show_help = true;
86 }
87 cfg_type_ = it->second;
88 } break;
89 case 's':
90 stats_ = true;
91 break;
92 case 'v':
93 verbose_ = true;
94 break;
95 case 'l':
96 list_classes_ = true;
97 break;
98 case 'e':
99 extract_class_ = ::optarg;
100 break;
101 case 'd':
102 disassemble_ = true;
103 break;
104 case 'm':
105 print_map_ = true;
106 break;
107 case 'o':
108 out_dex_filename_ = ::optarg;
109 break;
110 case 'x':
111 experiments_.push_back(::optarg);
112 break;
113 default:
114 show_help = true;
115 break;
116 }
117 }
118
119 if (show_help || ::optind + 1 != argc_) {
120 PrintHelp();
121 return 1;
122 }
123
124 dex_filename_ = argv_[::optind];
125 return ProcessDex();
126 }
127
128 // Print the layout map of the .dex sections
PrintDexMap(const dex::Reader & reader)129 static void PrintDexMap(const dex::Reader& reader) {
130 printf("\nSections summary: name, offset, size [count]\n");
131
132 const dex::MapList& dexMap = *reader.DexMapList();
133 for (dex::u4 i = 0; i < dexMap.size; ++i) {
134 const dex::MapItem& section = dexMap.list[i];
135 const char* sectionName = "UNKNOWN";
136 switch (section.type) {
137 case dex::kHeaderItem:
138 sectionName = "HeaderItem";
139 break;
140 case dex::kStringIdItem:
141 sectionName = "StringIdItem";
142 break;
143 case dex::kTypeIdItem:
144 sectionName = "TypeIdItem";
145 break;
146 case dex::kProtoIdItem:
147 sectionName = "ProtoIdItem";
148 break;
149 case dex::kFieldIdItem:
150 sectionName = "FieldIdItem";
151 break;
152 case dex::kMethodIdItem:
153 sectionName = "MethodIdItem";
154 break;
155 case dex::kMethodHandleItem:
156 sectionName = "MethodHandleIdItem";
157 break;
158 case dex::kClassDefItem:
159 sectionName = "ClassDefItem";
160 break;
161 case dex::kMapList:
162 sectionName = "MapList";
163 break;
164 case dex::kTypeList:
165 sectionName = "TypeList";
166 break;
167 case dex::kAnnotationSetRefList:
168 sectionName = "AnnotationSetRefList";
169 break;
170 case dex::kAnnotationSetItem:
171 sectionName = "AnnotationSetItem";
172 break;
173 case dex::kClassDataItem:
174 sectionName = "ClassDataItem";
175 break;
176 case dex::kCodeItem:
177 sectionName = "CodeItem";
178 break;
179 case dex::kStringDataItem:
180 sectionName = "StringDataItem";
181 break;
182 case dex::kDebugInfoItem:
183 sectionName = "DebugInfoItem";
184 break;
185 case dex::kAnnotationItem:
186 sectionName = "AnnotationItem";
187 break;
188 case dex::kEncodedArrayItem:
189 sectionName = "EncodedArrayItem";
190 break;
191 case dex::kAnnotationsDirectoryItem:
192 sectionName = "AnnotationsDirectoryItem";
193 break;
194 }
195
196 dex::u4 sectionByteSize = (i == dexMap.size - 1)
197 ? reader.Header()->file_size - section.offset
198 : dexMap.list[i + 1].offset - section.offset;
199
200 printf(" %-25s : %8x, %8x [%u]\n", sectionName, section.offset,
201 sectionByteSize, section.size);
202 }
203 }
204
205 // Print .dex IR stats
PrintDexIrStats(std::shared_ptr<const ir::DexFile> dex_ir)206 static void PrintDexIrStats(std::shared_ptr<const ir::DexFile> dex_ir) {
207 printf("\n.dex IR statistics:\n");
208 printf(" strings : %zu\n", dex_ir->strings.size());
209 printf(" types : %zu\n", dex_ir->types.size());
210 printf(" protos : %zu\n", dex_ir->protos.size());
211 printf(" fields : %zu\n", dex_ir->fields.size());
212 printf(" encoded_fields : %zu\n", dex_ir->encoded_fields.size());
213 printf(" methods : %zu\n", dex_ir->methods.size());
214 printf(" encoded_methods : %zu\n", dex_ir->encoded_methods.size());
215 printf(" classes : %zu\n", dex_ir->classes.size());
216 printf(" method_handles : %zu\n", dex_ir->method_handles.size());
217 printf(" type_lists : %zu\n", dex_ir->type_lists.size());
218 printf(" code : %zu\n", dex_ir->code.size());
219 printf(" debug_info : %zu\n", dex_ir->debug_info.size());
220 printf(" encoded_values : %zu\n", dex_ir->encoded_values.size());
221 printf(" encoded_arrays : %zu\n", dex_ir->encoded_arrays.size());
222 printf(" annotations : %zu\n", dex_ir->annotations.size());
223 printf(" annotation_elements : %zu\n", dex_ir->annotation_elements.size());
224 printf(" annotation_sets : %zu\n", dex_ir->annotation_sets.size());
225 printf(" annotation_set_ref_lists : %zu\n", dex_ir->annotation_set_ref_lists.size());
226 printf(" annotations_directories : %zu\n", dex_ir->annotations_directories.size());
227 printf(" field_annotations : %zu\n", dex_ir->field_annotations.size());
228 printf(" method_annotations : %zu\n", dex_ir->method_annotations.size());
229 printf(" param_annotations : %zu\n", dex_ir->param_annotations.size());
230 printf("\n");
231 }
232
233 // Print .dex file stats
PrintDexFileStats(const dex::Reader & reader)234 static void PrintDexFileStats(const dex::Reader& reader) {
235 auto header = reader.Header();
236 auto map_list = reader.DexMapList();
237 printf("\n.dex file statistics:\n");
238 printf(" file_size : %u\n", header->file_size);
239 printf(" data_size : %u\n", header->data_size);
240 printf(" link_size : %u\n", header->link_size);
241 printf(" class_defs_size : %u\n", header->class_defs_size);
242 printf(" string_ids_size : %u\n", header->string_ids_size);
243 printf(" type_ids_size : %u\n", header->type_ids_size);
244 printf(" proto_ids_size : %u\n", header->proto_ids_size);
245 printf(" field_ids_size : %u\n", header->field_ids_size);
246 printf(" method_ids_size : %u\n", header->method_ids_size);
247 printf(" method_handle_ids_size : %zu\n", reader.MethodHandles().size());
248 printf(" map_list_size : %u\n", map_list->size);
249 printf("\n");
250 }
251
252 // List all the classes defined in the dex file
ListClasses(const dex::Reader & reader)253 static void ListClasses(const dex::Reader& reader) {
254 printf("\nClass definitions:\n");
255 auto classes = reader.ClassDefs();
256 auto types = reader.TypeIds();
257 for (dex::u4 i = 0; i < classes.size(); ++i) {
258 auto typeId = types[classes[i].class_idx];
259 const char* descriptor = reader.GetStringMUTF8(typeId.descriptor_idx);
260 printf(" %s\n", dex::DescriptorToDecl(descriptor).c_str());
261 }
262 printf("\n");
263 }
264
265 // Create a new in-memory .dex image and optionally write it to disk
266 //
267 // NOTE: we always create the new in-memory image, even if we don't write it
268 // to an output file, for aggresive code coverage and perf timings
269 //
CreateNewImage(std::shared_ptr<ir::DexFile> dex_ir)270 bool Dexter::CreateNewImage(std::shared_ptr<ir::DexFile> dex_ir) {
271 // our custom (and trivial) allocator for dex::Writer
272 struct Allocator : public dex::Writer::Allocator {
273 virtual void* Allocate(size_t size) { return ::malloc(size); }
274 virtual void Free(void* ptr) { ::free(ptr); }
275 };
276
277 size_t new_image_size = 0;
278 dex::u1* new_image = nullptr;
279 Allocator allocator;
280
281 {
282 slicer::Chronometer chrono(writer_time_);
283
284 dex::Writer writer(dex_ir);
285 new_image = writer.CreateImage(&allocator, &new_image_size);
286 }
287
288 if (new_image == nullptr) {
289 printf("ERROR: Can't create a new .dex image\n");
290 return false;
291 }
292
293 SLICER_SCOPE_EXIT {
294 allocator.Free(new_image);
295 };
296
297 // write the new .dex image to disk?
298 if (out_dex_filename_ != nullptr) {
299 if (verbose_) {
300 printf("\nWriting: %s\n", out_dex_filename_);
301 }
302
303 // create output file
304 FILE* out_file = fopen(out_dex_filename_, "wb");
305 if (out_file == nullptr) {
306 printf("ERROR: Can't create output .dex file (%s)\n", out_dex_filename_);
307 return false;
308 }
309
310 SLICER_SCOPE_EXIT {
311 fclose(out_file);
312 };
313
314 // write the new image
315 SLICER_CHECK_EQ(fwrite(new_image, 1, new_image_size, out_file), new_image_size);
316 }
317
318 return true;
319 }
320
321 // Main entry point for processing an input .dex file
ProcessDex()322 int Dexter::ProcessDex() {
323 if (verbose_) {
324 printf("\nReading: %s\n", dex_filename_);
325 }
326
327 // check that target is a file
328 struct stat path_stat;
329 stat(dex_filename_, &path_stat);
330 if (!S_ISREG(path_stat.st_mode)) {
331 printf("ERROR: Path (%s) is not a regular file.\n", dex_filename_);
332 return 1;
333 }
334
335 // open input file
336 FILE* in_file = fopen(dex_filename_, "rb");
337 if (in_file == nullptr) {
338 printf("ERROR: Can't open input .dex file (%s)\n", dex_filename_);
339 return 1;
340 }
341
342 SLICER_SCOPE_EXIT {
343 fclose(in_file);
344 };
345
346 // calculate file size
347 fseek(in_file, 0, SEEK_END);
348 size_t in_size = ftell(in_file);
349
350 // allocate the in-memory .dex image buffer
351 std::unique_ptr<dex::u1[]> in_buff(new dex::u1[in_size]);
352
353 // read input .dex file
354 fseek(in_file, 0, SEEK_SET);
355 SLICER_CHECK_EQ(fread(in_buff.get(), 1, in_size, in_file), in_size);
356
357 // initialize the .dex reader
358 dex::Reader reader(in_buff.get(), in_size);
359
360 // print the .dex map?
361 if (print_map_) {
362 PrintDexMap(reader);
363 }
364
365 // list classes?
366 if (list_classes_) {
367 ListClasses(reader);
368 }
369
370 // parse the .dex image
371 {
372 slicer::Chronometer chrono(reader_time_);
373
374 if (extract_class_ != nullptr) {
375 // extract the specified class only
376 std::string descriptor = ClassNameToDescriptor(extract_class_);
377 dex::u4 class_idx = reader.FindClassIndex(descriptor.c_str());
378 if (class_idx != dex::kNoIndex) {
379 reader.CreateClassIr(class_idx);
380 } else {
381 printf("ERROR: Can't find class (%s)\n", extract_class_);
382 return 1;
383 }
384 } else {
385 // build the full .dex IR
386 reader.CreateFullIr();
387 }
388 }
389
390 auto dex_ir = reader.GetIr();
391
392 // experiments
393 for (auto experiment : experiments_) {
394 slicer::Chronometer chrono(experiments_time_, true);
395 if (verbose_) {
396 printf("\nRunning experiment '%s' ... \n", experiment);
397 }
398 experimental::Run(experiment, dex_ir);
399 }
400
401 // disassemble method bodies?
402 if (disassemble_) {
403 DexDisassembler disasm(dex_ir, cfg_type_);
404 disasm.DumpAllMethods();
405 }
406
407 if(!CreateNewImage(dex_ir)) {
408 return 1;
409 }
410
411 // stats?
412 if (stats_) {
413 PrintDexFileStats(reader);
414 PrintDexIrStats(dex_ir);
415 }
416
417 if (verbose_) {
418 printf("\nDone (reader: %.3fms, writer: %.3fms", reader_time_, writer_time_);
419 if (!experiments_.empty()) {
420 printf(", experiments: %.3fms", experiments_time_);
421 }
422 printf(")\n");
423 }
424
425 // done
426 return 0;
427 }
428