Files
sdk/runtime/vm/dwarf.cc
T
Alexander Markov 2786b9ff43 [vm/aot] Add detailed error messages and stack traces for null checks
This CL fixes error message in NoSuchMethodError thrown by null checks
and line numbers in corresponding stack traces (in AOT).

Name of the called function is placed into object pool, and metadata
for null check site is generated in CodeSourceMap.

Size of flutter gallery in release mode:
	  Before	 After
RW	  2165286	 2201642	(+1.68%)
RO	  2122192	 2168224	(+2.17%)
RX	  6871072	 6871072	(+0.00%)
Total	 11163079	11245467	(+0.74%)

Closes https://github.com/dart-lang/sdk/issues/32863

Change-Id: I5ad1190f2ec9452a669863f7dd114ea5f9092d52
Reviewed-on: https://dart-review.googlesource.com/52703
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
2018-04-26 20:33:15 +00:00

653 lines
21 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_descriptors.h"
#include "vm/object_store.h"
namespace dart {
#ifdef DART_PRECOMPILER
#if defined(ARCH_IS_32_BIT)
#define FORM_ADDR ".4byte"
#elif defined(ARCH_IS_64_BIT)
#define FORM_ADDR ".8byte"
#endif
class InliningNode : public ZoneAllocated {
public:
InliningNode(const Function& function,
TokenPosition call_pos,
int32_t start_pc_offset)
: function(function),
call_pos(call_pos),
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;
TokenPosition call_pos;
int32_t start_pc_offset;
int32_t end_pc_offset;
InliningNode* children_head;
InliningNode* children_tail;
InliningNode* children_next;
};
Dwarf::Dwarf(Zone* zone, StreamingWriteStream* stream)
: zone_(zone),
stream_(stream),
codes_(zone, 1024),
code_to_index_(zone),
functions_(zone, 1024),
function_to_index_(zone),
scripts_(zone, 1024),
script_to_index_(zone),
temp_(0) {}
intptr_t Dwarf::AddCode(const Code& code) {
RELEASE_ASSERT(!code.IsNull());
CodeIndexPair* pair = code_to_index_.Lookup(&code);
if (pair != NULL) {
return pair->index_;
}
intptr_t index = codes_.length();
const Code& zone_code = Code::ZoneHandle(zone_, code.raw());
code_to_index_.Insert(CodeIndexPair(&zone_code, index));
codes_.Add(&zone_code);
if (code.IsFunctionCode()) {
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);
}
}
return index;
}
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.raw());
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.raw());
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::Print(const char* format, ...) {
va_list args;
va_start(args, format);
stream_->VPrint(format, args);
va_end(args);
}
void Dwarf::WriteAbbreviations() {
// 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.
#if defined(TARGET_OS_MACOS) || defined(TARGET_OS_MACOS_IOS)
Print(".section __DWARF,__debug_abbrev,regular,debug\n");
#elif defined(TARGET_OS_LINUX) || defined(TARGET_OS_ANDROID) || \
defined(TARGET_OS_FUCHSIA)
Print(".section .debug_abbrev,\"\"\n");
#else
UNIMPLEMENTED();
#endif
uleb128(kCompilationUnit); // Abbrev code.
uleb128(DW_TAG_compile_unit); // Type.
u1(DW_CHILDREN_yes);
uleb128(DW_AT_name); // Start of attributes.
uleb128(DW_FORM_string);
uleb128(DW_AT_producer);
uleb128(DW_FORM_string);
uleb128(DW_AT_comp_dir);
uleb128(DW_FORM_string);
uleb128(DW_AT_low_pc);
uleb128(DW_FORM_addr);
uleb128(DW_AT_high_pc);
uleb128(DW_FORM_addr);
uleb128(DW_AT_stmt_list);
uleb128(DW_FORM_sec_offset);
uleb128(0);
uleb128(0); // End of attributes.
uleb128(kAbstractFunction); // Abbrev code.
uleb128(DW_TAG_subprogram); // Type.
u1(DW_CHILDREN_yes);
uleb128(DW_AT_name); // Start of attributes.
uleb128(DW_FORM_string);
uleb128(DW_AT_decl_file);
uleb128(DW_FORM_udata);
uleb128(DW_AT_decl_line);
uleb128(DW_FORM_udata);
uleb128(DW_AT_inline);
uleb128(DW_FORM_udata);
uleb128(0);
uleb128(0); // End of attributes.
uleb128(kConcreteFunction); // Abbrev code.
uleb128(DW_TAG_subprogram); // Type.
u1(DW_CHILDREN_yes);
uleb128(DW_AT_abstract_origin); // Start of attributes.
uleb128(DW_FORM_ref4);
uleb128(DW_AT_low_pc);
uleb128(DW_FORM_addr);
uleb128(DW_AT_high_pc);
uleb128(DW_FORM_addr);
uleb128(0);
uleb128(0); // End of attributes.
uleb128(kInlinedFunction); // Abbrev code.
uleb128(DW_TAG_inlined_subroutine); // Type.
u1(DW_CHILDREN_yes);
uleb128(DW_AT_abstract_origin); // Start of attributes.
uleb128(DW_FORM_ref4);
uleb128(DW_AT_low_pc);
uleb128(DW_FORM_addr);
uleb128(DW_AT_high_pc);
uleb128(DW_FORM_addr);
uleb128(DW_AT_call_file);
uleb128(DW_FORM_udata);
uleb128(DW_AT_call_line);
uleb128(DW_FORM_udata);
uleb128(0);
uleb128(0); // End of attributes.
uleb128(0); // End of abbreviations.
}
void Dwarf::WriteCompilationUnit() {
// 7.5.1.1 Compilation Unit Header
#if defined(TARGET_OS_MACOS) || defined(TARGET_OS_MACOS_IOS)
Print(".section __DWARF,__debug_info,regular,debug\n");
#elif defined(TARGET_OS_LINUX) || defined(TARGET_OS_ANDROID) || \
defined(TARGET_OS_FUCHSIA)
Print(".section .debug_info,\"\"\n");
#else
UNIMPLEMENTED();
#endif
Print(".Ldebug_info:\n");
// Unit length. Assignment to temp works around buggy Mac assembler.
Print("Lcu_size = .Lcu_end - .Lcu_start\n");
Print(".4byte Lcu_size\n");
Print(".Lcu_start:\n");
u2(2); // DWARF version 2
u4(0); // debug_abbrev_offset
u1(sizeof(void*)); // 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.
uleb128(kCompilationUnit);
const Library& root_library = Library::Handle(
zone_, Isolate::Current()->object_store()->root_library());
const String& root_uri = String::Handle(zone_, root_library.url());
Print(".string \"%s\"\n", root_uri.ToCString()); // DW_AT_name
Print(".string \"Dart VM\"\n"); // DW_AT_producer
Print(".string \"\"\n"); // 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.
Print(FORM_ADDR " _kDartIsolateSnapshotInstructions\n");
// 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.
intptr_t last_code_index = codes_.length() - 1;
const Code& last_code = *(codes_[last_code_index]);
Print(FORM_ADDR " .Lcode%" Pd " + %" Pd "\n", last_code_index,
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.
u4(0);
WriteAbstractFunctions();
WriteConcreteFunctions();
uleb128(0); // End of children.
uleb128(0); // End of entries.
Print(".Lcu_end:\n");
}
void Dwarf::WriteAbstractFunctions() {
Script& script = Script::Handle(zone_);
String& name = String::Handle(zone_);
for (intptr_t i = 0; i < functions_.length(); i++) {
const Function& function = *(functions_[i]);
name = function.QualifiedUserVisibleName();
script = function.script();
intptr_t file = LookupScript(script);
intptr_t line = 0; // Not known. Script has already lost its token stream.
Print(".Lfunc%" Pd ":\n", i); // Label for DW_AT_abstract_origin references
uleb128(kAbstractFunction);
Print(".string \"%s\"\n", name.ToCString()); // DW_AT_name
uleb128(file); // DW_AT_decl_file
uleb128(line); // DW_AT_decl_line
uleb128(DW_INL_inlined); // DW_AT_inline
uleb128(0); // End of children.
}
}
void Dwarf::WriteConcreteFunctions() {
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()) {
continue;
}
function = code.function();
intptr_t function_index = LookupFunction(function);
script = function.script();
uleb128(kConcreteFunction);
// DW_AT_abstract_origin
// References a node written above in WriteAbstractFunctions.
// Assignment to temp works around buggy Mac assembler.
intptr_t temp = temp_++;
Print("Ltemp%" Pd " = .Lfunc%" Pd " - .Ldebug_info\n", temp,
function_index);
Print(".4byte Ltemp%" Pd "\n", temp);
// DW_AT_low_pc
Print(FORM_ADDR " .Lcode%" Pd "\n", i);
// DW_AT_high_pc
Print(FORM_ADDR " .Lcode%" Pd " + %" Pd "\n", i, code.Size());
InliningNode* node = ExpandInliningTree(code);
if (node != NULL) {
for (InliningNode* child = node->children_head; child != NULL;
child = child->children_next) {
WriteInliningNode(child, i, script);
}
}
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<TokenPosition> token_positions(zone_, 4);
NoSafepointScope no_safepoint;
ReadStream stream(map.Data(), map.Length());
int32_t current_pc_offset = 0;
InliningNode* root_node =
new (zone_) InliningNode(root_function, TokenPosition(), 0);
root_node->end_pc_offset = code.Size();
node_stack.Add(root_node);
token_positions.Add(CodeSourceMapBuilder::kInitialPosition);
while (stream.PendingBytes() > 0) {
uint8_t opcode = stream.Read<uint8_t>();
switch (opcode) {
case CodeSourceMapBuilder::kChangePosition: {
int32_t position = stream.Read<int32_t>();
token_positions[token_positions.length() - 1] = TokenPosition(position);
break;
}
case CodeSourceMapBuilder::kAdvancePC: {
int32_t delta = stream.Read<int32_t>();
current_pc_offset += delta;
break;
}
case CodeSourceMapBuilder::kPushFunction: {
int32_t func = stream.Read<int32_t>();
const Function& child_func =
Function::ZoneHandle(zone_, Function::RawCast(functions.At(func)));
TokenPosition call_pos = token_positions.Last();
InliningNode* child_node =
new (zone_) InliningNode(child_func, call_pos, current_pc_offset);
node_stack.Last()->AppendChild(child_node);
node_stack.Add(child_node);
token_positions.Add(CodeSourceMapBuilder::kInitialPosition);
break;
}
case CodeSourceMapBuilder::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 CodeSourceMapBuilder::kNullCheck: {
stream.Read<int32_t>();
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(InliningNode* node,
intptr_t root_code_index,
const Script& parent_script) {
intptr_t file = LookupScript(parent_script);
intptr_t line = node->call_pos.value();
intptr_t function_index = LookupFunction(node->function);
const Script& script = Script::Handle(zone_, node->function.script());
uleb128(kInlinedFunction);
// DW_AT_abstract_origin
// References a node written above in WriteAbstractFunctions.
// Assignment to temp works around buggy Mac assembler.
intptr_t temp = temp_++;
Print("Ltemp%" Pd " = .Lfunc%" Pd " - .Ldebug_info\n", temp, function_index);
Print(".4byte Ltemp%" Pd "\n", temp);
// DW_AT_low_pc
Print(FORM_ADDR " .Lcode%" Pd " + %" Pd "\n", root_code_index,
node->start_pc_offset);
// DW_AT_high_pc
Print(FORM_ADDR " .Lcode%" Pd " + %" Pd "\n", root_code_index,
node->end_pc_offset);
// DW_AT_call_file
uleb128(file);
// DW_AT_call_line
uleb128(line);
for (InliningNode* child = node->children_head; child != NULL;
child = child->children_next) {
WriteInliningNode(child, root_code_index, script);
}
uleb128(0); // End of children.
}
void Dwarf::WriteLines() {
#if defined(TARGET_OS_MACOS) || defined(TARGET_OS_MACOS_IOS)
Print(".section __DWARF,__debug_line,regular,debug\n");
#elif defined(TARGET_OS_LINUX) || defined(TARGET_OS_ANDROID) || \
defined(TARGET_OS_FUCHSIA)
Print(".section .debug_line,\"\"\n");
#else
UNIMPLEMENTED();
#endif
// 6.2.4 The Line Number Program Header
// 1. unit_length. This encoding implies 32-bit DWARF.
Print("Lline_size = .Lline_end - .Lline_start\n");
Print(".4byte Lline_size\n");
Print(".Lline_start:\n");
u2(2); // 2. DWARF version 2
// 3. header_length
// Assignment to temp works around buggy Mac assembler.
Print("Llineheader_size = .Llineheader_end - .Llineheader_start\n");
Print(".4byte Llineheader_size\n");
Print(".Llineheader_start:\n");
u1(1); // 4. minimum_instruction_length
u1(0); // 5. default_is_stmt
u1(0); // 6. line_base
u1(1); // 7. line_range
u1(13); // 8. opcode_base (12 standard opcodes in Dwarf 2)
// 9. standard_opcode_lengths
u1(0); // DW_LNS_copy, 0 operands
u1(1); // DW_LNS_advance_pc, 1 operands
u1(1); // DW_LNS_advance_list, 1 operands
u1(1); // DW_LNS_set_file, 1 operands
u1(1); // DW_LNS_set_column, 1 operands
u1(0); // DW_LNS_negate_stmt, 0 operands
u1(0); // DW_LNS_set_basic_block, 0 operands
u1(0); // DW_LNS_const_add_pc, 0 operands
u1(1); // DW_LNS_fixed_advance_pc, 1 operands
u1(0); // DW_LNS_set_prolog_end, 0 operands
u1(0); // DW_LNS_set_epligoue_begin, 0 operands
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.
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]);
uri ^= script.url();
Print(".string \"%s\"\n", uri.ToCString());
uleb128(0); // Include directory index.
uleb128(0); // File modification time.
uleb128(0); // File length.
}
u1(0); // End of file names.
Print(".Llineheader_end:\n");
// 6.2.5 The Line Number Program
intptr_t previous_file = 1;
intptr_t previous_line = 1;
intptr_t previous_code_index = -1;
intptr_t previous_pc_offset = 0;
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<TokenPosition> token_positions(zone_, 8);
for (intptr_t i = 0; i < codes_.length(); i++) {
const Code& code = *(codes_[i]);
map = code.code_source_map();
if (map.IsNull()) {
continue;
}
root_function = code.function();
functions = code.inlined_id_to_function();
NoSafepointScope no_safepoint;
ReadStream stream(map.Data(), map.Length());
function_stack.Clear();
token_positions.Clear();
int32_t current_pc_offset = 0;
function_stack.Add(&root_function);
token_positions.Add(CodeSourceMapBuilder::kInitialPosition);
while (stream.PendingBytes() > 0) {
uint8_t opcode = stream.Read<uint8_t>();
switch (opcode) {
case CodeSourceMapBuilder::kChangePosition: {
int32_t position = stream.Read<int32_t>();
token_positions[token_positions.length() - 1] =
TokenPosition(position);
break;
}
case CodeSourceMapBuilder::kAdvancePC: {
int32_t delta = stream.Read<int32_t>();
current_pc_offset += delta;
const Function& function = *(function_stack.Last());
script = function.script();
intptr_t file = LookupScript(script);
// 1. Update LNP file.
if (file != previous_file) {
u1(DW_LNS_set_file);
uleb128(file);
previous_file = file;
}
// 2. Update LNP line.
TokenPosition pos = token_positions.Last();
intptr_t line = pos.value();
if (line != previous_line) {
u1(DW_LNS_advance_line);
sleb128(line - previous_line);
previous_line = line;
}
// 3. Emit LNP row.
u1(DW_LNS_copy);
// 4. Update LNP pc.
if (previous_code_index == -1) {
// This variant is relocatable.
u1(0); // This is an extended opcode
u1(1 + sizeof(void*)); // that is 5 or 9 bytes long
u1(DW_LNE_set_address);
Print(FORM_ADDR " .Lcode%" Pd " + %" Pd "\n", i, current_pc_offset);
} else {
u1(DW_LNS_advance_pc);
Print(".uleb128 .Lcode%" Pd " - .Lcode%" Pd " + %" Pd "\n", i,
previous_code_index, current_pc_offset - previous_pc_offset);
}
previous_code_index = i;
previous_pc_offset = current_pc_offset;
break;
}
case CodeSourceMapBuilder::kPushFunction: {
int32_t func_index = stream.Read<int32_t>();
const Function& child_func = Function::Handle(
zone_, Function::RawCast(functions.At(func_index)));
function_stack.Add(&child_func);
token_positions.Add(CodeSourceMapBuilder::kInitialPosition);
break;
}
case CodeSourceMapBuilder::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 CodeSourceMapBuilder::kNullCheck: {
stream.Read<int32_t>();
break;
}
default:
UNREACHABLE();
}
}
}
// Advance pc to end of the compilation unit.
intptr_t last_code_index = codes_.length() - 1;
const Code& last_code = *(codes_[last_code_index]);
u1(DW_LNS_advance_pc);
Print(".uleb128 .Lcode%" Pd " - .Lcode%" Pd " + %" Pd "\n", last_code_index,
previous_code_index, last_code.Size() - previous_pc_offset);
// End of contiguous machine code.
u1(0); // This is an extended opcode
u1(1); // that is 1 byte long
u1(DW_LNE_end_sequence);
Print(".Lline_end:\n");
}
#endif // DART_PRECOMPILER
} // namespace dart