// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "vm/compiler/assembler/disassembler.h" #include "platform/text_buffer.h" #include "platform/unaligned.h" #include "vm/code_comments.h" #include "vm/code_patcher.h" #include "vm/dart_entry.h" #include "vm/deopt_instructions.h" #include "vm/globals.h" #include "vm/instructions.h" #include "vm/json_stream.h" #include "vm/log.h" #include "vm/os.h" namespace dart { #if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER) #if !defined(DART_PRECOMPILED_RUNTIME) DECLARE_FLAG(bool, trace_inlining_intervals); #endif DEFINE_FLAG(bool, trace_source_positions, false, "Source position diagnostics"); DEFINE_FLAG(bool, include_inlining_info_in_disassembly, true, "Include inlining information when printing disassembly") void DisassembleToStdout::ConsumeInstruction(char* hex_buffer, intptr_t hex_size, char* human_buffer, intptr_t human_size, Object* object, uword pc) { const int kHexColumnWidth = 23; #if defined(TARGET_ARCH_IS_32_BIT) THR_Print("0x%" Px32 " %s", static_cast(pc), hex_buffer); #else THR_Print("0x%" Px64 " %s", static_cast(pc), hex_buffer); #endif int hex_length = strlen(hex_buffer); if (hex_length < kHexColumnWidth) { for (int i = kHexColumnWidth - hex_length; i > 0; i--) { THR_Print(" "); } } THR_Print("%s", human_buffer); if (object != nullptr) { if (object->IsString()) { *object = String::EscapeSpecialCharacters(String::Cast(*object)); THR_Print(" \"%s\"", object->ToCString()); } else { THR_Print(" %s", object->ToCString()); } } THR_Print("\n"); } void DisassembleToStdout::Print(const char* format, ...) { va_list args; va_start(args, format); THR_VPrint(format, args); va_end(args); } void DisassembleToMemory::ConsumeInstruction(char* hex_buffer, intptr_t hex_size, char* human_buffer, intptr_t human_size, Object* object, uword pc) { if (overflowed_) { return; } intptr_t len; // TODO(compiler): Update assembler tests for other architectures so there is // coverage of encodings, not just mnemonics. #if defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) || \ defined(TARGET_ARCH_ARM) len = strlen(hex_buffer); if (remaining_ < len + 100) { *buffer_++ = '.'; *buffer_++ = '.'; *buffer_++ = '.'; *buffer_++ = '\n'; *buffer_++ = '\0'; overflowed_ = true; return; } memmove(buffer_, hex_buffer, len); buffer_ += len; remaining_ -= len; *buffer_++ = ' '; remaining_--; *buffer_ = '\0'; #endif len = strlen(human_buffer); if (remaining_ < len + 100) { *buffer_++ = '.'; *buffer_++ = '.'; *buffer_++ = '.'; *buffer_++ = '\n'; *buffer_++ = '\0'; overflowed_ = true; return; } memmove(buffer_, human_buffer, len); buffer_ += len; remaining_ -= len; *buffer_++ = '\n'; remaining_--; *buffer_ = '\0'; } void DisassembleToMemory::Print(const char* format, ...) { if (overflowed_) { return; } va_list measure_args; va_start(measure_args, format); intptr_t len = Utils::VSNPrint(nullptr, 0, format, measure_args); va_end(measure_args); if (remaining_ < len + 100) { *buffer_++ = '.'; *buffer_++ = '.'; *buffer_++ = '.'; *buffer_++ = '\n'; *buffer_++ = '\0'; overflowed_ = true; return; } va_list print_args; va_start(print_args, format); intptr_t len2 = Utils::VSNPrint(buffer_, len, format, print_args); va_end(print_args); ASSERT(len == len2); buffer_ += len; remaining_ -= len; *buffer_++ = '\n'; remaining_--; *buffer_ = '\0'; } void Disassembler::Disassemble(uword start, uword end, DisassemblyFormatter* formatter, const Code& code, const CodeComments* comments) { if (comments == nullptr) { comments = code.IsNull() ? &Code::Comments::New(0) : &code.comments(); } ASSERT(formatter != nullptr); char hex_buffer[kHexadecimalBufferSize]; // Instruction in hexadecimal form. char human_buffer[kUserReadableBufferSize]; // Human-readable instruction. uword pc = start; intptr_t comment_finger = 0; GrowableArray inlined_functions; GrowableArray token_positions; while (pc < end) { const intptr_t offset = pc - start; const intptr_t old_comment_finger = comment_finger; while (comment_finger < comments->Length() && comments->PCOffsetAt(comment_finger) <= offset) { formatter->Print(" ;; %s\n", comments->CommentAt(comment_finger)); comment_finger++; } if (FLAG_include_inlining_info_in_disassembly && old_comment_finger != comment_finger && !code.IsNull()) { char str[4000]; BufferFormatter f(str, sizeof(str)); // Comment emitted, emit inlining information. code.GetInlinedFunctionsAtInstruction(offset, &inlined_functions, &token_positions); // Skip top scope function printing (last entry in 'inlined_functions'). bool first = true; for (intptr_t i = 1; i < inlined_functions.length(); i++) { const char* name = inlined_functions[i]->ToQualifiedCString(); if (first) { f.Printf(" ;; Inlined [%s", name); first = false; } else { f.Printf(" -> %s", name); } } if (!first) { f.AddString("]\n"); formatter->Print("%s", str); } } int instruction_length; Object* object; DecodeInstruction(hex_buffer, sizeof(hex_buffer), human_buffer, sizeof(human_buffer), &instruction_length, code, &object, pc); formatter->ConsumeInstruction(hex_buffer, sizeof(hex_buffer), human_buffer, sizeof(human_buffer), object, FLAG_disassemble_relative ? offset : pc); pc += instruction_length; } } void Disassembler::DisassembleCodeHelper(const char* function_fullname, const char* function_info, const Code& code, bool optimized) { Thread* thread = Thread::Current(); Zone* zone = thread->zone(); THR_Print("Code for %sfunction '%s' (%s) {\n", optimized ? "optimized " : "", function_fullname, function_info); code.Disassemble(); THR_Print("}\n"); #if defined(TARGET_ARCH_IA32) if (code.pointer_offsets_length() > 0) { THR_Print("Pointer offsets for function: {\n"); // Pointer offsets are stored in descending order. Object& obj = Object::Handle(zone); for (intptr_t i = code.pointer_offsets_length() - 1; i >= 0; i--) { const uword addr = code.GetPointerOffsetAt(i) + code.PayloadStart(); obj = LoadUnaligned(reinterpret_cast(addr)); THR_Print(" %d : %#" Px " '%s'\n", code.GetPointerOffsetAt(i), addr, obj.ToCString()); } THR_Print("}\n"); } #else ASSERT(code.pointer_offsets_length() == 0); #endif if (FLAG_precompiled_mode) { // Global object pool emitted after it is finalized instead of per-function. } else { const ObjectPool& object_pool = ObjectPool::Handle(zone, code.GetObjectPool()); if (!object_pool.IsNull() && object_pool.Length() > 0) { object_pool.DebugPrint(); } } code.DumpSourcePositions(/*relative_addresses=*/FLAG_disassemble_relative); const uword start = code.PayloadStart(); const uword base = FLAG_disassemble_relative ? 0 : start; const PcDescriptors& descriptors = PcDescriptors::Handle(zone, code.pc_descriptors()); if (descriptors.Length() > 0) { TextBuffer buffer(100); buffer.Printf("PC Descriptors for function '%s' {\n", function_fullname); descriptors.WriteToBuffer(&buffer, base); buffer.AddString("}\n"); THR_Print("%s", buffer.buffer()); } #if !defined(DART_PRECOMPILED_RUNTIME) const Array& deopt_table = Array::Handle(zone, code.deopt_info_array()); if (!deopt_table.IsNull()) { intptr_t deopt_table_length = DeoptTable::GetLength(deopt_table); if (deopt_table_length > 0) { THR_Print("DeoptInfo: {\n"); Smi& offset = Smi::Handle(zone); TypedData& info = TypedData::Handle(zone); Smi& reason_and_flags = Smi::Handle(zone); for (intptr_t i = 0; i < deopt_table_length; ++i) { DeoptTable::GetEntry(deopt_table, i, &offset, &info, &reason_and_flags); const intptr_t reason = DeoptTable::ReasonField::decode(reason_and_flags.Value()); ASSERT((0 <= reason) && (reason < ICData::kDeoptNumReasons)); THR_Print( "%4" Pd ": 0x%" Px " %s (%s)\n", i, base + offset.Value(), DeoptInfo::ToCString(deopt_table, info), DeoptReasonToCString(static_cast(reason))); } THR_Print("}\n"); } } #endif // !defined(DART_PRECOMPILED_RUNTIME) const auto& stackmaps = CompressedStackMaps::Handle(zone, code.compressed_stackmaps()); if (!stackmaps.IsNull() && stackmaps.payload_size() > 0) { TextBuffer buffer(100); buffer.Printf("StackMaps for function '%s' {\n", function_fullname); stackmaps.WriteToBuffer(&buffer, base, "\n"); buffer.AddString("\n}\n"); THR_Print("%s", buffer.buffer()); } LocalVarDescriptors& var_descriptors = LocalVarDescriptors::Handle(zone); if (FLAG_print_variable_descriptors) { var_descriptors = code.GetLocalVarDescriptors(); } const intptr_t var_desc_length = var_descriptors.IsNull() ? 0 : var_descriptors.Length(); if (var_desc_length > 0) { THR_Print("Variable Descriptors for function '%s' {\n", function_fullname); String& var_name = String::Handle(zone); for (intptr_t i = 0; i < var_desc_length; i++) { var_name = var_descriptors.GetName(i); UntaggedLocalVarDescriptors::VarInfo var_info; var_descriptors.GetInfo(i, &var_info); const int8_t kind = var_info.kind(); if (kind == UntaggedLocalVarDescriptors::kSavedCurrentContext) { THR_Print(" saved current CTX reg offset %d\n", var_info.index()); } else { if (kind == UntaggedLocalVarDescriptors::kContextLevel) { THR_Print(" context level %d scope %d", var_info.index(), var_info.scope_id); } else if (kind == UntaggedLocalVarDescriptors::kStackVar) { THR_Print(" stack var '%s' offset %d", var_name.ToCString(), var_info.index()); } else { ASSERT(kind == UntaggedLocalVarDescriptors::kContextVar); THR_Print(" context var '%s' level %d offset %d", var_name.ToCString(), var_info.scope_id, var_info.index()); } THR_Print(" (valid %s-%s)\n", var_info.begin_pos.ToCString(), var_info.end_pos.ToCString()); } } THR_Print("}\n"); } const ExceptionHandlers& handlers = ExceptionHandlers::Handle(zone, code.exception_handlers()); if (handlers.num_entries() > 0 || handlers.has_async_handler()) { TextBuffer buffer(100); buffer.Printf("Exception Handlers for function '%s' {\n", function_fullname); handlers.WriteToBuffer(&buffer, base); buffer.AddString("}\n"); THR_Print("%s", buffer.buffer()); } #if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER) if (FLAG_precompiled_mode && code.catch_entry_moves_maps() != Object::null()) { THR_Print("Catch entry moves for function '%s' {\n", function_fullname); CatchEntryMovesMapReader reader( TypedData::Handle(code.catch_entry_moves_maps())); reader.PrintEntries(); THR_Print("}\n"); } #endif // defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER) { THR_Print("Entry points for function '%s' {\n", function_fullname); THR_Print(" [code+0x%02" Px "] %" Px " kNormal\n", Code::entry_point_offset(CodeEntryKind::kNormal) - kHeapObjectTag, code.EntryPoint() - start + base); THR_Print( " [code+0x%02" Px "] %" Px " kMonomorphic\n", Code::entry_point_offset(CodeEntryKind::kMonomorphic) - kHeapObjectTag, code.MonomorphicEntryPoint() - start + base); THR_Print( " [code+0x%02" Px "] %" Px " kUnchecked\n", Code::entry_point_offset(CodeEntryKind::kUnchecked) - kHeapObjectTag, code.UncheckedEntryPoint() - start + base); THR_Print(" [code+0x%02" Px "] %" Px " kMonomorphicUnchecked\n", Code::entry_point_offset(CodeEntryKind::kMonomorphicUnchecked) - kHeapObjectTag, code.MonomorphicUncheckedEntryPoint() - start + base); THR_Print("}\n"); } #if defined(DART_PRECOMPILED_RUNTIME) THR_Print("(Cannot show static call target functions in AOT runtime.)\n"); #else const auto& table = Array::Handle(zone, code.static_calls_target_table()); if (!table.IsNull()) { StaticCallsTable static_calls(table); if (static_calls.Length() > 0) { THR_Print("Static call target functions {\n"); auto& cls = Class::Handle(zone); auto& kind_type_and_offset = Smi::Handle(zone); auto& function = Function::Handle(zone); auto& object = Object::Handle(zone); auto& code = Code::Handle(zone); auto& dst_type = AbstractType::Handle(zone); for (auto& call : static_calls) { kind_type_and_offset = call.Get(); function = call.Get(); object = call.Get(); dst_type = AbstractType::null(); if (object.IsAbstractType()) { dst_type = AbstractType::Cast(object).ptr(); } else if (object.IsCode()) { code = Code::Cast(object).ptr(); } auto kind = Code::KindField::decode(kind_type_and_offset.Value()); auto offset = Code::OffsetField::decode(kind_type_and_offset.Value()); auto entry_point = Code::EntryPointField::decode(kind_type_and_offset.Value()); const char* s_entry_point = entry_point == Code::kUncheckedEntry ? " " : ""; const char* skind = nullptr; switch (kind) { case Code::kPcRelativeCall: skind = "pc-relative-call"; break; case Code::kPcRelativeTTSCall: skind = "pc-relative-tts-call"; break; case Code::kPcRelativeTailCall: skind = "pc-relative-tail-call"; break; case Code::kCallViaCode: skind = "call-via-code"; break; default: UNREACHABLE(); } if (!dst_type.IsNull()) { THR_Print(" 0x%" Px ": type testing stub %s, (%s)%s\n", base + offset, dst_type.ToCString(), skind, s_entry_point); } else if (function.IsNull()) { cls ^= code.owner(); if (cls.IsNull()) { THR_Print( " 0x%" Px ": %s, (%s)%s\n", base + offset, code.QualifiedName(NameFormattingParams( Object::kScrubbedName, Object::NameDisambiguation::kYes)), skind, s_entry_point); } else { THR_Print(" 0x%" Px ": allocation stub for %s, (%s)%s\n", base + offset, cls.ToCString(), skind, s_entry_point); } } else { THR_Print(" 0x%" Px ": %s, (%s)%s\n", base + offset, function.ToFullyQualifiedCString(), skind, s_entry_point); } } THR_Print("}\n"); } } #endif // defined(DART_PRECOMPILED_RUNTIME) #if !defined(DART_PRECOMPILED_RUNTIME) if (optimized && FLAG_trace_inlining_intervals) { code.DumpInlineIntervals(); } #endif if (FLAG_trace_source_positions) { code.DumpSourcePositions(); } } void Disassembler::DisassembleCode(const Function& function, const Code& code, bool optimized) { if (code.IsUnknownDartCode()) { return; } if (Log::Current() == Log::NoOpLog()) { // Output for this isolate will be shallowed, so don't bother generating it. return; } TextBuffer buffer(128); const char* function_fullname = function.ToFullyQualifiedCString(); buffer.Printf("%s", Function::KindToCString(function.kind())); if (function.HasSavedArgumentsDescriptor()) { const auto& args_desc_array = Array::Handle(function.saved_args_desc()); const ArgumentsDescriptor args_desc(args_desc_array); buffer.AddString(", "); args_desc.PrintTo(&buffer); } LogBlock lb; DisassembleCodeHelper(function_fullname, buffer.buffer(), code, optimized); } void Disassembler::DisassembleStub(const char* name, const Code& code) { if (Log::Current() == Log::NoOpLog()) { // Output for this isolate will be shallowed, so don't bother generating it. return; } LogBlock lb; THR_Print("Code for stub '%s': {\n", name); DisassembleToStdout formatter; code.Disassemble(&formatter); THR_Print("}\n"); const ObjectPool& object_pool = ObjectPool::Handle(code.object_pool()); if (FLAG_precompiled_mode) { THR_Print("(No object pool for bare instructions.)\n"); } else if (!object_pool.IsNull() && object_pool.Length() > 0) { object_pool.DebugPrint(); } } #else // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER) void Disassembler::DisassembleCode(const Function& function, const Code& code, bool optimized) {} #endif // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER) #if !defined(PRODUCT) void DisassembleToJSONStream::ConsumeInstruction(char* hex_buffer, intptr_t hex_size, char* human_buffer, intptr_t human_size, Object* object, uword pc) { // Instructions are represented as four consecutive values in a JSON array. // The first is the address of the instruction, the second is the hex string, // of the code, and the third is a human readable string, and the fourth is // the object loaded by the instruction. jsarr_.AddValueF("%" Pp "", pc); jsarr_.AddValue(hex_buffer); jsarr_.AddValue(human_buffer); if (object != nullptr) { jsarr_.AddValue(*object); } else { jsarr_.AddValueNull(); // Not a reference to null. } } void DisassembleToJSONStream::Print(const char* format, ...) { va_list measure_args; va_start(measure_args, format); intptr_t len = Utils::VSNPrint(nullptr, 0, format, measure_args); va_end(measure_args); char* p = reinterpret_cast(malloc(len + 1)); va_list print_args; va_start(print_args, format); intptr_t len2 = Utils::VSNPrint(p, len, format, print_args); va_end(print_args); ASSERT(len == len2); for (intptr_t i = 0; i < len; i++) { if (p[i] == '\n' || p[i] == '\r') { p[i] = ' '; } } // Instructions are represented as four consecutive values in a JSON array. // Comments only use the third slot. See above comment for more information. jsarr_.AddValueNull(); jsarr_.AddValueNull(); jsarr_.AddValue(p); jsarr_.AddValueNull(); free(p); } #endif // !defined(PRODUCT) } // namespace dart