[email protected] | 3b63f8f4 | 2011-03-28 01:54:15 | [diff] [blame] | 1 | // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
[email protected] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 2 | // 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] | 53b19ac1 | 2010-07-21 16:35:21 | [diff] [blame] | 7 | #include <atlcomcli.h> |
[email protected] | 0535809 | 2010-07-21 16:07:24 | [diff] [blame] | 8 | |
[email protected] | 5ae94d2 | 2010-07-21 19:55:36 | [diff] [blame] | 9 | #include <algorithm> |
| 10 | |
[email protected] | d999c7c | 2010-03-23 21:09:02 | [diff] [blame] | 11 | #include "base/atomicops.h" |
[email protected] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 12 | #include "base/logging.h" |
[email protected] | 3b63f8f4 | 2011-03-28 01:54:15 | [diff] [blame] | 13 | #include "base/memory/scoped_ptr.h" |
[email protected] | 20305ec | 2011-01-21 04:55:52 | [diff] [blame] | 14 | #include "base/synchronization/lock.h" |
[email protected] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 15 | #include "chrome_frame/function_stub.h" |
[email protected] | ec18ca67 | 2010-09-02 13:39:50 | [diff] [blame] | 16 | #include "chrome_frame/utils.h" |
[email protected] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 17 | |
| 18 | namespace vtable_patch { |
| 19 | |
[email protected] | d999c7c | 2010-03-23 21:09:02 | [diff] [blame] | 20 | // 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. |
| 22 | const 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] | 20305ec | 2011-01-21 04:55:52 | [diff] [blame] | 27 | base::Lock patch_lock_; |
[email protected] | d999c7c | 2010-03-23 21:09:02 | [diff] [blame] | 28 | |
| 29 | namespace 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. |
| 35 | bool 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] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 54 | // Convenient definition of a VTABLE |
| 55 | typedef PROC* Vtable; |
| 56 | |
| 57 | // Returns a pointer to the VTable of a COM interface. |
| 58 | // @param unknown [in] The pointer of the COM interface. |
| 59 | inline Vtable GetIFVTable(void* unknown) { |
| 60 | return reinterpret_cast<Vtable>(*reinterpret_cast<void**>(unknown)); |
| 61 | } |
| 62 | |
| 63 | HRESULT 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] | d999c7c | 2010-03-23 21:09:02 | [diff] [blame] | 73 | // 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] | 20305ec | 2011-01-21 04:55:52 | [diff] [blame] | 77 | base::AutoLock lock(patch_lock_); |
[email protected] | d999c7c | 2010-03-23 21:09:02 | [diff] [blame] | 78 | |
[email protected] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 79 | for (MethodPatchInfo* it = patches; it->index_ != -1; ++it) { |
[email protected] | bc88e9d7 | 2009-09-25 16:04:43 | [diff] [blame] | 80 | if (it->stub_ != NULL) { |
| 81 | // If this DCHECK fires it means that we are using the same VTable |
[email protected] | d999c7c | 2010-03-23 21:09:02 | [diff] [blame] | 82 | // 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] | bc88e9d7 | 2009-09-25 16:04:43 | [diff] [blame] | 87 | continue; |
| 88 | } |
| 89 | |
[email protected] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 90 | PROC original_fn = vtable[it->index_]; |
[email protected] | d999c7c | 2010-03-23 21:09:02 | [diff] [blame] | 91 | FunctionStub* stub = NULL; |
| 92 | |
| 93 | #ifndef NDEBUG |
[email protected] | 5ae94d2 | 2010-07-21 19:55:36 | [diff] [blame] | 94 | stub = FunctionStub::FromCode(original_fn); |
[email protected] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 95 | if (stub != NULL) { |
| 96 | DLOG(ERROR) << "attempt to patch a function that's already patched"; |
[email protected] | d999c7c | 2010-03-23 21:09:02 | [diff] [blame] | 97 | DCHECK(stub->destination_function() == |
[email protected] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 98 | reinterpret_cast<uintptr_t>(it->method_)) << |
| 99 | "patching the same method multiple times with different hooks?"; |
| 100 | continue; |
| 101 | } |
[email protected] | d999c7c | 2010-03-23 21:09:02 | [diff] [blame] | 102 | #endif |
[email protected] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 103 | |
| 104 | stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(original_fn), |
| 105 | it->method_); |
| 106 | if (!stub) { |
| 107 | NOTREACHED(); |
| 108 | return E_OUTOFMEMORY; |
[email protected] | d999c7c | 2010-03-23 21:09:02 | [diff] [blame] | 109 | } |
| 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] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 116 | DWORD protect = 0; |
[email protected] | d999c7c | 2010-03-23 21:09:02 | [diff] [blame] | 117 | 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] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 125 | } |
[email protected] | d999c7c | 2010-03-23 21:09:02 | [diff] [blame] | 126 | 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] | ec18ca67 | 2010-09-02 13:39:50 | [diff] [blame] | 147 | PinModule(); |
[email protected] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 148 | } |
| 149 | } |
| 150 | |
| 151 | return S_OK; |
| 152 | } |
| 153 | |
| 154 | HRESULT UnpatchInterfaceMethods(MethodPatchInfo* patches) { |
[email protected] | 20305ec | 2011-01-21 04:55:52 | [diff] [blame] | 155 | base::AutoLock lock(patch_lock_); |
[email protected] | d999c7c | 2010-03-23 21:09:02 | [diff] [blame] | 156 | |
[email protected] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 157 | for (MethodPatchInfo* it = patches; it->index_ != -1; ++it) { |
| 158 | if (it->stub_) { |
[email protected] | d999c7c | 2010-03-23 21:09:02 | [diff] [blame] | 159 | DCHECK(it->stub_->destination_function() == |
[email protected] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 160 | 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] | d999c7c | 2010-03-23 21:09:02 | [diff] [blame] | 165 | |
| 166 | // TODO(siggi): why not restore the original VTBL pointer here, provided |
| 167 | // we haven't been chained? |
[email protected] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 168 | } else { |
| 169 | DLOG(WARNING) << "attempt to unpatch a function that wasn't patched"; |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | return S_OK; |
| 174 | } |
| 175 | |
[email protected] | 3f55e87 | 2009-10-17 04:48:37 | [diff] [blame] | 176 | // Disabled for now as we're not using it atm. |
| 177 | #if 0 |
| 178 | |
| 179 | DynamicPatchManager::DynamicPatchManager(const MethodPatchInfo* patch_prototype) |
| 180 | : patch_prototype_(patch_prototype) { |
| 181 | DCHECK(patch_prototype_); |
| 182 | DCHECK(patch_prototype_->stub_ == NULL); |
| 183 | } |
| 184 | |
| 185 | DynamicPatchManager::~DynamicPatchManager() { |
| 186 | UnpatchAll(); |
| 187 | } |
| 188 | |
| 189 | HRESULT 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 | |
| 229 | bool 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] | f781782 | 2009-09-24 05:11:58 | [diff] [blame] | 244 | } // namespace vtable_patch |