blob: 3a36b9c11649cbb5024051e8aa9724f0a69bfe9c [file] [log] [blame]
// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "syzygy/pe/dia_util.h"
#include <map>
#include <set>
#include <vector>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_comptr.h"
#include "gmock/gmock.h"
#include "syzygy/core/file_util.h"
#include "syzygy/core/unittest_util.h"
#include "syzygy/pdb/pdb_data.h"
#include "syzygy/pe/unittest_util.h"
namespace pe {
namespace {
using base::win::ScopedBstr;
using base::win::ScopedComPtr;
typedef std::vector<std::wstring> StringVector;
static const wchar_t kNonsenseStreamName[] =
L"ThisStreamNameCertainlyDoesNotExist";
// The test_dll output file name configured by the build system.
static const char kTestDllObjPath[] = TEST_DLL_OBJECT_FILE;
struct FilePathLess {
bool operator()(const std::wstring& lhs, const std::wstring& rhs) const {
return base::FilePath::CompareLessIgnoreCase(lhs, rhs);
}
};
class DiaUtilTest : public testing::PELibUnitTest {
};
bool PathsAreEquivalent(const base::FilePath::StringType& path1_val,
const base::FilePath::StringType& path2_val) {
base::FilePath path1(path1_val);
base::FilePath path2(path2_val);
core::FilePathCompareResult result = core::CompareFilePaths(path1, path2);
return result == core::kEquivalentFilePaths;
}
MATCHER_P(IsSameFile, value, "") {
return PathsAreEquivalent(arg, value);
}
bool StringVectorContainsPath(const StringVector& string_vector,
const base::FilePath::StringType& path) {
for (const auto& iter : string_vector) {
if (PathsAreEquivalent(iter, path))
return true;
}
return false;
}
} // namespace
TEST_F(DiaUtilTest, CreateDiaSource) {
ScopedComPtr<IDiaDataSource> dia_source;
EXPECT_TRUE(CreateDiaSource(dia_source.Receive()));
}
TEST_F(DiaUtilTest, CreateDiaSesssionDll) {
ScopedComPtr<IDiaDataSource> dia_source;
ASSERT_TRUE(CreateDiaSource(dia_source.Receive()));
ScopedComPtr<IDiaSession> dia_session;
EXPECT_TRUE(CreateDiaSession(
testing::GetExeRelativePath(testing::kTestDllName),
dia_source.get(),
dia_session.Receive()));
}
TEST_F(DiaUtilTest, CreateDiaSesssionPdb) {
ScopedComPtr<IDiaDataSource> dia_source;
ASSERT_TRUE(CreateDiaSource(dia_source.Receive()));
ScopedComPtr<IDiaSession> dia_session;
EXPECT_TRUE(CreateDiaSession(
testing::GetExeRelativePath(testing::kTestDllPdbName),
dia_source.get(),
dia_session.Receive()));
}
TEST_F(DiaUtilTest, FindDiaTableByIid) {
ScopedComPtr<IDiaDataSource> dia_source;
ASSERT_TRUE(CreateDiaSource(dia_source.Receive()));
ScopedComPtr<IDiaSession> dia_session;
ASSERT_TRUE(CreateDiaSession(
testing::GetExeRelativePath(testing::kTestDllPdbName),
dia_source.get(),
dia_session.Receive()));
ScopedComPtr<IDiaEnumSectionContribs> section_contribs;
EXPECT_EQ(kSearchSucceeded,
FindDiaTable(section_contribs.iid(),
dia_session.get(),
reinterpret_cast<void**>(section_contribs.Receive())));
}
TEST_F(DiaUtilTest, FindDiaTableByType) {
ScopedComPtr<IDiaDataSource> dia_source;
ASSERT_TRUE(CreateDiaSource(dia_source.Receive()));
ScopedComPtr<IDiaSession> dia_session;
ASSERT_TRUE(CreateDiaSession(
testing::GetExeRelativePath(testing::kTestDllPdbName),
dia_source.get(),
dia_session.Receive()));
ScopedComPtr<IDiaEnumSectionContribs> section_contribs;
EXPECT_EQ(kSearchSucceeded,
FindDiaTable(dia_session.get(), section_contribs.Receive()));
}
TEST_F(DiaUtilTest, FindDiaDebugStream) {
ScopedComPtr<IDiaDataSource> dia_source;
ASSERT_TRUE(CreateDiaSource(dia_source.Receive()));
ScopedComPtr<IDiaSession> dia_session;
ASSERT_TRUE(CreateDiaSession(
testing::GetExeRelativePath(testing::kTestDllPdbName),
dia_source.get(),
dia_session.Receive()));
ScopedComPtr<IDiaEnumDebugStreamData> debug_stream;
EXPECT_EQ(kSearchFailed,
FindDiaDebugStream(kNonsenseStreamName,
dia_session.get(),
debug_stream.Receive()));
EXPECT_EQ(kSearchSucceeded,
FindDiaDebugStream(kFixupDiaDebugStreamName,
dia_session.get(),
debug_stream.Receive()));
}
TEST_F(DiaUtilTest, LoadDiaDebugStream) {
ScopedComPtr<IDiaDataSource> dia_source;
ASSERT_TRUE(CreateDiaSource(dia_source.Receive()));
ScopedComPtr<IDiaSession> dia_session;
ASSERT_TRUE(CreateDiaSession(
testing::GetExeRelativePath(testing::kTestDllPdbName),
dia_source.get(),
dia_session.Receive()));
ScopedComPtr<IDiaEnumDebugStreamData> debug_stream;
ASSERT_EQ(kSearchSucceeded,
FindDiaDebugStream(kFixupDiaDebugStreamName,
dia_session.get(),
debug_stream.Receive()));
std::vector<pdb::PdbFixup> fixups;
EXPECT_TRUE(LoadDiaDebugStream(debug_stream.get(), &fixups));
EXPECT_FALSE(fixups.empty());
}
TEST_F(DiaUtilTest, FindAndLoadDiaDebugStreamByName) {
ScopedComPtr<IDiaDataSource> dia_source;
ASSERT_TRUE(CreateDiaSource(dia_source.Receive()));
ScopedComPtr<IDiaSession> dia_session;
ASSERT_TRUE(CreateDiaSession(
testing::GetExeRelativePath(testing::kTestDllPdbName),
dia_source.get(),
dia_session.Receive()));
std::vector<pdb::PdbFixup> fixups;
EXPECT_EQ(kSearchFailed,
FindAndLoadDiaDebugStreamByName(kNonsenseStreamName,
dia_session.get(),
&fixups));
EXPECT_TRUE(fixups.empty());
EXPECT_EQ(kSearchSucceeded,
FindAndLoadDiaDebugStreamByName(kFixupDiaDebugStreamName,
dia_session.get(),
&fixups));
EXPECT_FALSE(fixups.empty());
}
class DiaUtilVisitorTest : public DiaUtilTest {
public:
void SetUp() override {
ASSERT_TRUE(CreateDiaSource(dia_source_.Receive()));
ASSERT_TRUE(CreateDiaSession(
testing::GetExeRelativePath(testing::kTestDllName),
dia_source_.get(),
dia_session_.Receive()));
ASSERT_EQ(S_OK, dia_session_->get_globalScope(dia_globals_.Receive()));
}
bool OnFunction(StringVector* names, IDiaSymbol* function) {
EXPECT_TRUE(IsSymTag(function, SymTagFunction));
ScopedBstr name;
EXPECT_EQ(S_OK, function->get_name(name.Receive()));
names->push_back(common::ToString(name));
return true;
}
bool OnCompiland(StringVector* names, IDiaSymbol* compiland) {
EXPECT_TRUE(IsSymTag(compiland, SymTagCompiland));
ScopedBstr name;
EXPECT_EQ(S_OK, compiland->get_name(name.Receive()));
names->push_back(common::ToString(name));
return true;
}
bool OnCompilandFind(const std::wstring& compiland_path,
ScopedComPtr<IDiaSymbol>* compiland_out,
IDiaSymbol* compiland) {
EXPECT_TRUE(IsSymTag(compiland, SymTagCompiland));
ScopedBstr name;
EXPECT_EQ(S_OK, compiland->get_name(name.Receive()));
if (testing::Value(common::ToString(name), IsSameFile(compiland_path))) {
*compiland_out = compiland;
return false;
}
return true;
}
typedef std::set<std::pair<DWORD, DWORD>> LineSet;
typedef std::map<std::wstring, LineSet, FilePathLess> LineMap;
bool OnLine(LineMap* line_map, IDiaLineNumber* line) {
DCHECK_NE(reinterpret_cast<LineMap*>(NULL), line_map);
DCHECK_NE(reinterpret_cast<IDiaLineNumber*>(NULL), line);
ScopedComPtr<IDiaSourceFile> source_file;
EXPECT_HRESULT_SUCCEEDED(line->get_sourceFile(source_file.Receive()));
ScopedBstr source_name;
EXPECT_HRESULT_SUCCEEDED(source_file->get_fileName(source_name.Receive()));
ScopedComPtr<IDiaSymbol> compiland;
EXPECT_HRESULT_SUCCEEDED(line->get_compiland(compiland.Receive()));
DWORD line_number = 0;
EXPECT_HRESULT_SUCCEEDED(line->get_lineNumber(&line_number));
DWORD line_number_end = 0;
EXPECT_HRESULT_SUCCEEDED(line->get_lineNumberEnd(&line_number_end));
// This doesn't necessarily have to hold, but so far it seems to do so.
EXPECT_EQ(line_number, line_number_end);
(*line_map)[common::ToString(source_name)].insert(
std::make_pair(line_number, line_number_end));
return true;
}
ScopedComPtr<IDiaDataSource> dia_source_;
ScopedComPtr<IDiaSession> dia_session_;
ScopedComPtr<IDiaSymbol> dia_globals_;
};
TEST_F(DiaUtilVisitorTest, ChildVisitorTest) {
ChildVisitor visitor(dia_globals_.get(), SymTagFunction);
StringVector function_names;
ASSERT_TRUE(visitor.VisitChildren(
base::Bind(&DiaUtilVisitorTest::OnFunction,
base::Unretained(this),
&function_names)));
// Expect that we found a bunch of functions.
ASSERT_LT(1U, function_names.size());
// One of them should be "DllMain".
ASSERT_THAT(function_names, testing::Contains(L"DllMain"));
}
TEST_F(DiaUtilVisitorTest, CompilandVisitorTest) {
CompilandVisitor visitor(dia_session_.get());
StringVector compiland_names;
ASSERT_TRUE(visitor.VisitAllCompilands(
base::Bind(&DiaUtilVisitorTest::OnCompiland,
base::Unretained(this),
&compiland_names)));
// We expect to have seen some compiland_names.
ASSERT_LT(0U, compiland_names.size());
// One of the compiland_names should be the test_dll.obj file.
std::string test_dll_path(kTestDllObjPath);
std::wstring test_dll_wide_path(test_dll_path.begin(), test_dll_path.end());
base::FilePath test_dll_obj =
base::MakeAbsoluteFilePath(
testing::GetOutputRelativePath(test_dll_wide_path.c_str()));
ASSERT_TRUE(StringVectorContainsPath(compiland_names, test_dll_obj.value()));
}
TEST_F(DiaUtilVisitorTest, LineVisitorTest) {
CompilandVisitor compiland_visitor(dia_session_.get());
// Start by finding the test dll compiland.
ScopedComPtr<IDiaSymbol> compiland;
std::string test_dll_path(kTestDllObjPath);
std::wstring test_dll_wide_path(test_dll_path.begin(), test_dll_path.end());
base::FilePath test_dll_obj =
base::MakeAbsoluteFilePath(
testing::GetOutputRelativePath(test_dll_wide_path.c_str()));
ASSERT_FALSE(compiland_visitor.VisitAllCompilands(
base::Bind(&DiaUtilVisitorTest::OnCompilandFind,
base::Unretained(this),
test_dll_obj.value(),
&compiland)));
ASSERT_TRUE(compiland != NULL);
// Now enumerate all line entries in that compiland.
LineVisitor line_visitor(dia_session_.get(), compiland.get());
LineMap line_map;
ASSERT_TRUE(line_visitor.VisitLines(
base::Bind(&DiaUtilVisitorTest::OnLine,
base::Unretained(this),
&line_map)));
// We expect to have at least one file.
ASSERT_LE(1U, line_map.size());
// Expect test_dll.cc to be in the line map, and to contain at least one line
// worth of information. Search for equivalent paths because under some
// mysterious poorly understood conditions the drive letter in the PDB may or
// may not be capitalized.
base::FilePath test_dll_cc =
testing::GetSrcRelativePath(L"syzygy\\pe\\test_dll.cc");
LineMap::const_iterator it = line_map.begin();
for (; it != line_map.end(); ++it) {
if (PathsAreEquivalent(it->first, test_dll_cc.value()))
break;
}
ASSERT_TRUE(it != line_map.end());
ASSERT_LT(1U, it->second.size());
}
} // namespace pe