| // 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 |