Merge "Change name of MB_CUR_MAX implementation function."
diff --git a/libc/bionic/system_properties.cpp b/libc/bionic/system_properties.cpp
index a564c39..30081a5 100644
--- a/libc/bionic/system_properties.cpp
+++ b/libc/bionic/system_properties.cpp
@@ -26,6 +26,7 @@
* SUCH DAMAGE.
*/
#include <new>
+#include <stdatomic.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
@@ -45,7 +46,6 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <netinet/in.h>
-#include <unistd.h>
#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
#include <sys/_system_properties.h>
@@ -80,6 +80,16 @@
uint8_t namelen;
uint8_t reserved[3];
+ // TODO: The following fields should be declared as atomic_uint32_t.
+ // They should be assigned to with release semantics, instead of using
+ // explicit fences. Unfortunately, the read accesses are generally
+ // followed by more dependent read accesses, and the dependence
+ // is assumed to enforce memory ordering. Which it does on supported
+ // hardware. This technically should use memory_order_consume, if
+ // that worked as intended.
+ // We should also avoid rereading these fields redundantly, since not
+ // all processor implementations ensure that multiple loads from the
+ // same field are carried out in the right order.
volatile uint32_t prop;
volatile uint32_t left;
@@ -93,7 +103,8 @@
this->namelen = name_length;
memcpy(this->name, name, name_length);
this->name[name_length] = '\0';
- ANDROID_MEMBAR_FULL();
+ ANDROID_MEMBAR_FULL(); // TODO: Instead use a release store
+ // for subsequent pointer assignment.
}
private:
@@ -102,14 +113,15 @@
struct prop_area {
uint32_t bytes_used;
- volatile uint32_t serial;
+ atomic_uint_least32_t serial;
uint32_t magic;
uint32_t version;
uint32_t reserved[28];
char data[0];
prop_area(const uint32_t magic, const uint32_t version) :
- serial(0), magic(magic), version(version) {
+ magic(magic), version(version) {
+ atomic_init(&serial, 0);
memset(reserved, 0, sizeof(reserved));
// Allocate enough space for the root node.
bytes_used = sizeof(prop_bt);
@@ -120,7 +132,7 @@
};
struct prop_info {
- volatile uint32_t serial;
+ atomic_uint_least32_t serial;
char value[PROP_VALUE_MAX];
char name[0];
@@ -128,10 +140,11 @@
const uint8_t valuelen) {
memcpy(this->name, name, namelen);
this->name[namelen] = '\0';
- this->serial = (valuelen << 24);
+ atomic_init(&this->serial, valuelen << 24);
memcpy(this->value, value, valuelen);
this->value[valuelen] = '\0';
- ANDROID_MEMBAR_FULL();
+ ANDROID_MEMBAR_FULL(); // TODO: Instead use a release store
+ // for subsequent point assignment.
}
private:
DISALLOW_COPY_AND_ASSIGN(prop_info);
@@ -605,11 +618,20 @@
}
while (true) {
- uint32_t serial = __system_property_serial(pi);
+ uint32_t serial = __system_property_serial(pi); // acquire semantics
size_t len = SERIAL_VALUE_LEN(serial);
memcpy(value, pi->value, len + 1);
- ANDROID_MEMBAR_FULL();
- if (serial == pi->serial) {
+ // TODO: Fix the synchronization scheme here.
+ // There is no fully supported way to implement this kind
+ // of synchronization in C++11, since the memcpy races with
+ // updates to pi, and the data being accessed is not atomic.
+ // The following fence is unintuitive, but would be the
+ // correct one if memcpy used memory_order_relaxed atomic accesses.
+ // In practice it seems unlikely that the generated code would
+ // would be any different, so this should be OK.
+ atomic_thread_fence(memory_order_acquire);
+ if (serial ==
+ atomic_load_explicit(&(pi->serial), memory_order_relaxed)) {
if (name != 0) {
strcpy(name, pi->name);
}
@@ -658,14 +680,24 @@
if (len >= PROP_VALUE_MAX)
return -1;
- pi->serial = pi->serial | 1;
- ANDROID_MEMBAR_FULL();
+ uint32_t serial = atomic_load_explicit(&pi->serial, memory_order_relaxed);
+ serial |= 1;
+ atomic_store_explicit(&pi->serial, serial, memory_order_relaxed);
+ // The memcpy call here also races. Again pretend it
+ // used memory_order_relaxed atomics, and use the analogous
+ // counterintuitive fence.
+ atomic_thread_fence(memory_order_release);
memcpy(pi->value, value, len + 1);
- ANDROID_MEMBAR_FULL();
- pi->serial = (len << 24) | ((pi->serial + 1) & 0xffffff);
+ atomic_store_explicit(
+ &pi->serial,
+ (len << 24) | ((serial + 1) & 0xffffff),
+ memory_order_release);
__futex_wake(&pi->serial, INT32_MAX);
- pa->serial++;
+ atomic_store_explicit(
+ &pa->serial,
+ atomic_load_explicit(&pa->serial, memory_order_relaxed) + 1,
+ memory_order_release);
__futex_wake(&pa->serial, INT32_MAX);
return 0;
@@ -688,17 +720,25 @@
if (!pi)
return -1;
- pa->serial++;
+ // There is only a single mutator, but we want to make sure that
+ // updates are visible to a reader waiting for the update.
+ atomic_store_explicit(
+ &pa->serial,
+ atomic_load_explicit(&pa->serial, memory_order_relaxed) + 1,
+ memory_order_release);
__futex_wake(&pa->serial, INT32_MAX);
return 0;
}
+// Wait for non-locked serial, and retrieve it with acquire semantics.
unsigned int __system_property_serial(const prop_info *pi)
{
- uint32_t serial = pi->serial;
+ uint32_t serial = atomic_load_explicit(&pi->serial, memory_order_acquire);
while (SERIAL_DIRTY(serial)) {
- __futex_wait(const_cast<volatile uint32_t*>(&pi->serial), serial, NULL);
- serial = pi->serial;
+ __futex_wait(const_cast<volatile void *>(
+ reinterpret_cast<const void *>(&pi->serial)),
+ serial, NULL);
+ serial = atomic_load_explicit(&pi->serial, memory_order_acquire);
}
return serial;
}
@@ -706,12 +746,14 @@
unsigned int __system_property_wait_any(unsigned int serial)
{
prop_area *pa = __system_property_area__;
+ uint32_t my_serial;
do {
__futex_wait(&pa->serial, serial, NULL);
- } while (pa->serial == serial);
+ my_serial = atomic_load_explicit(&pa->serial, memory_order_acquire);
+ } while (my_serial == serial);
- return pa->serial;
+ return my_serial;
}
const prop_info *__system_property_find_nth(unsigned n)
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 @@
#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/libc/tools/check-symbols-glibc.py b/libc/tools/check-symbols-glibc.py
index 58a10e0..ffff964 100755
--- a/libc/tools/check-symbols-glibc.py
+++ b/libc/tools/check-symbols-glibc.py
@@ -3,12 +3,18 @@
import glob
import os
import re
-import string
import subprocess
import sys
+only_unwanted = False
+if len(sys.argv) > 1:
+ if sys.argv[1] in ('-u', '--unwanted'):
+ only_unwanted = True
+
toolchain = os.environ['ANDROID_TOOLCHAIN']
arch = re.sub(r'.*/linux-x86/([^/]+)/.*', r'\1', toolchain)
+if arch == 'aarch64':
+ arch = 'arm64'
def GetSymbolsFromSo(so_file):
# Example readelf output:
@@ -50,6 +56,15 @@
return glibc_to_bionic_names[name]
return name
+def GetNdkIgnored():
+ global arch
+ symbols = set()
+ files = glob.glob('%s/ndk/build/tools/unwanted-symbols/%s/*' %
+ (os.getenv('ANDROID_BUILD_TOP'), arch))
+ for f in files:
+ symbols |= set(open(f, 'r').read().splitlines())
+ return symbols
+
glibc_to_bionic_names = {
'__res_init': 'res_init',
'__res_mkquery': 'res_mkquery',
@@ -59,6 +74,7 @@
glibc = GetSymbolsFromSystemSo('libc.so.*', 'librt.so.*', 'libpthread.so.*', 'libresolv.so.*', 'libm.so.*')
bionic = GetSymbolsFromAndroidSo('libc.so', 'libm.so')
+ndk_ignored = GetNdkIgnored()
glibc = map(MangleGlibcNameToBionic, glibc)
@@ -100,6 +116,16 @@
'__errno',
'__fe_dfl_env',
'__get_h_errno',
+ '__fpclassifyd',
+ '__isfinite',
+ '__isfinitef',
+ '__isfinitel',
+ '__isnormal',
+ '__isnormalf',
+ '__isnormall',
+ '__sF',
+ '__pthread_cleanup_pop',
+ '__pthread_cleanup_push',
])
# bionic exposes various Linux features that glibc doesn't.
linux_stuff = set([
@@ -133,21 +159,62 @@
'mknodat',
'stat',
'stat64',
+ 'optreset',
+ 'sigsetjmp',
+])
+# These exist in glibc, but under slightly different names (generally one extra
+# or one fewer _). TODO: check against glibc names.
+libresolv_stuff = set([
+ '__res_send_setqhook',
+ '__res_send_setrhook',
+ '_resolv_flush_cache_for_net',
+ '_resolv_set_nameservers_for_net',
+ 'dn_expand',
+ 'nsdispatch',
+])
+# libstdc++ stuff we took over.
+libstdcxx_stuff = set([
+ # new, delete, nothrow
+ '_ZSt7nothrow',
+ '_ZdaPv',
+ '_ZdaPvRKSt9nothrow_t',
+ '_ZdlPv',
+ '_ZdlPvRKSt9nothrow_t',
+ '_Znam',
+ '_ZnamRKSt9nothrow_t',
+ '_Znwm',
+ '_ZnwmRKSt9nothrow_t',
+
+ '__cxa_guard_abort',
+ '__cxa_guard_acquire',
+ '__cxa_guard_release',
+ '__cxa_pure_virtual',
+])
+# Implementation details we know we export (and can't get away from).
+known = set([
+ '_ctype_',
+ '__libc_init',
])
-print 'glibc:'
-for symbol in sorted(glibc):
- print symbol
+if not only_unwanted:
+ print 'glibc:'
+ for symbol in sorted(glibc):
+ print symbol
-print
-print 'bionic:'
-for symbol in sorted(bionic):
- print symbol
+ print
+ print 'bionic:'
+ for symbol in sorted(bionic):
+ print symbol
-print
-print 'in bionic but not glibc:'
-allowed_stuff = (bsd_stuff | FORTIFY_stuff | linux_stuff | macro_stuff | std_stuff | weird_stuff)
+ print
+ print 'in bionic but not glibc:'
+
+allowed_stuff = (bsd_stuff | FORTIFY_stuff | linux_stuff | macro_stuff |
+ std_stuff | weird_stuff | libresolv_stuff | libstdcxx_stuff |
+ known)
for symbol in sorted((bionic - allowed_stuff).difference(glibc)):
+ if symbol in ndk_ignored:
+ symbol += '*'
print symbol
sys.exit(0)
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 77fb70c..9ab4e61 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -476,6 +476,29 @@
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;
@@ -798,6 +821,10 @@
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;
}
@@ -911,6 +938,53 @@
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;
@@ -1122,7 +1196,11 @@
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);
@@ -1301,7 +1379,11 @@
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);
@@ -1561,6 +1643,14 @@
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;
@@ -1577,6 +1667,14 @@
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;
@@ -1961,6 +2059,18 @@
}
#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 03672b2..0ea1f03 100644
--- a/linker/linker.h
+++ b/linker/linker.h
@@ -204,8 +204,12 @@
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 @@
// 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 9bc2557..be05230 100644
--- a/tests/dlfcn_test.cpp
+++ b/tests/dlfcn_test.cpp
@@ -106,6 +106,39 @@
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 7ed3e7b..bca2047 100644
--- a/tests/libs/Android.mk
+++ b/tests/libs/Android.mk
@@ -115,6 +115,20 @@
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 to test local symbol lookup
# -----------------------------------------------------------------------------
libtest_local_symbol_src_files := \
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