blob: aab88d98ed4f6e325f2189348fca0ff1a518c5b7 [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/block_util.h"
#include "gtest/gtest.h"
#include "syzygy/block_graph/basic_block_assembler.h"
#include "syzygy/block_graph/basic_block_test_util.h"
namespace block_graph {
namespace {
class BlockUtilTest: public testing::Test {
public:
BlockUtilTest()
: bb_(NULL),
start_addr_(0xF00D) {
bb_ = subgraph_.AddBasicCodeBlock("foo");
}
BlockGraph::Size AddInstructions(bool add_source_ranges) {
using assm::eax;
using assm::ebp;
using assm::esp;
BasicBlockAssembler assm(bb_->instructions().begin(), &bb_->instructions());
assm.push(ebp);
assm.mov(ebp, esp);
assm.mov(eax, Operand(ebp, Displacement(8)));
assm.pop(ebp);
// assm.ret(0);
BasicBlock::Instructions::iterator inst_it(bb_->instructions().begin());
BlockGraph::RelativeAddress next_addr(start_addr_);
BasicBlock::Offset next_offs = 0;
for (; inst_it != bb_->instructions().end(); ++inst_it) {
if (add_source_ranges)
inst_it->set_source_range(
Instruction::SourceRange(next_addr, inst_it->size()));
next_addr += inst_it->size();
next_offs += inst_it->size();
}
BasicBlockReference ref(BlockGraph::PC_RELATIVE_REF, 4, bb_);
bb_->successors().push_back(
Successor(Successor::kConditionAbove, ref, 5));
next_offs += 5;
if (add_source_ranges)
bb_->successors().back().set_source_range(
Successor::SourceRange(next_addr, 5));
bb_->successors().push_back(
Successor(Successor::kConditionBelowOrEqual, ref, 0));
return next_offs;
}
protected:
BlockGraph image_;
BlockGraph::RelativeAddress start_addr_;
BasicBlockSubGraph subgraph_;
BasicCodeBlock* bb_;
};
} // namespace
TEST_F(BlockUtilTest, GetBasicBlockSourceRangeEmptyFails) {
AddInstructions(false);
BlockGraph::Block::SourceRange source_range;
ASSERT_FALSE(GetBasicBlockSourceRange(*bb_, &source_range));
EXPECT_EQ(0, source_range.size());
}
TEST_F(BlockUtilTest, GetBasicBlockSourceRangeNonContiguousFails) {
AddInstructions(true);
// Make the range non-contiguous by pushing the successor out one byte.
BlockGraph::Block::SourceRange range =
bb_->successors().front().source_range();
bb_->successors().front().set_source_range(
BlockGraph::Block::SourceRange(range.start() + 1, range.size()));
BlockGraph::Block::SourceRange source_range;
ASSERT_FALSE(GetBasicBlockSourceRange(*bb_, &source_range));
EXPECT_EQ(0, source_range.size());
}
TEST_F(BlockUtilTest, GetBasicBlockSourceRangeSequentialSucceeds) {
BlockGraph::Size instr_len = AddInstructions(true);
BlockGraph::Block::SourceRange source_range;
ASSERT_TRUE(GetBasicBlockSourceRange(*bb_, &source_range));
BlockGraph::Block::SourceRange expected_range(
BlockGraph::RelativeAddress(0xF00D), instr_len);
EXPECT_EQ(expected_range, source_range);
}
TEST_F(BlockUtilTest, GetBasicBlockSourceRangeNonSequentialSucceeds) {
BlockGraph::Size instr_len = AddInstructions(true);
// Shuffle the ranges by flipping the first and last ranges.
BlockGraph::Block::SourceRange temp =
bb_->successors().front().source_range();
bb_->successors().front().set_source_range(
bb_->instructions().front().source_range());
bb_->instructions().front().set_source_range(temp);
BlockGraph::Block::SourceRange source_range;
ASSERT_TRUE(GetBasicBlockSourceRange(*bb_, &source_range));
BlockGraph::Block::SourceRange expected_range(
BlockGraph::RelativeAddress(0xF00D), instr_len);
EXPECT_EQ(expected_range, source_range);
}
TEST_F(BlockUtilTest, GetBasicBlockSourceRangePrependInstructionsSucceeds) {
BlockGraph::Size instr_len = AddInstructions(true);
// Prepend some instrumentation-like code.
BasicBlockAssembler assm(bb_->instructions().begin(), &bb_->instructions());
assm.push(Immediate(0xBADF00D));
assm.call(Immediate(bb_));
BlockGraph::Block::SourceRange source_range;
ASSERT_TRUE(GetBasicBlockSourceRange(*bb_, &source_range));
BlockGraph::Block::SourceRange expected_range(
BlockGraph::RelativeAddress(0xF00D), instr_len);
EXPECT_EQ(expected_range, source_range);
}
TEST_F(BlockUtilTest, IsUnsafeReference) {
// Some safe blocks.
BlockGraph::Block* s1 = image_.AddBlock(BlockGraph::CODE_BLOCK, 40, "s1");
BlockGraph::Block* s2 = image_.AddBlock(BlockGraph::CODE_BLOCK, 40, "s2");
// Some unsafe blocks.
BlockGraph::Block* u1 = image_.AddBlock(BlockGraph::CODE_BLOCK, 40, "u1");
u1->set_attribute(BlockGraph::HAS_INLINE_ASSEMBLY);
BlockGraph::Block* u2 = image_.AddBlock(BlockGraph::CODE_BLOCK, 40, "u2");
u2->set_attribute(BlockGraph::BUILT_BY_UNSUPPORTED_COMPILER);
// If neither or only one has an unsafe attribute then it's not unsafe.
EXPECT_FALSE(IsUnsafeReference(s1, BlockGraph::Reference(
BlockGraph::PC_RELATIVE_REF,
BlockGraph::Reference::kMaximumSize,
s2, 0, 0)));
EXPECT_FALSE(IsUnsafeReference(s1, BlockGraph::Reference(
BlockGraph::PC_RELATIVE_REF,
BlockGraph::Reference::kMaximumSize,
u1, 0, 0)));
EXPECT_FALSE(IsUnsafeReference(u2, BlockGraph::Reference(
BlockGraph::PC_RELATIVE_REF,
BlockGraph::Reference::kMaximumSize,
s2, 0, 0)));
// If the reference points to a non-zero offset then it's unsafe.
EXPECT_TRUE(IsUnsafeReference(s1, BlockGraph::Reference(
BlockGraph::PC_RELATIVE_REF,
BlockGraph::Reference::kMaximumSize,
s2, 4, 4)));
// If both the referring and referred blocks have unsafe attributes,
// the reference is unsafe.
EXPECT_TRUE(IsUnsafeReference(u1, BlockGraph::Reference(
BlockGraph::PC_RELATIVE_REF,
BlockGraph::Reference::kMaximumSize,
u2, 0, 0)));
}
TEST_F(BlockUtilTest, CheckNoUnexpectedStackFrameManipulation) {
// Prepend some instrumentation with a conventional calling convention.
BasicBlockAssembler assm(bb_->instructions().begin(), &bb_->instructions());
assm.push(assm::ebp);
assm.mov(assm::ebp, assm::esp);
assm.mov(assm::eax, Operand(assm::ebp, Displacement(8)));
assm.pop(assm::ebp);
assm.ret(0);
EXPECT_FALSE(HasUnexpectedStackFrameManipulation(&subgraph_));
}
TEST_F(BlockUtilTest, CheckInvalidInstructionUnexpectedStackFrameManipulation) {
// Prepend some instrumentation with a conventional calling convention.
BasicBlockAssembler assm(bb_->instructions().begin(), &bb_->instructions());
assm.push(assm::ebp);
assm.mov(assm::ebp, assm::esp);
// The instruction LEA is invalid stack frame manipulation.
assm.lea(assm::ebp, Operand(assm::ebp, Displacement(8)));
assm.pop(assm::ebp);
assm.ret(0);
EXPECT_TRUE(HasUnexpectedStackFrameManipulation(&subgraph_));
}
TEST_F(BlockUtilTest, CheckInvalidRegisterUnexpectedStackFrameManipulation) {
// Prepend some instrumentation with a conventional calling convention.
BasicBlockAssembler assm(bb_->instructions().begin(), &bb_->instructions());
assm.push(assm::ebp);
// The instruction MOV use an invalid register EAX.
assm.mov(assm::ebp, assm::eax);
assm.lea(assm::ebp, Operand(assm::ebp, Displacement(8)));
assm.pop(assm::ebp);
assm.ret(0);
EXPECT_TRUE(HasUnexpectedStackFrameManipulation(&subgraph_));
}
namespace {
// A utility class for using the test data built around the function in
// basic_block_assembly_func.asm.
class BlockUtilOnTestDataTest : public testing::BasicBlockTest {
public:
virtual void SetUp() override {
BasicBlockTest::SetUp();
ASSERT_NO_FATAL_FAILURE(InitBlockGraph());
}
};
} // namespace
TEST_F(BlockUtilOnTestDataTest, GetJumpTableSize) {
block_graph::BlockGraph::BlockMap::const_iterator block_iter =
block_graph_.blocks().begin();
size_t table_size = 0;
bool table_found = false;
// Iterates over the blocks of the block_graph. We expect to find only one
// block containing one jump table.
for (; block_iter != block_graph_.blocks().end(); ++block_iter) {
if (block_iter->second.type() != BlockGraph::CODE_BLOCK)
continue;
// Iterates over the labels of the block to find the jump tables.
BlockGraph::Block::LabelMap::const_iterator iter_label =
block_iter->second.labels().begin();
for (; iter_label != block_iter->second.labels().end(); ++iter_label) {
if (!iter_label->second.has_attributes(BlockGraph::JUMP_TABLE_LABEL))
continue;
// There's only one jump table in the test data.
EXPECT_FALSE(table_found);
table_found = true;
EXPECT_TRUE(block_graph::GetJumpTableSize(&block_iter->second,
iter_label,
&table_size));
}
}
EXPECT_EQ(3U, table_size);
}
} // namespace block_graph