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