blob: 23ecf934479f1b292a34daab2735c7a4a8aec45e [file] [log] [blame]
David Sehrcaacd112016-10-20 16:27:02 -07001/*
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 for control flow graph dumping for the dexdump utility.
17 */
18
19#include "dexdump_cfg.h"
20
21#include <inttypes.h>
Andreas Gampe8cf9cb32017-07-19 09:28:38 -070022
David Sehrcaacd112016-10-20 16:27:02 -070023#include <map>
Andreas Gampe8cf9cb32017-07-19 09:28:38 -070024#include <ostream>
David Sehrcaacd112016-10-20 16:27:02 -070025#include <set>
Andreas Gampe57943812017-12-06 21:39:13 -080026#include <sstream>
David Sehrcaacd112016-10-20 16:27:02 -070027
28#include "dex_file-inl.h"
29#include "dex_instruction-inl.h"
30
31namespace art {
32
33static void dumpMethodCFGImpl(const DexFile* dex_file,
34 uint32_t dex_method_idx,
35 const DexFile::CodeItem* code_item,
36 std::ostream& os) {
37 os << "digraph {\n";
38 os << " # /* " << dex_file->PrettyMethod(dex_method_idx, true) << " */\n";
39
40 std::set<uint32_t> dex_pc_is_branch_target;
41 {
42 // Go and populate.
43 const Instruction* inst = Instruction::At(code_item->insns_);
44 for (uint32_t dex_pc = 0;
45 dex_pc < code_item->insns_size_in_code_units_;
46 dex_pc += inst->SizeInCodeUnits(), inst = inst->Next()) {
47 if (inst->IsBranch()) {
48 dex_pc_is_branch_target.insert(dex_pc + inst->GetTargetOffset());
49 } else if (inst->IsSwitch()) {
50 const uint16_t* insns = code_item->insns_ + dex_pc;
51 int32_t switch_offset = insns[1] | (static_cast<int32_t>(insns[2]) << 16);
52 const uint16_t* switch_insns = insns + switch_offset;
53 uint32_t switch_count = switch_insns[1];
54 int32_t targets_offset;
55 if ((*insns & 0xff) == Instruction::PACKED_SWITCH) {
56 /* 0=sig, 1=count, 2/3=firstKey */
57 targets_offset = 4;
58 } else {
59 /* 0=sig, 1=count, 2..count*2 = keys */
60 targets_offset = 2 + 2 * switch_count;
61 }
62 for (uint32_t targ = 0; targ < switch_count; targ++) {
63 int32_t offset =
64 static_cast<int32_t>(switch_insns[targets_offset + targ * 2]) |
65 static_cast<int32_t>(switch_insns[targets_offset + targ * 2 + 1] << 16);
66 dex_pc_is_branch_target.insert(dex_pc + offset);
67 }
68 }
69 }
70 }
71
72 // Create nodes for "basic blocks."
73 std::map<uint32_t, uint32_t> dex_pc_to_node_id; // This only has entries for block starts.
74 std::map<uint32_t, uint32_t> dex_pc_to_incl_id; // This has entries for all dex pcs.
75
76 {
77 const Instruction* inst = Instruction::At(code_item->insns_);
78 bool first_in_block = true;
79 bool force_new_block = false;
80 for (uint32_t dex_pc = 0;
81 dex_pc < code_item->insns_size_in_code_units_;
82 dex_pc += inst->SizeInCodeUnits(), inst = inst->Next()) {
83 if (dex_pc == 0 ||
84 (dex_pc_is_branch_target.find(dex_pc) != dex_pc_is_branch_target.end()) ||
85 force_new_block) {
86 uint32_t id = dex_pc_to_node_id.size();
87 if (id > 0) {
88 // End last node.
89 os << "}\"];\n";
90 }
91 // Start next node.
92 os << " node" << id << " [shape=record,label=\"{";
93 dex_pc_to_node_id.insert(std::make_pair(dex_pc, id));
94 first_in_block = true;
95 force_new_block = false;
96 }
97
98 // Register instruction.
99 dex_pc_to_incl_id.insert(std::make_pair(dex_pc, dex_pc_to_node_id.size() - 1));
100
101 // Print instruction.
102 if (!first_in_block) {
103 os << " | ";
104 } else {
105 first_in_block = false;
106 }
107
108 // Dump the instruction. Need to escape '"', '<', '>', '{' and '}'.
109 os << "<" << "p" << dex_pc << ">";
110 os << " 0x" << std::hex << dex_pc << std::dec << ": ";
111 std::string inst_str = inst->DumpString(dex_file);
112 size_t cur_start = 0; // It's OK to start at zero, instruction dumps don't start with chars
113 // we need to escape.
114 while (cur_start != std::string::npos) {
115 size_t next_escape = inst_str.find_first_of("\"{}<>", cur_start + 1);
116 if (next_escape == std::string::npos) {
117 os << inst_str.substr(cur_start, inst_str.size() - cur_start);
118 break;
119 } else {
120 os << inst_str.substr(cur_start, next_escape - cur_start);
121 // Escape all necessary characters.
122 while (next_escape < inst_str.size()) {
123 char c = inst_str.at(next_escape);
124 if (c == '"' || c == '{' || c == '}' || c == '<' || c == '>') {
125 os << '\\' << c;
126 } else {
127 break;
128 }
129 next_escape++;
130 }
131 if (next_escape >= inst_str.size()) {
132 next_escape = std::string::npos;
133 }
134 cur_start = next_escape;
135 }
136 }
137
138 // Force a new block for some fall-throughs and some instructions that terminate the "local"
139 // control flow.
140 force_new_block = inst->IsSwitch() || inst->IsBasicBlockEnd();
141 }
142 // Close last node.
143 if (dex_pc_to_node_id.size() > 0) {
144 os << "}\"];\n";
145 }
146 }
147
148 // Create edges between them.
149 {
150 std::ostringstream regular_edges;
151 std::ostringstream taken_edges;
152 std::ostringstream exception_edges;
153
154 // Common set of exception edges.
155 std::set<uint32_t> exception_targets;
156
157 // These blocks (given by the first dex pc) need exception per dex-pc handling in a second
158 // pass. In the first pass we try and see whether we can use a common set of edges.
159 std::set<uint32_t> blocks_with_detailed_exceptions;
160
161 {
162 uint32_t last_node_id = std::numeric_limits<uint32_t>::max();
163 uint32_t old_dex_pc = 0;
164 uint32_t block_start_dex_pc = std::numeric_limits<uint32_t>::max();
165 const Instruction* inst = Instruction::At(code_item->insns_);
166 for (uint32_t dex_pc = 0;
167 dex_pc < code_item->insns_size_in_code_units_;
168 old_dex_pc = dex_pc, dex_pc += inst->SizeInCodeUnits(), inst = inst->Next()) {
169 {
170 auto it = dex_pc_to_node_id.find(dex_pc);
171 if (it != dex_pc_to_node_id.end()) {
172 if (!exception_targets.empty()) {
173 // It seems the last block had common exception handlers. Add the exception edges now.
174 uint32_t node_id = dex_pc_to_node_id.find(block_start_dex_pc)->second;
175 for (uint32_t handler_pc : exception_targets) {
176 auto node_id_it = dex_pc_to_incl_id.find(handler_pc);
177 if (node_id_it != dex_pc_to_incl_id.end()) {
178 exception_edges << " node" << node_id
179 << " -> node" << node_id_it->second << ":p" << handler_pc
180 << ";\n";
181 }
182 }
183 exception_targets.clear();
184 }
185
186 block_start_dex_pc = dex_pc;
187
188 // Seems to be a fall-through, connect to last_node_id. May be spurious edges for things
189 // like switch data.
190 uint32_t old_last = last_node_id;
191 last_node_id = it->second;
192 if (old_last != std::numeric_limits<uint32_t>::max()) {
193 regular_edges << " node" << old_last << ":p" << old_dex_pc
194 << " -> node" << last_node_id << ":p" << dex_pc
195 << ";\n";
196 }
197 }
198
199 // Look at the exceptions of the first entry.
200 CatchHandlerIterator catch_it(*code_item, dex_pc);
201 for (; catch_it.HasNext(); catch_it.Next()) {
202 exception_targets.insert(catch_it.GetHandlerAddress());
203 }
204 }
205
206 // Handle instruction.
207
208 // Branch: something with at most two targets.
209 if (inst->IsBranch()) {
210 const int32_t offset = inst->GetTargetOffset();
211 const bool conditional = !inst->IsUnconditional();
212
213 auto target_it = dex_pc_to_node_id.find(dex_pc + offset);
214 if (target_it != dex_pc_to_node_id.end()) {
215 taken_edges << " node" << last_node_id << ":p" << dex_pc
216 << " -> node" << target_it->second << ":p" << (dex_pc + offset)
217 << ";\n";
218 }
219 if (!conditional) {
220 // No fall-through.
221 last_node_id = std::numeric_limits<uint32_t>::max();
222 }
223 } else if (inst->IsSwitch()) {
224 // TODO: Iterate through all switch targets.
225 const uint16_t* insns = code_item->insns_ + dex_pc;
226 /* make sure the start of the switch is in range */
227 int32_t switch_offset = insns[1] | (static_cast<int32_t>(insns[2]) << 16);
228 /* offset to switch table is a relative branch-style offset */
229 const uint16_t* switch_insns = insns + switch_offset;
230 uint32_t switch_count = switch_insns[1];
231 int32_t targets_offset;
232 if ((*insns & 0xff) == Instruction::PACKED_SWITCH) {
233 /* 0=sig, 1=count, 2/3=firstKey */
234 targets_offset = 4;
235 } else {
236 /* 0=sig, 1=count, 2..count*2 = keys */
237 targets_offset = 2 + 2 * switch_count;
238 }
239 /* make sure the end of the switch is in range */
240 /* verify each switch target */
241 for (uint32_t targ = 0; targ < switch_count; targ++) {
242 int32_t offset =
243 static_cast<int32_t>(switch_insns[targets_offset + targ * 2]) |
244 static_cast<int32_t>(switch_insns[targets_offset + targ * 2 + 1] << 16);
245 int32_t abs_offset = dex_pc + offset;
246 auto target_it = dex_pc_to_node_id.find(abs_offset);
247 if (target_it != dex_pc_to_node_id.end()) {
248 // TODO: value label.
249 taken_edges << " node" << last_node_id << ":p" << dex_pc
250 << " -> node" << target_it->second << ":p" << (abs_offset)
251 << ";\n";
252 }
253 }
254 }
255
256 // Exception edges. If this is not the first instruction in the block
257 if (block_start_dex_pc != dex_pc) {
258 std::set<uint32_t> current_handler_pcs;
259 CatchHandlerIterator catch_it(*code_item, dex_pc);
260 for (; catch_it.HasNext(); catch_it.Next()) {
261 current_handler_pcs.insert(catch_it.GetHandlerAddress());
262 }
263 if (current_handler_pcs != exception_targets) {
264 exception_targets.clear(); // Clear so we don't do something at the end.
265 blocks_with_detailed_exceptions.insert(block_start_dex_pc);
266 }
267 }
268
269 if (inst->IsReturn() ||
270 (inst->Opcode() == Instruction::THROW) ||
271 (inst->IsBranch() && inst->IsUnconditional())) {
272 // No fall-through.
273 last_node_id = std::numeric_limits<uint32_t>::max();
274 }
275 }
276 // Finish up the last block, if it had common exceptions.
277 if (!exception_targets.empty()) {
278 // It seems the last block had common exception handlers. Add the exception edges now.
279 uint32_t node_id = dex_pc_to_node_id.find(block_start_dex_pc)->second;
280 for (uint32_t handler_pc : exception_targets) {
281 auto node_id_it = dex_pc_to_incl_id.find(handler_pc);
282 if (node_id_it != dex_pc_to_incl_id.end()) {
283 exception_edges << " node" << node_id
284 << " -> node" << node_id_it->second << ":p" << handler_pc
285 << ";\n";
286 }
287 }
288 exception_targets.clear();
289 }
290 }
291
292 // Second pass for detailed exception blocks.
293 // TODO
294 // Exception edges. If this is not the first instruction in the block
295 for (uint32_t dex_pc : blocks_with_detailed_exceptions) {
296 const Instruction* inst = Instruction::At(&code_item->insns_[dex_pc]);
297 uint32_t this_node_id = dex_pc_to_incl_id.find(dex_pc)->second;
298 while (true) {
299 CatchHandlerIterator catch_it(*code_item, dex_pc);
300 if (catch_it.HasNext()) {
301 std::set<uint32_t> handled_targets;
302 for (; catch_it.HasNext(); catch_it.Next()) {
303 uint32_t handler_pc = catch_it.GetHandlerAddress();
304 auto it = handled_targets.find(handler_pc);
305 if (it == handled_targets.end()) {
306 auto node_id_it = dex_pc_to_incl_id.find(handler_pc);
307 if (node_id_it != dex_pc_to_incl_id.end()) {
308 exception_edges << " node" << this_node_id << ":p" << dex_pc
309 << " -> node" << node_id_it->second << ":p" << handler_pc
310 << ";\n";
311 }
312
313 // Mark as done.
314 handled_targets.insert(handler_pc);
315 }
316 }
317 }
318 if (inst->IsBasicBlockEnd()) {
319 break;
320 }
321
322 // Loop update. Have a break-out if the next instruction is a branch target and thus in
323 // another block.
324 dex_pc += inst->SizeInCodeUnits();
325 if (dex_pc >= code_item->insns_size_in_code_units_) {
326 break;
327 }
328 if (dex_pc_to_node_id.find(dex_pc) != dex_pc_to_node_id.end()) {
329 break;
330 }
331 inst = inst->Next();
332 }
333 }
334
335 // Write out the sub-graphs to make edges styled.
336 os << "\n";
337 os << " subgraph regular_edges {\n";
338 os << " edge [color=\"#000000\",weight=.3,len=3];\n\n";
339 os << " " << regular_edges.str() << "\n";
340 os << " }\n\n";
341
342 os << " subgraph taken_edges {\n";
343 os << " edge [color=\"#00FF00\",weight=.3,len=3];\n\n";
344 os << " " << taken_edges.str() << "\n";
345 os << " }\n\n";
346
347 os << " subgraph exception_edges {\n";
348 os << " edge [color=\"#FF0000\",weight=.3,len=3];\n\n";
349 os << " " << exception_edges.str() << "\n";
350 os << " }\n\n";
351 }
352
353 os << "}\n";
354}
355
356void DumpMethodCFG(const DexFile* dex_file, uint32_t dex_method_idx, std::ostream& os) {
357 // This is painful, we need to find the code item. That means finding the class, and then
358 // iterating the table.
359 if (dex_method_idx >= dex_file->NumMethodIds()) {
360 os << "Could not find method-idx.";
361 return;
362 }
363 const DexFile::MethodId& method_id = dex_file->GetMethodId(dex_method_idx);
364
365 const DexFile::ClassDef* class_def = dex_file->FindClassDef(method_id.class_idx_);
366 if (class_def == nullptr) {
367 os << "Could not find class-def.";
368 return;
369 }
370
371 const uint8_t* class_data = dex_file->GetClassData(*class_def);
372 if (class_data == nullptr) {
373 os << "No class data.";
374 return;
375 }
376
377 ClassDataItemIterator it(*dex_file, class_data);
Mathieu Chartiere17cf242017-06-19 11:05:51 -0700378 it.SkipAllFields();
David Sehrcaacd112016-10-20 16:27:02 -0700379
380 // Find method, and dump it.
Mathieu Chartierb7c273c2017-11-10 18:07:56 -0800381 while (it.HasNextMethod()) {
David Sehrcaacd112016-10-20 16:27:02 -0700382 uint32_t method_idx = it.GetMemberIndex();
383 if (method_idx == dex_method_idx) {
384 dumpMethodCFGImpl(dex_file, dex_method_idx, it.GetMethodCodeItem(), os);
385 return;
386 }
387 it.Next();
388 }
389
390 // Otherwise complain.
391 os << "Something went wrong, didn't find the method in the class data.";
392}
393
394} // namespace art