| // Copyright (c) 2026, 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/debug_info.h" |
| |
| #if defined(DART_PRECOMPILER) |
| |
| #include "vm/code_descriptors.h" |
| #include "vm/datastream.h" |
| #include "vm/flags.h" |
| #include "vm/image_snapshot.h" |
| #include "vm/object.h" |
| #include "vm/os.h" |
| |
| namespace dart { |
| |
| DECLARE_FLAG(bool, resolve_dwarf_paths); |
| |
| namespace { |
| |
| 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; |
| |
| } // namespace |
| |
| const char* DebugInfo::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; |
| #else |
| return str + kResolvedFileRootLen - 1; |
| #endif |
| } |
| if (len > kResolvedFlutterRootLen && |
| strncmp(str, kResolvedFlutterRoot, kResolvedFlutterRootLen) == 0) { |
| return str + kResolvedFlutterRootLen; |
| } |
| if (len > kResolvedSdkRootLen && |
| strncmp(str, kResolvedSdkRoot, kResolvedSdkRootLen) == 0) { |
| return str + kResolvedSdkRootLen; |
| } |
| if (len > kResolvedGoogle3RootLen && |
| strncmp(str, kResolvedGoogle3Root, kResolvedGoogle3RootLen) == 0) { |
| return str + kResolvedGoogle3RootLen; |
| } |
| return nullptr; |
| } |
| |
| const char* DebugInfo::ResolveScriptUri( |
| Zone* zone, |
| const Script& script, |
| const Trie<const char>* deobfuscation_trie) { |
| String& uri = String::Handle(zone, String::null()); |
| if (FLAG_resolve_dwarf_paths) { |
| uri = script.resolved_url(); |
| if (uri.IsNull()) { |
| FATAL("no resolved URI for Script %s available", script.ToCString()); |
| } |
| const char* raw = uri.ToCString(); |
| const char* converted = ConvertResolvedURI(raw); |
| if (converted == nullptr) { |
| FATAL("cannot convert resolved URI %s", raw); |
| } |
| return OS::SCreate(zone, "%s", converted); |
| } |
| |
| uri = script.url(); |
| ASSERT(!uri.IsNull()); |
| const char* deobfuscated = |
| ImageWriter::Deobfuscate(zone, deobfuscation_trie, uri.ToCString()); |
| RELEASE_ASSERT(deobfuscated != nullptr && deobfuscated[0] != '\0'); |
| return OS::SCreate(zone, "%s", deobfuscated); |
| } |
| |
| void DebugInfo::WriteLineNumberProgramForCode( |
| Zone* zone, |
| const Code& code, |
| DebugInfoLineNumberProgramWriter* writer) { |
| const intptr_t label = writer->LookupCodeLabel(code); |
| ASSERT(label > 0); |
| |
| CodeSourceMap& map = CodeSourceMap::Handle(zone, code.code_source_map()); |
| if (map.IsNull()) { |
| return; |
| } |
| |
| Function& root_function = Function::Handle(zone, code.function()); |
| Script& script = Script::Handle(zone); |
| Array& functions = Array::Handle(zone, code.inlined_id_to_function()); |
| auto& function_stack = writer->function_stack_; |
| auto& token_positions = writer->token_positions_; |
| |
| NoSafepointScope no_safepoint; |
| ReadStream code_map_stream(map.Data(), map.Length()); |
| |
| // 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 rows |
| // with the same pc into line number table and different position information. |
| // To avoid this, if the function-entry position was actually emitted, the |
| // second row at PC 0 is either dropped when synthetic or moved to PC 1 when |
| // real. 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.Clear(); |
| token_positions.Clear(); |
| function_stack.Add(&root_function); |
| token_positions.Add(DebugInfoPosition()); |
| |
| 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: { |
| DebugInfoPosition& 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 = writer->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 |
| // corresponding to the function entry (marked by AdvancePC 0). Then |
| // adjust current_pc_offset to avoid duplicated entries. See the |
| // comment above 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(DebugInfoPosition()); |
| 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(); |
| } |
| } |
| } |
| |
| void DebugInfo::WriteLineNumberProgramFromCodeSourceMaps( |
| Zone* zone, |
| const GrowableArray<const Code*>& codes, |
| DebugInfoLineNumberProgramWriter* writer) { |
| for (intptr_t i = 0; i < codes.length(); i++) { |
| const Code& code = *(codes[i]); |
| WriteLineNumberProgramForCode(zone, code, writer); |
| } |
| } |
| |
| } // namespace dart |
| |
| #endif // DART_PRECOMPILER |