blob: 263a6fdbd6589b1ce93ddd9ae1a1d074db404c02 [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.
//
// Basic-block entry hook instrumentation transform unit-tests.
#include "syzygy/instrument/transforms/basic_block_entry_hook_transform.h"
#include "gtest/gtest.h"
#include "syzygy/agent/basic_block_entry/basic_block_entry.h"
#include "syzygy/block_graph/basic_block.h"
#include "syzygy/block_graph/basic_block_decomposer.h"
#include "syzygy/block_graph/basic_block_subgraph.h"
#include "syzygy/block_graph/block_graph.h"
#include "syzygy/block_graph/typed_block.h"
#include "syzygy/common/indexed_frequency_data.h"
#include "syzygy/core/unittest_util.h"
#include "syzygy/instrument/transforms/unittest_util.h"
#include "syzygy/pe/unittest_util.h"
#include "mnemonics.h" // NOLINT
namespace instrument {
namespace transforms {
namespace {
using agent::basic_block_entry::BasicBlockEntry;
using block_graph::BasicBlock;
using block_graph::BasicBlockDecomposer;
using block_graph::BasicBlockSubGraph;
using block_graph::BasicCodeBlock;
using block_graph::BlockGraph;
using block_graph::Instruction;
using common::IndexedFrequencyData;
using common::kBasicBlockEntryAgentId;
using common::kBasicBlockFrequencyDataVersion;
typedef BasicBlockEntry::BasicBlockIndexedFrequencyData
BasicBlockIndexedFrequencyData;
class TestBasicBlockEntryHookTransform : public BasicBlockEntryHookTransform {
public:
using BasicBlockEntryHookTransform::bb_entry_hook_ref_;
using BasicBlockEntryHookTransform::thunk_section_;
BlockGraph::Block* frequency_data_block() {
return add_frequency_data_.frequency_data_block();
}
BlockGraph::Block* frequency_data_buffer_block() {
return add_frequency_data_.frequency_data_buffer_block();
}
};
class BasicBlockEntryHookTransformTest : public testing::TestDllTransformTest {
public:
enum InstrumentationKind {
kAgentInstrumentation,
kFastPathInstrumentation
};
void CheckBasicBlockInstrumentation(InstrumentationKind kind);
protected:
TestBasicBlockEntryHookTransform tx_;
};
void BasicBlockEntryHookTransformTest::CheckBasicBlockInstrumentation(
InstrumentationKind kind) {
// Let's examine each eligible block to verify that its basic blocks have been
// instrumented.
size_t num_decomposed_blocks = 0;
size_t total_basic_blocks = 0;
BlockGraph::BlockMap::const_iterator block_iter =
block_graph_.blocks().begin();
for (; block_iter != block_graph_.blocks().end(); ++block_iter) {
const BlockGraph::Block& block = block_iter->second;
// Skip non-code blocks.
if (block.type() != BlockGraph::CODE_BLOCK)
continue;
// We'll skip thunks, they're a mixed bag of things.
if (block.section() == tx_.thunk_section_->id())
continue;
// Blocks which are not bb-decomposable should be thunked. While there may
// be some internal referrers, the only external referrers should be thunks.
if (!policy_->BlockIsSafeToBasicBlockDecompose(&block)) {
size_t num_external_thunks = 0;
BlockGraph::Block::ReferrerSet::const_iterator ref_iter =
block.referrers().begin();
for (; ref_iter != block.referrers().end(); ++ref_iter) {
if (ref_iter->first != &block) {
ASSERT_EQ(tx_.thunk_section_->id(), ref_iter->first->section());
++num_external_thunks;
}
}
// Each of the thunks for a non-decomposable block will reuse the same
// id to source range map entry, so we increment total_basic_blocks once
// if num_external_thunks is non-zero. Note that we cannot assert that
// num_external_thunks > 0 because the block could be statically dead
// (in a debug build, for example).
if (num_external_thunks != 0U)
++total_basic_blocks;
continue;
}
// Note that we have attempted to validate a block.
++num_decomposed_blocks;
// Decompose the block to basic-blocks.
BasicBlockSubGraph subgraph;
BasicBlockDecomposer bb_decomposer(&block, &subgraph);
ASSERT_TRUE(bb_decomposer.Decompose());
// Check if each non-padding basic code-block begins with the
// instrumentation sequence.
size_t num_basic_blocks = 0;
BasicBlockSubGraph::BBCollection::const_iterator bb_iter =
subgraph.basic_blocks().begin();
for (; bb_iter != subgraph.basic_blocks().end(); ++bb_iter) {
const BasicCodeBlock* bb = BasicCodeBlock::Cast(*bb_iter);
if (bb == NULL || bb->is_padding())
continue;
++num_basic_blocks;
if (kind == kAgentInstrumentation) {
ASSERT_LE(3U, bb->instructions().size());
BasicBlock::Instructions::const_iterator inst_iter =
bb->instructions().begin();
// Instruction 1 should push the basic block id.
const Instruction& inst1 = *inst_iter;
EXPECT_EQ(I_PUSH, inst1.representation().opcode);
// Instruction 2 should push the frequency data block pointer.
const Instruction& inst2 = *(++inst_iter);
EXPECT_EQ(I_PUSH, inst2.representation().opcode);
ASSERT_EQ(1U, inst2.references().size());
EXPECT_EQ(tx_.frequency_data_block(),
inst2.references().begin()->second.block());
// Instruction 3 should be a call to the bb entry hook.
const Instruction& inst3 = *(++inst_iter);
EXPECT_EQ(I_CALL, inst3.representation().opcode);
ASSERT_EQ(1U, inst3.references().size());
EXPECT_EQ(tx_.bb_entry_hook_ref_.referenced(),
inst3.references().begin()->second.block());
} else {
DCHECK(kind == kFastPathInstrumentation);
ASSERT_LE(2U, bb->instructions().size());
BasicBlock::Instructions::const_iterator inst_iter =
bb->instructions().begin();
// Instruction 1 should push the basic block id.
const Instruction& inst1 = *inst_iter;
EXPECT_EQ(I_PUSH, inst1.representation().opcode);
// Instruction 2 should be a call to the fast bb entry hook.
const Instruction& inst2 = *(++inst_iter);
EXPECT_EQ(I_CALL, inst2.representation().opcode);
ASSERT_EQ(1U, inst2.references().size());
}
}
EXPECT_NE(0U, num_basic_blocks);
total_basic_blocks += num_basic_blocks;
}
EXPECT_NE(0U, num_decomposed_blocks);
EXPECT_EQ(total_basic_blocks, tx_.bb_ranges().size());
}
} // namespace
TEST_F(BasicBlockEntryHookTransformTest, SetInlinePathFlag) {
EXPECT_FALSE(tx_.inline_fast_path());
tx_.set_inline_fast_path(true);
EXPECT_TRUE(tx_.inline_fast_path());
tx_.set_inline_fast_path(false);
EXPECT_FALSE(tx_.inline_fast_path());
}
TEST_F(BasicBlockEntryHookTransformTest, ApplyAgentInstrumentation) {
ASSERT_NO_FATAL_FAILURE(DecomposeTestDll());
// Apply the transform.
tx_.set_src_ranges_for_thunks(true);
ASSERT_TRUE(block_graph::ApplyBlockGraphTransform(
&tx_, policy_, &block_graph_, header_block_));
ASSERT_TRUE(tx_.frequency_data_block() != NULL);
ASSERT_TRUE(tx_.thunk_section_ != NULL);
ASSERT_TRUE(tx_.bb_entry_hook_ref_.IsValid());
ASSERT_LT(0u, tx_.bb_ranges().size());
// Validate the basic-block frequency data structure.
block_graph::ConstTypedBlock<IndexedFrequencyData> frequency_data;
ASSERT_TRUE(frequency_data.Init(0, tx_.frequency_data_block()));
EXPECT_EQ(kBasicBlockEntryAgentId, frequency_data->agent_id);
EXPECT_EQ(kBasicBlockFrequencyDataVersion, frequency_data->version);
EXPECT_EQ(IndexedFrequencyData::BASIC_BLOCK_ENTRY, frequency_data->data_type);
EXPECT_EQ(tx_.bb_ranges().size(), frequency_data->num_entries);
EXPECT_EQ(sizeof(uint32), frequency_data->frequency_size);
EXPECT_TRUE(frequency_data.HasReferenceAt(
frequency_data.OffsetOf(frequency_data->frequency_data)));
EXPECT_EQ(sizeof(BasicBlockIndexedFrequencyData),
tx_.frequency_data_block()->size());
EXPECT_EQ(sizeof(BasicBlockIndexedFrequencyData),
tx_.frequency_data_block()->data_size());
EXPECT_EQ(frequency_data->num_entries * frequency_data->frequency_size,
tx_.frequency_data_buffer_block()->size());
// Validate that all basic block have been instrumented.
CheckBasicBlockInstrumentation(kAgentInstrumentation);
}
} // namespace transforms
} // namespace instrument