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