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.
5 #include "chrome_frame/vtable_patch_manager.h"
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/utils.h"
18 namespace vtable_patch
{
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;
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_
;
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
) {
37 base::subtle::Atomic32 prev_value
;
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
));
44 return curr_proc
== reinterpret_cast<void*>(prev_value
);
45 } __except(EXCEPTION_EXECUTE_HANDLER
) {
46 // Oops, we took exception on access.
54 // Convenient definition of a VTABLE
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
));
63 HRESULT
PatchInterfaceMethods(void* unknown
, MethodPatchInfo
* patches
) {
64 // Do some sanity checking of the input arguments.
65 if (NULL
== unknown
|| NULL
== patches
) {
70 Vtable vtable
= GetIFVTable(unknown
);
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_
);
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 "
90 PROC original_fn
= vtable
[it
->index_
];
91 FunctionStub
* stub
= NULL
;
94 stub
= FunctionStub::FromCode(original_fn
);
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?";
104 stub
= FunctionStub::Create(reinterpret_cast<uintptr_t>(original_fn
),
108 return E_OUTOFMEMORY
;
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
) {
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
;
122 // Go around again in the feeble hope that this is
123 // a temporary problem.
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(),
132 if (!::VirtualProtect(&vtable
[it
->index_
], sizeof(PROC
), protect
,
134 DLOG(ERROR
) << "VirtualProtect failed to restore protection";
139 FunctionStub::Destroy(stub
);
142 DLOG(ERROR
) << "Failed to patch VTable.";
145 // Success, save the stub we created.
154 HRESULT
UnpatchInterfaceMethods(MethodPatchInfo
* patches
) {
155 base::AutoLock
lock(patch_lock_
);
157 for (MethodPatchInfo
* it
= patches
; it
->index_
!= -1; ++it
) {
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()));
164 // Leave the stub in memory so that we won't break any possible chains.
166 // TODO(siggi): why not restore the original VTBL pointer here, provided
167 // we haven't been chained?
169 DLOG(WARNING
) << "attempt to unpatch a function that wasn't patched";
176 // Disabled for now as we're not using it atm.
179 DynamicPatchManager::DynamicPatchManager(const MethodPatchInfo
* patch_prototype
)
180 : patch_prototype_(patch_prototype
) {
181 DCHECK(patch_prototype_
);
182 DCHECK(patch_prototype_
->stub_
== NULL
);
185 DynamicPatchManager::~DynamicPatchManager() {
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
);
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));
205 patch_list_lock_
.Acquire();
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
);
212 if (it
== patch_list_
.end()) {
213 hr
= PatchInterfaceMethods(unknown
, entry
->patch_info_
);
215 patch_list_
.push_back(entry
);
216 entry
= NULL
; // Ownership transferred to the array.
222 patch_list_lock_
.Release();
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_
);
237 patch_list_lock_
.Release();
242 #endif // disabled DynamicPatchManager
244 } // namespace vtable_patch