blob: 6a06e1be3c1465a0b840f058087c4a6d5e1e7bfd [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/simulate/page_fault_simulation.h"
#include "base/values.h"
#include "base/files/scoped_temp_dir.h"
#include "base/json/json_reader.h"
#include "syzygy/core/random_number_generator.h"
#include "syzygy/core/unittest_util.h"
#include "syzygy/pdb/omap.h"
#include "syzygy/pe/unittest_util.h"
#include "syzygy/version/syzygy_version.h"
namespace simulate {
namespace {
using base::DictionaryValue;
using base::Value;
using block_graph::BlockGraph;
class PageFaultSimulatorTest : public testing::PELibUnitTest {
public:
typedef PageFaultSimulation::PageSet PageSet;
struct MockBlockInfo {
uint32_t start;
size_t size;
BlockGraph::Block* block;
MockBlockInfo(uint32_t start_, size_t size_, BlockGraph* block_graph)
: start(start_), size(size_), block(NULL) {
DCHECK(block_graph != NULL);
block = block_graph->AddBlock(BlockGraph::CODE_BLOCK, size_, "block");
block->set_addr(core::RelativeAddress(start));
block->set_size(size);
}
MockBlockInfo()
: start(0U), size(0U), block(NULL) {
}
};
typedef std::vector<MockBlockInfo> MockBlockInfoList;
PageFaultSimulatorTest() : random_(123) {
}
void SetUp() {
ASSERT_NO_FATAL_FAILURE(CreateTemporaryDir(&temp_dir_));
simulation_.reset(new PageFaultSimulation());
ASSERT_TRUE(simulation_ != NULL);
blocks_[0] = MockBlockInfo(0x0, 0x50, &block_graph_);
blocks_[1] = MockBlockInfo(0x0, 0x100, &block_graph_);
blocks_[2] = MockBlockInfo(0x350, 0x100, &block_graph_);
blocks_[3] = MockBlockInfo(0x1000, 0x50, &block_graph_);
}
// Checks if the given address is on one of our mock blocks.
// @param addr The address to check.
// @returns true if a block that contains the given address exists,
// false on otherwise.
bool AddressInBlocks(size_t addr) {
for (size_t i = 0; i < arraysize(blocks_); i++) {
if (blocks_[i].start <= addr &&
blocks_[i].start + blocks_[i].size > addr)
return true;
}
return false;
}
// Check whether all the pages loaded in simulator correspond to one of our
// blocks, given the current page_size and pages_per_code_fault parameters.
// @returns true if all the pages are contained in our mock blocks,
// false on otherwise.
bool CorrectPageFaults() {
PageSet::const_iterator iter = simulation_->pages().begin();
for (; iter != simulation_->pages().end(); ++iter) {
bool block_found = false;
// If this address was loaded, then some address between this one and
// the one pages_per_code_fault pages before should have triggered
// a page fault.
for (uint32_t j = 0;
j <= *iter && j < simulation_->pages_per_code_fault(); j++) {
if (AddressInBlocks((*iter - j) * simulation_->page_size())) {
block_found = true;
break;
}
}
if (!block_found)
return false;
}
return true;
}
// Gives a random number in the range [from, to), or 0 if the range is empty.
// @param from The lowest possible return value.
// @param to The number after the highest possible return value.
uint32_t Random(uint32_t from, uint32_t to) {
int size = to - from;
if (from > to) {
// This should never be reached.
ADD_FAILURE();
return 0;
}
if (size == 0)
return 0;
return random_(size) + from;
}
// Add 5 random blocks that won't generate any page fault.
// @param mock_block_list The MockBlockInfoList where the blocks will
// be appended. This list should already generate page faults covering
// the range [start, start + size).
// @param start The start of the output sequence.
// @param size The size of the output sequence.
void AddRandomBlocks(MockBlockInfoList& mock_block_list,
uint32_t start,
size_t size) {
for (uint32_t i = 0; i < 5; i++) {
uint32_t block_start = Random(start, start + size);
size_t block_size = Random(1, start + size - block_start);
mock_block_list.push_back(
MockBlockInfo(block_start, block_size, &block_graph_));
}
}
// Generate a random MockBlockInfoList that should make PageFaultSimulation
// output the sequence [start, start + size).
// @param start The start of the output sequence.
// @param size The size of the output sequence.
// @param avg_length The average length of each page fault generated by the
// resulting input data.
MockBlockInfoList GeneratePartRandomInput(uint32_t start,
size_t size,
size_t avg_length) {
MockBlockInfoList input;
if (size == 0)
return input;
uint32_t page_fault_size =
simulation_->pages_per_code_fault() * simulation_->page_size();
if (size < page_fault_size) {
// If the size of this part is smaller than the number of bytes loaded
// into memory in each page fault, then the given output was impossible,
// so this should never be reached.
ADD_FAILURE();
return input;
}
int fault = start + size - page_fault_size;
uint32_t current_size = 0;
// The block page_fault_size bytes from the end of each sequence should
// always be loaded.
input.push_back(
MockBlockInfo(fault, Random(1, page_fault_size), &block_graph_));
fault--;
for (; fault >= static_cast<int>(start); fault--) {
current_size++;
// Randomly choose with 1 / avg_length probability whether to add blocks
// that would raise a page fault in the current byte.
if (random_(avg_length) == 0) {
input.push_back(MockBlockInfo(fault,
Random(page_fault_size * (current_size / page_fault_size) + 1,
start + size - fault),
&block_graph_));
current_size = 0;
}
}
// Add the bytes that weren't pagefaulted in a single big block.
if (current_size > 0)
input.push_back(MockBlockInfo(start,
Random(page_fault_size * (current_size / page_fault_size) + 1,
size),
&block_graph_));
// Add several random blocks at the end of the input that won't
// have any effect on the output.
AddRandomBlocks(input, start, size);
return input;
}
// Generate a random MockBlockInfoList that outputs output.
// This function separates output into blocks of contiguous sequences,
// creates a block that should raise a pagefault in the position
// size - cluster_size, and for every element before that creates a
// block that should raise another one with 1 / avg_length probability.
// It also adds a few bogus blocks that shouldn't change the output, and
// shuffles the list of inputs for contiguous sequences on the output.
// @param output The output that should come from the returned input.
// @param avg_length The average length of each page fault generated by the
// resulting input data.
MockBlockInfoList GenerateRandomInput(PageSet output, size_t avg_length) {
// A list with different "groups" of mock blocks.
std::vector<MockBlockInfoList> input_list;
uint32_t last = 0;
uint32_t size = 0;
// Search through the output for groups of adjacent numbers, and add a
// MockBlockInfoList that would generate these numbers to input_list.
PageSet::iterator iter = output.begin();
for (; iter != output.end(); ++iter) {
if (last != 0 && *iter - last > 1) {
input_list.push_back(
GeneratePartRandomInput(last - size + 1, size, avg_length));
size = 0;
}
size++;
last = *iter;
}
input_list.push_back(
GeneratePartRandomInput(last - size + 1, size, avg_length));
// Shuffle the groups of adjacent numbers.
std::random_shuffle(input_list.begin(), input_list.end(), random_);
// Append all the elements of each element of input_list to a single
// MockBlockInfoList and return it.
MockBlockInfoList input;
for (size_t i = 0; i < input_list.size(); i++)
input.insert(input.end(), input_list[i].begin(), input_list[i].end());
return input;
}
protected:
std::unique_ptr<PageFaultSimulation> simulation_;
base::FilePath temp_dir_;
MockBlockInfo blocks_[4];
core::RandomNumberGenerator random_;
const base::Time time_;
BlockGraph block_graph_;
};
} // namespace
TEST_F(PageFaultSimulatorTest, RandomInput) {
static const int output1[] = {1, 2, 3, 4};
static const int output2[] = {1, 2, 3, 4, 5, 6, 12, 13, 14, 15, 16, 20, 21,
22, 23, 100, 101, 102, 103, 104, 105};
static const int output3[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23};
static const int output4[] = {1, 2, 3, 4, 100, 101, 102, 103, 200, 201, 202,
203};
const PageSet outputs[] = {
PageSet(output1, output1 + arraysize(output1)),
PageSet(output2, output2 + arraysize(output2)),
PageSet(output3, output3 + arraysize(output3)),
PageSet(output4, output4 + arraysize(output4))
};
for (uint32_t i = 0; i < 1000; i++) {
// Make simulation_ a new instance of PageFaultSimulator.
simulation_.reset(new PageFaultSimulation());
ASSERT_TRUE(simulation_ != NULL);
simulation_->OnProcessStarted(time_, 1);
simulation_->set_pages_per_code_fault(4);
// Choose a random output, create an input with it,
// and simulate that input.
PageSet output = outputs[random_(arraysize(outputs))];
MockBlockInfoList input =
GenerateRandomInput(output, random_(output.size()) + 1);
for (size_t i = 0; i < input.size(); i++)
simulation_->OnFunctionEntry(time_, input[i].block);
std::stringstream input_string;
input_string << '{';
for (size_t i = 0; i < input.size(); i++) {
input_string << '(' << input[i].start << ", ";
input_string << input[i].size << "), ";
}
input_string << '}';
ASSERT_EQ(simulation_->pages(), output) <<
"Failed with input " << input_string.str();
}
}
TEST_F(PageFaultSimulatorTest, ExactPageFaults) {
simulation_->OnProcessStarted(time_, 1);
simulation_->set_page_size(1);
simulation_->set_pages_per_code_fault(4);
MockBlockInfo blocks[] = {
MockBlockInfo(0, 3, &block_graph_),
MockBlockInfo(2, 2, &block_graph_),
MockBlockInfo(5, 5, &block_graph_)
};
for (uint32_t i = 0; i < arraysize(blocks); i++) {
simulation_->OnFunctionEntry(time_, blocks[i].block);
}
PageSet::key_type expected_pages[] = {0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12};
EXPECT_EQ(simulation_->fault_count(), 3);
EXPECT_EQ(simulation_->pages(), PageSet(expected_pages, expected_pages +
arraysize(expected_pages)));
}
TEST_F(PageFaultSimulatorTest, CorrectPageFaults) {
simulation_->OnProcessStarted(time_, 1);
for (int i = 0; i < arraysize(blocks_); i++) {
simulation_->OnFunctionEntry(time_, blocks_[i].block);
}
EXPECT_EQ(simulation_->fault_count(), 74);
EXPECT_TRUE(CorrectPageFaults());
}
TEST_F(PageFaultSimulatorTest, CorrectPageFaultsWithBigPages) {
simulation_->OnProcessStarted(time_, 1);
simulation_->set_page_size(0x8000);
for (int i = 0; i < arraysize(blocks_); i++) {
simulation_->OnFunctionEntry(time_, blocks_[i].block);
}
EXPECT_EQ(simulation_->fault_count(), 1);
EXPECT_TRUE(CorrectPageFaults());
}
TEST_F(PageFaultSimulatorTest, CorrectPageFaultsWithFewPagesPerCodeFault) {
simulation_->OnProcessStarted(time_, 1);
simulation_->set_pages_per_code_fault(3);
for (int i = 0; i < arraysize(blocks_); i++) {
simulation_->OnFunctionEntry(time_, blocks_[i].block);
}
EXPECT_EQ(simulation_->fault_count(), 199);
EXPECT_TRUE(CorrectPageFaults());
}
TEST_F(PageFaultSimulatorTest, JSONSucceeds) {
simulation_->OnProcessStarted(time_, 1);
for (int i = 0; i < arraysize(blocks_); i++) {
simulation_->OnFunctionEntry(time_, blocks_[i].block);
}
// Output JSON data to a file.
base::FilePath path;
base::ScopedFILE temp_file;
temp_file.reset(base::CreateAndOpenTemporaryFileInDir(
temp_dir_, &path));
ASSERT_TRUE(temp_file.get() != NULL);
ASSERT_TRUE(simulation_->SerializeToJSON(temp_file.get(), false));
temp_file.reset();
// Read the JSON file we just wrote.
std::string file_string;
ASSERT_TRUE(base::ReadFileToString(path, &file_string));
std::unique_ptr<Value> value(base::JSONReader::Read(file_string).release());
ASSERT_TRUE(value.get() != NULL);
ASSERT_TRUE(value->IsType(Value::TYPE_DICTIONARY));
const DictionaryValue* outer_dict =
static_cast<const DictionaryValue*>(value.get());
static const char page_size_key[] = "page_size";
static const char pages_per_code_fault_key[] = "pages_per_code_fault";
static const char fault_count_key[] = "fault_count";
static const char loaded_pages_key[] = "loaded_pages";
int page_size = 0, pages_per_code_fault = 0, fault_count = 0;
const base::ListValue* loaded_pages = NULL;
outer_dict->GetInteger(page_size_key, &page_size);
outer_dict->GetInteger(pages_per_code_fault_key, &pages_per_code_fault);
outer_dict->GetInteger(fault_count_key, &fault_count);
outer_dict->GetList(loaded_pages_key, &loaded_pages);
EXPECT_EQ(page_size, 1);
EXPECT_EQ(pages_per_code_fault, 8);
EXPECT_EQ(fault_count, 74);
ASSERT_TRUE(loaded_pages != NULL);
// Compare it to our own data.
PageSet expected_pages = simulation_->pages();
ASSERT_EQ(expected_pages.size(), loaded_pages->GetSize());
PageSet::iterator expected_pages_iter = expected_pages.begin();
base::ListValue::const_iterator loaded_pages_iter = loaded_pages->begin();
for (; expected_pages_iter != expected_pages.end();
expected_pages_iter++, loaded_pages_iter++) {
int page = 0;
ASSERT_EQ((*loaded_pages_iter)->GetType(), Value::TYPE_INTEGER);
ASSERT_TRUE((*loaded_pages_iter)->GetAsInteger(&page));
EXPECT_EQ(*expected_pages_iter, static_cast<uint32_t>(page));
}
}
} // namespace simulate