blob: 04db5420df79f77d7f0f2e7649a5d0b322c9cc19 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "garnet/bin/zxdb/symbols/input_location.h"
#include "garnet/bin/zxdb/symbols/line_details.h"
#include "garnet/bin/zxdb/symbols/module_symbols_impl.h"
#include "garnet/bin/zxdb/symbols/resolve_options.h"
#include "garnet/bin/zxdb/symbols/symbol_context.h"
#include "garnet/bin/zxdb/symbols/test_symbol_module.h"
#include "garnet/bin/zxdb/symbols/type.h"
#include "garnet/bin/zxdb/symbols/variable.h"
#include "gtest/gtest.h"
namespace zxdb {
namespace {
class ScopedUnlink {
public:
explicit ScopedUnlink(const char* name) : name_(name) {}
~ScopedUnlink() { EXPECT_EQ(0, unlink(name_)); }
private:
const char* name_;
};
} // namespace
// Trying to load a nonexistant file should error.
TEST(ModuleSymbols, NonExistantFile) {
ModuleSymbolsImpl module(
TestSymbolModule::GetCheckedInTestFileName() + "_NONEXISTANT", "");
Err err = module.Load();
EXPECT_TRUE(err.has_error());
}
// Trying to load a random file should error.
TEST(ModuleSymbols, BadFileType) {
char temp_name[] = "/tmp/zxdb_symbol_test.txtXXXXXX";
int fd = mkstemp(temp_name);
ASSERT_LT(0, fd) << "Could not create temporary file: " << temp_name;
// Just use the file name itself as the contents of the file.
ScopedUnlink unlink(temp_name);
EXPECT_LT(0, write(fd, temp_name, strlen(temp_name)));
close(fd);
ModuleSymbolsImpl module(
TestSymbolModule::GetCheckedInTestFileName() + "_NONEXISTANT", "");
Err err = module.Load();
EXPECT_TRUE(err.has_error());
}
TEST(ModuleSymbols, Basic) {
ModuleSymbolsImpl module(TestSymbolModule::GetCheckedInTestFileName(), "");
Err err = module.Load();
EXPECT_FALSE(err.has_error()) << err.msg();
// Make a symbol context with some load address to ensure that the addresses
// round-trip properly.
SymbolContext symbol_context(0x18000);
// MyFunction() should have one implementation.
std::vector<Location> addrs = module.ResolveInputLocation(
symbol_context, InputLocation(TestSymbolModule::kMyFunctionName));
ASSERT_EQ(1u, addrs.size());
// On one occasion Clang generated a symbol file that listed many functions
// in this file starting at offset 0. This obviously causes problems and
// the test fails below with bafflingly incorrect line numbers. The problem
// went away after forcing recompilation of that file. It might be an
// intermittent Clang bug or some random corruption. If this assert hits,
// check the function start addresses in the DWARF dump, there should be
// no functions starting at offset 0 in the file.
ASSERT_NE(0u, addrs[0].address());
// That address should resolve back to the function name.
auto locations = module.ResolveInputLocation(
symbol_context, InputLocation(addrs[0].address()));
ASSERT_EQ(1u, locations.size());
EXPECT_TRUE(locations[0].is_symbolized());
EXPECT_EQ("zxdb_symbol_test.cc", locations[0].file_line().GetFileNamePart());
EXPECT_EQ(TestSymbolModule::kMyFunctionLine, locations[0].file_line().line());
}
TEST(ModuleSymbols, LineDetailsForAddress) {
ModuleSymbolsImpl module(TestSymbolModule::GetCheckedInTestFileName(), "");
Err err = module.Load();
EXPECT_FALSE(err.has_error()) << err.msg();
// Make a symbol context with some load address to ensure that the addresses
// round-trip properly.
SymbolContext symbol_context(0x18000);
// Get the canonical file name to test.
auto file_matches = module.FindFileMatches("line_lookup_symbol_test.cc");
ASSERT_EQ(1u, file_matches.size());
const std::string file_name = file_matches[0];
// Get address of line 28 which is a normal line with code on both sides.
int const kLineToQuery = 28;
ResolveOptions options;
options.symbolize = false;
std::vector<Location> addrs;
addrs = module.ResolveInputLocation(
symbol_context, InputLocation(FileLine(file_name, kLineToQuery)),
options);
ASSERT_LE(1u, addrs.size());
auto locations = module.ResolveInputLocation(
symbol_context, InputLocation(addrs[0].address()));
ASSERT_EQ(1u, locations.size());
EXPECT_EQ(kLineToQuery, locations[0].file_line().line());
EXPECT_EQ(file_name, locations[0].file_line().file());
// Lookup the line info. Normally we expect one line table entry for this but
// don't want to assume that since the compiler could emit multiple entries
// for it.
LineDetails line_details =
module.LineDetailsForAddress(symbol_context, addrs[0].address());
EXPECT_EQ(file_name, line_details.file_line().file());
EXPECT_EQ(kLineToQuery, line_details.file_line().line());
ASSERT_FALSE(line_details.entries().empty());
uint64_t begin_range = line_details.entries().front().range.begin();
uint64_t end_range = line_details.entries().back().range.end();
EXPECT_LT(begin_range, end_range);
// The address before the beginning of the range should be the previous line.
LineDetails prev_details =
module.LineDetailsForAddress(symbol_context, begin_range - 1);
EXPECT_EQ(kLineToQuery - 1, prev_details.file_line().line());
EXPECT_EQ(file_name, prev_details.file_line().file());
ASSERT_FALSE(prev_details.entries().empty());
EXPECT_EQ(begin_range, prev_details.entries().back().range.end());
// The end of the range (which is non-inclusive) should be the next line.
LineDetails next_details =
module.LineDetailsForAddress(symbol_context, end_range);
EXPECT_EQ(kLineToQuery + 1, next_details.file_line().line());
EXPECT_EQ(file_name, next_details.file_line().file());
ASSERT_FALSE(next_details.entries().empty());
EXPECT_EQ(end_range, next_details.entries().front().range.begin());
}
TEST(ModuleSymbols, ResolveLineInputLocation) {
ModuleSymbolsImpl module(TestSymbolModule::GetCheckedInTestFileName(), "");
Err err = module.Load();
EXPECT_FALSE(err.has_error()) << err.msg();
// Make a symbol context with some load address to ensure that the addresses
// round-trip properly.
SymbolContext symbol_context(0x18000);
// Get the canonical file name to test.
auto file_matches = module.FindFileMatches("line_lookup_symbol_test.cc");
ASSERT_EQ(1u, file_matches.size());
const std::string file_name = file_matches[0];
// Basic one, look for line 27 which is a normal statement.
ResolveOptions options;
options.symbolize = false;
std::vector<Location> addrs;
addrs = module.ResolveInputLocation(
symbol_context, InputLocation(FileLine(file_name, 27)), options);
ASSERT_LE(1u, addrs.size());
auto locations = module.ResolveInputLocation(
symbol_context, InputLocation(addrs[0].address()));
ASSERT_EQ(1u, locations.size());
EXPECT_EQ(27, locations[0].file_line().line());
EXPECT_EQ(file_name, locations[0].file_line().file());
// Line 26 is a comment line, looking it up should get the following line.
addrs = module.ResolveInputLocation(
symbol_context, InputLocation(FileLine(file_name, 26)), options);
ASSERT_LE(1u, addrs.size());
locations = module.ResolveInputLocation(symbol_context,
InputLocation(addrs[0].address()));
ASSERT_EQ(1u, locations.size());
EXPECT_EQ(27, locations[0].file_line().line());
EXPECT_EQ(file_name, locations[0].file_line().file());
// Line 15 is the beginning of the templatized function. There should be
// two matches since its instantiated twice.
addrs = module.ResolveInputLocation(
symbol_context, InputLocation(FileLine(file_name, 15)), options);
ASSERT_EQ(2u, addrs.size());
locations = module.ResolveInputLocation(symbol_context,
InputLocation(addrs[0].address()));
ASSERT_EQ(1u, locations.size());
EXPECT_EQ(15, locations[0].file_line().line());
EXPECT_EQ(file_name, locations[0].file_line().file());
locations = module.ResolveInputLocation(symbol_context,
InputLocation(addrs[1].address()));
ASSERT_EQ(1u, locations.size());
EXPECT_EQ(15, locations[0].file_line().line());
EXPECT_EQ(file_name, locations[0].file_line().file());
// Line 17 is only present in one of the two template instantiations.
// We should only find it once (see note below about case #2).
addrs = module.ResolveInputLocation(
symbol_context, InputLocation(FileLine(file_name, 17)), options);
ASSERT_TRUE(addrs.size() == 1u || addrs.size() == 2u);
locations = module.ResolveInputLocation(symbol_context,
InputLocation(addrs[0].address()));
ASSERT_EQ(1u, locations.size());
EXPECT_EQ(17, locations[0].file_line().line());
if (addrs.size() == 2u) {
// MSVC in debug mode will emit the full code in both instantiations of the
// template which is valid. To be more robust this test allows that form
// even though Clang doesn't do this. The important thing is that looking
// up line 17 never gives us line 19 (which is the other template
// instantiation).
locations = module.ResolveInputLocation(symbol_context,
InputLocation(addrs[1].address()));
EXPECT_EQ(17, locations[0].file_line().line());
}
}
TEST(ModuleSymbols, ResolveGlobalVariable) {
ModuleSymbolsImpl module(TestSymbolModule::GetCheckedInTestFileName(), "");
Err err = module.Load();
EXPECT_FALSE(err.has_error()) << err.msg();
SymbolContext symbol_context = SymbolContext::ForRelativeAddresses();
ResolveOptions options;
options.symbolize = true;
std::vector<Location> addrs;
// Look up "kGlobal" which should be a variable of type "int" at some
// nonzero location.
addrs = module.ResolveInputLocation(
symbol_context, InputLocation(TestSymbolModule::kGlobalName), options);
ASSERT_LE(1u, addrs.size());
EXPECT_TRUE(addrs[0].symbol());
const Variable* var = addrs[0].symbol().Get()->AsVariable();
ASSERT_TRUE(var);
EXPECT_EQ(TestSymbolModule::kGlobalName, var->GetFullName());
const Type* var_type = var->type().Get()->AsType();
ASSERT_TRUE(var_type);
EXPECT_EQ("int", var_type->GetFullName());
// This number may change if we recompile the symbol test. That's OK, just
// make sure it agrees with the relative address from symbol dump.
EXPECT_EQ(0x2000u, addrs[0].address());
// Look up the class static.
addrs = module.ResolveInputLocation(
symbol_context, InputLocation(TestSymbolModule::kClassStaticName), options);
ASSERT_LE(1u, addrs.size());
EXPECT_TRUE(addrs[0].symbol());
var = addrs[0].symbol().Get()->AsVariable();
ASSERT_TRUE(var);
EXPECT_EQ(TestSymbolModule::kClassStaticName, var->GetFullName());
var_type = var->type().Get()->AsType();
ASSERT_TRUE(var_type);
EXPECT_EQ("int", var_type->GetFullName());
// This number may change if we recompile the symbol test. That's OK, just
// make sure it agrees with the relative address from symbol dump.
EXPECT_EQ(0x2004u, addrs[0].address());
}
} // namespace zxdb