blob: 13a34810682605c35bc3e461d40e909abc86bf10 [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.
//
// Unittests for BlockGraph transform wrapper.
#include "syzygy/block_graph/transform.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "syzygy/block_graph/unittest_util.h"
namespace block_graph {
namespace {
using testing::_;
using testing::DummyTransformPolicy;
using testing::Invoke;
using testing::Return;
// A constant data structure/buffer from which to initialize a data block.
// The structure contains a core reference (function pointer) and an integer
// data element.
struct MyData {
void (*code)(int);
int data;
};
const BlockGraph::Offset kOffsetOfReferenceToCode = offsetof(MyData, code);
const BlockGraph::Offset kOffsetOfData = offsetof(MyData, data);
const MyData kDataBytes = { reinterpret_cast<void(*)(int)>(0xCAFEBABE),
0xDEADBEEF };
// A byte buffer from which to initialize a code block. The original C source
// code for this function is:
//
// static int y = 1;
// void add(int x) {
// y += x;
// }
//
// Note the reference to
// y starts 5 bytes from the end.
const uint8_t kCodeBytes[] = {
0x8B,
0x44,
0x24,
0x04, // mov eax,dword ptr [esp+4]
0x01,
0x05,
0x00,
0x00,
0x00,
0x00, // add dword ptr [_y],eax
0xC3 // ret
};
const BlockGraph::Offset kOffsetOfCode = 0;
const BlockGraph::Offset kOffsetOfReferenceToData = sizeof(kCodeBytes) - 5;
class ApplyBlockGraphTransformTest : public testing::Test {
public:
virtual void SetUp() {
header_block_ = block_graph_.AddBlock(BlockGraph::DATA_BLOCK, 10, "Header");
}
protected:
DummyTransformPolicy policy_;
BlockGraph block_graph_;
BlockGraph::Block* header_block_;
};
class LenientMockBlockGraphTransform : public BlockGraphTransformInterface {
public:
virtual ~LenientMockBlockGraphTransform() { }
virtual const char* name() const { return "MockBlockGraphTransform"; }
MOCK_METHOD3(TransformBlockGraph,
bool(const TransformPolicyInterface*,
BlockGraph*,
BlockGraph::Block*));
bool DeleteHeader(const TransformPolicyInterface* policy,
BlockGraph* block_graph,
BlockGraph::Block* header_block) {
CHECK(block_graph->RemoveBlock(header_block));
return true;
}
};
typedef testing::StrictMock<LenientMockBlockGraphTransform>
MockBlockGraphTransform;
class ApplyBasicBlockSubGraphTransformTest : public testing::Test {
public:
ApplyBasicBlockSubGraphTransformTest()
: data_block_(NULL), code_block_(NULL) {
}
virtual void SetUp() {
// Create some blocks to test with.
data_block_ = block_graph_.AddBlock(
BlockGraph::DATA_BLOCK, sizeof(kDataBytes), "Data");
ASSERT_TRUE(data_block_ != NULL);
code_block_ = block_graph_.AddBlock(
BlockGraph::CODE_BLOCK, sizeof(kCodeBytes), "Code");
ASSERT_TRUE(code_block_ != NULL);
// Set up the data block.
data_block_->SetData(reinterpret_cast<const uint8_t*>(&kDataBytes),
sizeof(kDataBytes));
// Set up the code block.
ASSERT_TRUE(code_block_->SetLabel(
kOffsetOfCode,
BlockGraph::Label("Code", BlockGraph::CODE_LABEL)));
code_block_->SetData(kCodeBytes, sizeof(kCodeBytes));
// Set up the references
ASSERT_TRUE(
data_block_->SetReference(kOffsetOfReferenceToCode,
MakeReference(code_block_, kOffsetOfCode)));
ASSERT_TRUE(
code_block_->SetReference(kOffsetOfReferenceToData,
MakeReference(data_block_, kOffsetOfData)));
}
static BlockGraph::Reference MakeReference(BlockGraph::Block* target,
BlockGraph::Offset offset) {
EXPECT_TRUE(target != NULL);
return BlockGraph::Reference(BlockGraph::RELATIVE_REF,
BlockGraph::Reference::kMaximumSize,
target, offset, offset);
}
protected:
DummyTransformPolicy policy_;
BlockGraph block_graph_;
BlockGraph::Block* data_block_;
BlockGraph::Block* code_block_;
};
class MockBasicBlockSubGraphTransform :
public BasicBlockSubGraphTransformInterface {
public:
virtual ~MockBasicBlockSubGraphTransform() { }
virtual const char* name() const { return "MockBasicBlockSubGraphTransform"; }
MOCK_METHOD3(TransformBasicBlockSubGraph,
bool(const TransformPolicyInterface*,
BlockGraph*,
BasicBlockSubGraph*));
};
class ApplyImageLayoutTransformTest : public testing::Test {
public:
virtual void SetUp() {
BlockGraph block_graph_;
block_graph_.AddBlock(BlockGraph::DATA_BLOCK, 10, "Block1");
block_graph_.AddBlock(BlockGraph::DATA_BLOCK, 20, "Block2");
image_layout_ = new pe::ImageLayout(&block_graph_);
ordered_block_graph_ = new OrderedBlockGraph(&block_graph_);
}
protected:
DummyTransformPolicy policy_;
pe::ImageLayout* image_layout_;
OrderedBlockGraph* ordered_block_graph_;
};
class LenientMockImageLayoutTransform : public ImageLayoutTransformInterface {
public:
virtual ~LenientMockImageLayoutTransform() { }
virtual const char* name() const { return "MockImageLayoutTransform"; }
MOCK_METHOD3(TransformImageLayout,
bool(const TransformPolicyInterface*,
const pe::ImageLayout*,
const OrderedBlockGraph*));
};
typedef testing::StrictMock<LenientMockImageLayoutTransform>
MockImageLayoutTransform;
} // namespace
TEST_F(ApplyBlockGraphTransformTest, NormalTransformSucceeds) {
MockBlockGraphTransform transform;
EXPECT_CALL(transform, TransformBlockGraph(_, _, _)).Times(1).
WillOnce(Return(true));
EXPECT_TRUE(ApplyBlockGraphTransform(&transform,
&policy_,
&block_graph_,
header_block_));
}
TEST_F(ApplyBlockGraphTransformTest, DeletingHeaderFails) {
MockBlockGraphTransform transform;
EXPECT_CALL(transform, TransformBlockGraph(_, _, _)).Times(1).WillOnce(
Invoke(&transform, &MockBlockGraphTransform::DeleteHeader));
EXPECT_FALSE(ApplyBlockGraphTransform(&transform,
&policy_,
&block_graph_,
header_block_));
}
TEST_F(ApplyBlockGraphTransformTest, VectorTransformSucceeds) {
MockBlockGraphTransform tx1, tx2, tx3;
std::vector<BlockGraphTransformInterface*> txs;
txs.push_back(&tx1);
txs.push_back(&tx2);
txs.push_back(&tx3);
EXPECT_CALL(tx1, TransformBlockGraph(&policy_, &block_graph_, header_block_))
.WillOnce(Return(true));
EXPECT_CALL(tx2, TransformBlockGraph(&policy_, &block_graph_, header_block_))
.WillOnce(Return(true));
EXPECT_CALL(tx3, TransformBlockGraph(&policy_, &block_graph_, header_block_))
.WillOnce(Return(true));
EXPECT_TRUE(ApplyBlockGraphTransforms(
txs, &policy_, &block_graph_, header_block_));
}
TEST_F(ApplyBlockGraphTransformTest, VectorTransformFails) {
MockBlockGraphTransform tx1, tx2, tx3;
std::vector<BlockGraphTransformInterface*> txs;
txs.push_back(&tx1);
txs.push_back(&tx2);
txs.push_back(&tx3);
EXPECT_CALL(tx1, TransformBlockGraph(&policy_, &block_graph_, header_block_))
.WillOnce(Return(true));
EXPECT_CALL(tx2, TransformBlockGraph(&policy_, &block_graph_, header_block_))
.WillOnce(Return(false));
EXPECT_FALSE(ApplyBlockGraphTransforms(
txs, &policy_, &block_graph_, header_block_));
}
TEST_F(ApplyBasicBlockSubGraphTransformTest, TransformFails) {
// Remember the block ids of the original blocks.
BlockGraph::BlockId data_block_id = data_block_->id();
BlockGraph::BlockId code_block_id = code_block_->id();
// Apply an empty transform that reports failure.
MockBasicBlockSubGraphTransform transform;
EXPECT_CALL(transform, TransformBasicBlockSubGraph(_, _, _)).Times(1).
WillOnce(Return(false));
EXPECT_FALSE(ApplyBasicBlockSubGraphTransform(&transform,
&policy_,
&block_graph_,
code_block_,
NULL));
// The original block graph should be unchanged.
EXPECT_EQ(2U, block_graph_.blocks().size());
EXPECT_EQ(data_block_, block_graph_.GetBlockById(data_block_id));
EXPECT_EQ(code_block_, block_graph_.GetBlockById(code_block_id));
}
TEST_F(ApplyBasicBlockSubGraphTransformTest, EmptyTransformSucceeds) {
// Remember the block ids of the original blocks.
BlockGraph::BlockId data_block_id = data_block_->id();
BlockGraph::BlockId code_block_id = code_block_->id();
// Apply an empty transform that reports success.
MockBasicBlockSubGraphTransform transform;
BlockVector new_blocks;
EXPECT_CALL(transform, TransformBasicBlockSubGraph(_, _, _)).Times(1).
WillOnce(Return(true));
EXPECT_TRUE(ApplyBasicBlockSubGraphTransform(&transform,
&policy_,
&block_graph_,
code_block_,
&new_blocks));
// The code block should have been replaced with an equivalent one. We'll
// have the same number of blocks, but the code block should no longer
// be in the graph.
EXPECT_EQ(2U, block_graph_.blocks().size());
EXPECT_EQ(data_block_, block_graph_.GetBlockById(data_block_id));
EXPECT_EQ(NULL, block_graph_.GetBlockById(code_block_id));
// Clean up our dangling pointer.
code_block_ = NULL;
// Find the new block.
ASSERT_EQ(1U, new_blocks.size());
const BlockGraph::Block* new_block = new_blocks[0];
// Validate the references.
EXPECT_EQ(1U, new_block->references().size());
BlockGraph::Reference ref;
EXPECT_TRUE(new_block->GetReference(kOffsetOfReferenceToData, &ref));
EXPECT_EQ(kOffsetOfData, ref.offset());
EXPECT_EQ(data_block_, ref.referenced());
// Validate the referrers.
EXPECT_EQ(1U, new_block->referrers().size());
EXPECT_EQ(data_block_, new_block->referrers().begin()->first);
EXPECT_EQ(kOffsetOfReferenceToCode, new_block->referrers().begin()->second);
EXPECT_TRUE(new_block->referrers().begin()->first->GetReference(
kOffsetOfReferenceToCode, &ref));
EXPECT_EQ(new_block, ref.referenced());
}
TEST_F(ApplyBasicBlockSubGraphTransformTest, VectorTransformSucceeds) {
// Validate applying a vector of transforms.
MockBasicBlockSubGraphTransform transform1;
MockBasicBlockSubGraphTransform transform2;
BlockVector new_blocks;
EXPECT_CALL(transform1, TransformBasicBlockSubGraph(_, _, _)).Times(1).
WillOnce(Return(true));
EXPECT_CALL(transform2, TransformBasicBlockSubGraph(_, _, _)).Times(1).
WillOnce(Return(true));
std::vector<BasicBlockSubGraphTransformInterface*> transforms;
transforms.push_back(&transform1);
transforms.push_back(&transform2);
EXPECT_TRUE(ApplyBasicBlockSubGraphTransforms(transforms,
&policy_,
&block_graph_,
code_block_,
&new_blocks));
}
TEST_F(ApplyImageLayoutTransformTest, NormalTransformSucceeds) {
MockImageLayoutTransform transform;
EXPECT_CALL(transform, TransformImageLayout(_, _, _)).Times(1).
WillOnce(Return(true));
EXPECT_TRUE(ApplyImageLayoutTransform(&transform,
&policy_,
image_layout_,
ordered_block_graph_));
}
TEST_F(ApplyImageLayoutTransformTest, VectorTransformSucceeds) {
MockImageLayoutTransform tx1, tx2, tx3;
std::vector<ImageLayoutTransformInterface*> txs;
txs.push_back(&tx1);
txs.push_back(&tx2);
txs.push_back(&tx3);
EXPECT_CALL(tx1, TransformImageLayout(&policy_,
image_layout_,
ordered_block_graph_))
.WillOnce(Return(true));
EXPECT_CALL(tx2, TransformImageLayout(&policy_,
image_layout_,
ordered_block_graph_))
.WillOnce(Return(true));
EXPECT_CALL(tx3, TransformImageLayout(&policy_,
image_layout_,
ordered_block_graph_))
.WillOnce(Return(true));
EXPECT_TRUE(ApplyImageLayoutTransforms(
txs, &policy_, image_layout_, ordered_block_graph_));
}
} // namespace block_graph