| 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| 2 | // for details. All rights reserved. Use of this source code is governed by a |
| 3 | // BSD-style license that can be found in the LICENSE file. |
| 4 | |
| 5 | #include "vm/dwarf.h" |
| 6 | |
| 7 | #include "vm/code_comments.h" |
| 8 | #include "vm/code_descriptors.h" |
| 9 | #include "vm/elf.h" |
| 10 | #include "vm/image_snapshot.h" |
| 11 | #include "vm/object_store.h" |
| 12 | |
| 13 | namespace dart { |
| 14 | |
| 15 | #if defined(DART_PRECOMPILER) |
| 16 | |
| 17 | DEFINE_FLAG(bool, |
| 18 | resolve_dwarf_paths, |
| 19 | false, |
| 20 | "Resolve script URIs to absolute or relative file paths in DWARF" ); |
| 21 | |
| 22 | DEFINE_FLAG(charp, |
| 23 | write_code_comments_as_synthetic_source_to, |
| 24 | nullptr, |
| 25 | "Print comments associated with instructions into the given file" ); |
| 26 | |
| 27 | class DwarfPosition { |
| 28 | public: |
| 29 | DwarfPosition(int32_t line, int32_t column) : line_(line), column_(column) { |
| 30 | // Should only have no line information if also no column information. |
| 31 | ASSERT(line_ > kNoLine || column_ <= kNoColumn); |
| 32 | } |
| 33 | // CodeSourceMaps start the line and column registers at -1, not at 0, and |
| 34 | // the arguments passed to ChangePosition are retrieved from CodeSourceMaps. |
| 35 | explicit DwarfPosition(int32_t line) : DwarfPosition(line, -1) {} |
| 36 | constexpr DwarfPosition() : line_(-1), column_(-1) {} |
| 37 | |
| 38 | // The DWARF standard uses 0 to denote missing line or column |
| 39 | // information. |
| 40 | static constexpr int32_t kNoLine = 0; |
| 41 | static constexpr int32_t kNoColumn = 0; |
| 42 | |
| 43 | int32_t line() const { return line_ > kNoLine ? line_ : kNoLine; } |
| 44 | int32_t column() const { return column_ > kNoColumn ? column_ : kNoColumn; } |
| 45 | |
| 46 | // Adjusts the contents given the arguments to a ChangePosition instruction |
| 47 | // from CodeSourceMaps. |
| 48 | void ChangePosition(int32_t line_delta, int32_t new_column) { |
| 49 | line_ = Utils::AddWithWrapAround(line_, line_delta); |
| 50 | column_ = new_column; |
| 51 | } |
| 52 | |
| 53 | private: |
| 54 | int32_t line_; |
| 55 | int32_t column_; |
| 56 | }; |
| 57 | |
| 58 | static constexpr auto kNoDwarfPositionInfo = DwarfPosition(); |
| 59 | |
| 60 | class InliningNode : public ZoneAllocated { |
| 61 | public: |
| 62 | InliningNode(const Function& function, |
| 63 | const DwarfPosition& position, |
| 64 | int32_t start_pc_offset) |
| 65 | : function(function), |
| 66 | position(position), |
| 67 | start_pc_offset(start_pc_offset), |
| 68 | end_pc_offset(-1), |
| 69 | children_head(nullptr), |
| 70 | children_tail(nullptr), |
| 71 | children_next(nullptr) { |
| 72 | ASSERT(!function.IsNull()); |
| 73 | DEBUG_ASSERT(function.IsNotTemporaryScopedHandle()); |
| 74 | } |
| 75 | |
| 76 | void AppendChild(InliningNode* child) { |
| 77 | if (children_tail == nullptr) { |
| 78 | children_head = children_tail = child; |
| 79 | } else { |
| 80 | children_tail->children_next = child; |
| 81 | children_tail = child; |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | const Function& function; |
| 86 | DwarfPosition position; |
| 87 | int32_t start_pc_offset; |
| 88 | int32_t end_pc_offset; |
| 89 | InliningNode* children_head; |
| 90 | InliningNode* children_tail; |
| 91 | InliningNode* children_next; |
| 92 | }; |
| 93 | |
| 94 | Dwarf::Dwarf(Zone* zone, const Trie<const char>* deobfuscation_trie) |
| 95 | : zone_(zone), |
| 96 | deobfuscation_trie_(deobfuscation_trie), |
| 97 | codes_(zone, 1024), |
| 98 | code_to_label_(zone), |
| 99 | functions_(zone, 1024), |
| 100 | function_to_index_(zone), |
| 101 | scripts_(zone, 1024), |
| 102 | script_to_index_(zone) {} |
| 103 | |
| 104 | void Dwarf::AddCode(const Code& orig_code, intptr_t label) { |
| 105 | ASSERT(!orig_code.IsNull()); |
| 106 | ASSERT(label > 0); |
| 107 | |
| 108 | if (auto const old_pair = code_to_label_.Lookup(&orig_code)) { |
| 109 | // Dwarf objects can be shared, so we may get the same information for a |
| 110 | // given code object in different calls. In DEBUG mode, make sure the |
| 111 | // information is the same before returning. |
| 112 | ASSERT_EQUAL(label, old_pair->value); |
| 113 | return; |
| 114 | } |
| 115 | |
| 116 | // Generate an appropriately zoned ZoneHandle for storing. |
| 117 | const auto& code = Code::ZoneHandle(zone_, orig_code.ptr()); |
| 118 | codes_.Add(&code); |
| 119 | // Currently assumes the name has the same lifetime as the Zone of the |
| 120 | // Dwarf object (which is currently true). Otherwise, need to copy. |
| 121 | code_to_label_.Insert({&code, label}); |
| 122 | |
| 123 | if (code.IsFunctionCode() && !code.IsUnknownDartCode()) { |
| 124 | const Function& function = Function::Handle(zone_, code.function()); |
| 125 | AddFunction(function); |
| 126 | } |
| 127 | const Array& inline_functions = |
| 128 | Array::Handle(zone_, code.inlined_id_to_function()); |
| 129 | if (!inline_functions.IsNull()) { |
| 130 | Function& function = Function::Handle(zone_); |
| 131 | for (intptr_t i = 0; i < inline_functions.Length(); i++) { |
| 132 | function ^= inline_functions.At(i); |
| 133 | AddFunction(function); |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | intptr_t Dwarf::AddFunction(const Function& function) { |
| 139 | RELEASE_ASSERT(!function.IsNull()); |
| 140 | FunctionIndexPair* pair = function_to_index_.Lookup(&function); |
| 141 | if (pair != nullptr) { |
| 142 | return pair->index_; |
| 143 | } |
| 144 | intptr_t index = functions_.length(); |
| 145 | const Function& zone_func = Function::ZoneHandle(zone_, function.ptr()); |
| 146 | function_to_index_.Insert(FunctionIndexPair(&zone_func, index)); |
| 147 | functions_.Add(&zone_func); |
| 148 | const Script& script = Script::Handle(zone_, function.script()); |
| 149 | AddScript(script); |
| 150 | return index; |
| 151 | } |
| 152 | |
| 153 | intptr_t Dwarf::AddScript(const Script& script) { |
| 154 | RELEASE_ASSERT(!script.IsNull()); |
| 155 | ScriptIndexPair* pair = script_to_index_.Lookup(&script); |
| 156 | if (pair != nullptr) { |
| 157 | return pair->index_; |
| 158 | } |
| 159 | // DWARF file numbers start from 1. |
| 160 | intptr_t index = scripts_.length() + 1; |
| 161 | const Script& zone_script = Script::ZoneHandle(zone_, script.ptr()); |
| 162 | script_to_index_.Insert(ScriptIndexPair(&zone_script, index)); |
| 163 | scripts_.Add(&zone_script); |
| 164 | return index; |
| 165 | } |
| 166 | |
| 167 | intptr_t Dwarf::LookupFunction(const Function& function) { |
| 168 | RELEASE_ASSERT(!function.IsNull()); |
| 169 | FunctionIndexPair* pair = function_to_index_.Lookup(&function); |
| 170 | if (pair == nullptr) { |
| 171 | FATAL("Function detected too late during DWARF generation: %s" , |
| 172 | function.ToCString()); |
| 173 | } |
| 174 | return pair->index_; |
| 175 | } |
| 176 | |
| 177 | intptr_t Dwarf::LookupScript(const Script& script) { |
| 178 | RELEASE_ASSERT(!script.IsNull()); |
| 179 | ScriptIndexPair* pair = script_to_index_.Lookup(&script); |
| 180 | if (pair == nullptr) { |
| 181 | FATAL("Script detected too late during DWARF generation: %s" , |
| 182 | script.ToCString()); |
| 183 | } |
| 184 | return pair->index_; |
| 185 | } |
| 186 | |
| 187 | void Dwarf::WriteAbbreviations(DwarfWriteStream* stream) { |
| 188 | // Dwarf data mostly takes the form of a tree, whose nodes are called |
| 189 | // DIEs. Each DIE begins with an abbreviation code, and the abbreviation |
| 190 | // describes the attributes of that DIE and their representation. |
| 191 | |
| 192 | stream->uleb128(kCompilationUnit); // Abbrev code. |
| 193 | stream->uleb128(DW_TAG_compile_unit); // Type. |
| 194 | stream->u1(DW_CHILDREN_yes); |
| 195 | stream->uleb128(DW_AT_name); // Start of attributes. |
| 196 | stream->uleb128(DW_FORM_string); |
| 197 | stream->uleb128(DW_AT_producer); |
| 198 | stream->uleb128(DW_FORM_string); |
| 199 | stream->uleb128(DW_AT_comp_dir); |
| 200 | stream->uleb128(DW_FORM_string); |
| 201 | stream->uleb128(DW_AT_low_pc); |
| 202 | stream->uleb128(DW_FORM_addr); |
| 203 | stream->uleb128(DW_AT_high_pc); |
| 204 | stream->uleb128(DW_FORM_addr); |
| 205 | stream->uleb128(DW_AT_stmt_list); |
| 206 | stream->uleb128(DW_FORM_sec_offset); |
| 207 | stream->uleb128(0); |
| 208 | stream->uleb128(0); // End of attributes. |
| 209 | |
| 210 | stream->uleb128(kAbstractFunction); // Abbrev code. |
| 211 | stream->uleb128(DW_TAG_subprogram); // Type. |
| 212 | stream->u1(DW_CHILDREN_yes); |
| 213 | stream->uleb128(DW_AT_name); // Start of attributes. |
| 214 | stream->uleb128(DW_FORM_string); |
| 215 | stream->uleb128(DW_AT_decl_file); |
| 216 | stream->uleb128(DW_FORM_udata); |
| 217 | stream->uleb128(DW_AT_inline); |
| 218 | stream->uleb128(DW_FORM_udata); |
| 219 | stream->uleb128(0); |
| 220 | stream->uleb128(0); // End of attributes. |
| 221 | |
| 222 | stream->uleb128(kConcreteFunction); // Abbrev code. |
| 223 | stream->uleb128(DW_TAG_subprogram); // Type. |
| 224 | stream->u1(DW_CHILDREN_yes); |
| 225 | stream->uleb128(DW_AT_abstract_origin); // Start of attributes. |
| 226 | stream->uleb128(DW_FORM_ref4); |
| 227 | stream->uleb128(DW_AT_low_pc); |
| 228 | stream->uleb128(DW_FORM_addr); |
| 229 | stream->uleb128(DW_AT_high_pc); |
| 230 | stream->uleb128(DW_FORM_addr); |
| 231 | stream->uleb128(DW_AT_artificial); |
| 232 | stream->uleb128(DW_FORM_flag); |
| 233 | stream->uleb128(0); |
| 234 | stream->uleb128(0); // End of attributes. |
| 235 | |
| 236 | stream->uleb128(kInlinedFunction); // Abbrev code. |
| 237 | stream->uleb128(DW_TAG_inlined_subroutine); // Type. |
| 238 | stream->u1(DW_CHILDREN_yes); |
| 239 | stream->uleb128(DW_AT_abstract_origin); // Start of attributes. |
| 240 | stream->uleb128(DW_FORM_ref4); |
| 241 | stream->uleb128(DW_AT_low_pc); |
| 242 | stream->uleb128(DW_FORM_addr); |
| 243 | stream->uleb128(DW_AT_high_pc); |
| 244 | stream->uleb128(DW_FORM_addr); |
| 245 | stream->uleb128(DW_AT_call_file); |
| 246 | stream->uleb128(DW_FORM_udata); |
| 247 | stream->uleb128(DW_AT_call_line); |
| 248 | stream->uleb128(DW_FORM_udata); |
| 249 | stream->uleb128(DW_AT_call_column); |
| 250 | stream->uleb128(DW_FORM_udata); |
| 251 | stream->uleb128(DW_AT_artificial); |
| 252 | stream->uleb128(DW_FORM_flag); |
| 253 | stream->uleb128(0); |
| 254 | stream->uleb128(0); // End of attributes. |
| 255 | |
| 256 | stream->uleb128(0); // End of abbreviations. |
| 257 | } |
| 258 | |
| 259 | void Dwarf::WriteDebugInfo(DwarfWriteStream* stream) { |
| 260 | // 7.5.1.1 Compilation Unit Header |
| 261 | |
| 262 | // Unit length. |
| 263 | stream->WritePrefixedLength("cu" , [&]() { |
| 264 | stream->u2(2); // DWARF version 2 |
| 265 | stream->u4(0); // debug_abbrev_offset |
| 266 | stream->u1(compiler::target::kWordSize); // address_size |
| 267 | |
| 268 | // Compilation Unit DIE. We describe the entire Dart program as a single |
| 269 | // compilation unit. Note we write attributes in the same order we declared |
| 270 | // them in our abbreviation above in WriteAbbreviations. |
| 271 | stream->uleb128(kCompilationUnit); |
| 272 | const Library& root_library = Library::Handle( |
| 273 | zone_, IsolateGroup::Current()->object_store()->root_library()); |
| 274 | const String& root_uri = String::Handle(zone_, root_library.url()); |
| 275 | stream->string(root_uri.ToCString()); // DW_AT_name |
| 276 | stream->string("Dart VM" ); // DW_AT_producer |
| 277 | stream->string("" ); // DW_AT_comp_dir |
| 278 | |
| 279 | // DW_AT_low_pc |
| 280 | // The lowest instruction address in this object file that is part of our |
| 281 | // compilation unit. Dwarf consumers use this to quickly decide which |
| 282 | // compilation unit DIE to consult for a given pc. |
| 283 | auto const isolate_instructions_label = ImageWriter::SectionLabel( |
| 284 | ImageWriter::ProgramSection::Text, /*vm=*/false); |
| 285 | stream->OffsetFromSymbol(isolate_instructions_label, 0); |
| 286 | |
| 287 | // DW_AT_high_pc |
| 288 | // The highest instruction address in this object file that is part of our |
| 289 | // compilation unit. Dwarf consumers use this to quickly decide which |
| 290 | // compilation unit DIE to consult for a given pc. |
| 291 | if (codes_.is_empty()) { |
| 292 | // No code objects in this program, so set high_pc to same as low_pc. |
| 293 | stream->OffsetFromSymbol(isolate_instructions_label, 0); |
| 294 | } else { |
| 295 | const Code& last_code = *codes_.Last(); |
| 296 | auto const last_code_label = code_to_label_.LookupValue(&last_code); |
| 297 | ASSERT(last_code_label > 0); |
| 298 | stream->OffsetFromSymbol(last_code_label, last_code.Size()); |
| 299 | } |
| 300 | |
| 301 | // DW_AT_stmt_list (offset into .debug_line) |
| 302 | // Indicates which line number program is associated with this compilation |
| 303 | // unit. We only emit a single line number program. |
| 304 | stream->u4(0); |
| 305 | |
| 306 | WriteAbstractFunctions(stream); |
| 307 | WriteConcreteFunctions(stream); |
| 308 | |
| 309 | stream->uleb128(0); // End of children. |
| 310 | |
| 311 | stream->uleb128(0); // End of entries. |
| 312 | }); |
| 313 | } |
| 314 | |
| 315 | void Dwarf::WriteAbstractFunctions(DwarfWriteStream* stream) { |
| 316 | Script& script = Script::Handle(zone_); |
| 317 | String& name = String::Handle(zone_); |
| 318 | stream->InitializeAbstractOrigins(functions_.length()); |
| 319 | // By the point we're creating DWARF information, scripts have already lost |
| 320 | // their token stream and we can't look up their line number or column |
| 321 | // information, hence the lack of DW_AT_decl_line and DW_AT_decl_column. |
| 322 | for (intptr_t i = 0; i < functions_.length(); i++) { |
| 323 | const Function& function = *(functions_[i]); |
| 324 | name = function.QualifiedUserVisibleName(); |
| 325 | script = function.script(); |
| 326 | const intptr_t file = LookupScript(script); |
| 327 | auto const name_cstr = |
| 328 | ImageWriter::Deobfuscate(zone_, deobfuscation_trie_, name.ToCString()); |
| 329 | |
| 330 | stream->RegisterAbstractOrigin(i); |
| 331 | stream->uleb128(kAbstractFunction); |
| 332 | stream->string(name_cstr); // DW_AT_name |
| 333 | stream->uleb128(file); // DW_AT_decl_file |
| 334 | stream->uleb128(DW_INL_inlined); // DW_AT_inline |
| 335 | stream->uleb128(0); // End of children. |
| 336 | } |
| 337 | } |
| 338 | |
| 339 | void Dwarf::WriteConcreteFunctions(DwarfWriteStream* stream) { |
| 340 | Function& function = Function::Handle(zone_); |
| 341 | Script& script = Script::Handle(zone_); |
| 342 | for (intptr_t i = 0; i < codes_.length(); i++) { |
| 343 | const Code& code = *(codes_[i]); |
| 344 | RELEASE_ASSERT(!code.IsNull()); |
| 345 | if (!code.IsFunctionCode() || code.IsUnknownDartCode()) { |
| 346 | continue; |
| 347 | } |
| 348 | |
| 349 | function = code.function(); |
| 350 | intptr_t function_index = LookupFunction(function); |
| 351 | script = function.script(); |
| 352 | intptr_t label = code_to_label_.LookupValue(&code); |
| 353 | ASSERT(label > 0); |
| 354 | |
| 355 | stream->uleb128(kConcreteFunction); |
| 356 | // DW_AT_abstract_origin |
| 357 | // References a node written above in WriteAbstractFunctions. |
| 358 | stream->AbstractOrigin(function_index); |
| 359 | |
| 360 | // DW_AT_low_pc |
| 361 | stream->OffsetFromSymbol(label, 0); |
| 362 | // DW_AT_high_pc |
| 363 | stream->OffsetFromSymbol(label, code.Size()); |
| 364 | // DW_AT_artificial |
| 365 | stream->u1(function.is_visible() ? 0 : 1); |
| 366 | |
| 367 | InliningNode* node = ExpandInliningTree(code); |
| 368 | if (node != nullptr) { |
| 369 | for (InliningNode* child = node->children_head; child != nullptr; |
| 370 | child = child->children_next) { |
| 371 | WriteInliningNode(stream, child, label, script); |
| 372 | } |
| 373 | } |
| 374 | |
| 375 | stream->uleb128(0); // End of children. |
| 376 | } |
| 377 | } |
| 378 | |
| 379 | // Our state machine encodes position metadata such that we don't know the |
| 380 | // end pc for an inlined function until it is popped, but DWARF DIEs encode |
| 381 | // it where the function is pushed. We expand the state transitions into |
| 382 | // an in-memory tree to do the conversion. |
| 383 | InliningNode* Dwarf::ExpandInliningTree(const Code& code) { |
| 384 | const CodeSourceMap& map = |
| 385 | CodeSourceMap::Handle(zone_, code.code_source_map()); |
| 386 | if (map.IsNull()) { |
| 387 | return nullptr; |
| 388 | } |
| 389 | const Array& functions = Array::Handle(zone_, code.inlined_id_to_function()); |
| 390 | const Function& root_function = Function::ZoneHandle(zone_, code.function()); |
| 391 | if (root_function.IsNull()) { |
| 392 | FATAL("Wherefore art thou functionless code, %s?\n" , code.ToCString()); |
| 393 | } |
| 394 | |
| 395 | GrowableArray<InliningNode*> node_stack(zone_, 4); |
| 396 | GrowableArray<DwarfPosition> token_positions(zone_, 4); |
| 397 | |
| 398 | NoSafepointScope no_safepoint; |
| 399 | ReadStream stream(map.Data(), map.Length()); |
| 400 | |
| 401 | int32_t current_pc_offset = 0; |
| 402 | token_positions.Add(kNoDwarfPositionInfo); |
| 403 | InliningNode* root_node = |
| 404 | new (zone_) InliningNode(root_function, token_positions.Last(), 0); |
| 405 | root_node->end_pc_offset = code.Size(); |
| 406 | node_stack.Add(root_node); |
| 407 | |
| 408 | while (stream.PendingBytes() > 0) { |
| 409 | int32_t arg1; |
| 410 | int32_t arg2 = -1; |
| 411 | const uint8_t opcode = CodeSourceMapOps::Read(&stream, &arg1, &arg2); |
| 412 | switch (opcode) { |
| 413 | case CodeSourceMapOps::kChangePosition: { |
| 414 | DwarfPosition& pos = token_positions[token_positions.length() - 1]; |
| 415 | pos.ChangePosition(arg1, arg2); |
| 416 | break; |
| 417 | } |
| 418 | case CodeSourceMapOps::kAdvancePC: { |
| 419 | current_pc_offset += arg1; |
| 420 | if (arg1 == 0) { |
| 421 | // This happens at the start of the function where we emit a special |
| 422 | // kAdvancePC 0 instruction to record information about the function |
| 423 | // itself. We need to advance current_pc_offset a bit to prevent |
| 424 | // starting inlining interval directy at the start of the function |
| 425 | // itself. |
| 426 | current_pc_offset += 1; |
| 427 | } |
| 428 | break; |
| 429 | } |
| 430 | case CodeSourceMapOps::kPushFunction: { |
| 431 | const Function& child_func = |
| 432 | Function::ZoneHandle(zone_, Function::RawCast(functions.At(arg1))); |
| 433 | InliningNode* child_node = new (zone_) |
| 434 | InliningNode(child_func, token_positions.Last(), current_pc_offset); |
| 435 | node_stack.Last()->AppendChild(child_node); |
| 436 | node_stack.Add(child_node); |
| 437 | token_positions.Add(kNoDwarfPositionInfo); |
| 438 | break; |
| 439 | } |
| 440 | case CodeSourceMapOps::kPopFunction: { |
| 441 | // We never pop the root function. |
| 442 | ASSERT(node_stack.length() > 1); |
| 443 | ASSERT(token_positions.length() > 1); |
| 444 | node_stack.Last()->end_pc_offset = current_pc_offset; |
| 445 | node_stack.RemoveLast(); |
| 446 | token_positions.RemoveLast(); |
| 447 | break; |
| 448 | } |
| 449 | case CodeSourceMapOps::kNullCheck: { |
| 450 | break; |
| 451 | } |
| 452 | default: |
| 453 | UNREACHABLE(); |
| 454 | } |
| 455 | } |
| 456 | |
| 457 | while (node_stack.length() > 1) { |
| 458 | node_stack.Last()->end_pc_offset = current_pc_offset; |
| 459 | node_stack.RemoveLast(); |
| 460 | token_positions.RemoveLast(); |
| 461 | } |
| 462 | ASSERT(node_stack[0] == root_node); |
| 463 | return root_node; |
| 464 | } |
| 465 | |
| 466 | void Dwarf::WriteInliningNode(DwarfWriteStream* stream, |
| 467 | InliningNode* node, |
| 468 | intptr_t root_label, |
| 469 | const Script& parent_script) { |
| 470 | ASSERT(root_label > 0); |
| 471 | intptr_t file = LookupScript(parent_script); |
| 472 | intptr_t function_index = LookupFunction(node->function); |
| 473 | const Script& script = Script::Handle(zone_, node->function.script()); |
| 474 | |
| 475 | stream->uleb128(kInlinedFunction); |
| 476 | // DW_AT_abstract_origin |
| 477 | // References a node written above in WriteAbstractFunctions. |
| 478 | stream->AbstractOrigin(function_index); |
| 479 | |
| 480 | // DW_AT_low_pc |
| 481 | stream->OffsetFromSymbol(root_label, node->start_pc_offset); |
| 482 | // DW_AT_high_pc |
| 483 | stream->OffsetFromSymbol(root_label, node->end_pc_offset); |
| 484 | // DW_AT_call_file |
| 485 | stream->uleb128(file); |
| 486 | |
| 487 | // DW_AT_call_line |
| 488 | stream->uleb128(node->position.line()); |
| 489 | // DW_AT_call_column |
| 490 | stream->uleb128(node->position.column()); |
| 491 | // DW_AT_artificial |
| 492 | stream->u1(node->function.is_visible() ? 0 : 1); |
| 493 | |
| 494 | for (InliningNode* child = node->children_head; child != nullptr; |
| 495 | child = child->children_next) { |
| 496 | WriteInliningNode(stream, child, root_label, script); |
| 497 | } |
| 498 | |
| 499 | stream->uleb128(0); // End of children. |
| 500 | } |
| 501 | |
| 502 | // Helper class for tracking state of DWARF registers and emitting |
| 503 | // line number program commands to set these registers to the right |
| 504 | // state. |
| 505 | class LineNumberProgramWriter { |
| 506 | public: |
| 507 | explicit LineNumberProgramWriter(DwarfWriteStream* stream) |
| 508 | : stream_(stream) {} |
| 509 | |
| 510 | void EmitRow(intptr_t file, |
| 511 | intptr_t line, |
| 512 | intptr_t column, |
| 513 | intptr_t label, |
| 514 | intptr_t pc_offset) { |
| 515 | if (AddRow(file, line, column, label, pc_offset)) { |
| 516 | // Address register must be updated from 0 before emitting an LNP row |
| 517 | // (dartbug.com/41756). |
| 518 | stream_->u1(Dwarf::DW_LNS_copy); |
| 519 | } |
| 520 | } |
| 521 | |
| 522 | // Associates the given file, line, and column information for the instruction |
| 523 | // at the pc_offset into the instructions payload of the Code object with the |
| 524 | // symbol asm_name. Returns whether any changes were made to the stream. |
| 525 | DART_WARN_UNUSED_RESULT bool AddRow(intptr_t file, |
| 526 | intptr_t line, |
| 527 | intptr_t column, |
| 528 | intptr_t label, |
| 529 | intptr_t pc_offset) { |
| 530 | ASSERT_EQUAL(end_sequence_, false); |
| 531 | bool source_info_changed = false; |
| 532 | // Note that files are 1-indexed. |
| 533 | ASSERT(file >= 1); |
| 534 | if (file != file_) { |
| 535 | stream_->u1(Dwarf::DW_LNS_set_file); |
| 536 | stream_->uleb128(file); |
| 537 | file_ = file; |
| 538 | source_info_changed = true; |
| 539 | } |
| 540 | ASSERT(line >= DwarfPosition::kNoLine); |
| 541 | if (line != line_) { |
| 542 | stream_->u1(Dwarf::DW_LNS_advance_line); |
| 543 | stream_->sleb128(line - line_); |
| 544 | line_ = line; |
| 545 | source_info_changed = true; |
| 546 | } |
| 547 | ASSERT(column >= DwarfPosition::kNoColumn); |
| 548 | if (column != column_) { |
| 549 | stream_->u1(Dwarf::DW_LNS_set_column); |
| 550 | stream_->uleb128(column); |
| 551 | column_ = column; |
| 552 | source_info_changed = true; |
| 553 | } |
| 554 | // If the file, line, and column information match that for the previous |
| 555 | // AddRow call, no change is made to the stream. This is because all |
| 556 | // addresses between two line number program rows inherit the source |
| 557 | // information from the first. |
| 558 | if (source_info_changed) { |
| 559 | SetCurrentPosition(label, pc_offset); |
| 560 | } |
| 561 | return source_info_changed; |
| 562 | } |
| 563 | |
| 564 | void MarkEnd() { |
| 565 | ASSERT_EQUAL(end_sequence_, false); |
| 566 | // End of contiguous machine code. |
| 567 | stream_->u1(0); // This is an extended opcode |
| 568 | stream_->u1(1); // that is 1 byte long |
| 569 | stream_->u1(Dwarf::DW_LNE_end_sequence); |
| 570 | end_sequence_ = true; |
| 571 | } |
| 572 | |
| 573 | void MarkEnd(intptr_t label, intptr_t pc_offset) { |
| 574 | ASSERT_EQUAL(end_sequence_, false); |
| 575 | SetCurrentPosition(label, pc_offset); |
| 576 | MarkEnd(); |
| 577 | } |
| 578 | |
| 579 | private: |
| 580 | void SetCurrentPosition(intptr_t label, intptr_t pc_offset) { |
| 581 | // Each LNP row is either in a different function from the previous row |
| 582 | // or is at an increasing PC offset into the same function. |
| 583 | ASSERT(label > 0); |
| 584 | ASSERT(pc_offset >= 0); |
| 585 | ASSERT(label_ != label || pc_offset > pc_offset_); |
| 586 | if (label_ != label) { |
| 587 | // Set the address register to the given offset into the new code payload. |
| 588 | auto const instr_size = 1 + compiler::target::kWordSize; |
| 589 | stream_->u1(0); // This is an extended opcode |
| 590 | stream_->u1(instr_size); // that is 5 or 9 bytes long |
| 591 | stream_->u1(Dwarf::DW_LNE_set_address); |
| 592 | stream_->OffsetFromSymbol(label, pc_offset); |
| 593 | } else { |
| 594 | // Change the address register by the difference in the two offsets. |
| 595 | stream_->u1(Dwarf::DW_LNS_advance_pc); |
| 596 | stream_->uleb128(pc_offset - pc_offset_); |
| 597 | } |
| 598 | label_ = label; |
| 599 | pc_offset_ = pc_offset; |
| 600 | } |
| 601 | |
| 602 | DwarfWriteStream* const stream_; |
| 603 | // The initial values for the line number program state machine registers |
| 604 | // according to the DWARF standard. |
| 605 | intptr_t pc_offset_ = 0; |
| 606 | intptr_t file_ = 1; |
| 607 | intptr_t line_ = 1; |
| 608 | intptr_t column_ = 0; |
| 609 | bool end_sequence_ = false; |
| 610 | |
| 611 | // Other info not stored in the state machine registers. |
| 612 | intptr_t label_ = 0; |
| 613 | }; |
| 614 | |
| 615 | void Dwarf::WriteSyntheticLineNumberProgram(LineNumberProgramWriter* writer) { |
| 616 | // We emit it last after all other scripts. |
| 617 | const intptr_t comments_file_index = scripts_.length() + 1; |
| 618 | |
| 619 | auto file_open = Dart::file_open_callback(); |
| 620 | auto file_write = Dart::file_write_callback(); |
| 621 | auto file_close = Dart::file_close_callback(); |
| 622 | if ((file_open == nullptr) || (file_write == nullptr) || |
| 623 | (file_close == nullptr)) { |
| 624 | OS::PrintErr("warning: Could not access file callbacks." ); |
| 625 | return; |
| 626 | } |
| 627 | |
| 628 | TextBuffer comments_buffer(128 * KB); |
| 629 | |
| 630 | const char* filename = FLAG_write_code_comments_as_synthetic_source_to; |
| 631 | void* comments_file = file_open(filename, /*write=*/true); |
| 632 | if (comments_file == nullptr) { |
| 633 | OS::PrintErr("warning: Failed to write code comments source: %s\n" , |
| 634 | filename); |
| 635 | return; |
| 636 | } |
| 637 | |
| 638 | // Lines in DWARF are 1-indexed. |
| 639 | intptr_t current_line = 1; |
| 640 | |
| 641 | for (intptr_t i = 0; i < codes_.length(); i++) { |
| 642 | const Code& code = *(codes_[i]); |
| 643 | auto const label = code_to_label_.LookupValue(&code); |
| 644 | ASSERT(label > 0); |
| 645 | |
| 646 | auto& comments = code.comments(); |
| 647 | for (intptr_t i = 0, len = comments.Length(); i < len;) { |
| 648 | intptr_t current_pc_offset = comments.PCOffsetAt(i); |
| 649 | writer->EmitRow(comments_file_index, current_line, |
| 650 | DwarfPosition::kNoColumn, label, current_pc_offset); |
| 651 | while (i < len && current_pc_offset == comments.PCOffsetAt(i)) { |
| 652 | comments_buffer.AddString(comments.CommentAt(i)); |
| 653 | comments_buffer.AddChar('\n'); |
| 654 | current_line++; |
| 655 | i++; |
| 656 | } |
| 657 | } |
| 658 | } |
| 659 | |
| 660 | file_write(comments_buffer.buffer(), comments_buffer.length(), comments_file); |
| 661 | file_close(comments_file); |
| 662 | } |
| 663 | |
| 664 | void Dwarf::WriteLineNumberProgramFromCodeSourceMaps( |
| 665 | LineNumberProgramWriter* writer) { |
| 666 | Function& root_function = Function::Handle(zone_); |
| 667 | Script& script = Script::Handle(zone_); |
| 668 | CodeSourceMap& map = CodeSourceMap::Handle(zone_); |
| 669 | Array& functions = Array::Handle(zone_); |
| 670 | GrowableArray<const Function*> function_stack(zone_, 8); |
| 671 | GrowableArray<DwarfPosition> token_positions(zone_, 8); |
| 672 | |
| 673 | for (intptr_t i = 0; i < codes_.length(); i++) { |
| 674 | const Code& code = *(codes_[i]); |
| 675 | auto const label = code_to_label_.LookupValue(&code); |
| 676 | ASSERT(label > 0); |
| 677 | |
| 678 | map = code.code_source_map(); |
| 679 | if (map.IsNull()) { |
| 680 | continue; |
| 681 | } |
| 682 | root_function = code.function(); |
| 683 | functions = code.inlined_id_to_function(); |
| 684 | |
| 685 | NoSafepointScope no_safepoint; |
| 686 | ReadStream code_map_stream(map.Data(), map.Length()); |
| 687 | |
| 688 | function_stack.Clear(); |
| 689 | token_positions.Clear(); |
| 690 | |
| 691 | // CodeSourceMap might start in the following way: |
| 692 | // |
| 693 | // ChangePosition function.token_pos() |
| 694 | // AdvancePC 0 |
| 695 | // ChangePosition x |
| 696 | // AdvancePC y |
| 697 | // |
| 698 | // This entry is emitted to ensure correct symbolization of |
| 699 | // function listener frames produced by async unwinding. |
| 700 | // (See EmitFunctionEntrySourcePositionDescriptorIfNeeded). |
| 701 | // Directly interpreting this sequence would cause us to emit |
| 702 | // multiple with the same pc into line number table and different |
| 703 | // position information. To avoid this will make an adjustment for |
| 704 | // the second record we emit: if position x is a synthetic one we will |
| 705 | // simply drop the second record, if position x is real then we will |
| 706 | // emit row with a slightly adjusted PC (by 1 byte). This would not |
| 707 | // affect symbolization (you can't have a call that is 1 byte long) |
| 708 | // but will avoid line number table entries with the same PC. |
| 709 | bool function_entry_position_was_emitted = false; |
| 710 | |
| 711 | int32_t current_pc_offset = 0; |
| 712 | function_stack.Add(&root_function); |
| 713 | token_positions.Add(kNoDwarfPositionInfo); |
| 714 | |
| 715 | while (code_map_stream.PendingBytes() > 0) { |
| 716 | int32_t arg1; |
| 717 | int32_t arg2 = -1; |
| 718 | const uint8_t opcode = |
| 719 | CodeSourceMapOps::Read(&code_map_stream, &arg1, &arg2); |
| 720 | switch (opcode) { |
| 721 | case CodeSourceMapOps::kChangePosition: { |
| 722 | DwarfPosition& pos = token_positions[token_positions.length() - 1]; |
| 723 | pos.ChangePosition(arg1, arg2); |
| 724 | break; |
| 725 | } |
| 726 | case CodeSourceMapOps::kAdvancePC: { |
| 727 | // Emit a row for the previous PC value if the source location |
| 728 | // changed since the last row was emitted. |
| 729 | const Function& function = *(function_stack.Last()); |
| 730 | script = function.script(); |
| 731 | const intptr_t file = LookupScript(script); |
| 732 | const intptr_t line = token_positions.Last().line(); |
| 733 | const intptr_t column = token_positions.Last().column(); |
| 734 | intptr_t pc_offset_adjustment = 0; |
| 735 | bool should_emit = true; |
| 736 | |
| 737 | // If we are at the function entry and have already emitted a row |
| 738 | // then adjust current_pc_offset to avoid duplicated entries. |
| 739 | // See the comment below which explains why this code is here. |
| 740 | if (current_pc_offset == 0 && function_entry_position_was_emitted) { |
| 741 | pc_offset_adjustment = 1; |
| 742 | // Ignore synthetic positions. Function entry position gives |
| 743 | // more information anyway. |
| 744 | should_emit = !(line == 0 && column == 0); |
| 745 | } |
| 746 | |
| 747 | if (should_emit) { |
| 748 | writer->EmitRow(file, line, column, label, |
| 749 | current_pc_offset + pc_offset_adjustment); |
| 750 | } |
| 751 | |
| 752 | current_pc_offset += arg1; |
| 753 | if (arg1 == 0) { // Special case of AdvancePC 0. |
| 754 | ASSERT(current_pc_offset == 0); |
| 755 | ASSERT(!function_entry_position_was_emitted); |
| 756 | function_entry_position_was_emitted = true; |
| 757 | } |
| 758 | break; |
| 759 | } |
| 760 | case CodeSourceMapOps::kPushFunction: { |
| 761 | auto child_func = |
| 762 | &Function::Handle(zone_, Function::RawCast(functions.At(arg1))); |
| 763 | function_stack.Add(child_func); |
| 764 | token_positions.Add(kNoDwarfPositionInfo); |
| 765 | break; |
| 766 | } |
| 767 | case CodeSourceMapOps::kPopFunction: { |
| 768 | // We never pop the root function. |
| 769 | ASSERT(function_stack.length() > 1); |
| 770 | ASSERT(token_positions.length() > 1); |
| 771 | function_stack.RemoveLast(); |
| 772 | token_positions.RemoveLast(); |
| 773 | break; |
| 774 | } |
| 775 | case CodeSourceMapOps::kNullCheck: { |
| 776 | break; |
| 777 | } |
| 778 | default: |
| 779 | UNREACHABLE(); |
| 780 | } |
| 781 | } |
| 782 | } |
| 783 | } |
| 784 | |
| 785 | static constexpr char kResolvedFileRoot[] = "file:///" ; |
| 786 | static constexpr intptr_t kResolvedFileRootLen = sizeof(kResolvedFileRoot) - 1; |
| 787 | static constexpr char kResolvedFlutterRoot[] = "org-dartlang-sdk:///flutter/" ; |
| 788 | static constexpr intptr_t kResolvedFlutterRootLen = |
| 789 | sizeof(kResolvedFlutterRoot) - 1; |
| 790 | static constexpr char kResolvedSdkRoot[] = "org-dartlang-sdk:///" ; |
| 791 | static constexpr intptr_t kResolvedSdkRootLen = sizeof(kResolvedSdkRoot) - 1; |
| 792 | static constexpr char kResolvedGoogle3Root[] = "google3:///" ; |
| 793 | static constexpr intptr_t kResolvedGoogle3RootLen = |
| 794 | sizeof(kResolvedGoogle3Root) - 1; |
| 795 | |
| 796 | static const char* ConvertResolvedURI(const char* str) { |
| 797 | const intptr_t len = strlen(str); |
| 798 | if (len > kResolvedFileRootLen && |
| 799 | strncmp(str, kResolvedFileRoot, kResolvedFileRootLen) == 0) { |
| 800 | #if defined(DART_HOST_OS_WINDOWS) |
| 801 | return str + kResolvedFileRootLen; // Strip off the entire prefix. |
| 802 | #else |
| 803 | return str + kResolvedFileRootLen - 1; // Leave a '/' on the front. |
| 804 | #endif |
| 805 | } |
| 806 | // Must do kResolvedFlutterRoot before kResolvedSdkRoot, since the latter is |
| 807 | // a prefix of the former. |
| 808 | if (len > kResolvedFlutterRootLen && |
| 809 | strncmp(str, kResolvedFlutterRoot, kResolvedFlutterRootLen) == 0) { |
| 810 | return str + kResolvedFlutterRootLen; // Strip off the entire prefix. |
| 811 | } |
| 812 | if (len > kResolvedSdkRootLen && |
| 813 | strncmp(str, kResolvedSdkRoot, kResolvedSdkRootLen) == 0) { |
| 814 | return str + kResolvedSdkRootLen; // Strip off the entire prefix. |
| 815 | } |
| 816 | if (len > kResolvedGoogle3RootLen && |
| 817 | strncmp(str, kResolvedGoogle3Root, kResolvedGoogle3RootLen) == 0) { |
| 818 | return str + kResolvedGoogle3RootLen; // Strip off the entire prefix. |
| 819 | } |
| 820 | return nullptr; |
| 821 | } |
| 822 | |
| 823 | void Dwarf::WriteLineNumberProgram(DwarfWriteStream* stream) { |
| 824 | // 6.2.4 The Line Number Program Header |
| 825 | |
| 826 | // 1. unit_length. This encoding implies 32-bit DWARF. |
| 827 | stream->WritePrefixedLength("line" , [&]() { |
| 828 | stream->u2(2); // 2. DWARF version 2 |
| 829 | |
| 830 | // 3. header_length |
| 831 | stream->WritePrefixedLength("lineheader" , [&]() { |
| 832 | stream->u1(1); // 4. minimum_instruction_length |
| 833 | stream->u1(1); // 5. default_is_stmt (true for dsymutil compatibility). |
| 834 | stream->u1(0); // 6. line_base |
| 835 | stream->u1(1); // 7. line_range |
| 836 | stream->u1(13); // 8. opcode_base (12 standard opcodes in Dwarf 2) |
| 837 | |
| 838 | // 9. standard_opcode_lengths |
| 839 | stream->u1(0); // DW_LNS_copy, 0 operands |
| 840 | stream->u1(1); // DW_LNS_advance_pc, 1 operands |
| 841 | stream->u1(1); // DW_LNS_advance_list, 1 operands |
| 842 | stream->u1(1); // DW_LNS_set_file, 1 operands |
| 843 | stream->u1(1); // DW_LNS_set_column, 1 operands |
| 844 | stream->u1(0); // DW_LNS_negate_stmt, 0 operands |
| 845 | stream->u1(0); // DW_LNS_set_basic_block, 0 operands |
| 846 | stream->u1(0); // DW_LNS_const_add_pc, 0 operands |
| 847 | stream->u1(1); // DW_LNS_fixed_advance_pc, 1 operands |
| 848 | stream->u1(0); // DW_LNS_set_prolog_end, 0 operands |
| 849 | stream->u1(0); // DW_LNS_set_epilogue_begin, 0 operands |
| 850 | stream->u1(1); // DW_LNS_set_isa, 1 operands |
| 851 | |
| 852 | // 10. include_directories (sequence of path names) |
| 853 | // We don't emit any because we use full paths below. |
| 854 | stream->u1(0); |
| 855 | |
| 856 | // 11. file_names (sequence of file entries) |
| 857 | String& uri = String::Handle(zone_); |
| 858 | for (intptr_t i = 0; i < scripts_.length(); i++) { |
| 859 | const Script& script = *(scripts_[i]); |
| 860 | const char* uri_cstr = nullptr; |
| 861 | if (FLAG_resolve_dwarf_paths) { |
| 862 | uri = script.resolved_url(); |
| 863 | // Strictly enforce this to catch unresolvable cases. |
| 864 | if (uri.IsNull()) { |
| 865 | FATAL("no resolved URI for Script %s available" , |
| 866 | script.ToCString()); |
| 867 | } |
| 868 | // resolved_url is never obfuscated, so just convert the prefix. |
| 869 | auto const orig_cstr = uri.ToCString(); |
| 870 | auto const converted_cstr = ConvertResolvedURI(orig_cstr); |
| 871 | // Strictly enforce this to catch inconvertible cases. |
| 872 | if (converted_cstr == nullptr) { |
| 873 | FATAL("cannot convert resolved URI %s" , orig_cstr); |
| 874 | } |
| 875 | uri_cstr = converted_cstr; |
| 876 | } else { |
| 877 | uri = script.url(); |
| 878 | ASSERT(!uri.IsNull()); |
| 879 | uri_cstr = ImageWriter::Deobfuscate(zone_, deobfuscation_trie_, |
| 880 | uri.ToCString()); |
| 881 | } |
| 882 | RELEASE_ASSERT(strlen(uri_cstr) != 0); |
| 883 | |
| 884 | stream->string(uri_cstr); // NOLINT |
| 885 | stream->uleb128(0); // Include directory index. |
| 886 | stream->uleb128(0); // File modification time. |
| 887 | stream->uleb128(0); // File length. |
| 888 | } |
| 889 | if (FLAG_write_code_comments_as_synthetic_source_to != nullptr) { |
| 890 | stream->string( // NOLINT |
| 891 | FLAG_write_code_comments_as_synthetic_source_to); |
| 892 | stream->uleb128(0); // Include directory index. |
| 893 | stream->uleb128(0); // File modification time. |
| 894 | stream->uleb128(0); // File length. |
| 895 | } |
| 896 | stream->u1(0); // End of file names. |
| 897 | }); |
| 898 | |
| 899 | // 6.2.5 The Line Number Program |
| 900 | LineNumberProgramWriter lnp_writer(stream); |
| 901 | if (FLAG_write_code_comments_as_synthetic_source_to != nullptr) { |
| 902 | WriteSyntheticLineNumberProgram(&lnp_writer); |
| 903 | } else { |
| 904 | WriteLineNumberProgramFromCodeSourceMaps(&lnp_writer); |
| 905 | } |
| 906 | |
| 907 | // Advance pc to end of the compilation unit if not already there. |
| 908 | if (codes_.length() != 0) { |
| 909 | const intptr_t last_code_index = codes_.length() - 1; |
| 910 | const Code& last_code = *(codes_[last_code_index]); |
| 911 | const intptr_t last_pc_offset = last_code.Size(); |
| 912 | auto const last_label = code_to_label_.LookupValue(&last_code); |
| 913 | ASSERT(last_label > 0); |
| 914 | lnp_writer.MarkEnd(last_label, last_pc_offset); |
| 915 | } else { |
| 916 | lnp_writer.MarkEnd(); |
| 917 | } |
| 918 | }); |
| 919 | } |
| 920 | |
| 921 | #endif // DART_PRECOMPILER |
| 922 | |
| 923 | } // namespace dart |
| 924 | |