dart-sdk/runtime/vm/dwarf.cc
Tess Strickland 3b2c41d789 [vm] Make symbol names more user-readable.
Instead of trying to make names assembler-friendly, instead just
quote the symbols names in the assembly output and use user visible
names for function and object symbols.

More specifically:

* This CL changes the symbol name from an assembler-safe version of the
  scrubbed name to an assembler-unsafe version of the user visible name
  that is then quoted when used in assembly output.

* For code symbols, regular stubs are prefixed with `stub`, allocation
  stubs are prefixed with `new` and type testing stubs are prefixed with
  `assert type is` (e.g., `stub Await`, `new RegExp`,
  `assert type is List<Int>`). Function symbols have no prefix.

* Readonly data symbols begin with the type of the readonly data. If
  there's any additional information (e.g., the function name for
  objects like `PcDescriptors` when `SNAPSHOT_BACKTRACE` is defined),
  that follows the type in parentheses.

* For direct-to-ELF snapshots, we allow symbols to have duplicate names.
  Thus, internally we create unique ids for all created symbols and
  refactor the ELF and DWARF subsystems to pass and store these ids
  instead of the symbol names.

* For assembly output, symbols must have unique names. The namer now
  has a `CStringIntMap` that keeps counts of the number of times a
  given symbol name has been generated for assembly output. If the
  symbol name is in the `CStringIntMap`, then the new count in the map
  is added as a suffix to ensure uniqueness.

  For example, the first symbol created using the name `_StringBase.[]`
  has no suffix. If a second symbol is created using the same name,
  the second symbol is instead named `_StringBase.[] (#2)`.

  This also happens for read-only objects with additional information if
  that additional information is not sufficient to make it unique, and
  is added separately from that additional information.

* A further restriction for assembly output is that symbols that begin
  with 'L' have special meaning to the assembler. Thus, we keep the
  old system of prepending such symbol names with an underscore to
  avoid this behavior.

TEST=vm/dart{,_2}/use_add_readonly_data_symbol_flag

Bug: b/248012965
Change-Id: I1b6e876f4fb4f42f4a3b90e03110a34a87a03a7c
Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-dwarf-linux-product-x64-try,vm-kernel-precomp-linux-product-x64-try,vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-kernel-precomp-nnbd-linux-release-x64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try,vm-kernel-precomp-mac-product-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/262242
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
2022-10-06 10:33:09 +00:00

987 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 kResolvedSdkRoot[] = "org-dartlang-sdk:///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
}
if (len > kResolvedSdkRootLen &&
strncmp(str, kResolvedSdkRoot, kResolvedSdkRootLen) == 0) {
// Leave "sdk/" as a prefix in the returned path.
return str + (kResolvedSdkRootLen - 4);
}
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