diff options
author | slightlyoff@chromium.org <slightlyoff@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-24 05:11:58 +0000 |
---|---|---|
committer | slightlyoff@chromium.org <slightlyoff@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-24 05:11:58 +0000 |
commit | f781782dd67077478e117c61dca4ea5eefce3544 (patch) | |
tree | 4801f724123cfdcbb69c4e7fe40a565b331723ae /chrome_frame/function_stub.h | |
parent | 63cf4759efa2373e33436fb5df6849f930081226 (diff) | |
download | chromium_src-f781782dd67077478e117c61dca4ea5eefce3544.zip chromium_src-f781782dd67077478e117c61dca4ea5eefce3544.tar.gz chromium_src-f781782dd67077478e117c61dca4ea5eefce3544.tar.bz2 |
Initial import of the Chrome Frame codebase. Integration in chrome.gyp coming in a separate CL.
BUG=None
TEST=None
Review URL: http://codereview.chromium.org/218019
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@27042 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome_frame/function_stub.h')
-rw-r--r-- | chrome_frame/function_stub.h | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/chrome_frame/function_stub.h b/chrome_frame/function_stub.h new file mode 100644 index 0000000..55f0c5e --- /dev/null +++ b/chrome_frame/function_stub.h @@ -0,0 +1,241 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +#ifndef CHROME_FRAME_FUNCTION_STUB_H_ +#define CHROME_FRAME_FUNCTION_STUB_H_ + +#include <windows.h> +#include "base/logging.h" + +// IMPORTANT: The struct below must be byte aligned. +#pragma pack(push) +#pragma pack(1) + +#ifndef _M_IX86 +#error Only x86 supported right now. +#endif + +extern "C" IMAGE_DOS_HEADER __ImageBase; + +// This struct is assembly code + signature. The purpose of the struct is to be +// able to hook an existing function with our own and store information such +// as the original function pointer with the code stub. Typically this is used +// for patching entries of a vtable or e.g. a globally registered wndproc +// for a class as opposed to a window. +// When unhooking, you can just call the BypassStub() function and leave the +// stub in memory. This unhooks your function while leaving the (potential) +// chain of patches intact. +// +// @note: This class is meant for __stdcall calling convention and +// it uses eax as a temporary variable. The struct can +// be improved in the future to save eax before the +// operation and then restore it. +// +// For instance if the function prototype is: +// +// @code +// LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); +// @endcode +// +// and we would like to add one static argument to make it, say: +// +// @code +// LRESULT MyNewWndProc(WNDPROC original, HWND hwnd, UINT msg, +// WPARAM wparam, LPARAM lparam); +// @endcode +// +// That can be achieved by wrapping the function up with a FunctionStub: +// +// @code +// FunctionStub* stub = FunctionStub::Create(original_wndproc, MyNewWndProc); +// SetClassLongPtr(wnd, GCLP_WNDPROC, stub->code()); +// @endcode +struct FunctionStub { + private: + typedef enum AsmConstants { + POP_EAX = 0x58, + PUSH = 0x68, + PUSH_EAX = 0x50, + JUMP_RELATIVE = 0xE9 + }; + + FunctionStub(uintptr_t extra_argument, void* dest) + : signature_(reinterpret_cast<HMODULE>(&__ImageBase)) { + Opcodes::Hook& hook = code_.hook_; + hook.pop_return_addr_ = POP_EAX; + hook.push_ = PUSH; + hook.arg_ = extra_argument; + hook.push_return_addr_ = PUSH_EAX; + hook.jump_to_ = JUMP_RELATIVE; + + // Calculate the target jump to the destination function. + hook.target_ = CalculateRelativeJump(dest, &hook.jump_to_); + + // Allow the process to execute this struct as code. + DWORD old_protect = 0; + // Allow reads too since we want read-only member variable access at + // all times. + ::VirtualProtect(this, sizeof(FunctionStub), PAGE_EXECUTE_READ, + &old_protect); + } + + ~FunctionStub() { + // No more execution rights. + DWORD old_protect = 0; + ::VirtualProtect(this, sizeof(FunctionStub), PAGE_READWRITE, &old_protect); + } + + // Calculates the target value for a relative jump. + // The function assumes that the size of the opcode is 1 byte. + inline uintptr_t CalculateRelativeJump(const void* absolute_to, + const void* absolute_from) const { + return (reinterpret_cast<uintptr_t>(absolute_to) - + reinterpret_cast<uintptr_t>(absolute_from)) - + (sizeof(uint8) + sizeof(uintptr_t)); + } + + // Does the opposite of what CalculateRelativeJump does, which is to + // calculate back the absolute address that previously was relative to + // some other address. + inline uintptr_t CalculateAbsoluteAddress(const void* relative_to, + uintptr_t relative_address) const { + return relative_address + sizeof(uint8) + sizeof(uintptr_t) + + reinterpret_cast<uintptr_t>(relative_to); + } + + // Used to identify function stubs that belong to this module. + HMODULE signature_; + + // IMPORTANT: Do not change the order of member variables + union Opcodes { + // Use this struct when the stub forwards the call to our hook function + // providing an extra argument. + struct Hook { + uint8 pop_return_addr_; // pop eax + uint8 push_; // push arg ; push... + uintptr_t arg_; // ; extra argument + uint8 push_return_addr_; // push eax ; push the return address + uint8 jump_to_; // jmp ; jump... + uintptr_t target_; // ; to the hook function + } hook_; + // When the stub is bypassed, we jump directly to a given target, + // usually the originally hooked function. + struct Bypassed { + uint8 jump_to_; // jmp to + uintptr_t target_; // relative target. + } bypassed_; + }; + + Opcodes code_; + + public: + // Neutralizes this stub and converts it to a direct jump to a new target. + void BypassStub(void* new_target) { + DWORD old_protect = 0; + // Temporarily allow us to write to member variables + ::VirtualProtect(this, sizeof(FunctionStub), PAGE_EXECUTE_READWRITE, + &old_protect); + + // Now, just change the first 5 bytes to jump directly to the new target. + Opcodes::Bypassed& bypassed = code_.bypassed_; + bypassed.jump_to_ = JUMP_RELATIVE; + bypassed.target_ = CalculateRelativeJump(new_target, &bypassed.jump_to_); + + // Restore the previous protection flags. + ::VirtualProtect(this, sizeof(FunctionStub), old_protect, &old_protect); + + // Flush the instruction cache just in case. + ::FlushInstructionCache(::GetCurrentProcess(), this, sizeof(FunctionStub)); + } + + // @returns the argument supplied in the call to @ref Create + inline uintptr_t argument() const { + DCHECK(code_.hook_.pop_return_addr_ == POP_EAX) << "stub has been disabled"; + return code_.hook_.arg_; + } + + inline bool is_bypassed() const { + return code_.bypassed_.jump_to_ == JUMP_RELATIVE; + } + + inline uintptr_t absolute_target() const { + DCHECK(code_.hook_.pop_return_addr_ == POP_EAX) << "stub has been disabled"; + return CalculateAbsoluteAddress( + reinterpret_cast<const void*>(&code_.hook_.jump_to_), + code_.hook_.target_); + } + + // Returns true if the stub is valid and enabled. + // Don't call this method after bypassing the stub. + inline bool is_valid() const { + return signature_ == reinterpret_cast<HMODULE>(&__ImageBase) && + code_.hook_.pop_return_addr_ == POP_EAX; + } + + inline PROC code() const { + return reinterpret_cast<PROC>(const_cast<Opcodes*>(&code_)); + } + + // Use to create a new function stub as shown above. + // + // @param extra_argument The static argument to pass to the function. + // @param dest Target function to which the stub applies. + // @returns NULL if an error occurs, otherwise a pointer to the + // function stub. + // + // TODO(tommi): Change this so that VirtualAlloc isn't called for + // every stub. Instead we should re-use each allocation for + // multiple stubs. In practice I'm guessing that there would + // only be one allocation per process, since each allocation actually + // allocates at least one page of memory (4K). Size of FunctionStub + // is 12 bytes, so one page could house 341 function stubs. + // When stubs are created frequently, VirtualAlloc could fail + // and last error is ERROR_NOT_ENOUGH_MEMORY (8). + static FunctionStub* Create(uintptr_t extra_argument, void* dest) { + DCHECK(dest); + + // Use VirtualAlloc to get memory for the stub. This gives us a + // whole page that we don't share with anyone else. + // Initially the memory must be READWRITE to allow for construction + // PAGE_EXECUTE is set in constructor. + FunctionStub* ret = reinterpret_cast<FunctionStub*>(VirtualAlloc(NULL, + sizeof(FunctionStub), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + + if (!ret) { + NOTREACHED(); + } else { + // Construct + ret->FunctionStub::FunctionStub(extra_argument, dest); + } + + return ret; + } + + static FunctionStub* FromCode(void* address) { + Opcodes* code = reinterpret_cast<Opcodes*>(address); + if (code->hook_.pop_return_addr_ == POP_EAX) { + FunctionStub* stub = reinterpret_cast<FunctionStub*>( + reinterpret_cast<uint8*>(address) - sizeof(HMODULE)); + if (stub->is_valid()) + return stub; + } + + return NULL; + } + + // Deallocates a FunctionStub. The stub must not be in use on any thread! + static bool Destroy(FunctionStub* stub) { + DCHECK(stub != NULL); + FunctionStub* to_free = reinterpret_cast<FunctionStub*>(stub); + to_free->FunctionStub::~FunctionStub(); + BOOL success = VirtualFree(to_free, sizeof(FunctionStub), MEM_DECOMMIT); + DCHECK(success) << "VirtualFree"; + return success != FALSE; + } +}; + +#pragma pack(pop) + +#endif // CHROME_FRAME_FUNCTION_STUB_H_ |