From 31b88da8431096a6df276705046ca7a012fa3530 Mon Sep 17 00:00:00 2001 From: Brigid Smith Date: Wed, 23 Jul 2014 11:22:25 -0700 Subject: [PATCH] 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 --- libc/include/elf.h | 19 ++++--- linker/linker.cpp | 114 +++++++++++++++++++++++++++++++++++++- linker/linker.h | 6 ++ linker/linker_debug.h | 1 + tests/dlfcn_test.cpp | 33 +++++++++++ tests/libs/Android.mk | 14 +++++ tests/libs/dlopen_testlib_ifunc.c | 37 +++++++++++++ 7 files changed, 214 insertions(+), 10 deletions(-) create mode 100644 tests/libs/dlopen_testlib_ifunc.c diff --git a/libc/include/elf.h b/libc/include/elf.h index 0975b7a61..faae73e92 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 cf657057e..b28a01d49 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(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(_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(rel->r_offset + si->load_bias); + ElfW(Addr) sym_addr = 0; + const char* sym_name = NULL; + sym_name = reinterpret_cast(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(s->st_value + si->base); + *reinterpret_cast(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(rela->r_offset + si->load_bias); + ElfW(Addr) sym_addr = 0; + const char* sym_name = NULL; + sym_name = reinterpret_cast(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(s->st_value + si->base); + *reinterpret_cast(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(reloc), static_cast(sym_addr + rela->r_addend), sym_name); - *reinterpret_cast(reloc) = sym_addr + rela->r_addend; + if (ELF_ST_TYPE(s->st_info) == STT_GNU_IFUNC) { + si->set_has_ifuncs(true); + } else { + *reinterpret_cast(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(reloc) = sym_addr; + if (ELF_ST_TYPE(s->st_info) == STT_GNU_IFUNC) { + si->set_has_ifuncs(true); + } else { + *reinterpret_cast(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 374652eb8..d7cf24bfa 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 3faa38ef4..0c7a78418 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 457fcd5c0..260cbd601 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 75df539b9..8f0ec7a56 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 000000000..1c4baface --- /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 +#include + +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 -- 2.11.0