blob: 8b2fb35ea6829a8caabc97a4a5f28ea7d8c85983 [file] [log] [blame]
// Copyright 2012 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/pdb/pdb_util.h"
#include <objbase.h>
#include "base/command_line.h"
#include "base/path_service.h"
#include "base/scoped_native_library.h"
#include "base/files/scoped_temp_dir.h"
#include "base/process/launch.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/pe_image.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "syzygy/common/dbghelp_util.h"
#include "syzygy/core/unittest_util.h"
#include "syzygy/pdb/pdb_byte_stream.h"
#include "syzygy/pdb/pdb_reader.h"
#include "syzygy/pdb/pdb_writer.h"
#include "syzygy/pdb/unittest_util.h"
#include "syzygy/pe/pe_data.h"
#include "syzygy/pe/unittest_util.h"
namespace pdb {
namespace {
const wchar_t* kTempPdbFileName = L"temp.pdb";
const wchar_t* kTempPdbFileName2 = L"temp2.pdb";
const GUID kSampleGuid = {0xACDC900D, 0x9009, 0xFEED, {7, 6, 5, 4, 3, 2, 1, 0}};
const PdbInfoHeader70 kSamplePdbHeader = {
kPdbCurrentVersion,
1336402486, // 7 May 2012, 14:54:00 GMT.
999,
{0xDEADBEEF, 0x900D, 0xF00D, {0, 1, 2, 3, 4, 5, 6, 7}}
};
const DbiHeader kSampleDbiHeader = {
-1, // signature.
19990903, // version.
999, // age.
};
const OMAP kOmapToData[] = {
{4096, 4096},
{5012, 5012},
{6064, 6064},
{7048, 240504}
};
const OMAP kOmapFromData[] = {
{4096, 4096},
{5012, 5012},
{240504, 7048}
};
class PdbUtilTest : public testing::Test {
public:
PdbUtilTest() : process_(this) {
}
void SetUp() {
ASSERT_TRUE(common::SymInitialize(process_, NULL, false));
ASSERT_HRESULT_SUCCEEDED(::CoCreateGuid(&new_guid_));
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
temp_pdb_file_path_ = temp_dir_.path().Append(kTempPdbFileName);
temp_pdb_file_path2_ = temp_dir_.path().Append(kTempPdbFileName2);
}
void TearDown() {
ASSERT_TRUE(::SymCleanup(process_));
}
void VerifyGuidData(const base::FilePath& pdb_path,
const GUID& guid) {
DWORD64 base_address =
::SymLoadModuleExW(process_,
NULL,
pdb_path.value().c_str(),
NULL,
1,
1,
NULL,
0);
EXPECT_NE(0, base_address);
// Get the module info to verify that the new PDB has the GUID we specified.
IMAGEHLP_MODULEW64 module_info = { sizeof(module_info) };
EXPECT_TRUE(::SymGetModuleInfoW64(process_, base_address, &module_info));
EXPECT_EQ(new_guid_, module_info.PdbSig70);
EXPECT_TRUE(::SymUnloadModule64(process_, base_address));
}
void VerifyOmapData(const base::FilePath& pdb_path,
const std::vector<OMAP>& omap_to_list,
const std::vector<OMAP>& omap_from_list) {
DWORD64 base_address =
::SymLoadModuleExW(process_,
NULL,
pdb_path.value().c_str(),
NULL,
1,
1,
NULL,
0);
ASSERT_NE(0, base_address);
OMAP* omap_to = NULL;
DWORD64 omap_to_length = 0;
OMAP* omap_from = NULL;
DWORD64 omap_from_length = 0;
ASSERT_TRUE(::SymGetOmaps(process_,
base_address,
&omap_to,
&omap_to_length,
&omap_from,
&omap_from_length));
ASSERT_EQ(omap_to_list.size(), omap_to_length);
EXPECT_EQ(0, memcmp(&omap_to_list[0], omap_to,
omap_to_list.size() * sizeof(OMAP)));
ASSERT_EQ(omap_from_list.size(), omap_from_length);
EXPECT_EQ(0, memcmp(&omap_from_list[0], omap_from,
omap_from_list.size() * sizeof(OMAP)));
EXPECT_TRUE(::SymUnloadModule64(process_, base_address));
}
protected:
HANDLE process_;
GUID new_guid_;
base::ScopedTempDir temp_dir_;
base::FilePath temp_pdb_file_path_;
base::FilePath temp_pdb_file_path2_;
};
class TestPdbStream : public PdbStream {
public:
TestPdbStream() : PdbStream(0), bytes_(NULL) {
}
template<typename T> TestPdbStream(const T& t)
: PdbStream(sizeof(T)),
bytes_(reinterpret_cast<const uint8*>(&t)) {
}
virtual bool ReadBytes(void* dest,
size_t count,
size_t* bytes_read) OVERRIDE {
if (pos() >= length())
return false;
size_t max_count = length() - pos();
*bytes_read = max_count < count ? max_count : count;
::memcpy(dest, bytes_ + pos(), *bytes_read);
Seek(pos() + *bytes_read);
return *bytes_read == count;
}
private:
const uint8* const bytes_;
};
// Comparison operator for PdbInfoHeader70 objects.
bool AreEqual(const PdbInfoHeader70& header1,
const PdbInfoHeader70& header2) {
return ::memcmp(&header1, &header2, sizeof(header1)) == 0;
}
} // namespace
TEST(PdbBitSetTest, ReadEmptyStream) {
scoped_refptr<PdbStream> stream(new TestPdbStream());
PdbBitSet bs;
EXPECT_FALSE(bs.Read(stream.get()));
}
TEST(PdbBitSetTest, SimpleMutators) {
PdbBitSet bs;
EXPECT_TRUE(bs.IsEmpty());
EXPECT_EQ(bs.size(), 0u);
bs.Resize(43);
EXPECT_EQ(bs.size(), 64u);
for (size_t i = 0; i < 64; ++i)
EXPECT_FALSE(bs.IsSet(i));
bs.Toggle(15);
EXPECT_TRUE(bs.IsSet(15));
bs.Toggle(15);
EXPECT_FALSE(bs.IsSet(15));
bs.Set(25);
EXPECT_TRUE(bs.IsSet(25));
bs.Clear(25);
EXPECT_FALSE(bs.IsSet(25));
for (size_t i = 0; i < 64; i += 10)
bs.Set(i);
for (size_t i = 0; i < 64; ++i)
EXPECT_EQ((i % 10) == 0, bs.IsSet(i));
}
TEST(PdbBitSetTest, ReadEmptyBitSet) {
const uint32 kSize = 0;
scoped_refptr<PdbStream> stream(new TestPdbStream(kSize));
PdbBitSet bs;
EXPECT_TRUE(bs.Read(stream.get()));
EXPECT_TRUE(bs.IsEmpty());
EXPECT_EQ(bs.size(), 0u);
}
TEST(PdbBitSetTest, ReadSingleDwordBitSet) {
const uint32 kData[] = { 1, (1<<0) | (1<<5) | (1<<13) };
scoped_refptr<PdbStream> stream(new TestPdbStream(kData));
PdbBitSet bs;
EXPECT_TRUE(bs.Read(stream.get()));
EXPECT_FALSE(bs.IsEmpty());
EXPECT_EQ(bs.size(), 32u);
for (size_t i = 0; i < bs.size(); ++i)
EXPECT_EQ(i == 0 || i == 5 || i == 13, bs.IsSet(i));
}
TEST(PdbBitSetTest, ReadMultiDwordBitSet) {
const uint32 kData[] = { 2, (1<<0) | (1<<5) | (1<<13), (1<<5) };
scoped_refptr<PdbStream> stream(new TestPdbStream(kData));
PdbBitSet bs;
EXPECT_TRUE(bs.Read(stream.get()));
EXPECT_FALSE(bs.IsEmpty());
EXPECT_EQ(bs.size(), 64u);
for (size_t i = 0; i < bs.size(); ++i)
EXPECT_EQ(i == 0 || i == 5 || i == 13 || i == (32 + 5), bs.IsSet(i));
}
TEST(PdbBitSetTest, WriteEmptyBitSet) {
const uint32 kData[] = { 0 };
scoped_refptr<PdbStream> stream(new TestPdbStream(kData));
PdbBitSet bs;
EXPECT_TRUE(bs.Read(stream.get()));
scoped_refptr<PdbByteStream> reader(new PdbByteStream());
scoped_refptr<WritablePdbStream> writer(reader->GetWritablePdbStream());
EXPECT_TRUE(bs.Write(writer.get(), true));
EXPECT_EQ(sizeof(kData), reader->length());
std::vector<uint32> data;
EXPECT_TRUE(reader->Read(&data, arraysize(kData)));
EXPECT_THAT(data, testing::ElementsAreArray(kData));
}
TEST(PdbBitSetTest, WriteEmptyBitSetWithoutSize) {
const uint32 kData[] = { 0 };
scoped_refptr<PdbStream> stream(new TestPdbStream(kData));
PdbBitSet bs;
EXPECT_TRUE(bs.Read(stream.get()));
scoped_refptr<PdbByteStream> reader(new PdbByteStream());
scoped_refptr<WritablePdbStream> writer(reader->GetWritablePdbStream());
EXPECT_TRUE(bs.Write(writer.get(), false));
EXPECT_EQ(0, reader->length());
}
TEST(PdbBitSetTest, WriteBitSet) {
const uint32 kData[] = { 2, (1<<0) | (1<<5) | (1<<13), (1<<5) };
scoped_refptr<PdbStream> stream(new TestPdbStream(kData));
PdbBitSet bs;
EXPECT_TRUE(bs.Read(stream.get()));
scoped_refptr<PdbByteStream> reader(new PdbByteStream());
scoped_refptr<WritablePdbStream> writer(reader->GetWritablePdbStream());
EXPECT_TRUE(bs.Write(writer.get(), true));
EXPECT_EQ(sizeof(kData), reader->length());
std::vector<uint32> data;
EXPECT_TRUE(reader->Read(&data, arraysize(kData)));
EXPECT_THAT(data, testing::ElementsAreArray(kData));
}
TEST(PdbBitSetTest, WriteBitSetWithoutSize) {
const uint32 kInputData[] = { 2, (1 << 0) | (1 << 5) | (1 << 13), (1 << 5) };
const uint32 kExpectedData[] = { (1 << 0) | (1 << 5) | (1 << 13), (1 << 5) };
scoped_refptr<PdbStream> stream(new TestPdbStream(kInputData));
PdbBitSet bs;
EXPECT_TRUE(bs.Read(stream.get()));
scoped_refptr<PdbByteStream> reader(new PdbByteStream());
scoped_refptr<WritablePdbStream> writer(reader->GetWritablePdbStream());
EXPECT_TRUE(bs.Write(writer.get(), false));
EXPECT_EQ(sizeof(kExpectedData), reader->length());
std::vector<uint32> data;
EXPECT_TRUE(reader->Read(&data, arraysize(kExpectedData)));
EXPECT_THAT(data, testing::ElementsAreArray(kExpectedData));
}
TEST_F(PdbUtilTest, HashString) {
EXPECT_EQ(1024, HashString(""));
EXPECT_EQ(20527, HashString("___onexitend"));
EXPECT_EQ(24736, HashString("__imp____getmainargs"));
EXPECT_EQ(61647, HashString("___security_cookie"));
}
TEST_F(PdbUtilTest, GetDbiDbgHeaderOffsetTestDll) {
// Test the test_dll.dll.pdb doesn't have Omap information.
PdbReader reader;
PdbFile pdb_file;
EXPECT_TRUE(reader.Read(
testing::GetSrcRelativePath(testing::kTestPdbFilePath),
&pdb_file));
PdbStream* dbi_stream = pdb_file.GetStream(kDbiStream);
DbiHeader dbi_header;
EXPECT_TRUE(dbi_stream->Read(&dbi_header, 1));
uint32 offset = GetDbiDbgHeaderOffset(dbi_header);
EXPECT_LE(offset, dbi_stream->length() - sizeof(DbiDbgHeader));
EXPECT_TRUE(dbi_stream->Seek(offset));
DbiDbgHeader dbi_dbg_header;
EXPECT_TRUE(dbi_stream->Read(&dbi_dbg_header, 1));
EXPECT_EQ(-1, dbi_dbg_header.omap_to_src);
EXPECT_EQ(-1, dbi_dbg_header.omap_from_src);
}
TEST_F(PdbUtilTest, GetDbiDbgHeaderOffsetOmappedTestDll) {
// Test that omapped_test_dll.dll.pdb does have Omap information.
PdbReader reader;
PdbFile pdb_file;
EXPECT_TRUE(reader.Read(
testing::GetSrcRelativePath(testing::kOmappedTestPdbFilePath),
&pdb_file));
PdbStream* dbi_stream = pdb_file.GetStream(kDbiStream);
DbiHeader dbi_header;
EXPECT_TRUE(dbi_stream->Read(&dbi_header, 1));
uint32 offset = GetDbiDbgHeaderOffset(dbi_header);
EXPECT_LE(offset, dbi_stream->length() - sizeof(DbiDbgHeader));
EXPECT_TRUE(dbi_stream->Seek(offset));
DbiDbgHeader dbi_dbg_header;
EXPECT_TRUE(dbi_stream->Read(&dbi_dbg_header, 1));
EXPECT_NE(-1, dbi_dbg_header.omap_to_src);
EXPECT_NE(-1, dbi_dbg_header.omap_from_src);
}
TEST_F(PdbUtilTest, TestDllHasNoOmap) {
// Test that test_dll.dll.pdb has no Omap information.
base::FilePath test_pdb_file_path = testing::GetSrcRelativePath(
testing::kTestPdbFilePath);
DWORD64 base_address =
::SymLoadModuleExW(process_,
NULL,
test_pdb_file_path.value().c_str(),
NULL,
1,
1,
NULL,
0);
EXPECT_NE(0, base_address);
OMAP* omap_to = NULL;
DWORD64 omap_to_length = 0;
OMAP* omap_from = NULL;
DWORD64 omap_from_length = 0;
EXPECT_FALSE(::SymGetOmaps(process_,
base_address,
&omap_to,
&omap_to_length,
&omap_from,
&omap_from_length));
EXPECT_TRUE(::SymUnloadModule64(process_, base_address));
}
TEST_F(PdbUtilTest, SetOmapToAndFromStream) {
// Add Omap information to test_dll.pdb and test that the output file
// has Omap information.
std::vector<OMAP> omap_to_list(kOmapToData,
kOmapToData + arraysize(kOmapToData));
std::vector<OMAP> omap_from_list(kOmapFromData,
kOmapFromData + arraysize(kOmapFromData));
base::FilePath test_pdb_file_path = testing::GetSrcRelativePath(
testing::kTestPdbFilePath);
PdbReader pdb_reader;
PdbFile pdb_file;
ASSERT_TRUE(pdb_reader.Read(test_pdb_file_path, &pdb_file));
EXPECT_TRUE(SetOmapToStream(omap_to_list, &pdb_file));
EXPECT_TRUE(SetOmapFromStream(omap_from_list, &pdb_file));
PdbWriter pdb_writer;
ASSERT_TRUE(pdb_writer.Write(temp_pdb_file_path_, pdb_file));
VerifyOmapData(temp_pdb_file_path_,
omap_to_list,
omap_from_list);
}
TEST_F(PdbUtilTest, PdbHeaderMatchesImageDebugDirectory) {
PdbReader reader;
PdbFile pdb_file;
EXPECT_TRUE(reader.Read(
testing::GetSrcRelativePath(testing::kTestPdbFilePath),
&pdb_file));
PdbInfoHeader70 header = { 0 };
ASSERT_GE(pdb_file.StreamCount(), kPdbHeaderInfoStream);
ASSERT_TRUE(pdb_file.GetStream(kPdbHeaderInfoStream) != NULL);
EXPECT_TRUE(pdb_file.GetStream(kPdbHeaderInfoStream)->Read(&header, 1));
EXPECT_EQ(header.version, kPdbCurrentVersion);
base::NativeLibraryLoadError error;
base::NativeLibrary test_dll =
base::LoadNativeLibrary(
testing::GetSrcRelativePath(testing::kTestDllFilePath),
&error);
ASSERT_TRUE(test_dll != NULL);
// Make sure the DLL is unloaded on exit.
base::ScopedNativeLibrary test_dll_keeper(test_dll);
base::win::PEImage image(test_dll);
// Retrieve the NT headers to make it easy to look at them in debugger.
const IMAGE_NT_HEADERS* nt_headers = image.GetNTHeaders();
ASSERT_EQ(sizeof(IMAGE_DEBUG_DIRECTORY),
image.GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_DEBUG));
const IMAGE_DEBUG_DIRECTORY* debug_directory =
reinterpret_cast<const IMAGE_DEBUG_DIRECTORY*>(
image.GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_DEBUG));
ASSERT_EQ(IMAGE_DEBUG_TYPE_CODEVIEW, debug_directory->Type);
ASSERT_GE(debug_directory->SizeOfData, sizeof(pe::CvInfoPdb70));
const pe::CvInfoPdb70* cv_info =
reinterpret_cast<const pe::CvInfoPdb70*>(
image.RVAToAddr(debug_directory->AddressOfRawData));
ASSERT_EQ(pe::kPdb70Signature, cv_info->cv_signature);
ASSERT_EQ(header.signature, cv_info->signature);
ASSERT_EQ(header.pdb_age, cv_info->pdb_age);
}
TEST_F(PdbUtilTest, ReadPdbHeader) {
const base::FilePath pdb_path = testing::GetSrcRelativePath(
testing::kTestPdbFilePath);
PdbInfoHeader70 pdb_header = {};
EXPECT_TRUE(ReadPdbHeader(pdb_path, &pdb_header));
}
TEST(EnsureStreamWritableTest, DoesNothingWhenAlreadyWritable) {
PdbFile pdb_file;
scoped_refptr<PdbStream> stream = new PdbByteStream();
size_t index = pdb_file.AppendStream(stream.get());
EXPECT_TRUE(EnsureStreamWritable(index, &pdb_file));
scoped_refptr<PdbStream> stream2 = pdb_file.GetStream(index);
EXPECT_EQ(stream.get(), stream2.get());
}
TEST(EnsureStreamWritableTest, WorksWhenReadOnly) {
PdbFile pdb_file;
scoped_refptr<PdbStream> stream = new TestPdbStream();
size_t index = pdb_file.AppendStream(stream.get());
EXPECT_TRUE(EnsureStreamWritable(index, &pdb_file));
scoped_refptr<PdbStream> stream2 = pdb_file.GetStream(index);
EXPECT_TRUE(stream2.get() != NULL);
EXPECT_NE(stream.get(), stream2.get());
EXPECT_TRUE(stream2->GetWritablePdbStream() != NULL);
}
TEST(EnsureStreamWritableTest, FailsWhenNonExistent) {
PdbFile pdb_file;
EXPECT_FALSE(EnsureStreamWritable(45, &pdb_file));
}
TEST(SetGuidTest, FailsWhenStreamsDoNotExist) {
PdbFile pdb_file;
// Leave the Pdb header missing.
pdb_file.SetStream(kPdbHeaderInfoStream, NULL);
pdb_file.SetStream(kDbiStream, new TestPdbStream(kSampleDbiHeader));
EXPECT_FALSE(SetGuid(kSampleGuid, &pdb_file));
// Add the header stream, but leave the Dbi header missing.
pdb_file.SetStream(kPdbHeaderInfoStream, new TestPdbStream(kSamplePdbHeader));
pdb_file.SetStream(kDbiStream, NULL);
EXPECT_FALSE(SetGuid(kSampleGuid, &pdb_file));
}
TEST(SetGuidTest, FailsWhenStreamsAreTooShort) {
PdbFile pdb_file;
const uint8 kByte = 6;
pdb_file.SetStream(kPdbHeaderInfoStream, new TestPdbStream(kByte));
pdb_file.SetStream(kDbiStream, new TestPdbStream(kSampleDbiHeader));
EXPECT_FALSE(SetGuid(kSampleGuid, &pdb_file));
pdb_file.SetStream(kPdbHeaderInfoStream, new TestPdbStream(kSamplePdbHeader));
pdb_file.SetStream(kDbiStream, new TestPdbStream(kByte));
EXPECT_FALSE(SetGuid(kSampleGuid, &pdb_file));
}
TEST(SetGuidTest, Succeeds) {
PdbFile pdb_file;
pdb_file.SetStream(kPdbHeaderInfoStream, new TestPdbStream(kSamplePdbHeader));
pdb_file.SetStream(kDbiStream, new TestPdbStream(kSampleDbiHeader));
scoped_refptr<PdbStream> stream =
pdb_file.GetStream(kPdbHeaderInfoStream);
ASSERT_TRUE(stream.get() != NULL);
ASSERT_EQ(stream->length(), sizeof(PdbInfoHeader70));
uint32 time1 = static_cast<uint32>(time(NULL));
EXPECT_TRUE(SetGuid(kSampleGuid, &pdb_file));
uint32 time2 = static_cast<uint32>(time(NULL));
// Read the new header.
PdbInfoHeader70 pdb_header = {};
stream = pdb_file.GetStream(kPdbHeaderInfoStream);
EXPECT_TRUE(stream->Seek(0));
EXPECT_TRUE(stream->Read(&pdb_header, 1));
// Validate that the fields are as expected.
EXPECT_LE(time1, pdb_header.timestamp);
EXPECT_LE(pdb_header.timestamp, time2);
EXPECT_EQ(1u, pdb_header.pdb_age);
EXPECT_EQ(kSampleGuid, pdb_header.signature);
DbiHeader dbi_header = {};
stream = pdb_file.GetStream(kDbiStream);
ASSERT_TRUE(stream.get() != NULL);
ASSERT_EQ(stream->length(), sizeof(dbi_header));
EXPECT_TRUE(stream->Seek(0));
EXPECT_TRUE(stream->Read(&dbi_header, 1));
EXPECT_EQ(1u, dbi_header.age);
}
TEST(ReadHeaderInfoStreamTest, ReadFromPdbFile) {
const base::FilePath pdb_path = testing::GetSrcRelativePath(
testing::kTestPdbFilePath);
PdbFile pdb_file;
PdbReader pdb_reader;
ASSERT_TRUE(pdb_reader.Read(pdb_path, &pdb_file));
PdbInfoHeader70 pdb_header = {};
NameStreamMap name_stream_map;
EXPECT_TRUE(ReadHeaderInfoStream(pdb_file, &pdb_header, &name_stream_map));
}
TEST(ReadHeaderInfoStreamTest, ReadEmptyStream) {
scoped_refptr<PdbStream> stream(new TestPdbStream());
PdbInfoHeader70 pdb_header = {};
NameStreamMap name_stream_map;
EXPECT_FALSE(ReadHeaderInfoStream(stream, &pdb_header, &name_stream_map));
}
TEST(ReadHeaderInfoStreamTest, ReadStreamWithOnlyHeader) {
scoped_refptr<PdbStream> reader(new PdbByteStream());
scoped_refptr<WritablePdbStream> writer(reader->GetWritablePdbStream());
PdbInfoHeader70 pdb_header = {};
ASSERT_TRUE(writer->Write(pdb_header));
NameStreamMap name_stream_map;
EXPECT_FALSE(ReadHeaderInfoStream(reader, &pdb_header, &name_stream_map));
}
TEST(ReadHeaderInfoStreamTest, ReadStreamWithEmptyNameStreamMap) {
scoped_refptr<PdbStream> reader(new PdbByteStream());
scoped_refptr<WritablePdbStream> writer(reader->GetWritablePdbStream());
PdbInfoHeader70 pdb_header = {};
ASSERT_TRUE(writer->Write(pdb_header));
ASSERT_TRUE(writer->Write(static_cast<uint32>(0))); // total string length.
ASSERT_TRUE(writer->Write(static_cast<uint32>(0))); // number of names.
ASSERT_TRUE(writer->Write(static_cast<uint32>(0))); // size of bitsets.
ASSERT_TRUE(writer->Write(static_cast<uint32>(0))); // first bitset.
ASSERT_TRUE(writer->Write(static_cast<uint32>(0))); // second bitset.
NameStreamMap name_stream_map;
EXPECT_TRUE(ReadHeaderInfoStream(reader, &pdb_header, &name_stream_map));
EXPECT_EQ(name_stream_map.size(), 0u);
}
TEST(ReadHeaderInfoStreamTest, ReadStreamWithNameStreamMap) {
scoped_refptr<PdbStream> reader(new PdbByteStream());
scoped_refptr<WritablePdbStream> writer(reader->GetWritablePdbStream());
PdbInfoHeader70 pdb_header = {};
ASSERT_TRUE(writer->Write(pdb_header));
ASSERT_TRUE(writer->Write(static_cast<uint32>(9))); // total string length.
uint32 offset1 = writer->pos();
ASSERT_TRUE(writer->Write(3, "/a")); // name 1.
uint32 offset2 = writer->pos();
ASSERT_TRUE(writer->Write(3, "/b")); // name 2.
uint32 offset3 = writer->pos();
ASSERT_TRUE(writer->Write(3, "/c")); // name 3.
ASSERT_TRUE(writer->Write(static_cast<uint32>(3))); // number of names.
ASSERT_TRUE(writer->Write(static_cast<uint32>(3))); // size of bitsets.
PdbBitSet present;
present.Resize(3);
present.Set(0);
present.Set(1);
present.Set(2);
ASSERT_TRUE(present.Write(writer, true));
ASSERT_TRUE(writer->Write(static_cast<uint32>(0))); // second bitset.
ASSERT_TRUE(writer->Write(0));
ASSERT_TRUE(writer->Write(static_cast<uint32>(42)));
ASSERT_TRUE(writer->Write(offset2 - offset1));
ASSERT_TRUE(writer->Write(static_cast<uint32>(7)));
ASSERT_TRUE(writer->Write(offset3 - offset1));
ASSERT_TRUE(writer->Write(static_cast<uint32>(95)));
NameStreamMap name_stream_map;
EXPECT_TRUE(ReadHeaderInfoStream(reader, &pdb_header, &name_stream_map));
NameStreamMap expected;
expected["/a"] = 42;
expected["/b"] = 7;
expected["/c"] = 95;
EXPECT_THAT(name_stream_map, testing::ContainerEq(expected));
}
TEST(ReadHeaderInfoStreamTest, ReadFromPdb) {
const base::FilePath pdb_path = testing::GetSrcRelativePath(
testing::kTestPdbFilePath);
PdbFile pdb_file;
PdbReader pdb_reader;
EXPECT_TRUE(pdb_reader.Read(pdb_path, &pdb_file));
PdbInfoHeader70 pdb_header = {};
NameStreamMap name_stream_map;
EXPECT_TRUE(ReadHeaderInfoStream(pdb_file.GetStream(kPdbHeaderInfoStream),
&pdb_header,
&name_stream_map));
}
TEST(WriteHeaderInfoStreamTest, WriteToPdbFile) {
const base::FilePath pdb_path = testing::GetSrcRelativePath(
testing::kTestPdbFilePath);
PdbFile pdb_file;
PdbReader pdb_reader;
ASSERT_TRUE(pdb_reader.Read(pdb_path, &pdb_file));
PdbInfoHeader70 pdb_header = {};
NameStreamMap name_stream_map;
ASSERT_TRUE(ReadHeaderInfoStream(pdb_file, &pdb_header, &name_stream_map));
pdb_header.pdb_age++;
name_stream_map["NewStream!"] = 999;
EXPECT_TRUE(WriteHeaderInfoStream(pdb_header, name_stream_map, &pdb_file));
PdbInfoHeader70 pdb_header2 = {};
NameStreamMap name_stream_map2;
ASSERT_TRUE(ReadHeaderInfoStream(pdb_file, &pdb_header2, &name_stream_map2));
EXPECT_TRUE(AreEqual(pdb_header, pdb_header2));
EXPECT_EQ(name_stream_map, name_stream_map2);
}
TEST(WriteHeaderInfoStreamTest, WriteEmpty) {
scoped_refptr<PdbStream> reader(new PdbByteStream());
scoped_refptr<WritablePdbStream> writer(reader->GetWritablePdbStream());
NameStreamMap name_stream_map;
EXPECT_TRUE(WriteHeaderInfoStream(kSamplePdbHeader,
name_stream_map,
writer.get()));
PdbInfoHeader70 read_pdb_header = {};
NameStreamMap read_name_stream_map;
EXPECT_TRUE(ReadHeaderInfoStream(reader.get(),
&read_pdb_header,
&read_name_stream_map));
EXPECT_EQ(0, ::memcmp(&kSamplePdbHeader,
&read_pdb_header,
sizeof(kSamplePdbHeader)));
EXPECT_THAT(name_stream_map, testing::ContainerEq(read_name_stream_map));
}
TEST(WriteHeaderInfoStreamTest, WriteNonEmpty) {
scoped_refptr<PdbStream> reader(new PdbByteStream());
scoped_refptr<WritablePdbStream> writer(reader->GetWritablePdbStream());
NameStreamMap name_stream_map;
name_stream_map["/StreamFoo"] = 9;
name_stream_map["/StreamBar"] = 42;
name_stream_map["/Stream/With/A/Path"] = 19;
EXPECT_TRUE(WriteHeaderInfoStream(kSamplePdbHeader,
name_stream_map,
writer.get()));
PdbInfoHeader70 read_pdb_header = {};
NameStreamMap read_name_stream_map;
EXPECT_TRUE(ReadHeaderInfoStream(reader.get(),
&read_pdb_header,
&read_name_stream_map));
EXPECT_EQ(0, ::memcmp(&kSamplePdbHeader,
&read_pdb_header,
sizeof(kSamplePdbHeader)));
EXPECT_THAT(name_stream_map, testing::ContainerEq(read_name_stream_map));
}
TEST_F(PdbUtilTest, NamedStreamsWorkWithPdbStr) {
// We start by creating a PDB file (a copy of a checked in sample one) and
// adding a new stream to it using our named-stream implementation.
{
base::FilePath orig_pdb_path = testing::GetSrcRelativePath(
testing::kTestPdbFilePath);
// Read the sample PDB.
PdbReader pdb_reader;
PdbFile pdb_file;
ASSERT_TRUE(pdb_reader.Read(orig_pdb_path, &pdb_file));
// Add a new stream to it.
scoped_refptr<PdbStream> foo_reader(new PdbByteStream());
scoped_refptr<WritablePdbStream> foo_writer(
foo_reader->GetWritablePdbStream());
size_t foo_index = pdb_file.AppendStream(foo_reader.get());
foo_writer->WriteString("foo");
// Get the PDB header stream.
scoped_refptr<PdbStream> header_stream(pdb_file.GetStream(
kPdbHeaderInfoStream));
ASSERT_TRUE(header_stream.get() != NULL);
// Read the existing name-stream map.
PdbInfoHeader70 pdb_header = {};
NameStreamMap name_stream_map;
ASSERT_TRUE(ReadHeaderInfoStream(
header_stream, &pdb_header, &name_stream_map));
// Add an entry for the new stream.
name_stream_map["foo"] = foo_index;
// Write the new header stream to it.
scoped_refptr<PdbStream> new_header_reader(new PdbByteStream());
scoped_refptr<WritablePdbStream> new_header_writer(
new_header_reader->GetWritablePdbStream());
ASSERT_TRUE(pdb::WriteHeaderInfoStream(
pdb_header, name_stream_map, new_header_writer));
pdb_file.ReplaceStream(kPdbHeaderInfoStream, new_header_reader);
// Write the PDB.
PdbWriter pdb_writer;
ASSERT_TRUE(pdb_writer.Write(temp_pdb_file_path_, pdb_file));
}
// We've now created a new PDB file. We want to make sure that pdbstr.exe
// plays nicely with our named streams by doing a few things:
// (1) If we try to read a non-existing stream, we should get empty output.
// (2) We should be able to read an existing stream and get non-empty output.
// (3) We should be able to add a new stream, and then read it using our
// mechanisms.
// Get the path to pdbstr.exe, which we redistribute in third_party.
base::FilePath pdbstr_path =
testing::GetSrcRelativePath(testing::kPdbStrPath);
// Create the argument specifying the PDB path.
std::string pdb_arg = base::WideToUTF8(temp_pdb_file_path_.value());
pdb_arg.insert(0, "-p:");
// First test: try to read a non-existing stream. Should produce no output.
{
CommandLine cmd(pdbstr_path);
cmd.AppendArg(pdb_arg);
cmd.AppendArg("-r");
cmd.AppendArg("-s:nonexistent-stream-name");
std::string output;
ASSERT_TRUE(base::GetAppOutput(cmd, &output));
ASSERT_TRUE(output.empty());
}
// Second test: read an existing stream (the one we just added). Should
// exit without error and return the expected contents (with a trailing
// newline).
{
CommandLine cmd(pdbstr_path);
cmd.AppendArg(pdb_arg);
cmd.AppendArg("-r");
cmd.AppendArg("-s:foo");
std::string output;
ASSERT_TRUE(base::GetAppOutput(cmd, &output));
ASSERT_EQ(std::string("foo\r\n"), output);
}
// Third test: Add another new stream. This should return without error, and
// we should then be able to read the stream using our mechanisms.
{
base::FilePath bar_txt = temp_dir_.path().Append(L"bar.txt");
base::ScopedFILE bar_file(base::OpenFile(
bar_txt, "wb"));
fprintf(bar_file.get(), "bar");
bar_file.reset();
std::string bar_arg = base::WideToUTF8(bar_txt.value());
bar_arg.insert(0, "-i:");
CommandLine cmd(pdbstr_path);
cmd.AppendArg(pdb_arg);
cmd.AppendArg("-w");
cmd.AppendArg("-s:bar");
cmd.AppendArg(bar_arg);
std::string output;
ASSERT_TRUE(base::GetAppOutput(cmd, &output));
ASSERT_TRUE(output.empty());
PdbFile pdb_file;
PdbReader pdb_reader;
ASSERT_TRUE(pdb_reader.Read(temp_pdb_file_path_, &pdb_file));
// Get the PDB header stream.
scoped_refptr<PdbStream> header_stream(pdb_file.GetStream(
kPdbHeaderInfoStream));
ASSERT_TRUE(header_stream.get() != NULL);
// Read the existing name-stream map.
PdbInfoHeader70 pdb_header = {};
NameStreamMap name_stream_map;
ASSERT_TRUE(ReadHeaderInfoStream(
header_stream, &pdb_header, &name_stream_map));
// There should be a 'bar' stream.
ASSERT_TRUE(name_stream_map.count("bar"));
// Get the bar stream.
scoped_refptr<PdbStream> bar_stream(pdb_file.GetStream(
name_stream_map["bar"]));
ASSERT_TRUE(bar_stream.get() != NULL);
// Read all of the data and ensure it is as expected.
bar_stream->Seek(0);
std::string bar_data;
bar_data.resize(bar_stream->length());
ASSERT_TRUE(bar_stream->Read(&bar_data.at(0), bar_data.size()));
ASSERT_EQ("bar", bar_data);
}
}
TEST_F(PdbUtilTest, LoadNamedStreamFromPdbFile) {
PdbReader reader;
PdbFile pdb_file;
EXPECT_TRUE(reader.Read(
testing::GetOutputRelativePath(testing::kTestDllPdbName),
&pdb_file));
scoped_refptr<PdbStream> stream;
EXPECT_TRUE(LoadNamedStreamFromPdbFile(
"StreamThatDoesNotExist", &pdb_file, &stream));
EXPECT_TRUE(stream.get() == NULL);
// The MSVC toolchain produces a handful of named streams whose existence we
// can rely on.
EXPECT_TRUE(LoadNamedStreamFromPdbFile("/LinkInfo", &pdb_file, &stream));
ASSERT_TRUE(stream.get() != NULL);
}
} // namespace pdb