1 : // Copyright 2012 Google Inc. All Rights Reserved.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : #include "syzygy/pdb/pdb_writer.h"
16 :
17 : #include <algorithm>
18 : #include <cstdio>
19 : #include <cstring>
20 : #include <string>
21 : #include <vector>
22 :
23 : #include "base/command_line.h"
24 : #include "base/files/scoped_temp_dir.h"
25 : #include "base/process/launch.h"
26 : #include "base/strings/utf_string_conversions.h"
27 : #include "gtest/gtest.h"
28 : #include "syzygy/msf/unittest_util.h"
29 : #include "syzygy/pdb/pdb_file.h"
30 : #include "syzygy/pdb/pdb_reader.h"
31 : #include "syzygy/pdb/pdb_stream.h"
32 : #include "syzygy/pdb/unittest_util.h"
33 : #include "syzygy/pe/unittest_util.h"
34 :
35 : namespace pdb {
36 :
37 : namespace {
38 :
39 : class TestPdbWriter : public PdbWriter {
40 : public:
41 : TestPdbWriter() {
42 : }
43 :
44 : ~TestPdbWriter() {
45 : }
46 :
47 : using PdbWriter::AppendStream;
48 : };
49 :
50 : class TestPdbStream : public PdbStream {
51 : public:
52 E : TestPdbStream(uint32_t length, uint32_t mask)
53 E : : PdbStream(length), data_(length) {
54 E : uint32_t* data = reinterpret_cast<uint32_t*>(data_.data());
55 :
56 : // Just to make sure the data is non-repeating (so we can distinguish if it
57 : // has been correctly written or not) fill it with integers encoding their
58 : // own position in the stream.
59 E : for (size_t i = 0; i < data_.size() / sizeof(data[0]); ++i)
60 E : data[i] = i | mask;
61 E : }
62 :
63 E : bool ReadBytesAt(size_t pos, size_t count, void* dest) override {
64 E : DCHECK(dest != NULL);
65 :
66 E : if (count > length() - pos)
67 i : return false;
68 :
69 E : ::memcpy(dest, data_.data() + pos, count);
70 :
71 E : return true;
72 E : }
73 :
74 E : const std::vector<uint8_t> data() const { return data_; }
75 :
76 : private:
77 : std::vector<uint8_t> data_;
78 : };
79 :
80 : } // namespace
81 :
82 E : TEST(PdbWriterTest, PdbStrCompatible) {
83 : base::FilePath test_dll_msf =
84 E : testing::GetSrcRelativePath(testing::kTestPdbFilePath);
85 :
86 E : PdbFile file;
87 E : PdbReader reader;
88 E : ASSERT_TRUE(reader.Read(test_dll_msf, &file));
89 :
90 : // We need at least 8 MB of data in the DLL to ensure that the free page map
91 : // requires a second page. We manually add data to it until we get to that
92 : // point.
93 E : int64_t test_dll_msf_length = 0;
94 E : ASSERT_TRUE(base::GetFileSize(test_dll_msf, &test_dll_msf_length));
95 E : while (test_dll_msf_length < 9 * 1024 * 1024) {
96 E : file.AppendStream(new TestPdbStream(1024 * 1024, file.StreamCount()));
97 E : test_dll_msf_length += 1024 * 1024;
98 E : }
99 :
100 : // Write the Syzygy modified PDB to disk.
101 E : base::ScopedTempDir temp_dir;
102 E : ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
103 E : base::FilePath msf_path = temp_dir.path().Append(testing::kTestPdbFilePath);
104 E : ASSERT_TRUE(base::CreateDirectory(msf_path.DirName()));
105 E : PdbWriter writer;
106 E : ASSERT_TRUE(writer.Write(msf_path, file));
107 :
108 : // Write a new stream to disk.
109 E : base::FilePath stream_path = temp_dir.path().AppendASCII("new_stream.dat");
110 E : scoped_refptr<TestPdbStream> new_stream(new TestPdbStream(1024 * 1024, 0xff));
111 : {
112 E : base::ScopedFILE stream_file(base::OpenFile(stream_path, "wb"));
113 E : ASSERT_TRUE(stream_file.get() != NULL);
114 E : ASSERT_EQ(new_stream->data().size(),
115 : ::fwrite(new_stream->data().data(), sizeof(new_stream->data()[0]),
116 E : new_stream->data().size(), stream_file.get()));
117 E : }
118 :
119 : // Get the path to pdbstr.exe, which we redistribute in third_party.
120 : base::FilePath msfstr_path =
121 E : testing::GetSrcRelativePath(testing::kPdbStrPath);
122 :
123 : // Create the arguments to pdbstr.
124 E : std::string msf_arg = base::WideToUTF8(msf_path.value());
125 E : msf_arg.insert(0, "-p:");
126 E : std::string stream_arg = base::WideToUTF8(stream_path.value());
127 E : stream_arg.insert(0, "-i:");
128 :
129 : // Add a new stream to the PDB in place. This should produce no output.
130 : {
131 E : base::CommandLine cmd(msfstr_path);
132 E : cmd.AppendArg(msf_arg);
133 E : cmd.AppendArg(stream_arg);
134 E : cmd.AppendArg("-w");
135 E : cmd.AppendArg("-s:nonexistent-stream-name");
136 :
137 E : std::string output;
138 E : ASSERT_TRUE(base::GetAppOutput(cmd, &output));
139 E : ASSERT_TRUE(output.empty());
140 E : }
141 :
142 : // Read the pdbstr modified PDB.
143 E : PdbFile file_read;
144 E : ASSERT_TRUE(reader.Read(msf_path, &file_read));
145 :
146 : // Add the new stream to the original PDB.
147 E : file.AppendStream(new_stream.get());
148 :
149 : // Clear stream 0 (the previous directory) and stream 1 (the PDB header
150 : // stream). These can vary but be functionally equivalent. We only care about
151 : // the actual content streams, which are the rest of them.
152 E : scoped_refptr<PdbStream> empty_stream(new TestPdbStream(0, 0));
153 E : file.ReplaceStream(0, empty_stream.get());
154 E : file.ReplaceStream(1, empty_stream.get());
155 E : file_read.ReplaceStream(0, empty_stream.get());
156 E : file_read.ReplaceStream(1, empty_stream.get());
157 :
158 : // Ensure that the two PDBs are identical.
159 E : ASSERT_NO_FATAL_FAILURE(
160 E : testing::EnsureMsfContentsAreIdentical(file, file_read));
161 E : }
162 :
163 : } // namespace pdb
|