blob: 2f4d424804c59c2fb9a6a8903d5d4a54c3e3bf7e [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/reorder/linear_order_generator.h"
#include <algorithm>
namespace reorder {
namespace {
// This is effectively arbitrarily chosen for now. Higher is better from
// benchmarked reorderings. This value is effectively 'infinite' as far as the
// chrome data goes.
const size_t kDataRecursionDepth = 100;
typedef LinearOrderGenerator::BlockCall BlockCall;
// Comparator for sorting BlockCalls by increasing time.
struct BlockCallSortIncrTime {
bool operator()(const BlockCall& bc1, const BlockCall& bc2) {
return bc1.time < bc2.time;
}
};
// Used for aggregating block call information across multiple runs.
struct AverageBlockCall {
const block_graph::BlockGraph::Block* block;
size_t sum_order;
// The number of runs of the instrumented binary in which this block was
// seen, NOT the number of times it was seen called in aggregate.
size_t call_count;
// This is only meaningful if call_count == 1.
uint32 process_group_id;
double AverageOrder() const {
DCHECK_LT(0U, call_count);
return static_cast<double>(sum_order) / call_count;
}
};
// Sorts by decreasing call count. Anything with more than one call count
// is sorted with a secondary key of increasing order. Anything with a single
// call count has a secondary key of process_group_id, and a tertiary key of
// increasing order.
struct AverageBlockCallSort {
bool operator()(const AverageBlockCall& abc1, const AverageBlockCall& abc2) {
if (abc1.call_count != abc2.call_count)
return abc1.call_count > abc2.call_count;
if (abc1.call_count > 1 || abc1.process_group_id == abc2.process_group_id)
return abc1.AverageOrder() < abc2.AverageOrder();
return abc1.process_group_id < abc2.process_group_id;
}
};
// Extract the values of a map to a vector.
template<typename K, typename V> void MapToVector(const std::map<K, V>& map,
std::vector<V>* vector) {
DCHECK(vector != NULL);
vector->clear();
std::map<K, V>::const_iterator it = map.begin();
for (; it != map.end(); ++it)
vector->push_back(it->second);
}
} // namespace
LinearOrderGenerator::LinearOrderGenerator()
: Reorderer::OrderGenerator("Linear Order Generator"),
active_process_count_(0),
next_process_group_id_(0) {
}
LinearOrderGenerator::~LinearOrderGenerator() {
}
bool LinearOrderGenerator::OnProcessStarted(uint32 process_id,
const UniqueTime& time) {
if (active_process_count_ == 0) {
if (!CloseProcessGroup())
return false;
}
++active_process_count_;
return true;
}
bool LinearOrderGenerator::OnProcessEnded(uint32 process_id,
const UniqueTime& time) {
DCHECK_LT(0U, active_process_count_);
--active_process_count_;
return true;
}
bool LinearOrderGenerator::OnCodeBlockEntry(const BlockGraph::Block* block,
RelativeAddress address,
uint32 process_id,
uint32 thread_id,
const UniqueTime& time) {
return TouchBlock(BlockCall(block, process_id, thread_id, time));
}
bool LinearOrderGenerator::CalculateReordering(const PEFile& pe_file,
const ImageLayout& image,
bool reorder_code,
bool reorder_data,
Order* order) {
DCHECK(order != NULL);
// Ensure the last running process group is closed.
if (!CloseProcessGroup())
return false;
LOG(INFO) << "Encountered " << process_group_calls_.size()
<< " process groups.";
// Aggregate the block calls.
std::map<const BlockGraph::Block*, AverageBlockCall> average_block_call_map;
ProcessGroupBlockCalls::const_iterator it = process_group_calls_.begin();
for (; it != process_group_calls_.end(); ++it) {
for (size_t i = 0; i < it->second.size(); ++i) {
const BlockCall& block_call = it->second[i];
AverageBlockCall& average_block_call =
average_block_call_map[block_call.block];
average_block_call.block = block_call.block;
average_block_call.sum_order += i;
++average_block_call.call_count;
average_block_call.process_group_id = it->first;
}
}
// Now create a sorted list.
std::vector<AverageBlockCall> average_block_calls;
MapToVector(average_block_call_map, &average_block_calls);
average_block_call_map.clear();
std::sort(average_block_calls.begin(), average_block_calls.end(),
AverageBlockCallSort());
// TODO(chrisha): Create an option to evenly distribute the common startup
// blocks (those with call_count == process_group_calls_.size()) among
// the remaining blocks.
// Initialize the section list and ordering meta data.
order->comment = "Linear ordering by earliest appearance";
order->sections.clear();
order->sections.resize(image.sections.size());
for (size_t i = 0; i < image.sections.size(); ++i) {
order->sections[i].id = i;
order->sections[i].name = image.sections[i].name;
order->sections[i].characteristics = image.sections[i].characteristics;
}
// Create the ordering from this list.
BlockSet inserted_blocks;
for (size_t i = 0; i < average_block_calls.size(); ++i) {
const BlockGraph::Block* code_block = average_block_calls[i].block;
if (reorder_code) {
order->sections[code_block->section()].blocks.push_back(
Order::BlockSpec(code_block));
inserted_blocks.insert(code_block);
}
// Create an analogous data ordering if we were asked to.
if (reorder_data) {
if (!InsertDataBlocks(kDataRecursionDepth, code_block, order,
&inserted_blocks))
return false;
}
}
// Add the remaining blocks in each section to the order.
for (size_t section_index = 0; ; ++section_index) {
const IMAGE_SECTION_HEADER* section =
pe_file.section_header(section_index);
if (section == NULL)
break;
RelativeAddress section_start = RelativeAddress(section->VirtualAddress);
AddressSpace::RangeMapConstIterPair section_blocks =
image.blocks.GetIntersectingBlocks(
section_start, section->Misc.VirtualSize);
AddressSpace::RangeMapConstIter& section_it = section_blocks.first;
const AddressSpace::RangeMapConstIter& section_end = section_blocks.second;
for (; section_it != section_end; ++section_it) {
BlockGraph::Block* block = section_it->second;
if (inserted_blocks.count(block) > 0)
continue;
order->sections[section_index].blocks.push_back(Order::BlockSpec(block));
}
}
return true;
}
bool LinearOrderGenerator::TouchBlock(const BlockCall& block_call) {
DCHECK(block_call.block != NULL);
// All code blocks should belong to a defined section.
DCHECK_NE(pe::kInvalidSection, block_call.block->section());
// Store the block along with the earliest time it was called.
BlockCallMap::iterator it = block_call_map_.find(block_call.block);
if (it == block_call_map_.end()) {
std::pair<BlockCallMap::iterator, bool> insert_return;
insert_return = block_call_map_.insert(
std::make_pair(block_call.block, block_call));
it = insert_return.first;
DCHECK(insert_return.second);
DCHECK(it != block_call_map_.end());
} else {
// Keep around the earliest call to this block only.
if (block_call.time < it->second.time)
it->second = block_call;
}
return true;
}
bool LinearOrderGenerator::InsertDataBlocks(size_t max_recursion_depth,
const BlockGraph::Block* block,
Order* order,
BlockSet* inserted_blocks) {
DCHECK(block != NULL);
DCHECK(order != NULL);
// Stop the recursion.
if (max_recursion_depth == 0)
return true;
block_graph::ConstBlockVector data_blocks;
// Iterate through any data blocks that are referenced by this
// block, and also store them with the same time. This is a pessimistic
// optimization, and assumes that all data linked to a code block will
// be touched by that code block (and all data linked to by that data block,
// and so on, up to 'max_recursion_depth').
BlockGraph::Block::ReferenceMap::const_iterator ref_it =
block->references().begin();
for (; ref_it != block->references().end(); ++ref_it) {
const BlockGraph::Block* ref = ref_it->second.referenced();
DCHECK(ref != NULL);
// We only touch data blocks with a valid section id.
if (ref->type() != BlockGraph::DATA_BLOCK ||
ref->section() == pe::kInvalidSection)
continue;
// Only insert data blocks that have not yet been seen.
if (!inserted_blocks->insert(ref).second)
continue;
// Finally, insert this block to the appropriate section ordering.
order->sections[ref->section()].blocks.push_back(Order::BlockSpec(ref));
data_blocks.push_back(ref);
}
// Recurse on the data blocks we just added.
if (max_recursion_depth > 1) {
for (size_t i = 0; i < data_blocks.size(); ++i) {
if (!InsertDataBlocks(max_recursion_depth - 1, data_blocks[i], order,
inserted_blocks))
return false;
}
}
return true;
}
bool LinearOrderGenerator::CloseProcessGroup() {
if (block_call_map_.empty())
return true;
BlockCalls& block_calls = process_group_calls_[next_process_group_id_];
++next_process_group_id_;
MapToVector(block_call_map_, &block_calls);
block_call_map_.clear();
std::sort(block_calls.begin(), block_calls.end(), BlockCallSortIncrTime());
return true;
}
} // namespace reorder