#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 <dlfcn.h>
// 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 <typename Derived, typename FnPtr>
struct hook;
template <typename Derived, typename Ret, typename... Args, bool IsNoexcept>
struct hook<Derived, Ret (*)(Args...) noexcept(IsNoexcept)> {
// 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<fn_t>(::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<hook_##fn, decltype(&::fn)> { \
static constexpr const char* sym = #fn; \
} fn; \
} \
\
extern "C" ret fn(__VA_ARGS__) noexcept(detail::hook_##fn::is_noexcept())
// -- EXAMPLE ------------------------------------------------------------------
#include <stdio.h>
#include <unistd.h>
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());
}