| Index: chrome_frame/function_stub_unittest.cc
|
| ===================================================================
|
| --- chrome_frame/function_stub_unittest.cc (revision 41717)
|
| +++ chrome_frame/function_stub_unittest.cc (working copy)
|
| @@ -1,65 +1,207 @@
|
| -// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
| +// 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/function_stub.h"
|
| #include "testing/gtest/include/gtest/gtest.h"
|
| +#include "testing/gmock/include/gmock/gmock.h"
|
|
|
| -#define NO_INLINE __declspec(noinline)
|
| -
|
| namespace {
|
|
|
| -typedef int (__stdcall* FooPrototype)();
|
| +// Test subclass to expose extra stuff.
|
| +class TestFunctionStub: public FunctionStub {
|
| + public:
|
| + static void Init(TestFunctionStub* stub) {
|
| + stub->FunctionStub::Init(&stub->stub_);
|
| + }
|
|
|
| -NO_INLINE int __stdcall Foo() {
|
| - return 1;
|
| + // Expose the offset to our signature_ field.
|
| + static const size_t kSignatureOffset;
|
| +
|
| + void set_signature(HMODULE signature) { signature_ = signature; }
|
| +};
|
| +
|
| +const size_t TestFunctionStub::kSignatureOffset =
|
| + FIELD_OFFSET(TestFunctionStub, signature_);
|
| +
|
| +class FunctionStubTest: public testing::Test {
|
| + public:
|
| + FunctionStubTest() : stub_(NULL) {
|
| + }
|
| +
|
| + virtual void SetUp() {
|
| + SYSTEM_INFO sys_info;
|
| + ::GetSystemInfo(&sys_info);
|
| +
|
| + // Playpen size is a system page.
|
| + playpen_size_ = sys_info.dwPageSize;
|
| +
|
| + // Reserve two pages.
|
| + playpen_ = reinterpret_cast<uint8*>(
|
| + ::VirtualAlloc(NULL,
|
| + 2 * playpen_size_,
|
| + MEM_RESERVE,
|
| + PAGE_EXECUTE_READWRITE));
|
| + ASSERT_TRUE(playpen_ != NULL);
|
| +
|
| + // And commit the first one.
|
| + ASSERT_TRUE(::VirtualAlloc(playpen_,
|
| + playpen_size_,
|
| + MEM_COMMIT,
|
| + PAGE_EXECUTE_READWRITE));
|
| + }
|
| +
|
| + virtual void TearDown() {
|
| + if (stub_ != NULL) {
|
| + EXPECT_TRUE(FunctionStub::Destroy(stub_));
|
| + }
|
| +
|
| + if (playpen_ != NULL) {
|
| + EXPECT_TRUE(::VirtualFree(playpen_, 0, MEM_RELEASE));
|
| + }
|
| + }
|
| +
|
| + protected:
|
| + typedef uintptr_t (CALLBACK *FuncPtr0)();
|
| + typedef uintptr_t (CALLBACK *FuncPtr1)(uintptr_t arg);
|
| +
|
| + MOCK_METHOD0(Foo0, uintptr_t());
|
| + MOCK_METHOD1(Foo1, uintptr_t(uintptr_t));
|
| + MOCK_METHOD0(Bar0, uintptr_t());
|
| + MOCK_METHOD1(Bar1, uintptr_t(uintptr_t));
|
| +
|
| + static uintptr_t CALLBACK FooCallback0(FunctionStubTest* test) {
|
| + return test->Foo0();
|
| + }
|
| + static uintptr_t CALLBACK FooCallback1(FunctionStubTest* test, uintptr_t arg) {
|
| + return test->Foo1(arg);
|
| + }
|
| + static uintptr_t CALLBACK BarCallback0(FunctionStubTest* test) {
|
| + return test->Foo0();
|
| + }
|
| + static uintptr_t CALLBACK BarCallback1(FunctionStubTest* test, uintptr_t arg) {
|
| + return test->Foo1(arg);
|
| + }
|
| +
|
| + // If a stub is allocated during testing, assigning it here
|
| + // will deallocate it at the end of test.
|
| + FunctionStub* stub_;
|
| +
|
| + // playpen_[0 .. playpen_size_ - 1] is committed, writable memory.
|
| + // playpen_[playpen_size_] is uncommitted, defined memory.
|
| + uint8* playpen_;
|
| + size_t playpen_size_;
|
| +};
|
| +
|
| +const uintptr_t kDivertedRetVal = 0x42;
|
| +const uintptr_t kFooRetVal = 0xCAFEBABE;
|
| +const uintptr_t kFooArg = 0xF0F0F0F0;
|
| +
|
| +uintptr_t CALLBACK Foo() {
|
| + return kFooRetVal;
|
| }
|
|
|
| -NO_INLINE int __stdcall PatchedFoo(FooPrototype original) {
|
| - return original() + 1;
|
| +uintptr_t CALLBACK FooDivert(uintptr_t arg) {
|
| + return kFooRetVal;
|
| }
|
|
|
| -} // end namespace
|
| +} // namespace
|
|
|
| -TEST(PatchTests, FunctionStub) {
|
| - EXPECT_EQ(Foo(), 1);
|
| - // Create a function stub that calls PatchedFoo and supplies it with
|
| - // a pointer to Foo.
|
| - FunctionStub* stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(&Foo),
|
| - &PatchedFoo);
|
| - EXPECT_TRUE(stub != NULL);
|
| - // Call the stub as it were Foo(). The call should get forwarded to Foo().
|
| - FooPrototype patch = reinterpret_cast<FooPrototype>(stub->code());
|
| - EXPECT_EQ(patch(), 2);
|
| - // Now neutralize the stub so that it calls Foo() directly without touching
|
| - // PatchedFoo().
|
| - // stub->BypassStub(&Foo);
|
| - stub->BypassStub(reinterpret_cast<void*>(stub->argument()));
|
| - EXPECT_EQ(patch(), 1);
|
| - // We're done with the stub.
|
| - FunctionStub::Destroy(stub);
|
| +TEST_F(FunctionStubTest, Accessors) {
|
| + uintptr_t argument = reinterpret_cast<uintptr_t>(this);
|
| + uintptr_t dest_fn = reinterpret_cast<uintptr_t>(FooDivert);
|
| + stub_ = FunctionStub::Create(argument, FooDivert);
|
| +
|
| + EXPECT_FALSE(stub_->is_bypassed());
|
| + EXPECT_TRUE(stub_->is_valid());
|
| + EXPECT_TRUE(stub_->code() != NULL);
|
| +
|
| + // Check that the stub code is executable.
|
| + MEMORY_BASIC_INFORMATION info = {};
|
| + EXPECT_NE(0, ::VirtualQuery(stub_->code(), &info, sizeof(info)));
|
| + const DWORD kExecutableMask = PAGE_EXECUTE | PAGE_EXECUTE_READ |
|
| + PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY;
|
| + EXPECT_NE(0, info.Protect & kExecutableMask);
|
| +
|
| + EXPECT_EQ(argument, stub_->argument());
|
| + EXPECT_TRUE(stub_->bypass_address() != NULL);
|
| + EXPECT_EQ(dest_fn, stub_->destination_function());
|
| }
|
|
|
| -// Basic tests to check the validity of a stub.
|
| -TEST(PatchTests, FunctionStubCompare) {
|
| - EXPECT_EQ(Foo(), 1);
|
| +TEST_F(FunctionStubTest, ZeroArgumentStub) {
|
| + stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(this),
|
| + &FunctionStubTest::FooCallback0);
|
|
|
| - // Detect the absence of a stub
|
| - FunctionStub* stub = reinterpret_cast<FunctionStub*>(&Foo);
|
| - EXPECT_FALSE(stub->is_valid());
|
| + FuncPtr0 func = reinterpret_cast<FuncPtr0>(stub_->code());
|
| + EXPECT_CALL(*this, Foo0())
|
| + .WillOnce(testing::Return(kDivertedRetVal));
|
|
|
| - stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(&Foo), &PatchedFoo);
|
| - EXPECT_TRUE(stub != NULL);
|
| - EXPECT_TRUE(stub->is_valid());
|
| + EXPECT_EQ(kDivertedRetVal, func());
|
| +}
|
|
|
| - FooPrototype patch = reinterpret_cast<FooPrototype>(stub->code());
|
| - EXPECT_EQ(patch(), 2);
|
| +TEST_F(FunctionStubTest, OneArgumentStub) {
|
| + stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(this),
|
| + &FunctionStubTest::FooCallback1);
|
|
|
| - // See if we can get the correct absolute pointer to the hook function
|
| - // back from the stub.
|
| - EXPECT_EQ(stub->absolute_target(), reinterpret_cast<uintptr_t>(&PatchedFoo));
|
| + FuncPtr1 func = reinterpret_cast<FuncPtr1>(stub_->code());
|
| + EXPECT_CALL(*this, Foo1(kFooArg))
|
| + .WillOnce(testing::Return(kDivertedRetVal));
|
|
|
| - // Also verify that the argument being passed to the hook function is indeed
|
| - // the pointer to the original function (again, absolute not relative).
|
| - EXPECT_EQ(stub->argument(), reinterpret_cast<uintptr_t>(&Foo));
|
| + EXPECT_EQ(kDivertedRetVal, func(kFooArg));
|
| }
|
| +
|
| +TEST_F(FunctionStubTest, Bypass) {
|
| + stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(this),
|
| + &FunctionStubTest::FooCallback0);
|
| +
|
| + FuncPtr0 func = reinterpret_cast<FuncPtr0>(stub_->code());
|
| + EXPECT_CALL(*this, Foo0())
|
| + .WillOnce(testing::Return(kDivertedRetVal));
|
| +
|
| + // This will call through to foo.
|
| + EXPECT_EQ(kDivertedRetVal, func());
|
| +
|
| + // Now bypass to Foo().
|
| + stub_->BypassStub(Foo);
|
| + EXPECT_TRUE(stub_->is_bypassed());
|
| + EXPECT_FALSE(stub_->is_valid());
|
| +
|
| + // We should not call through anymore.
|
| + EXPECT_CALL(*this, Foo0())
|
| + .Times(0);
|
| +
|
| + EXPECT_EQ(kFooRetVal, func());
|
| +}
|
| +
|
| +TEST_F(FunctionStubTest, FromCode) {
|
| + // We should get NULL and no crash from reserved memory.
|
| + EXPECT_EQ(NULL, FunctionStub::FromCode(playpen_ + playpen_size_));
|
| +
|
| + // Create a FunctionStub pointer whose signature_
|
| + // field hangs just off the playpen.
|
| + TestFunctionStub* stub =
|
| + reinterpret_cast<TestFunctionStub*>(playpen_ + playpen_size_ -
|
| + TestFunctionStub::kSignatureOffset);
|
| + TestFunctionStub::Init(stub);
|
| + EXPECT_EQ(NULL, FunctionStub::FromCode(stub));
|
| +
|
| + // Create a stub in committed memory.
|
| + stub = reinterpret_cast<TestFunctionStub*>(playpen_);
|
| + TestFunctionStub::Init(stub);
|
| + // Signature is NULL, which won't do.
|
| + EXPECT_EQ(NULL, FunctionStub::FromCode(stub));
|
| +
|
| + const DWORD kFlags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
|
| + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT;
|
| +
|
| + HMODULE my_module = NULL;
|
| + EXPECT_TRUE(::GetModuleHandleEx(kFlags,
|
| + reinterpret_cast<const wchar_t*>(&kDivertedRetVal),
|
| + &my_module));
|
| +
|
| + // Set our module as signature.
|
| + stub->set_signature(my_module);
|
| + EXPECT_EQ(stub, FunctionStub::FromCode(stub));
|
| +}
|
| +
|
|
|