// Copyright (c) 2017, 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/dwarf.h" #include "vm/code_comments.h" #include "vm/code_descriptors.h" #include "vm/elf.h" #include "vm/image_snapshot.h" #include "vm/object_store.h" #include "vm/version.h" namespace dart { #if defined(DART_PRECOMPILER) DEFINE_FLAG(bool, resolve_dwarf_paths, false, "Resolve script URIs to absolute or relative file paths in DWARF"); DEFINE_FLAG(charp, write_code_comments_as_synthetic_source_to, nullptr, "Print comments associated with instructions into the given file"); class DwarfPosition { public: DwarfPosition(int32_t line, int32_t column) : line_(line), column_(column) { // Should only have no line information if also no column information. ASSERT(line_ > kNoLine || column_ <= kNoColumn); } // CodeSourceMaps start the line and column registers at -1, not at 0, and // the arguments passed to ChangePosition are retrieved from CodeSourceMaps. explicit DwarfPosition(int32_t line) : DwarfPosition(line, -1) {} constexpr DwarfPosition() : line_(-1), column_(-1) {} // The DWARF standard uses 0 to denote missing line or column // information. static constexpr int32_t kNoLine = 0; static constexpr int32_t kNoColumn = 0; int32_t line() const { return line_ > kNoLine ? line_ : kNoLine; } int32_t column() const { return column_ > kNoColumn ? column_ : kNoColumn; } // Adjusts the contents given the arguments to a ChangePosition instruction // from CodeSourceMaps. void ChangePosition(int32_t line_delta, int32_t new_column) { line_ = Utils::AddWithWrapAround(line_, line_delta); column_ = new_column; } private: int32_t line_; int32_t column_; }; static constexpr auto kNoDwarfPositionInfo = DwarfPosition(); class InliningNode : public ZoneAllocated { public: InliningNode(const Function& function, const DwarfPosition& position, int32_t start_pc_offset) : function(function), position(position), start_pc_offset(start_pc_offset), end_pc_offset(-1), children_head(nullptr), children_tail(nullptr), children_next(nullptr) { ASSERT(!function.IsNull()); DEBUG_ASSERT(function.IsNotTemporaryScopedHandle()); } void AppendChild(InliningNode* child) { if (children_tail == nullptr) { children_head = children_tail = child; } else { children_tail->children_next = child; children_tail = child; } } const Function& function; DwarfPosition position; int32_t start_pc_offset; int32_t end_pc_offset; InliningNode* children_head; InliningNode* children_tail; InliningNode* children_next; }; Dwarf::Dwarf(Zone* zone, const Trie* deobfuscation_trie) : zone_(zone), deobfuscation_trie_(deobfuscation_trie), codes_(zone, 1024), code_to_label_(zone), functions_(zone, 1024), function_to_index_(zone), scripts_(zone, 1024), script_to_index_(zone) {} void Dwarf::AddCode(const Code& orig_code, intptr_t label) { ASSERT(!orig_code.IsNull()); ASSERT(label > 0); if (auto const old_pair = code_to_label_.Lookup(&orig_code)) { // Dwarf objects can be shared, so we may get the same information for a // given code object in different calls. In DEBUG mode, make sure the // information is the same before returning. ASSERT_EQUAL(label, old_pair->value); return; } // Generate an appropriately zoned ZoneHandle for storing. const auto& code = Code::ZoneHandle(zone_, orig_code.ptr()); codes_.Add(&code); // Currently assumes the name has the same lifetime as the Zone of the // Dwarf object (which is currently true). Otherwise, need to copy. code_to_label_.Insert({&code, label}); if (code.IsFunctionCode() && !code.IsUnknownDartCode()) { const Function& function = Function::Handle(zone_, code.function()); AddFunction(function); } const Array& inline_functions = Array::Handle(zone_, code.inlined_id_to_function()); if (!inline_functions.IsNull()) { Function& function = Function::Handle(zone_); for (intptr_t i = 0; i < inline_functions.Length(); i++) { function ^= inline_functions.At(i); AddFunction(function); } } } intptr_t Dwarf::AddFunction(const Function& function) { RELEASE_ASSERT(!function.IsNull()); FunctionIndexPair* pair = function_to_index_.Lookup(&function); if (pair != nullptr) { return pair->index_; } intptr_t index = functions_.length(); const Function& zone_func = Function::ZoneHandle(zone_, function.ptr()); function_to_index_.Insert(FunctionIndexPair(&zone_func, index)); functions_.Add(&zone_func); const Script& script = Script::Handle(zone_, function.script()); AddScript(script); return index; } intptr_t Dwarf::AddScript(const Script& script) { RELEASE_ASSERT(!script.IsNull()); ScriptIndexPair* pair = script_to_index_.Lookup(&script); if (pair != nullptr) { return pair->index_; } // DWARF file numbers start from 1. intptr_t index = scripts_.length() + 1; const Script& zone_script = Script::ZoneHandle(zone_, script.ptr()); script_to_index_.Insert(ScriptIndexPair(&zone_script, index)); scripts_.Add(&zone_script); return index; } intptr_t Dwarf::LookupFunction(const Function& function) { RELEASE_ASSERT(!function.IsNull()); FunctionIndexPair* pair = function_to_index_.Lookup(&function); if (pair == nullptr) { FATAL("Function detected too late during DWARF generation: %s", function.ToCString()); } return pair->index_; } intptr_t Dwarf::LookupScript(const Script& script) { RELEASE_ASSERT(!script.IsNull()); ScriptIndexPair* pair = script_to_index_.Lookup(&script); if (pair == nullptr) { FATAL("Script detected too late during DWARF generation: %s", script.ToCString()); } return pair->index_; } void Dwarf::WriteAbbreviations(DwarfWriteStream* stream) { // Dwarf data mostly takes the form of a tree, whose nodes are called // DIEs. Each DIE begins with an abbreviation code, and the abbreviation // describes the attributes of that DIE and their representation. stream->uleb128(kCompilationUnit); // Abbrev code. stream->uleb128(DW_TAG_compile_unit); // Type. stream->u1(DW_CHILDREN_yes); stream->uleb128(DW_AT_name); // Start of attributes. stream->uleb128(DW_FORM_string); stream->uleb128(DW_AT_producer); stream->uleb128(DW_FORM_string); stream->uleb128(DW_AT_comp_dir); stream->uleb128(DW_FORM_string); stream->uleb128(DW_AT_low_pc); stream->uleb128(DW_FORM_addr); stream->uleb128(DW_AT_high_pc); stream->uleb128(DW_FORM_addr); stream->uleb128(DW_AT_stmt_list); stream->uleb128(DW_FORM_sec_offset); stream->uleb128(0); stream->uleb128(0); // End of attributes. stream->uleb128(kAbstractFunction); // Abbrev code. stream->uleb128(DW_TAG_subprogram); // Type. stream->u1(DW_CHILDREN_yes); stream->uleb128(DW_AT_name); // Start of attributes. stream->uleb128(DW_FORM_string); stream->uleb128(DW_AT_decl_file); stream->uleb128(DW_FORM_udata); stream->uleb128(DW_AT_decl_line); stream->uleb128(DW_FORM_udata); stream->uleb128(DW_AT_decl_column); stream->uleb128(DW_FORM_udata); stream->uleb128(DW_AT_inline); stream->uleb128(DW_FORM_udata); stream->uleb128(0); stream->uleb128(0); // End of attributes. stream->uleb128(kConcreteFunction); // Abbrev code. stream->uleb128(DW_TAG_subprogram); // Type. stream->u1(DW_CHILDREN_yes); stream->uleb128(DW_AT_abstract_origin); // Start of attributes. stream->uleb128(DW_FORM_ref4); stream->uleb128(DW_AT_low_pc); stream->uleb128(DW_FORM_addr); stream->uleb128(DW_AT_high_pc); stream->uleb128(DW_FORM_addr); stream->uleb128(DW_AT_artificial); stream->uleb128(DW_FORM_flag); stream->uleb128(0); stream->uleb128(0); // End of attributes. stream->uleb128(kInlinedFunction); // Abbrev code. stream->uleb128(DW_TAG_inlined_subroutine); // Type. stream->u1(DW_CHILDREN_yes); stream->uleb128(DW_AT_abstract_origin); // Start of attributes. stream->uleb128(DW_FORM_ref4); stream->uleb128(DW_AT_low_pc); stream->uleb128(DW_FORM_addr); stream->uleb128(DW_AT_high_pc); stream->uleb128(DW_FORM_addr); stream->uleb128(DW_AT_call_file); stream->uleb128(DW_FORM_udata); stream->uleb128(DW_AT_call_line); stream->uleb128(DW_FORM_udata); stream->uleb128(DW_AT_call_column); stream->uleb128(DW_FORM_udata); stream->uleb128(DW_AT_artificial); stream->uleb128(DW_FORM_flag); stream->uleb128(0); stream->uleb128(0); // End of attributes. stream->uleb128(0); // End of abbreviations. } void Dwarf::WriteDebugInfo(DwarfWriteStream* stream) { // 7.5.1.1 Compilation Unit Header // Unit length. stream->WritePrefixedLength("cu", [&]() { stream->u2(2); // DWARF version 2 stream->u4(0); // debug_abbrev_offset stream->u1(compiler::target::kWordSize); // address_size // Compilation Unit DIE. We describe the entire Dart program as a single // compilation unit. Note we write attributes in the same order we declared // them in our abbreviation above in WriteAbbreviations. stream->uleb128(kCompilationUnit); const Library& root_library = Library::Handle( zone_, IsolateGroup::Current()->object_store()->root_library()); const String& root_uri = String::Handle(zone_, root_library.url()); stream->string(root_uri.ToCString()); // DW_AT_name const char* producer = zone_->PrintToString("Dart %s\n", Version::String()); stream->string(producer); // DW_AT_producer stream->string(""); // DW_AT_comp_dir // DW_AT_low_pc // The lowest instruction address in this object file that is part of our // compilation unit. Dwarf consumers use this to quickly decide which // compilation unit DIE to consult for a given pc. auto const isolate_instructions_label = ImageWriter::SectionLabel( ImageWriter::ProgramSection::Text, /*vm=*/false); stream->OffsetFromSymbol(isolate_instructions_label, 0); // DW_AT_high_pc // The highest instruction address in this object file that is part of our // compilation unit. Dwarf consumers use this to quickly decide which // compilation unit DIE to consult for a given pc. if (codes_.is_empty()) { // No code objects in this program, so set high_pc to same as low_pc. stream->OffsetFromSymbol(isolate_instructions_label, 0); } else { const Code& last_code = *codes_.Last(); auto const last_code_label = code_to_label_.LookupValue(&last_code); ASSERT(last_code_label > 0); stream->OffsetFromSymbol(last_code_label, last_code.Size()); } // DW_AT_stmt_list (offset into .debug_line) // Indicates which line number program is associated with this compilation // unit. We only emit a single line number program. stream->u4(0); WriteAbstractFunctions(stream); WriteConcreteFunctions(stream); stream->uleb128(0); // End of children. stream->uleb128(0); // End of entries. }); } void Dwarf::WriteAbstractFunctions(DwarfWriteStream* stream) { Script& script = Script::Handle(zone_); String& name = String::Handle(zone_); stream->InitializeAbstractOrigins(functions_.length()); // By the point we're creating DWARF information, scripts have already lost // their token stream and we can't look up their line number or column // information, hence the lack of DW_AT_decl_line and DW_AT_decl_column. for (intptr_t i = 0; i < functions_.length(); i++) { const Function& function = *(functions_[i]); name = function.QualifiedUserVisibleName(); script = function.script(); const intptr_t file = LookupScript(script); intptr_t line = 0; intptr_t column = 0; script.GetTokenLocation(function.token_pos(), &line, &column); auto const name_cstr = ImageWriter::Deobfuscate(zone_, deobfuscation_trie_, name.ToCString()); stream->RegisterAbstractOrigin(i); stream->uleb128(kAbstractFunction); stream->string(name_cstr); // DW_AT_name stream->uleb128(file); // DW_AT_decl_file stream->uleb128(line); // DW_AT_decl_line stream->uleb128(column); // DW_AT_decl_column stream->uleb128(DW_INL_inlined); // DW_AT_inline stream->uleb128(0); // End of children. } } void Dwarf::WriteConcreteFunctions(DwarfWriteStream* stream) { Function& function = Function::Handle(zone_); Script& script = Script::Handle(zone_); for (intptr_t i = 0; i < codes_.length(); i++) { const Code& code = *(codes_[i]); RELEASE_ASSERT(!code.IsNull()); if (!code.IsFunctionCode() || code.IsUnknownDartCode()) { continue; } function = code.function(); intptr_t function_index = LookupFunction(function); script = function.script(); intptr_t label = code_to_label_.LookupValue(&code); ASSERT(label > 0); stream->uleb128(kConcreteFunction); // DW_AT_abstract_origin // References a node written above in WriteAbstractFunctions. stream->AbstractOrigin(function_index); // DW_AT_low_pc stream->OffsetFromSymbol(label, 0); // DW_AT_high_pc stream->OffsetFromSymbol(label, code.Size()); // DW_AT_artificial stream->u1(function.is_visible() ? 0 : 1); InliningNode* node = ExpandInliningTree(code); if (node != nullptr) { for (InliningNode* child = node->children_head; child != nullptr; child = child->children_next) { WriteInliningNode(stream, child, label, script); } } stream->uleb128(0); // End of children. } } // Our state machine encodes position metadata such that we don't know the // end pc for an inlined function until it is popped, but DWARF DIEs encode // it where the function is pushed. We expand the state transitions into // an in-memory tree to do the conversion. InliningNode* Dwarf::ExpandInliningTree(const Code& code) { const CodeSourceMap& map = CodeSourceMap::Handle(zone_, code.code_source_map()); if (map.IsNull()) { return nullptr; } const Array& functions = Array::Handle(zone_, code.inlined_id_to_function()); const Function& root_function = Function::ZoneHandle(zone_, code.function()); if (root_function.IsNull()) { FATAL("Wherefore art thou functionless code, %s?\n", code.ToCString()); } GrowableArray node_stack(zone_, 4); GrowableArray token_positions(zone_, 4); NoSafepointScope no_safepoint; ReadStream stream(map.Data(), map.Length()); int32_t current_pc_offset = 0; token_positions.Add(kNoDwarfPositionInfo); InliningNode* root_node = new (zone_) InliningNode(root_function, token_positions.Last(), 0); root_node->end_pc_offset = code.Size(); node_stack.Add(root_node); while (stream.PendingBytes() > 0) { int32_t arg1; int32_t arg2 = -1; const uint8_t opcode = CodeSourceMapOps::Read(&stream, &arg1, &arg2); switch (opcode) { case CodeSourceMapOps::kChangePosition: { DwarfPosition& pos = token_positions[token_positions.length() - 1]; pos.ChangePosition(arg1, arg2); break; } case CodeSourceMapOps::kAdvancePC: { current_pc_offset += arg1; if (arg1 == 0) { // This happens at the start of the function where we emit a special // kAdvancePC 0 instruction to record information about the function // itself. We need to advance current_pc_offset a bit to prevent // starting inlining interval directy at the start of the function // itself. current_pc_offset += 1; } break; } case CodeSourceMapOps::kPushFunction: { const Function& child_func = Function::ZoneHandle(zone_, Function::RawCast(functions.At(arg1))); InliningNode* child_node = new (zone_) InliningNode(child_func, token_positions.Last(), current_pc_offset); node_stack.Last()->AppendChild(child_node); node_stack.Add(child_node); token_positions.Add(kNoDwarfPositionInfo); break; } case CodeSourceMapOps::kPopFunction: { // We never pop the root function. ASSERT(node_stack.length() > 1); ASSERT(token_positions.length() > 1); node_stack.Last()->end_pc_offset = current_pc_offset; node_stack.RemoveLast(); token_positions.RemoveLast(); break; } case CodeSourceMapOps::kNullCheck: { break; } default: UNREACHABLE(); } } while (node_stack.length() > 1) { node_stack.Last()->end_pc_offset = current_pc_offset; node_stack.RemoveLast(); token_positions.RemoveLast(); } ASSERT(node_stack[0] == root_node); return root_node; } void Dwarf::WriteInliningNode(DwarfWriteStream* stream, InliningNode* node, intptr_t root_label, const Script& parent_script) { ASSERT(root_label > 0); intptr_t file = LookupScript(parent_script); intptr_t function_index = LookupFunction(node->function); const Script& script = Script::Handle(zone_, node->function.script()); stream->uleb128(kInlinedFunction); // DW_AT_abstract_origin // References a node written above in WriteAbstractFunctions. stream->AbstractOrigin(function_index); // DW_AT_low_pc stream->OffsetFromSymbol(root_label, node->start_pc_offset); // DW_AT_high_pc stream->OffsetFromSymbol(root_label, node->end_pc_offset); // DW_AT_call_file stream->uleb128(file); // DW_AT_call_line stream->uleb128(node->position.line()); // DW_AT_call_column stream->uleb128(node->position.column()); // DW_AT_artificial stream->u1(node->function.is_visible() ? 0 : 1); for (InliningNode* child = node->children_head; child != nullptr; child = child->children_next) { WriteInliningNode(stream, child, root_label, script); } stream->uleb128(0); // End of children. } // Helper class for tracking state of DWARF registers and emitting // line number program commands to set these registers to the right // state. class LineNumberProgramWriter { public: explicit LineNumberProgramWriter(DwarfWriteStream* stream) : stream_(stream) {} void EmitRow(intptr_t file, intptr_t line, intptr_t column, intptr_t label, intptr_t pc_offset) { if (AddRow(file, line, column, label, pc_offset)) { // Address register must be updated from 0 before emitting an LNP row // (dartbug.com/41756). stream_->u1(Dwarf::DW_LNS_copy); } } // Associates the given file, line, and column information for the instruction // at the pc_offset into the instructions payload of the Code object with the // symbol asm_name. Returns whether any changes were made to the stream. DART_WARN_UNUSED_RESULT bool AddRow(intptr_t file, intptr_t line, intptr_t column, intptr_t label, intptr_t pc_offset) { ASSERT_EQUAL(end_sequence_, false); bool source_info_changed = false; // Note that files are 1-indexed. ASSERT(file >= 1); if (file != file_) { stream_->u1(Dwarf::DW_LNS_set_file); stream_->uleb128(file); file_ = file; source_info_changed = true; } ASSERT(line >= DwarfPosition::kNoLine); if (line != line_) { stream_->u1(Dwarf::DW_LNS_advance_line); stream_->sleb128(line - line_); line_ = line; source_info_changed = true; } ASSERT(column >= DwarfPosition::kNoColumn); if (column != column_) { stream_->u1(Dwarf::DW_LNS_set_column); stream_->uleb128(column); column_ = column; source_info_changed = true; } // If the file, line, and column information match that for the previous // AddRow call, no change is made to the stream. This is because all // addresses between two line number program rows inherit the source // information from the first. if (source_info_changed) { SetCurrentPosition(label, pc_offset); } return source_info_changed; } void MarkEnd() { ASSERT_EQUAL(end_sequence_, false); // End of contiguous machine code. stream_->u1(0); // This is an extended opcode stream_->u1(1); // that is 1 byte long stream_->u1(Dwarf::DW_LNE_end_sequence); end_sequence_ = true; } void MarkEnd(intptr_t label, intptr_t pc_offset) { ASSERT_EQUAL(end_sequence_, false); SetCurrentPosition(label, pc_offset); MarkEnd(); } private: void SetCurrentPosition(intptr_t label, intptr_t pc_offset) { // Each LNP row is either in a different function from the previous row // or is at an increasing PC offset into the same function. ASSERT(label > 0); ASSERT(pc_offset >= 0); ASSERT(label_ != label || pc_offset > pc_offset_); if (label_ != label) { // Set the address register to the given offset into the new code payload. auto const instr_size = 1 + compiler::target::kWordSize; stream_->u1(0); // This is an extended opcode stream_->u1(instr_size); // that is 5 or 9 bytes long stream_->u1(Dwarf::DW_LNE_set_address); stream_->OffsetFromSymbol(label, pc_offset); } else { // Change the address register by the difference in the two offsets. stream_->u1(Dwarf::DW_LNS_advance_pc); stream_->uleb128(pc_offset - pc_offset_); } label_ = label; pc_offset_ = pc_offset; } DwarfWriteStream* const stream_; // The initial values for the line number program state machine registers // according to the DWARF standard. intptr_t pc_offset_ = 0; intptr_t file_ = 1; intptr_t line_ = 1; intptr_t column_ = 0; bool end_sequence_ = false; // Other info not stored in the state machine registers. intptr_t label_ = 0; }; void Dwarf::WriteSyntheticLineNumberProgram(LineNumberProgramWriter* writer) { // We emit it last after all other scripts. const intptr_t comments_file_index = scripts_.length() + 1; auto file_open = Dart::file_open_callback(); auto file_write = Dart::file_write_callback(); auto file_close = Dart::file_close_callback(); if ((file_open == nullptr) || (file_write == nullptr) || (file_close == nullptr)) { OS::PrintErr("warning: Could not access file callbacks."); return; } TextBuffer comments_buffer(128 * KB); const char* filename = FLAG_write_code_comments_as_synthetic_source_to; void* comments_file = file_open(filename, /*write=*/true); if (comments_file == nullptr) { OS::PrintErr("warning: Failed to write code comments source: %s\n", filename); return; } // Lines in DWARF are 1-indexed. intptr_t current_line = 1; for (intptr_t i = 0; i < codes_.length(); i++) { const Code& code = *(codes_[i]); auto const label = code_to_label_.LookupValue(&code); ASSERT(label > 0); auto& comments = code.comments(); for (intptr_t i = 0, len = comments.Length(); i < len;) { intptr_t current_pc_offset = comments.PCOffsetAt(i); while (i < len && current_pc_offset == comments.PCOffsetAt(i)) { comments_buffer.AddString(comments.CommentAt(i)); comments_buffer.AddChar('\n'); current_line++; i++; } writer->EmitRow(comments_file_index, current_line - 1, DwarfPosition::kNoColumn, label, current_pc_offset); } } file_write(comments_buffer.buffer(), comments_buffer.length(), comments_file); file_close(comments_file); } void Dwarf::WriteLineNumberProgramFromCodeSourceMaps( LineNumberProgramWriter* writer) { Function& root_function = Function::Handle(zone_); Script& script = Script::Handle(zone_); CodeSourceMap& map = CodeSourceMap::Handle(zone_); Array& functions = Array::Handle(zone_); GrowableArray function_stack(zone_, 8); GrowableArray token_positions(zone_, 8); for (intptr_t i = 0; i < codes_.length(); i++) { const Code& code = *(codes_[i]); auto const label = code_to_label_.LookupValue(&code); ASSERT(label > 0); map = code.code_source_map(); if (map.IsNull()) { continue; } root_function = code.function(); functions = code.inlined_id_to_function(); NoSafepointScope no_safepoint; ReadStream code_map_stream(map.Data(), map.Length()); function_stack.Clear(); token_positions.Clear(); // CodeSourceMap might start in the following way: // // ChangePosition function.token_pos() // AdvancePC 0 // ChangePosition x // AdvancePC y // // This entry is emitted to ensure correct symbolization of // function listener frames produced by async unwinding. // (See EmitFunctionEntrySourcePositionDescriptorIfNeeded). // Directly interpreting this sequence would cause us to emit // multiple with the same pc into line number table and different // position information. To avoid this will make an adjustment for // the second record we emit: if position x is a synthetic one we will // simply drop the second record, if position x is real then we will // emit row with a slightly adjusted PC (by 1 byte). This would not // affect symbolization (you can't have a call that is 1 byte long) // but will avoid line number table entries with the same PC. bool function_entry_position_was_emitted = false; int32_t current_pc_offset = 0; function_stack.Add(&root_function); token_positions.Add(kNoDwarfPositionInfo); while (code_map_stream.PendingBytes() > 0) { int32_t arg1; int32_t arg2 = -1; const uint8_t opcode = CodeSourceMapOps::Read(&code_map_stream, &arg1, &arg2); switch (opcode) { case CodeSourceMapOps::kChangePosition: { DwarfPosition& pos = token_positions[token_positions.length() - 1]; pos.ChangePosition(arg1, arg2); break; } case CodeSourceMapOps::kAdvancePC: { // Emit a row for the previous PC value if the source location // changed since the last row was emitted. const Function& function = *(function_stack.Last()); script = function.script(); const intptr_t file = LookupScript(script); const intptr_t line = token_positions.Last().line(); const intptr_t column = token_positions.Last().column(); intptr_t pc_offset_adjustment = 0; bool should_emit = true; // If we are at the function entry and have already emitted a row // then adjust current_pc_offset to avoid duplicated entries. // See the comment below which explains why this code is here. if (current_pc_offset == 0 && function_entry_position_was_emitted) { pc_offset_adjustment = 1; // Ignore synthetic positions. Function entry position gives // more information anyway. should_emit = !(line == 0 && column == 0); } if (should_emit) { writer->EmitRow(file, line, column, label, current_pc_offset + pc_offset_adjustment); } current_pc_offset += arg1; if (arg1 == 0) { // Special case of AdvancePC 0. ASSERT(current_pc_offset == 0); ASSERT(!function_entry_position_was_emitted); function_entry_position_was_emitted = true; } break; } case CodeSourceMapOps::kPushFunction: { auto child_func = &Function::Handle(zone_, Function::RawCast(functions.At(arg1))); function_stack.Add(child_func); token_positions.Add(kNoDwarfPositionInfo); break; } case CodeSourceMapOps::kPopFunction: { // We never pop the root function. ASSERT(function_stack.length() > 1); ASSERT(token_positions.length() > 1); function_stack.RemoveLast(); token_positions.RemoveLast(); break; } case CodeSourceMapOps::kNullCheck: { break; } default: UNREACHABLE(); } } } } static constexpr char kResolvedFileRoot[] = "file:///"; static constexpr intptr_t kResolvedFileRootLen = sizeof(kResolvedFileRoot) - 1; static constexpr char kResolvedFlutterRoot[] = "org-dartlang-sdk:///flutter/"; static constexpr intptr_t kResolvedFlutterRootLen = sizeof(kResolvedFlutterRoot) - 1; static constexpr char kResolvedSdkRoot[] = "org-dartlang-sdk:///"; static constexpr intptr_t kResolvedSdkRootLen = sizeof(kResolvedSdkRoot) - 1; static constexpr char kResolvedGoogle3Root[] = "google3:///"; static constexpr intptr_t kResolvedGoogle3RootLen = sizeof(kResolvedGoogle3Root) - 1; static const char* ConvertResolvedURI(const char* str) { const intptr_t len = strlen(str); if (len > kResolvedFileRootLen && strncmp(str, kResolvedFileRoot, kResolvedFileRootLen) == 0) { #if defined(DART_HOST_OS_WINDOWS) return str + kResolvedFileRootLen; // Strip off the entire prefix. #else return str + kResolvedFileRootLen - 1; // Leave a '/' on the front. #endif } // Must do kResolvedFlutterRoot before kResolvedSdkRoot, since the latter is // a prefix of the former. if (len > kResolvedFlutterRootLen && strncmp(str, kResolvedFlutterRoot, kResolvedFlutterRootLen) == 0) { return str + kResolvedFlutterRootLen; // Strip off the entire prefix. } if (len > kResolvedSdkRootLen && strncmp(str, kResolvedSdkRoot, kResolvedSdkRootLen) == 0) { return str + kResolvedSdkRootLen; // Strip off the entire prefix. } if (len > kResolvedGoogle3RootLen && strncmp(str, kResolvedGoogle3Root, kResolvedGoogle3RootLen) == 0) { return str + kResolvedGoogle3RootLen; // Strip off the entire prefix. } return nullptr; } void Dwarf::WriteLineNumberProgram(DwarfWriteStream* stream) { // 6.2.4 The Line Number Program Header // 1. unit_length. This encoding implies 32-bit DWARF. stream->WritePrefixedLength("line", [&]() { stream->u2(2); // 2. DWARF version 2 // 3. header_length stream->WritePrefixedLength("lineheader", [&]() { stream->u1(1); // 4. minimum_instruction_length stream->u1(1); // 5. default_is_stmt (true for dsymutil compatibility). stream->u1(0); // 6. line_base stream->u1(1); // 7. line_range stream->u1(13); // 8. opcode_base (12 standard opcodes in Dwarf 2) // 9. standard_opcode_lengths stream->u1(0); // DW_LNS_copy, 0 operands stream->u1(1); // DW_LNS_advance_pc, 1 operands stream->u1(1); // DW_LNS_advance_list, 1 operands stream->u1(1); // DW_LNS_set_file, 1 operands stream->u1(1); // DW_LNS_set_column, 1 operands stream->u1(0); // DW_LNS_negate_stmt, 0 operands stream->u1(0); // DW_LNS_set_basic_block, 0 operands stream->u1(0); // DW_LNS_const_add_pc, 0 operands stream->u1(1); // DW_LNS_fixed_advance_pc, 1 operands stream->u1(0); // DW_LNS_set_prolog_end, 0 operands stream->u1(0); // DW_LNS_set_epilogue_begin, 0 operands stream->u1(1); // DW_LNS_set_isa, 1 operands // 10. include_directories (sequence of path names) // We don't emit any because we use full paths below. stream->u1(0); // 11. file_names (sequence of file entries) String& uri = String::Handle(zone_); for (intptr_t i = 0; i < scripts_.length(); i++) { const Script& script = *(scripts_[i]); const char* uri_cstr = nullptr; if (FLAG_resolve_dwarf_paths) { uri = script.resolved_url(); // Strictly enforce this to catch unresolvable cases. if (uri.IsNull()) { FATAL("no resolved URI for Script %s available", script.ToCString()); } // resolved_url is never obfuscated, so just convert the prefix. auto const orig_cstr = uri.ToCString(); auto const converted_cstr = ConvertResolvedURI(orig_cstr); // Strictly enforce this to catch inconvertible cases. if (converted_cstr == nullptr) { FATAL("cannot convert resolved URI %s", orig_cstr); } uri_cstr = converted_cstr; } else { uri = script.url(); ASSERT(!uri.IsNull()); uri_cstr = ImageWriter::Deobfuscate(zone_, deobfuscation_trie_, uri.ToCString()); } RELEASE_ASSERT(strlen(uri_cstr) != 0); stream->string(uri_cstr); // NOLINT stream->uleb128(0); // Include directory index. stream->uleb128(0); // File modification time. stream->uleb128(0); // File length. } if (FLAG_write_code_comments_as_synthetic_source_to != nullptr) { stream->string( // NOLINT FLAG_write_code_comments_as_synthetic_source_to); stream->uleb128(0); // Include directory index. stream->uleb128(0); // File modification time. stream->uleb128(0); // File length. } stream->u1(0); // End of file names. }); // 6.2.5 The Line Number Program LineNumberProgramWriter lnp_writer(stream); if (FLAG_write_code_comments_as_synthetic_source_to != nullptr) { WriteSyntheticLineNumberProgram(&lnp_writer); } else { WriteLineNumberProgramFromCodeSourceMaps(&lnp_writer); } // Advance pc to end of the compilation unit if not already there. if (codes_.length() != 0) { const intptr_t last_code_index = codes_.length() - 1; const Code& last_code = *(codes_[last_code_index]); const intptr_t last_pc_offset = last_code.Size(); auto const last_label = code_to_label_.LookupValue(&last_code); ASSERT(last_label > 0); lnp_writer.MarkEnd(last_label, last_pc_offset); } else { lnp_writer.MarkEnd(); } }); } #endif // DART_PRECOMPILER } // namespace dart