#if 0 set -xe trap 'rm -f a.out' EXIT g++ -std=c++17 -rdynamic fn_hook.cc -Wno-ignored-attributes && ./a.out clang++ -std=c++17 -rdynamic fn_hook.cc && ./a.out g++ -std=c++20 -rdynamic fn_hook.cc -Wno-ignored-attributes && ./a.out clang++ -std=c++20 -rdynamic fn_hook.cc && ./a.out exit 0 #endif #include // Function wrapper hook with deducing noexcept. // // This is an experiment providing a function wrapper which automatically // deduces if the wrapped function is marked noexcept. The information is then // generated through the whole wrapper chain. // // To achieve this, a template specialization for function pointers is defined // over a non implemented primary template. // -- FN HOOK ------------------------------------------------------------------ namespace detail { template struct hook; template struct hook { // Function pointer type. using fn_t = Ret (*)(Args...) noexcept(IsNoexcept); constexpr hook() noexcept = default; // Forwarding wrapper implementation. This also initializes the function // pointer to the next function to call, on the first invocation (non-thread // safe). Alternatively, the function pointer could be retrieved during // construction, but then the construction and first invocation of the wrapped // function must be coordinated. Ret operator()(Args... args) noexcept(IsNoexcept) { if (ptr == nullptr) { init(); } return ptr(args...); } // Initialize function pointer to next function to call. void init() noexcept { ptr = reinterpret_cast(::dlsym(RTLD_NEXT, Derived::sym)); } // Remember whether this hook was noexcept, to be used for the outer wrapper. static constexpr bool is_noexcept() { return IsNoexcept; } private: fn_t ptr = nullptr; }; } // namespace detail // Used to ensure that global function hook objects are initialized during // compile time; constant initialization, which however allows mutation during // runtime, compared to constexpr object, which is non-mutable. #if defined(__cpp_constinit) #define CONSTINIT constinit #else #define CONSTINIT #endif // Helper macro to generate boilerplate code for function wrapper hook. #define WRAP(ret, fn, ...) \ namespace detail { \ static CONSTINIT struct hook_##fn : hook { \ static constexpr const char* sym = #fn; \ } fn; \ } \ \ extern "C" ret fn(__VA_ARGS__) noexcept(detail::hook_##fn::is_noexcept()) // -- EXAMPLE ------------------------------------------------------------------ #include #include WRAP(ssize_t, write, int fd, const void* buf, size_t sz) { puts("wrapper write(..)"); return detail::write(fd, buf, sz); } // glibc: write is not marked noexcept, as it is a cancellation point. static_assert(!detail::hook_write::is_noexcept()); WRAP(pid_t, getpid) { puts("wrapper getpid()"); return detail::getpid(); } // glibc: getpid is marked noexcept. static_assert(detail::hook_getpid::is_noexcept()); int main() { const char msg[] = "hello moose\n"; write(STDOUT_FILENO, msg, sizeof(msg)); printf("pid = %d\n", getpid()); }