diff options
author | siggi@chromium.org <siggi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-23 21:09:02 +0000 |
---|---|---|
committer | siggi@chromium.org <siggi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-23 21:09:02 +0000 |
commit | d999c7c3f4bd9c7c779b4c06ee148d3d0d227e60 (patch) | |
tree | d4fca3d3c24d56a325000a4129d11c35b548abfa /chrome_frame/vtable_patch_manager.cc | |
parent | 722ed48734460567be2c97590a036480c4667815 (diff) | |
download | chromium_src-d999c7c3f4bd9c7c779b4c06ee148d3d0d227e60.zip chromium_src-d999c7c3f4bd9c7c779b4c06ee148d3d0d227e60.tar.gz chromium_src-d999c7c3f4bd9c7c779b4c06ee148d3d0d227e60.tar.bz2 |
Reimplementation of FunctionStub to avoid rewriting potentially executing code for a slight improvement in thread safety.
Make VTABLE patching treadsafe to the extent possible. As-is it's now safe against itself running on other threads at lease, as well as against other similar implementations, though the inherent VM operation race is resolved by retrying.
BUG=27415
TEST=Included unittests.
Review URL: http://codereview.chromium.org/992008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@42381 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome_frame/vtable_patch_manager.cc')
-rw-r--r-- | chrome_frame/vtable_patch_manager.cc | 112 |
1 files changed, 96 insertions, 16 deletions
diff --git a/chrome_frame/vtable_patch_manager.cc b/chrome_frame/vtable_patch_manager.cc index c070211..dc88079 100644 --- a/chrome_frame/vtable_patch_manager.cc +++ b/chrome_frame/vtable_patch_manager.cc @@ -5,7 +5,10 @@ #include "chrome_frame/vtable_patch_manager.h" #include <algorithm> +#include <atlcomcli.h> +#include "base/atomicops.h" +#include "base/lock.h" #include "base/logging.h" #include "base/scoped_ptr.h" @@ -13,6 +16,40 @@ namespace vtable_patch { +// The number of times we retry a patch/unpatch operation in case of +// VM races with other 3rd party software trying to patch the same thing. +const int kMaxRetries = 3; + +// We hold a lock over all patching operations to make sure that we don't +// e.g. race on VM operations to the same patches, or to physical pages +// shared across different VTABLEs. +Lock patch_lock_; + +namespace internal { +// Because other parties in our process might be attempting to patch the same +// virtual tables at the same time, we have a race to modify the VM protections +// on the pages. We also need to do a compare/swap type operation when we +// modify the function, so as to be sure that we grab the most recent value. +// Hence the SEH blocks and the nasty-looking compare/swap operation. +bool ReplaceFunctionPointer(void** entry, void* new_proc, void* curr_proc) { + __try { + base::subtle::Atomic32 prev_value; + + prev_value = base::subtle::NoBarrier_CompareAndSwap( + reinterpret_cast<base::subtle::Atomic32 volatile*>(entry), + reinterpret_cast<base::subtle::Atomic32>(curr_proc), + reinterpret_cast<base::subtle::Atomic32>(new_proc)); + + return curr_proc == reinterpret_cast<void*>(prev_value); + } __except(EXCEPTION_EXECUTE_HANDLER) { + // Oops, we took exception on access. + } + + return false; +} + +} // namespace + // Convenient definition of a VTABLE typedef PROC* Vtable; @@ -32,57 +69,100 @@ HRESULT PatchInterfaceMethods(void* unknown, MethodPatchInfo* patches) { Vtable vtable = GetIFVTable(unknown); DCHECK(vtable); + // All VM operations, patching and manipulation of MethodPatchInfo + // is done under a global lock, to ensure multiple threads don't + // race, whether on an individual patch, or on VM operations to + // the same physical pages. + AutoLock lock(patch_lock_); + for (MethodPatchInfo* it = patches; it->index_ != -1; ++it) { if (it->stub_ != NULL) { // If this DCHECK fires it means that we are using the same VTable - // information to patch two different interfaces. - DCHECK(false); - DLOG(ERROR) << "Attempting to patch two different VTables with the " - << "same VTable information"; + // information to patch two different interfaces, or we've lost a + // race with another thread who's patching the same interface. + DLOG(WARNING) << "Attempting to patch two different VTables with the " + "same VTable information, or patching the same interface on " + "multiple threads"; continue; } PROC original_fn = vtable[it->index_]; - FunctionStub* stub = FunctionStub::FromCode(original_fn); + FunctionStub* stub = NULL; + +#ifndef NDEBUG + FunctionStub::FromCode(original_fn); if (stub != NULL) { DLOG(ERROR) << "attempt to patch a function that's already patched"; - DCHECK(stub->absolute_target() == + DCHECK(stub->destination_function() == reinterpret_cast<uintptr_t>(it->method_)) << "patching the same method multiple times with different hooks?"; continue; } +#endif stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(original_fn), it->method_); if (!stub) { NOTREACHED(); return E_OUTOFMEMORY; - } else { + } + + // Do the VM operations and the patching in a loop, to try and ensure + // we succeed even if there's a VM operation or a patch race against + // other 3rd parties patching. + bool succeeded = false; + for (int i = 0; !succeeded && i < kMaxRetries; ++i) { DWORD protect = 0; - if (::VirtualProtect(&vtable[it->index_], sizeof(PROC), - PAGE_EXECUTE_READWRITE, &protect)) { - it->stub_ = stub; // save the stub - vtable[it->index_] = stub->code(); - ::VirtualProtect(&vtable[it->index_], sizeof(PROC), protect, - &protect); - } else { - NOTREACHED(); + if (!::VirtualProtect(&vtable[it->index_], sizeof(PROC), + PAGE_EXECUTE_READWRITE, &protect)) { + HRESULT hr = AtlHresultFromLastError(); + DLOG(ERROR) << "VirtualProtect failed 0x" << std::hex << hr; + + // Go around again in the feeble hope that this is + // a temporary problem. + continue; + } + original_fn = vtable[it->index_]; + stub->set_argument(reinterpret_cast<uintptr_t>(original_fn)); + succeeded = internal::ReplaceFunctionPointer( + reinterpret_cast<void**>(&vtable[it->index_]), stub->code(), + original_fn); + + if (!::VirtualProtect(&vtable[it->index_], sizeof(PROC), protect, + &protect)) { + DLOG(ERROR) << "VirtualProtect failed to restore protection"; } } + + if (!succeeded) { + FunctionStub::Destroy(stub); + stub = NULL; + + DLOG(ERROR) << "Failed to patch VTable."; + return E_FAIL; + } else { + // Success, save the stub we created. + it->stub_ = stub; + } } return S_OK; } HRESULT UnpatchInterfaceMethods(MethodPatchInfo* patches) { + AutoLock lock(patch_lock_); + for (MethodPatchInfo* it = patches; it->index_ != -1; ++it) { if (it->stub_) { - DCHECK(it->stub_->absolute_target() == + DCHECK(it->stub_->destination_function() == reinterpret_cast<uintptr_t>(it->method_)); // Modify the stub to just jump directly to the original function. it->stub_->BypassStub(reinterpret_cast<void*>(it->stub_->argument())); it->stub_ = NULL; // Leave the stub in memory so that we won't break any possible chains. + + // TODO(siggi): why not restore the original VTBL pointer here, provided + // we haven't been chained? } else { DLOG(WARNING) << "attempt to unpatch a function that wasn't patched"; } |