blob: a24e7686dc821181a3a2a98a3f11cecbc0fe458b [file] [edit]
// 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