mirror of
				https://github.com/PancakeTAS/lsfg-vk.git
				synced 2025-10-30 07:01:10 +00:00 
			
		
		
		
	implement dynamic laoder with overrides
This commit is contained in:
		
							parent
							
								
									d7b597ed0f
								
							
						
					
					
						commit
						37ed1e34f5
					
				
					 4 changed files with 320 additions and 0 deletions
				
			
		| 
						 | 
					@ -19,6 +19,7 @@ project(lsfg-vk
 | 
				
			||||||
add_subdirectory(lsfg-vk-gen)
 | 
					add_subdirectory(lsfg-vk-gen)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
file(GLOB SOURCES
 | 
					file(GLOB SOURCES
 | 
				
			||||||
 | 
					    "src/loader/*.cpp"
 | 
				
			||||||
    "src/*.cpp"
 | 
					    "src/*.cpp"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										133
									
								
								include/loader/dl.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								include/loader/dl.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,133 @@
 | 
				
			||||||
 | 
					#ifndef DL_HPP
 | 
				
			||||||
 | 
					#define DL_HPP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					#include <unordered_map>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// This dynamic loader replaces the standard dlopen, dlsym, and dlclose functions.
 | 
				
			||||||
 | 
					// On initialize, the original functions are obtained via dlvsym (glibc exclusive)
 | 
				
			||||||
 | 
					// and made available under functions with the "o" prefix.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Any call to regular dlopen, dlsym or dlclose is intercepted and may be
 | 
				
			||||||
 | 
					// overriden by registering a File override via `Loader::DL::registerFile`.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Loader::DL {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Dynamic loader override structure.
 | 
				
			||||||
 | 
					    class File {
 | 
				
			||||||
 | 
					    public:
 | 
				
			||||||
 | 
					        ///
 | 
				
			||||||
 | 
					        /// Create a dynamic loader override for a specific file.
 | 
				
			||||||
 | 
					        ///
 | 
				
			||||||
 | 
					        /// @param filename The name of the file to override.
 | 
				
			||||||
 | 
					        ///
 | 
				
			||||||
 | 
					        File(std::string filename)
 | 
				
			||||||
 | 
					            : filename(std::move(filename)) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ///
 | 
				
			||||||
 | 
					        /// Append a symbol to the dynamic loader override.
 | 
				
			||||||
 | 
					        ///
 | 
				
			||||||
 | 
					        /// @param symbol The name of the symbol to add.
 | 
				
			||||||
 | 
					        /// @param address The address of the symbol.
 | 
				
			||||||
 | 
					        ///
 | 
				
			||||||
 | 
					        void defineSymbol(const std::string& symbol, void* address) {
 | 
				
			||||||
 | 
					            symbols[symbol] = address;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Get the filename
 | 
				
			||||||
 | 
					        [[nodiscard]] const std::string& getFilename() const { return filename; }
 | 
				
			||||||
 | 
					        /// Get all overriden symbols
 | 
				
			||||||
 | 
					        [[nodiscard]] const std::unordered_map<std::string, void*>& getSymbols() const { return symbols; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Find a specific symbol
 | 
				
			||||||
 | 
					        [[nodiscard]] void* findSymbol(const std::string& symbol) const {
 | 
				
			||||||
 | 
					            auto it = symbols.find(symbol);
 | 
				
			||||||
 | 
					            return (it != symbols.end()) ? it->second : nullptr;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Get the fake handle
 | 
				
			||||||
 | 
					        [[nodiscard]] void* getHandle() const { return handle; }
 | 
				
			||||||
 | 
					        /// Get the real handle
 | 
				
			||||||
 | 
					        [[nodiscard]] void* getOriginalHandle() const { return handle_orig; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Set the fake handle
 | 
				
			||||||
 | 
					        void setHandle(void* new_handle) { handle = new_handle; }
 | 
				
			||||||
 | 
					        /// Set the real handle
 | 
				
			||||||
 | 
					        void setOriginalHandle(void* new_handle) { handle_orig = new_handle; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Copyable, moveable, default destructor
 | 
				
			||||||
 | 
					        File(const File&) = default;
 | 
				
			||||||
 | 
					        File(File&&) = default;
 | 
				
			||||||
 | 
					        File& operator=(const File&) = default;
 | 
				
			||||||
 | 
					        File& operator=(File&&) = default;
 | 
				
			||||||
 | 
					        ~File() = default;
 | 
				
			||||||
 | 
					    private:
 | 
				
			||||||
 | 
					        std::string filename;
 | 
				
			||||||
 | 
					        std::unordered_map<std::string, void*> symbols;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        void* handle = nullptr;
 | 
				
			||||||
 | 
					        void* handle_orig = nullptr;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Initialize the dynamic loader
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    void initialize();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Register a file with the dynamic loader.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// @param file The file to register.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    void registerFile(const File& file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Disable hooks temporarily. This may be useful
 | 
				
			||||||
 | 
					    /// when loading third-party libraries you wish not
 | 
				
			||||||
 | 
					    /// to hook.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    void disableHooks();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Re-enable hooks after they were disabled.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    void enableHooks();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Call the original dlopen function.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// @param filename The name of the file to open.
 | 
				
			||||||
 | 
					    /// @param flag The flags to use when opening the file.
 | 
				
			||||||
 | 
					    /// @return A handle to the opened file, or NULL on failure.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    void* odlopen(const char* filename, int flag);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Call the original dlsym function.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// @param handle The handle to the opened file.
 | 
				
			||||||
 | 
					    /// @param symbol The name of the symbol to look up.
 | 
				
			||||||
 | 
					    /// @return A pointer to the symbol, or NULL on failure.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    void* odlsym(void* handle, const char* symbol);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Call the original dlclose function.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// @param handle The handle to the opened file.
 | 
				
			||||||
 | 
					    /// @return 0 on success, or -1 on failure.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    int odlclose(void* handle);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Modified version of the dlopen function.
 | 
				
			||||||
 | 
					extern "C" void* dlopen(const char* filename, int flag);
 | 
				
			||||||
 | 
					/// Modified version of the dlsym function.
 | 
				
			||||||
 | 
					extern "C" void* dlsym(void* handle, const char* symbol);
 | 
				
			||||||
 | 
					/// Modified version of the dlclose function.
 | 
				
			||||||
 | 
					extern "C" int dlclose(void* handle);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif // DL_HPP
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
 | 
					#include "loader/dl.hpp"
 | 
				
			||||||
#include "log.hpp"
 | 
					#include "log.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern "C" void __attribute__((constructor)) init();
 | 
					extern "C" void __attribute__((constructor)) init();
 | 
				
			||||||
| 
						 | 
					@ -5,6 +6,9 @@ extern "C" [[noreturn]] void __attribute__((destructor)) deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void init() {
 | 
					void init() {
 | 
				
			||||||
    Log::info("lsfg-vk: init() called");
 | 
					    Log::info("lsfg-vk: init() called");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // hook loaders
 | 
				
			||||||
 | 
					    Loader::DL::initialize();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void deinit() {
 | 
					void deinit() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										182
									
								
								src/loader/dl.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								src/loader/dl.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,182 @@
 | 
				
			||||||
 | 
					#include "loader/dl.hpp"
 | 
				
			||||||
 | 
					#include "log.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace Loader;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using dlopen_t  = void* (*)(const char*, int);
 | 
				
			||||||
 | 
					using dlsym_t   = void* (*)(void*, const char*);
 | 
				
			||||||
 | 
					using dlclose_t = int   (*)(void*);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// glibc exclusive function to get versioned symbols
 | 
				
			||||||
 | 
					extern "C" void* dlvsym(long, const char*, const char*);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace {
 | 
				
			||||||
 | 
					    // original function pointers
 | 
				
			||||||
 | 
					    dlopen_t  dlopen_ptr;
 | 
				
			||||||
 | 
					    dlsym_t   dlsym_ptr;
 | 
				
			||||||
 | 
					    dlclose_t dlclose_ptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // map of all registered overrides
 | 
				
			||||||
 | 
					    auto& overrides() {
 | 
				
			||||||
 | 
					        // this has to be a function rather than a static variable
 | 
				
			||||||
 | 
					        // because of weird initialization order issues.
 | 
				
			||||||
 | 
					        static std::unordered_map<std::string, DL::File> overrides;
 | 
				
			||||||
 | 
					        return overrides;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // vector of loaded handles
 | 
				
			||||||
 | 
					    auto& handles() {
 | 
				
			||||||
 | 
					        static std::vector<void*> handles;
 | 
				
			||||||
 | 
					        return handles;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool enable_hooks{true};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DL::initialize() {
 | 
				
			||||||
 | 
					    if (dlopen_ptr || dlsym_ptr || dlclose_ptr) {
 | 
				
			||||||
 | 
					        Log::warn("lsfg-vk(dl): Dynamic loader already initialized, did you call it twice?");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dlopen_ptr  = reinterpret_cast<dlopen_t> (dlvsym(-1, "dlopen",  "GLIBC_2.2.5"));
 | 
				
			||||||
 | 
					    dlsym_ptr   = reinterpret_cast<dlsym_t>  (dlvsym(-1, "dlsym",   "GLIBC_2.2.5"));
 | 
				
			||||||
 | 
					    dlclose_ptr = reinterpret_cast<dlclose_t>(dlvsym(-1, "dlclose", "GLIBC_2.2.5"));
 | 
				
			||||||
 | 
					    if (!dlopen_ptr || !dlsym_ptr || !dlclose_ptr) {
 | 
				
			||||||
 | 
					        Log::error("lsfg-vk(dl): Failed to initialize dynamic loader, missing symbols");
 | 
				
			||||||
 | 
					        exit(EXIT_FAILURE);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Log::debug("lsfg-vk(dl): Initialized dynamic loader with original functions");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DL::registerFile(const File& file) {
 | 
				
			||||||
 | 
					    auto& files = overrides();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto it = files.find(file.getFilename());
 | 
				
			||||||
 | 
					    if (it == files.end()) {
 | 
				
			||||||
 | 
					        // simply register if the file hasn't been registered yet
 | 
				
			||||||
 | 
					        files.emplace(file.getFilename(), file);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // merge the new file's symbols into the previously registered one
 | 
				
			||||||
 | 
					    auto& existing_file = it->second;
 | 
				
			||||||
 | 
					    for (const auto& [symbol, func] : file.getSymbols())
 | 
				
			||||||
 | 
					        if (existing_file.findSymbol(symbol) == nullptr)
 | 
				
			||||||
 | 
					            existing_file.defineSymbol(symbol, func);
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					            Log::warn("lsfg-vk(dl): Tried registering symbol {}::{}, but it is already defined",
 | 
				
			||||||
 | 
					                existing_file.getFilename(), symbol);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DL::disableHooks() { enable_hooks = false; }
 | 
				
			||||||
 | 
					void DL::enableHooks()  { enable_hooks = true; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern "C" void* dlopen(const char* filename, int flag) {
 | 
				
			||||||
 | 
					    auto& files = overrides();
 | 
				
			||||||
 | 
					    auto& loaded = handles();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ALWAYS load the library and ensure it's tracked
 | 
				
			||||||
 | 
					    auto* handle = dlopen_ptr(filename, flag);
 | 
				
			||||||
 | 
					    if (handle && std::ranges::find(loaded, handle) == loaded.end())
 | 
				
			||||||
 | 
					        loaded.push_back(handle);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // no need to check for overrides if hooks are disabled
 | 
				
			||||||
 | 
					    if (!enable_hooks || !filename)
 | 
				
			||||||
 | 
					        return handle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // try to find an override for this filename
 | 
				
			||||||
 | 
					    const std::string filename_str(filename);
 | 
				
			||||||
 | 
					    auto it = files.find(filename_str);
 | 
				
			||||||
 | 
					    if (it == files.end())
 | 
				
			||||||
 | 
					        return handle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto& file = it->second;
 | 
				
			||||||
 | 
					    file.setOriginalHandle(handle);
 | 
				
			||||||
 | 
					    file.setHandle(reinterpret_cast<void*>(&file));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Log::debug("lsfg-vk(dl): Intercepted module load for {}", file.getFilename());
 | 
				
			||||||
 | 
					    return file.getHandle();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern "C" void* dlsym(void* handle, const char* symbol) {
 | 
				
			||||||
 | 
					    const auto& files = overrides();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!enable_hooks || !handle || !symbol)
 | 
				
			||||||
 | 
					        return dlsym_ptr(handle, symbol);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // see if handle is a fake one
 | 
				
			||||||
 | 
					    const auto it = std::ranges::find_if(files, [handle](const auto& pair) {
 | 
				
			||||||
 | 
					        return pair.second.getHandle() == handle;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (it == files.end())
 | 
				
			||||||
 | 
					        return dlsym_ptr(handle, symbol);
 | 
				
			||||||
 | 
					    const auto& file = it->second;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // find a symbol override
 | 
				
			||||||
 | 
					    const std::string symbol_str(symbol);
 | 
				
			||||||
 | 
					    auto* func = file.findSymbol(symbol_str);
 | 
				
			||||||
 | 
					    if (func == nullptr)
 | 
				
			||||||
 | 
					        return dlsym_ptr(file.getOriginalHandle(), symbol);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Log::debug("lsfg-vk(dl): Intercepted symbol {}::{}", file.getFilename(), symbol_str);
 | 
				
			||||||
 | 
					    return func;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern "C" int dlclose(void* handle) {
 | 
				
			||||||
 | 
					    auto& files = overrides();
 | 
				
			||||||
 | 
					    auto& loaded = handles();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // no handle, let the original dlclose handle it
 | 
				
			||||||
 | 
					    if (!handle)
 | 
				
			||||||
 | 
					        return dlclose_ptr(handle);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // see if the handle is a fake one
 | 
				
			||||||
 | 
					    auto it = std::ranges::find_if(files, [handle](const auto& pair) {
 | 
				
			||||||
 | 
					        return pair.second.getHandle() == handle;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (it == files.end()) {
 | 
				
			||||||
 | 
					        // if the handle is not fake, check if it's still loaded.
 | 
				
			||||||
 | 
					        // this is necessary to avoid double closing when
 | 
				
			||||||
 | 
					        // one handle was acquired while hooks were disabled
 | 
				
			||||||
 | 
					        auto l_it = std::ranges::find(loaded, handle);
 | 
				
			||||||
 | 
					        if (l_it == loaded.end())
 | 
				
			||||||
 | 
					            return 0;
 | 
				
			||||||
 | 
					        loaded.erase(l_it);
 | 
				
			||||||
 | 
					        return dlclose_ptr(handle);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto& file = it->second;
 | 
				
			||||||
 | 
					    handle = file.getOriginalHandle();
 | 
				
			||||||
 | 
					    file.setHandle(nullptr);
 | 
				
			||||||
 | 
					    file.setOriginalHandle(nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // similarly, if it is fake, check if it's still loaded
 | 
				
			||||||
 | 
					    // before unloading it again.
 | 
				
			||||||
 | 
					    auto l_it = std::ranges::find(loaded, handle);
 | 
				
			||||||
 | 
					    if (l_it == loaded.end()) {
 | 
				
			||||||
 | 
					        Log::debug("lsfg-vk(dl): Skipping unload for {} (already unloaded)", file.getFilename());
 | 
				
			||||||
 | 
					        return 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    loaded.erase(l_it);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Log::debug("lsfg-vk(dl): Unloaded {}", file.getFilename());
 | 
				
			||||||
 | 
					    return dlclose_ptr(handle);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// original function calls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void* DL::odlopen(const char* filename, int flag) {
 | 
				
			||||||
 | 
					    return dlopen_ptr(filename, flag);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void* DL::odlsym(void* handle, const char* symbol) {
 | 
				
			||||||
 | 
					    return dlsym_ptr(handle, symbol);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int DL::odlclose(void* handle) {
 | 
				
			||||||
 | 
					    return dlclose_ptr(handle);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue