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