summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrigid Smith <brigidsmith@google.com>2014-07-23 11:22:25 -0700
committerDmitriy Ivanov <dimitry@google.com>2014-10-01 15:50:38 -0700
commit31b88da8431096a6df276705046ca7a012fa3530 (patch)
tree9da7fa8cac2cad6eb20ceae8a78ff0146d244876
parente4f8962ab5b4fb64842f4635fdf01a860bb180ee (diff)
downloadbionic-31b88da8431096a6df276705046ca7a012fa3530.zip
bionic-31b88da8431096a6df276705046ca7a012fa3530.tar.gz
bionic-31b88da8431096a6df276705046ca7a012fa3530.tar.bz2
Added test for ifunc support in dynamic linker.
ifuncs now work in i386 and x86_64 when called in the same library as well as in a different library. Bug:6657325 (cherry picked from commit c5a13efa9bc4264be0a9a9e37c00633af01584ed) Change-Id: I321d780bc2f9bd1baa749e1acacd2683aefe827b
-rw-r--r--libc/include/elf.h19
-rw-r--r--linker/linker.cpp114
-rw-r--r--linker/linker.h6
-rw-r--r--linker/linker_debug.h1
-rw-r--r--tests/dlfcn_test.cpp33
-rw-r--r--tests/libs/Android.mk14
-rw-r--r--tests/libs/dlopen_testlib_ifunc.c37
7 files changed, 214 insertions, 10 deletions
diff --git a/libc/include/elf.h b/libc/include/elf.h
index 0975b7a..faae73e 100644
--- a/libc/include/elf.h
+++ b/libc/include/elf.h
@@ -69,14 +69,17 @@ typedef struct {
#define PT_GNU_RELRO 0x6474e552
-#define STB_LOOS 10
-#define STB_HIOS 12
-#define STB_LOPROC 13
-#define STB_HIPROC 15
+#define STB_LOOS 10
+#define STB_HIOS 12
+#define STB_LOPROC 13
+#define STB_HIPROC 15
-#define STT_LOOS 10
-#define STT_HIOS 12
-#define STT_LOPROC 13
-#define STT_HIPROC 15
+#define STT_GNU_IFUNC 10
+#define STT_LOOS 10
+#define STT_HIOS 12
+#define STT_LOPROC 13
+#define STT_HIPROC 15
+
+#define R_386_IRELATIVE 42
#endif /* _ELF_H */
diff --git a/linker/linker.cpp b/linker/linker.cpp
index cf65705..b28a01d 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -466,6 +466,29 @@ static ElfW(Sym)* soinfo_elf_lookup(soinfo* si, unsigned hash, const char* name)
return NULL;
}
+static void resolve_ifunc_symbols(soinfo* si) {
+
+ phdr_table_unprotect_segments(si->phdr, si->phnum, si->load_bias);
+
+ TRACE_TYPE(IFUNC, "CHECKING FOR IFUNCS AND PERFORMING SYMBOL UPDATES");
+
+ for (size_t i = 0; i < si->nchain; ++i) {
+ ElfW(Sym)* s = &si->symtab[i];
+ if (ELF_ST_TYPE(s->st_info) == STT_GNU_IFUNC) {
+ // The address of the ifunc in the symbol table is the address of the
+ // function that chooses the function to which the ifunc will refer.
+ // In order to return the proper value, we run the choosing function
+ // in the linker and then return its result (minus the base offset).
+ TRACE_TYPE(IFUNC, "FOUND IFUNC");
+ ElfW(Addr) (*ifunc_ptr)();
+ ifunc_ptr = reinterpret_cast<ElfW(Addr)(*)()>(s->st_value + si->base);
+ s->st_value = (ifunc_ptr() - si->base);
+ TRACE_TYPE(IFUNC, "NEW VALUE IS %p", (void*)s->st_value);
+ }
+ }
+ phdr_table_protect_segments(si->phdr, si->phnum, si->load_bias);
+}
+
static unsigned elfhash(const char* _name) {
const unsigned char* name = reinterpret_cast<const unsigned char*>(_name);
unsigned h = 0, g;
@@ -804,6 +827,10 @@ static soinfo* load_library(const char* name, int dlflags, const android_dlextin
return NULL;
}
+ // if the library has any ifuncs, we will need to resolve them so that dlsym
+ // can handle them properly
+ resolve_ifunc_symbols(si);
+
return si;
}
@@ -931,6 +958,53 @@ void do_dlclose(soinfo* si) {
protect_data(PROT_READ);
}
+// ifuncs are only defined for x86
+#if defined(__i386__)
+static void soinfo_ifunc_relocate(soinfo* si, ElfW(Rel)* rel, unsigned count, soinfo* needed[]) {
+ for (size_t idx = 0; idx < count; ++idx, ++rel) {
+ ElfW(Sym)* s;
+ soinfo* lsi;
+ unsigned type = ELFW(R_TYPE)(rel->r_info);
+ unsigned sym = ELFW(R_SYM)(rel->r_info);
+ ElfW(Addr) reloc = static_cast<ElfW(Addr)>(rel->r_offset + si->load_bias);
+ ElfW(Addr) sym_addr = 0;
+ const char* sym_name = NULL;
+ sym_name = reinterpret_cast<const char*>(si->strtab + si->symtab[sym].st_name);
+ s = soinfo_do_lookup(si, sym_name, &lsi, needed);
+
+ if (ELF_ST_TYPE(s->st_info) == STT_GNU_IFUNC && type == R_386_JMP_SLOT) {
+ TRACE("IFUNC RELOCATION, PASS 2: %p", (void*)(sym_addr));
+ ElfW(Addr) (*ifunc_ptr)();
+ ifunc_ptr = reinterpret_cast<ElfW(Addr)(*)()>(s->st_value + si->base);
+ *reinterpret_cast<ElfW(Addr)*>(reloc) = ifunc_ptr();
+ }
+ }
+}
+#endif
+
+#if defined(__x86_64__)
+static void soinfo_ifunc_relocate(soinfo* si, ElfW(Rela)* rela, unsigned count, soinfo* needed[]) {
+ for (size_t idx = 0; idx < count; ++idx, ++rela) {
+ ElfW(Sym)* s;
+ soinfo* lsi;
+ unsigned type = ELFW(R_TYPE)(rela->r_info);
+ unsigned sym = ELFW(R_SYM)(rela->r_info);
+ ElfW(Addr) reloc = static_cast<ElfW(Addr)>(rela->r_offset + si->load_bias);
+ ElfW(Addr) sym_addr = 0;
+ const char* sym_name = NULL;
+ sym_name = reinterpret_cast<const char*>(si->strtab + si->symtab[sym].st_name);
+ s = soinfo_do_lookup(si, sym_name, &lsi, needed);
+
+ if (ELF_ST_TYPE(s->st_info) == STT_GNU_IFUNC && type == R_X86_64_JUMP_SLOT) {
+ TRACE("IFUNC RELOCATION, PASS 2: %p", (void*)(sym_addr + rela->r_addend));
+ ElfW(Addr) (*ifunc_ptr)();
+ ifunc_ptr = reinterpret_cast<ElfW(Addr)(*)()>(s->st_value + si->base);
+ *reinterpret_cast<ElfW(Addr)*>(reloc) = ifunc_ptr();
+ }
+ }
+}
+#endif
+
#if defined(USE_RELA)
static int soinfo_relocate(soinfo* si, ElfW(Rela)* rela, unsigned count, soinfo* needed[]) {
ElfW(Sym)* s;
@@ -1142,7 +1216,11 @@ static int soinfo_relocate(soinfo* si, ElfW(Rela)* rela, unsigned count, soinfo*
MARK(rela->r_offset);
TRACE_TYPE(RELO, "RELO JMP_SLOT %08zx <- %08zx %s", static_cast<size_t>(reloc),
static_cast<size_t>(sym_addr + rela->r_addend), sym_name);
- *reinterpret_cast<ElfW(Addr)*>(reloc) = sym_addr + rela->r_addend;
+ if (ELF_ST_TYPE(s->st_info) == STT_GNU_IFUNC) {
+ si->set_has_ifuncs(true);
+ } else {
+ *reinterpret_cast<ElfW(Addr)*>(reloc) = sym_addr + rela->r_addend;
+ }
break;
case R_X86_64_GLOB_DAT:
count_relocation(kRelocAbsolute);
@@ -1321,7 +1399,11 @@ static int soinfo_relocate(soinfo* si, ElfW(Rel)* rel, unsigned count, soinfo* n
count_relocation(kRelocAbsolute);
MARK(rel->r_offset);
TRACE_TYPE(RELO, "RELO JMP_SLOT %08x <- %08x %s", reloc, sym_addr, sym_name);
- *reinterpret_cast<ElfW(Addr)*>(reloc) = sym_addr;
+ if (ELF_ST_TYPE(s->st_info) == STT_GNU_IFUNC) {
+ si->set_has_ifuncs(true);
+ } else {
+ *reinterpret_cast<ElfW(Addr)*>(reloc) = sym_addr;
+ }
break;
case R_386_GLOB_DAT:
count_relocation(kRelocAbsolute);
@@ -1581,6 +1663,14 @@ void soinfo::set_st_ino(ino_t ino) {
st_ino = ino;
}
+void soinfo::set_has_ifuncs(bool ifuncs) {
+ if ((this->flags & FLAG_NEW_SOINFO) == 0) {
+ return;
+ }
+
+ has_ifuncs = ifuncs;
+}
+
dev_t soinfo::get_st_dev() {
if ((this->flags & FLAG_NEW_SOINFO) == 0) {
return 0;
@@ -1597,6 +1687,14 @@ ino_t soinfo::get_st_ino() {
return st_ino;
}
+bool soinfo::get_has_ifuncs() {
+ if ((this->flags & FLAG_NEW_SOINFO) == 0) {
+ return false;
+ }
+
+ return has_ifuncs;
+}
+
// This is a return on get_children() in case
// 'this->flags' does not have FLAG_NEW_SOINFO set.
static soinfo::soinfo_list_t g_empty_list;
@@ -1981,6 +2079,18 @@ static bool soinfo_link_image(soinfo* si, const android_dlextinfo* extinfo) {
}
#endif
+ // if there are ifuncs, we need to do an additional relocation pass.
+ // they cannot be resolved until the rest of the relocations are done
+ // because we need to call the resolution function which may be waiting
+ // on relocations.
+ if(si->get_has_ifuncs()) {
+#if defined(__i386__)
+ soinfo_ifunc_relocate(si, si->plt_rel, si->plt_rel_count, needed);
+#elif defined(__x86_64__)
+ soinfo_ifunc_relocate(si, si->plt_rela, si->plt_rela_count, needed);
+#endif
+ }
+
#if defined(__mips__)
if (!mips_relocate_got(si, needed)) {
return false;
diff --git a/linker/linker.h b/linker/linker.h
index 374652e..d7cf24b 100644
--- a/linker/linker.h
+++ b/linker/linker.h
@@ -204,8 +204,12 @@ struct soinfo {
void set_st_dev(dev_t st_dev);
void set_st_ino(ino_t st_ino);
+ void set_has_ifuncs(bool ifunc);
ino_t get_st_ino();
dev_t get_st_dev();
+ bool get_has_ifuncs();
+
+
soinfo_list_t& get_children();
@@ -218,6 +222,8 @@ struct soinfo {
// when FLAG_NEW_SOINFO is set in this->flags.
unsigned int version;
+ bool has_ifuncs;
+
dev_t st_dev;
ino_t st_ino;
diff --git a/linker/linker_debug.h b/linker/linker_debug.h
index 3faa38e..0c7a784 100644
--- a/linker/linker_debug.h
+++ b/linker/linker_debug.h
@@ -42,6 +42,7 @@
#define TRACE_DEBUG 1
#define DO_TRACE_LOOKUP 1
#define DO_TRACE_RELO 1
+#define DO_TRACE_IFUNC 1
#define TIMING 0
#define STATS 0
#define COUNT_PAGES 0
diff --git a/tests/dlfcn_test.cpp b/tests/dlfcn_test.cpp
index 457fcd5..260cbd6 100644
--- a/tests/dlfcn_test.cpp
+++ b/tests/dlfcn_test.cpp
@@ -87,6 +87,39 @@ TEST(dlfcn, dlopen_noload) {
ASSERT_EQ(0, dlclose(handle2));
}
+// ifuncs are only supported on intel for now
+#if defined(__i386__) || defined(__x86_64__)
+TEST(dlfcn, ifunc) {
+ const char* (*foo_ptr)();
+ const char* (*foo_library_ptr)();
+
+ // ifunc's choice depends on whether IFUNC_CHOICE has a value
+ // first check the set case
+ setenv("IFUNC_CHOICE", "set", 1);
+ void* handle = dlopen("libtest_ifunc.so", RTLD_NOW);
+ ASSERT_TRUE(handle != NULL);
+ *(void **)(&foo_ptr) = dlsym(handle, "foo");
+ *(void **)(&foo_library_ptr) = dlsym(handle, "foo_library");
+ ASSERT_TRUE(foo_ptr != NULL);
+ ASSERT_TRUE(foo_library_ptr != NULL);
+ ASSERT_EQ(strncmp("set", (*foo_ptr)(), 3), 0);
+ ASSERT_EQ(strncmp("set", (*foo_library_ptr)(), 3), 0);
+ dlclose(handle);
+
+ // then check the unset case
+ unsetenv("IFUNC_CHOICE");
+ handle = dlopen("libtest_ifunc.so", RTLD_NOW);
+ ASSERT_TRUE(handle != NULL);
+ *(void **)(&foo_ptr) = dlsym(handle, "foo");
+ *(void **)(&foo_library_ptr) = dlsym(handle, "foo_library");
+ ASSERT_TRUE(foo_ptr != NULL);
+ ASSERT_TRUE(foo_library_ptr != NULL);
+ ASSERT_EQ(strncmp("unset", (*foo_ptr)(), 5), 0);
+ ASSERT_EQ(strncmp("unset", (*foo_library_ptr)(), 3), 0);
+ dlclose(handle);
+}
+#endif
+
TEST(dlfcn, dlopen_failure) {
void* self = dlopen("/does/not/exist", RTLD_NOW);
ASSERT_TRUE(self == NULL);
diff --git a/tests/libs/Android.mk b/tests/libs/Android.mk
index 75df539..8f0ec7a 100644
--- a/tests/libs/Android.mk
+++ b/tests/libs/Android.mk
@@ -115,6 +115,20 @@ build_target := SHARED_LIBRARY
include $(TEST_PATH)/Android.build.mk
# -----------------------------------------------------------------------------
+# Library used by ifunc tests
+# -----------------------------------------------------------------------------
+ifeq ($(TARGET_ARCH),$(filter $(TARGET_ARCH),x86 x86_64))
+ libtest_ifunc_src_files := \
+ dlopen_testlib_ifunc.c
+
+ LOCAL_SDK_VERSION := current
+ module := libtest_ifunc
+ build_type := target
+ build_target := SHARED_LIBRARY
+ include $(TEST_PATH)/Android.build.mk
+endif
+
+# -----------------------------------------------------------------------------
# Library used by atexit tests
# -----------------------------------------------------------------------------
diff --git a/tests/libs/dlopen_testlib_ifunc.c b/tests/libs/dlopen_testlib_ifunc.c
new file mode 100644
index 0000000..1c4bafa
--- /dev/null
+++ b/tests/libs/dlopen_testlib_ifunc.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+const char* foo() __attribute__ ((ifunc ("foo_ifunc")));
+
+const char* f1() {
+ return "unset";
+}
+
+const char* f2() {
+ return "set";
+}
+
+void* foo_ifunc() {
+ char* choice = getenv("IFUNC_CHOICE");
+ return choice == NULL ? f1 : f2;
+}
+
+const char* foo_library() {
+ return foo();
+} \ No newline at end of file