blob: 3dacd4cfb60031f4375b1a2355d855c919e2066f [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/conflicts/module_database_win.h"
#include <algorithm>
#include <memory>
#include <vector>
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/simple_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
// A simple mechanism for running a single task on a separate thread.
class SingleTaskRunner : public base::SimpleThread {
public:
explicit SingleTaskRunner(base::Closure task)
: base::SimpleThread("SingleTaskRunner"), task_(std::move(task)) {}
// Runs the provided task and exits.
void Run() override { task_.Run(); }
private:
base::Closure task_;
DISALLOW_COPY_AND_ASSIGN(SingleTaskRunner);
};
// Launches a thread and runs a single task on it.
void RunTask(base::Closure task) {
SingleTaskRunner task_runner(std::move(task));
task_runner.Start();
task_runner.Join();
}
constexpr uint32_t kPid1 = 1234u;
constexpr uint32_t kPid2 = 2345u;
constexpr uint64_t kCreateTime1 = 1234u;
constexpr uint64_t kCreateTime2 = 2345u;
constexpr wchar_t kDll1[] = L"dummy.dll";
constexpr wchar_t kDll2[] = L"foo.dll";
constexpr size_t kSize1 = 100 * 4096;
constexpr size_t kSize2 = 20 * 4096;
constexpr uint32_t kTime1 = 0xDEADBEEF;
constexpr uint32_t kTime2 = 0xBAADF00D;
constexpr uintptr_t kGoodAddress1 = 0x04000000u;
constexpr uintptr_t kGoodAddress2 = 0x05000000u;
} // namespace
class TestModuleDatabase : ModuleDatabase {
public:
// Types.
using ModuleDatabase::ModuleLoadAddresses;
// Constants.
using ModuleDatabase::kInvalidIndex;
// Functions.
using ModuleDatabase::FindLoadAddressIndexById;
using ModuleDatabase::FindLoadAddressIndexByAddress;
using ModuleDatabase::InsertLoadAddress;
using ModuleDatabase::RemoveLoadAddressById;
using ModuleDatabase::RemoveLoadAddressByIndex;
};
class ModuleDatabaseTest : public testing::Test {
protected:
ModuleDatabaseTest()
: dll1_(kDll1),
dll2_(kDll2),
module_database_(base::MakeUnique<ModuleDatabase>(
base::SequencedTaskRunnerHandle::Get())) {}
const ModuleDatabase::ModuleMap& modules() {
return module_database_->modules_;
}
const ModuleDatabase::ProcessMap& processes() {
return module_database_->processes_;
}
ModuleDatabase* module_database() { return module_database_.get(); }
static uint32_t ProcessTypeToBit(content::ProcessType process_type) {
return ModuleDatabase::ProcessTypeToBit(process_type);
}
// Counts the occurrences of the given |module_id| in the given collection of
// |load_addresses|.
static size_t ModuleIdCount(
ModuleId module_id,
const ModuleDatabase::ModuleLoadAddresses& load_addresses) {
return std::count_if(
load_addresses.begin(), load_addresses.end(),
[module_id](const auto& x) { return module_id == x.first; });
}
const base::FilePath dll1_;
const base::FilePath dll2_;
private:
// Must be before |module_database_|.
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<ModuleDatabase> module_database_;
DISALLOW_COPY_AND_ASSIGN(ModuleDatabaseTest);
};
TEST_F(ModuleDatabaseTest, LoadAddressVectorOperations) {
using TMD = TestModuleDatabase;
TMD::ModuleLoadAddresses la;
// Finds should fail in an empty collection.
EXPECT_EQ(TMD::kInvalidIndex, TMD::FindLoadAddressIndexById(0, la));
EXPECT_EQ(TMD::kInvalidIndex, TMD::FindLoadAddressIndexById(0x04000000, la));
// A first insert should work. Don't start with ModuleId 0 so that later
// inserts can insert that module.
TMD::InsertLoadAddress(10, 0x04000000, &la);
EXPECT_EQ(1u, la.size());
EXPECT_EQ(10, la[0].first);
EXPECT_EQ(0x04000000u, la[0].second);
// Finds should work.
EXPECT_EQ(TMD::kInvalidIndex, TMD::FindLoadAddressIndexById(0, la));
EXPECT_EQ(TMD::kInvalidIndex, TMD::FindLoadAddressIndexById(0x03000000, la));
EXPECT_EQ(0u, TMD::FindLoadAddressIndexById(10, la));
EXPECT_EQ(0u, TMD::FindLoadAddressIndexByAddress(0x04000000, la));
// A second insert should work. This is the new max so should be at the end
// of the collection.
TMD::InsertLoadAddress(12, 0x06000000, &la);
EXPECT_EQ(2u, la.size());
EXPECT_EQ(10, la[0].first);
EXPECT_EQ(0x04000000u, la[0].second);
EXPECT_EQ(12, la[1].first);
EXPECT_EQ(0x06000000u, la[1].second);
// Finds should work.
EXPECT_EQ(TMD::kInvalidIndex, TMD::FindLoadAddressIndexById(0, la));
EXPECT_EQ(TMD::kInvalidIndex, TMD::FindLoadAddressIndexById(0x03000000, la));
EXPECT_EQ(0u, TMD::FindLoadAddressIndexById(10, la));
EXPECT_EQ(0u, TMD::FindLoadAddressIndexByAddress(0x04000000, la));
EXPECT_EQ(1u, TMD::FindLoadAddressIndexById(12, la));
EXPECT_EQ(1u, TMD::FindLoadAddressIndexByAddress(0x06000000, la));
// Another insert should work. This is not the new max, so a swap should
// happen to keep the maximum element at the end of the collection.
TMD::InsertLoadAddress(11, 0x05000000, &la);
EXPECT_EQ(3u, la.size());
EXPECT_EQ(10, la[0].first);
EXPECT_EQ(0x04000000u, la[0].second);
EXPECT_EQ(11, la[1].first);
EXPECT_EQ(0x05000000u, la[1].second);
EXPECT_EQ(12, la[2].first);
EXPECT_EQ(0x06000000u, la[2].second);
// An insert of an existing module should work, but simply overwrite the
// load address.
TMD::InsertLoadAddress(11, 0x0F000000, &la);
EXPECT_EQ(3u, la.size());
EXPECT_EQ(11, la[1].first);
EXPECT_EQ(0x0F000000u, la[1].second);
TMD::InsertLoadAddress(11, 0x05000000, &la);
EXPECT_EQ(3u, la.size());
EXPECT_EQ(11, la[1].first);
EXPECT_EQ(0x05000000u, la[1].second);
// Finds should work.
EXPECT_EQ(TMD::kInvalidIndex, TMD::FindLoadAddressIndexById(0, la));
EXPECT_EQ(TMD::kInvalidIndex, TMD::FindLoadAddressIndexById(0x03000000, la));
EXPECT_EQ(0u, TMD::FindLoadAddressIndexById(10, la));
EXPECT_EQ(0u, TMD::FindLoadAddressIndexByAddress(0x04000000, la));
EXPECT_EQ(1u, TMD::FindLoadAddressIndexById(11, la));
EXPECT_EQ(1u, TMD::FindLoadAddressIndexByAddress(0x05000000, la));
EXPECT_EQ(2u, TMD::FindLoadAddressIndexById(12, la));
EXPECT_EQ(2u, TMD::FindLoadAddressIndexByAddress(0x06000000, la));
// Do some inserts of lower modules IDs. This ensures that we'll have some
// higher module IDs in the vector before some lower modules IDs, for testing
// the deletion logic.
TMD::InsertLoadAddress(3, 0x07000000, &la);
TMD::InsertLoadAddress(4, 0x08000000, &la);
TMD::InsertLoadAddress(5, 0x09000000, &la);
EXPECT_EQ(6u, la.size());
EXPECT_EQ(10, la[0].first);
EXPECT_EQ(0x04000000u, la[0].second);
EXPECT_EQ(11, la[1].first);
EXPECT_EQ(0x05000000u, la[1].second);
EXPECT_EQ(3, la[2].first);
EXPECT_EQ(0x07000000u, la[2].second);
EXPECT_EQ(4, la[3].first);
EXPECT_EQ(0x08000000u, la[3].second);
EXPECT_EQ(5, la[4].first);
EXPECT_EQ(0x09000000u, la[4].second);
EXPECT_EQ(12, la[5].first);
EXPECT_EQ(0x06000000u, la[5].second);
// Remove an element that isn't in the second last position. The second last
// element should be swapped into its position, and the last element moved
// to the second last place.
TMD::RemoveLoadAddressByIndex(2, &la);
EXPECT_EQ(5u, la.size());
EXPECT_EQ(10, la[0].first);
EXPECT_EQ(0x04000000u, la[0].second);
EXPECT_EQ(11, la[1].first);
EXPECT_EQ(0x05000000u, la[1].second);
EXPECT_EQ(5, la[2].first);
EXPECT_EQ(0x09000000u, la[2].second);
EXPECT_EQ(4, la[3].first);
EXPECT_EQ(0x08000000u, la[3].second);
EXPECT_EQ(12, la[4].first);
EXPECT_EQ(0x06000000u, la[4].second);
// Remove the second last element. Only the last element should move.
TMD::RemoveLoadAddressByIndex(3, &la);
EXPECT_EQ(4u, la.size());
EXPECT_EQ(10, la[0].first);
EXPECT_EQ(0x04000000u, la[0].second);
EXPECT_EQ(11, la[1].first);
EXPECT_EQ(0x05000000u, la[1].second);
EXPECT_EQ(5, la[2].first);
EXPECT_EQ(0x09000000u, la[2].second);
EXPECT_EQ(12, la[3].first);
EXPECT_EQ(0x06000000u, la[3].second);
// Remove the last element. The new maximum should be found moved to the
// end.
TMD::RemoveLoadAddressByIndex(3, &la);
EXPECT_EQ(3u, la.size());
EXPECT_EQ(10, la[0].first);
EXPECT_EQ(0x04000000u, la[0].second);
EXPECT_EQ(5, la[1].first);
EXPECT_EQ(0x09000000u, la[1].second);
EXPECT_EQ(11, la[2].first);
EXPECT_EQ(0x05000000u, la[2].second);
// Remove the last element by ModuleId. The remaining modules should be
// swapped.
TMD::RemoveLoadAddressById(11, &la);
EXPECT_EQ(2u, la.size());
EXPECT_EQ(5, la[0].first);
EXPECT_EQ(0x09000000u, la[0].second);
EXPECT_EQ(10, la[1].first);
EXPECT_EQ(0x04000000u, la[1].second);
// Remove the first element by ModuleId.
TMD::RemoveLoadAddressById(5, &la);
EXPECT_EQ(1u, la.size());
EXPECT_EQ(10, la[0].first);
EXPECT_EQ(0x04000000u, la[0].second);
// Remove the only remaining element.
TMD::RemoveLoadAddressByIndex(0, &la);
EXPECT_TRUE(la.empty());
}
TEST_F(ModuleDatabaseTest, LoadAddressVectorStressTest) {
using TMD = TestModuleDatabase;
TMD::ModuleLoadAddresses la;
for (size_t n = 1; n < 200; ++n) {
// Will keep track of which elements have been inserted.
std::vector<bool> inserted(n);
size_t inserted_count = 0;
// Generate a shuffled list of IDs. This will be the insertion order.
// More insertions than elements will occur so that rewrites occur.,
std::vector<ModuleId> ids(11 * n / 10);
for (size_t i = 0; i < 11 * n / 10; ++i)
ids[i] = i % n;
std::random_shuffle(ids.begin(), ids.end());
// Do the insertions.
for (auto id : ids) {
if (!inserted[id]) {
inserted[id] = true;
++inserted_count;
}
// Generate a load address. The load address bakes in the index so that
// searching by load address is easy.
uintptr_t load_address = static_cast<uintptr_t>(id) << 16;
// Do the insertion.
TMD::InsertLoadAddress(id, load_address, &la);
EXPECT_EQ(inserted_count, la.size());
}
// Validate that every element is there, via both search mechanisms.
for (size_t id = 0; id < n; ++id) {
uintptr_t load_address = static_cast<uintptr_t>(id) << 16;
size_t index1 = TMD::FindLoadAddressIndexById(id, la);
size_t index2 = TMD::FindLoadAddressIndexByAddress(load_address, la);
EXPECT_NE(TMD::kInvalidIndex, index1);
EXPECT_EQ(index1, index2);
}
// Generate the deletion order.
ids.resize(n);
for (size_t i = 0; i < ids.size(); ++i)
ids[i] = i;
std::random_shuffle(ids.begin(), ids.end());
// Do the deletions.
for (auto id : ids) {
--inserted_count;
TMD::RemoveLoadAddressById(id, &la);
EXPECT_EQ(inserted_count, la.size());
}
}
}
TEST_F(ModuleDatabaseTest, TasksAreBounced) {
// Run a task on the current thread. This should not be bounced, so their
// results should be immediately available.
module_database()->OnProcessStarted(kPid1, kCreateTime1,
content::PROCESS_TYPE_BROWSER);
EXPECT_EQ(1u, processes().size());
module_database()->OnModuleLoad(kPid1, kCreateTime1, dll1_, kSize1, kTime1,
kGoodAddress1);
EXPECT_EQ(1u, modules().size());
module_database()->OnProcessEnded(kPid1, kCreateTime1);
EXPECT_EQ(0u, processes().size());
// Indicate another process start on this thread. This call can't be
// bounced.
module_database()->OnProcessStarted(kPid2, kCreateTime2,
content::PROCESS_TYPE_BROWSER);
EXPECT_EQ(1u, processes().size());
// Run similar tasks on another thread with another module. These should be
// bounced.
RunTask(base::Bind(&ModuleDatabase::OnModuleLoad,
base::Unretained(module_database()), kPid2, kCreateTime2,
dll2_, kSize1, kTime1, kGoodAddress1));
EXPECT_EQ(1u, modules().size());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(2u, modules().size());
RunTask(base::Bind(&ModuleDatabase::OnProcessEnded,
base::Unretained(module_database()), kPid2, kCreateTime2));
EXPECT_EQ(1u, processes().size());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0u, processes().size());
}
TEST_F(ModuleDatabaseTest, EventsWithoutProcessIgnore) {
EXPECT_EQ(0u, modules().size());
EXPECT_EQ(0u, processes().size());
module_database()->OnModuleLoad(kPid1, kCreateTime1, dll1_, kSize1, kTime1,
kGoodAddress1);
EXPECT_EQ(0u, modules().size());
EXPECT_EQ(0u, processes().size());
}
TEST_F(ModuleDatabaseTest, OrphanedUnloadIgnored) {
EXPECT_EQ(0u, modules().size());
EXPECT_EQ(0u, processes().size());
// Start a process.
module_database()->OnProcessStarted(kPid1, kCreateTime1,
content::PROCESS_TYPE_BROWSER);
EXPECT_EQ(0u, modules().size());
EXPECT_EQ(1u, processes().size());
auto p1 = processes().begin();
EXPECT_EQ(kPid1, p1->first.process_id);
EXPECT_EQ(kCreateTime1, p1->first.creation_time);
EXPECT_EQ(content::PROCESS_TYPE_BROWSER, p1->first.process_type);
EXPECT_EQ(0u, p1->second.loaded_modules.size());
EXPECT_EQ(0u, p1->second.unloaded_modules.size());
// Indicate a module unload. This should do nothing because there's no
// corresponding module.
module_database()->OnModuleUnload(kPid1, kCreateTime1, kGoodAddress1);
EXPECT_EQ(0u, modules().size());
EXPECT_EQ(1u, processes().size());
EXPECT_EQ(0u, p1->second.loaded_modules.size());
EXPECT_EQ(0u, p1->second.unloaded_modules.size());
}
TEST_F(ModuleDatabaseTest, DatabaseIsConsistent) {
EXPECT_EQ(0u, modules().size());
EXPECT_EQ(0u, processes().size());
// Start a process.
module_database()->OnProcessStarted(kPid1, kCreateTime1,
content::PROCESS_TYPE_BROWSER);
EXPECT_EQ(0u, modules().size());
EXPECT_EQ(1u, processes().size());
auto p1 = processes().begin();
EXPECT_EQ(kPid1, p1->first.process_id);
EXPECT_EQ(kCreateTime1, p1->first.creation_time);
EXPECT_EQ(content::PROCESS_TYPE_BROWSER, p1->first.process_type);
EXPECT_EQ(0u, p1->second.loaded_modules.size());
EXPECT_EQ(0u, p1->second.unloaded_modules.size());
// Load a module.
module_database()->OnModuleLoad(kPid1, kCreateTime1, dll1_, kSize1, kTime1,
kGoodAddress1);
EXPECT_EQ(1u, modules().size());
EXPECT_EQ(1u, processes().size());
// Ensure that the process and module sets are up to date.
auto m1 = modules().begin();
EXPECT_EQ(dll1_, m1->first.module_path);
EXPECT_EQ(ProcessTypeToBit(content::PROCESS_TYPE_BROWSER),
m1->second.process_types);
EXPECT_EQ(kPid1, p1->first.process_id);
EXPECT_EQ(kCreateTime1, p1->first.creation_time);
EXPECT_EQ(content::PROCESS_TYPE_BROWSER, p1->first.process_type);
EXPECT_EQ(1u, p1->second.loaded_modules.size());
EXPECT_EQ(0u, p1->second.unloaded_modules.size());
EXPECT_EQ(1u, ModuleIdCount(m1->first.module_id, p1->second.loaded_modules));
// Provide a redundant load message for that module.
module_database()->OnModuleLoad(kPid1, kCreateTime1, dll1_, kSize1, kTime1,
kGoodAddress1);
EXPECT_EQ(1u, modules().size());
EXPECT_EQ(1u, processes().size());
// Ensure that the process and module sets haven't changed.
EXPECT_EQ(dll1_, m1->first.module_path);
EXPECT_EQ(ProcessTypeToBit(content::PROCESS_TYPE_BROWSER),
m1->second.process_types);
EXPECT_EQ(kPid1, p1->first.process_id);
EXPECT_EQ(kCreateTime1, p1->first.creation_time);
EXPECT_EQ(content::PROCESS_TYPE_BROWSER, p1->first.process_type);
EXPECT_EQ(1u, p1->second.loaded_modules.size());
EXPECT_EQ(0u, p1->second.unloaded_modules.size());
EXPECT_EQ(1u, ModuleIdCount(m1->first.module_id, p1->second.loaded_modules));
// Load a second module into the process.
module_database()->OnModuleLoad(kPid1, kCreateTime1, dll2_, kSize2, kTime2,
kGoodAddress2);
EXPECT_EQ(2u, modules().size());
EXPECT_EQ(1u, processes().size());
// Ensure that the process and module sets are up to date.
auto m2 = modules().rbegin();
EXPECT_EQ(dll2_, m2->first.module_path);
EXPECT_EQ(ProcessTypeToBit(content::PROCESS_TYPE_BROWSER),
m2->second.process_types);
EXPECT_EQ(kPid1, p1->first.process_id);
EXPECT_EQ(kCreateTime1, p1->first.creation_time);
EXPECT_EQ(content::PROCESS_TYPE_BROWSER, p1->first.process_type);
EXPECT_EQ(2u, p1->second.loaded_modules.size());
EXPECT_EQ(0u, p1->second.unloaded_modules.size());
EXPECT_EQ(1u, ModuleIdCount(m1->first.module_id, p1->second.loaded_modules));
EXPECT_EQ(1u, ModuleIdCount(m2->first.module_id, p1->second.loaded_modules));
// Unload the second module.
module_database()->OnModuleUnload(kPid1, kCreateTime1, kGoodAddress2);
EXPECT_EQ(2u, modules().size());
EXPECT_EQ(1u, processes().size());
// Ensure that the process and module sets are up to date.
EXPECT_EQ(dll2_, m2->first.module_path);
EXPECT_EQ(ProcessTypeToBit(content::PROCESS_TYPE_BROWSER),
m2->second.process_types);
EXPECT_EQ(kPid1, p1->first.process_id);
EXPECT_EQ(kCreateTime1, p1->first.creation_time);
EXPECT_EQ(content::PROCESS_TYPE_BROWSER, p1->first.process_type);
EXPECT_EQ(1u, p1->second.loaded_modules.size());
EXPECT_EQ(1u, p1->second.unloaded_modules.size());
EXPECT_EQ(1u, ModuleIdCount(m1->first.module_id, p1->second.loaded_modules));
EXPECT_EQ(1u,
ModuleIdCount(m2->first.module_id, p1->second.unloaded_modules));
// Start a process.
module_database()->OnProcessStarted(kPid2, kCreateTime2,
content::PROCESS_TYPE_RENDERER);
EXPECT_EQ(2u, modules().size());
EXPECT_EQ(2u, processes().size());
auto p2 = processes().rbegin();
EXPECT_EQ(kPid2, p2->first.process_id);
EXPECT_EQ(kCreateTime2, p2->first.creation_time);
EXPECT_EQ(content::PROCESS_TYPE_RENDERER, p2->first.process_type);
EXPECT_EQ(0u, p2->second.loaded_modules.size());
EXPECT_EQ(0u, p2->second.unloaded_modules.size());
// Load the dummy.dll in the second process as well.
module_database()->OnModuleLoad(kPid2, kCreateTime2, dll1_, kSize1, kTime1,
kGoodAddress1);
EXPECT_EQ(ProcessTypeToBit(content::PROCESS_TYPE_BROWSER) |
ProcessTypeToBit(content::PROCESS_TYPE_RENDERER),
m1->second.process_types);
EXPECT_EQ(kPid2, p2->first.process_id);
EXPECT_EQ(kCreateTime2, p2->first.creation_time);
EXPECT_EQ(content::PROCESS_TYPE_RENDERER, p2->first.process_type);
EXPECT_EQ(1u, p2->second.loaded_modules.size());
EXPECT_EQ(0u, p2->second.unloaded_modules.size());
EXPECT_EQ(1u, ModuleIdCount(m1->first.module_id, p2->second.loaded_modules));
// End the second process without an explicit unload. This invalidates |p2|.
module_database()->OnProcessEnded(kPid2, kCreateTime2);
EXPECT_EQ(2u, modules().size());
EXPECT_EQ(1u, processes().size());
EXPECT_EQ(kPid1, p1->first.process_id);
EXPECT_EQ(kCreateTime1, p1->first.creation_time);
EXPECT_EQ(ProcessTypeToBit(content::PROCESS_TYPE_BROWSER) |
ProcessTypeToBit(content::PROCESS_TYPE_RENDERER),
m1->second.process_types);
EXPECT_EQ(ProcessTypeToBit(content::PROCESS_TYPE_BROWSER),
m2->second.process_types);
// End the first process without an explicit unload. This invalidates |p1|.
module_database()->OnProcessEnded(kPid1, kCreateTime1);
EXPECT_EQ(2u, modules().size());
EXPECT_EQ(0u, processes().size());
EXPECT_EQ(ProcessTypeToBit(content::PROCESS_TYPE_BROWSER) |
ProcessTypeToBit(content::PROCESS_TYPE_RENDERER),
m1->second.process_types);
EXPECT_EQ(ProcessTypeToBit(content::PROCESS_TYPE_BROWSER),
m2->second.process_types);
}