OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
| 5 |
5 #include "chrome_frame/function_stub.h" | 6 #include "chrome_frame/function_stub.h" |
6 #include "testing/gtest/include/gtest/gtest.h" | 7 #include "testing/gtest/include/gtest/gtest.h" |
7 | 8 #include "testing/gmock/include/gmock/gmock.h" |
8 #define NO_INLINE __declspec(noinline) | |
9 | 9 |
10 namespace { | 10 namespace { |
11 | 11 |
12 typedef int (__stdcall* FooPrototype)(); | 12 // Test subclass to expose extra stuff. |
13 | 13 class TestFunctionStub: public FunctionStub { |
14 NO_INLINE int __stdcall Foo() { | 14 public: |
15 return 1; | 15 static void Init(TestFunctionStub* stub) { |
16 } | 16 stub->FunctionStub::Init(&stub->stub_); |
17 | 17 } |
18 NO_INLINE int __stdcall PatchedFoo(FooPrototype original) { | 18 |
19 return original() + 1; | 19 // Expose the offset to our signature_ field. |
20 } | 20 static const size_t kSignatureOffset; |
21 | 21 |
22 } // end namespace | 22 void set_signature(HMODULE signature) { signature_ = signature; } |
23 | 23 }; |
24 TEST(PatchTests, FunctionStub) { | 24 |
25 EXPECT_EQ(Foo(), 1); | 25 const size_t TestFunctionStub::kSignatureOffset = |
26 // Create a function stub that calls PatchedFoo and supplies it with | 26 FIELD_OFFSET(TestFunctionStub, signature_); |
27 // a pointer to Foo. | 27 |
28 FunctionStub* stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(&Foo), | 28 class FunctionStubTest: public testing::Test { |
29 &PatchedFoo); | 29 public: |
30 EXPECT_TRUE(stub != NULL); | 30 FunctionStubTest() : stub_(NULL) { |
31 // Call the stub as it were Foo(). The call should get forwarded to Foo(). | 31 } |
32 FooPrototype patch = reinterpret_cast<FooPrototype>(stub->code()); | 32 |
33 EXPECT_EQ(patch(), 2); | 33 virtual void SetUp() { |
34 // Now neutralize the stub so that it calls Foo() directly without touching | 34 SYSTEM_INFO sys_info; |
35 // PatchedFoo(). | 35 ::GetSystemInfo(&sys_info); |
36 // stub->BypassStub(&Foo); | 36 |
37 stub->BypassStub(reinterpret_cast<void*>(stub->argument())); | 37 // Playpen size is a system page. |
38 EXPECT_EQ(patch(), 1); | 38 playpen_size_ = sys_info.dwPageSize; |
39 // We're done with the stub. | 39 |
40 FunctionStub::Destroy(stub); | 40 // Reserve two pages. |
41 } | 41 playpen_ = reinterpret_cast<uint8*>( |
42 | 42 ::VirtualAlloc(NULL, |
43 // Basic tests to check the validity of a stub. | 43 2 * playpen_size_, |
44 TEST(PatchTests, FunctionStubCompare) { | 44 MEM_RESERVE, |
45 EXPECT_EQ(Foo(), 1); | 45 PAGE_EXECUTE_READWRITE)); |
46 | 46 ASSERT_TRUE(playpen_ != NULL); |
47 // Detect the absence of a stub | 47 |
48 FunctionStub* stub = reinterpret_cast<FunctionStub*>(&Foo); | 48 // And commit the first one. |
49 EXPECT_FALSE(stub->is_valid()); | 49 ASSERT_TRUE(::VirtualAlloc(playpen_, |
50 | 50 playpen_size_, |
51 stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(&Foo), &PatchedFoo); | 51 MEM_COMMIT, |
52 EXPECT_TRUE(stub != NULL); | 52 PAGE_EXECUTE_READWRITE)); |
53 EXPECT_TRUE(stub->is_valid()); | 53 } |
54 | 54 |
55 FooPrototype patch = reinterpret_cast<FooPrototype>(stub->code()); | 55 virtual void TearDown() { |
56 EXPECT_EQ(patch(), 2); | 56 if (stub_ != NULL) { |
57 | 57 EXPECT_TRUE(FunctionStub::Destroy(stub_)); |
58 // See if we can get the correct absolute pointer to the hook function | 58 } |
59 // back from the stub. | 59 |
60 EXPECT_EQ(stub->absolute_target(), reinterpret_cast<uintptr_t>(&PatchedFoo)); | 60 if (playpen_ != NULL) { |
61 | 61 EXPECT_TRUE(::VirtualFree(playpen_, 0, MEM_RELEASE)); |
62 // Also verify that the argument being passed to the hook function is indeed | 62 } |
63 // the pointer to the original function (again, absolute not relative). | 63 } |
64 EXPECT_EQ(stub->argument(), reinterpret_cast<uintptr_t>(&Foo)); | 64 |
65 } | 65 protected: |
| 66 typedef uintptr_t (CALLBACK *FuncPtr0)(); |
| 67 typedef uintptr_t (CALLBACK *FuncPtr1)(uintptr_t arg); |
| 68 |
| 69 MOCK_METHOD0(Foo0, uintptr_t()); |
| 70 MOCK_METHOD1(Foo1, uintptr_t(uintptr_t)); |
| 71 MOCK_METHOD0(Bar0, uintptr_t()); |
| 72 MOCK_METHOD1(Bar1, uintptr_t(uintptr_t)); |
| 73 |
| 74 static uintptr_t CALLBACK FooCallback0(FunctionStubTest* test) { |
| 75 return test->Foo0(); |
| 76 } |
| 77 static uintptr_t CALLBACK FooCallback1(FunctionStubTest* test, uintptr_t arg)
{ |
| 78 return test->Foo1(arg); |
| 79 } |
| 80 static uintptr_t CALLBACK BarCallback0(FunctionStubTest* test) { |
| 81 return test->Foo0(); |
| 82 } |
| 83 static uintptr_t CALLBACK BarCallback1(FunctionStubTest* test, uintptr_t arg)
{ |
| 84 return test->Foo1(arg); |
| 85 } |
| 86 |
| 87 // If a stub is allocated during testing, assigning it here |
| 88 // will deallocate it at the end of test. |
| 89 FunctionStub* stub_; |
| 90 |
| 91 // playpen_[0 .. playpen_size_ - 1] is committed, writable memory. |
| 92 // playpen_[playpen_size_] is uncommitted, defined memory. |
| 93 uint8* playpen_; |
| 94 size_t playpen_size_; |
| 95 }; |
| 96 |
| 97 const uintptr_t kDivertedRetVal = 0x42; |
| 98 const uintptr_t kFooRetVal = 0xCAFEBABE; |
| 99 const uintptr_t kFooArg = 0xF0F0F0F0; |
| 100 |
| 101 uintptr_t CALLBACK Foo() { |
| 102 return kFooRetVal; |
| 103 } |
| 104 |
| 105 uintptr_t CALLBACK FooDivert(uintptr_t arg) { |
| 106 return kFooRetVal; |
| 107 } |
| 108 |
| 109 } // namespace |
| 110 |
| 111 TEST_F(FunctionStubTest, Accessors) { |
| 112 uintptr_t argument = reinterpret_cast<uintptr_t>(this); |
| 113 uintptr_t dest_fn = reinterpret_cast<uintptr_t>(FooDivert); |
| 114 stub_ = FunctionStub::Create(argument, FooDivert); |
| 115 |
| 116 EXPECT_FALSE(stub_->is_bypassed()); |
| 117 EXPECT_TRUE(stub_->is_valid()); |
| 118 EXPECT_TRUE(stub_->code() != NULL); |
| 119 |
| 120 // Check that the stub code is executable. |
| 121 MEMORY_BASIC_INFORMATION info = {}; |
| 122 EXPECT_NE(0, ::VirtualQuery(stub_->code(), &info, sizeof(info))); |
| 123 const DWORD kExecutableMask = PAGE_EXECUTE | PAGE_EXECUTE_READ | |
| 124 PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY; |
| 125 EXPECT_NE(0, info.Protect & kExecutableMask); |
| 126 |
| 127 EXPECT_EQ(argument, stub_->argument()); |
| 128 EXPECT_TRUE(stub_->bypass_address() != NULL); |
| 129 EXPECT_EQ(dest_fn, stub_->destination_function()); |
| 130 } |
| 131 |
| 132 TEST_F(FunctionStubTest, ZeroArgumentStub) { |
| 133 stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(this), |
| 134 &FunctionStubTest::FooCallback0); |
| 135 |
| 136 FuncPtr0 func = reinterpret_cast<FuncPtr0>(stub_->code()); |
| 137 EXPECT_CALL(*this, Foo0()) |
| 138 .WillOnce(testing::Return(kDivertedRetVal)); |
| 139 |
| 140 EXPECT_EQ(kDivertedRetVal, func()); |
| 141 } |
| 142 |
| 143 TEST_F(FunctionStubTest, OneArgumentStub) { |
| 144 stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(this), |
| 145 &FunctionStubTest::FooCallback1); |
| 146 |
| 147 FuncPtr1 func = reinterpret_cast<FuncPtr1>(stub_->code()); |
| 148 EXPECT_CALL(*this, Foo1(kFooArg)) |
| 149 .WillOnce(testing::Return(kDivertedRetVal)); |
| 150 |
| 151 EXPECT_EQ(kDivertedRetVal, func(kFooArg)); |
| 152 } |
| 153 |
| 154 TEST_F(FunctionStubTest, Bypass) { |
| 155 stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(this), |
| 156 &FunctionStubTest::FooCallback0); |
| 157 |
| 158 FuncPtr0 func = reinterpret_cast<FuncPtr0>(stub_->code()); |
| 159 EXPECT_CALL(*this, Foo0()) |
| 160 .WillOnce(testing::Return(kDivertedRetVal)); |
| 161 |
| 162 // This will call through to foo. |
| 163 EXPECT_EQ(kDivertedRetVal, func()); |
| 164 |
| 165 // Now bypass to Foo(). |
| 166 stub_->BypassStub(Foo); |
| 167 EXPECT_TRUE(stub_->is_bypassed()); |
| 168 EXPECT_FALSE(stub_->is_valid()); |
| 169 |
| 170 // We should not call through anymore. |
| 171 EXPECT_CALL(*this, Foo0()) |
| 172 .Times(0); |
| 173 |
| 174 EXPECT_EQ(kFooRetVal, func()); |
| 175 } |
| 176 |
| 177 TEST_F(FunctionStubTest, FromCode) { |
| 178 // We should get NULL and no crash from reserved memory. |
| 179 EXPECT_EQ(NULL, FunctionStub::FromCode(playpen_ + playpen_size_)); |
| 180 |
| 181 // Create a FunctionStub pointer whose signature_ |
| 182 // field hangs just off the playpen. |
| 183 TestFunctionStub* stub = |
| 184 reinterpret_cast<TestFunctionStub*>(playpen_ + playpen_size_ - |
| 185 TestFunctionStub::kSignatureOffset); |
| 186 TestFunctionStub::Init(stub); |
| 187 EXPECT_EQ(NULL, FunctionStub::FromCode(stub)); |
| 188 |
| 189 // Create a stub in committed memory. |
| 190 stub = reinterpret_cast<TestFunctionStub*>(playpen_); |
| 191 TestFunctionStub::Init(stub); |
| 192 // Signature is NULL, which won't do. |
| 193 EXPECT_EQ(NULL, FunctionStub::FromCode(stub)); |
| 194 |
| 195 const DWORD kFlags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | |
| 196 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT; |
| 197 |
| 198 HMODULE my_module = NULL; |
| 199 EXPECT_TRUE(::GetModuleHandleEx(kFlags, |
| 200 reinterpret_cast<const wchar_t*>(&kDivertedRetVal), |
| 201 &my_module)); |
| 202 |
| 203 // Set our module as signature. |
| 204 stub->set_signature(my_module); |
| 205 EXPECT_EQ(stub, FunctionStub::FromCode(stub)); |
| 206 } |
| 207 |
OLD | NEW |