main: Prepend RPATH or RUNPATH paths to gir search paths

If one wants to run tests the non-installed gnome-shell, that currently
fails as gnome-shell the executable attempts to link against
./build/src/libgnome-shell.so, but when GObject introspection tries to
find what library to link to for Shell, it goes to the installed
libgnome-shell.so, causing two different versions of libgnome-shell.so
to be loaded.

This, however, can be avoided thanks to meson adding $ORIGIN paths to
relevant libraries before installing an executable. What this means in
practice is that we can inspect ourself upon startup, discover whether
the RPATH/RUNPATH header contains $ORIGIN, and if so, expand it to the
directory containing the executable, and prepend the introspection
search paths with said directory.

This effectively means that the introspection machinery now finds the
same library that the linker linked the gnome-shell executable with,
making it run successfully.

It's not possible to use $GI_TYPELIB_PATH since
g_irepository_prepend_library_path() takes precedence. There is no
"append" variant of that API.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1349>
This commit is contained in:
Jonas Ådahl 2022-08-17 21:43:21 +02:00
parent 1559f03a82
commit 9bc89b821c
3 changed files with 82 additions and 0 deletions

View File

@ -36,3 +36,6 @@
/* Define if polkit defines autocleanup functions */
#mesondefine HAVE_POLKIT_AUTOCLEANUP
/* Define to 1 if the <elf.h> and <link.h> header file exist. */
#mesondefine HAVE_EXE_INTROSPECTION

View File

@ -148,6 +148,8 @@ cdata.set('HAVE_FDWALK', cc.has_function('fdwalk'))
cdata.set('HAVE_MALLINFO', cc.has_function('mallinfo'))
cdata.set('HAVE_MALLINFO2', cc.has_function('mallinfo2'))
cdata.set('HAVE_SYS_RESOURCE_H', cc.has_header('sys/resource.h'))
cdata.set('HAVE_EXE_INTROSPECTION',
cc.has_header('elf.h') and cc.has_header('link.h'))
cdata.set('HAVE__NL_TIME_FIRST_WEEKDAY',
cc.has_header_symbol('langinfo.h', '_NL_TIME_FIRST_WEEKDAY')
)

View File

@ -18,6 +18,11 @@
#include <meta/meta-plugin.h>
#include <meta/prefs.h>
#include <atk-bridge.h>
#include <link.h>
#ifdef HAVE_EXE_INTROSPECTION
#include <elf.h>
#endif
#include "shell-global.h"
#include "shell-global-private.h"
@ -123,6 +128,74 @@ shell_dbus_init (gboolean replace)
g_object_unref (session);
}
#ifdef HAVE_EXE_INTROSPECTION
static void
maybe_add_rpath_introspection_paths (void)
{
ElfW (Dyn) *dyn;
ElfW (Dyn) *rpath = NULL;
ElfW (Dyn) *runpath = NULL;
const char *strtab = NULL;
g_auto (GStrv) paths = NULL;
g_autofree char *exe_dir = NULL;
GStrv str;
for (dyn = _DYNAMIC; dyn->d_tag != DT_NULL; dyn++)
{
if (dyn->d_tag == DT_RPATH)
rpath = dyn;
else if (dyn->d_tag == DT_RUNPATH)
runpath = dyn;
else if (dyn->d_tag == DT_STRTAB)
strtab = (const char *) dyn->d_un.d_val;
}
if ((!rpath && !runpath) || !strtab)
return;
if (rpath)
paths = g_strsplit (strtab + rpath->d_un.d_val, ":", -1);
else
paths = g_strsplit (strtab + runpath->d_un.d_val, ":", -1);
if (!paths)
return;
for (str = paths; *str; str++)
{
g_autoptr (GError) error = NULL;
g_autoptr (GString) rpath_dir = NULL;
if (!strstr (*str, "$ORIGIN"))
continue;
if (!exe_dir)
{
g_autofree char *exe_path = NULL;
exe_path = g_file_read_link ("/proc/self/exe", &error);
if (!exe_path)
{
g_warning ("Failed to find directory of executable: %s",
error->message);
return;
}
exe_dir = g_path_get_dirname (exe_path);
}
rpath_dir = g_string_new (*str);
g_string_replace (rpath_dir, "$ORIGIN", exe_dir, 0);
g_debug ("Prepending RPATH directory '%s' "
"to introsepciton library search path",
rpath_dir->str);
g_irepository_prepend_search_path (rpath_dir->str);
g_irepository_prepend_library_path (rpath_dir->str);
}
}
#endif /* HAVE_EXE_INTROSPECTION */
static void
shell_introspection_init (void)
{
@ -137,6 +210,10 @@ shell_introspection_init (void)
*/
g_irepository_prepend_library_path (MUTTER_TYPELIB_DIR);
g_irepository_prepend_library_path (GNOME_SHELL_PKGLIBDIR);
#ifdef HAVE_EXE_INTROSPECTION
maybe_add_rpath_introspection_paths ();
#endif
}
static void