glibc/elf/tst-dl_find_object-threads.c
Florian Weimer 5d28a8962d elf: Add _dl_find_object function
It can be used to speed up the libgcc unwinder, and the internal
_dl_find_dso_for_object function (which is used for caller
identification in dlopen and related functions, and in dladdr).

_dl_find_object is in the internal namespace due to bug 28503.
If libgcc switches to _dl_find_object, this namespace issue will
be fixed.  It is located in libc for two reasons: it is necessary
to forward the call to the static libc after static dlopen, and
there is a link ordering issue with -static-libgcc and libgcc_eh.a
because libc.so is not a linker script that includes ld.so in the
glibc build tree (so that GCC's internal -lc after libgcc_eh.a does
not pick up ld.so).

It is necessary to do the i386 customization in the
sysdeps/x86/bits/dl_find_object.h header shared with x86-64 because
otherwise, multilib installations are broken.

The implementation uses software transactional memory, as suggested
by Torvald Riegel.  Two copies of the supporting data structures are
used, also achieving full async-signal-safety.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
2021-12-28 22:52:56 +01:00

276 lines
8.1 KiB
C

/* _dl_find_object test with parallelism.
Copyright (C) 2021 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <array_length.h>
#include <dlfcn.h>
#include <elf/dl-find_object.h>
#include <stdio.h>
#include <stdlib.h>
#include <support/check.h>
#include <support/support.h>
#include <support/xdlfcn.h>
#include <support/xthread.h>
#include <support/xunistd.h>
/* Computes the expected _dl_find_object result directly from the
map. */
static void
from_map (struct link_map *l, struct dl_find_object *expected)
{
struct dl_find_object_internal internal;
_dl_find_object_from_map (l, &internal);
_dl_find_object_to_external (&internal, expected);
}
/* Returns the soname for the test object NUMBER. */
static char *
soname (int number)
{
return xasprintf ("tst-dl_find_object-mod%d.so", number);
}
/* Returns the data symbol name for the test object NUMBER. */
static char *
symbol (int number)
{
return xasprintf ("mod%d_data", number);
}
struct verify_data
{
char *soname;
void *address; /* Address in the shared object. */
struct dl_find_object dlfo;
pthread_t thr;
};
/* Compare _dl_find_object result at ADDRESS with *EXPECTED. */
static void
check (void *address, struct dl_find_object *expected, int line)
{
struct dl_find_object actual;
int ret = _dl_find_object (address, &actual);
if (expected == NULL)
{
if (ret != -1)
{
support_record_failure ();
printf ("%s:%d: unexpected success for %p\n",
__FILE__, line, address);
}
return;
}
if (ret != 0)
{
support_record_failure ();
printf ("%s:%d: unexpected failure for %p\n",
__FILE__, line, address);
return;
}
if (actual.dlfo_flags != expected->dlfo_flags)
{
support_record_failure ();
printf ("%s:%d: error: %p: flags is %llu, expected %llu\n",
__FILE__, line, address,
actual.dlfo_flags, expected->dlfo_flags);
}
if (actual.dlfo_flags != expected->dlfo_flags)
{
support_record_failure ();
printf ("%s:%d: error: %p: map start is %p, expected %p\n",
__FILE__, line,
address, actual.dlfo_map_start, expected->dlfo_map_start);
}
if (actual.dlfo_map_end != expected->dlfo_map_end)
{
support_record_failure ();
printf ("%s:%d: error: %p: map end is %p, expected %p\n",
__FILE__, line,
address, actual.dlfo_map_end, expected->dlfo_map_end);
}
if (actual.dlfo_link_map != expected->dlfo_link_map)
{
support_record_failure ();
printf ("%s:%d: error: %p: link map is %p, expected %p\n",
__FILE__, line,
address, actual.dlfo_link_map, expected->dlfo_link_map);
}
if (actual.dlfo_eh_frame != expected->dlfo_eh_frame)
{
support_record_failure ();
printf ("%s:%d: error: %p: EH frame is %p, expected %p\n",
__FILE__, line,
address, actual.dlfo_eh_frame, expected->dlfo_eh_frame);
}
#if DLFO_STRUCT_HAS_EH_DBASE
if (actual.dlfo_eh_dbase != expected->dlfo_eh_dbase)
{
support_record_failure ();
printf ("%s:%d: error: %p: data base is %p, expected %p\n",
__FILE__, line,
address, actual.dlfo_eh_dbase, expected->dlfo_eh_dbase);
}
#endif
#if DLFO_STRUCT_HAS_EH_COUNT
if (actual.dlfo_eh_count != expected->dlfo_eh_count)
{
support_record_failure ();
printf ("%s:%d: error: %p: count is %d, expected %d\n",
__FILE__, line,
address, actual.dlfo_eh_count, expected->dlfo_eh_count);
}
#endif
}
/* Request process termination after 3 seconds. */
static bool exit_requested;
static void *
exit_thread (void *ignored)
{
usleep (3 * 100 * 1000);
__atomic_store_n (&exit_requested, true, __ATOMIC_RELAXED);
return NULL;
}
static void *
verify_thread (void *closure)
{
struct verify_data *data = closure;
while (!__atomic_load_n (&exit_requested, __ATOMIC_RELAXED))
{
check (data->address, &data->dlfo, __LINE__);
check (data->dlfo.dlfo_map_start, &data->dlfo, __LINE__);
check (data->dlfo.dlfo_map_end - 1, &data->dlfo, __LINE__);
}
return NULL;
}
/* Sets up the verification data, dlopen'ing shared object NUMBER, and
launches a verification thread. */
static void
start_verify (int number, struct verify_data *data)
{
data->soname = soname (number);
struct link_map *l = xdlopen (data->soname, RTLD_NOW);
from_map (l, &data->dlfo);
TEST_VERIFY_EXIT (data->dlfo.dlfo_link_map == l);
char *sym = symbol (number);
data->address = xdlsym (data->dlfo.dlfo_link_map, sym);
free (sym);
data->thr = xpthread_create (NULL, verify_thread, data);
}
static int
do_test (void)
{
struct verify_data data_mod2;
struct verify_data data_mod4;
struct verify_data data_mod7;
/* Load the modules with gaps. */
{
void *mod1 = xdlopen ("tst-dl_find_object-mod1.so", RTLD_NOW);
start_verify (2, &data_mod2);
void *mod3 = xdlopen ("tst-dl_find_object-mod3.so", RTLD_NOW);
start_verify (4, &data_mod4);
void *mod5 = xdlopen ("tst-dl_find_object-mod5.so", RTLD_NOW);
void *mod6 = xdlopen ("tst-dl_find_object-mod6.so", RTLD_NOW);
start_verify (7, &data_mod7);
xdlclose (mod6);
xdlclose (mod5);
xdlclose (mod3);
xdlclose (mod1);
}
/* Objects that continuously opened and closed. */
struct temp_object
{
char *soname;
char *symbol;
struct link_map *link_map;
void *address;
} temp_objects[] =
{
{ soname (1), symbol (1), },
{ soname (3), symbol (3), },
{ soname (5), symbol (5), },
{ soname (6), symbol (6), },
{ soname (8), symbol (8), },
{ soname (9), symbol (9), },
};
pthread_t exit_thr = xpthread_create (NULL, exit_thread, NULL);
struct drand48_data state;
srand48_r (1, &state);
while (!__atomic_load_n (&exit_requested, __ATOMIC_RELAXED))
{
long int idx;
lrand48_r (&state, &idx);
idx %= array_length (temp_objects);
if (temp_objects[idx].link_map == NULL)
{
temp_objects[idx].link_map = xdlopen (temp_objects[idx].soname,
RTLD_NOW);
temp_objects[idx].address = xdlsym (temp_objects[idx].link_map,
temp_objects[idx].symbol);
}
else
{
xdlclose (temp_objects[idx].link_map);
temp_objects[idx].link_map = NULL;
struct dl_find_object dlfo;
int ret = _dl_find_object (temp_objects[idx].address, &dlfo);
if (ret != -1)
{
TEST_VERIFY_EXIT (ret == 0);
support_record_failure ();
printf ("%s: error: %s EH found after dlclose, link map %p\n",
__FILE__, temp_objects[idx].soname, dlfo.dlfo_link_map);
}
}
}
xpthread_join (data_mod2.thr);
xpthread_join (data_mod4.thr);
xpthread_join (data_mod7.thr);
xpthread_join (exit_thr);
for (size_t i = 0; i < array_length (temp_objects); ++i)
{
free (temp_objects[i].soname);
free (temp_objects[i].symbol);
if (temp_objects[i].link_map != NULL)
xdlclose (temp_objects[i].link_map);
}
free (data_mod2.soname);
free (data_mod4.soname);
xdlclose (data_mod4.dlfo.dlfo_link_map);
free (data_mod7.soname);
xdlclose (data_mod7.dlfo.dlfo_link_map);
return 0;
}
#include <support/test-driver.c>