mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 12:24:24 +00:00
c1afa7f85e
Before, we only stripped `org-dartlang-sdk:///` as a prefix when it was followed by `sdk/...`, which happens for code compiled with the Dart SDK. However, the same prefix can show up in Flutter code (e.g., `org-dartlang-sdk:///third_party/dart/sdk/...`). Thus, except for one case, just strip the prefix at all times when `--resolve-dwarf-paths` is set, leaving a relative path. The one case is `org-dartlang-sdk:///flutter/`, which appears in the resolved version of `dart:ui` (`org-dartlang-sdk:///flutter/lib/ui`), where the `flutter` isn't part of the actual filesystem path. In this case, we strip off the `flutter/` as well. TEST=vm/dart{,_2}/use_resolve_dwarf_paths_flag Bug: https://github.com/dart-lang/sdk/issues/44325 Change-Id: Ia9abca877e41657089a438d4723ff08a2e16fe69 Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-dwarf-linux-product-x64-try,vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try,vm-kernel-precomp-nnbd-win-release-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/268762 Reviewed-by: Daco Harkes <dacoharkes@google.com> Commit-Queue: Tess Strickland <sstrickl@google.com>
994 lines
35 KiB
C++
994 lines
35 KiB
C++
// 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/elf.h"
|
|
#include "vm/image_snapshot.h"
|
|
#include "vm/object_store.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(NULL),
|
|
children_tail(NULL),
|
|
children_next(NULL) {
|
|
RELEASE_ASSERT(!function.IsNull());
|
|
RELEASE_ASSERT(function.IsNotTemporaryScopedHandle());
|
|
}
|
|
|
|
void AppendChild(InliningNode* child) {
|
|
if (children_tail == NULL) {
|
|
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;
|
|
};
|
|
|
|
template <typename T>
|
|
Trie<T>* Trie<T>::AddString(Zone* zone,
|
|
Trie<T>* trie,
|
|
const char* key,
|
|
const T* value) {
|
|
ASSERT(key != nullptr);
|
|
if (trie == nullptr) {
|
|
trie = new (zone) Trie<T>();
|
|
}
|
|
if (*key == '\0') {
|
|
ASSERT(trie->value_ == nullptr);
|
|
trie->value_ = value;
|
|
} else {
|
|
auto const index = ChildIndex(*key);
|
|
ASSERT(index >= 0 && index < kNumValidChars);
|
|
trie->children_[index] =
|
|
AddString(zone, trie->children_[index], key + 1, value);
|
|
}
|
|
|
|
return trie;
|
|
}
|
|
|
|
template <typename T>
|
|
const T* Trie<T>::Lookup(const Trie<T>* trie, const char* key, intptr_t* end) {
|
|
intptr_t i = 0;
|
|
for (; key[i] != '\0'; i++) {
|
|
auto const index = ChildIndex(key[i]);
|
|
ASSERT(index < kNumValidChars);
|
|
if (index < 0) {
|
|
if (end == nullptr) return nullptr;
|
|
break;
|
|
}
|
|
// Still find the longest valid trie prefix when no stored value.
|
|
if (trie == nullptr) continue;
|
|
trie = trie->children_[index];
|
|
}
|
|
if (end != nullptr) {
|
|
*end = i;
|
|
}
|
|
if (trie == nullptr) return nullptr;
|
|
return trie->value_;
|
|
}
|
|
|
|
Dwarf::Dwarf(Zone* zone)
|
|
: zone_(zone),
|
|
reverse_obfuscation_trie_(CreateReverseObfuscationTrie(zone)),
|
|
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 != NULL) {
|
|
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 != NULL) {
|
|
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 == NULL) {
|
|
FATAL1("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 == NULL) {
|
|
FATAL1("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_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(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);
|
|
const Library& root_library = Library::Handle(
|
|
zone_, IsolateGroup::Current()->object_store()->root_library());
|
|
const String& root_uri = String::Handle(zone_, root_library.url());
|
|
stream->string(root_uri.ToCString()); // DW_AT_name
|
|
stream->string("Dart VM"); // 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);
|
|
auto const name_cstr = Deobfuscate(name.ToCString());
|
|
|
|
stream->RegisterAbstractOrigin(i);
|
|
stream->uleb128(kAbstractFunction);
|
|
stream->string(name_cstr); // DW_AT_name
|
|
stream->uleb128(file); // DW_AT_decl_file
|
|
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 != NULL) {
|
|
for (InliningNode* child = node->children_head; child != NULL;
|
|
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 NULL;
|
|
}
|
|
const Array& functions = Array::Handle(zone_, code.inlined_id_to_function());
|
|
const Function& root_function = Function::ZoneHandle(zone_, code.function());
|
|
if (root_function.IsNull()) {
|
|
FATAL1("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;
|
|
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());
|
|
|
|
for (InliningNode* child = node->children_head; child != NULL;
|
|
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;
|
|
intptr_t 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);
|
|
writer->EmitRow(comments_file_index, current_line,
|
|
DwarfPosition::kNoColumn, label, current_pc_offset);
|
|
while (i < len && current_pc_offset == comments.PCOffsetAt(i)) {
|
|
comments_buffer.AddString(comments.CommentAt(i));
|
|
comments_buffer.AddChar('\n');
|
|
current_line++;
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
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_epligoue_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]);
|
|
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());
|
|
}
|
|
} else {
|
|
uri = script.url();
|
|
}
|
|
ASSERT(!uri.IsNull());
|
|
auto uri_cstr = Deobfuscate(uri.ToCString());
|
|
if (FLAG_resolve_dwarf_paths) {
|
|
auto const converted_cstr = ConvertResolvedURI(uri_cstr);
|
|
// Strictly enforce this to catch inconvertible cases.
|
|
if (converted_cstr == nullptr) {
|
|
FATAL("cannot convert resolved URI %s", uri_cstr);
|
|
}
|
|
uri_cstr = converted_cstr;
|
|
}
|
|
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();
|
|
}
|
|
});
|
|
}
|
|
|
|
const char* Dwarf::Deobfuscate(const char* cstr) {
|
|
if (reverse_obfuscation_trie_ == nullptr) return cstr;
|
|
TextBuffer buffer(256);
|
|
// Used to avoid Zone-allocating strings if no deobfuscation was performed.
|
|
bool changed = false;
|
|
intptr_t i = 0;
|
|
while (cstr[i] != '\0') {
|
|
intptr_t offset;
|
|
auto const value = reverse_obfuscation_trie_->Lookup(cstr + i, &offset);
|
|
if (offset == 0) {
|
|
// The first character was an invalid key element (that isn't the null
|
|
// terminator due to the while condition), copy it and skip to the next.
|
|
buffer.AddChar(cstr[i++]);
|
|
} else if (value != nullptr) {
|
|
changed = true;
|
|
buffer.AddString(value);
|
|
} else {
|
|
buffer.AddRaw(reinterpret_cast<const uint8_t*>(cstr + i), offset);
|
|
}
|
|
i += offset;
|
|
}
|
|
if (!changed) return cstr;
|
|
return OS::SCreate(zone_, "%s", buffer.buffer());
|
|
}
|
|
|
|
Trie<const char>* Dwarf::CreateReverseObfuscationTrie(Zone* zone) {
|
|
auto const map_array = IsolateGroup::Current()->obfuscation_map();
|
|
if (map_array == nullptr) return nullptr;
|
|
|
|
Trie<const char>* trie = nullptr;
|
|
for (intptr_t i = 0; map_array[i] != nullptr; i += 2) {
|
|
auto const key = map_array[i];
|
|
auto const value = map_array[i + 1];
|
|
ASSERT(value != nullptr);
|
|
// Don't include identity mappings.
|
|
if (strcmp(key, value) == 0) continue;
|
|
// Otherwise, any value in the obfuscation map should be a valid key.
|
|
ASSERT(Trie<const char>::IsValidKey(value));
|
|
trie = Trie<const char>::AddString(zone, trie, value, key);
|
|
}
|
|
return trie;
|
|
}
|
|
|
|
#endif // DART_PRECOMPILER
|
|
|
|
} // namespace dart
|