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 "base/command_line.h"
18 : #include "base/file_util.h"
19 : #include "base/process_util.h"
20 : #include "base/utf_string_conversions.h"
21 : #include "base/files/scoped_temp_dir.h"
22 : #include "gmock/gmock.h"
23 : #include "gtest/gtest.h"
24 : #include "syzygy/core/unittest_util.h"
25 : #include "syzygy/pdb/pdb_constants.h"
26 : #include "syzygy/pdb/pdb_data.h"
27 : #include "syzygy/pdb/pdb_reader.h"
28 : #include "syzygy/pdb/pdb_util.h"
29 : #include "syzygy/pdb/unittest_util.h"
30 : #include "syzygy/pe/unittest_util.h"
31 :
32 : namespace pdb {
33 :
34 : namespace {
35 :
36 : uint32 GetNumPages(uint32 num_bytes) {
37 : return (num_bytes + pdb::kPdbPageSize - 1) / pdb::kPdbPageSize;
38 : }
39 :
40 : class TestPdbWriter : public PdbWriter {
41 : public:
42 E : TestPdbWriter() {
43 E : file_.reset(file_util::CreateAndOpenTemporaryFile(&path_));
44 E : EXPECT_TRUE(file_.get() != NULL);
45 E : }
46 :
47 E : ~TestPdbWriter() {
48 E : if (file_.get()) {
49 i : fclose(file_.get());
50 i : file_.reset();
51 : }
52 E : file_util::Delete(path_, false);
53 E : }
54 :
55 E : file_util::ScopedFILE& file() { return file_; }
56 :
57 : using PdbWriter::AppendStream;
58 : using PdbWriter::WriteHeader;
59 :
60 : base::FilePath path_;
61 : };
62 :
63 : class TestPdbStream : public PdbStream {
64 : public:
65 E : TestPdbStream(uint32 length, uint32 mask)
66 : : PdbStream(length), data_(length) {
67 E : uint32* data = reinterpret_cast<uint32*>(data_.data());
68 :
69 : // Just to make sure the data is non-repeating (so we can distinguish if it
70 : // has been correctly written or not) fill it with integers encoding their
71 : // own position in the stream.
72 E : for (size_t i = 0; i < data_.size() / sizeof(data[0]); ++i)
73 E : data[i] = i | mask;
74 E : }
75 :
76 E : bool ReadBytes(void* dest, size_t count, size_t* bytes_read) {
77 E : DCHECK(bytes_read != NULL);
78 :
79 E : if (pos() == length()) {
80 i : *bytes_read = 0;
81 i : return true;
82 : }
83 :
84 E : count = std::min(count, length() - pos());
85 E : ::memcpy(dest, data_.data() + pos(), count);
86 E : Seek(pos() + count);
87 E : *bytes_read = count;
88 :
89 E : return true;
90 E : }
91 :
92 E : const std::vector<uint8> data() const { return data_; }
93 :
94 : private:
95 : std::vector<uint8> data_;
96 : };
97 :
98 : void EnsurePdbContentsAreIdentical(const PdbFile& pdb_file,
99 E : const PdbFile& pdb_file_read) {
100 E : ASSERT_EQ(pdb_file.StreamCount(), pdb_file_read.StreamCount());
101 :
102 E : for (size_t i = 0; i < pdb_file.StreamCount(); ++i) {
103 E : PdbStream* stream = pdb_file.GetStream(i);
104 E : PdbStream* stream_read = pdb_file_read.GetStream(i);
105 :
106 E : ASSERT_TRUE(stream != NULL);
107 E : ASSERT_TRUE(stream_read != NULL);
108 :
109 E : ASSERT_EQ(stream->length(), stream_read->length());
110 :
111 E : std::vector<uint8> data;
112 E : std::vector<uint8> data_read;
113 E : ASSERT_TRUE(stream->Seek(0));
114 E : ASSERT_TRUE(stream_read->Seek(0));
115 E : ASSERT_TRUE(stream->Read(&data, stream->length()));
116 E : ASSERT_TRUE(stream_read->Read(&data_read, stream_read->length()));
117 :
118 : // We don't use ContainerEq because upon failure this generates a
119 : // ridiculously long and useless error message. We don't use memcmp because
120 : // it doesn't given any context as to where the failure occurs.
121 E : for (size_t j = 0; j < data.size(); ++j)
122 E : ASSERT_EQ(data[j], data_read[j]);
123 E : }
124 E : }
125 :
126 : } // namespace
127 :
128 : using pdb::kPdbHeaderMagicString;
129 : using pdb::kPdbPageSize;
130 : using pdb::PdbHeader;
131 : using pdb::PdbReader;
132 :
133 E : TEST(PdbWriterTest, AppendStream) {
134 E : TestPdbWriter writer;
135 :
136 E : testing::ScopedTempFile temp_file;
137 E : writer.file().reset(file_util::OpenFile(temp_file.path(), "wb"));
138 E : ASSERT_TRUE(writer.file().get() != NULL);
139 :
140 : scoped_refptr<PdbStream> stream(
141 E : new TestPdbStream(4 * kPdbPageSize, 0));
142 :
143 : // Test writing a stream that will force allocation of the free page map
144 : // pages.
145 E : std::vector<uint32> pages_written;
146 E : uint32 page_count = 0;
147 E : EXPECT_TRUE(writer.AppendStream(stream.get(), &pages_written, &page_count));
148 E : writer.file().reset();
149 :
150 : // We expect pages_written to contain 4 pages, like the stream. However, we
151 : // expect page_count to have 2 more pages for the free page map.
152 E : uint32 expected_pages_written[] = { 0, 3, 4, 5 };
153 : EXPECT_THAT(pages_written,
154 E : ::testing::ElementsAreArray(expected_pages_written));
155 E : EXPECT_EQ(page_count, 6);
156 :
157 : // Build the expected stream contents. Two blank pages should have been
158 : // reserved by the append stream routine.
159 E : stream->Seek(0);
160 E : std::vector<uint8> expected_contents(6 * kPdbPageSize);
161 E : ASSERT_TRUE(stream->Read(expected_contents.data(), kPdbPageSize));
162 : ASSERT_TRUE(stream->Read(expected_contents.data() + 3 * kPdbPageSize,
163 E : 3 * kPdbPageSize));
164 :
165 E : std::vector<uint8> contents(6 * kPdbPageSize);
166 : ASSERT_EQ(contents.size(),
167 : file_util::ReadFile(temp_file.path(),
168 : reinterpret_cast<char*>(contents.data()),
169 E : contents.size()));
170 :
171 E : EXPECT_THAT(contents, ::testing::ContainerEq(expected_contents));
172 E : }
173 :
174 E : TEST(PdbWriterTest, WriteHeader) {
175 E : TestPdbWriter writer;
176 :
177 E : testing::ScopedTempFile temp_file;
178 E : writer.file().reset(file_util::OpenFile(temp_file.path(), "wb"));
179 E : ASSERT_TRUE(writer.file().get() != NULL);
180 :
181 E : std::vector<uint32> root_directory_pages(kPdbMaxDirPages + 10, 1);
182 :
183 : // Try to write a root directorty that's too big and expect this to fail.
184 E : EXPECT_FALSE(writer.WriteHeader(root_directory_pages, 67 * 4, 438));
185 :
186 : // Now write a reasonable root directory size.
187 E : root_directory_pages.resize(1);
188 E : EXPECT_TRUE(writer.WriteHeader(root_directory_pages, 67 * 4, 438));
189 E : writer.file().reset();
190 :
191 : // Build the expected stream contents. Two blank pages should have been
192 : // reserved by the append stream routine.
193 E : std::vector<uint8> expected_contents(sizeof(PdbHeader));
194 E : PdbHeader* header = reinterpret_cast<PdbHeader*>(expected_contents.data());
195 : ::memcpy(header->magic_string, kPdbHeaderMagicString,
196 E : kPdbHeaderMagicStringSize);
197 E : header->page_size = kPdbPageSize;
198 E : header->free_page_map = 1;
199 E : header->num_pages = 438;
200 E : header->directory_size = 67 * 4;
201 E : header->root_pages[0] = 1;
202 :
203 E : std::vector<uint8> contents(sizeof(PdbHeader));
204 : ASSERT_EQ(contents.size(),
205 : file_util::ReadFile(temp_file.path(),
206 : reinterpret_cast<char*>(contents.data()),
207 E : contents.size()));
208 :
209 E : EXPECT_THAT(contents, ::testing::ContainerEq(expected_contents));
210 E : }
211 :
212 E : TEST(PdbWriterTest, WritePdbFile) {
213 E : PdbFile pdb_file;
214 E : for (uint32 i = 0; i < 4; ++i)
215 E : pdb_file.AppendStream(new TestPdbStream(1 << (8 + i), (i << 24)));
216 :
217 : // Test that we can create a pdb file and then read it successfully.
218 E : testing::ScopedTempFile file;
219 : {
220 : // Create a scope so that the file gets closed.
221 E : TestPdbWriter writer;
222 E : EXPECT_TRUE(writer.Write(file.path(), pdb_file));
223 E : }
224 :
225 E : PdbFile pdb_file_read;
226 E : PdbReader reader;
227 E : EXPECT_TRUE(reader.Read(file.path(), &pdb_file_read));
228 :
229 : ASSERT_NO_FATAL_FAILURE(
230 E : EnsurePdbContentsAreIdentical(pdb_file, pdb_file_read));
231 E : }
232 :
233 E : TEST(PdbWriterTest, PdbStrCompatible) {
234 : base::FilePath test_dll_pdb =
235 E : testing::GetSrcRelativePath(testing::kTestPdbFilePath);
236 :
237 E : PdbFile file;
238 E : PdbReader reader;
239 E : ASSERT_TRUE(reader.Read(test_dll_pdb, &file));
240 :
241 : // We need at least 8 MB of data in the DLL to ensure that the free page map
242 : // requires a second page. We manually add data to it until we get to that
243 : // point.
244 E : int64 test_dll_pdb_length = 0;
245 E : ASSERT_TRUE(file_util::GetFileSize(test_dll_pdb, &test_dll_pdb_length));
246 E : while (test_dll_pdb_length < 9 * 1024 * 1024) {
247 E : file.AppendStream(new TestPdbStream(1024 * 1024, file.StreamCount()));
248 E : test_dll_pdb_length += 1024 * 1024;
249 E : }
250 :
251 : // Write the Syzygy modified PDB to disk.
252 E : base::ScopedTempDir temp_dir;
253 E : ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
254 E : base::FilePath pdb_path = temp_dir.path().Append(testing::kTestDllPdbName);
255 E : PdbWriter writer;
256 E : ASSERT_TRUE(writer.Write(pdb_path, file));
257 :
258 : // Write a new stream to disk.
259 E : base::FilePath stream_path = temp_dir.path().AppendASCII("new_stream.dat");
260 : scoped_refptr<TestPdbStream> new_stream(
261 E : new TestPdbStream(1024 * 1024, 0xff));
262 : {
263 : file_util::ScopedFILE stream_file(file_util::OpenFile(
264 E : stream_path, "wb"));
265 E : ASSERT_TRUE(stream_file.get() != NULL);
266 : ASSERT_EQ(new_stream->data().size(),
267 : ::fwrite(new_stream->data().data(),
268 : sizeof(new_stream->data()[0]),
269 : new_stream->data().size(),
270 E : stream_file.get()));
271 E : }
272 :
273 : // Get the path to pdbstr.exe, which we redistribute in third_party.
274 : base::FilePath pdbstr_path =
275 E : testing::GetSrcRelativePath(testing::kPdbStrPath);
276 :
277 : // Create the arguments to pdbstr.
278 E : std::string pdb_arg = ::WideToUTF8(pdb_path.value());
279 E : pdb_arg.insert(0, "-p:");
280 E : std::string stream_arg = ::WideToUTF8(stream_path.value());
281 E : stream_arg.insert(0, "-i:");
282 :
283 : // Add a new stream to the PDB in place. This should produce no output.
284 : {
285 E : CommandLine cmd(pdbstr_path);
286 E : cmd.AppendArg(pdb_arg);
287 E : cmd.AppendArg(stream_arg);
288 E : cmd.AppendArg("-w");
289 E : cmd.AppendArg("-s:nonexistent-stream-name");
290 :
291 E : std::string output;
292 E : ASSERT_TRUE(base::GetAppOutput(cmd, &output));
293 E : ASSERT_TRUE(output.empty());
294 E : }
295 :
296 : // Read the pdbstr modified PDB.
297 E : PdbFile file_read;
298 E : ASSERT_TRUE(reader.Read(pdb_path, &file_read));
299 :
300 : // Add the new stream to the original PDB.
301 E : file.AppendStream(new_stream.get());
302 :
303 : // Clear stream 0 (the previous directory) and stream 1 (the PDB header
304 : // stream). These can vary but be functionally equivalent. We only care about
305 : // the actual content streams, which are the rest of them.
306 E : scoped_refptr<PdbStream> empty_stream(new TestPdbStream(0, 0));
307 E : file.ReplaceStream(0, empty_stream.get());
308 E : file.ReplaceStream(1, empty_stream.get());
309 E : file_read.ReplaceStream(0, empty_stream.get());
310 E : file_read.ReplaceStream(1, empty_stream.get());
311 :
312 : // Ensure that the two PDBs are identical.
313 : ASSERT_NO_FATAL_FAILURE(
314 E : EnsurePdbContentsAreIdentical(file, file_read));
315 E : }
316 :
317 : } // namespace pdb
|