Fix: generate probe registration constructor as a C++ constuctor
authorJérémie Galarneau <jeremie.galarneau@efficios.com>
Mon, 6 Dec 2021 21:51:48 +0000 (16:51 -0500)
committerMathieu Desnoyers <mathieu.desnoyers@efficios.com>
Thu, 9 Dec 2021 19:04:50 +0000 (14:04 -0500)
Observed issue
==============

Applications which transitively dlopen() a library which, in turn,
dlopen() providers crash when they are compiled with clang or
if LTTNG_UST_ALLOCATE_COMPOUND_LITERAL_ON_HEAP is defined.

  Core was generated by `././myapp.exe'.
  Program terminated with signal SIGSEGV, Segmentation fault.
  #0  0x00007fa94f860bc2 in check_event_provider (probe_desc=<optimized out>) at lttng-probes.c:153
  153 if (!check_type_provider(field->type)) {
  [Current thread is 1 (Thread 0x7fa94fcbc740 (LWP 511754))]

  (gdb) bt
  #0  0x00007fa94f860bc2 in check_event_provider (probe_desc=<optimized out>) at lttng-probes.c:153
  #1  lttng_ust_probe_register (desc=0x7fa94fe9dc80 <lttng_ust__probe_desc___embedded_sys>)
      at lttng-probes.c:242
  #2  0x00007fa94fe9ba3c in lttng_ust__tracepoints__ptrs_destroy ()
      at /usr/include/lttng/tracepoint.h:590
  #3  0x00007fa94fedfe2e in call_init () from /lib64/ld-linux-x86-64.so.2
  #4  0x00007fa94fedff1c in _dl_init () from /lib64/ld-linux-x86-64.so.2
  #5  0x00007fa94fdf7d45 in _dl_catch_exception () from /usr/lib/libc.so.6
  #6  0x00007fa94fee420a in dl_open_worker () from /lib64/ld-linux-x86-64.so.2
  #7  0x00007fa94fdf7ce8 in _dl_catch_exception () from /usr/lib/libc.so.6
  #8  0x00007fa94fee39bb in _dl_open () from /lib64/ld-linux-x86-64.so.2
  #9  0x00007fa94fe8d36c in ?? () from /usr/lib/libdl.so.2
  #10 0x00007fa94fdf7ce8 in _dl_catch_exception () from /usr/lib/libc.so.6
  #11 0x00007fa94fdf7db3 in _dl_catch_error () from /usr/lib/libc.so.6
  #12 0x00007fa94fe8db99 in ?? () from /usr/lib/libdl.so.2
  #13 0x00007fa94fe8d3f8 in dlopen () from /usr/lib/libdl.so.2
  #14 0x00007fa94fecc647 in mon_constructeur () at mylib.cpp:20
  #15 0x00007fa94fedfe2e in call_init () from /lib64/ld-linux-x86-64.so.2
  #16 0x00007fa94fedff1c in _dl_init () from /lib64/ld-linux-x86-64.so.2
  #17 0x00007fa94fdf7d45 in _dl_catch_exception () from /usr/lib/libc.so.6
  #18 0x00007fa94fee420a in dl_open_worker () from /lib64/ld-linux-x86-64.so.2
  #19 0x00007fa94fdf7ce8 in _dl_catch_exception () from /usr/lib/libc.so.6
  #20 0x00007fa94fee39bb in _dl_open () from /lib64/ld-linux-x86-64.so.2
  #21 0x00007fa94fe8d36c in ?? () from /usr/lib/libdl.so.2
  #22 0x00007fa94fdf7ce8 in _dl_catch_exception () from /usr/lib/libc.so.6
  #23 0x00007fa94fdf7db3 in _dl_catch_error () from /usr/lib/libc.so.6
  #24 0x00007fa94fe8db99 in ?? () from /usr/lib/libdl.so.2
  #25 0x00007fa94fe8d3f8 in dlopen () from /usr/lib/libdl.so.2
  #26 0x00005594f478c18c in main ()

Cause
=====

Building tracepoint instrumentation as C++ using clang causes
LTTNG_UST_ALLOCATE_COMPOUND_LITERAL_ON_HEAP to be defined due to a
compiler version detection problem addressed by another patch.

However, building with LTTNG_UST_ALLOCATE_COMPOUND_LITERAL_ON_HEAP
defined still results in the crash.

When LTTNG_UST_ALLOCATE_COMPOUND_LITERAL_ON_HEAP is defined, the
lttng_ust_event_field lttng_ust__event_fields__[...] structure is
initialized by dynamically-allocating field structures for the various
fields.

As the initialization can't be performed statically, it is performed at
run-time _after_ the execution of the library constructors has
completed.

Moreover, the generated initialization
function of the provider (lttng_ust__events_init__[...]) is declared as being a library
constructor. Hence, this causes it to run before the
tracepoint fields structures has a chance to be initialized.

This all results in a NULL pointer dereference during the validation of
the fields.

Solution
========

When building providers as C++, the initialization function is defined
as the constructor of a class. This class is, in turn, instantiated in
an anonymous namespace.

For the purposes of this patch, the use of an anonymous namespace is
equivalent to declaring the instance as 'static', but it is preferred in
C++11.

Known drawbacks
===============

None.

References
==========

A reproducer is available:
https://github.com/jgalar/ust-clang-reproducer

Problem initially reported on dotnet/runtime's issue tracker:
https://github.com/dotnet/runtime/issues/62398

Relevant LTTng-UST issue:
https://bugs.lttng.org/issues/1339

Fixes: #1339
Change-Id: I51cfbe74729bd45e2613a30bc8de17e08ea8233d
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
include/lttng/ust-compiler.h
include/lttng/ust-tracepoint-event.h

index eb201bdc93c791f142d2cbc0aa7a86fd90d2472d..c938361d496ed84e1b924f0e69da489b8e3893f9 100644 (file)
     typedef char lttng_ust_static_assert_##c_identifier_msg[2*!!(predicate)-1]
 #endif
 
+/*
+ * Wrap constructor and destructor functions to invoke them as functions with
+ * the constructor/destructor GNU C attributes when building as C, or as the
+ * constructor/destructor of a variable defined within an anonymous namespace
+ * when building as C++.
+ */
+#ifdef __cplusplus
+#define LTTNG_UST_DECLARE_CONSTRUCTOR_DESTRUCTOR(name, constructor_func,        \
+                                                destructor_func, ...)          \
+namespace lttng {                                                               \
+namespace ust {                                                                 \
+namespace details {                                                             \
+class LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust_constructor_destructor_,           \
+                                  name) {                                      \
+public:                                                                         \
+       LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust_constructor_destructor_,         \
+                                    name)() __VA_ARGS__        \
+       {                                                                       \
+               constructor_func();                                             \
+       }                                                                       \
+       ~LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust_constructor_destructor_,        \
+                                      name)() __VA_ARGS__       \
+       {                                                                       \
+               destructor_func();                                              \
+       }                                                                       \
+};                                                                              \
+}                                                                               \
+}                                                                               \
+}                                                                               \
+                                                                                \
+namespace {                                                                     \
+const lttng::ust::details::LTTNG_UST__TP_COMBINE_TOKENS(                        \
+       lttng_ust_constructor_destructor_, name)                                \
+               LTTNG_UST__TP_COMBINE_TOKENS(name, registration_instance);      \
+}
+#else /* __cplusplus */
+#define LTTNG_UST_DECLARE_CONSTRUCTOR_DESTRUCTOR(name, constructor_func,             \
+                                                destructor_func, ...)               \
+       static void LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust_constructor_, name)(void) \
+               __attribute__((constructor)) __VA_ARGS__;            \
+       static void LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust_constructor_, name)(void) \
+       {                                                                            \
+               constructor_func();                                                  \
+       }                                                                            \
+       static void LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust_destructor_, name)(void)  \
+               __attribute__((destructor)) __VA_ARGS__;             \
+       static void LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust_destructor_, name)(void)  \
+       {                                                                            \
+               destructor_func();                                                   \
+       }
+#endif
+
 #endif /* _LTTNG_UST_COMPILER_H */
index c68f7d98cb1748b5e540946ea87cb7bdffdaf160..1edb50c13d295cac4d0f973223dc1c3453ee285e 100644 (file)
@@ -1169,13 +1169,24 @@ static struct lttng_ust_registered_probe *LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust
  * linking the probe statically.
  *
  * Register refcount is protected by libc dynamic loader mutex.
+ *
+ * Note that when building this code as C++, the definition of constructors
+ * and destructors invoking the registration and unregistration functions
+ * must be performed _after_ the initialization of the probes.
+ *
+ * This is because we rely on the order of initialization of static variables
+ * and anonymous namespaces (their order of declaration) to ensure probes are
+ * fully initialized, see
+ * https://en.cppreference.com/w/cpp/language/initialization. This is especially
+ * important when LTTNG_UST_ALLOCATE_COMPOUND_LITERAL_ON_HEAP is defined as
+ * compound literals are then dynamically initialized.
  */
 
 /* Reset all macros within LTTNG_UST_TRACEPOINT_EVENT */
 #include <lttng/ust-tracepoint-event-reset.h>
+
 static void
-LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust__events_init__, LTTNG_UST_TRACEPOINT_PROVIDER)(void)
-       lttng_ust_notrace __attribute__((constructor));
+LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust__events_init__, LTTNG_UST_TRACEPOINT_PROVIDER)(void) lttng_ust_notrace;
 static void
 LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust__events_init__, LTTNG_UST_TRACEPOINT_PROVIDER)(void)
 {
@@ -1204,8 +1215,7 @@ LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust__events_init__, LTTNG_UST_TRACEPOINT_PROV
 }
 
 static void
-LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust__events_exit__, LTTNG_UST_TRACEPOINT_PROVIDER)(void)
-       lttng_ust_notrace __attribute__((destructor));
+LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust__events_exit__, LTTNG_UST_TRACEPOINT_PROVIDER)(void) lttng_ust_notrace;
 static void
 LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust__events_exit__, LTTNG_UST_TRACEPOINT_PROVIDER)(void)
 {
@@ -1217,6 +1227,12 @@ LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust__events_exit__, LTTNG_UST_TRACEPOINT_PROV
        LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust__probe_register_cookie___, LTTNG_UST_TRACEPOINT_PROVIDER) = NULL;
 }
 
+LTTNG_UST_DECLARE_CONSTRUCTOR_DESTRUCTOR(
+       LTTNG_UST_TRACEPOINT_PROVIDER,
+       LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust__events_init__, LTTNG_UST_TRACEPOINT_PROVIDER),
+       LTTNG_UST__TP_COMBINE_TOKENS(lttng_ust__events_exit__, LTTNG_UST_TRACEPOINT_PROVIDER),
+       lttng_ust_notrace)
+
 /*
  * LTTNG_UST_TRACEPOINT_PROVIDER_HIDDEN_DEFINITION: Define this before
  * including a tracepoint instrumentation header to hide symbols
This page took 0.028437 seconds and 4 git commands to generate.