1 /*
2  * Copyright (C) 2016 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  * Implementation file of the dex layout visualization.
17  *
18  * This is a tool to read dex files into an internal representation,
19  * reorganize the representation, and emit dex files with a better
20  * file layout.
21  */
22 
23 #include "dex_visualize.h"
24 
25 #include <inttypes.h>
26 #include <stdio.h>
27 
28 #include <functional>
29 #include <memory>
30 #include <vector>
31 
32 #include <android-base/logging.h>
33 
34 #include "dex_ir.h"
35 #include "dexlayout.h"
36 #include "profile/profile_compilation_info.h"
37 
38 namespace art {
39 
MultidexName(const std::string & prefix,size_t dex_file_index,const std::string & suffix)40 static std::string MultidexName(const std::string& prefix,
41                                 size_t dex_file_index,
42                                 const std::string& suffix) {
43   return prefix + ((dex_file_index > 0) ? std::to_string(dex_file_index + 1) : "") + suffix;
44 }
45 
46 class Dumper {
47  public:
48   // Colors are based on the type of the section in MapList.
Dumper(dex_ir::Header * header)49   explicit Dumper(dex_ir::Header* header)
50       : out_file_(nullptr),
51         sorted_sections_(
52             dex_ir::GetSortedDexFileSections(header, dex_ir::SortDirection::kSortDescending)) { }
53 
OpenAndPrintHeader(size_t dex_index)54   bool OpenAndPrintHeader(size_t dex_index) {
55     // Open the file and emit the gnuplot prologue.
56     out_file_ = fopen(MultidexName("layout", dex_index, ".gnuplot").c_str(), "we");
57     if (out_file_ == nullptr) {
58       return false;
59     }
60     fprintf(out_file_, "set terminal png size 1920,1080\n");
61     fprintf(out_file_, "set output \"%s\"\n", MultidexName("layout", dex_index, ".png").c_str());
62     fprintf(out_file_, "set title \"%s\"\n", MultidexName("classes", dex_index, ".dex").c_str());
63     fprintf(out_file_, "set xlabel \"Page offset into dex\"\n");
64     fprintf(out_file_, "set ylabel \"ClassDef index\"\n");
65     fprintf(out_file_, "set xtics rotate out (");
66     bool printed_one = false;
67 
68     for (const dex_ir::DexFileSection& s : sorted_sections_) {
69       if (s.size > 0) {
70         if (printed_one) {
71           fprintf(out_file_, ", ");
72         }
73         fprintf(out_file_, "\"%s\" %d", s.name.c_str(), s.offset / kPageSize);
74         printed_one = true;
75       }
76     }
77     fprintf(out_file_, ")\n");
78     fprintf(out_file_,
79             "plot \"-\" using 1:2:3:4:5 with vector nohead linewidth 1 lc variable notitle\n");
80     return true;
81   }
82 
GetColor(uint32_t offset) const83   int GetColor(uint32_t offset) const {
84     // The dread linear search to find the right section for the reference.
85     uint16_t section = 0;
86     for (const dex_ir::DexFileSection& file_section : sorted_sections_) {
87       if (file_section.offset < offset) {
88         section = file_section.type;
89         break;
90       }
91     }
92     // And a lookup table from type to color.
93     ColorMapType::const_iterator iter = kColorMap.find(section);
94     if (iter != kColorMap.end()) {
95       return iter->second;
96     }
97     return 0;
98   }
99 
DumpAddressRange(uint32_t from,uint32_t size,int class_index)100   void DumpAddressRange(uint32_t from, uint32_t size, int class_index) {
101     const uint32_t low_page = from / kPageSize;
102     const uint32_t high_page = (size > 0) ? (from + size - 1) / kPageSize : low_page;
103     const uint32_t size_delta = high_page - low_page;
104     fprintf(out_file_, "%d %d %d 0 %d\n", low_page, class_index, size_delta, GetColor(from));
105   }
106 
DumpAddressRange(const dex_ir::Item * item,int class_index)107   void DumpAddressRange(const dex_ir::Item* item, int class_index) {
108     if (item != nullptr) {
109       DumpAddressRange(item->GetOffset(), item->GetSize(), class_index);
110     }
111   }
112 
DumpStringData(const dex_ir::StringData * string_data,int class_index)113   void DumpStringData(const dex_ir::StringData* string_data, int class_index) {
114     DumpAddressRange(string_data, class_index);
115   }
116 
DumpStringId(const dex_ir::StringId * string_id,int class_index)117   void DumpStringId(const dex_ir::StringId* string_id, int class_index) {
118     DumpAddressRange(string_id, class_index);
119     if (string_id == nullptr) {
120       return;
121     }
122     DumpStringData(string_id->DataItem(), class_index);
123   }
124 
DumpTypeId(const dex_ir::TypeId * type_id,int class_index)125   void DumpTypeId(const dex_ir::TypeId* type_id, int class_index) {
126     DumpAddressRange(type_id, class_index);
127     DumpStringId(type_id->GetStringId(), class_index);
128   }
129 
DumpFieldId(const dex_ir::FieldId * field_id,int class_index)130   void DumpFieldId(const dex_ir::FieldId* field_id, int class_index) {
131     DumpAddressRange(field_id, class_index);
132     if (field_id == nullptr) {
133       return;
134     }
135     DumpTypeId(field_id->Class(), class_index);
136     DumpTypeId(field_id->Type(), class_index);
137     DumpStringId(field_id->Name(), class_index);
138   }
139 
DumpFieldItem(const dex_ir::FieldItem * field,int class_index)140   void DumpFieldItem(const dex_ir::FieldItem* field, int class_index) {
141     DumpAddressRange(field, class_index);
142     if (field == nullptr) {
143       return;
144     }
145     DumpFieldId(field->GetFieldId(), class_index);
146   }
147 
DumpProtoId(const dex_ir::ProtoId * proto_id,int class_index)148   void DumpProtoId(const dex_ir::ProtoId* proto_id, int class_index) {
149     DumpAddressRange(proto_id, class_index);
150     if (proto_id == nullptr) {
151       return;
152     }
153     DumpStringId(proto_id->Shorty(), class_index);
154     const dex_ir::TypeList* type_list = proto_id->Parameters();
155     if (type_list != nullptr) {
156       for (const dex_ir::TypeId* t : *type_list->GetTypeList()) {
157         DumpTypeId(t, class_index);
158       }
159     }
160     DumpTypeId(proto_id->ReturnType(), class_index);
161   }
162 
DumpMethodId(const dex_ir::MethodId * method_id,int class_index)163   void DumpMethodId(const dex_ir::MethodId* method_id, int class_index) {
164     DumpAddressRange(method_id, class_index);
165     if (method_id == nullptr) {
166       return;
167     }
168     DumpTypeId(method_id->Class(), class_index);
169     DumpProtoId(method_id->Proto(), class_index);
170     DumpStringId(method_id->Name(), class_index);
171   }
172 
DumpMethodItem(dex_ir::MethodItem * method,const DexFile * dex_file,int class_index,ProfileCompilationInfo * profile_info)173   void DumpMethodItem(dex_ir::MethodItem* method,
174                       const DexFile* dex_file,
175                       int class_index,
176                       ProfileCompilationInfo* profile_info) {
177     if (profile_info != nullptr) {
178       uint32_t method_idx = method->GetMethodId()->GetIndex();
179       if (!profile_info->GetMethodHotness(MethodReference(dex_file, method_idx)).IsHot()) {
180         return;
181       }
182     }
183     DumpAddressRange(method, class_index);
184     if (method == nullptr) {
185       return;
186     }
187     DumpMethodId(method->GetMethodId(), class_index);
188     const dex_ir::CodeItem* code_item = method->GetCodeItem();
189     if (code_item != nullptr) {
190       DumpAddressRange(code_item, class_index);
191       const dex_ir::CodeFixups* fixups = code_item->GetCodeFixups();
192       if (fixups != nullptr) {
193         for (dex_ir::TypeId* type_id : fixups->TypeIds()) {
194           DumpTypeId(type_id, class_index);
195         }
196         for (dex_ir::StringId* string_id : fixups->StringIds()) {
197           DumpStringId(string_id, class_index);
198         }
199         for (dex_ir::MethodId* method_id : fixups->MethodIds()) {
200           DumpMethodId(method_id, class_index);
201         }
202         for (dex_ir::FieldId* field_id : fixups->FieldIds()) {
203           DumpFieldId(field_id, class_index);
204         }
205       }
206     }
207   }
208 
~Dumper()209   ~Dumper() {
210     fclose(out_file_);
211   }
212 
213  private:
214   using ColorMapType = std::map<uint16_t, int>;
215   const ColorMapType kColorMap = {
216     { DexFile::kDexTypeHeaderItem, 1 },
217     { DexFile::kDexTypeStringIdItem, 2 },
218     { DexFile::kDexTypeTypeIdItem, 3 },
219     { DexFile::kDexTypeProtoIdItem, 4 },
220     { DexFile::kDexTypeFieldIdItem, 5 },
221     { DexFile::kDexTypeMethodIdItem, 6 },
222     { DexFile::kDexTypeClassDefItem, 7 },
223     { DexFile::kDexTypeTypeList, 8 },
224     { DexFile::kDexTypeAnnotationSetRefList, 9 },
225     { DexFile::kDexTypeAnnotationSetItem, 10 },
226     { DexFile::kDexTypeClassDataItem, 11 },
227     { DexFile::kDexTypeCodeItem, 12 },
228     { DexFile::kDexTypeStringDataItem, 13 },
229     { DexFile::kDexTypeDebugInfoItem, 14 },
230     { DexFile::kDexTypeAnnotationItem, 15 },
231     { DexFile::kDexTypeEncodedArrayItem, 16 },
232     { DexFile::kDexTypeAnnotationsDirectoryItem, 16 }
233   };
234 
235   FILE* out_file_;
236   std::vector<dex_ir::DexFileSection> sorted_sections_;
237 
238   DISALLOW_COPY_AND_ASSIGN(Dumper);
239 };
240 
241 /*
242  * Dumps a gnuplot data file showing the parts of the dex_file that belong to each class.
243  * If profiling information is present, it dumps only those classes that are marked as hot.
244  */
VisualizeDexLayout(dex_ir::Header * header,const DexFile * dex_file,size_t dex_file_index,ProfileCompilationInfo * profile_info)245 void VisualizeDexLayout(dex_ir::Header* header,
246                         const DexFile* dex_file,
247                         size_t dex_file_index,
248                         ProfileCompilationInfo* profile_info) {
249   std::unique_ptr<Dumper> dumper(new Dumper(header));
250   if (!dumper->OpenAndPrintHeader(dex_file_index)) {
251     LOG(ERROR) << "Could not open output file.";
252     return;
253   }
254 
255   const uint32_t class_defs_size = header->ClassDefs().Size();
256   for (uint32_t class_index = 0; class_index < class_defs_size; class_index++) {
257     dex_ir::ClassDef* class_def = header->ClassDefs()[class_index];
258     dex::TypeIndex type_idx(class_def->ClassType()->GetIndex());
259     if (profile_info != nullptr && !profile_info->ContainsClass(*dex_file, type_idx)) {
260       continue;
261     }
262     dumper->DumpAddressRange(class_def, class_index);
263     // Type id.
264     dumper->DumpTypeId(class_def->ClassType(), class_index);
265     // Superclass type id.
266     dumper->DumpTypeId(class_def->Superclass(), class_index);
267     // Interfaces.
268     // TODO(jeffhao): get TypeList from class_def to use Item interface.
269     static constexpr uint32_t kInterfaceSizeKludge = 8;
270     dumper->DumpAddressRange(class_def->InterfacesOffset(), kInterfaceSizeKludge, class_index);
271     // Source file info.
272     dumper->DumpStringId(class_def->SourceFile(), class_index);
273     // Annotations.
274     dumper->DumpAddressRange(class_def->Annotations(), class_index);
275     // TODO(sehr): walk the annotations and dump them.
276     // Class data.
277     dex_ir::ClassData* class_data = class_def->GetClassData();
278     if (class_data != nullptr) {
279       dumper->DumpAddressRange(class_data, class_index);
280       if (class_data->StaticFields()) {
281         for (auto& field_item : *class_data->StaticFields()) {
282           dumper->DumpFieldItem(&field_item, class_index);
283         }
284       }
285       if (class_data->InstanceFields()) {
286         for (auto& field_item : *class_data->InstanceFields()) {
287           dumper->DumpFieldItem(&field_item, class_index);
288         }
289       }
290       if (class_data->DirectMethods()) {
291         for (auto& method_item : *class_data->DirectMethods()) {
292           dumper->DumpMethodItem(&method_item, dex_file, class_index, profile_info);
293         }
294       }
295       if (class_data->VirtualMethods()) {
296         for (auto& method_item : *class_data->VirtualMethods()) {
297           dumper->DumpMethodItem(&method_item, dex_file, class_index, profile_info);
298         }
299       }
300     }
301   }  // for
302 }
303 
FindNextByteAfterSection(dex_ir::Header * header,const std::vector<dex_ir::DexFileSection> & sorted_sections,size_t section_index)304 static uint32_t FindNextByteAfterSection(dex_ir::Header* header,
305                                          const std::vector<dex_ir::DexFileSection>& sorted_sections,
306                                          size_t section_index) {
307   for (size_t i = section_index + 1; i < sorted_sections.size(); ++i) {
308     const dex_ir::DexFileSection& section = sorted_sections[i];
309     if (section.size != 0) {
310       return section.offset;
311     }
312   }
313   return header->FileSize();
314 }
315 
316 /*
317  * Dumps the offset and size of sections within the file.
318  */
ShowDexSectionStatistics(dex_ir::Header * header,size_t dex_file_index)319 void ShowDexSectionStatistics(dex_ir::Header* header, size_t dex_file_index) {
320   // Compute the (multidex) class file name).
321   fprintf(stdout, "%s (%d bytes)\n",
322           MultidexName("classes", dex_file_index, ".dex").c_str(),
323           header->FileSize());
324   fprintf(stdout, "section      offset    items    bytes    pages pct\n");
325   std::vector<dex_ir::DexFileSection> sorted_sections =
326       GetSortedDexFileSections(header, dex_ir::SortDirection::kSortAscending);
327   for (size_t i = 0; i < sorted_sections.size(); ++i) {
328     const dex_ir::DexFileSection& file_section = sorted_sections[i];
329     uint32_t bytes = 0;
330     if (file_section.size > 0) {
331       bytes = FindNextByteAfterSection(header, sorted_sections, i) - file_section.offset;
332     }
333     fprintf(stdout,
334             "%-10s %8d %8d %8d %8d %%%02d\n",
335             file_section.name.c_str(),
336             file_section.offset,
337             file_section.size,
338             bytes,
339             RoundUp(bytes, kPageSize) / kPageSize,
340             100 * bytes / header->FileSize());
341   }
342   fprintf(stdout, "\n");
343 }
344 
345 }  // namespace art
346