diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e9b69387..efad65ef6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ if(APPLE) endif() if("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "FreeBSD") - set(FreeBSD YES) + set(FREEBSD YES) endif() # Configure some stuff that needs to be set really early diff --git a/qrenderdoc/3rdparty/catch/official/catch.hpp b/qrenderdoc/3rdparty/catch/official/catch.hpp index e505b6152..8ef0132d5 100644 --- a/qrenderdoc/3rdparty/catch/official/catch.hpp +++ b/qrenderdoc/3rdparty/catch/official/catch.hpp @@ -20,7 +20,6 @@ actual site failure. So the debugger stops on the CHECK() or REQUIRE() call instead of inside AssertionHandler::complete() * https://github.com/baldurk/renderdoc/commit/4232736fc21fc6a13a4de6997a5ae106598b225f -* Add FreeBSD support with correct debugger handling */ #ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED @@ -2725,7 +2724,7 @@ namespace Catch { #define CATCH_TRAP() __asm__(".inst 0xde01") #endif -#elif defined(CATCH_PLATFORM_LINUX) || defined(__FreeBSD__) +#elif defined(CATCH_PLATFORM_LINUX) // If we can use inline assembler, do it because this allows us to break // directly at the location of the failing check instead of breaking inside // raise() called from it, i.e. one stack frame below. @@ -10429,7 +10428,7 @@ namespace Catch { // end catch_debug_console.cpp // start catch_debugger.cpp -#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE) || defined(__FreeBSD__) +#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE) # include # include @@ -10441,13 +10440,10 @@ namespace Catch { // These headers will only compile with AppleClang (XCode) // For other compilers (Clang, GCC, ... ) we need to exclude them # include -#elif defined(__FreeBSD__) -# include -# include #endif namespace Catch { - #if defined(__apple_build_version__) || defined(__FreeBSD__) + #ifdef __apple_build_version__ // The following function is taken directly from the following technical note: // https://developer.apple.com/library/archive/qa/qa1361/_index.html @@ -10458,12 +10454,10 @@ namespace Catch { struct kinfo_proc info; std::size_t size; - #ifndef __FreeBSD__ // Initialize the flags so that, if sysctl fails for some bizarre // reason, we get a predictable result. info.kp_proc.p_flag = 0; - #endif // Initialize mib, which tells sysctl the info we want, in this case // we're looking for information about a specific process ID. @@ -10483,11 +10477,7 @@ namespace Catch { // We're being debugged if the P_TRACED flag is set. - #ifdef __FreeBSD__ - return ( (info.ki_flag & P_TRACED) != 0 ); - #else return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); - #endif } #else bool isDebuggerActive() { diff --git a/qrenderdoc/Code/QRDUtils.h b/qrenderdoc/Code/QRDUtils.h index 8734eafcd..d887e42f7 100644 --- a/qrenderdoc/Code/QRDUtils.h +++ b/qrenderdoc/Code/QRDUtils.h @@ -37,7 +37,7 @@ #include #include "Code/Interface/QRDInterface.h" -#if !defined(RELEASE) +#if !defined(RELEASE) && !defined(__FreeBSD__) #define ENABLE_UNIT_TESTS 1 #else #define ENABLE_UNIT_TESTS 0 diff --git a/renderdoc/3rdparty/catch/official/catch.hpp b/renderdoc/3rdparty/catch/official/catch.hpp index e505b6152..8ef0132d5 100644 --- a/renderdoc/3rdparty/catch/official/catch.hpp +++ b/renderdoc/3rdparty/catch/official/catch.hpp @@ -20,7 +20,6 @@ actual site failure. So the debugger stops on the CHECK() or REQUIRE() call instead of inside AssertionHandler::complete() * https://github.com/baldurk/renderdoc/commit/4232736fc21fc6a13a4de6997a5ae106598b225f -* Add FreeBSD support with correct debugger handling */ #ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED @@ -2725,7 +2724,7 @@ namespace Catch { #define CATCH_TRAP() __asm__(".inst 0xde01") #endif -#elif defined(CATCH_PLATFORM_LINUX) || defined(__FreeBSD__) +#elif defined(CATCH_PLATFORM_LINUX) // If we can use inline assembler, do it because this allows us to break // directly at the location of the failing check instead of breaking inside // raise() called from it, i.e. one stack frame below. @@ -10429,7 +10428,7 @@ namespace Catch { // end catch_debug_console.cpp // start catch_debugger.cpp -#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE) || defined(__FreeBSD__) +#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE) # include # include @@ -10441,13 +10440,10 @@ namespace Catch { // These headers will only compile with AppleClang (XCode) // For other compilers (Clang, GCC, ... ) we need to exclude them # include -#elif defined(__FreeBSD__) -# include -# include #endif namespace Catch { - #if defined(__apple_build_version__) || defined(__FreeBSD__) + #ifdef __apple_build_version__ // The following function is taken directly from the following technical note: // https://developer.apple.com/library/archive/qa/qa1361/_index.html @@ -10458,12 +10454,10 @@ namespace Catch { struct kinfo_proc info; std::size_t size; - #ifndef __FreeBSD__ // Initialize the flags so that, if sysctl fails for some bizarre // reason, we get a predictable result. info.kp_proc.p_flag = 0; - #endif // Initialize mib, which tells sysctl the info we want, in this case // we're looking for information about a specific process ID. @@ -10483,11 +10477,7 @@ namespace Catch { // We're being debugged if the P_TRACED flag is set. - #ifdef __FreeBSD__ - return ( (info.ki_flag & P_TRACED) != 0 ); - #else return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); - #endif } #else bool isDebuggerActive() { diff --git a/renderdoc/CMakeLists.txt b/renderdoc/CMakeLists.txt index fdb659b48..7d7d9c35f 100644 --- a/renderdoc/CMakeLists.txt +++ b/renderdoc/CMakeLists.txt @@ -59,7 +59,7 @@ elseif(UNIX) PRIVATE -ldl PRIVATE -lrt) - if(FreeBSD) + if(FREEBSD) list(APPEND RDOC_LIBRARIES PRIVATE -L/usr/local/lib PRIVATE -liconv @@ -338,15 +338,15 @@ elseif(ENABLE_GGP) os/posix/posix_stringio.cpp os/posix/posix_threading.cpp os/posix/posix_specific.h) -elseif(FreeBSD) +elseif(FREEBSD) list(APPEND sources data/embedded_files.h - os/posix/linux/linux_stringio.cpp - os/posix/linux/linux_callstack.cpp - os/posix/linux/linux_threading.cpp - os/posix/freebsd/freebsd_process.cpp - os/posix/linux/linux_hook.cpp - os/posix/linux/linux_network.cpp + os/posix/bsd/bsd_stringio.cpp + os/posix/bsd/bsd_callstack.cpp + os/posix/bsd/bsd_threading.cpp + os/posix/bsd/bsd_process.cpp + os/posix/bsd/bsd_hook.cpp + os/posix/bsd/bsd_network.cpp 3rdparty/plthook/plthook.h 3rdparty/plthook/plthook_elf.c os/posix/posix_network.h diff --git a/renderdoc/common/globalconfig.h b/renderdoc/common/globalconfig.h index 45f11458d..957cc8ace 100644 --- a/renderdoc/common/globalconfig.h +++ b/renderdoc/common/globalconfig.h @@ -226,8 +226,8 @@ enum // this strips them completely #define STRIP_DEBUG_LOGS OPTION_OFF -// disable unit tests on android -#if ENABLED(RDOC_ANDROID) +// disable unit tests on android and *BSD +#if ENABLED(RDOC_ANDROID) || defined(__FreeBSD__) #define ENABLE_UNIT_TESTS OPTION_OFF diff --git a/renderdoc/driver/gl/gl_manager.cpp b/renderdoc/driver/gl/gl_manager.cpp index b89758e8c..cc2517114 100644 --- a/renderdoc/driver/gl/gl_manager.cpp +++ b/renderdoc/driver/gl/gl_manager.cpp @@ -84,9 +84,6 @@ void GLResourceManager::MarkFBOAttachmentsReferenced(ResourceId fboid, GLResourc { FBOCache *cache = m_FBOAttachmentsCache[fboid]; - if(!record) - return; - if(!cache) { cache = m_FBOAttachmentsCache[fboid] = new FBOCache; diff --git a/renderdoc/os/posix/bsd/bsd_callstack.cpp b/renderdoc/os/posix/bsd/bsd_callstack.cpp new file mode 100644 index 000000000..3de67b457 --- /dev/null +++ b/renderdoc/os/posix/bsd/bsd_callstack.cpp @@ -0,0 +1,88 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2023 Baldur Karlsson + * + * 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. + ******************************************************************************/ + +#include "common/common.h" +#include "os/os_specific.h" + +class BSDCallstack : public Callstack::Stackwalk +{ +public: + BSDCallstack() + { + RDCEraseEl(addrs); + numLevels = 0; + } + BSDCallstack(uint64_t *calls, size_t num) { Set(calls, num); } + ~BSDCallstack() {} + void Set(uint64_t *calls, size_t num) + { + numLevels = num; + for(size_t i = 0; i < numLevels; i++) + addrs[i] = calls[i]; + } + + size_t NumLevels() const { return 0; } + const uint64_t *GetAddrs() const { return addrs; } +private: + BSDCallstack(const Callstack::Stackwalk &other); + + uint64_t addrs[128]; + size_t numLevels; +}; + +namespace Callstack +{ +void Init() +{ +} + +Stackwalk *Collect() +{ + return new BSDCallstack(); +} + +Stackwalk *Create() +{ + return new BSDCallstack(NULL, 0); +} + +bool GetLoadedModules(byte *buf, size_t &size) +{ + size = 0; + + if(buf) + memcpy(buf, "BSD_CALL", 8); + + size += 8; + + return true; +} + +StackResolver *MakeResolver(bool interactive, byte *moduleDB, size_t DBSize, + RENDERDOC_ProgressCallback progress) +{ + RDCERR("Callstack resolving not supported on *BSD."); + return NULL; +} +}; diff --git a/renderdoc/os/posix/bsd/bsd_hook.cpp b/renderdoc/os/posix/bsd/bsd_hook.cpp new file mode 100644 index 000000000..567bb1198 --- /dev/null +++ b/renderdoc/os/posix/bsd/bsd_hook.cpp @@ -0,0 +1,556 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2023 Baldur Karlsson + * + * 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. + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "common/threading.h" +#include "core/core.h" +#include "core/settings.h" +#include "hooks/hooks.h" +#include "os/os_specific.h" +#include "plthook/plthook.h" +#include "strings/string_utils.h" + +Threading::CriticalSection libLock; + +static std::map> libraryCallbacks; +static rdcarray libraryHooks; +static rdcarray functionHooks; + +void *intercept_dlopen(const char *filename, int flag, void *ret); +void plthook_lib(void *handle); + +typedef pid_t (*FORKPROC)(); +typedef int (*EXECLEPROC)(const char *pathname, const char *arg, ...); +typedef int (*EXECVEPROC)(const char *pathname, char *const argv[], char *const envp[]); +typedef int (*EXECVPEPROC)(const char *pathname, char *const argv[], char *const envp[]); +typedef void *(*DLOPENPROC)(const char *, int); +typedef void *(*DLSYMPROC)(void *, const char *); +DLOPENPROC realdlopen = NULL; +EXECLEPROC realexecle = NULL; +EXECVEPROC realexecve = NULL; +EXECVPEPROC realexecvpe = NULL; +FORKPROC realfork = NULL; +DLSYMPROC realdlsym = NULL; + +static int32_t tlsbusyflag = 0; + +__attribute__((visibility("default"))) void *dlopen(const char *filename, int flag) +{ + if(!realdlopen) + { + DLOPENPROC passthru = (DLOPENPROC)dlsym(RTLD_NEXT, "dlopen"); + + void *ret = passthru(filename, flag); + + if(filename && ret && (flag & RTLD_DEEPBIND)) + plthook_lib(ret); + + return ret; + } + + if(RenderDoc::Inst().IsReplayApp()) + return realdlopen(filename, flag); + + // don't do any hook processing inside here even if we call dlopen again + Atomic::Inc32(&tlsbusyflag); + void *ret = realdlopen(filename, flag); + Atomic::Dec32(&tlsbusyflag); + + if(filename && ret) + { + SCOPED_LOCK(libLock); + ret = intercept_dlopen(filename, flag, ret); + } + + return ret; +} + +int GetIdentPort(pid_t childPid); + +void PreForkConfigureHooks(); +void GetUnhookedEnvp(char *const *envp, rdcstr &envpStr, rdcarray &modifiedEnv); +void GetHookedEnvp(char *const *envp, rdcstr &envpStr, rdcarray &modifiedEnv); +void ResetHookingEnvVars(); +void StopAtMainInChild(); +bool StopChildAtMain(pid_t childPid, bool *exitWithNoExec); +void ResumeProcess(pid_t childPid, uint32_t delay = 0); +int direct_setenv(const char *name, const char *value, int overwrite); + +/////////////////////////////////////////////////////////////// +// exec hooks - we have to hook each variant since if the application calls the 'real' one of a +// variant, even if it ultimately goes to execve it will be resolved to the real libc one as libc +// doesn't get LD_PRELOAD hooked. +// +// There are two 'real' implementations we have, execve and execvpe that forward to the real +// function after patching the environment. That's to allow the real function to handle the path +// handling in the vpe case, otherwise they're identical. +// +// The other variants all forward to one of those - the 'l' cases unroll the va_args first before +// calling onwards + +#if 1 // TODO: environ bug +char **GetCurrentEnvironment(); +#define environ GetCurrentEnvironment() + +#define GET_EXECL_PARAMS(has_e) \ + va_list args; \ + va_start(args, arg); \ + \ + rdcarray arglist; \ + arglist.push_back((char *)arg); \ + \ + while(true) \ + { \ + char *nextArg = va_arg(args, char *); \ + arglist.push_back(nextArg); \ + \ + /* list is terminated with a NULL */ \ + if(!nextArg) \ + break; \ + } \ + \ + char **envp = NULL; \ + if(has_e) \ + envp = va_arg(args, char **); \ + \ + va_end(args); + +__attribute__((visibility("default"))) int execl(const char *pathname, const char *arg, ...) +{ + GET_EXECL_PARAMS(false); + + return execve(pathname, arglist.data(), environ); +} + +__attribute__((visibility("default"))) int execle(const char *pathname, const char *arg, ...) +{ + GET_EXECL_PARAMS(true); + + return execve(pathname, arglist.data(), envp); +} + +__attribute__((visibility("default"))) int execv(const char *pathname, char *const argv[]) +{ + return execve(pathname, argv, environ); +} +#endif + +__attribute__((visibility("default"))) int execve(const char *pathname, char *const argv[], + char *const envp[]) +{ + if(!realexecve) + { + EXECVEPROC passthru = (EXECVEPROC)dlsym(RTLD_NEXT, "execve"); + return passthru(pathname, argv, envp); + } + + if(RenderDoc::Inst().IsReplayApp()) + return realexecve(pathname, argv, envp); + + rdcarray modifiedEnv; + rdcstr envpStr; + + // if we're not hooking children just call to the real one, but ensure we remove any hooking env + // vars that were kept around after initialisation + if(!RenderDoc::Inst().GetCaptureOptions().hookIntoChildren) + { + GetUnhookedEnvp(envp, envpStr, modifiedEnv); + return realexecve(pathname, argv, modifiedEnv.data()); + } + + GetHookedEnvp(envp, envpStr, modifiedEnv); + return realexecve(pathname, argv, modifiedEnv.data()); +} + +__attribute__((visibility("default"))) int execvpe(const char *pathname, char *const argv[], + char *const envp[]) +{ + if(!realexecvpe) + { + EXECVPEPROC passthru = (EXECVPEPROC)dlsym(RTLD_NEXT, "execvpe"); + return passthru(pathname, argv, envp); + } + + if(RenderDoc::Inst().IsReplayApp()) + return realexecvpe(pathname, argv, envp); + + rdcarray modifiedEnv; + rdcstr envpStr; + + // if we're not hooking children just call to the real one, but ensure we remove any hooking env + // vars that were kept around after initialisation + if(!RenderDoc::Inst().GetCaptureOptions().hookIntoChildren) + { + GetUnhookedEnvp(envp, envpStr, modifiedEnv); + return realexecvpe(pathname, argv, modifiedEnv.data()); + } + + GetHookedEnvp(envp, envpStr, modifiedEnv); + return realexecvpe(pathname, argv, modifiedEnv.data()); +} + +__attribute__((visibility("default"))) pid_t fork() +{ + if(!realfork) + { + FORKPROC passthru = (FORKPROC)dlsym(RTLD_NEXT, "fork"); + return passthru(); + } + + if(RenderDoc::Inst().IsReplayApp()) + return realfork(); + + // if we're not hooking children just call to the real one, we don't have to do anything + if(!RenderDoc::Inst().GetCaptureOptions().hookIntoChildren) + { + // this is a nasty hack. We set this env var when we inject into a process, but because we don't + // know when vulkan may be initialised we need to leave it on indefinitely. If we're not + // injecting into children we need to unset this variable so it doesn't get inherited. + // + // Note this does nothing if the application is doing fork + execve or other variant that passes + // envp because it was probably fetched before the fork - see the exec hooks for where we patch + // that part. + + pid_t ret = realfork(); + if(ret == 0) + direct_setenv(RENDERDOC_VULKAN_LAYER_VAR, "", true); + + return ret; + } + + // fork in a captured application. Need to get the child ident and register it + + // set up environment variables for hooking now + PreForkConfigureHooks(); + + pid_t ret = realfork(); + + if(ret == 0) + { + StopAtMainInChild(); + } + else if(ret > 0) + { + // restore environment variables + ResetHookingEnvVars(); + + bool exitWithNoExec = false; + bool stopped = StopChildAtMain(ret, &exitWithNoExec); + + if(!exitWithNoExec && stopped) + { + int ident = GetIdentPort(ret); + + ResumeProcess(ret); + + if(ident) + { + RDCLOG("Identified child process %u with ident %u", ret, ident); + RenderDoc::Inst().AddChildProcess((uint32_t)ret, (uint32_t)ident); + } + else + { + RDCERR("Couldn't get ident for PID %u after stopping at main", ret); + } + } + else + { + // resume the process just in case something went wrong. This should be harmless if we're not + // actually tracing + ResumeProcess(ret); + + // ptrace_scope isn't amenable, or we hit an error. We'll have to spin up a thread to check + // the ident on the child process and add it as soon as it's available + Threading::ThreadHandle handle = Threading::CreateThread([ret]() { + RDCLOG("Starting thread to get ident for PID %u", ret); + + // don't accept a return value of our own ident, that means we've checked too early and exec + // hasn't run yet + const uint32_t ownIdent = RenderDoc::Inst().GetTargetControlIdent(); + uint32_t ident = ownIdent; + for(uint32_t i = 0; i < 10 && ident == ownIdent; i++) + { + ident = (uint32_t)GetIdentPort(ret); + if(ident == ownIdent) + usleep(1000); + } + + if(ident == ownIdent) + ident = 0; + + RDCLOG("PID %u has ident %u", ret, ident); + + RenderDoc::Inst().AddChildProcess((uint32_t)ret, (uint32_t)ident); + RenderDoc::Inst().CompleteChildThread((uint32_t)ret); + }); + RenderDoc::Inst().AddChildThread((uint32_t)ret, handle); + } + } + + return ret; +} + +#if defined(RENDERDOC_HOOK_DLSYM) + +#pragma message("ALERT: dlsym() hooking enabled! This is unreliable & relies on glibc internals.") + +extern "C" { + +__attribute__((visibility("default"))) void *dlsym(void *handle, const char *name); + +extern void *_dl_sym(void *, const char *, void *); + +void bootstrap_dlsym() +{ + realdlsym = (DLSYMPROC)_dl_sym(RTLD_NEXT, "dlsym", (void *)&dlsym); +} + +__attribute__((visibility("default"))) void *dlsym(void *handle, const char *name) +{ + if(!strcmp(name, "dlsym")) + return (void *)&dlsym; + + if(!strcmp(name, "dlopen") && realdlopen) + return (void *)&dlopen; + + if(realdlsym == NULL) + bootstrap_dlsym(); + + if(realdlsym == NULL) + { + fprintf(stderr, "Couldn't get onwards dlsym in hooked dlsym\n"); + exit(-1); + } + + return realdlsym(handle, name); +} + +}; // extern "C" + +#endif + +void plthook_lib(void *handle) +{ + plthook_t *plthook = NULL; + + // minimal error handling as we can't do much more than log the error, and since this is + // 'best-effort' attempt to hook the unhookable, we just try and allow it to fail. + if(plthook_open_by_handle(&plthook, handle)) + return; + + plthook_replace(plthook, "dlopen", (void *)dlopen, NULL); + + for(FunctionHook &hook : functionHooks) + { + void *orig = NULL; + plthook_replace(plthook, hook.function.c_str(), hook.hook, &orig); + if(hook.orig && *hook.orig == NULL && orig) + *hook.orig = orig; + } + + plthook_close(plthook); +} + +// multiple libraries names pointing at the same file are declared as hooks +// in this case, if the second version gets loaded or when CheckLoadedLibraries is run, +// hooks are run another time. Avoid this by clearing callbacks of hooks pointing at the same +// library +static void PreventDoubleHook(const void *loadedHandle) +{ + for(auto it = libraryHooks.begin(); it != libraryHooks.end(); ++it) + { + rdcstr libName = *it; + // if callbacks are empty, there is no risk of executing anything + if(libraryCallbacks[libName].empty()) + continue; + + void *handle = realdlopen(libName.c_str(), RTLD_NOW | RTLD_NOLOAD | RTLD_GLOBAL); + if(handle == loadedHandle) + { + // we have been loaded by a different name, don't run hooks again + libraryCallbacks[libName].clear(); + } + } +} + +static void CheckLoadedLibraries() +{ + // don't process anything if the busy flag was set, otherwise set it ourselves + if(Atomic::CmpExch32(&tlsbusyflag, 0, 1) != 0) + return; + + // iterate over the libraries and see which ones are already loaded, process function hooks for + // them and call callbacks. + for(auto it = libraryHooks.begin(); it != libraryHooks.end(); ++it) + { + rdcstr libName = *it; + void *handle = realdlopen(libName.c_str(), RTLD_NOW | RTLD_NOLOAD | RTLD_GLOBAL); + + if(handle) + { + for(FunctionHook &hook : functionHooks) + { + if(hook.orig && *hook.orig == NULL) + *hook.orig = dlsym(handle, hook.function.c_str()); + } + + rdcarray callbacks; + + // don't call callbacks again if the library is dlopen'd again + libraryCallbacks[libName].swap(callbacks); + PreventDoubleHook(handle); + + for(FunctionLoadCallback cb : callbacks) + if(cb) + cb(handle); + } + } + + // clear any dl errors in case twitchy applications get set off by false positive errors. + dlerror(); + + // decrement the flag counter + Atomic::Dec32(&tlsbusyflag); +} + +void *intercept_dlopen(const char *filename, int flag, void *ret) +{ + if(filename == NULL) + return ret; + + if(flag & RTLD_DEEPBIND) + plthook_lib(ret); + + rdcstr base = get_basename(filename); + + for(auto it = libraryHooks.begin(); it != libraryHooks.end(); ++it) + { + const rdcstr &libName = *it; + if(*it == base) + { + RDCDEBUG("Redirecting dlopen to ourselves for %s", filename); + + for(FunctionHook &hook : functionHooks) + { + if(hook.orig && *hook.orig == NULL) + *hook.orig = dlsym(ret, hook.function.c_str()); + } + + rdcarray callbacks; + + // don't call callbacks again if the library is dlopen'd again + libraryCallbacks[libName].swap(callbacks); + PreventDoubleHook(ret); + + for(FunctionLoadCallback cb : callbacks) + if(cb) + cb(ret); + + ret = realdlopen("lib" STRINGIZE(RDOC_BASE_NAME) ".so", flag); + break; + } + } + + // this library might depend on one we care about, so check again as we + // did in EndHookRegistration to see if any library has been loaded. + CheckLoadedLibraries(); + + return ret; +} + +void LibraryHooks::ReplayInitialise() +{ + realdlopen = (DLOPENPROC)dlsym(RTLD_NEXT, "dlopen"); + realfork = (FORKPROC)dlsym(RTLD_NEXT, "fork"); + realexecle = (EXECLEPROC)dlsym(RTLD_NEXT, "execle"); + realexecve = (EXECVEPROC)dlsym(RTLD_NEXT, "execve"); + realexecvpe = (EXECVPEPROC)dlsym(RTLD_NEXT, "execvpe"); +} + +void LibraryHooks::BeginHookRegistration() +{ + realdlopen = (DLOPENPROC)dlsym(RTLD_NEXT, "dlopen"); + realfork = (FORKPROC)dlsym(RTLD_NEXT, "fork"); + realexecle = (EXECLEPROC)dlsym(RTLD_NEXT, "execle"); + realexecve = (EXECVEPROC)dlsym(RTLD_NEXT, "execve"); + realexecvpe = (EXECVPEPROC)dlsym(RTLD_NEXT, "execvpe"); +} + +bool LibraryHooks::Detect(const char *identifier) +{ + return dlsym(RTLD_DEFAULT, identifier) != NULL; +} + +void LibraryHooks::RemoveHooks() +{ + RDCERR("Removing hooks is not possible on this platform"); +} + +void LibraryHooks::EndHookRegistration() +{ + CheckLoadedLibraries(); +} + +void LibraryHooks::Refresh() +{ + // don't need to refresh on linux +} + +void LibraryHooks::RegisterFunctionHook(const char *libraryName, const FunctionHook &hook) +{ + // we don't use the library name + (void)libraryName; + + SCOPED_LOCK(libLock); + functionHooks.push_back(hook); +} + +void LibraryHooks::RegisterLibraryHook(char const *name, FunctionLoadCallback cb) +{ + SCOPED_LOCK(libLock); + + if(!libraryHooks.contains(name)) + libraryHooks.push_back(name); + + if(cb) + libraryCallbacks[name].push_back(cb); +} + +void LibraryHooks::IgnoreLibrary(const char *libraryName) +{ +} + +// android only hooking functions, not used on linux +ScopedSuppressHooking::ScopedSuppressHooking() +{ +} + +ScopedSuppressHooking::~ScopedSuppressHooking() +{ +} diff --git a/renderdoc/os/posix/bsd/bsd_network.cpp b/renderdoc/os/posix/bsd/bsd_network.cpp new file mode 100644 index 000000000..4b67d3d18 --- /dev/null +++ b/renderdoc/os/posix/bsd/bsd_network.cpp @@ -0,0 +1,44 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2023 Baldur Karlsson + * + * 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. + ******************************************************************************/ + +#include "os/os_specific.h" +#include "os/posix/posix_network.h" + +namespace Network +{ +void SocketPostSend() +{ + // only needed for awful hack on Android +} + +uint32_t Socket::GetRemoteIP() const +{ + return GetIPFromTCPSocket((int)socket); +} + +Socket *CreateServerSocket(const rdcstr &bindaddr, uint16_t port, int queuesize) +{ + return CreateTCPServerSocket(bindaddr, port, queuesize); +} +}; diff --git a/renderdoc/os/posix/freebsd/freebsd_process.cpp b/renderdoc/os/posix/bsd/bsd_process.cpp similarity index 98% rename from renderdoc/os/posix/freebsd/freebsd_process.cpp rename to renderdoc/os/posix/bsd/bsd_process.cpp index c048bcc3b..905cd8b2d 100644 --- a/renderdoc/os/posix/freebsd/freebsd_process.cpp +++ b/renderdoc/os/posix/bsd/bsd_process.cpp @@ -24,8 +24,11 @@ #include // for dlsym #include -#include +// clang-format off +// sys/types.h needs to be included before sys/sysctl.h #include +#include +// clang-format on #include #include #include "common/common.h" diff --git a/renderdoc/os/posix/bsd/bsd_stringio.cpp b/renderdoc/os/posix/bsd/bsd_stringio.cpp new file mode 100644 index 000000000..f23d81874 --- /dev/null +++ b/renderdoc/os/posix/bsd/bsd_stringio.cpp @@ -0,0 +1,809 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2023 Baldur Karlsson + * + * 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. + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "api/app/renderdoc_app.h" +#include "api/replay/replay_enums.h" +#include "common/common.h" +#include "common/threading.h" +#include "os/os_specific.h" +#include "strings/string_utils.h" + +#if ENABLED(RDOC_XLIB) +#include +#include +#else +typedef struct _XDisplay Display; +#endif + +#if ENABLED(RDOC_XCB) +#include +#include +#else +struct xcb_connection_t; +#endif + +#if ENABLED(RDOC_WAYLAND) +#include +#include +#include +#else +struct wl_display; +struct wl_surface; +#endif + +namespace Keyboard +{ +void Init() +{ +} + +#if ENABLED(RDOC_XLIB) + +Display *CurrentXDisplay = NULL; + +void UseXlibDisplay(Display *dpy) +{ + if(CurrentXDisplay || dpy == NULL) + return; + + CurrentXDisplay = XOpenDisplay(XDisplayString(dpy)); +} + +bool HasXlibInput() +{ + return CurrentXDisplay != NULL; +} + +bool GetXlibKeyState(int key) +{ + if(CurrentXDisplay == NULL) + return false; + + KeySym ks = 0; + + if(key >= eRENDERDOC_Key_A && key <= eRENDERDOC_Key_Z) + ks = key; + if(key >= eRENDERDOC_Key_0 && key <= eRENDERDOC_Key_9) + ks = key; + + switch(key) + { + case eRENDERDOC_Key_Divide: ks = XK_KP_Divide; break; + case eRENDERDOC_Key_Multiply: ks = XK_KP_Multiply; break; + case eRENDERDOC_Key_Subtract: ks = XK_KP_Subtract; break; + case eRENDERDOC_Key_Plus: ks = XK_KP_Add; break; + case eRENDERDOC_Key_F1: ks = XK_F1; break; + case eRENDERDOC_Key_F2: ks = XK_F2; break; + case eRENDERDOC_Key_F3: ks = XK_F3; break; + case eRENDERDOC_Key_F4: ks = XK_F4; break; + case eRENDERDOC_Key_F5: ks = XK_F5; break; + case eRENDERDOC_Key_F6: ks = XK_F6; break; + case eRENDERDOC_Key_F7: ks = XK_F7; break; + case eRENDERDOC_Key_F8: ks = XK_F8; break; + case eRENDERDOC_Key_F9: ks = XK_F9; break; + case eRENDERDOC_Key_F10: ks = XK_F10; break; + case eRENDERDOC_Key_F11: ks = XK_F11; break; + case eRENDERDOC_Key_F12: ks = XK_F12; break; + case eRENDERDOC_Key_Home: ks = XK_Home; break; + case eRENDERDOC_Key_End: ks = XK_End; break; + case eRENDERDOC_Key_Insert: ks = XK_Insert; break; + case eRENDERDOC_Key_Delete: ks = XK_Delete; break; + case eRENDERDOC_Key_PageUp: ks = XK_Prior; break; + case eRENDERDOC_Key_PageDn: ks = XK_Next; break; + case eRENDERDOC_Key_Backspace: ks = XK_BackSpace; break; + case eRENDERDOC_Key_Tab: ks = XK_Tab; break; + case eRENDERDOC_Key_PrtScrn: ks = XK_Print; break; + case eRENDERDOC_Key_Pause: ks = XK_Pause; break; + default: break; + } + + if(ks == 0) + return false; + + KeyCode kc = XKeysymToKeycode(CurrentXDisplay, ks); + + char keyState[32]; + XQueryKeymap(CurrentXDisplay, keyState); + + int byteIdx = (kc / 8); + int bitMask = 1 << (kc % 8); + + uint8_t keyByte = (uint8_t)keyState[byteIdx]; + + return (keyByte & bitMask) != 0; +} + +#else + +// if RENDERDOC_WINDOWING_XLIB is not enabled + +void UseXlibDisplay(Display *dpy) +{ +} + +bool HasXlibInput() +{ + return false; +} + +bool GetXlibKeyState(int key) +{ + return false; +} + +#endif + +#if ENABLED(RDOC_XCB) + +xcb_connection_t *connection; +xcb_key_symbols_t *symbols; + +void UseXcbConnection(xcb_connection_t *conn) +{ + connection = conn; + symbols = xcb_key_symbols_alloc(conn); +} + +bool HasXCBInput() +{ + return symbols != NULL; +} + +bool GetXCBKeyState(int key) +{ + if(symbols == NULL) + return false; + + xcb_keysym_t ks = 0; + + if(key >= eRENDERDOC_Key_A && key <= eRENDERDOC_Key_Z) + ks = key; + if(key >= eRENDERDOC_Key_0 && key <= eRENDERDOC_Key_9) + ks = key; + + switch(key) + { + case eRENDERDOC_Key_Divide: ks = XK_KP_Divide; break; + case eRENDERDOC_Key_Multiply: ks = XK_KP_Multiply; break; + case eRENDERDOC_Key_Subtract: ks = XK_KP_Subtract; break; + case eRENDERDOC_Key_Plus: ks = XK_KP_Add; break; + case eRENDERDOC_Key_F1: ks = XK_F1; break; + case eRENDERDOC_Key_F2: ks = XK_F2; break; + case eRENDERDOC_Key_F3: ks = XK_F3; break; + case eRENDERDOC_Key_F4: ks = XK_F4; break; + case eRENDERDOC_Key_F5: ks = XK_F5; break; + case eRENDERDOC_Key_F6: ks = XK_F6; break; + case eRENDERDOC_Key_F7: ks = XK_F7; break; + case eRENDERDOC_Key_F8: ks = XK_F8; break; + case eRENDERDOC_Key_F9: ks = XK_F9; break; + case eRENDERDOC_Key_F10: ks = XK_F10; break; + case eRENDERDOC_Key_F11: ks = XK_F11; break; + case eRENDERDOC_Key_F12: ks = XK_F12; break; + case eRENDERDOC_Key_Home: ks = XK_Home; break; + case eRENDERDOC_Key_End: ks = XK_End; break; + case eRENDERDOC_Key_Insert: ks = XK_Insert; break; + case eRENDERDOC_Key_Delete: ks = XK_Delete; break; + case eRENDERDOC_Key_PageUp: ks = XK_Prior; break; + case eRENDERDOC_Key_PageDn: ks = XK_Next; break; + case eRENDERDOC_Key_Backspace: ks = XK_BackSpace; break; + case eRENDERDOC_Key_Tab: ks = XK_Tab; break; + case eRENDERDOC_Key_PrtScrn: ks = XK_Print; break; + case eRENDERDOC_Key_Pause: ks = XK_Pause; break; + default: break; + } + + if(ks == 0) + return false; + + xcb_keycode_t *keyCodes = xcb_key_symbols_get_keycode(symbols, ks); + + if(!keyCodes) + return false; + + xcb_query_keymap_cookie_t keymapcookie = xcb_query_keymap(connection); + xcb_query_keymap_reply_t *keys = xcb_query_keymap_reply(connection, keymapcookie, NULL); + + bool ret = false; + + if(keys && keyCodes[0] != XCB_NO_SYMBOL) + { + int byteIdx = (keyCodes[0] / 8); + int bitMask = 1 << (keyCodes[0] % 8); + + ret = (keys->keys[byteIdx] & bitMask) != 0; + } + + free(keyCodes); + free(keys); + + return ret; +} + +#else + +// if RENDERDOC_WINDOWING_XCB is not enabled + +void UseXcbConnection(xcb_connection_t *conn) +{ +} + +bool GetXCBKeyState(int key) +{ + return false; +} + +bool HasXCBInput() +{ + return false; +} + +#endif + +#if ENABLED(RDOC_WAYLAND) + +#include + +std::set displays; +std::set surfaces; +std::map, wl_seat *> seatNames; +std::map seatKeyboard; +bool inFocus = false; +Threading::CriticalSection waylandLock; + +bool keyState[eRENDERDOC_Key_Max] = {}; + +void WaylandKeymapDummy(void *data, wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) +{ +} + +void WaylandModifiersDummy(void *data, wl_keyboard *keyboard, uint32_t serial, + uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) +{ +} + +void WaylandRepeatInfoDummy(void *data, wl_keyboard *keyboard, int32_t rate, int32_t delay) +{ +} + +void WaylandEnter(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surf, wl_array *keys) +{ + SCOPED_LOCK(waylandLock); + inFocus = surfaces.find(surf) != surfaces.end(); + RDCEraseEl(keyState); +} + +void WaylandLeave(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surf) +{ + SCOPED_LOCK(waylandLock); + inFocus = false; + RDCEraseEl(keyState); +} + +void WaylandKeypress(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t time, + uint32_t key, uint32_t state) +{ + int keyIdx = -1; + switch(key) + { + case KEY_0: keyIdx = eRENDERDOC_Key_0; break; + case KEY_1: keyIdx = eRENDERDOC_Key_1; break; + case KEY_2: keyIdx = eRENDERDOC_Key_2; break; + case KEY_3: keyIdx = eRENDERDOC_Key_3; break; + case KEY_4: keyIdx = eRENDERDOC_Key_4; break; + case KEY_5: keyIdx = eRENDERDOC_Key_5; break; + case KEY_6: keyIdx = eRENDERDOC_Key_6; break; + case KEY_7: keyIdx = eRENDERDOC_Key_7; break; + case KEY_8: keyIdx = eRENDERDOC_Key_8; break; + case KEY_9: keyIdx = eRENDERDOC_Key_9; break; + case KEY_A: keyIdx = eRENDERDOC_Key_A; break; + case KEY_B: keyIdx = eRENDERDOC_Key_B; break; + case KEY_C: keyIdx = eRENDERDOC_Key_C; break; + case KEY_D: keyIdx = eRENDERDOC_Key_D; break; + case KEY_E: keyIdx = eRENDERDOC_Key_E; break; + case KEY_F: keyIdx = eRENDERDOC_Key_F; break; + case KEY_G: keyIdx = eRENDERDOC_Key_G; break; + case KEY_H: keyIdx = eRENDERDOC_Key_H; break; + case KEY_I: keyIdx = eRENDERDOC_Key_I; break; + case KEY_J: keyIdx = eRENDERDOC_Key_J; break; + case KEY_K: keyIdx = eRENDERDOC_Key_K; break; + case KEY_L: keyIdx = eRENDERDOC_Key_L; break; + case KEY_M: keyIdx = eRENDERDOC_Key_M; break; + case KEY_N: keyIdx = eRENDERDOC_Key_N; break; + case KEY_O: keyIdx = eRENDERDOC_Key_O; break; + case KEY_P: keyIdx = eRENDERDOC_Key_P; break; + case KEY_Q: keyIdx = eRENDERDOC_Key_Q; break; + case KEY_R: keyIdx = eRENDERDOC_Key_R; break; + case KEY_S: keyIdx = eRENDERDOC_Key_S; break; + case KEY_T: keyIdx = eRENDERDOC_Key_T; break; + case KEY_U: keyIdx = eRENDERDOC_Key_U; break; + case KEY_V: keyIdx = eRENDERDOC_Key_V; break; + case KEY_W: keyIdx = eRENDERDOC_Key_W; break; + case KEY_X: keyIdx = eRENDERDOC_Key_X; break; + case KEY_Y: keyIdx = eRENDERDOC_Key_Y; break; + case KEY_Z: keyIdx = eRENDERDOC_Key_Z; break; + + case KEY_KPSLASH: keyIdx = eRENDERDOC_Key_Divide; break; + case KEY_KPASTERISK: keyIdx = eRENDERDOC_Key_Multiply; break; + case KEY_KPMINUS: keyIdx = eRENDERDOC_Key_Subtract; break; + case KEY_KPPLUS: keyIdx = eRENDERDOC_Key_Plus; break; + + case KEY_F1: keyIdx = eRENDERDOC_Key_F1; break; + case KEY_F2: keyIdx = eRENDERDOC_Key_F2; break; + case KEY_F3: keyIdx = eRENDERDOC_Key_F3; break; + case KEY_F4: keyIdx = eRENDERDOC_Key_F4; break; + case KEY_F5: keyIdx = eRENDERDOC_Key_F5; break; + case KEY_F6: keyIdx = eRENDERDOC_Key_F6; break; + case KEY_F7: keyIdx = eRENDERDOC_Key_F7; break; + case KEY_F8: keyIdx = eRENDERDOC_Key_F8; break; + case KEY_F9: keyIdx = eRENDERDOC_Key_F9; break; + case KEY_F10: keyIdx = eRENDERDOC_Key_F10; break; + case KEY_F11: keyIdx = eRENDERDOC_Key_F11; break; + case KEY_F12: keyIdx = eRENDERDOC_Key_F12; break; + + case KEY_HOME: keyIdx = eRENDERDOC_Key_Home; break; + case KEY_END: keyIdx = eRENDERDOC_Key_End; break; + case KEY_INSERT: keyIdx = eRENDERDOC_Key_Insert; break; + case KEY_DELETE: keyIdx = eRENDERDOC_Key_Delete; break; + case KEY_PAGEUP: keyIdx = eRENDERDOC_Key_PageUp; break; + case KEY_PAGEDOWN: keyIdx = eRENDERDOC_Key_PageDn; break; + + case KEY_BACKSPACE: keyIdx = eRENDERDOC_Key_Backspace; break; + case KEY_TAB: keyIdx = eRENDERDOC_Key_Tab; break; + case KEY_SYSRQ: keyIdx = eRENDERDOC_Key_PrtScrn; break; + case KEY_PAUSE: keyIdx = eRENDERDOC_Key_Pause; break; + } + + if(keyIdx < 0) + return; + + { + SCOPED_LOCK(waylandLock); + keyState[keyIdx] = (state == WL_KEYBOARD_KEY_STATE_PRESSED); + } +} + +void WaylandSeatCaps(void *data, wl_seat *seat, uint32_t capabilities) +{ + if(capabilities & WL_SEAT_CAPABILITY_KEYBOARD) + { + { + SCOPED_LOCK(waylandLock); + if(seatKeyboard[seat]) + return; + } + + wl_keyboard *keyboard = wl_seat_get_keyboard(seat); + static const wl_keyboard_listener listener = { + WaylandKeymapDummy, WaylandEnter, WaylandLeave, + WaylandKeypress, WaylandModifiersDummy, WaylandRepeatInfoDummy, + }; + wl_keyboard_add_listener(keyboard, &listener, NULL); + + { + SCOPED_LOCK(waylandLock); + seatKeyboard[seat] = keyboard; + } + } + else + { + wl_keyboard *keyboard = NULL; + + { + SCOPED_LOCK(waylandLock); + keyboard = seatKeyboard[seat]; + + if(!keyboard) + return; + + seatKeyboard[seat] = NULL; + } + + wl_keyboard_destroy(keyboard); + } +} + +void WaylandRegistryAdd(void *data, wl_registry *reg, uint32_t name, const char *iface, + uint32_t version) +{ + if(!strcmp(iface, "wl_seat")) + { + wl_seat *seat = (wl_seat *)wl_registry_bind(reg, name, &wl_seat_interface, 1); + static const wl_seat_listener listener = {&WaylandSeatCaps}; + wl_seat_add_listener(seat, &listener, NULL); + + { + SCOPED_LOCK(waylandLock); + seatNames[{reg, name}] = seat; + } + } +} + +void WaylandRegistryRemove(void *data, wl_registry *reg, uint32_t name) +{ + SCOPED_LOCK(waylandLock); + auto it = seatNames.find({reg, name}); + if(it != seatNames.end()) + { + wl_seat_destroy(it->second); + seatNames.erase(it); + } +} + +void UseWaylandDisplay(wl_display *disp) +{ + // only listen to each display once at most + { + SCOPED_LOCK(waylandLock); + if(displays.find(disp) != displays.end()) + return; + displays.insert(disp); + } + + static const wl_registry_listener listener = {&WaylandRegistryAdd, &WaylandRegistryRemove}; + + // get the registry and listen to it. This will then let us find seats + wl_registry_add_listener(wl_display_get_registry(disp), &listener, NULL); +} + +void AddWaylandInputWindow(wl_surface *wnd) +{ + SCOPED_LOCK(waylandLock); + surfaces.insert(wnd); +} + +void RemoveWaylandInputWindow(wl_surface *wnd) +{ + SCOPED_LOCK(waylandLock); + surfaces.erase(wnd); +} + +bool HasWaylandInput() +{ + SCOPED_LOCK(waylandLock); + return !displays.empty(); +} + +bool GetWaylandKeyState(int key) +{ + SCOPED_LOCK(waylandLock); + return keyState[key]; +} + +#else + +void UseWaylandDisplay(wl_display *disp) +{ +} + +void AddWaylandInputWindow(wl_surface *wnd) +{ +} + +void RemoveWaylandInputWindow(wl_surface *wnd) +{ +} + +bool HasWaylandInput() +{ + return false; +} + +bool GetWaylandKeyState(int key) +{ + return false; +} + +#endif + +WindowingSystem UseUnknownDisplay(void *disp) +{ + if(disp == NULL) + return WindowingSystem::Unknown; + + // could be wayland or xlib, try to detect. + // both Display* and wl_display* are valid pointers, so dereference and read the first pointer + // sized bytes + void *firstPointer = NULL; + memcpy(&firstPointer, disp, sizeof(void *)); + + // in a Display* we don't know what this contains, but in a wl_display it should point to the + // wl_display_interface exported symbol. Check with dladdr + Dl_info info; + if(dladdr(firstPointer, &info) && !strcmp(info.dli_sname, "wl_display_interface")) + { + UseWaylandDisplay((wl_display *)disp); + return WindowingSystem::Wayland; + } + else + { + UseXlibDisplay((Display *)disp); + return WindowingSystem::Xlib; + } +} + +void AddInputWindow(WindowingSystem windowSystem, void *wnd) +{ + if(windowSystem == WindowingSystem::Wayland) + { + AddWaylandInputWindow((wl_surface *)wnd); + } + else + { + // TODO check against this drawable & parent window being focused in GetKeyState + } +} + +void RemoveInputWindow(WindowingSystem windowSystem, void *wnd) +{ + if(windowSystem == WindowingSystem::Wayland) + { + RemoveWaylandInputWindow((wl_surface *)wnd); + } +} + +bool PlatformHasKeyInput() +{ + return HasXCBInput() || HasXlibInput() || HasWaylandInput(); +} + +bool GetKeyState(int key) +{ + return GetXCBKeyState(key) || GetXlibKeyState(key) || GetWaylandKeyState(key); +} +} + +namespace FileIO +{ +rdcstr GetTempRootPath() +{ + return "/tmp"; +} + +rdcstr GetAppFolderFilename(const rdcstr &filename) +{ + passwd *pw = getpwuid(getuid()); + rdcstr homedir = pw ? pw->pw_dir : ""; + + if(homedir.empty()) + homedir = Process::GetEnvVariable("HOME"); + + if(homedir.empty()) + { + RDCERR("Can't get HOME directory, defaulting to '/' instead"); + homedir = ""; + } + + rdcstr ret = homedir + "/.renderdoc/"; + + mkdir(ret.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + + return ret + filename; +} + +rdcstr DefaultFindFileInPath(const rdcstr &fileName); +rdcstr FindFileInPath(const rdcstr &fileName) +{ + return DefaultFindFileInPath(fileName); +} + +void GetExecutableFilename(rdcstr &selfName) +{ + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, getpid()}; + + char path[512] = {0}; + size_t size = sizeof(path); + + if(sysctl(mib, ARRAY_COUNT(mib), path, &size, NULL, 0) != 0) + { + selfName = "/unknown/unknown"; + RDCERR("Can't get executable name"); + return; // don't try and readlink this + } + selfName = rdcstr(path); + + memset(path, 0, sizeof(path)); + readlink(selfName.c_str(), path, 511); + + if(path[0] != 0) + selfName = rdcstr(path); +} + +int LibraryLocator = 42; + +void GetLibraryFilename(rdcstr &selfName) +{ + Dl_info info; + if(dladdr(&LibraryLocator, &info)) + { + selfName = info.dli_fname; + } + else + { + RDCERR("dladdr failed to get library path"); + selfName = ""; + } +} +}; + +namespace StringFormat +{ +// cache iconv_t descriptor to save on iconv_open/iconv_close each time +iconv_t iconvWide2UTF8 = (iconv_t)-1; +iconv_t iconvUTF82Wide = (iconv_t)-1; + +// iconv is not thread safe when sharing an iconv_t descriptor +// I don't expect much contention but if it happens we could TryLock +// before creating a temporary iconv_t, or hold two iconv_ts, or something. +Threading::CriticalSection iconvLock; + +void Shutdown() +{ + SCOPED_LOCK(iconvLock); + + if(iconvWide2UTF8 != (iconv_t)-1) + iconv_close(iconvWide2UTF8); + iconvWide2UTF8 = (iconv_t)-1; + + if(iconvUTF82Wide != (iconv_t)-1) + iconv_close(iconvUTF82Wide); + iconvUTF82Wide = (iconv_t)-1; +} + +rdcstr Wide2UTF8(const rdcwstr &s) +{ + // include room for null terminator, assuming unicode input (not ucs) + // utf-8 characters can be max 4 bytes. + size_t len = (s.length() + 1) * 4; + + rdcarray charBuffer; + charBuffer.resize(len); + + size_t ret; + + { + SCOPED_LOCK(iconvLock); + + if(iconvWide2UTF8 == (iconv_t)-1) + iconvWide2UTF8 = iconv_open("UTF-8", "WCHAR_T"); + + if(iconvWide2UTF8 == (iconv_t)-1) + { + RDCERR("Couldn't open iconv for WCHAR_T to UTF-8: %d", errno); + return ""; + } + + char *inbuf = (char *)s.c_str(); + size_t insize = (s.length() + 1) * sizeof(wchar_t); // include null terminator + char *outbuf = &charBuffer[0]; + size_t outsize = len; + + ret = iconv(iconvWide2UTF8, &inbuf, &insize, &outbuf, &outsize); + } + + if(ret == (size_t)-1) + { +#if ENABLED(RDOC_DEVEL) + RDCWARN("Failed to convert wstring"); +#endif + return ""; + } + + // convert to string from null-terminated string - utf-8 never contains + // 0 bytes before the null terminator, and this way we don't care if + // charBuffer is larger than the string + return rdcstr(&charBuffer[0]); +} + +rdcwstr UTF82Wide(const rdcstr &s) +{ + // include room for null terminator, for ascii input we need at least as many output chars as + // input. + size_t len = s.length() + 1; + + rdcarray wcharBuffer; + wcharBuffer.resize(len); + + size_t ret; + + { + SCOPED_LOCK(iconvLock); + + if(iconvUTF82Wide == (iconv_t)-1) + iconvUTF82Wide = iconv_open("WCHAR_T", "UTF-8"); + + if(iconvUTF82Wide == (iconv_t)-1) + { + RDCERR("Couldn't open iconv for UTF-8 to WCHAR_T: %d", errno); + return L""; + } + + char *inbuf = (char *)s.c_str(); + size_t insize = s.length() + 1; // include null terminator + char *outbuf = (char *)&wcharBuffer[0]; + size_t outsize = len * sizeof(wchar_t); + + ret = iconv(iconvUTF82Wide, &inbuf, &insize, &outbuf, &outsize); + } + + if(ret == (size_t)-1) + { +#if ENABLED(RDOC_DEVEL) + RDCWARN("Failed to convert wstring"); +#endif + return L""; + } + + // convert to string from null-terminated string + return rdcwstr(&wcharBuffer[0]); +} +}; + +namespace OSUtility +{ +void WriteOutput(int channel, const char *str) +{ + if(channel == OSUtility::Output_StdOut) + { + fprintf(stdout, "%s", str); + fflush(stdout); + } + else if(channel == OSUtility::Output_StdErr) + { + fprintf(stderr, "%s", str); + fflush(stderr); + } +} + +uint64_t GetMachineIdent() +{ + uint64_t ret = MachineIdent_Linux; + +#if defined(_M_ARM) || defined(__arm__) + ret |= MachineIdent_Arch_ARM; +#else + ret |= MachineIdent_Arch_x86; +#endif + +#if ENABLED(RDOC_X64) + ret |= MachineIdent_64bit; +#else + ret |= MachineIdent_32bit; +#endif + + return ret; +} +}; diff --git a/renderdoc/os/posix/bsd/bsd_threading.cpp b/renderdoc/os/posix/bsd/bsd_threading.cpp new file mode 100644 index 000000000..1e70759f5 --- /dev/null +++ b/renderdoc/os/posix/bsd/bsd_threading.cpp @@ -0,0 +1,44 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2023 Baldur Karlsson + * + * 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. + ******************************************************************************/ + +#include "os/os_specific.h" + +#include +#include + +double Timing::GetTickFrequency() +{ + return 1000000.0; +} + +uint64_t Timing::GetTick() +{ + timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return uint64_t(ts.tv_sec) * 1000000000ULL + uint32_t(ts.tv_nsec & 0xffffffff); +} + +void Threading::SetCurrentThreadName(const rdcstr &name) +{ +} diff --git a/renderdoc/os/posix/linux/linux_callstack.cpp b/renderdoc/os/posix/linux/linux_callstack.cpp index c266f126b..e97d235b9 100644 --- a/renderdoc/os/posix/linux/linux_callstack.cpp +++ b/renderdoc/os/posix/linux/linux_callstack.cpp @@ -66,11 +66,11 @@ private: { void *addrs_ptr[ARRAY_COUNT(addrs)]; - size_t ret = backtrace(addrs_ptr, ARRAY_COUNT(addrs)); + int ret = backtrace(addrs_ptr, ARRAY_COUNT(addrs)); numLevels = 0; if(ret > 0) - numLevels = ret; + numLevels = (size_t)ret; int offs = 0; // if we want to trim levels of the stack, we can do that here diff --git a/renderdoc/os/posix/linux/linux_hook.cpp b/renderdoc/os/posix/linux/linux_hook.cpp index 3148d364c..7138904d8 100644 --- a/renderdoc/os/posix/linux/linux_hook.cpp +++ b/renderdoc/os/posix/linux/linux_hook.cpp @@ -38,11 +38,7 @@ Threading::CriticalSection libLock; -#ifdef __linux__ RDOC_EXTERN_CONFIG(bool, Linux_Debug_PtraceLogging); -#else -#define Linux_Debug_PtraceLogging() false -#endif static std::map> libraryCallbacks; static rdcarray libraryHooks; @@ -120,7 +116,6 @@ int direct_setenv(const char *name, const char *value, int overwrite); // The other variants all forward to one of those - the 'l' cases unroll the va_args first before // calling onwards -#ifndef __FreeBSD__ // TODO: environ bug #define GET_EXECL_PARAMS(has_e) \ va_list args; \ va_start(args, arg); \ @@ -199,7 +194,6 @@ __attribute__((visibility("default"))) int execvp(const char *pathname, char *co return execvpe(pathname, argv, environ); } -#endif // __FreeBSD__ __attribute__((visibility("default"))) int execve(const char *pathname, char *const argv[], char *const envp[]) diff --git a/renderdoc/os/posix/linux/linux_threading.cpp b/renderdoc/os/posix/linux/linux_threading.cpp index a47f68f51..ebd57602f 100644 --- a/renderdoc/os/posix/linux/linux_threading.cpp +++ b/renderdoc/os/posix/linux/linux_threading.cpp @@ -24,9 +24,7 @@ #include "os/os_specific.h" -#ifdef __linux__ #include -#endif #include #include @@ -44,7 +42,5 @@ uint64_t Timing::GetTick() void Threading::SetCurrentThreadName(const rdcstr &name) { -#ifdef __linux__ prctl(PR_SET_NAME, (unsigned long)name.c_str(), 0, 0, 0); -#endif } diff --git a/renderdoccmd/CMakeLists.txt b/renderdoccmd/CMakeLists.txt index 3cac9e71f..918a33618 100644 --- a/renderdoccmd/CMakeLists.txt +++ b/renderdoccmd/CMakeLists.txt @@ -50,7 +50,7 @@ elseif(ENABLE_GGP) elseif(UNIX) list(APPEND sources renderdoccmd_linux.cpp) - if(FreeBSD) + if(FREEBSD) list(APPEND libraries PRIVATE -L/usr/local/lib) list(APPEND includes /usr/local/include) endif()