blob: 2709a3836b420621e509d5766df110fd39090ba2 [file] [log] [blame]
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "syzygy/pe/pe_relinker_util.h"
#include "base/files/file_util.h"
#include "syzygy/block_graph/transform.h"
#include "syzygy/core/file_util.h"
#include "syzygy/core/zstream.h"
#include "syzygy/pdb/pdb_byte_stream.h"
#include "syzygy/pdb/pdb_util.h"
#include "syzygy/pe/find.h"
#include "syzygy/pe/metadata.h"
#include "syzygy/pe/pe_image_layout_builder.h"
#include "syzygy/pe/pe_utils.h"
#include "syzygy/pe/serialization.h"
#include "syzygy/pe/orderers/pe_orderer.h"
#include "syzygy/pe/transforms/add_metadata_transform.h"
#include "syzygy/pe/transforms/add_pdb_info_transform.h"
#include "syzygy/pe/transforms/pe_prepare_headers_transform.h"
#include "syzygy/pe/transforms/pe_remove_empty_sections_transform.h"
namespace pe {
namespace {
using block_graph::BlockGraph;
using block_graph::BlockGraphTransformInterface;
using block_graph::OrderedBlockGraph;
using core::RelativeAddress;
using pdb::NameStreamMap;
using pdb::PdbByteStream;
using pdb::PdbFile;
using pdb::PdbInfoHeader70;
using pdb::PdbStream;
using pdb::WritablePdbStream;
using pe::PETransformPolicy;
// A utility class for wrapping a serialization OutStream around a
// WritablePdbStream.
// TODO(chrisha): We really need to centralize stream/buffer semantics in
// a small set of clean interfaces, and make all input/output/parsing work
// on these interfaces.
class PdbOutStream : public core::OutStream {
public:
explicit PdbOutStream(WritablePdbStream* pdb_stream)
: pdb_stream_(pdb_stream) {
DCHECK(pdb_stream != NULL);
}
virtual ~PdbOutStream() { }
bool Write(size_t length, const core::Byte* bytes) override {
return pdb_stream_->Write(length, bytes);
}
private:
scoped_refptr<WritablePdbStream> pdb_stream_;
};
void BuildOmapVectors(const RelativeAddressRange& input_range,
const ImageLayout& output_image_layout,
std::vector<OMAP>* omap_to,
std::vector<OMAP>* omap_from) {
DCHECK(omap_to != NULL);
DCHECK(omap_from != NULL);
LOG(INFO) << "Building OMAP vectors.";
// Get the range of the output image, sans headers. This is required for
// generating OMAP information.
RelativeAddressRange output_range;
GetOmapRange(output_image_layout.sections, &output_range);
ImageSourceMap reverse_map;
BuildImageSourceMap(output_image_layout, &reverse_map);
ImageSourceMap forward_map;
if (reverse_map.ComputeInverse(&forward_map) != 0) {
LOG(WARNING) << "OMAPFROM not unique (there exist repeated source ranges).";
}
// Build the two OMAP vectors.
BuildOmapVectorFromImageSourceMap(output_range, reverse_map, omap_to);
BuildOmapVectorFromImageSourceMap(input_range, forward_map, omap_from);
}
// Get a specific named stream if it already exists, otherwise create one.
// @param stream_name The name of the stream.
// @param name_stream_map The map containing the names of the streams in the
// PDB. If the stream doesn't already exist the map will be augmented with
// another entry.
// @param pdb_file The PDB file to which the stream will be added.
// @param replace_stream If true, will cause a new stream to be created even if
// another one already existed.
// @return a pointer to the PDB stream on success, NULL on failure.
PdbStream* GetOrCreatePdbStreamByName(const char* stream_name,
bool replace_stream,
NameStreamMap* name_stream_map,
PdbFile* pdb_file) {
DCHECK(name_stream_map != NULL);
DCHECK(pdb_file != NULL);
scoped_refptr<PdbStream> stream;
NameStreamMap::const_iterator name_it = name_stream_map->find(stream_name);
if (name_it != name_stream_map->end()) {
// Replace the existing stream by a brand-new one if it's required.
if (replace_stream) {
stream = new PdbByteStream();
pdb_file->ReplaceStream(name_it->second, stream.get());
} else {
if (!pdb::EnsureStreamWritable(name_it->second, pdb_file)) {
LOG(ERROR) << "Failed to make " << stream_name << " stream writable.";
return NULL;
}
stream = pdb_file->GetStream(name_it->second);
}
} else {
stream = new PdbByteStream();
uint32_t index = static_cast<uint32_t>(
pdb_file->AppendStream(stream.get()));
(*name_stream_map)[stream_name] = index;
}
return stream.get();
}
// This updates or creates the Syzygy history stream, appending the metadata
// describing this module and transform. The history stream consists of
// a named PDB stream with the name /Syzygy/History. It consists of:
//
// uint32_t version
// uint32_t history_length
// serialized pe::Metadata 0
// ...
// serialized pe::Metadata history_length - 1
//
// If the format is changed, be sure to update this documentation and
// pdb::kSyzygyHistoryStreamVersion (in pdb_constants.h).
bool WriteSyzygyHistoryStream(const base::FilePath& input_path,
NameStreamMap* name_stream_map,
PdbFile* pdb_file) {
// Get the history stream.
scoped_refptr<PdbStream> history_reader =
GetOrCreatePdbStreamByName(pdb::kSyzygyHistoryStreamName,
false,
name_stream_map,
pdb_file);
if (history_reader.get() == nullptr) {
LOG(ERROR) << "Failed to get the history stream.";
return false;
}
scoped_refptr<WritablePdbStream> history_writer =
history_reader->GetWritableStream();
DCHECK(history_writer.get() != nullptr);
// Get the metadata.
Metadata metadata;
PEFile pe_file;
if (!pe_file.Init(input_path)) {
LOG(ERROR) << "Failed to initialize PE file for \"" << input_path.value()
<< "\".";
return false;
}
PEFile::Signature pe_sig;
pe_file.GetSignature(&pe_sig);
if (!metadata.Init(pe_sig)) {
LOG(ERROR) << "Failed to initialize metadata for \"" << input_path.value()
<< "\".";
return false;
}
// Validate the history stream if it is non-empty.
if (history_reader->length() > 0) {
// Read the header.
uint32_t version = 0;
uint32_t history_length = 0;
if (!history_reader->ReadBytesAt(0, sizeof(version), &version) ||
!history_reader->ReadBytesAt(sizeof(version), sizeof(history_length),
&history_length)) {
LOG(ERROR) << "Failed to read existing Syzygy history stream header.";
return false;
}
// Check the version.
if (version != pdb::kSyzygyHistoryStreamVersion) {
LOG(ERROR) << "PDB contains unsupported Syzygy history stream version "
<< "(got " << version << ", expected "
<< pdb::kSyzygyHistoryStreamVersion << ").";
return false;
}
// Increment the history length and rewrite it.
history_length++;
history_writer->set_pos(sizeof(pdb::kSyzygyHistoryStreamVersion));
if (!history_writer->Write(history_length)) {
LOG(ERROR) << "Failed to write new Syzygy history stream length.";
return false;
}
} else {
// If there wasn't already a history stream, create one and write the
// header.
DCHECK_EQ(0u, history_writer->pos());
const uint32_t kHistoryLength = 1;
if (!history_writer->Write(pdb::kSyzygyHistoryStreamVersion) ||
!history_writer->Write(kHistoryLength)) {
LOG(ERROR) << "Failed to write Syzygy history stream header.";
return false;
}
}
// Append the metadata to the history.
history_writer->set_pos(history_writer->length());
PdbOutStream out_stream(history_writer.get());
core::OutArchive out_archive(&out_stream);
if (!out_archive.Save(metadata)) {
LOG(ERROR) << "Failed to write metadata to Syzygy history stream.";
return false;
}
return true;
}
// This writes the serialized block-graph and the image layout in a PDB stream
// named /Syzygy/BlockGraph. If the format is changed, be sure to update this
// documentation and pdb::kSyzygyBlockGraphStreamVersion (in pdb_constants.h).
// The block graph stream will not include the data from the blocks of the
// block-graph. If the strip-strings flag is set to true the strings contained
// in the block-graph won't be saved.
bool WriteSyzygyBlockGraphStream(const PEFile& pe_file,
const ImageLayout& image_layout,
bool strip_strings,
bool compress,
NameStreamMap* name_stream_map,
PdbFile* pdb_file) {
// Get the redecomposition data stream.
scoped_refptr<PdbStream> block_graph_reader =
GetOrCreatePdbStreamByName(pdb::kSyzygyBlockGraphStreamName,
true,
name_stream_map,
pdb_file);
if (block_graph_reader.get() == nullptr) {
LOG(ERROR) << "Failed to get the block-graph stream.";
return false;
}
DCHECK_EQ(0u, block_graph_reader->length());
scoped_refptr<WritablePdbStream> block_graph_writer =
block_graph_reader->GetWritableStream();
DCHECK(block_graph_writer.get() != NULL);
// Write the version of the BlockGraph stream, and whether or not its
// contents are compressed.
if (!block_graph_writer->Write(pdb::kSyzygyBlockGraphStreamVersion) ||
!block_graph_writer->Write(static_cast<unsigned char>(compress))) {
LOG(ERROR) << "Failed to write Syzygy BlockGraph stream header.";
return false;
}
// Set up the output stream.
PdbOutStream pdb_out_stream(block_graph_writer.get());
core::OutStream* out_stream = &pdb_out_stream;
// If requested, compress the output.
std::unique_ptr<core::ZOutStream> zip_stream;
if (compress) {
zip_stream.reset(new core::ZOutStream(&pdb_out_stream));
out_stream = zip_stream.get();
if (!zip_stream->Init(core::ZOutStream::kZBestCompression)) {
LOG(ERROR) << "Failed to initialize zlib compressor.";
return false;
}
}
core::OutArchive out_archive(out_stream);
// Set up the serialization properties.
block_graph::BlockGraphSerializer::Attributes attributes = 0;
if (strip_strings)
attributes |= block_graph::BlockGraphSerializer::OMIT_STRINGS;
// And finally, perform the serialization.
if (!SaveBlockGraphAndImageLayout(pe_file, attributes, image_layout,
&out_archive)) {
LOG(ERROR) << "SaveBlockGraphAndImageLayout failed.";
return false;
}
// We have to flush the stream in case it's a zstream.
out_stream->Flush();
return true;
}
} // namespace
bool ValidateAndInferPaths(
const base::FilePath& input_module,
const base::FilePath& output_module,
bool allow_overwrite,
base::FilePath* input_pdb,
base::FilePath* output_pdb) {
DCHECK(!input_module.empty());
DCHECK(!output_module.empty());
DCHECK_NE(reinterpret_cast<base::FilePath*>(NULL), input_pdb);
DCHECK_NE(reinterpret_cast<base::FilePath*>(NULL), output_pdb);
if (!base::PathExists(input_module)) {
LOG(ERROR) << "Input module not found: " << input_module.value();
return false;
}
if (!allow_overwrite && base::PathExists(output_module)) {
LOG(ERROR) << "Output module exists: " << output_module.value();
LOG(ERROR) << "Specify --overwrite to ignore this error.";
return false;
}
// If no input PDB was specified then search for it.
if (input_pdb->empty()) {
LOG(INFO) << "Input PDB not specified, searching for it.";
if (!pe::FindPdbForModule(input_module, input_pdb) ||
input_pdb->empty()) {
LOG(ERROR) << "Unable to find PDB file for module: "
<< input_module.value();
return NULL;
}
}
if (!base::PathExists(*input_pdb)) {
LOG(ERROR) << "Input PDB not found: " << input_pdb->value();
return false;
}
// If no output PDB path is specified, infer one.
if (output_pdb->empty()) {
// If the input and output DLLs have the same basename, default to writing
// using the same PDB basename, but alongside the new module.
if (input_module.BaseName() == output_module.BaseName()) {
*output_pdb = output_module.DirName().Append(input_pdb->BaseName());
} else {
// Otherwise, default to using the output basename with a PDB extension
// added to it.
*output_pdb = output_module.AddExtension(L"pdb");
}
LOG(INFO) << "Using default output PDB path: " << output_pdb->value();
}
if (!allow_overwrite && base::PathExists(*output_pdb)) {
LOG(ERROR) << "Output PDB exists: " << output_pdb->value();
LOG(ERROR) << "Specify --overwrite to ignore this error.";
return false;
}
// Perform some extra checking to make sure that writes aren't going to
// collide. This prevents us from overwriting the input, effectively
// preventing in-place transforms. This is not fool-proof in the face of
// weird junctions but it will catch common errors.
core::FilePathCompareResult result =
core::CompareFilePaths(input_module, output_module);
if (result == core::kEquivalentFilePaths) {
LOG(ERROR) << "Input and output module paths are equivalent.";
LOG(ERROR) << "Input module path: " << input_module.value();
LOG(ERROR) << "Output module path: " << output_module.value();
return false;
}
result = core::CompareFilePaths(*input_pdb, *output_pdb);
if (result == core::kEquivalentFilePaths) {
LOG(ERROR) << "Input and output PDB paths are equivalent.";
LOG(ERROR) << "Input PDB path: " << input_pdb->value();
LOG(ERROR) << "Output PDB path: " << output_pdb->value();
return false;
}
result = core::CompareFilePaths(output_module, *output_pdb);
if (result == core::kEquivalentFilePaths) {
LOG(ERROR) << "Output module and PDB paths are equivalent.";
LOG(ERROR) << "Output module path: " << output_module.value();
LOG(ERROR) << "Output PDB path: " << output_pdb->value();
return false;
}
return true;
}
bool FinalizeBlockGraph(const base::FilePath& input_module,
const base::FilePath& output_pdb,
const GUID& pdb_guid,
bool add_metadata,
const PETransformPolicy* policy,
BlockGraph* block_graph,
BlockGraph::Block* dos_header_block) {
DCHECK_NE(reinterpret_cast<PETransformPolicy*>(NULL), policy);
DCHECK_NE(reinterpret_cast<BlockGraph*>(NULL), block_graph);
DCHECK_NE(reinterpret_cast<BlockGraph::Block*>(NULL), dos_header_block);
LOG(INFO) << "Finalizing block-graph for \"" << input_module.value() << "\".";
std::vector<BlockGraphTransformInterface*> post_transforms;
pe::transforms::AddMetadataTransform add_metadata_tx(input_module);
pe::transforms::AddPdbInfoTransform add_pdb_info_tx(output_pdb, 1,
pdb_guid);
pe::transforms::PERemoveEmptySectionsTransform remove_empty_sections;
pe::transforms::PEPrepareHeadersTransform prep_headers_tx;
if (add_metadata)
post_transforms.push_back(&add_metadata_tx);
post_transforms.push_back(&add_pdb_info_tx);
post_transforms.push_back(&remove_empty_sections);
post_transforms.push_back(&prep_headers_tx);
if (!block_graph::ApplyBlockGraphTransforms(post_transforms,
policy,
block_graph,
dos_header_block)) {
return false;
}
return true;
}
bool FinalizeOrderedBlockGraph(
OrderedBlockGraph* ordered_block_graph,
BlockGraph::Block* dos_header_block) {
DCHECK_NE(reinterpret_cast<OrderedBlockGraph*>(NULL), ordered_block_graph);
DCHECK_NE(reinterpret_cast<BlockGraph::Block*>(NULL), dos_header_block);
pe::orderers::PEOrderer pe_orderer;
if (!pe_orderer.OrderBlockGraph(ordered_block_graph, dos_header_block))
return false;
return true;
}
bool BuildImageLayout(size_t padding,
size_t code_alignment,
const OrderedBlockGraph& ordered_block_graph,
BlockGraph::Block* dos_header_block,
ImageLayout* image_layout) {
DCHECK_NE(reinterpret_cast<BlockGraph::Block*>(NULL), dos_header_block);
DCHECK_NE(reinterpret_cast<ImageLayout*>(NULL), image_layout);
LOG(INFO) << "Building image layout.";
PEImageLayoutBuilder builder(image_layout);
builder.set_padding(padding);
builder.set_code_alignment(code_alignment);
if (!builder.LayoutImageHeaders(dos_header_block)) {
LOG(ERROR) << "PEImageLayoutBuilder::LayoutImageHeaders failed.";
return false;
}
if (!builder.LayoutOrderedBlockGraph(ordered_block_graph)) {
LOG(ERROR) << "PEImageLayoutBuilder::LayoutOrderedBlockGraph failed.";
return false;
}
LOG(INFO) << "Finalizing image layout.";
if (!builder.Finalize()) {
LOG(ERROR) << "PEImageLayoutBuilder::Finalize failed.";
return false;
}
return true;
}
void GetOmapRange(const std::vector<ImageLayout::SectionInfo>& sections,
RelativeAddressRange* range) {
DCHECK_NE(reinterpret_cast<RelativeAddressRange*>(NULL), range);
// There need to be at least two sections, one containing something and the
// other containing the relocs.
DCHECK_GT(sections.size(), 1u);
DCHECK_EQ(sections.back().name, std::string(kRelocSectionName));
// For some reason, if we output OMAP entries for the headers (before the
// first section), everything falls apart. Not outputting these allows the
// unittests to pass. Also, we don't want to output OMAP information for
// the relocs, as these are entirely different from image to image.
RelativeAddress start_of_image = sections.front().addr;
RelativeAddress end_of_image = sections.back().addr;
*range = RelativeAddressRange(start_of_image, end_of_image - start_of_image);
}
bool FinalizePdbFile(const base::FilePath input_module,
const base::FilePath output_module,
const RelativeAddressRange input_range,
const ImageLayout& image_layout,
const GUID& guid,
bool augment_pdb,
bool strip_strings,
bool compress_pdb,
pdb::PdbFile* pdb_file) {
DCHECK(pdb_file != NULL);
LOG(INFO) << "Finalizing PDB file.";
VLOG(1) << "Updating GUID.";
if (!pdb::SetGuid(guid, pdb_file)) {
LOG(ERROR) << "Unable to set PDB GUID.";
return false;
}
VLOG(1) << "Building OMAP vectors.";
std::vector<OMAP> omap_to, omap_from;
BuildOmapVectors(input_range, image_layout, &omap_to, &omap_from);
VLOG(1) << "Writing OMAP vectors.";
if (!pdb::SetOmapToStream(omap_to, pdb_file)) {
LOG(ERROR) << "Unable to set OMAP_TO.";
return false;
}
if (!pdb::SetOmapFromStream(omap_from, pdb_file)) {
LOG(ERROR) << "Unable to set OMAP_FROM.";
return false;
}
// Parse the header and named streams.
pdb::PdbInfoHeader70 header = {};
pdb::NameStreamMap name_stream_map;
if (!pdb::ReadHeaderInfoStream(*pdb_file, &header, &name_stream_map))
return false;
// Update/create the Syzygy history stream.
VLOG(1) << "Adding history stream to PDB.";
if (!WriteSyzygyHistoryStream(input_module, &name_stream_map, pdb_file))
return false;
// Add redecomposition data in another stream, only if augment_pdb_ is set.
if (augment_pdb) {
PEFile new_pe_file;
if (!new_pe_file.Init(output_module)) {
LOG(ERROR) << "Failed to read newly written PE file.";
return false;
}
VLOG(1) << "Adding serialized block-graph stream to PDB.";
if (!WriteSyzygyBlockGraphStream(new_pe_file,
image_layout,
strip_strings,
compress_pdb,
&name_stream_map,
pdb_file)) {
return false;
}
}
// Write the updated name-stream map back to the header info stream.
VLOG(1) << "Updating PDB headers.";
if (!pdb::WriteHeaderInfoStream(header, name_stream_map, pdb_file))
return false;
// Stream 0 contains a copy of the previous PDB's directory. This, combined
// with copy-on-write semantics of individual blocks makes the file contain
// its whole edit history. Since we're writing a 'new' PDB file (we reset the
// GUID and age), we have no history so can safely throw away this stream.
VLOG(1) << "Removing previous PDB directory stream.";
pdb_file->ReplaceStream(0, NULL);
return true;
}
} // namespace pe