blob: 6f8945354c3094a17cdd6c90adf80740723b0770 [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/block_graph/basic_block_test_util.h"
#include "gtest/gtest.h"
#include "syzygy/block_graph/basic_block_decomposer.h"
#include "syzygy/block_graph/basic_block_subgraph.h"
extern "C" {
// Functions invoked or referred by the .asm test stub.
int func1() {
return 1;
}
int func2() {
return 2;
}
} // extern "C"
namespace testing {
namespace {
using block_graph::BlockGraph;
#define POINTER_DIFF(x, y) \
(reinterpret_cast<const uint8_t*>(x) - reinterpret_cast<const uint8_t*>(y))
const int32_t kAssemblyFuncSize =
POINTER_DIFF(assembly_func_end, assembly_func);
const int32_t kCaseTableOffset = POINTER_DIFF(case_table, assembly_func);
const int32_t kJumpTableOffset = POINTER_DIFF(jump_table, assembly_func);
const int32_t kCase0Offset = POINTER_DIFF(case_0, assembly_func);
const int32_t kCase1Offset = POINTER_DIFF(case_1, assembly_func);
const int32_t kCaseDefaultOffset = POINTER_DIFF(case_default, assembly_func);
const int32_t kInterruptOffset = POINTER_DIFF(interrupt_label, assembly_func);
const int32_t kUnreachableOffset =
POINTER_DIFF(unreachable_label, assembly_func);
#undef POINTER_DIFF
const BlockGraph::LabelAttributes kCaseTableAttributes =
BlockGraph::DATA_LABEL | BlockGraph::CASE_TABLE_LABEL;
const BlockGraph::LabelAttributes kJumpTableAttributes =
BlockGraph::DATA_LABEL | BlockGraph::JUMP_TABLE_LABEL;
} // namespace
BasicBlockTest::BasicBlockTest()
: text_section_(NULL), data_section_(NULL), assembly_func_(NULL),
func1_(NULL), func2_(NULL), data_(NULL) {
}
void BasicBlockTest::InitBlockGraph() {
start_addr_ = RelativeAddress(0xF00D);
text_section_ = block_graph_.AddSection(
".text", IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE);
ASSERT_TRUE(text_section_ != NULL);
data_section_ = block_graph_.AddSection(
".data",
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ |
IMAGE_SCN_MEM_WRITE);
ASSERT_TRUE(data_section_ != NULL);
// Create func1, which will be called from assembly_func.
func1_ = block_graph_.AddBlock(BlockGraph::CODE_BLOCK, 1, "func1");
ASSERT_TRUE(func1_ != NULL);
func1_->set_section(text_section_->id());
// Create func2, a non-returning function called from assembly_func.
func2_ = block_graph_.AddBlock(BlockGraph::CODE_BLOCK, 1, "func2");
ASSERT_TRUE(func2_ != NULL);
func2_->set_attributes(BlockGraph::NON_RETURN_FUNCTION);
func2_->set_section(text_section_->id());
// Create a data block to refer to assembly_func.
data_ = block_graph_.AddBlock(BlockGraph::DATA_BLOCK, 4, "data");
ASSERT_TRUE(data_ != NULL);
data_->set_section(data_section_->id());
// Create assembly_func, and mark it as BUILT_BY_SYZYGY so the basic-block
// decomposer is willing to process it.
assembly_func_ = block_graph_.AddBlock(BlockGraph::CODE_BLOCK,
kAssemblyFuncSize,
"assembly_func_");
ASSERT_TRUE(assembly_func_ != NULL);
assembly_func_->SetData(reinterpret_cast<const uint8_t*>(assembly_func),
kAssemblyFuncSize);
assembly_func_->set_attributes(BlockGraph::BUILT_BY_SYZYGY);
assembly_func_->set_section(text_section_->id());
assembly_func_->
source_ranges().Push(Block::DataRange(0, kAssemblyFuncSize),
Block::SourceRange(start_addr_, kAssemblyFuncSize));
// This block contains aligned case and jump tables, so the decomposer would
// give it pointer alignment.
assembly_func_->set_alignment(4);
// Add the data labels.
ASSERT_TRUE(assembly_func_->SetLabel(
kCaseTableOffset, "case_table", kCaseTableAttributes));
ASSERT_TRUE(assembly_func_->SetLabel(
kJumpTableOffset, "jump_table", kJumpTableAttributes));
// Add the instruction references to the jump and case tables. Note that
// the jump table reference is at the end of the indirect jmp instruction
// (7-bytes) that immediately precedes the unreachable label and that the
// case table reference is at the end of the movzx instruction which
// immediately preceeds the jmp.
ASSERT_TRUE(assembly_func_->SetReference(
kUnreachableOffset - (Reference::kMaximumSize + 7),
Reference(BlockGraph::RELATIVE_REF, Reference::kMaximumSize,
assembly_func_, kCaseTableOffset, kCaseTableOffset)));
ASSERT_TRUE(assembly_func_->SetReference(
kUnreachableOffset - Reference::kMaximumSize,
Reference(BlockGraph::RELATIVE_REF, Reference::kMaximumSize,
assembly_func_, kJumpTableOffset, kJumpTableOffset)));
// Add the jump table references to the cases.
ASSERT_TRUE(assembly_func_->SetReference(
kJumpTableOffset + (Reference::kMaximumSize * 0),
Reference(BlockGraph::RELATIVE_REF, Reference::kMaximumSize,
assembly_func_, kCase0Offset, kCase0Offset)));
ASSERT_TRUE(assembly_func_->SetReference(
kJumpTableOffset + (Reference::kMaximumSize * 1),
Reference(BlockGraph::RELATIVE_REF, Reference::kMaximumSize,
assembly_func_, kCase1Offset, kCase1Offset)));
ASSERT_TRUE(assembly_func_->SetReference(
kJumpTableOffset + (Reference::kMaximumSize * 2),
Reference(BlockGraph::RELATIVE_REF, Reference::kMaximumSize,
assembly_func_, kCaseDefaultOffset, kCaseDefaultOffset)));
// Add the external outbound references.
ASSERT_TRUE(assembly_func_->SetReference(
kCase1Offset + 1,
Reference(BlockGraph::RELATIVE_REF, Reference::kMaximumSize,
func1_, 0, 0)));
ASSERT_TRUE(assembly_func_->SetReference(
kInterruptOffset - Reference::kMaximumSize,
Reference(BlockGraph::RELATIVE_REF, Reference::kMaximumSize,
func2_, 0, 0)));
// Add an inbound reference to the top of the function.
ASSERT_TRUE(data_->SetReference(
0,
Reference(BlockGraph::RELATIVE_REF, Reference::kMaximumSize,
assembly_func_, 0, 0)));
}
void BasicBlockTest::InitBasicBlockSubGraph() {
BasicBlockDecomposer bb_decomposer(assembly_func_, &subgraph_);
logging::SetMinLogLevel(logging::LOG_ERROR);
ASSERT_TRUE(bb_decomposer.Decompose());
ASSERT_TRUE(subgraph_.IsValid());
ASSERT_EQ(1u, subgraph_.block_descriptions().size());
bds_.reserve(subgraph_.block_descriptions().size());
BasicBlockSubGraph::BlockDescriptionList::iterator bd_it =
subgraph_.block_descriptions().begin();
for (; bd_it != subgraph_.block_descriptions().end(); ++bd_it)
bds_.push_back(&(*bd_it));
ASSERT_EQ(subgraph_.block_descriptions().size(), bds_.size());
ASSERT_EQ(kNumBasicBlocks, bds_[0]->basic_block_order.size());
bbs_.reserve(bds_[0]->basic_block_order.size());
bbs_.assign(bds_[0]->basic_block_order.begin(),
bds_[0]->basic_block_order.end());
ASSERT_EQ(bds_[0]->basic_block_order.size(), bbs_.size());
}
void BasicBlockTest::InitBasicBlockSubGraphWithLabelPastEnd() {
// We create a simple block-graph containing two blocks. One of them is a
// simple function that contains a single int3 instruction. The second block
// contains a call to the first block. The second block has no epilog (given
// that it calls a non-returning function) and has a debug-end label past the
// end of the block.
ASSERT_TRUE(block_graph_.sections().empty());
ASSERT_TRUE(block_graph_.blocks().empty());
text_section_ = block_graph_.AddSection(
".text", IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE);
ASSERT_TRUE(text_section_ != NULL);
func1_ = block_graph_.AddBlock(BlockGraph::CODE_BLOCK, 1, "noret");
ASSERT_TRUE(func1_ != NULL);
func1_->ResizeData(1);
func1_->GetMutableData()[0] = 0xCC; // int3.
func1_->SetLabel(0, "noret", BlockGraph::CODE_LABEL);
func2_ = block_graph_.AddBlock(BlockGraph::CODE_BLOCK, 6, "no_epilog");
ASSERT_TRUE(func2_ != NULL);
func2_->ResizeData(6);
func2_->GetMutableData()[0] = 0xE8; // call 0x00000000 (non returning).
func2_->GetMutableData()[5] = 0xCC; // int3.
func2_->SetLabel(0, "no_epilog, debug-start",
BlockGraph::CODE_LABEL | BlockGraph::DEBUG_START_LABEL);
func2_->SetLabel(6, "debug-end", BlockGraph::DEBUG_END_LABEL);
func2_->SetReference(1, BlockGraph::Reference(BlockGraph::ABSOLUTE_REF,
4,
func1_,
0,
0));
// Decompose the second function.
BasicBlockDecomposer bb_decomposer(func2_, &subgraph_);
ASSERT_TRUE(bb_decomposer.Decompose());
ASSERT_TRUE(subgraph_.IsValid());
ASSERT_EQ(1u, subgraph_.block_descriptions().size());
ASSERT_EQ(2u, subgraph_.basic_blocks().size());
BasicBlockSubGraph::BlockDescriptionList::const_iterator bd_it =
subgraph_.block_descriptions().begin();
ASSERT_EQ(2u, bd_it->basic_block_order.size());
BasicBlockSubGraph::BBCollection::const_iterator bb_it =
subgraph_.basic_blocks().begin();
const block_graph::BasicBlock* bb = *bb_it;
const block_graph::BasicCodeBlock* bcb =
block_graph::BasicCodeBlock::Cast(bb);
ASSERT_TRUE(bcb != NULL);
ASSERT_EQ(2u, bcb->instructions().size());
ASSERT_EQ(1u, bcb->instructions().begin()->references().size());
ASSERT_TRUE(bcb->instructions().begin()->has_label());
ASSERT_EQ(BlockGraph::CODE_LABEL | BlockGraph::DEBUG_START_LABEL,
bcb->instructions().begin()->label().attributes());
++bb_it;
bb = *bb_it;
const block_graph::BasicEndBlock* beb =
block_graph::BasicEndBlock::Cast(bb);
ASSERT_TRUE(beb != NULL);
BlockGraph::Label expected_label("debug-end", BlockGraph::DEBUG_END_LABEL);
ASSERT_TRUE(beb->has_label());
ASSERT_EQ(expected_label, beb->label());
++bb_it;
ASSERT_TRUE(bb_it == subgraph_.basic_blocks().end());
}
} // namespace testing