blob: 342b541cf58ed136f3f826c359c2948a0270b85e [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.
//
// Implements the JumpTableCaseCountTransform class.
#include "syzygy/instrument/transforms/jump_table_count_transform.h"
#include <limits>
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "syzygy/block_graph/block_builder.h"
#include "syzygy/block_graph/block_util.h"
#include "syzygy/common/defs.h"
#include "syzygy/common/indexed_frequency_data.h"
#include "syzygy/instrument/transforms/entry_thunk_transform.h"
#include "syzygy/pe/pe_utils.h"
#include "syzygy/pe/transforms/pe_add_imports_transform.h"
namespace instrument {
namespace transforms {
namespace {
using block_graph::BasicBlock;
using block_graph::BasicBlockSubGraph;
using block_graph::BasicCodeBlock;
using block_graph::BasicDataBlock;
using block_graph::BasicBlockAssembler;
using block_graph::BasicBlockReference;
using block_graph::BlockBuilder;
using block_graph::BlockGraph;
using block_graph::Displacement;
using block_graph::Immediate;
using block_graph::Instruction;
using block_graph::Operand;
using block_graph::TransformPolicyInterface;
using pe::transforms::PEAddImportsTransform;
using pe::transforms::ImportedModule;
const char kDefaultModuleName[] = "basic_block_entry_client.dll";
const char kJumpTableCaseCounter[] = "_increment_indexed_freq_data";
const char kThunkSuffix[] = "_jump_table_thunk";
// Sets up the jump table counter hook import.
// @param policy The policy object restricting how the transform is applied.
// @param block_graph The block-graph to populate.
// @param header_block The header block from block_graph.
// @param module_name The name of the module implementing the hooks.
// @param jump_table_case_counter will refer to the imported hook function.
// @returns true on success, false otherwise.
bool SetupCounterHook(const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BlockGraph::Block* header_block,
const std::string& module_name,
BlockGraph::Reference* jump_table_case_counter) {
DCHECK(block_graph != NULL);
DCHECK(header_block != NULL);
DCHECK(jump_table_case_counter != NULL);
// Setup the import module.
ImportedModule module(module_name);
size_t index_case_counter = module.AddSymbol(
kJumpTableCaseCounter,
ImportedModule::kAlwaysImport);
// Setup the add-imports transform.
PEAddImportsTransform add_imports;
add_imports.AddModule(&module);
// Add the imports to the block-graph.
if (!ApplyBlockGraphTransform(
&add_imports, policy, block_graph, header_block)) {
LOG(ERROR) << "Unable to add import entry for jump table hook functions.";
return false;
}
// Get a reference to the hook function.
if (!module.GetSymbolReference(index_case_counter, jump_table_case_counter)) {
LOG(ERROR) << "Unable to get jump table hooks.";
return false;
}
DCHECK(jump_table_case_counter->IsValid());
return true;
}
} // namespace
const char JumpTableCaseCountTransform::kTransformName[] =
"JumpTableCountTransform";
JumpTableCaseCountTransform::JumpTableCaseCountTransform()
: add_frequency_data_(common::kJumpTableCountAgentId,
"Jump Table Frequency Data",
common::kJumpTableFrequencyDataVersion,
common::IndexedFrequencyData::JUMP_TABLE,
sizeof(common::IndexedFrequencyData)),
instrument_dll_name_(kDefaultModuleName),
jump_table_case_count_(0) {
}
bool JumpTableCaseCountTransform::PreBlockGraphIteration(
const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BlockGraph::Block* header_block) {
DCHECK_NE(reinterpret_cast<TransformPolicyInterface*>(NULL), policy);
DCHECK_NE(reinterpret_cast<BlockGraph*>(NULL), block_graph);
DCHECK_NE(reinterpret_cast<BlockGraph::Block*>(NULL), header_block);
DCHECK_EQ(BlockGraph::PE_IMAGE, block_graph->image_format());
// Setup the jump table counter entry hook.
if (!SetupCounterHook(policy,
block_graph,
header_block,
instrument_dll_name_,
&jump_table_case_counter_hook_ref_)) {
return false;
}
// Add the static jump table count frequency data.
if (!ApplyBlockGraphTransform(&add_frequency_data_,
policy,
block_graph,
header_block)) {
LOG(ERROR) << "Failed to insert jump table count frequency data.";
return false;
}
// Find or create the section we put our thunks in.
thunk_section_ = block_graph->FindOrAddSection(common::kThunkSectionName,
pe::kCodeCharacteristics);
DCHECK(thunk_section_ != NULL);
return true;
}
bool JumpTableCaseCountTransform::OnBlock(
const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BlockGraph::Block* block) {
DCHECK(policy != NULL);
DCHECK(block_graph != NULL);
DCHECK(block != NULL);
if (block->type() != BlockGraph::CODE_BLOCK)
return true;
// Iterate over the labels of the block to find the jump tables.
for (BlockGraph::Block::LabelMap::const_iterator iter_label(
block->labels().begin());
iter_label != block->labels().end();
++iter_label) {
if (!iter_label->second.has_attributes(BlockGraph::JUMP_TABLE_LABEL))
continue;
size_t table_size = 0;
if (!block_graph::GetJumpTableSize(block, iter_label, &table_size))
return false;
jump_table_infos_.push_back(
std::make_pair(block->addr() + iter_label->first, table_size));
BlockGraph::Block::ReferenceMap::const_iterator iter_ref =
block->references().find(iter_label->first);
// Iterate over the references and thunk them.
for (size_t i = 0; i < table_size; ++i) {
DCHECK(iter_ref != block->references().end());
BlockGraph::Block* thunk_block = CreateOneThunk(block_graph,
iter_ref->second);
if (thunk_block == NULL) {
jump_table_infos_.pop_back();
return false;
}
BlockGraph::Reference thunk_ref(BlockGraph::ABSOLUTE_REF,
sizeof(iter_ref->second.size()),
thunk_block, 0, 0);
block->SetReference(iter_ref->first, thunk_ref);
++iter_ref;
}
}
return true;
}
bool JumpTableCaseCountTransform::PostBlockGraphIteration(
const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BlockGraph::Block* header_block) {
DCHECK(policy != NULL);
DCHECK(block_graph != NULL);
DCHECK(header_block != NULL);
if (jump_table_case_count_ == 0) {
LOG(INFO) << "Encountered no jump tables during instrumentation.";
return true;
}
if (!add_frequency_data_.ConfigureFrequencyDataBuffer(jump_table_case_count_,
1, sizeof(uint32_t))) {
LOG(ERROR) << "Failed to configure frequency data buffer.";
return false;
}
// Add the module entry thunks.
EntryThunkTransform add_thunks;
add_thunks.set_only_instrument_module_entry(true);
add_thunks.set_instrument_dll_name(instrument_dll_name_);
auto module_data(Immediate(add_frequency_data_.frequency_data_block(), 0));
if (!add_thunks.SetEntryThunkParameter(module_data)) {
LOG(ERROR) << "Failed to configure the entry thunks with the module_data "
<< "parameter.";
return false;
}
if (!ApplyBlockGraphTransform(
&add_thunks, policy, block_graph, header_block)) {
LOG(ERROR) << "Unable to thunk module entry points.";
return false;
}
return true;
}
BlockGraph::Block* JumpTableCaseCountTransform::CreateOneThunk(
BlockGraph* block_graph,
const BlockGraph::Reference& destination) {
// Construct the name for the new thunk.
std::string thunk_name(destination.referenced()->name() + kThunkSuffix);
auto jump_table_case_counter_hook(
Operand(Displacement(jump_table_case_counter_hook_ref_.referenced(),
jump_table_case_counter_hook_ref_.offset())));
// Construct the thunk basic block.
BasicBlockSubGraph bbsg;
BasicBlockSubGraph::BlockDescription* block_desc = bbsg.AddBlockDescription(
thunk_name,
NULL,
BlockGraph::CODE_BLOCK,
thunk_section_->id(),
1,
0);
BasicCodeBlock* bb = bbsg.AddBasicCodeBlock(thunk_name);
block_desc->basic_block_order.push_back(bb);
BasicBlockAssembler assm(bb->instructions().begin(), &bb->instructions());
DCHECK_LT(jump_table_case_count_, std::numeric_limits<size_t>::max());
assm.push(Immediate(jump_table_case_count_++, assm::kSize32Bit));
assm.call(jump_table_case_counter_hook);
assm.jmp(Immediate(destination.referenced(), destination.offset()));
// Condense into a block.
BlockBuilder block_builder(block_graph);
if (!block_builder.Merge(&bbsg)) {
LOG(ERROR) << "Failed to build thunk block.";
return NULL;
}
// Exactly one new block should have been created.
DCHECK_EQ(1u, block_builder.new_blocks().size());
return block_builder.new_blocks().front();
}
} // namespace transforms
} // namespace instrument