1 | // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
2 | // for details. All rights reserved. Use of this source code is governed by a |
3 | // BSD-style license that can be found in the LICENSE file. |
4 | |
5 | #include "vm/compiler/assembler/disassembler.h" |
6 | |
7 | #include "platform/text_buffer.h" |
8 | #include "platform/unaligned.h" |
9 | #include "vm/code_patcher.h" |
10 | #include "vm/dart_entry.h" |
11 | #include "vm/deopt_instructions.h" |
12 | #include "vm/globals.h" |
13 | #include "vm/instructions.h" |
14 | #include "vm/json_stream.h" |
15 | #include "vm/log.h" |
16 | #include "vm/os.h" |
17 | |
18 | namespace dart { |
19 | |
20 | #if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER) |
21 | |
22 | #if !defined(DART_PRECOMPILED_RUNTIME) |
23 | DECLARE_FLAG(bool, trace_inlining_intervals); |
24 | #endif |
25 | |
26 | DEFINE_FLAG(bool, trace_source_positions, false, "Source position diagnostics" ); |
27 | |
28 | void DisassembleToStdout::ConsumeInstruction(char* hex_buffer, |
29 | intptr_t hex_size, |
30 | char* human_buffer, |
31 | intptr_t human_size, |
32 | Object* object, |
33 | uword pc) { |
34 | static const int kHexColumnWidth = 23; |
35 | #if defined(TARGET_ARCH_IS_32_BIT) |
36 | THR_Print("0x%" Px32 " %s" , static_cast<uint32_t>(pc), hex_buffer); |
37 | #else |
38 | THR_Print("0x%" Px64 " %s" , static_cast<uint64_t>(pc), hex_buffer); |
39 | #endif |
40 | int hex_length = strlen(hex_buffer); |
41 | if (hex_length < kHexColumnWidth) { |
42 | for (int i = kHexColumnWidth - hex_length; i > 0; i--) { |
43 | THR_Print(" " ); |
44 | } |
45 | } |
46 | THR_Print("%s" , human_buffer); |
47 | if (object != NULL) { |
48 | THR_Print(" %s" , object->ToCString()); |
49 | } |
50 | THR_Print("\n" ); |
51 | } |
52 | |
53 | void DisassembleToStdout::Print(const char* format, ...) { |
54 | va_list args; |
55 | va_start(args, format); |
56 | THR_VPrint(format, args); |
57 | va_end(args); |
58 | } |
59 | |
60 | void DisassembleToJSONStream::ConsumeInstruction(char* hex_buffer, |
61 | intptr_t hex_size, |
62 | char* human_buffer, |
63 | intptr_t human_size, |
64 | Object* object, |
65 | uword pc) { |
66 | // Instructions are represented as four consecutive values in a JSON array. |
67 | // The first is the address of the instruction, the second is the hex string, |
68 | // of the code, and the third is a human readable string, and the fourth is |
69 | // the object loaded by the instruction. |
70 | jsarr_.AddValueF("%" Pp "" , pc); |
71 | jsarr_.AddValue(hex_buffer); |
72 | jsarr_.AddValue(human_buffer); |
73 | |
74 | if (object != NULL) { |
75 | jsarr_.AddValue(*object); |
76 | } else { |
77 | jsarr_.AddValueNull(); // Not a reference to null. |
78 | } |
79 | } |
80 | |
81 | void DisassembleToJSONStream::Print(const char* format, ...) { |
82 | va_list args; |
83 | va_start(args, format); |
84 | intptr_t len = Utils::VSNPrint(NULL, 0, format, args); |
85 | va_end(args); |
86 | char* p = reinterpret_cast<char*>(malloc(len + 1)); |
87 | va_start(args, format); |
88 | intptr_t len2 = Utils::VSNPrint(p, len, format, args); |
89 | va_end(args); |
90 | ASSERT(len == len2); |
91 | for (intptr_t i = 0; i < len; i++) { |
92 | if (p[i] == '\n' || p[i] == '\r') { |
93 | p[i] = ' '; |
94 | } |
95 | } |
96 | // Instructions are represented as four consecutive values in a JSON array. |
97 | // Comments only use the third slot. See above comment for more information. |
98 | jsarr_.AddValueNull(); |
99 | jsarr_.AddValueNull(); |
100 | jsarr_.AddValue(p); |
101 | jsarr_.AddValueNull(); |
102 | free(p); |
103 | } |
104 | |
105 | void DisassembleToMemory::ConsumeInstruction(char* hex_buffer, |
106 | intptr_t hex_size, |
107 | char* human_buffer, |
108 | intptr_t human_size, |
109 | Object* object, |
110 | uword pc) { |
111 | if (overflowed_) { |
112 | return; |
113 | } |
114 | intptr_t len = strlen(human_buffer); |
115 | if (remaining_ < len + 100) { |
116 | *buffer_++ = '.'; |
117 | *buffer_++ = '.'; |
118 | *buffer_++ = '.'; |
119 | *buffer_++ = '\n'; |
120 | *buffer_++ = '\0'; |
121 | overflowed_ = true; |
122 | return; |
123 | } |
124 | memmove(buffer_, human_buffer, len); |
125 | buffer_ += len; |
126 | remaining_ -= len; |
127 | *buffer_++ = '\n'; |
128 | remaining_--; |
129 | *buffer_ = '\0'; |
130 | } |
131 | |
132 | void DisassembleToMemory::Print(const char* format, ...) { |
133 | if (overflowed_) { |
134 | return; |
135 | } |
136 | va_list args; |
137 | va_start(args, format); |
138 | intptr_t len = Utils::VSNPrint(NULL, 0, format, args); |
139 | va_end(args); |
140 | if (remaining_ < len + 100) { |
141 | *buffer_++ = '.'; |
142 | *buffer_++ = '.'; |
143 | *buffer_++ = '.'; |
144 | *buffer_++ = '\n'; |
145 | *buffer_++ = '\0'; |
146 | overflowed_ = true; |
147 | return; |
148 | } |
149 | va_start(args, format); |
150 | intptr_t len2 = Utils::VSNPrint(buffer_, len, format, args); |
151 | va_end(args); |
152 | ASSERT(len == len2); |
153 | buffer_ += len; |
154 | remaining_ -= len; |
155 | *buffer_++ = '\n'; |
156 | remaining_--; |
157 | *buffer_ = '\0'; |
158 | } |
159 | |
160 | void Disassembler::(uword start, |
161 | uword end, |
162 | DisassemblyFormatter* formatter, |
163 | const Code& code, |
164 | const Code::Comments* ) { |
165 | if (comments == nullptr) { |
166 | comments = code.IsNull() ? &Code::Comments::New(0) : &code.comments(); |
167 | } |
168 | ASSERT(formatter != NULL); |
169 | char hex_buffer[kHexadecimalBufferSize]; // Instruction in hexadecimal form. |
170 | char human_buffer[kUserReadableBufferSize]; // Human-readable instruction. |
171 | uword pc = start; |
172 | intptr_t = 0; |
173 | GrowableArray<const Function*> inlined_functions; |
174 | GrowableArray<TokenPosition> token_positions; |
175 | while (pc < end) { |
176 | const intptr_t offset = pc - start; |
177 | const intptr_t = comment_finger; |
178 | while (comment_finger < comments->Length() && |
179 | comments->PCOffsetAt(comment_finger) <= offset) { |
180 | formatter->Print( |
181 | " ;; %s\n" , |
182 | String::Handle(comments->CommentAt(comment_finger)).ToCString()); |
183 | comment_finger++; |
184 | } |
185 | if (old_comment_finger != comment_finger && !code.IsNull()) { |
186 | char str[4000]; |
187 | BufferFormatter f(str, sizeof(str)); |
188 | // Comment emitted, emit inlining information. |
189 | code.GetInlinedFunctionsAtInstruction(offset, &inlined_functions, |
190 | &token_positions); |
191 | // Skip top scope function printing (last entry in 'inlined_functions'). |
192 | bool first = true; |
193 | for (intptr_t i = 1; i < inlined_functions.length(); i++) { |
194 | const char* name = inlined_functions[i]->ToQualifiedCString(); |
195 | if (first) { |
196 | f.Printf(" ;; Inlined [%s" , name); |
197 | first = false; |
198 | } else { |
199 | f.Printf(" -> %s" , name); |
200 | } |
201 | } |
202 | if (!first) { |
203 | f.AddString("]\n" ); |
204 | formatter->Print("%s" , str); |
205 | } |
206 | } |
207 | int instruction_length; |
208 | Object* object; |
209 | DecodeInstruction(hex_buffer, sizeof(hex_buffer), human_buffer, |
210 | sizeof(human_buffer), &instruction_length, code, &object, |
211 | pc); |
212 | formatter->ConsumeInstruction(hex_buffer, sizeof(hex_buffer), human_buffer, |
213 | sizeof(human_buffer), object, |
214 | FLAG_disassemble_relative ? offset : pc); |
215 | pc += instruction_length; |
216 | } |
217 | } |
218 | |
219 | void Disassembler::DisassembleCodeHelper(const char* function_fullname, |
220 | const char* function_info, |
221 | const Code& code, |
222 | bool optimized) { |
223 | Zone* zone = Thread::Current()->zone(); |
224 | LocalVarDescriptors& var_descriptors = LocalVarDescriptors::Handle(zone); |
225 | if (FLAG_print_variable_descriptors) { |
226 | var_descriptors = code.GetLocalVarDescriptors(); |
227 | } |
228 | THR_Print("Code for %sfunction '%s' (%s) {\n" , optimized ? "optimized " : "" , |
229 | function_fullname, function_info); |
230 | code.Disassemble(); |
231 | THR_Print("}\n" ); |
232 | |
233 | #if defined(TARGET_ARCH_IA32) |
234 | THR_Print("Pointer offsets for function: {\n" ); |
235 | // Pointer offsets are stored in descending order. |
236 | Object& obj = Object::Handle(zone); |
237 | for (intptr_t i = code.pointer_offsets_length() - 1; i >= 0; i--) { |
238 | const uword addr = code.GetPointerOffsetAt(i) + code.PayloadStart(); |
239 | obj = LoadUnaligned(reinterpret_cast<ObjectPtr*>(addr)); |
240 | THR_Print(" %d : %#" Px " '%s'\n" , code.GetPointerOffsetAt(i), addr, |
241 | obj.ToCString()); |
242 | } |
243 | THR_Print("}\n" ); |
244 | #else |
245 | ASSERT(code.pointer_offsets_length() == 0); |
246 | #endif |
247 | |
248 | if (FLAG_precompiled_mode && FLAG_use_bare_instructions) { |
249 | THR_Print("(No object pool for bare instructions.)\n" ); |
250 | } else { |
251 | const ObjectPool& object_pool = |
252 | ObjectPool::Handle(zone, code.GetObjectPool()); |
253 | if (!object_pool.IsNull()) { |
254 | object_pool.DebugPrint(); |
255 | } |
256 | } |
257 | |
258 | code.DumpSourcePositions(/*relative_addresses=*/FLAG_disassemble_relative); |
259 | |
260 | THR_Print("PC Descriptors for function '%s' {\n" , function_fullname); |
261 | PcDescriptors::PrintHeaderString(); |
262 | const PcDescriptors& descriptors = |
263 | PcDescriptors::Handle(zone, code.pc_descriptors()); |
264 | THR_Print("%s}\n" , descriptors.ToCString()); |
265 | |
266 | const uword start = code.PayloadStart(); |
267 | const uword base = FLAG_disassemble_relative ? 0 : start; |
268 | |
269 | #if !defined(DART_PRECOMPILED_RUNTIME) |
270 | const Array& deopt_table = Array::Handle(zone, code.deopt_info_array()); |
271 | if (!deopt_table.IsNull()) { |
272 | intptr_t deopt_table_length = DeoptTable::GetLength(deopt_table); |
273 | if (deopt_table_length > 0) { |
274 | THR_Print("DeoptInfo: {\n" ); |
275 | Smi& offset = Smi::Handle(zone); |
276 | TypedData& info = TypedData::Handle(zone); |
277 | Smi& reason_and_flags = Smi::Handle(zone); |
278 | for (intptr_t i = 0; i < deopt_table_length; ++i) { |
279 | DeoptTable::GetEntry(deopt_table, i, &offset, &info, &reason_and_flags); |
280 | const intptr_t reason = |
281 | DeoptTable::ReasonField::decode(reason_and_flags.Value()); |
282 | ASSERT((0 <= reason) && (reason < ICData::kDeoptNumReasons)); |
283 | THR_Print( |
284 | "%4" Pd ": 0x%" Px " %s (%s)\n" , i, base + offset.Value(), |
285 | DeoptInfo::ToCString(deopt_table, info), |
286 | DeoptReasonToCString(static_cast<ICData::DeoptReasonId>(reason))); |
287 | } |
288 | THR_Print("}\n" ); |
289 | } |
290 | } |
291 | #endif // !defined(DART_PRECOMPILED_RUNTIME) |
292 | |
293 | THR_Print("StackMaps for function '%s' {\n" , function_fullname); |
294 | if (code.compressed_stackmaps() != CompressedStackMaps::null()) { |
295 | const auto& stackmaps = |
296 | CompressedStackMaps::Handle(zone, code.compressed_stackmaps()); |
297 | THR_Print("%s\n" , stackmaps.ToCString()); |
298 | } |
299 | THR_Print("}\n" ); |
300 | |
301 | if (FLAG_print_variable_descriptors) { |
302 | THR_Print("Variable Descriptors for function '%s' {\n" , function_fullname); |
303 | intptr_t var_desc_length = |
304 | var_descriptors.IsNull() ? 0 : var_descriptors.Length(); |
305 | String& var_name = String::Handle(zone); |
306 | for (intptr_t i = 0; i < var_desc_length; i++) { |
307 | var_name = var_descriptors.GetName(i); |
308 | LocalVarDescriptorsLayout::VarInfo var_info; |
309 | var_descriptors.GetInfo(i, &var_info); |
310 | const int8_t kind = var_info.kind(); |
311 | if (kind == LocalVarDescriptorsLayout::kSavedCurrentContext) { |
312 | THR_Print(" saved current CTX reg offset %d\n" , var_info.index()); |
313 | } else { |
314 | if (kind == LocalVarDescriptorsLayout::kContextLevel) { |
315 | THR_Print(" context level %d scope %d" , var_info.index(), |
316 | var_info.scope_id); |
317 | } else if (kind == LocalVarDescriptorsLayout::kStackVar) { |
318 | THR_Print(" stack var '%s' offset %d" , var_name.ToCString(), |
319 | var_info.index()); |
320 | } else { |
321 | ASSERT(kind == LocalVarDescriptorsLayout::kContextVar); |
322 | THR_Print(" context var '%s' level %d offset %d" , |
323 | var_name.ToCString(), var_info.scope_id, var_info.index()); |
324 | } |
325 | THR_Print(" (valid %s-%s)\n" , var_info.begin_pos.ToCString(), |
326 | var_info.end_pos.ToCString()); |
327 | } |
328 | } |
329 | THR_Print("}\n" ); |
330 | } |
331 | |
332 | THR_Print("Exception Handlers for function '%s' {\n" , function_fullname); |
333 | const ExceptionHandlers& handlers = |
334 | ExceptionHandlers::Handle(zone, code.exception_handlers()); |
335 | THR_Print("%s}\n" , handlers.ToCString()); |
336 | |
337 | #if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER) |
338 | if (code.catch_entry_moves_maps() != Object::null()) { |
339 | THR_Print("Catch entry moves for function '%s' {\n" , function_fullname); |
340 | CatchEntryMovesMapReader reader( |
341 | TypedData::Handle(code.catch_entry_moves_maps())); |
342 | reader.PrintEntries(); |
343 | THR_Print("}\n" ); |
344 | } |
345 | #endif // defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER) |
346 | |
347 | { |
348 | THR_Print("Entry points for function '%s' {\n" , function_fullname); |
349 | THR_Print(" [code+0x%02" Px "] %" Px " kNormal\n" , |
350 | Code::entry_point_offset(CodeEntryKind::kNormal) - kHeapObjectTag, |
351 | code.EntryPoint() - start + base); |
352 | THR_Print( |
353 | " [code+0x%02" Px "] %" Px " kMonomorphic\n" , |
354 | Code::entry_point_offset(CodeEntryKind::kMonomorphic) - kHeapObjectTag, |
355 | code.MonomorphicEntryPoint() - start + base); |
356 | THR_Print( |
357 | " [code+0x%02" Px "] %" Px " kUnchecked\n" , |
358 | Code::entry_point_offset(CodeEntryKind::kUnchecked) - kHeapObjectTag, |
359 | code.UncheckedEntryPoint() - start + base); |
360 | THR_Print(" [code+0x%02" Px "] %" Px " kMonomorphicUnchecked\n" , |
361 | Code::entry_point_offset(CodeEntryKind::kMonomorphicUnchecked) - |
362 | kHeapObjectTag, |
363 | code.MonomorphicUncheckedEntryPoint() - start + base); |
364 | THR_Print("}\n" ); |
365 | } |
366 | |
367 | #if defined(DART_PRECOMPILED_RUNTIME) |
368 | THR_Print("(Cannot show static call target functions in AOT runtime.)\n" ); |
369 | #else |
370 | { |
371 | THR_Print("Static call target functions {\n" ); |
372 | const auto& table = Array::Handle(zone, code.static_calls_target_table()); |
373 | auto& cls = Class::Handle(zone); |
374 | auto& kind_type_and_offset = Smi::Handle(zone); |
375 | auto& function = Function::Handle(zone); |
376 | auto& object = Object::Handle(zone); |
377 | auto& code = Code::Handle(zone); |
378 | auto& dst_type = AbstractType::Handle(zone); |
379 | if (!table.IsNull()) { |
380 | StaticCallsTable static_calls(table); |
381 | for (auto& call : static_calls) { |
382 | kind_type_and_offset = call.Get<Code::kSCallTableKindAndOffset>(); |
383 | function = call.Get<Code::kSCallTableFunctionTarget>(); |
384 | object = call.Get<Code::kSCallTableCodeOrTypeTarget>(); |
385 | |
386 | dst_type = AbstractType::null(); |
387 | if (object.IsAbstractType()) { |
388 | dst_type = AbstractType::Cast(object).raw(); |
389 | } else if (object.IsCode()) { |
390 | code = Code::Cast(object).raw(); |
391 | } |
392 | |
393 | auto kind = Code::KindField::decode(kind_type_and_offset.Value()); |
394 | auto offset = Code::OffsetField::decode(kind_type_and_offset.Value()); |
395 | auto entry_point = |
396 | Code::EntryPointField::decode(kind_type_and_offset.Value()); |
397 | |
398 | const char* s_entry_point = |
399 | entry_point == Code::kUncheckedEntry ? " <unchecked-entry>" : "" ; |
400 | const char* skind = nullptr; |
401 | switch (kind) { |
402 | case Code::kPcRelativeCall: |
403 | skind = "pc-relative-call" ; |
404 | break; |
405 | case Code::kPcRelativeTTSCall: |
406 | skind = "pc-relative-tts-call" ; |
407 | break; |
408 | case Code::kPcRelativeTailCall: |
409 | skind = "pc-relative-tail-call" ; |
410 | break; |
411 | case Code::kCallViaCode: |
412 | skind = "call-via-code" ; |
413 | break; |
414 | default: |
415 | UNREACHABLE(); |
416 | } |
417 | if (!dst_type.IsNull()) { |
418 | THR_Print(" 0x%" Px ": type testing stub %s, (%s)%s\n" , |
419 | base + offset, dst_type.ToCString(), skind, s_entry_point); |
420 | } else if (function.IsNull()) { |
421 | cls ^= code.owner(); |
422 | if (cls.IsNull()) { |
423 | THR_Print( |
424 | " 0x%" Px ": %s, (%s)%s\n" , base + offset, |
425 | code.QualifiedName(NameFormattingParams( |
426 | Object::kScrubbedName, Object::NameDisambiguation::kYes)), |
427 | skind, s_entry_point); |
428 | } else { |
429 | THR_Print(" 0x%" Px ": allocation stub for %s, (%s)%s\n" , |
430 | base + offset, cls.ToCString(), skind, s_entry_point); |
431 | } |
432 | } else { |
433 | THR_Print(" 0x%" Px ": %s, (%s)%s\n" , base + offset, |
434 | function.ToFullyQualifiedCString(), skind, s_entry_point); |
435 | } |
436 | } |
437 | } |
438 | THR_Print("}\n" ); |
439 | } |
440 | #endif // defined(DART_PRECOMPILED_RUNTIME) |
441 | |
442 | #if !defined(DART_PRECOMPILED_RUNTIME) |
443 | if (optimized && FLAG_trace_inlining_intervals) { |
444 | code.DumpInlineIntervals(); |
445 | } |
446 | #endif |
447 | |
448 | if (FLAG_trace_source_positions) { |
449 | code.DumpSourcePositions(); |
450 | } |
451 | } |
452 | |
453 | void Disassembler::DisassembleCode(const Function& function, |
454 | const Code& code, |
455 | bool optimized) { |
456 | TextBuffer buffer(128); |
457 | const char* function_fullname = function.ToFullyQualifiedCString(); |
458 | buffer.Printf("%s" , Function::KindToCString(function.kind())); |
459 | if (function.IsInvokeFieldDispatcher() || |
460 | function.IsNoSuchMethodDispatcher()) { |
461 | const auto& args_desc_array = Array::Handle(function.saved_args_desc()); |
462 | const ArgumentsDescriptor args_desc(args_desc_array); |
463 | buffer.AddString(", " ); |
464 | args_desc.PrintTo(&buffer); |
465 | } |
466 | LogBlock lb; |
467 | DisassembleCodeHelper(function_fullname, buffer.buffer(), code, optimized); |
468 | } |
469 | |
470 | void Disassembler::DisassembleStub(const char* name, const Code& code) { |
471 | LogBlock lb; |
472 | THR_Print("Code for stub '%s': {\n" , name); |
473 | DisassembleToStdout formatter; |
474 | code.Disassemble(&formatter); |
475 | THR_Print("}\n" ); |
476 | const ObjectPool& object_pool = ObjectPool::Handle(code.object_pool()); |
477 | if (FLAG_precompiled_mode && FLAG_use_bare_instructions) { |
478 | THR_Print("(No object pool for bare instructions.)\n" ); |
479 | } else if (!object_pool.IsNull()) { |
480 | object_pool.DebugPrint(); |
481 | } |
482 | } |
483 | |
484 | #else // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER) |
485 | |
486 | void Disassembler::DisassembleCode(const Function& function, |
487 | const Code& code, |
488 | bool optimized) {} |
489 | #endif // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER) |
490 | |
491 | } // namespace dart |
492 | |