From 90fe47efbc1c04348f65186455aaa3bef2dbccae Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Mon, 6 Dec 2021 16:51:48 -0500 Subject: [PATCH] Fix: generate probe registration constructor as a C++ constuctor MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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=) 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=) at lttng-probes.c:153 #1 lttng_ust_probe_register (desc=0x7fa94fe9dc80 ) 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 Signed-off-by: Mathieu Desnoyers --- include/lttng/ust-compiler.h | 52 ++++++++++++++++++++++++++++ include/lttng/ust-tracepoint-event.h | 24 ++++++++++--- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/include/lttng/ust-compiler.h b/include/lttng/ust-compiler.h index eb201bdc..c938361d 100644 --- a/include/lttng/ust-compiler.h +++ b/include/lttng/ust-compiler.h @@ -86,4 +86,56 @@ 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 */ diff --git a/include/lttng/ust-tracepoint-event.h b/include/lttng/ust-tracepoint-event.h index c68f7d98..1edb50c1 100644 --- a/include/lttng/ust-tracepoint-event.h +++ b/include/lttng/ust-tracepoint-event.h @@ -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 + 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 -- 2.34.1