OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
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 | |
7 #include <atlcomcli.h> | |
8 | |
9 #include <algorithm> | |
10 | |
11 #include "base/atomicops.h" | |
12 #include "base/logging.h" | |
13 #include "base/memory/scoped_ptr.h" | |
14 #include "base/synchronization/lock.h" | |
15 #include "chrome_frame/function_stub.h" | |
16 #include "chrome_frame/pin_module.h" | |
17 | |
18 namespace vtable_patch { | |
19 | |
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. | |
27 base::Lock patch_lock_; | |
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 | |
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 | |
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. | |
77 base::AutoLock lock(patch_lock_); | |
78 | |
79 for (MethodPatchInfo* it = patches; it->index_ != -1; ++it) { | |
80 if (it->stub_ != NULL) { | |
81 // If this DCHECK fires it means that we are using the same VTable | |
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"; | |
87 continue; | |
88 } | |
89 | |
90 PROC original_fn = vtable[it->index_]; | |
91 FunctionStub* stub = NULL; | |
92 | |
93 #ifndef NDEBUG | |
94 stub = FunctionStub::FromCode(original_fn); | |
95 if (stub != NULL) { | |
96 DLOG(ERROR) << "attempt to patch a function that's already patched"; | |
97 DCHECK(stub->destination_function() == | |
98 reinterpret_cast<uintptr_t>(it->method_)) << | |
99 "patching the same method multiple times with different hooks?"; | |
100 continue; | |
101 } | |
102 #endif | |
103 | |
104 stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(original_fn), | |
105 it->method_); | |
106 if (!stub) { | |
107 NOTREACHED(); | |
108 return E_OUTOFMEMORY; | |
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) { | |
116 DWORD protect = 0; | |
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; | |
125 } | |
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; | |
147 chrome_frame::PinModule(); | |
148 } | |
149 } | |
150 | |
151 return S_OK; | |
152 } | |
153 | |
154 HRESULT UnpatchInterfaceMethods(MethodPatchInfo* patches) { | |
155 base::AutoLock lock(patch_lock_); | |
156 | |
157 for (MethodPatchInfo* it = patches; it->index_ != -1; ++it) { | |
158 if (it->stub_) { | |
159 DCHECK(it->stub_->destination_function() == | |
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. | |
165 | |
166 // TODO(siggi): why not restore the original VTBL pointer here, provided | |
167 // we haven't been chained? | |
168 } else { | |
169 DLOG(WARNING) << "attempt to unpatch a function that wasn't patched"; | |
170 } | |
171 } | |
172 | |
173 return S_OK; | |
174 } | |
175 | |
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 | |
244 } // namespace vtable_patch | |
OLD | NEW |