|  | // 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/dwarf_so_writer.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; | 
|  | }; | 
|  |  | 
|  | 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, /*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<InliningNode*> node_stack(zone_, 4); | 
|  | GrowableArray<DwarfPosition> 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<const Function*> function_stack(zone_, 8); | 
|  | GrowableArray<DwarfPosition> 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(); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | #if !defined(TARGET_ARCH_IA32) | 
|  | void Dwarf::WriteCallFrameInformationRecords( | 
|  | DwarfSharedObjectStream* 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 |