linker: Add support for dynamic "shim" libs
Add a new environment variable
LD_SHIM_LIBS
that is a colon (":") separated list of vertical bar ("|") separated pairs.
The pairs are the name for a soinfo reference (executable or shared library)
followed by the name of the shim library to load. For example:
LD_SHIM_LIBS=rmt_storage|libshim_ioprio.so:/system/lib/libicuuv.so|libshim_icu53.so
will instruct the linker to load the dynamic library libshim_ioprio.so
whenver rmt_storage is executed [*] and will load libshim_icu53.so whenever
any executable or other shared library links against /system/lib/libicuuv.so.
There are no restrictions against circular references. In this example,
libshim_icu53.so can link against libicuuv.so which provides a simple and
convenient means of adding compatibility symbols.
[*] Note that the absolute path is not available to the linker and therefore
using the name of executables does depend on the invocation and therefore
should only be used if absolutely necessary. That is, running
/system/bin/rmt_storage would not load any shim libs in this example because
it does not match the name of the invocation of the command.
If you have trouble determining the sonames being loaded, you can also set
the environment variable LD_DEBUG=1 which will cause additional information
to be logged to help trace the detection of the shim libs.
Change-Id: I0ef80fa466167f7bcb7dac90842bef1c3cf879b6
linker: Fix the fact that shim libs do not properly call constructors
Change-Id: I34333e13443a154e675b853fa41442351bc4243a
linker: Don't try to walk the g_active_shim_libs when doing dlsym
This is a bug in the original shim_lib implementation which was
doing the shim lib resolution both when loading the libraries
and when doing the dynamic symbol resolution.
Change-Id: Ib2df0498cf551b3bbd37d7c351410b9908eb1795
Revert "Revert "linker: Reset the active shim libs each time we do a dlopen""
This reverts commit fd0140b028dedabc572f4659cc015edfeee3cd60.
Change-Id: I42b3acfcdc6b84251a396b9e42604bb5685196bd
Make shim lib load failure non-fatal.
Instead, print an appropriate warning message. Aborting symbol
resolution on shim lib load failure leads to weird symbol lookup
failures, because symbols in libraries referenced after the one loading
the shim won't be loaded anymore without a log message stating why that
happened.
Change-Id: Ic3ad7095ddae7ea1039cb6a18603d5cde8a16143
bionic: Do not allow LD_SHIM_LIBS for setuid executables
That's really not safe...
Change-Id: If79af951830966fc21812cd0f60a8998a752a941
bionic: linker: Load shim libs *before* the self-linked libs
By loading them earlier, this allows us to override a symbol in
a library that is being directly linked.
I believe this explains why some people have had problems shimming
one lib but when the changet he shim to be against a different
lib it magically works.
It also makes it possible to override some symbols that were
nearly impossible to override before this change. For example, it is
pretty much impossible to override a symbol in libutils without
this change because it's loaded almost everywhere so no matter
where you try to place the shimming, it will be too late and
the other symbol will have priority.
In particularly, this is necessary to be able to correctly
shim the VectorImpl symbols for dlx.
Change-Id: I461ca416bc288e28035352da00fde5f34f8d9ffa
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 9dc928e..ba57b9c 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -1230,6 +1230,67 @@
typedef linked_list_t<const char> StringLinkedList;
typedef std::vector<LoadTask*> LoadTaskList;
+static soinfo* find_library(const char* name, int rtld_flags, const android_dlextinfo* extinfo);
+
+// g_ld_all_shim_libs maintains the references to memory as it used
+// in the soinfo structures and in the g_active_shim_libs list.
+
+static std::vector<std::string> g_ld_all_shim_libs;
+
+// g_active_shim_libs are all shim libs that are still eligible
+// to be loaded. We must remove a shim lib from the list before
+// we load the library to avoid recursive loops (load shim libA
+// for libB where libA also links against libB).
+
+static linked_list_t<const std::string> g_active_shim_libs;
+
+static void reset_g_active_shim_libs(void) {
+ g_active_shim_libs.clear();
+ for (const auto& pair : g_ld_all_shim_libs) {
+ g_active_shim_libs.push_back(&pair);
+ }
+}
+
+static void parse_LD_SHIM_LIBS(const char* path) {
+ parse_path(path, " :", &g_ld_all_shim_libs);
+ reset_g_active_shim_libs();
+}
+
+static bool shim_lib_matches(const char *shim_lib, const char *realpath) {
+ const char *sep = strchr(shim_lib, '|');
+ return sep != nullptr && strncmp(realpath, shim_lib, sep - shim_lib) == 0;
+}
+
+template<typename F>
+static void shim_libs_for_each(const char *const path, F action) {
+ if (path == nullptr) return;
+ INFO("Finding shim libs for \"%s\"\n", path);
+ std::vector<const std::string *> matched;
+
+ g_active_shim_libs.for_each([&](const std::string *a_pair) {
+ const char *pair = a_pair->c_str();
+ if (shim_lib_matches(pair, path)) {
+ matched.push_back(a_pair);
+ }
+ });
+
+ g_active_shim_libs.remove_if([&](const std::string *a_pair) {
+ const char *pair = a_pair->c_str();
+ return shim_lib_matches(pair, path);
+ });
+
+ for (const auto& one_pair : matched) {
+ const char* const pair = one_pair->c_str();
+ const char* sep = strchr(pair, '|');
+ soinfo *child = find_library(sep+1, RTLD_GLOBAL, nullptr);
+ if (child) {
+ INFO("Using shim lib \"%s\"\n", sep+1);
+ action(child);
+ } else {
+ PRINT("Shim lib \"%s\" can not be loaded, ignoring.", sep+1);
+ }
+ }
+}
// This function walks down the tree of soinfo dependencies
// in breadth-first order and
@@ -1239,7 +1300,7 @@
// walk_dependencies_tree returns false if walk was terminated
// by the action and true otherwise.
template<typename F>
-static bool walk_dependencies_tree(soinfo* root_soinfos[], size_t root_soinfos_size, F action) {
+static bool walk_dependencies_tree(soinfo* root_soinfos[], size_t root_soinfos_size, bool do_shims, F action) {
SoinfoLinkedList visit_list;
SoinfoLinkedList visited;
@@ -1259,6 +1320,13 @@
visited.push_back(si);
+ if (do_shims) {
+ shim_libs_for_each(si->get_realpath(), [&](soinfo* child) {
+ si->add_child(child);
+ visit_list.push_back(child);
+ });
+ }
+
si->get_children().for_each([&](soinfo* child) {
visit_list.push_back(child);
});
@@ -1274,7 +1342,7 @@
const ElfW(Sym)* result = nullptr;
bool skip_lookup = skip_until != nullptr;
- walk_dependencies_tree(&root, 1, [&](soinfo* current_soinfo) {
+ walk_dependencies_tree(&root, 1, false, [&](soinfo* current_soinfo) {
if (skip_lookup) {
skip_lookup = current_soinfo != skip_until;
return true;
@@ -2127,6 +2195,7 @@
walk_dependencies_tree(
(start_with != nullptr && add_as_children) ? &start_with : soinfos,
(start_with != nullptr && add_as_children) ? 1 : soinfos_count,
+ true,
[&] (soinfo* si) {
local_group.push_back(si);
return true;
@@ -2397,6 +2466,7 @@
}
ProtectedDataGuard guard;
+ reset_g_active_shim_libs();
soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);
if (si != nullptr) {
si->call_constructors();
@@ -4217,6 +4287,7 @@
// doesn't cost us anything.
const char* ldpath_env = nullptr;
const char* ldpreload_env = nullptr;
+ const char* ldshim_libs_env = nullptr;
if (!getauxval(AT_SECURE)) {
ldpath_env = getenv("LD_LIBRARY_PATH");
if (ldpath_env != nullptr) {
@@ -4226,6 +4297,7 @@
if (ldpreload_env != nullptr) {
INFO("[ LD_PRELOAD set to \"%s\" ]", ldpreload_env);
}
+ ldshim_libs_env = getenv("LD_SHIM_LIBS");
}
struct stat file_stat;
@@ -4284,6 +4356,7 @@
// Use LD_LIBRARY_PATH and LD_PRELOAD (but only if we aren't setuid/setgid).
parse_LD_LIBRARY_PATH(ldpath_env);
parse_LD_PRELOAD(ldpreload_env);
+ parse_LD_SHIM_LIBS(ldshim_libs_env);
somain = si;