Skip to content

Instantly share code, notes, and snippets.

@aderumier
Created March 22, 2023 22:32
Show Gist options
  • Save aderumier/3d11f22dfe42c6e9ac866f1ab775b2eb to your computer and use it in GitHub Desktop.
Save aderumier/3d11f22dfe42c6e9ac866f1ab775b2eb to your computer and use it in GitHub Desktop.
include/qemu/rcu.h | 19 ++++++++++++
util/rcu.c | 76 +++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 94 insertions(+), 1 deletion(-)
diff --git a/include/qemu/rcu.h b/include/qemu/rcu.h
index b063c6fde8..7ab8b899f6 100644
--- a/include/qemu/rcu.h
+++ b/include/qemu/rcu.h
@@ -196,6 +196,25 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(RCUReadAuto, rcu_read_auto_unlock)
void rcu_add_force_rcu_notifier(Notifier *n);
void rcu_remove_force_rcu_notifier(Notifier *n);
+/* This macro may be used to block constant propagation that lets the compiler
+ * detect a possible NULL dereference on a variable resulting from an explicit
+ * assignment in an impossible check. Sometimes a function is called which does
+ * safety checks and returns NULL if safe conditions are not met. The place
+ * where it's called cannot hit this condition and dereferencing the pointer
+ * without first checking it will make the compiler emit a warning about a
+ * "potential null pointer dereference" which is hard to work around. This
+ * macro "washes" the pointer and prevents the compiler from emitting tests
+ * branching to undefined instructions. It may only be used when the developer
+ * is absolutely certain that the conditions are guaranteed and that the
+ * pointer passed in argument cannot be NULL by design.
+ */
+#define ALREADY_CHECKED(p) do { asm("" : "=rm"(p) : "0"(p)); } while (0)
+
+/* same as above but to be used to pass the input value to the output but
+ * without letting the compiler know about its initial properties.
+ */
+#define DISGUISE(v) ({ typeof(v) __v = (v); ALREADY_CHECKED(__v); __v; })
+
#ifdef __cplusplus
}
#endif
diff --git a/util/rcu.c b/util/rcu.c
index b6d6c71cff..5dc0fa5f58 100644
--- a/util/rcu.c
+++ b/util/rcu.c
@@ -35,6 +35,13 @@
#if defined(CONFIG_MALLOC_TRIM)
#include <malloc.h>
#endif
+#if (defined(__GNU_LIBRARY__) && (__GLIBC__ > 2 || __GLIBC__ == 2 && __GLIBC_MINOR__ >= 8))
+#define GLIBC_HAVE_MALLOC_TRIM
+#endif
+/* glibc 2.33 provides mallinfo2() that overcomes mallinfo()'s type limitations */
+#if (defined(__GNU_LIBRARY__) && (__GLIBC__ > 2 || __GLIBC__ == 2 && __GLIBC_MINOR__ >= 33))
+#define GLIBC_HAVE_MALLINFO2
+#endif
/*
* Global grace period counter. Bit 0 is always one in rcu_gp_ctr.
@@ -50,6 +57,9 @@ static int in_drain_call_rcu;
static QemuMutex rcu_registry_lock;
static QemuMutex rcu_sync_lock;
+static int using_default_allocator = 1;
+static int(*my_mallctl)(const char *, void *, size_t *, void *, size_t) = NULL;
+
/*
* Check whether a quiescent state was crossed between the beginning of
* update_counter_and_wait and now.
@@ -256,7 +266,8 @@ static void *call_rcu_thread(void *opaque)
n = qatomic_read(&rcu_call_count);
if (n == 0) {
#if defined(CONFIG_MALLOC_TRIM)
- malloc_trim(4 * 1024 * 1024);
+ if (!using_default_allocator)
+ malloc_trim(4 * 1024 * 1024);
#endif
qemu_event_wait(&rcu_call_ready_event);
}
@@ -445,11 +456,74 @@ static void rcu_init_child(void)
}
#endif
+/* Tries to retrieve the address of the first occurrence symbol <name>.
+ * Note that NULL in return is not always an error as a symbol may have that
+ * address in special situations.
+ */
+static void *get_sym_curr_addr(const char *name)
+{
+ void *ptr = NULL;
+
+#ifdef RTLD_DEFAULT
+ if (!build_is_static)
+ ptr = dlsym(RTLD_DEFAULT, name);
+#endif
+ return ptr;
+}
+
+/* check if we're using the same allocator as the one that provides
+ * malloc_trim() and mallinfo(). The principle is that on glibc, both
+ * malloc_trim() and mallinfo() are provided, and using mallinfo() we
+ * can check if malloc() is performed through glibc or any other one
+ * the executable was linked against (e.g. jemalloc). Prior to this we
+ * have to check whether we're running on jemalloc by verifying if the
+ * mallctl() function is provided. Its pointer will be used later.
+ */
+static void detect_allocator(void)
+{
+#if defined(__ELF__)
+ extern int mallctl(const char *, void *, size_t *, void *, size_t) __attribute__((weak));
+
+ my_mallctl = mallctl;
+#endif
+ if (!my_mallctl)
+ my_mallctl = get_sym_curr_addr("mallctl");
+
+ using_default_allocator = (my_mallctl == NULL);
+
+ if (!my_mallctl) {
+#if defined(GLIBC_HAVE_MALLOC_TRIM)
+
+#ifdef GLIBC_HAVE_MALLINFO2
+ struct mallinfo2 mi1, mi2;
+ void *ptr;
+ mi1 = mallinfo2();
+ ptr = DISGUISE(malloc(1));
+ mi2 = mallinfo2();
+
+#else
+ struct mallinfo mi1, mi2;
+ void *ptr;
+ mi1 = mallinfo();
+ ptr = DISGUISE(malloc(1));
+ mi2 = mallinfo();
+
+#endif
+
+ free(DISGUISE(ptr));
+ using_default_allocator = !!memcmp(&mi1, &mi2, sizeof(mi1));
+#endif
+ }
+}
+
static void __attribute__((__constructor__)) rcu_init(void)
{
smp_mb_global_init();
#ifdef CONFIG_POSIX
pthread_atfork(rcu_init_lock, rcu_init_unlock, rcu_init_child);
+#endif
+#if defined(CONFIG_MALLOC_TRIM)
+ detect_allocator();
#endif
rcu_init_complete();
}
--
2.30.2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment