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 length, uint32 mask) : PdbStream(length), data_(length) {
53 E : uint32* data = reinterpret_cast<uint32*>(data_.data());
54 :
55 : // Just to make sure the data is non-repeating (so we can distinguish if it
56 : // has been correctly written or not) fill it with integers encoding their
57 : // own position in the stream.
58 E : for (size_t i = 0; i < data_.size() / sizeof(data[0]); ++i)
59 E : data[i] = i | mask;
60 E : }
61 :
62 E : bool ReadBytes(void* dest, size_t count, size_t* bytes_read) {
63 E : DCHECK(bytes_read != NULL);
64 :
65 E : if (pos() == length()) {
66 i : *bytes_read = 0;
67 i : return true;
68 : }
69 :
70 E : count = std::min(count, length() - pos());
71 E : ::memcpy(dest, data_.data() + pos(), count);
72 E : Seek(pos() + count);
73 E : *bytes_read = count;
74 :
75 E : return true;
76 E : }
77 :
78 E : const std::vector<uint8> data() const { return data_; }
79 :
80 : private:
81 : std::vector<uint8> data_;
82 : };
83 :
84 : } // namespace
85 :
86 E : TEST(PdbWriterTest, PdbStrCompatible) {
87 : base::FilePath test_dll_msf =
88 E : testing::GetSrcRelativePath(testing::kTestPdbFilePath);
89 :
90 E : PdbFile file;
91 E : PdbReader reader;
92 E : ASSERT_TRUE(reader.Read(test_dll_msf, &file));
93 :
94 : // We need at least 8 MB of data in the DLL to ensure that the free page map
95 : // requires a second page. We manually add data to it until we get to that
96 : // point.
97 E : int64 test_dll_msf_length = 0;
98 E : ASSERT_TRUE(base::GetFileSize(test_dll_msf, &test_dll_msf_length));
99 E : while (test_dll_msf_length < 9 * 1024 * 1024) {
100 E : file.AppendStream(new TestPdbStream(1024 * 1024, file.StreamCount()));
101 E : test_dll_msf_length += 1024 * 1024;
102 E : }
103 :
104 : // Write the Syzygy modified PDB to disk.
105 E : base::ScopedTempDir temp_dir;
106 E : ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
107 E : base::FilePath msf_path = temp_dir.path().Append(testing::kTestPdbFilePath);
108 E : ASSERT_TRUE(base::CreateDirectory(msf_path.DirName()));
109 E : PdbWriter writer;
110 E : ASSERT_TRUE(writer.Write(msf_path, file));
111 :
112 : // Write a new stream to disk.
113 E : base::FilePath stream_path = temp_dir.path().AppendASCII("new_stream.dat");
114 E : scoped_refptr<TestPdbStream> new_stream(new TestPdbStream(1024 * 1024, 0xff));
115 : {
116 E : base::ScopedFILE stream_file(base::OpenFile(stream_path, "wb"));
117 E : ASSERT_TRUE(stream_file.get() != NULL);
118 : ASSERT_EQ(new_stream->data().size(),
119 : ::fwrite(new_stream->data().data(), sizeof(new_stream->data()[0]),
120 E : new_stream->data().size(), stream_file.get()));
121 E : }
122 :
123 : // Get the path to pdbstr.exe, which we redistribute in third_party.
124 : base::FilePath msfstr_path =
125 E : testing::GetSrcRelativePath(testing::kPdbStrPath);
126 :
127 : // Create the arguments to pdbstr.
128 E : std::string msf_arg = base::WideToUTF8(msf_path.value());
129 E : msf_arg.insert(0, "-p:");
130 E : std::string stream_arg = base::WideToUTF8(stream_path.value());
131 E : stream_arg.insert(0, "-i:");
132 :
133 : // Add a new stream to the PDB in place. This should produce no output.
134 : {
135 E : base::CommandLine cmd(msfstr_path);
136 E : cmd.AppendArg(msf_arg);
137 E : cmd.AppendArg(stream_arg);
138 E : cmd.AppendArg("-w");
139 E : cmd.AppendArg("-s:nonexistent-stream-name");
140 :
141 E : std::string output;
142 E : ASSERT_TRUE(base::GetAppOutput(cmd, &output));
143 E : ASSERT_TRUE(output.empty());
144 E : }
145 :
146 : // Read the pdbstr modified PDB.
147 E : PdbFile file_read;
148 E : ASSERT_TRUE(reader.Read(msf_path, &file_read));
149 :
150 : // Add the new stream to the original PDB.
151 E : file.AppendStream(new_stream.get());
152 :
153 : // Clear stream 0 (the previous directory) and stream 1 (the PDB header
154 : // stream). These can vary but be functionally equivalent. We only care about
155 : // the actual content streams, which are the rest of them.
156 E : scoped_refptr<PdbStream> empty_stream(new TestPdbStream(0, 0));
157 E : file.ReplaceStream(0, empty_stream.get());
158 E : file.ReplaceStream(1, empty_stream.get());
159 E : file_read.ReplaceStream(0, empty_stream.get());
160 E : file_read.ReplaceStream(1, empty_stream.get());
161 :
162 : // Ensure that the two PDBs are identical.
163 : ASSERT_NO_FATAL_FAILURE(
164 E : testing::EnsureMsfContentsAreIdentical(file, file_read));
165 E : }
166 :
167 : } // namespace pdb
|