Files
renderdoc/renderdoc/hooks/hooks.h
T
baldurk f9d03761b3 Avoid libraries loading and unloading for GL hooks on windows
* This can cause the hooks to get out of sync if the libraries move between
  loads, so should be avoided. Most applications won't do this anyway.
2025-04-16 12:08:49 +01:00

221 lines
10 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2025 Baldur Karlsson
* Copyright (c) 2014 Crytek
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#pragma once
#include "os/os_specific.h"
typedef std::function<void(void *, const char *)> FunctionLoadCallback;
struct FunctionHook
{
FunctionHook() : orig(NULL), hook(NULL) {}
FunctionHook(const char *f, void **o, void *d) : function(f), orig(o), hook(d) {}
bool operator<(const FunctionHook &h) const { return function < h.function; }
rdcstr function;
void **orig;
void *hook;
};
// == Hooking workflow overview ==
//
// Each subsystem that wants to hook libraries creates a LibraryHook instance. That registers with
// LibraryHooks via the singleton in global constructors, but does nothing initially.
//
// Early in init, during RenderDoc's initialisation, the last thing that happens is a call to
// LibraryHooks::RegisterHooks(). This iterates through the LibraryHook instances and calls
// RegisterHooks on each of them.
//
// Each subsystem should call LibraryHooks::RegisterLibraryHook() for the filename of each library
// it wants to hook, then LibraryHooks::RegisterFunctionHook() for each function it wants to hook
// within that library. Note that not all platforms will use all the information provided, but only
// in a way that's invisible to the user. These are lightweight calls to register the hooks, and
// don't do any work yet.
//
// A registered library hook can get an optional callback when that library is first loaded.
//
// Similarly a registered function hook can provide a function pointer location which will be filled
// out with the function pointer to call onwards. This may just be the real implementation, or a
// trampoline, depending on the platform hooking method.
//
// Once all of this is completed, these hooks will be applied and activated as necessary. If any
// libraries are already loaded, library callbacks will fire here. Similarly function hook original
// pointers will be filled out if they are already available. Library callbacks will fire precisely
// once, the first time the library is loaded.
//
// NOTE: An important result of the behaviour above is that original function pointers are not
// necessarily available until the function is actually hooked. The hooking will automatically
// propagate all functions hooked in a library once it's loaded, and since some platforms may
// trampoline-hook target functions it is *not* safe to just get the target function's pointer as
// this may then call back into hooks. The only exception to this is with a library-specific
// function for fetching pointers such as GetProcAddress on OpenGL/Vulkan. However in this case care
// must be taken to suppress any effect of function interception by calling the relevant suppression
// functions. This ensures that if the API-specific GetProcAddress calls into the platform function
// fetch, it doesn't form an infinite loop.
//
// Also in the case that a function may come from many libraries, or it has multiple aliased names,
// the order of registration is important. The hooking implementations will always follow the order
// provided as a priority list, so if you register libFoo before libBar, any functions in libFoo
// have precedence. Similarly if you register foofunc() before barfunc() but pointed to the same
// pointer location for storing the original function pointer, barfunc()'s pointer will not
// overwrite foofunc() if it exists.
//
// == Hooking details (platform-specific) ==
//
// The method of hooking varies by platform but follows the same general pattern. During
// registration we build up lists of which libraries and functions are to be hooked. Once the
// registration is complete we apply all of the hooks.
//
// NOTE: The library name for function hooks is only used on windows, since on linux/android
// namespace resolution is a bit fuzzier. At the time of writing there are no cases where two
// libraries have the same function that aren't functionally equivalent.
//
// - Windows -
// On windows this involves iterating over all loaded libraries and hooking their IAT tables. We
// also hook internally any functions for dynamically loading libraries or fetching functions, so
// that we can continue to re-wrap any newly loaded libraries.
//
// Loaded libraries at startup (most likely because the exe linked against them) have their
// callbacks fired immediately. Otherwise any time a new library is found on a subsequent iteration
// from a LoadLibrary-type call we fire the callback.
//
// Whenever a library is newly found, we hook all the entry points and update any original function
// pointers that are set to NULL.
//
// - Linux -
// On linux we rely on exporting all hooked symbols, and using LD_PRELOAD to load our library first
// in resolution order. All we do is hook dlopen so that when a new library is loaded we can
// redirect the returned library handle to ourselves, as well as process any pending function hooks.
//
// - Android -
// On android the implementation varies depending on whether we're using interceptor-lib or not.
// This is optional at build time but produces more reliable results:
//
// Without interceptor-lib:
// The implementation is similar to windows. Since we don't have LD_PRELOAD we need to patch import
// tables of any loaded libraries. We also cannot reliably hook dlopen on android to redirect the
// library handle to ourselves, so instead we hook dlsym and check to see if it corresponds to a
// function we're intercepting. This is the need for the 'suppress hooking' function, since if
// eglGetProcAddress calls into dlsym we don't want to intercept that dlsym and return our own
// function.
//
// With interceptor-lib:
// Instead of patching imports we overwrite the assembly at each entry point in the *target*
// library, and patch it to call into our hooks. Then we create trampolines to restore the original
// function for onwards-calling.
//
// To ensure sanity, we always load all registered libraries in the first hook applying phase. This
// ensures that we don't end up in a weird situation where one library is loaded, not all functions
// are hooked, the user code tries to populate any missing functions and then later another library
// is loaded and we patch it after having requested function pointers. Ensuring all libraries that
// we *might* patch are loaded ASAP, everything is consistent since after that any functions that
// don't have trampolines provided will never be trampolined in the future.
//
// Sometimes interceptor-lib can fail, at which point we fall back to the path above for those
// functions and try to follow and patch any imports to them.
struct LibraryHook;
// this singleton allows you to compile in code that defines a hook for a given library
// (and it will be registered). Then when the renderdoc library is initialised in the target
// program RegisterHooks() will be called to set up the hooks.
class LibraryHooks
{
public:
// generic, implemented in hooks.cpp to iterate over all registered libraries
static void RegisterHooks();
static void OptionsUpdated();
static void RemoveHookCallbacks();
// platform specific implementations
// some platforms may unavoidably hook on replay, this gives them a chance to do any
// initialisation needed to ensure those hooks don't do anything
static void ReplayInitialise();
// Removes hooks (where possible) and restores everything to an un-hooked state
static void RemoveHooks();
// refreshes hooks, useful on android where hooking can be unreliable
static void Refresh();
// Ignore this library - i.e. do not hook any calls it makes. Useful in the case where a library
// might call in to hooked APIs but we want to treat it as a black box.
static void IgnoreLibrary(const char *libraryName);
// register a library for hooking, providing an optional callback to be called the first time the
// library has been loaded and all functions in it hooked.
static void RegisterLibraryHook(const char *libraryName, FunctionLoadCallback loadedCallback);
// registers a function to be hooked, and an optional location of where to store the original
// onward function pointer
static void RegisterFunctionHook(const char *libraryName, const FunctionHook &hook);
// detect if an identifier is present in the current process - used as a marker to indicate
// replay-type programs.
static bool Detect(const char *identifier);
private:
static void BeginHookRegistration();
static void EndHookRegistration();
};
// defines the interface that a library hooking class will implement.
struct LibraryHook
{
LibraryHook();
virtual void RegisterHooks() = 0;
virtual void OptionsUpdated() {}
virtual void RemoveHooks() {}
private:
friend class LibraryHooks;
static rdcarray<LibraryHook *> m_Libraries;
};
template <typename FuncType>
class HookedFunction
{
public:
HookedFunction() { orig_funcptr = NULL; }
~HookedFunction() {}
FuncType operator()() { return (FuncType)orig_funcptr; }
void SetFuncPtr(void *ptr) { orig_funcptr = ptr; }
void Register(const char *module_name, const char *function, void *destination_function_ptr)
{
LibraryHooks::RegisterFunctionHook(
module_name, FunctionHook(function, &orig_funcptr, destination_function_ptr));
}
private:
void *orig_funcptr;
};
struct ScopedSuppressHooking
{
ScopedSuppressHooking();
~ScopedSuppressHooking();
};