From patchwork Thu Feb 14 17:08:52 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vincent Whitchurch X-Patchwork-Id: 31480 Received: (qmail 95528 invoked by alias); 14 Feb 2019 17:09:02 -0000 Mailing-List: contact libc-alpha-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: libc-alpha-owner@sourceware.org Delivered-To: mailing list libc-alpha@sourceware.org Received: (qmail 93952 invoked by uid 89); 14 Feb 2019 17:09:02 -0000 Authentication-Results: sourceware.org; auth=none X-Spam-SWARE-Status: No, score=-26.9 required=5.0 tests=BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, RCVD_IN_DNSWL_NONE, SPF_PASS autolearn=ham version=3.3.2 spammy=Filters, linkedin, nearly, ld_preload X-HELO: bastet.se.axis.com X-Axis-User: NO X-Axis-NonUser: YES From: Vincent Whitchurch To: libc-alpha@sourceware.org Cc: Vincent Whitchurch Subject: [PATCH] Add LD_PRELOAD_INIT_EARLY [BZ #14379] Date: Thu, 14 Feb 2019 18:08:52 +0100 Message-Id: <20190214170852.30862-1-vincent.whitchurch@axis.com> Currently, DSOs preloaded with LD_PRELOAD are initialized after linked-in DSOs, unless dependencies require otherwise. However, in some cases it is desirable that preloaded DSO are initialized before linked-in DSOs (unless dependencies require otherwise). For example, when malloc is overloaded using a preloaded DSO for the purpose of heap profiling, we ideally want the preloaded DSO to be initialized before other DSOs so that it has a chance to set up its accounting code before their initializers are called. Changing the default behaviour could lead to breakage, so add a new environment variable, LD_PRELOAD_INIT_EARLY, the presence of which will ask preloaded libraries to be initialized as early as possible and finalized as late as possible. If multiple DSOs are preloaded, DSOs earlier on the LD_PRELOAD list will be initalized earlier and finalized later than DSOs present later on the list. Note that dependencies are still taken into account: DSOs which the preloaded DSO depends on are correctly initalized before it and finalized after it. Add tests to test both the current, default behaviour and the behaviour when the new environment variable is set. Test suite run on x86-64. 2019-02-14 Vincent Whitchurch [BZ #14379] * elf/Makefile (tests): Add tst-preload-initorder and tst-preload-initorder-early and related rules. * elf/dl-open.c: Pass new argument to _dl_map_object_deps(). * elf/dl-deps.c (_dl_map_object_deps): Based on new parameter, initialize preloaded DSOs earlier and mark them for late finalization. * elf/dl-fini.c (_dl_fini): Finalize DSOs marked for late finalization later. * elf/rtld.c: Handle environment variable LD_PRELOAD_INIT_EARLY and pass new argument to _dl_map_object_deps(). * elf/tst-preload-initorder.c: New file. * elf/tst-preload-initorder.exp: Likewise. * elf/tst-preload-initorder-early.c: Likewise. * elf/tst-preload-initorder-early.exp: Likewise. * include/link.h (struct link_map): Add new field l_late_fini for late finalization. diff --git a/elf/Makefile b/elf/Makefile index 5c625b89fa3..27891abd97e 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -183,6 +183,7 @@ tests += restest1 preloadtest loadfail multiload origtest resolvfail \ tst-unique1 tst-unique2 $(if $(CXX),tst-unique3 tst-unique4 \ tst-nodelete) \ tst-initorder tst-initorder2 tst-relsort1 tst-null-argv \ + tst-preload-initorder tst-preload-initorder-early \ tst-tlsalign tst-tlsalign-extern tst-nodelete-opened \ tst-nodelete2 tst-audit11 tst-audit12 tst-dlsym-error tst-noload \ tst-latepthread tst-tls-manydynamic tst-nodelete-dlclose \ @@ -266,7 +267,7 @@ modules-names = testobj1 testobj2 testobj3 testobj4 testobj5 testobj6 \ tst-initordera2 tst-initorderb2 \ tst-initordera3 tst-initordera4 \ tst-initorder2a tst-initorder2b tst-initorder2c \ - tst-initorder2d \ + tst-initorder2d tst-initorder2x tst-initorder2y \ tst-relsort1mod1 tst-relsort1mod2 tst-array2dep \ tst-array5dep tst-null-argv-lib \ tst-tlsalign-lib tst-nodelete-opened-lib tst-nodelete2mod \ @@ -369,6 +370,8 @@ tests-special += $(objpfx)order-cmp.out $(objpfx)tst-array1-cmp.out \ $(objpfx)tst-array4-cmp.out $(objpfx)tst-array5-cmp.out \ $(objpfx)tst-array5-static-cmp.out $(objpfx)order2-cmp.out \ $(objpfx)tst-initorder-cmp.out \ + $(objpfx)tst-preload-initorder-cmp.out \ + $(objpfx)tst-preload-initorder-early-cmp.out \ $(objpfx)tst-initorder2-cmp.out $(objpfx)tst-unused-dep.out \ $(objpfx)tst-unused-dep-cmp.out endif @@ -773,6 +776,17 @@ $(objpfx)preloadtest.out: $(preloadtest-preloads:%=$(objpfx)%.so) preloadtest-ENV = \ LD_PRELOAD=$(subst $(empty) ,:,$(strip $(preloadtest-preloads:=.so))) +tst-preload-initorder-preloads = tst-initorder2x tst-initorder2y +$(objpfx)tst-preload-initorder $(objpfx)tst-preload-initorder-early: \ + $(objpfx)tst-initorder2c.so +$(objpfx)tst-preload-initorder.out $(objpfx)tst-preload-initorder-early.out: \ + $(tst-preload-initorder-preloads:%=$(objpfx)%.so) +tst-preload-initorder-ENV = \ + LD_PRELOAD=$(subst $(empty) ,:,$(strip $(tst-preload-initorder-preloads:=.so))) +tst-preload-initorder-early-ENV = \ + LD_PRELOAD_INIT_EARLY=1 \ + LD_PRELOAD=$(subst $(empty) ,:,$(strip $(tst-preload-initorder-preloads:=.so))) + $(objpfx)loadfail: $(libdl) LDFLAGS-loadfail = -rdynamic @@ -1343,19 +1357,33 @@ $(objpfx)tst-initorder-cmp.out: tst-initorder.exp $(objpfx)tst-initorder.out cmp $^ > $@; \ $(evaluate-test) +$(objpfx)tst-preload-initorder-cmp.out: tst-preload-initorder.exp \ + $(objpfx)tst-preload-initorder.out + cmp $^ > $@; \ + $(evaluate-test) + +$(objpfx)tst-preload-initorder-early-cmp.out: \ + tst-preload-initorder-early.exp $(objpfx)tst-preload-initorder-early.out + cmp $^ > $@; \ + $(evaluate-test) + $(objpfx)tst-initorder2: $(objpfx)tst-initorder2a.so $(objpfx)tst-initorder2d.so $(objpfx)tst-initorder2c.so $(objpfx)tst-initorder2a.so: $(objpfx)tst-initorder2b.so $(objpfx)tst-initorder2b.so: $(objpfx)tst-initorder2c.so $(objpfx)tst-initorder2c.so: $(objpfx)tst-initorder2d.so +$(objpfx)tst-initorder2x.so: $(objpfx)tst-initorder2d.so +$(objpfx)tst-initorder2y.so: $(objpfx)tst-initorder2d.so LDFLAGS-tst-initorder2 = $(no-as-needed) LDFLAGS-tst-initorder2a.so = $(no-as-needed) LDFLAGS-tst-initorder2b.so = $(no-as-needed) LDFLAGS-tst-initorder2c.so = $(no-as-needed) +LDFLAGS-tst-initorder2x.so = $(no-as-needed) +LDFLAGS-tst-initorder2y.so = $(no-as-needed) define o-iterator-doit $(objpfx)tst-initorder2$o.os: tst-initorder2.c; \ $$(compile-command.c) -DNAME=\"$o\" endef -object-suffixes-left := a b c d +object-suffixes-left := a b c d x y include $(o-iterator) $(objpfx)tst-initorder2-cmp.out: tst-initorder2.exp $(objpfx)tst-initorder2.out diff --git a/elf/dl-deps.c b/elf/dl-deps.c index e12c353158a..128163d1af3 100644 --- a/elf/dl-deps.c +++ b/elf/dl-deps.c @@ -139,7 +139,8 @@ cannot load auxiliary `%s' because of empty dynamic string token " \ __result; }) static void -preload (struct list *known, unsigned int *nlist, struct link_map *map) +preload (struct list *known, unsigned int *nlist, struct link_map *map, + int late_fini) { known[*nlist].done = 0; known[*nlist].map = map; @@ -150,12 +151,13 @@ preload (struct list *known, unsigned int *nlist, struct link_map *map) already put in the search list and avoid adding duplicate elements later in the list. */ map->l_reserved = 1; + map->l_late_fini = late_fini; } void _dl_map_object_deps (struct link_map *map, struct link_map **preloads, unsigned int npreloads, - int trace_mode, int open_mode) + int trace_mode, int open_mode, int preload_init_early) { struct list *known = __alloca (sizeof *known * (1 + npreloads + 1)); struct list *runp, *tail; @@ -170,11 +172,11 @@ _dl_map_object_deps (struct link_map *map, nlist = 0; /* First load MAP itself. */ - preload (known, &nlist, map); + preload (known, &nlist, map, 0); /* Add the preloaded items after MAP but before any of its dependencies. */ for (i = 0; i < npreloads; ++i) - preload (known, &nlist, preloads[i]); + preload (known, &nlist, preloads[i], preload_init_early); /* Terminate the lists. */ known[nlist - 1].next = NULL; @@ -587,8 +589,32 @@ Filters not supported with LD_TRACE_PRELINKING")); /* Sort the initializer list to take dependencies into account. The binary itself will always be initialize last. */ - memcpy (l_initfini, map->l_searchlist.r_list, + if (__glibc_unlikely (preload_init_early && npreloads + && nlist > npreloads + 1)) { + if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS)) + _dl_debug_printf ("\nearly preload init requested\n\n"); + + /* The binary itself will always be initialized last. */ + memcpy (&l_initfini[0], &map->l_searchlist.r_list[0], + 1 * sizeof (struct link_map *)); + + /* Non-preloaded DSOs will be initialized after preloaded DSOs + * (unless dependencies mandate a different order). */ + unsigned int nnormal = nlist - npreloads - 1; + memcpy (&l_initfini[1], &map->l_searchlist.r_list[1 + npreloads], + nnormal * sizeof (struct link_map *)); + + /* Insert the preloads in reverse order so that preloads earlier on the + * list are initialized earlier (again, unless dependencies require + * otherwise). */ + struct link_map **base = &map->l_searchlist.r_list[1]; + for (i = 0; i < npreloads; i++) + l_initfini[1 + nnormal + i] = base[npreloads - 1 - i]; + } else { + memcpy (l_initfini, map->l_searchlist.r_list, nlist * sizeof (struct link_map *)); + } + /* We can skip looking for the binary itself which is at the front of the search list. */ _dl_sort_maps (&l_initfini[1], nlist - 1, NULL, false); diff --git a/elf/dl-fini.c b/elf/dl-fini.c index 1e55d398149..39fce569f27 100644 --- a/elf/dl-fini.c +++ b/elf/dl-fini.c @@ -68,22 +68,45 @@ _dl_fini (void) struct link_map *maps[nloaded]; unsigned int i; - struct link_map *l; + bool want_late_fini = false; + struct link_map *l, *last = NULL; assert (nloaded != 0 || GL(dl_ns)[ns]._ns_loaded == NULL); for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next) - /* Do not handle ld.so in secondary namespaces. */ - if (l == l->l_real) - { - assert (i < nloaded); - - maps[i] = l; - l->l_idx = i; - ++i; - - /* Bump l_direct_opencount of all objects so that they - are not dlclose()ed from underneath us. */ - ++l->l_direct_opencount; - } + { + last = l; + if (__glibc_unlikely (l->l_late_fini)) + want_late_fini = true; + /* Do not handle ld.so in secondary namespaces. */ + if (l == l->l_real && !l->l_late_fini) + { + assert (i < nloaded); + + maps[i] = l; + l->l_idx = i; + ++i; + + /* Bump l_direct_opencount of all objects so that they + are not dlclose()ed from underneath us. */ + ++l->l_direct_opencount; + } + } + + /* Late finalized DSOs should be processed in reverse order. */ + if (__glibc_unlikely (want_late_fini)) + for (l = last; l != NULL; l = l->l_prev) + if (l == l->l_real && l->l_late_fini) + { + assert (i < nloaded); + + maps[i] = l; + l->l_idx = i; + ++i; + + /* Bump l_direct_opencount of all objects so that they + are not dlclose()ed from underneath us. */ + ++l->l_direct_opencount; + } + assert (ns != LM_ID_BASE || i == nloaded); assert (ns == LM_ID_BASE || i == nloaded || i == nloaded - 1); unsigned int nmaps = i; diff --git a/elf/dl-open.c b/elf/dl-open.c index 12a4f8b8539..ea2a5f90976 100644 --- a/elf/dl-open.c +++ b/elf/dl-open.c @@ -258,7 +258,7 @@ dl_open_worker (void *a) /* Load that object's dependencies. */ _dl_map_object_deps (new, NULL, 0, 0, - mode & (__RTLD_DLOPEN | RTLD_DEEPBIND | __RTLD_AUDIT)); + mode & (__RTLD_DLOPEN | RTLD_DEEPBIND | __RTLD_AUDIT), 0); /* So far, so good. Now check the versions. */ for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i) diff --git a/elf/rtld.c b/elf/rtld.c index c1cc1b01f2d..065e5c2de98 100644 --- a/elf/rtld.c +++ b/elf/rtld.c @@ -828,6 +828,8 @@ static const char *preloadlist attribute_relro; static int version_info attribute_relro; /* The preload list passed as a command argument. */ static const char *preloadarg attribute_relro; +/* Nonzero if preloaded objects should be initialized early. */ +static int preload_init_early attribute_relro; /* The LD_PRELOAD environment variable gives list of libraries separated by white space or colons that are loaded before the @@ -1786,7 +1788,8 @@ ERROR: '%s': cannot process note segment.\n", _dl_argv[0]); specified some libraries to load, these are inserted before the actual dependencies in the executable's searchlist for symbol resolution. */ HP_TIMING_NOW (start); - _dl_map_object_deps (main_map, preloads, npreloads, mode == trace, 0); + _dl_map_object_deps (main_map, preloads, npreloads, mode == trace, 0, + preload_init_early); HP_TIMING_NOW (stop); HP_TIMING_DIFF (diff, start, stop); HP_TIMING_ACCUM_NT (load_time, diff); @@ -2676,6 +2679,11 @@ process_envvars (enum mode *modep) } break; + case 18: + if (memcmp (envline, "PRELOAD_INIT_EARLY", 18) == 0) + preload_init_early = 1; + break; + case 20: /* The mode of the dynamic linker can be set. */ if (memcmp (envline, "TRACE_LOADED_OBJECTS", 20) == 0) diff --git a/elf/tst-preload-initorder-early.c b/elf/tst-preload-initorder-early.c new file mode 100644 index 00000000000..4305ba79763 --- /dev/null +++ b/elf/tst-preload-initorder-early.c @@ -0,0 +1 @@ +#include "tst-initorder.c" diff --git a/elf/tst-preload-initorder-early.exp b/elf/tst-preload-initorder-early.exp new file mode 100644 index 00000000000..a163dc3ec09 --- /dev/null +++ b/elf/tst-preload-initorder-early.exp @@ -0,0 +1,9 @@ +init: d +init: x +init: y +init: c +main +fini: c +fini: y +fini: x +fini: d diff --git a/elf/tst-preload-initorder.c b/elf/tst-preload-initorder.c new file mode 100644 index 00000000000..4305ba79763 --- /dev/null +++ b/elf/tst-preload-initorder.c @@ -0,0 +1 @@ +#include "tst-initorder.c" diff --git a/elf/tst-preload-initorder.exp b/elf/tst-preload-initorder.exp new file mode 100644 index 00000000000..a2ef4e69b07 --- /dev/null +++ b/elf/tst-preload-initorder.exp @@ -0,0 +1,9 @@ +init: d +init: c +init: y +init: x +main +fini: x +fini: y +fini: c +fini: d diff --git a/include/link.h b/include/link.h index 736e1d72aec..6d341a13b2d 100644 --- a/include/link.h +++ b/include/link.h @@ -202,6 +202,7 @@ struct link_map unsigned int l_free_initfini:1; /* Nonzero if l_initfini can be freed, ie. not allocated with the dummy malloc in ld.so. */ + unsigned int l_late_fini:1; /* Nonzero if DSO should be finalized late. */ #include diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h index 37cab6f06b2..6f37fba10ee 100644 --- a/sysdeps/generic/ldsodefs.h +++ b/sysdeps/generic/ldsodefs.h @@ -872,7 +872,7 @@ extern struct link_map *_dl_map_object (struct link_map *loader, extern void _dl_map_object_deps (struct link_map *map, struct link_map **preloads, unsigned int npreloads, int trace_mode, - int open_mode) + int open_mode, int preloads_init_early) attribute_hidden; /* Cache the locations of MAP's hash table. */