blob: f1fcce6637f74e4d2f68add03b82d29bbb77648a [file] [log] [blame]
// Copyright 2012 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/relink/relink_app.h"
#include "base/logging_win.h"
#include "base/string_number_conversions.h"
#include "syzygy/block_graph/orderers/original_orderer.h"
#include "syzygy/block_graph/orderers/random_orderer.h"
#include "syzygy/pe/pe_relinker.h"
#include "syzygy/pe/transforms/explode_basic_blocks_transform.h"
#include "syzygy/reorder/orderers/explicit_orderer.h"
#include "syzygy/reorder/transforms/basic_block_layout_transform.h"
namespace relink {
namespace {
const char kUsageFormatStr[] =
"Usage: %ls [options]\n"
" Required Options:\n"
" --input-image=<path> The input image file to relink.\n"
" --output-image=<path> Output path for the rewritten image file.\n"
" Optional Options:\n"
" --basic-blocks Reorder at the basic-block level. At present,\n"
" this is only supported for random reorderings.\n"
" --compress-pdb If --no-augment-pdb is specified, causes the\n"
" augmented PDB stream to be compressed.\n"
" --exclude-bb-padding When randomly reordering basic blocks, exclude\n"
" padding and unreachable code from the relinked\n"
" output binary.\n"
" --input-pdb=<path> The PDB file associated with the input DLL.\n"
" Default is inferred from input-image.\n"
" --no-augment-pdb Indicates that the relinker should not augment\n"
" the PDB with roundtrip decomposition info.\n"
" --no-metadata Prevents the relinker from adding metadata\n"
" to the output DLL.\n"
" --no-strip-strings Causes strings to be output in the augmented\n"
" PDB stream. The default is to omit these to\n"
" make smaller PDBs.\n"
" --order-file=<path> Reorder based on a JSON ordering file.\n"
" --output-pdb=<path> Output path for the rewritten PDB file.\n"
" Default is inferred from output-image.\n"
" --overwrite Allow output files to be overwritten.\n"
" --padding=<integer> Add bytes of padding between blocks.\n"
" --seed=<integer> Randomly reorder based on the given seed.\n"
" Deprecated Options:\n"
" --input-dll=<path> Aliased to --input-image.\n"
" --output-dll=<path> Aliased to --output-image.\n"
" Notes:\n"
" * The --seed and --order-file options are mutually exclusive\n"
" * If --order-file is specified, --input-image is optional.\n"
" * The --compress-pdb and --no-strip-strings options are only\n"
" effective if --no-augment-pdb is not specified.\n"
" * The --exclude-bb-padding option is only effective if\n"
" --basic-blocks is specified.\n";
bool ParsePadding(const std::wstring& value_str, size_t* out_value) {
DCHECK(out_value != NULL);
int temp;
if (!base::StringToInt(value_str, &temp) || temp < 0) {
return false;
}
*out_value = static_cast<size_t>(temp);
return true;
}
bool ParseUInt32(const std::wstring& value_str, uint32* out_value) {
DCHECK(out_value != NULL);
return base::StringToInt(value_str, reinterpret_cast<int*>(out_value));
}
void GuessPdbPath(const FilePath& module_path, FilePath* pdb_path) {
DCHECK(pdb_path != NULL);
*pdb_path = module_path.ReplaceExtension(L"pdb");
}
} // namespace
bool RelinkApp::ParseCommandLine(const CommandLine* cmd_line) {
input_image_path_ = AbsolutePath(cmd_line->GetSwitchValuePath("input-image"));
if (cmd_line->HasSwitch("input-dll")) {
if (input_image_path_.empty()) {
LOG(WARNING) << "Using deprecated switch --input-dll.";
input_image_path_ =
AbsolutePath(cmd_line->GetSwitchValuePath("input-dll"));
} else {
return Usage(cmd_line,
"Can't specify both --input-dll and --input-image.");
}
}
input_pdb_path_ = AbsolutePath(cmd_line->GetSwitchValuePath("input-pdb"));
output_image_path_ = cmd_line->GetSwitchValuePath("output-image");
if (cmd_line->HasSwitch("output-dll")) {
if (output_image_path_.empty()) {
LOG(WARNING) << "Using deprecated switch --output-dll.";
output_image_path_ =
AbsolutePath(cmd_line->GetSwitchValuePath("output-dll"));
} else {
return Usage(cmd_line,
"Can't specify both --output-dll and --output-image.");
}
}
output_pdb_path_ = cmd_line->GetSwitchValuePath("output-pdb");
order_file_path_ = AbsolutePath(cmd_line->GetSwitchValuePath("order-file"));
no_augment_pdb_ = cmd_line->HasSwitch("no-augment-pdb");
compress_pdb_ = cmd_line->HasSwitch("compress-pdb");
no_strip_strings_ = cmd_line->HasSwitch("no-strip-strings");
output_metadata_ = !cmd_line->HasSwitch("no-metadata");
overwrite_ = cmd_line->HasSwitch("overwrite");
basic_blocks_ = cmd_line->HasSwitch("basic-blocks");
exclude_bb_padding_ = cmd_line->HasSwitch("exclude-bb-padding");
// The --output-image argument is required.
if (output_image_path_.empty()) {
return Usage(cmd_line, "You must specify --output-image.");
}
// Ensure that we have an input-image, either explicity specified, or to be
// taken from an order file.
if (input_image_path_.empty() && order_file_path_.empty()) {
return Usage(
cmd_line,
"You must specify --input-image if --order-file is not given.");
}
// Parse the random seed, if given. Note that the --seed and --order-file
// arguments are mutually exclusive.
if (cmd_line->HasSwitch("seed")) {
if (cmd_line->HasSwitch("order-file")) {
return Usage(cmd_line,
"The seed and order-file arguments are mutually exclusive.");
}
std::wstring seed_str(cmd_line->GetSwitchValueNative("seed"));
if (!ParseUInt32(seed_str, &seed_))
return Usage(cmd_line, "Invalid seed value.");
}
// Parse the padding argument.
if (cmd_line->HasSwitch("padding")) {
std::wstring padding_str(cmd_line->GetSwitchValueNative("padding"));
if (!ParsePadding(padding_str, &padding_))
return Usage(cmd_line, "Invalid padding value.");
}
return true;
}
bool RelinkApp::SetUp() {
if (input_image_path_.empty()) {
DCHECK(!order_file_path_.empty());
if (!reorder::Reorderer::Order::GetOriginalModulePath(order_file_path_,
&input_image_path_)) {
LOG(ERROR) << "Unable to infer input-image.";
return false;
}
LOG(INFO) << "Inferring input DLL path from order file: "
<< input_image_path_.value();
}
DCHECK(!input_image_path_.empty());
DCHECK(!output_image_path_.empty());
DCHECK(order_file_path_.empty() || seed_ == 0);
return true;
}
int RelinkApp::Run() {
pe::PERelinker relinker;
relinker.set_input_path(input_image_path_);
relinker.set_input_pdb_path(input_pdb_path_);
relinker.set_output_path(output_image_path_);
relinker.set_output_pdb_path(output_pdb_path_);
relinker.set_padding(padding_);
relinker.set_add_metadata(output_metadata_);
relinker.set_allow_overwrite(overwrite_);
relinker.set_augment_pdb(!no_augment_pdb_);
relinker.set_compress_pdb(compress_pdb_);
relinker.set_strip_strings(!no_strip_strings_);
// Initialize the relinker. This does the decomposition, etc.
if (!relinker.Init()) {
LOG(ERROR) << "Failed to initialize relinker.";
return 1;
}
// Transforms that may be used.
scoped_ptr<pe::transforms::ExplodeBasicBlocksTransform> bb_explode;
scoped_ptr<reorder::transforms::BasicBlockLayoutTransform> bb_layout;
// Orderers that may be used.
reorder::Reorderer::Order order;
scoped_ptr<block_graph::orderers::OriginalOrderer> orig_orderer;
scoped_ptr<block_graph::BlockGraphOrdererInterface> orderer;
// If an order file is provided we are performing an explicit ordering.
if (!order_file_path_.empty()) {
if (!order.LoadFromJSON(relinker.input_pe_file(),
relinker.input_image_layout(),
order_file_path_)) {
LOG(ERROR) << "Failed to load order file: " << order_file_path_.value();
return 1;
}
// Allocate a BB layout transform. This applies the basic block portion of
// the order specification. It will modify it in place so that it is ready
// to be used by the ExplicitOrderer to finish the job.
bb_layout.reset(new reorder::transforms::BasicBlockLayoutTransform(&order));
relinker.AppendTransform(bb_layout.get());
// Append an OriginalOrderer to the relinker. We do this so that the
// original order is preserved entirely for sections that are not
// fully specified by the order file, and therefore not ordered by the
// ExplicitOrderer.
orig_orderer.reset(new block_graph::orderers::OriginalOrderer());
relinker.AppendOrderer(orig_orderer.get());
// Allocate an explicit orderer.
orderer.reset(new reorder::orderers::ExplicitOrderer(&order));
} else {
// No order file was provided, so we're doing a random ordering.
// If we've been asked to go down to the basic block level, then we want to
// use an explode basic blocks transform so that we randomize the entire
// image at the BB level.
if (basic_blocks_) {
bb_explode.reset(new pe::transforms::ExplodeBasicBlocksTransform());
bb_explode->set_exclude_padding(exclude_bb_padding_);
relinker.AppendTransform(bb_explode.get());
}
// Allocate the random block orderer.
orderer.reset(new block_graph::orderers::RandomOrderer(true, seed_));
}
// Append the orderer to the relinker.
relinker.AppendOrderer(orderer.get());
// Perform the actual relink.
if (!relinker.Relink()) {
LOG(ERROR) << "Unable to relink input image.";
return 1;
}
return 0;
}
bool RelinkApp::Usage(const CommandLine* cmd_line,
const base::StringPiece& message) const {
if (!message.empty()) {
::fwrite(message.data(), 1, message.length(), err());
::fprintf(err(), "\n\n");
}
::fprintf(err(),
kUsageFormatStr,
cmd_line->GetProgram().BaseName().value().c_str());
return false;
}
} // namespace relink