1 : // Copyright 2013 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/pe/pe_relinker_util.h"
16 :
17 : #include "base/file_util.h"
18 : #include "syzygy/block_graph/transform.h"
19 : #include "syzygy/core/file_util.h"
20 : #include "syzygy/core/zstream.h"
21 : #include "syzygy/pdb/pdb_byte_stream.h"
22 : #include "syzygy/pdb/pdb_util.h"
23 : #include "syzygy/pe/find.h"
24 : #include "syzygy/pe/metadata.h"
25 : #include "syzygy/pe/pe_image_layout_builder.h"
26 : #include "syzygy/pe/pe_utils.h"
27 : #include "syzygy/pe/serialization.h"
28 : #include "syzygy/pe/orderers/pe_orderer.h"
29 : #include "syzygy/pe/transforms/add_metadata_transform.h"
30 : #include "syzygy/pe/transforms/add_pdb_info_transform.h"
31 : #include "syzygy/pe/transforms/pe_prepare_headers_transform.h"
32 :
33 : namespace pe {
34 :
35 : namespace {
36 :
37 : using block_graph::BlockGraph;
38 : using block_graph::BlockGraphTransformInterface;
39 : using block_graph::OrderedBlockGraph;
40 : using core::RelativeAddress;
41 : using pdb::NameStreamMap;
42 : using pdb::PdbByteStream;
43 : using pdb::PdbFile;
44 : using pdb::PdbInfoHeader70;
45 : using pdb::PdbStream;
46 : using pdb::WritablePdbStream;
47 : using pe::PETransformPolicy;
48 :
49 : // A utility class for wrapping a serialization OutStream around a
50 : // WritablePdbStream.
51 : // TODO(chrisha): We really need to centralize stream/buffer semantics in
52 : // a small set of clean interfaces, and make all input/output/parsing work
53 : // on these interfaces.
54 : class PdbOutStream : public core::OutStream {
55 : public:
56 E : explicit PdbOutStream(WritablePdbStream* pdb_stream)
57 : : pdb_stream_(pdb_stream) {
58 E : DCHECK(pdb_stream != NULL);
59 E : }
60 :
61 E : virtual ~PdbOutStream() { }
62 :
63 E : virtual bool Write(size_t length, const core::Byte* bytes) OVERRIDE {
64 E : return pdb_stream_->Write(length, bytes);
65 E : }
66 :
67 : private:
68 : scoped_refptr<WritablePdbStream> pdb_stream_;
69 : };
70 :
71 : void BuildOmapVectors(const RelativeAddressRange& input_range,
72 : const ImageLayout& output_image_layout,
73 : std::vector<OMAP>* omap_to,
74 E : std::vector<OMAP>* omap_from) {
75 E : DCHECK(omap_to != NULL);
76 E : DCHECK(omap_from != NULL);
77 :
78 E : LOG(INFO) << "Building OMAP vectors.";
79 :
80 : // Get the range of the output image, sans headers. This is required for
81 : // generating OMAP information.
82 E : RelativeAddressRange output_range;
83 E : GetOmapRange(output_image_layout.sections, &output_range);
84 :
85 E : ImageSourceMap reverse_map;
86 E : BuildImageSourceMap(output_image_layout, &reverse_map);
87 :
88 E : ImageSourceMap forward_map;
89 E : if (reverse_map.ComputeInverse(&forward_map) != 0) {
90 E : LOG(WARNING) << "OMAPFROM not unique (there exist repeated source ranges).";
91 : }
92 :
93 : // Build the two OMAP vectors.
94 E : BuildOmapVectorFromImageSourceMap(output_range, reverse_map, omap_to);
95 E : BuildOmapVectorFromImageSourceMap(input_range, forward_map, omap_from);
96 E : }
97 :
98 : // Get a specific named stream if it already exists, otherwise create one.
99 : // @param stream_name The name of the stream.
100 : // @param name_stream_map The map containing the names of the streams in the
101 : // PDB. If the stream doesn't already exist the map will be augmented with
102 : // another entry.
103 : // @param pdb_file The PDB file to which the stream will be added.
104 : // @param replace_stream If true, will cause a new stream to be created even if
105 : // another one already existed.
106 : // @return a pointer to the PDB stream on success, NULL on failure.
107 : PdbStream* GetOrCreatePdbStreamByName(const char* stream_name,
108 : bool replace_stream,
109 : NameStreamMap* name_stream_map,
110 E : PdbFile* pdb_file) {
111 E : DCHECK(name_stream_map != NULL);
112 E : DCHECK(pdb_file != NULL);
113 E : scoped_refptr<PdbStream> stream;
114 :
115 E : NameStreamMap::const_iterator name_it = name_stream_map->find(stream_name);
116 E : if (name_it != name_stream_map->end()) {
117 : // Replace the existing stream by a brand-new one if it's required.
118 i : if (replace_stream) {
119 i : stream = new PdbByteStream();
120 i : pdb_file->ReplaceStream(name_it->second, stream.get());
121 i : } else {
122 i : if (!pdb::EnsureStreamWritable(name_it->second, pdb_file)) {
123 i : LOG(ERROR) << "Failed to make " << stream_name << " stream writable.";
124 i : return NULL;
125 : }
126 i : stream = pdb_file->GetStream(name_it->second);
127 : }
128 i : } else {
129 E : stream = new PdbByteStream();
130 E : uint32 index = pdb_file->AppendStream(stream.get());
131 E : (*name_stream_map)[stream_name] = index;
132 : }
133 :
134 E : return stream.get();
135 E : }
136 :
137 : // This updates or creates the Syzygy history stream, appending the metadata
138 : // describing this module and transform. The history stream consists of
139 : // a named PDB stream with the name /Syzygy/History. It consists of:
140 : //
141 : // uint32 version
142 : // uint32 history_length
143 : // serialized pe::Metadata 0
144 : // ...
145 : // serialized pe::Metadata history_length - 1
146 : //
147 : // If the format is changed, be sure to update this documentation and
148 : // pdb::kSyzygyHistoryStreamVersion (in pdb_constants.h).
149 : bool WriteSyzygyHistoryStream(const base::FilePath& input_path,
150 : NameStreamMap* name_stream_map,
151 E : PdbFile* pdb_file) {
152 : // Get the history stream.
153 : scoped_refptr<PdbStream> history_reader =
154 : GetOrCreatePdbStreamByName(pdb::kSyzygyHistoryStreamName,
155 : false,
156 : name_stream_map,
157 E : pdb_file);
158 :
159 E : if (history_reader == NULL) {
160 i : LOG(ERROR) << "Failed to get the history stream.";
161 i : return false;
162 : }
163 :
164 : scoped_refptr<WritablePdbStream> history_writer =
165 E : history_reader->GetWritablePdbStream();
166 E : DCHECK(history_writer.get() != NULL);
167 :
168 : // Get the metadata.
169 E : Metadata metadata;
170 E : PEFile pe_file;
171 E : if (!pe_file.Init(input_path)) {
172 i : LOG(ERROR) << "Failed to initialize PE file for \"" << input_path.value()
173 : << "\".";
174 i : return false;
175 : }
176 :
177 E : PEFile::Signature pe_sig;
178 E : pe_file.GetSignature(&pe_sig);
179 E : if (!metadata.Init(pe_sig)) {
180 i : LOG(ERROR) << "Failed to initialize metadata for \"" << input_path.value()
181 : << "\".";
182 i : return false;
183 : }
184 :
185 : // Validate the history stream if it is non-empty.
186 E : if (history_reader->length() > 0) {
187 : // Read the header.
188 i : uint32 version = 0;
189 i : uint32 history_length = 0;
190 : if (!history_reader->Seek(0) ||
191 : !history_reader->Read(&version, 1) ||
192 i : !history_reader->Read(&history_length, 1)) {
193 i : LOG(ERROR) << "Failed to read existing Syzygy history stream header.";
194 i : return false;
195 : }
196 :
197 : // Check the version.
198 i : if (version != pdb::kSyzygyHistoryStreamVersion) {
199 i : LOG(ERROR) << "PDB contains unsupported Syzygy history stream version "
200 : << "(got " << version << ", expected "
201 : << pdb::kSyzygyHistoryStreamVersion << ").";
202 i : return false;
203 : }
204 :
205 : // Increment the history length and rewrite it.
206 i : history_length++;
207 i : history_writer->set_pos(sizeof(pdb::kSyzygyHistoryStreamVersion));
208 i : if (!history_writer->Write(history_length)) {
209 i : LOG(ERROR) << "Failed to write new Syzygy history stream length.";
210 i : return false;
211 : }
212 i : } else {
213 : // If there wasn't already a history stream, create one and write the
214 : // header.
215 E : DCHECK_EQ(0u, history_writer->pos());
216 E : const uint32 kHistoryLength = 1;
217 : if (!history_writer->Write(pdb::kSyzygyHistoryStreamVersion) ||
218 E : !history_writer->Write(kHistoryLength)) {
219 i : LOG(ERROR) << "Failed to write Syzygy history stream header.";
220 i : return false;
221 : }
222 : }
223 :
224 : // Append the metadata to the history.
225 E : history_writer->set_pos(history_writer->length());
226 E : PdbOutStream out_stream(history_writer.get());
227 E : core::OutArchive out_archive(&out_stream);
228 E : if (!out_archive.Save(metadata)) {
229 i : LOG(ERROR) << "Failed to write metadata to Syzygy history stream.";
230 i : return false;
231 : }
232 :
233 E : return true;
234 E : }
235 :
236 : // This writes the serialized block-graph and the image layout in a PDB stream
237 : // named /Syzygy/BlockGraph. If the format is changed, be sure to update this
238 : // documentation and pdb::kSyzygyBlockGraphStreamVersion (in pdb_constants.h).
239 : // The block graph stream will not include the data from the blocks of the
240 : // block-graph. If the strip-strings flag is set to true the strings contained
241 : // in the block-graph won't be saved.
242 : bool WriteSyzygyBlockGraphStream(const PEFile& pe_file,
243 : const ImageLayout& image_layout,
244 : bool strip_strings,
245 : bool compress,
246 : NameStreamMap* name_stream_map,
247 E : PdbFile* pdb_file) {
248 : // Get the redecomposition data stream.
249 : scoped_refptr<PdbStream> block_graph_reader =
250 : GetOrCreatePdbStreamByName(pdb::kSyzygyBlockGraphStreamName,
251 : true,
252 : name_stream_map,
253 E : pdb_file);
254 :
255 E : if (block_graph_reader == NULL) {
256 i : LOG(ERROR) << "Failed to get the block-graph stream.";
257 i : return false;
258 : }
259 E : DCHECK_EQ(0u, block_graph_reader->length());
260 :
261 : scoped_refptr<WritablePdbStream> block_graph_writer =
262 E : block_graph_reader->GetWritablePdbStream();
263 E : DCHECK(block_graph_writer.get() != NULL);
264 :
265 : // Write the version of the BlockGraph stream, and whether or not its
266 : // contents are compressed.
267 : if (!block_graph_writer->Write(pdb::kSyzygyBlockGraphStreamVersion) ||
268 E : !block_graph_writer->Write(static_cast<unsigned char>(compress))) {
269 i : LOG(ERROR) << "Failed to write Syzygy BlockGraph stream header.";
270 i : return false;
271 : }
272 :
273 : // Set up the output stream.
274 E : PdbOutStream pdb_out_stream(block_graph_writer.get());
275 E : core::OutStream* out_stream = &pdb_out_stream;
276 :
277 : // If requested, compress the output.
278 E : scoped_ptr<core::ZOutStream> zip_stream;
279 E : if (compress) {
280 E : zip_stream.reset(new core::ZOutStream(&pdb_out_stream));
281 E : out_stream = zip_stream.get();
282 E : if (!zip_stream->Init(core::ZOutStream::kZBestCompression)) {
283 i : LOG(ERROR) << "Failed to initialize zlib compressor.";
284 i : return false;
285 : }
286 : }
287 :
288 E : core::OutArchive out_archive(out_stream);
289 :
290 : // Set up the serialization properties.
291 E : block_graph::BlockGraphSerializer::Attributes attributes = 0;
292 E : if (strip_strings)
293 E : attributes |= block_graph::BlockGraphSerializer::OMIT_STRINGS;
294 :
295 : // And finally, perform the serialization.
296 : if (!SaveBlockGraphAndImageLayout(pe_file, attributes, image_layout,
297 E : &out_archive)) {
298 i : LOG(ERROR) << "SaveBlockGraphAndImageLayout failed.";
299 i : return false;
300 : }
301 :
302 : // We have to flush the stream in case it's a zstream.
303 E : out_stream->Flush();
304 :
305 E : return true;
306 E : }
307 :
308 : } // namespace
309 :
310 : bool ValidateAndInferPaths(
311 : const base::FilePath& input_module,
312 : const base::FilePath& output_module,
313 : bool allow_overwrite,
314 : base::FilePath* input_pdb,
315 E : base::FilePath* output_pdb) {
316 E : DCHECK(!input_module.empty());
317 E : DCHECK(!output_module.empty());
318 E : DCHECK_NE(reinterpret_cast<base::FilePath*>(NULL), input_pdb);
319 E : DCHECK_NE(reinterpret_cast<base::FilePath*>(NULL), output_pdb);
320 :
321 E : if (!file_util::PathExists(input_module)) {
322 E : LOG(ERROR) << "Input module not found: " << input_module.value();
323 E : return false;
324 : }
325 :
326 E : if (!allow_overwrite && file_util::PathExists(output_module)) {
327 E : LOG(ERROR) << "Output module exists: " << output_module.value();
328 E : LOG(ERROR) << "Specify --overwrite to ignore this error.";
329 E : return false;
330 : }
331 :
332 : // If no input PDB was specified then search for it.
333 E : if (input_pdb->empty()) {
334 E : LOG(INFO) << "Input PDB not specified, searching for it.";
335 : if (!pe::FindPdbForModule(input_module, input_pdb) ||
336 E : input_pdb->empty()) {
337 i : LOG(ERROR) << "Unable to find PDB file for module: "
338 : << input_module.value();
339 i : return NULL;
340 : }
341 : }
342 :
343 E : if (!file_util::PathExists(*input_pdb)) {
344 E : LOG(ERROR) << "Input PDB not found: " << input_pdb->value();
345 E : return false;
346 : }
347 :
348 : // If no output PDB path is specified, infer one.
349 E : if (output_pdb->empty()) {
350 : // If the input and output DLLs have the same basename, default to writing
351 : // using the same PDB basename, but alongside the new module.
352 E : if (input_module.BaseName() == output_module.BaseName()) {
353 E : *output_pdb = output_module.DirName().Append(input_pdb->BaseName());
354 E : } else {
355 : // Otherwise, default to using the output basename with a PDB extension
356 : // added to it.
357 E : *output_pdb = output_module.AddExtension(L"pdb");
358 : }
359 :
360 E : LOG(INFO) << "Using default output PDB path: " << output_pdb->value();
361 : }
362 :
363 E : if (!allow_overwrite && file_util::PathExists(*output_pdb)) {
364 E : LOG(ERROR) << "Output PDB exists: " << output_pdb->value();
365 E : LOG(ERROR) << "Specify --overwrite to ignore this error.";
366 E : return false;
367 : }
368 :
369 : // Perform some extra checking to make sure that writes aren't going to
370 : // collide. This prevents us from overwriting the input, effectively
371 : // preventing in-place transforms. This is not fool-proof in the face of
372 : // weird junctions but it will catch common errors.
373 :
374 : core::FilePathCompareResult result =
375 E : core::CompareFilePaths(input_module, output_module);
376 E : if (result == core::kEquivalentFilePaths) {
377 i : LOG(ERROR) << "Input and output module paths are equivalent.";
378 i : LOG(ERROR) << "Input module path: " << input_module.value();
379 i : LOG(ERROR) << "Output module path: " << output_module.value();
380 i : return false;
381 : }
382 :
383 E : result = core::CompareFilePaths(*input_pdb, *output_pdb);
384 E : if (result == core::kEquivalentFilePaths) {
385 i : LOG(ERROR) << "Input and output PDB paths are equivalent.";
386 i : LOG(ERROR) << "Input PDB path: " << input_pdb->value();
387 i : LOG(ERROR) << "Output PDB path: " << output_pdb->value();
388 i : return false;
389 : }
390 :
391 E : result = core::CompareFilePaths(output_module, *output_pdb);
392 E : if (result == core::kEquivalentFilePaths) {
393 E : LOG(ERROR) << "Output module and PDB paths are equivalent.";
394 E : LOG(ERROR) << "Output module path: " << output_module.value();
395 E : LOG(ERROR) << "Output PDB path: " << output_pdb->value();
396 E : return false;
397 : }
398 :
399 E : return true;
400 E : }
401 :
402 : bool FinalizeBlockGraph(const base::FilePath& input_module,
403 : const base::FilePath& output_pdb,
404 : const GUID& pdb_guid,
405 : bool add_metadata,
406 : const PETransformPolicy* policy,
407 : BlockGraph* block_graph,
408 E : BlockGraph::Block* dos_header_block) {
409 E : DCHECK_NE(reinterpret_cast<PETransformPolicy*>(NULL), policy);
410 E : DCHECK_NE(reinterpret_cast<BlockGraph*>(NULL), block_graph);
411 E : DCHECK_NE(reinterpret_cast<BlockGraph::Block*>(NULL), dos_header_block);
412 E : LOG(INFO) << "Finalizing block-graph for \"" << input_module.value() << "\".";
413 :
414 E : std::vector<BlockGraphTransformInterface*> post_transforms;
415 E : pe::transforms::AddMetadataTransform add_metadata_tx(input_module);
416 : pe::transforms::AddPdbInfoTransform add_pdb_info_tx(output_pdb, 1,
417 E : pdb_guid);
418 E : pe::transforms::PEPrepareHeadersTransform prep_headers_tx;
419 :
420 E : if (add_metadata)
421 E : post_transforms.push_back(&add_metadata_tx);
422 E : post_transforms.push_back(&add_pdb_info_tx);
423 E : post_transforms.push_back(&prep_headers_tx);
424 :
425 : if (!block_graph::ApplyBlockGraphTransforms(post_transforms,
426 : policy,
427 : block_graph,
428 E : dos_header_block)) {
429 i : return false;
430 : }
431 :
432 E : return true;
433 E : }
434 :
435 : bool FinalizeOrderedBlockGraph(
436 : OrderedBlockGraph* ordered_block_graph,
437 E : BlockGraph::Block* dos_header_block) {
438 E : DCHECK_NE(reinterpret_cast<OrderedBlockGraph*>(NULL), ordered_block_graph);
439 E : DCHECK_NE(reinterpret_cast<BlockGraph::Block*>(NULL), dos_header_block);
440 E : pe::orderers::PEOrderer pe_orderer;
441 E : if (!pe_orderer.OrderBlockGraph(ordered_block_graph, dos_header_block))
442 i : return false;
443 E : return true;
444 E : }
445 :
446 : bool BuildImageLayout(size_t padding,
447 : size_t code_alignment,
448 : const OrderedBlockGraph& ordered_block_graph,
449 : BlockGraph::Block* dos_header_block,
450 E : ImageLayout* image_layout) {
451 E : DCHECK_NE(reinterpret_cast<BlockGraph::Block*>(NULL), dos_header_block);
452 E : DCHECK_NE(reinterpret_cast<ImageLayout*>(NULL), image_layout);
453 :
454 E : LOG(INFO) << "Building image layout.";
455 :
456 E : PEImageLayoutBuilder builder(image_layout);
457 E : builder.set_padding(padding);
458 E : builder.set_code_alignment(code_alignment);
459 E : if (!builder.LayoutImageHeaders(dos_header_block)) {
460 i : LOG(ERROR) << "PEImageLayoutBuilder::LayoutImageHeaders failed.";
461 i : return false;
462 : }
463 :
464 E : if (!builder.LayoutOrderedBlockGraph(ordered_block_graph)) {
465 i : LOG(ERROR) << "PEImageLayoutBuilder::LayoutOrderedBlockGraph failed.";
466 i : return false;
467 : }
468 :
469 E : LOG(INFO) << "Finalizing image layout.";
470 E : if (!builder.Finalize()) {
471 i : LOG(ERROR) << "PEImageLayoutBuilder::Finalize failed.";
472 i : return false;
473 : }
474 :
475 E : return true;
476 E : }
477 :
478 : void GetOmapRange(const std::vector<ImageLayout::SectionInfo>& sections,
479 E : RelativeAddressRange* range) {
480 E : DCHECK_NE(reinterpret_cast<RelativeAddressRange*>(NULL), range);
481 :
482 : // There need to be at least two sections, one containing something and the
483 : // other containing the relocs.
484 E : DCHECK_GT(sections.size(), 1u);
485 E : DCHECK_EQ(sections.back().name, std::string(kRelocSectionName));
486 :
487 : // For some reason, if we output OMAP entries for the headers (before the
488 : // first section), everything falls apart. Not outputting these allows the
489 : // unittests to pass. Also, we don't want to output OMAP information for
490 : // the relocs, as these are entirely different from image to image.
491 E : RelativeAddress start_of_image = sections.front().addr;
492 E : RelativeAddress end_of_image = sections.back().addr;
493 E : *range = RelativeAddressRange(start_of_image, end_of_image - start_of_image);
494 E : }
495 :
496 : bool FinalizePdbFile(const base::FilePath input_module,
497 : const base::FilePath output_module,
498 : const RelativeAddressRange input_range,
499 : const ImageLayout& image_layout,
500 : const GUID& guid,
501 : bool augment_pdb,
502 : bool strip_strings,
503 : bool compress_pdb,
504 E : pdb::PdbFile* pdb_file) {
505 E : DCHECK(pdb_file != NULL);
506 :
507 E : LOG(INFO) << "Finalizing PDB file.";
508 :
509 E : VLOG(1) << "Updating GUID.";
510 E : if (!pdb::SetGuid(guid, pdb_file)) {
511 i : LOG(ERROR) << "Unable to set PDB GUID.";
512 i : return false;
513 : }
514 :
515 E : VLOG(1) << "Building OMAP vectors.";
516 E : std::vector<OMAP> omap_to, omap_from;
517 E : BuildOmapVectors(input_range, image_layout, &omap_to, &omap_from);
518 :
519 E : VLOG(1) << "Writing OMAP vectors.";
520 E : if (!pdb::SetOmapToStream(omap_to, pdb_file)) {
521 i : LOG(ERROR) << "Unable to set OMAP_TO.";
522 i : return false;
523 : }
524 E : if (!pdb::SetOmapFromStream(omap_from, pdb_file)) {
525 i : LOG(ERROR) << "Unable to set OMAP_FROM.";
526 i : return false;
527 : }
528 :
529 : // Parse the header and named streams.
530 E : pdb::PdbInfoHeader70 header = {};
531 E : pdb::NameStreamMap name_stream_map;
532 E : if (!pdb::ReadHeaderInfoStream(*pdb_file, &header, &name_stream_map))
533 i : return false;
534 :
535 : // Update/create the Syzygy history stream.
536 E : VLOG(1) << "Adding history stream to PDB.";
537 E : if (!WriteSyzygyHistoryStream(input_module, &name_stream_map, pdb_file))
538 i : return false;
539 :
540 : // Add redecomposition data in another stream, only if augment_pdb_ is set.
541 E : if (augment_pdb) {
542 E : PEFile new_pe_file;
543 E : if (!new_pe_file.Init(output_module)) {
544 i : LOG(ERROR) << "Failed to read newly written PE file.";
545 i : return false;
546 : }
547 :
548 E : VLOG(1) << "Adding serialized block-graph stream to PDB.";
549 : if (!WriteSyzygyBlockGraphStream(new_pe_file,
550 : image_layout,
551 : strip_strings,
552 : compress_pdb,
553 : &name_stream_map,
554 E : pdb_file)) {
555 i : return false;
556 : }
557 E : }
558 :
559 : // Write the updated name-stream map back to the header info stream.
560 E : VLOG(1) << "Updating PDB headers.";
561 E : if (!pdb::WriteHeaderInfoStream(header, name_stream_map, pdb_file))
562 i : return false;
563 :
564 : // Stream 0 contains a copy of the previous PDB's directory. This, combined
565 : // with copy-on-write semantics of individual blocks makes the file contain
566 : // its whole edit history. Since we're writing a 'new' PDB file (we reset the
567 : // GUID and age), we have no history so can safely throw away this stream.
568 E : VLOG(1) << "Removing previous PDB directory stream.";
569 E : pdb_file->ReplaceStream(0, NULL);
570 :
571 E : return true;
572 E : }
573 :
574 : } // namespace pe
|