• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 for control flow graph dumping for the dexdump utility.
17  */
18 
19 #include "dexdump_cfg.h"
20 
21 #include <inttypes.h>
22 
23 #include <map>
24 #include <ostream>
25 #include <set>
26 #include <sstream>
27 
28 #include "dex/class_accessor-inl.h"
29 #include "dex/code_item_accessors-inl.h"
30 #include "dex/dex_file-inl.h"
31 #include "dex/dex_file_exception_helpers.h"
32 #include "dex/dex_instruction-inl.h"
33 
34 namespace art {
35 
DumpMethodCFG(const ClassAccessor::Method & method,std::ostream & os)36 void DumpMethodCFG(const ClassAccessor::Method& method, std::ostream& os) {
37   const DexFile* dex_file = &method.GetDexFile();
38   os << "digraph {\n";
39   os << "  # /* " << dex_file->PrettyMethod(method.GetIndex(), true) << " */\n";
40 
41   CodeItemDataAccessor accessor(method.GetInstructionsAndData());
42   std::set<uint32_t> dex_pc_is_branch_target;
43   {
44     // Go and populate.
45     for (const DexInstructionPcPair& pair : accessor) {
46       const Instruction* inst = &pair.Inst();
47       if (inst->IsBranch()) {
48         dex_pc_is_branch_target.insert(pair.DexPc() + inst->GetTargetOffset());
49       } else if (inst->IsSwitch()) {
50         const uint16_t* insns = reinterpret_cast<const uint16_t*>(inst);
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(pair.DexPc() + 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     bool first_in_block = true;
78     bool force_new_block = false;
79     for (const DexInstructionPcPair& pair : accessor) {
80       const uint32_t dex_pc = pair.DexPc();
81       if (dex_pc == 0 ||
82           (dex_pc_is_branch_target.find(dex_pc) != dex_pc_is_branch_target.end()) ||
83           force_new_block) {
84         uint32_t id = dex_pc_to_node_id.size();
85         if (id > 0) {
86           // End last node.
87           os << "}\"];\n";
88         }
89         // Start next node.
90         os << "  node" << id << " [shape=record,label=\"{";
91         dex_pc_to_node_id.insert(std::make_pair(dex_pc, id));
92         first_in_block = true;
93         force_new_block = false;
94       }
95 
96       // Register instruction.
97       dex_pc_to_incl_id.insert(std::make_pair(dex_pc, dex_pc_to_node_id.size() - 1));
98 
99       // Print instruction.
100       if (!first_in_block) {
101         os << " | ";
102       } else {
103         first_in_block = false;
104       }
105 
106       // Dump the instruction. Need to escape '"', '<', '>', '{' and '}'.
107       os << "<" << "p" << dex_pc << ">";
108       os << " 0x" << std::hex << dex_pc << std::dec << ": ";
109       std::string inst_str = pair.Inst().DumpString(dex_file);
110       size_t cur_start = 0;  // It's OK to start at zero, instruction dumps don't start with chars
111                              // we need to escape.
112       while (cur_start != std::string::npos) {
113         size_t next_escape = inst_str.find_first_of("\"{}<>", cur_start + 1);
114         if (next_escape == std::string::npos) {
115           os << inst_str.substr(cur_start, inst_str.size() - cur_start);
116           break;
117         } else {
118           os << inst_str.substr(cur_start, next_escape - cur_start);
119           // Escape all necessary characters.
120           while (next_escape < inst_str.size()) {
121             char c = inst_str[next_escape];
122             if (c == '"' || c == '{' || c == '}' || c == '<' || c == '>') {
123               os << '\\' << c;
124             } else {
125               break;
126             }
127             next_escape++;
128           }
129           if (next_escape >= inst_str.size()) {
130             next_escape = std::string::npos;
131           }
132           cur_start = next_escape;
133         }
134       }
135 
136       // Force a new block for some fall-throughs and some instructions that terminate the "local"
137       // control flow.
138       force_new_block = pair.Inst().IsSwitch() || pair.Inst().IsBasicBlockEnd();
139     }
140     // Close last node.
141     if (dex_pc_to_node_id.size() > 0) {
142       os << "}\"];\n";
143     }
144   }
145 
146   // Create edges between them.
147   {
148     std::ostringstream regular_edges;
149     std::ostringstream taken_edges;
150     std::ostringstream exception_edges;
151 
152     // Common set of exception edges.
153     std::set<uint32_t> exception_targets;
154 
155     // These blocks (given by the first dex pc) need exception per dex-pc handling in a second
156     // pass. In the first pass we try and see whether we can use a common set of edges.
157     std::set<uint32_t> blocks_with_detailed_exceptions;
158 
159     {
160       uint32_t last_node_id = std::numeric_limits<uint32_t>::max();
161       uint32_t old_dex_pc = 0;
162       uint32_t block_start_dex_pc = std::numeric_limits<uint32_t>::max();
163       for (const DexInstructionPcPair& pair : accessor) {
164         const Instruction* inst = &pair.Inst();
165         const uint32_t dex_pc = pair.DexPc();
166         {
167           auto it = dex_pc_to_node_id.find(dex_pc);
168           if (it != dex_pc_to_node_id.end()) {
169             if (!exception_targets.empty()) {
170               // It seems the last block had common exception handlers. Add the exception edges now.
171               uint32_t node_id = dex_pc_to_node_id.find(block_start_dex_pc)->second;
172               for (uint32_t handler_pc : exception_targets) {
173                 auto node_id_it = dex_pc_to_incl_id.find(handler_pc);
174                 if (node_id_it != dex_pc_to_incl_id.end()) {
175                   exception_edges << "  node" << node_id
176                       << " -> node" << node_id_it->second << ":p" << handler_pc
177                       << ";\n";
178                 }
179               }
180               exception_targets.clear();
181             }
182 
183             block_start_dex_pc = dex_pc;
184 
185             // Seems to be a fall-through, connect to last_node_id. May be spurious edges for things
186             // like switch data.
187             uint32_t old_last = last_node_id;
188             last_node_id = it->second;
189             if (old_last != std::numeric_limits<uint32_t>::max()) {
190               regular_edges << "  node" << old_last << ":p" << old_dex_pc
191                   << " -> node" << last_node_id << ":p" << dex_pc
192                   << ";\n";
193             }
194           }
195 
196           // Look at the exceptions of the first entry.
197           CatchHandlerIterator catch_it(accessor, dex_pc);
198           for (; catch_it.HasNext(); catch_it.Next()) {
199             exception_targets.insert(catch_it.GetHandlerAddress());
200           }
201         }
202 
203         // Handle instruction.
204 
205         // Branch: something with at most two targets.
206         if (inst->IsBranch()) {
207           const int32_t offset = inst->GetTargetOffset();
208           const bool conditional = !inst->IsUnconditional();
209 
210           auto target_it = dex_pc_to_node_id.find(dex_pc + offset);
211           if (target_it != dex_pc_to_node_id.end()) {
212             taken_edges << "  node" << last_node_id << ":p" << dex_pc
213                 << " -> node" << target_it->second << ":p" << (dex_pc + offset)
214                 << ";\n";
215           }
216           if (!conditional) {
217             // No fall-through.
218             last_node_id = std::numeric_limits<uint32_t>::max();
219           }
220         } else if (inst->IsSwitch()) {
221           // TODO: Iterate through all switch targets.
222           const uint16_t* insns = reinterpret_cast<const uint16_t*>(inst);
223           /* make sure the start of the switch is in range */
224           int32_t switch_offset = insns[1] | (static_cast<int32_t>(insns[2]) << 16);
225           /* offset to switch table is a relative branch-style offset */
226           const uint16_t* switch_insns = insns + switch_offset;
227           uint32_t switch_count = switch_insns[1];
228           int32_t targets_offset;
229           if ((*insns & 0xff) == Instruction::PACKED_SWITCH) {
230             /* 0=sig, 1=count, 2/3=firstKey */
231             targets_offset = 4;
232           } else {
233             /* 0=sig, 1=count, 2..count*2 = keys */
234             targets_offset = 2 + 2 * switch_count;
235           }
236           /* make sure the end of the switch is in range */
237           /* verify each switch target */
238           for (uint32_t targ = 0; targ < switch_count; targ++) {
239             int32_t offset =
240                 static_cast<int32_t>(switch_insns[targets_offset + targ * 2]) |
241                 static_cast<int32_t>(switch_insns[targets_offset + targ * 2 + 1] << 16);
242             int32_t abs_offset = dex_pc + offset;
243             auto target_it = dex_pc_to_node_id.find(abs_offset);
244             if (target_it != dex_pc_to_node_id.end()) {
245               // TODO: value label.
246               taken_edges << "  node" << last_node_id << ":p" << dex_pc
247                   << " -> node" << target_it->second << ":p" << (abs_offset)
248                   << ";\n";
249             }
250           }
251         }
252 
253         // Exception edges. If this is not the first instruction in the block
254         if (block_start_dex_pc != dex_pc) {
255           std::set<uint32_t> current_handler_pcs;
256           CatchHandlerIterator catch_it(accessor, dex_pc);
257           for (; catch_it.HasNext(); catch_it.Next()) {
258             current_handler_pcs.insert(catch_it.GetHandlerAddress());
259           }
260           if (current_handler_pcs != exception_targets) {
261             exception_targets.clear();  // Clear so we don't do something at the end.
262             blocks_with_detailed_exceptions.insert(block_start_dex_pc);
263           }
264         }
265 
266         if (inst->IsReturn() ||
267             (inst->Opcode() == Instruction::THROW) ||
268             (inst->IsBranch() && inst->IsUnconditional())) {
269           // No fall-through.
270           last_node_id = std::numeric_limits<uint32_t>::max();
271         }
272         old_dex_pc = pair.DexPc();
273       }
274       // Finish up the last block, if it had common exceptions.
275       if (!exception_targets.empty()) {
276         // It seems the last block had common exception handlers. Add the exception edges now.
277         uint32_t node_id = dex_pc_to_node_id.find(block_start_dex_pc)->second;
278         for (uint32_t handler_pc : exception_targets) {
279           auto node_id_it = dex_pc_to_incl_id.find(handler_pc);
280           if (node_id_it != dex_pc_to_incl_id.end()) {
281             exception_edges << "  node" << node_id
282                 << " -> node" << node_id_it->second << ":p" << handler_pc
283                 << ";\n";
284           }
285         }
286         exception_targets.clear();
287       }
288     }
289 
290     // Second pass for detailed exception blocks.
291     // TODO
292     // Exception edges. If this is not the first instruction in the block
293     for (uint32_t dex_pc : blocks_with_detailed_exceptions) {
294       const Instruction* inst = &accessor.InstructionAt(dex_pc);
295       uint32_t this_node_id = dex_pc_to_incl_id.find(dex_pc)->second;
296       while (true) {
297         CatchHandlerIterator catch_it(accessor, dex_pc);
298         if (catch_it.HasNext()) {
299           std::set<uint32_t> handled_targets;
300           for (; catch_it.HasNext(); catch_it.Next()) {
301             uint32_t handler_pc = catch_it.GetHandlerAddress();
302             auto it = handled_targets.find(handler_pc);
303             if (it == handled_targets.end()) {
304               auto node_id_it = dex_pc_to_incl_id.find(handler_pc);
305               if (node_id_it != dex_pc_to_incl_id.end()) {
306                 exception_edges << "  node" << this_node_id << ":p" << dex_pc
307                     << " -> node" << node_id_it->second << ":p" << handler_pc
308                     << ";\n";
309               }
310 
311               // Mark as done.
312               handled_targets.insert(handler_pc);
313             }
314           }
315         }
316         if (inst->IsBasicBlockEnd()) {
317           break;
318         }
319 
320         // Loop update. Have a break-out if the next instruction is a branch target and thus in
321         // another block.
322         dex_pc += inst->SizeInCodeUnits();
323         if (dex_pc >= accessor.InsnsSizeInCodeUnits()) {
324           break;
325         }
326         if (dex_pc_to_node_id.find(dex_pc) != dex_pc_to_node_id.end()) {
327           break;
328         }
329         inst = inst->Next();
330       }
331     }
332 
333     // Write out the sub-graphs to make edges styled.
334     os << "\n";
335     os << "  subgraph regular_edges {\n";
336     os << "    edge [color=\"#000000\",weight=.3,len=3];\n\n";
337     os << "    " << regular_edges.str() << "\n";
338     os << "  }\n\n";
339 
340     os << "  subgraph taken_edges {\n";
341     os << "    edge [color=\"#00FF00\",weight=.3,len=3];\n\n";
342     os << "    " << taken_edges.str() << "\n";
343     os << "  }\n\n";
344 
345     os << "  subgraph exception_edges {\n";
346     os << "    edge [color=\"#FF0000\",weight=.3,len=3];\n\n";
347     os << "    " << exception_edges.str() << "\n";
348     os << "  }\n\n";
349   }
350 
351   os << "}\n";
352 }
353 
354 
355 }  // namespace art
356