blob: 30a8fe25f7542df38dc46c0e11336040567a3c7a [file] [log] [blame]
[email protected]3b63f8f42011-03-28 01:54:151// Copyright (c) 2011 The Chromium Authors. All rights reserved.
[email protected]f7817822009-09-24 05:11:582// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome_frame/vtable_patch_manager.h"
6
[email protected]53b19ac12010-07-21 16:35:217#include <atlcomcli.h>
[email protected]05358092010-07-21 16:07:248
[email protected]5ae94d22010-07-21 19:55:369#include <algorithm>
10
[email protected]d999c7c2010-03-23 21:09:0211#include "base/atomicops.h"
[email protected]f7817822009-09-24 05:11:5812#include "base/logging.h"
[email protected]3b63f8f42011-03-28 01:54:1513#include "base/memory/scoped_ptr.h"
[email protected]20305ec2011-01-21 04:55:5214#include "base/synchronization/lock.h"
[email protected]f7817822009-09-24 05:11:5815#include "chrome_frame/function_stub.h"
[email protected]ec18ca672010-09-02 13:39:5016#include "chrome_frame/utils.h"
[email protected]f7817822009-09-24 05:11:5817
18namespace vtable_patch {
19
[email protected]d999c7c2010-03-23 21:09:0220// The number of times we retry a patch/unpatch operation in case of
21// VM races with other 3rd party software trying to patch the same thing.
22const int kMaxRetries = 3;
23
24// We hold a lock over all patching operations to make sure that we don't
25// e.g. race on VM operations to the same patches, or to physical pages
26// shared across different VTABLEs.
[email protected]20305ec2011-01-21 04:55:5227base::Lock patch_lock_;
[email protected]d999c7c2010-03-23 21:09:0228
29namespace internal {
30// Because other parties in our process might be attempting to patch the same
31// virtual tables at the same time, we have a race to modify the VM protections
32// on the pages. We also need to do a compare/swap type operation when we
33// modify the function, so as to be sure that we grab the most recent value.
34// Hence the SEH blocks and the nasty-looking compare/swap operation.
35bool ReplaceFunctionPointer(void** entry, void* new_proc, void* curr_proc) {
36 __try {
37 base::subtle::Atomic32 prev_value;
38
39 prev_value = base::subtle::NoBarrier_CompareAndSwap(
40 reinterpret_cast<base::subtle::Atomic32 volatile*>(entry),
41 reinterpret_cast<base::subtle::Atomic32>(curr_proc),
42 reinterpret_cast<base::subtle::Atomic32>(new_proc));
43
44 return curr_proc == reinterpret_cast<void*>(prev_value);
45 } __except(EXCEPTION_EXECUTE_HANDLER) {
46 // Oops, we took exception on access.
47 }
48
49 return false;
50}
51
52} // namespace
53
[email protected]f7817822009-09-24 05:11:5854// Convenient definition of a VTABLE
55typedef PROC* Vtable;
56
57// Returns a pointer to the VTable of a COM interface.
58// @param unknown [in] The pointer of the COM interface.
59inline Vtable GetIFVTable(void* unknown) {
60 return reinterpret_cast<Vtable>(*reinterpret_cast<void**>(unknown));
61}
62
63HRESULT PatchInterfaceMethods(void* unknown, MethodPatchInfo* patches) {
64 // Do some sanity checking of the input arguments.
65 if (NULL == unknown || NULL == patches) {
66 NOTREACHED();
67 return E_INVALIDARG;
68 }
69
70 Vtable vtable = GetIFVTable(unknown);
71 DCHECK(vtable);
72
[email protected]d999c7c2010-03-23 21:09:0273 // All VM operations, patching and manipulation of MethodPatchInfo
74 // is done under a global lock, to ensure multiple threads don't
75 // race, whether on an individual patch, or on VM operations to
76 // the same physical pages.
[email protected]20305ec2011-01-21 04:55:5277 base::AutoLock lock(patch_lock_);
[email protected]d999c7c2010-03-23 21:09:0278
[email protected]f7817822009-09-24 05:11:5879 for (MethodPatchInfo* it = patches; it->index_ != -1; ++it) {
[email protected]bc88e9d72009-09-25 16:04:4380 if (it->stub_ != NULL) {
81 // If this DCHECK fires it means that we are using the same VTable
[email protected]d999c7c2010-03-23 21:09:0282 // information to patch two different interfaces, or we've lost a
83 // race with another thread who's patching the same interface.
84 DLOG(WARNING) << "Attempting to patch two different VTables with the "
85 "same VTable information, or patching the same interface on "
86 "multiple threads";
[email protected]bc88e9d72009-09-25 16:04:4387 continue;
88 }
89
[email protected]f7817822009-09-24 05:11:5890 PROC original_fn = vtable[it->index_];
[email protected]d999c7c2010-03-23 21:09:0291 FunctionStub* stub = NULL;
92
93#ifndef NDEBUG
[email protected]5ae94d22010-07-21 19:55:3694 stub = FunctionStub::FromCode(original_fn);
[email protected]f7817822009-09-24 05:11:5895 if (stub != NULL) {
96 DLOG(ERROR) << "attempt to patch a function that's already patched";
[email protected]d999c7c2010-03-23 21:09:0297 DCHECK(stub->destination_function() ==
[email protected]f7817822009-09-24 05:11:5898 reinterpret_cast<uintptr_t>(it->method_)) <<
99 "patching the same method multiple times with different hooks?";
100 continue;
101 }
[email protected]d999c7c2010-03-23 21:09:02102#endif
[email protected]f7817822009-09-24 05:11:58103
104 stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(original_fn),
105 it->method_);
106 if (!stub) {
107 NOTREACHED();
108 return E_OUTOFMEMORY;
[email protected]d999c7c2010-03-23 21:09:02109 }
110
111 // Do the VM operations and the patching in a loop, to try and ensure
112 // we succeed even if there's a VM operation or a patch race against
113 // other 3rd parties patching.
114 bool succeeded = false;
115 for (int i = 0; !succeeded && i < kMaxRetries; ++i) {
[email protected]f7817822009-09-24 05:11:58116 DWORD protect = 0;
[email protected]d999c7c2010-03-23 21:09:02117 if (!::VirtualProtect(&vtable[it->index_], sizeof(PROC),
118 PAGE_EXECUTE_READWRITE, &protect)) {
119 HRESULT hr = AtlHresultFromLastError();
120 DLOG(ERROR) << "VirtualProtect failed 0x" << std::hex << hr;
121
122 // Go around again in the feeble hope that this is
123 // a temporary problem.
124 continue;
[email protected]f7817822009-09-24 05:11:58125 }
[email protected]d999c7c2010-03-23 21:09:02126 original_fn = vtable[it->index_];
127 stub->set_argument(reinterpret_cast<uintptr_t>(original_fn));
128 succeeded = internal::ReplaceFunctionPointer(
129 reinterpret_cast<void**>(&vtable[it->index_]), stub->code(),
130 original_fn);
131
132 if (!::VirtualProtect(&vtable[it->index_], sizeof(PROC), protect,
133 &protect)) {
134 DLOG(ERROR) << "VirtualProtect failed to restore protection";
135 }
136 }
137
138 if (!succeeded) {
139 FunctionStub::Destroy(stub);
140 stub = NULL;
141
142 DLOG(ERROR) << "Failed to patch VTable.";
143 return E_FAIL;
144 } else {
145 // Success, save the stub we created.
146 it->stub_ = stub;
[email protected]ec18ca672010-09-02 13:39:50147 PinModule();
[email protected]f7817822009-09-24 05:11:58148 }
149 }
150
151 return S_OK;
152}
153
154HRESULT UnpatchInterfaceMethods(MethodPatchInfo* patches) {
[email protected]20305ec2011-01-21 04:55:52155 base::AutoLock lock(patch_lock_);
[email protected]d999c7c2010-03-23 21:09:02156
[email protected]f7817822009-09-24 05:11:58157 for (MethodPatchInfo* it = patches; it->index_ != -1; ++it) {
158 if (it->stub_) {
[email protected]d999c7c2010-03-23 21:09:02159 DCHECK(it->stub_->destination_function() ==
[email protected]f7817822009-09-24 05:11:58160 reinterpret_cast<uintptr_t>(it->method_));
161 // Modify the stub to just jump directly to the original function.
162 it->stub_->BypassStub(reinterpret_cast<void*>(it->stub_->argument()));
163 it->stub_ = NULL;
164 // Leave the stub in memory so that we won't break any possible chains.
[email protected]d999c7c2010-03-23 21:09:02165
166 // TODO(siggi): why not restore the original VTBL pointer here, provided
167 // we haven't been chained?
[email protected]f7817822009-09-24 05:11:58168 } else {
169 DLOG(WARNING) << "attempt to unpatch a function that wasn't patched";
170 }
171 }
172
173 return S_OK;
174}
175
[email protected]3f55e872009-10-17 04:48:37176// Disabled for now as we're not using it atm.
177#if 0
178
179DynamicPatchManager::DynamicPatchManager(const MethodPatchInfo* patch_prototype)
180 : patch_prototype_(patch_prototype) {
181 DCHECK(patch_prototype_);
182 DCHECK(patch_prototype_->stub_ == NULL);
183}
184
185DynamicPatchManager::~DynamicPatchManager() {
186 UnpatchAll();
187}
188
189HRESULT DynamicPatchManager::PatchObject(void* unknown) {
190 int patched_methods = 0;
191 for (; patch_prototype_[patched_methods].index_ != -1; patched_methods++) {
192 // If you hit this, then you are likely using the prototype instance for
193 // patching in _addition_ to this class. This is not a good idea :)
194 DCHECK(patch_prototype_[patched_methods].stub_ == NULL);
195 }
196
197 // Prepare a new patch object using the patch info from the prototype.
198 int mem_size = sizeof(PatchedObject) +
199 sizeof(MethodPatchInfo) * patched_methods;
200 PatchedObject* entry = reinterpret_cast<PatchedObject*>(new char[mem_size]);
201 entry->vtable_ = GetIFVTable(unknown);
202 memcpy(entry->patch_info_, patch_prototype_,
203 sizeof(MethodPatchInfo) * (patched_methods + 1));
204
205 patch_list_lock_.Acquire();
206
207 // See if we've already patched this vtable before.
208 // The search is done via the == operator of the PatchedObject class.
209 PatchList::const_iterator it = std::find(patch_list_.begin(),
210 patch_list_.end(), entry);
211 HRESULT hr;
212 if (it == patch_list_.end()) {
213 hr = PatchInterfaceMethods(unknown, entry->patch_info_);
214 if (SUCCEEDED(hr)) {
215 patch_list_.push_back(entry);
216 entry = NULL; // Ownership transferred to the array.
217 }
218 } else {
219 hr = S_FALSE;
220 }
221
222 patch_list_lock_.Release();
223
224 delete entry;
225
226 return hr;
227}
228
229bool DynamicPatchManager::UnpatchAll() {
230 patch_list_lock_.Acquire();
231 PatchList::iterator it;
232 for (it = patch_list_.begin(); it != patch_list_.end(); it++) {
233 UnpatchInterfaceMethods((*it)->patch_info_);
234 delete (*it);
235 }
236 patch_list_.clear();
237 patch_list_lock_.Release();
238
239 return true;
240}
241
242#endif // disabled DynamicPatchManager
243
[email protected]f7817822009-09-24 05:11:58244} // namespace vtable_patch