| // 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/debug_info_stream.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 InliningNode : public ZoneObject { | |
| public: | |
| InliningNode(const Function& function, | |
| const DebugInfoPosition& 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; | |
| DebugInfoPosition position; | |
| int32_t start_pc_offset; | |
| int32_t end_pc_offset; | |
| InliningNode* children_head; | |
| InliningNode* children_tail; | |
| InliningNode* children_next; | |
| }; | |
| static const char* GetRootLibraryName(Zone* zone) { | |
| const auto& root_library = Library::Handle( | |
| zone, IsolateGroup::Current()->object_store()->root_library()); | |
| const auto& root_uri = String::Handle(zone, root_library.url()); | |
| return root_uri.ToCString(); | |
| } | |
| Dwarf::Dwarf(Zone* zone, | |
| const Trie<const char>* deobfuscation_trie, | |
| const char* compilation_unit_name) | |
| : zone_(zone), | |
| compilation_unit_name_(compilation_unit_name != nullptr | |
| ? compilation_unit_name | |
| : GetRootLibraryName(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); | |
| stream->string(compilation_unit_name_); // 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); | |
| 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<InliningNode*> node_stack(zone_, 4); | |
| GrowableArray<DebugInfoPosition> token_positions(zone_, 4); | |
| NoSafepointScope no_safepoint; | |
| ReadStream stream(map.Data(), map.Length()); | |
| int32_t current_pc_offset = 0; | |
| token_positions.Add(kNoDebugInfoPositionInfo); | |
| 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: { | |
| DebugInfoPosition& 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(kNoDebugInfoPositionInfo); | |
| 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 DwarfLineNumberProgramWriter : public DebugInfoLineNumberProgramWriter { | |
| public: | |
| DwarfLineNumberProgramWriter(Zone* zone, | |
| Dwarf* dwarf, | |
| DwarfWriteStream* stream) | |
| : DebugInfoLineNumberProgramWriter(zone), | |
| dwarf_(ASSERT_NOTNULL(dwarf)), | |
| stream_(stream) {} | |
| intptr_t LookupCodeLabel(const Code& code) override { | |
| return dwarf_->LookupCodeLabel(code); | |
| } | |
| intptr_t LookupScript(const Script& script) override { | |
| return dwarf_->LookupScript(script); | |
| } | |
| void EmitRow(intptr_t file, | |
| intptr_t line, | |
| intptr_t column, | |
| intptr_t label, | |
| intptr_t pc_offset) override { | |
| 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 >= DebugInfoPosition::kNoLine); | |
| if (line != line_) { | |
| stream_->u1(Dwarf::DW_LNS_advance_line); | |
| stream_->sleb128(line - line_); | |
| line_ = line; | |
| source_info_changed = true; | |
| } | |
| ASSERT(column >= DebugInfoPosition::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; | |
| } | |
| Dwarf* const dwarf_; | |
| 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( | |
| DebugInfoLineNumberProgramWriter* 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, | |
| DebugInfoPosition::kNoColumn, label, current_pc_offset); | |
| } | |
| } | |
| file_write(comments_buffer.buffer(), comments_buffer.length(), comments_file); | |
| file_close(comments_file); | |
| } | |
| 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) | |
| for (intptr_t i = 0; i < scripts_.length(); i++) { | |
| const Script& script = *(scripts_[i]); | |
| const char* uri_cstr = | |
| DebugInfo::ResolveScriptUri(zone_, script, deobfuscation_trie_); | |
| 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 | |
| DwarfLineNumberProgramWriter lnp_writer(zone_, this, stream); | |
| if (FLAG_write_code_comments_as_synthetic_source_to != nullptr) { | |
| WriteSyntheticLineNumberProgram(&lnp_writer); | |
| } else { | |
| DebugInfo::WriteLineNumberProgramFromCodeSourceMaps(zone_, codes_, | |
| &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(); | |
| } | |
| }); | |
| } | |
| #if !defined(TARGET_ARCH_IA32) | |
| void Dwarf::WriteCallFrameInformationRecords( | |
| DebugInfoStream* stream, | |
| const GrowableArray<FrameDescriptionEntry>& fdes) { | |
| ASSERT(!fdes.is_empty()); | |
| #if defined(TARGET_ARCH_X64) | |
| // The x86_64 psABI defines the DWARF register numbers, which differ from | |
| // the registers' usual encoding within instructions. | |
| const intptr_t DWARF_RA = 16; // No corresponding register. | |
| const intptr_t DWARF_FP = 6; // RBP | |
| #else | |
| const intptr_t DWARF_RA = ConcreteRegister(LINK_REGISTER); | |
| const intptr_t DWARF_FP = FP; | |
| #endif | |
| // Multiplier which will be used to scale operands of DW_CFA_offset and | |
| // DW_CFA_val_offset. | |
| const intptr_t kDataAlignment = -compiler::target::kWordSize; | |
| // Used to calculate offset to CIE in FDEs. | |
| const intptr_t cie_start = stream->Position(); | |
| stream->WritePrefixedLength([&] { | |
| stream->u4(0); // CIE | |
| stream->u1(1); // Version (must be 1 or 3) | |
| // Augmentation String | |
| stream->string("zR"); // NOLINT | |
| stream->uleb128(1); // Code alignment (must be 1). | |
| stream->sleb128(kDataAlignment); // Data alignment | |
| stream->u1(DWARF_RA); // Return address register | |
| stream->uleb128(1); // Augmentation size | |
| stream->u1(DW_EH_PE_pcrel | DW_EH_PE_sdata4); // FDE encoding. | |
| // CFA is caller's SP (FP+kCallerSpSlotFromFp*kWordSize) | |
| stream->u1(Dwarf::DW_CFA_def_cfa); | |
| stream->uleb128(DWARF_FP); | |
| stream->uleb128(kCallerSpSlotFromFp * compiler::target::kWordSize); | |
| }); | |
| // Emit rule defining that |reg| value is stored at CFA+offset. | |
| const auto cfa_offset = [&](intptr_t reg, intptr_t offset) { | |
| const intptr_t scaled_offset = offset / kDataAlignment; | |
| RELEASE_ASSERT(scaled_offset >= 0); | |
| stream->u1(Dwarf::DW_CFA_offset | reg); | |
| stream->uleb128(scaled_offset); | |
| }; | |
| // Emit an FDE covering each .text section. | |
| for (const auto& fde : fdes) { | |
| #if defined(DART_TARGET_OS_WINDOWS) && defined(TARGET_ARCH_IS_64_BIT) | |
| if (fde.label == 0) { | |
| // Unwinding instructions sections doesn't have label, doesn't dwarf | |
| continue; | |
| } | |
| #endif | |
| ASSERT(fde.label != 0); // Needed for relocations. | |
| stream->WritePrefixedLength([&]() { | |
| // Offset to CIE. Note that unlike pcrel this offset is encoded | |
| // backwards: it will be subtracted from the current position. | |
| stream->u4(stream->Position() - cie_start); | |
| // Start address as a PC relative reference. | |
| stream->RelativeSymbolOffset(fde.label, kInt32Size); | |
| stream->u4(fde.size); // Size. | |
| stream->u1(0); // Augmentation Data length. | |
| // Caller FP at FP+kSavedCallerPcSlotFromFp*kWordSize, | |
| // where FP is CFA - kCallerSpSlotFromFp*kWordSize. | |
| COMPILE_ASSERT((kSavedCallerFpSlotFromFp - kCallerSpSlotFromFp) <= 0); | |
| cfa_offset(DWARF_FP, (kSavedCallerFpSlotFromFp - kCallerSpSlotFromFp) * | |
| compiler::target::kWordSize); | |
| // Caller LR at FP+kSavedCallerPcSlotFromFp*kWordSize, | |
| // where FP is CFA - kCallerSpSlotFromFp*kWordSize | |
| COMPILE_ASSERT((kSavedCallerPcSlotFromFp - kCallerSpSlotFromFp) <= 0); | |
| cfa_offset(DWARF_RA, (kSavedCallerPcSlotFromFp - kCallerSpSlotFromFp) * | |
| compiler::target::kWordSize); | |
| }); | |
| } | |
| stream->u4(0); // end of section (FDE with zero length) | |
| } | |
| #endif | |
| #endif // DART_PRECOMPILER | |
| } // namespace dart |