summaryrefslogtreecommitdiffstats
path: root/chrome_frame/vtable_patch_manager_unittest.cc
diff options
context:
space:
mode:
authorsiggi@chromium.org <siggi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-23 21:09:02 +0000
committersiggi@chromium.org <siggi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-23 21:09:02 +0000
commitd999c7c3f4bd9c7c779b4c06ee148d3d0d227e60 (patch)
treed4fca3d3c24d56a325000a4129d11c35b548abfa /chrome_frame/vtable_patch_manager_unittest.cc
parent722ed48734460567be2c97590a036480c4667815 (diff)
downloadchromium_src-d999c7c3f4bd9c7c779b4c06ee148d3d0d227e60.zip
chromium_src-d999c7c3f4bd9c7c779b4c06ee148d3d0d227e60.tar.gz
chromium_src-d999c7c3f4bd9c7c779b4c06ee148d3d0d227e60.tar.bz2
Reimplementation of FunctionStub to avoid rewriting potentially executing code for a slight improvement in thread safety.
Make VTABLE patching treadsafe to the extent possible. As-is it's now safe against itself running on other threads at lease, as well as against other similar implementations, though the inherent VM operation race is resolved by retrying. BUG=27415 TEST=Included unittests. Review URL: http://codereview.chromium.org/992008 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@42381 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome_frame/vtable_patch_manager_unittest.cc')
-rw-r--r--chrome_frame/vtable_patch_manager_unittest.cc287
1 files changed, 287 insertions, 0 deletions
diff --git a/chrome_frame/vtable_patch_manager_unittest.cc b/chrome_frame/vtable_patch_manager_unittest.cc
new file mode 100644
index 0000000..dd1eb83
--- /dev/null
+++ b/chrome_frame/vtable_patch_manager_unittest.cc
@@ -0,0 +1,287 @@
+// Copyright (c) 2010 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_frame/vtable_patch_manager.h"
+#include <unknwn.h>
+#include "base/thread.h"
+#include "base/scoped_handle.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+namespace {
+// GMock names we use.
+using testing::_;
+using testing::Return;
+
+class MockClassFactory : public IClassFactory {
+ public:
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, QueryInterface,
+ HRESULT(REFIID riid, void **object));
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, AddRef, ULONG());
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Release, ULONG());
+ MOCK_METHOD3_WITH_CALLTYPE(__stdcall, CreateInstance,
+ HRESULT (IUnknown *outer, REFIID riid, void **object));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, LockServer, HRESULT(BOOL lock));
+};
+
+// Retrieve the vtable for an interface.
+void* GetVtable(IUnknown* unk) {
+ return *reinterpret_cast<void**>(unk);
+}
+
+// Forward decl.
+extern vtable_patch::MethodPatchInfo IClassFactory_PatchInfo[];
+
+class VtablePatchManagerTest: public testing::Test {
+ public:
+ VtablePatchManagerTest() {
+ EXPECT_TRUE(current_ == NULL);
+ current_ = this;
+ }
+
+ ~VtablePatchManagerTest() {
+ EXPECT_TRUE(current_ == this);
+ current_ = NULL;
+ }
+
+ virtual void SetUp() {
+ // Make a backup of the test vtable and it's page protection settings.
+ void* vtable = GetVtable(&factory_);
+ MEMORY_BASIC_INFORMATION info;
+ ASSERT_TRUE(::VirtualQuery(vtable, &info, sizeof(info)));
+ vtable_protection_ = info.Protect;
+ memcpy(vtable_backup_, vtable, sizeof(vtable_backup_));
+ }
+
+ virtual void TearDown() {
+ // Unpatch to make sure we've restored state for subsequent test.
+ UnpatchInterfaceMethods(IClassFactory_PatchInfo);
+
+ // Restore the test vtable and its page protection settings.
+ void* vtable = GetVtable(&factory_);
+ DWORD old_protect = 0;
+ EXPECT_TRUE(::VirtualProtect(vtable, sizeof(vtable_backup_),
+ PAGE_EXECUTE_WRITECOPY, &old_protect));
+ memcpy(vtable, vtable_backup_, sizeof(vtable_backup_));
+ EXPECT_TRUE(::VirtualProtect(vtable, sizeof(vtable_backup_),
+ vtable_protection_, &old_protect));
+ }
+
+ typedef HRESULT (__stdcall* LockServerFun)(IClassFactory* self, BOOL lock);
+ MOCK_METHOD3(LockServerPatch,
+ HRESULT(LockServerFun old_fun, IClassFactory* self, BOOL lock));
+
+ static HRESULT STDMETHODCALLTYPE LockServerPatchCallback(
+ LockServerFun fun, IClassFactory* self, BOOL lock) {
+ EXPECT_TRUE(current_ != NULL);
+ if (current_ != NULL)
+ return current_->LockServerPatch(fun, self, lock);
+ else
+ return E_UNEXPECTED;
+ }
+
+ protected:
+ // Number of functions in the IClassFactory vtable.
+ static const size_t kFunctionCount = 5;
+
+ // Backup of the factory_ vtable as we found it at Setup.
+ PROC vtable_backup_[kFunctionCount];
+ // VirtualProtect flags on the factory_ vtable as we found it at Setup.
+ DWORD vtable_protection_;
+
+ // The mock factory class we patch.
+ MockClassFactory factory_;
+
+ // Current test running for routing the patch callback function.
+ static VtablePatchManagerTest* current_;
+};
+
+VtablePatchManagerTest* VtablePatchManagerTest::current_ = NULL;
+
+BEGIN_VTABLE_PATCHES(IClassFactory)
+ VTABLE_PATCH_ENTRY(4, &VtablePatchManagerTest::LockServerPatchCallback)
+END_VTABLE_PATCHES();
+
+} // namespace
+
+TEST_F(VtablePatchManagerTest, ReplacePointer) {
+ void* const kFunctionOriginal = reinterpret_cast<void*>(0xCAFEBABE);
+ void* const kFunctionFoo = reinterpret_cast<void*>(0xF0F0F0F0);
+ void* const kFunctionBar = reinterpret_cast<void*>(0xBABABABA);
+
+ using vtable_patch::internal::ReplaceFunctionPointer;
+ // Replacing a non-writable location should fail, but not crash.
+ EXPECT_FALSE(ReplaceFunctionPointer(NULL, kFunctionBar, kFunctionFoo));
+
+ void* foo_entry = kFunctionOriginal;
+ // Replacing with the wrong original function should
+ // fail and not change the entry.
+ EXPECT_FALSE(ReplaceFunctionPointer(&foo_entry, kFunctionBar, kFunctionFoo));
+ EXPECT_EQ(foo_entry, kFunctionOriginal);
+
+ // Replacing with the correct original should succeed.
+ EXPECT_TRUE(ReplaceFunctionPointer(&foo_entry,
+ kFunctionBar,
+ kFunctionOriginal));
+ EXPECT_EQ(foo_entry, kFunctionBar);
+}
+
+TEST_F(VtablePatchManagerTest, PatchInterfaceMethods) {
+ // Unpatched.
+ EXPECT_CALL(factory_, LockServer(TRUE))
+ .WillOnce(Return(E_FAIL));
+ EXPECT_EQ(E_FAIL, factory_.LockServer(TRUE));
+
+ EXPECT_HRESULT_SUCCEEDED(
+ PatchInterfaceMethods(&factory_, IClassFactory_PatchInfo));
+
+ EXPECT_NE(0, memcmp(GetVtable(&factory_),
+ vtable_backup_,
+ sizeof(vtable_backup_)));
+
+ // This should not be called while the patch is in effect.
+ EXPECT_CALL(factory_, LockServer(_))
+ .Times(0);
+
+ EXPECT_CALL(*this, LockServerPatch(testing::_, &factory_, TRUE))
+ .WillOnce(testing::Return(S_FALSE));
+
+ EXPECT_EQ(S_FALSE, factory_.LockServer(TRUE));
+}
+
+TEST_F(VtablePatchManagerTest, UnpatchInterfaceMethods) {
+ // Patch it.
+ EXPECT_HRESULT_SUCCEEDED(
+ PatchInterfaceMethods(&factory_, IClassFactory_PatchInfo));
+
+ EXPECT_NE(0, memcmp(GetVtable(&factory_),
+ vtable_backup_,
+ sizeof(vtable_backup_)));
+
+ // This should not be called while the patch is in effect.
+ EXPECT_CALL(factory_, LockServer(testing::_))
+ .Times(0);
+
+ EXPECT_CALL(*this, LockServerPatch(testing::_, &factory_, TRUE))
+ .WillOnce(testing::Return(S_FALSE));
+
+ EXPECT_EQ(S_FALSE, factory_.LockServer(TRUE));
+
+ // Now unpatch.
+ EXPECT_HRESULT_SUCCEEDED(
+ UnpatchInterfaceMethods(IClassFactory_PatchInfo));
+
+ // And check that the call comes through correctly.
+ EXPECT_CALL(factory_, LockServer(FALSE))
+ .WillOnce(testing::Return(E_FAIL));
+ EXPECT_EQ(E_FAIL, factory_.LockServer(FALSE));
+}
+
+TEST_F(VtablePatchManagerTest, DoublePatch) {
+ // Patch it.
+ EXPECT_HRESULT_SUCCEEDED(
+ PatchInterfaceMethods(&factory_, IClassFactory_PatchInfo));
+
+ // Capture the VTable after patching.
+ PROC vtable[kFunctionCount];
+ memcpy(vtable, GetVtable(&factory_), sizeof(vtable));
+
+ // Patch it again, this should be idempotent.
+ EXPECT_HRESULT_SUCCEEDED(
+ PatchInterfaceMethods(&factory_, IClassFactory_PatchInfo));
+
+ // Should not have changed the VTable on second call.
+ EXPECT_EQ(0, memcmp(vtable, GetVtable(&factory_), sizeof(vtable)));
+}
+
+namespace vtable_patch {
+// Expose internal implementation detail, purely for testing.
+extern Lock patch_lock_;
+
+} // namespace vtable_patch
+
+TEST_F(VtablePatchManagerTest, ThreadSafePatching) {
+ // It's difficult to test for threadsafe patching, but as a close proxy,
+ // test for no patching happening from a background thread while the patch
+ // lock is held.
+ base::Thread background("Background Test Thread");
+
+ EXPECT_TRUE(background.Start());
+ ScopedHandle event(::CreateEvent(NULL, TRUE, FALSE, NULL));
+
+ // Grab the patch lock.
+ vtable_patch::patch_lock_.Acquire();
+
+ // Instruct the background thread to patch factory_.
+ background.message_loop()->PostTask(FROM_HERE,
+ NewRunnableFunction(&vtable_patch::PatchInterfaceMethods,
+ &factory_,
+ &IClassFactory_PatchInfo[0]));
+
+ // And subsequently to signal the event. Neither of these actions should
+ // occur until we've released the patch lock.
+ background.message_loop()->PostTask(FROM_HERE,
+ NewRunnableFunction(::SetEvent, event.Get()));
+
+ // Wait for a little while, to give the background thread time to process.
+ // We expect this wait to time out, as the background thread should end up
+ // blocking on the patch lock.
+ EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(event.Get(), 50));
+
+ // Verify that patching did not take place yet.
+ EXPECT_CALL(factory_, LockServer(TRUE))
+ .WillOnce(Return(S_FALSE));
+ EXPECT_EQ(S_FALSE, factory_.LockServer(TRUE));
+
+ // Release the lock and wait on the event again to ensure
+ // the patching has taken place now.
+ vtable_patch::patch_lock_.Release();
+ EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(event.Get(), INFINITE));
+
+ // We should not get called here anymore.
+ EXPECT_CALL(factory_, LockServer(TRUE))
+ .Times(0);
+
+ // But should be diverted here.
+ EXPECT_CALL(*this, LockServerPatch(_, &factory_, TRUE))
+ .WillOnce(Return(S_FALSE));
+ EXPECT_EQ(S_FALSE, factory_.LockServer(TRUE));
+
+ // Same deal for unpatching.
+ ::ResetEvent(event.Get());
+
+ // Grab the patch lock.
+ vtable_patch::patch_lock_.Acquire();
+
+ // Instruct the background thread to unpatch.
+ background.message_loop()->PostTask(FROM_HERE,
+ NewRunnableFunction(&vtable_patch::UnpatchInterfaceMethods,
+ &IClassFactory_PatchInfo[0]));
+
+ // And subsequently to signal the event. Neither of these actions should
+ // occur until we've released the patch lock.
+ background.message_loop()->PostTask(FROM_HERE,
+ NewRunnableFunction(::SetEvent, event.Get()));
+
+ // Wait for a little while, to give the background thread time to process.
+ // We expect this wait to time out, as the background thread should end up
+ // blocking on the patch lock.
+ EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(event.Get(), 50));
+
+ // We should still be patched.
+ EXPECT_CALL(factory_, LockServer(TRUE))
+ .Times(0);
+ EXPECT_CALL(*this, LockServerPatch(_, &factory_, TRUE))
+ .WillOnce(Return(S_FALSE));
+ EXPECT_EQ(S_FALSE, factory_.LockServer(TRUE));
+
+ // Release the patch lock and wait on the event.
+ vtable_patch::patch_lock_.Release();
+ EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(event.Get(), INFINITE));
+
+ // Verify that unpatching took place.
+ EXPECT_CALL(factory_, LockServer(TRUE))
+ .WillOnce(Return(S_FALSE));
+ EXPECT_EQ(S_FALSE, factory_.LockServer(TRUE));
+}