summaryrefslogtreecommitdiff
path: root/fn_hook.cc
blob: bf6d8f28eb390de819dcdd812beb6080e5976fe8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#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());
}