linker: add dlvsym(3)

This changes implements dlvsym - dlsym for versioned symbols.

Bug: http://b/22865643
Change-Id: Ic90a60d512104261a1416c43f9100f0d88e3b46f
diff --git a/tests/dlfcn_test.cpp b/tests/dlfcn_test.cpp
index 30eea4a..81479d5 100644
--- a/tests/dlfcn_test.cpp
+++ b/tests/dlfcn_test.cpp
@@ -624,8 +624,10 @@
   handle = dlopen("libtest_with_dependency_loop.so", RTLD_NOW | RTLD_NOLOAD);
   ASSERT_TRUE(handle == nullptr);
 #ifdef __BIONIC__
-  // TODO: glibc returns nullptr on dlerror() here. Is it bug?
   ASSERT_STREQ("dlopen failed: library \"libtest_with_dependency_loop.so\" wasn't loaded and RTLD_NOLOAD prevented it", dlerror());
+#else
+  // TODO: glibc returns nullptr on dlerror() here. Is it bug?
+  ASSERT_TRUE(dlerror() == nullptr);
 #endif
 
   handle = dlopen("libtest_with_dependency_a.so", RTLD_NOW | RTLD_NOLOAD);
@@ -763,14 +765,6 @@
   ASSERT_STREQ("dlsym failed: library handle is null", dlerror());
 #endif
 
-  // NULL symbol name.
-#if defined(__BIONIC__)
-  // glibc marks this parameter non-null and SEGVs if you cheat.
-  sym = dlsym(self, nullptr);
-  ASSERT_TRUE(sym == nullptr);
-  ASSERT_STREQ("dlsym failed: symbol name is null", dlerror());
-#endif
-
   // Symbol that doesn't exist.
   sym = dlsym(self, "ThisSymbolDoesNotExist");
   ASSERT_TRUE(sym == nullptr);
@@ -1054,6 +1048,26 @@
   dlclose(handle);
 }
 
+TEST(dlfcn, dlvsym_smoke) {
+  void* handle = dlopen("libtest_versioned_lib.so", RTLD_NOW);
+  ASSERT_TRUE(handle != nullptr) << dlerror();
+  typedef int (*fn_t)();
+
+  {
+    fn_t fn = reinterpret_cast<fn_t>(dlvsym(handle, "versioned_function", "nonversion"));
+    ASSERT_TRUE(fn == nullptr);
+    ASSERT_SUBSTR("undefined symbol: versioned_function, version nonversion", dlerror());
+  }
+
+  {
+    fn_t fn = reinterpret_cast<fn_t>(dlvsym(handle, "versioned_function", "TESTLIB_V2"));
+    ASSERT_TRUE(fn != nullptr) << dlerror();
+    ASSERT_EQ(2, fn());
+  }
+
+  dlclose(handle);
+}
+
 // This preempts the implementation from libtest_versioned_lib.so
 extern "C" int version_zero_function() {
   return 0;