Commit 596a0dd7 authored by rockot's avatar rockot Committed by Commit bot

Enable loading native libraries with RTLD_DEEPBIND

Using RTLD_DEEPBIND by default is problematic (see comments
in native_library_posix.cc), but in the case of Mojo services
where the host binary is lightweight but otherwise may export some
conflicting symbols (e.g. tcmalloc), it is desirable.

This CL adds a NativeLibraryOptions structure and a new
LoadNativeLibraryWithOptions() call. The only supported
option (|prefer_own_symbols|) only affects behavior on
non-OSX, non-Android, POSIX systems.

The default behavior of LoadNativeLibrary is unchanged.

Also adds a unit test for general native library loading since
there wasn't one before.

BUG=594674

Review-Url: https://codereview.chromium.org/2277863002
Cr-Commit-Position: refs/heads/master@{#414605}
parent 700fe269
......@@ -555,6 +555,7 @@ component("base") {
"metrics/user_metrics.cc",
"metrics/user_metrics.h",
"metrics/user_metrics_action.h",
"native_library.cc",
"native_library.h",
"native_library_ios.mm",
"native_library_mac.mm",
......@@ -1133,6 +1134,7 @@ component("base") {
"memory/discardable_shared_memory.cc",
"memory/discardable_shared_memory.h",
"memory/shared_memory_posix.cc",
"native_library.cc",
"native_library_posix.cc",
"path_service.cc",
"process/kill.cc",
......@@ -2007,6 +2009,7 @@ test("base_unittests") {
":i18n",
":message_loop_tests",
"//base/allocator:features",
"//base/test:native_library_test_utils",
"//base/test:run_all_base_unittests",
"//base/test:test_support",
"//base/third_party/dynamic_annotations",
......@@ -2015,6 +2018,10 @@ test("base_unittests") {
"//third_party/icu",
]
data_deps = [
"//base/test:test_shared_library",
]
if (is_ios || is_mac) {
deps += [ ":base_unittests_arc" ]
}
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/native_library.h"
namespace base {
NativeLibrary LoadNativeLibrary(const FilePath& library_path,
NativeLibraryLoadError* error) {
return LoadNativeLibraryWithOptions(
library_path, NativeLibraryOptions(), error);
}
} // namespace base
......@@ -65,12 +65,32 @@ struct BASE_EXPORT NativeLibraryLoadError {
#endif // OS_WIN
};
struct BASE_EXPORT NativeLibraryOptions {
NativeLibraryOptions() = default;
NativeLibraryOptions(const NativeLibraryOptions& options) = default;
// If |true|, a loaded library is required to prefer local symbol resolution
// before considering global symbols. Note that this is already the default
// behavior on most systems. Setting this to |false| does not guarantee the
// inverse, i.e., it does not force a preference for global symbols over local
// ones.
bool prefer_own_symbols = false;
};
// Loads a native library from disk. Release it with UnloadNativeLibrary when
// you're done. Returns NULL on failure.
// If |error| is not NULL, it may be filled in on load error.
BASE_EXPORT NativeLibrary LoadNativeLibrary(const FilePath& library_path,
NativeLibraryLoadError* error);
// Loads a native library from disk. Release it with UnloadNativeLibrary when
// you're done. Returns NULL on failure.
// If |error| is not NULL, it may be filled in on load error.
BASE_EXPORT NativeLibrary LoadNativeLibraryWithOptions(
const FilePath& library_path,
const NativeLibraryOptions& options,
NativeLibraryLoadError* error);
#if defined(OS_WIN)
// Loads a native library from disk. Release it with UnloadNativeLibrary when
// you're done.
......
......@@ -15,8 +15,9 @@ std::string NativeLibraryLoadError::ToString() const {
}
// static
NativeLibrary LoadNativeLibrary(const base::FilePath& library_path,
NativeLibraryLoadError* error) {
NativeLibrary LoadNativeLibraryWithOptions(const base::FilePath& library_path,
const NativeLibraryOptions& options,
NativeLibraryLoadError* error) {
NOTIMPLEMENTED();
if (error)
error->message = "Not implemented.";
......
......@@ -39,8 +39,9 @@ std::string NativeLibraryLoadError::ToString() const {
}
// static
NativeLibrary LoadNativeLibrary(const FilePath& library_path,
NativeLibraryLoadError* error) {
NativeLibrary LoadNativeLibraryWithOptions(const FilePath& library_path,
const NativeLibraryOptions& options,
NativeLibraryLoadError* error) {
// dlopen() etc. open the file off disk.
if (library_path.Extension() == "dylib" || !DirectoryExists(library_path)) {
void* dylib = dlopen(library_path.value().c_str(), RTLD_LAZY);
......
......@@ -19,16 +19,27 @@ std::string NativeLibraryLoadError::ToString() const {
}
// static
NativeLibrary LoadNativeLibrary(const FilePath& library_path,
NativeLibraryLoadError* error) {
NativeLibrary LoadNativeLibraryWithOptions(const FilePath& library_path,
const NativeLibraryOptions& options,
NativeLibraryLoadError* error) {
// dlopen() opens the file off disk.
ThreadRestrictions::AssertIOAllowed();
// We deliberately do not use RTLD_DEEPBIND. For the history why, please
// refer to the bug tracker. Some useful bug reports to read include:
// We deliberately do not use RTLD_DEEPBIND by default. For the history why,
// please refer to the bug tracker. Some useful bug reports to read include:
// http://crbug.com/17943, http://crbug.com/17557, http://crbug.com/36892,
// and http://crbug.com/40794.
void* dl = dlopen(library_path.value().c_str(), RTLD_LAZY);
int flags = RTLD_LAZY;
#if defined(OS_ANDROID)
// Android dlopen() requires further investigation, as it might vary across
// versions. Crash here to warn developers that they're trying to rely on
// uncertain behavior.
CHECK(!options.prefer_own_symbols);
#else
if (options.prefer_own_symbols)
flags |= RTLD_DEEPBIND;
#endif
void* dl = dlopen(library_path.value().c_str(), flags);
if (!dl && error)
error->message = dlerror();
......
......@@ -3,7 +3,11 @@
// found in the LICENSE file.
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/native_library.h"
#include "base/path_service.h"
#include "base/test/native_library_test_utils.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
......@@ -36,4 +40,100 @@ TEST(NativeLibraryTest, GetNativeLibraryName) {
EXPECT_EQ(kExpectedName, GetNativeLibraryName("mylib"));
}
// We don't support dynamic loading on iOS, and ASAN will complain about our
// intentional ODR violation because of |g_native_library_exported_value| being
// defined globally both here and in the shared library.
#if !defined(OS_IOS) && !defined(ADDRESS_SANITIZER)
const char kTestLibraryName[] =
#if defined(OS_MACOSX)
"libtest_shared_library.dylib";
#elif defined(OS_ANDROID) && defined(COMPONENT_BUILD)
"libtest_shared_library.cr.so";
#elif defined(OS_POSIX)
"libtest_shared_library.so";
#elif defined(OS_WIN)
"test_shared_library.dll";
#endif
class TestLibrary {
public:
TestLibrary() : TestLibrary(NativeLibraryOptions()) {}
explicit TestLibrary(const NativeLibraryOptions& options)
: library_(nullptr) {
base::FilePath exe_path;
CHECK(base::PathService::Get(base::DIR_EXE, &exe_path));
library_ = LoadNativeLibraryWithOptions(
exe_path.AppendASCII(kTestLibraryName), options, nullptr);
CHECK(library_);
}
~TestLibrary() {
UnloadNativeLibrary(library_);
}
template <typename ReturnType, typename... Args>
ReturnType Call(const char* function_name, Args... args) {
return reinterpret_cast<ReturnType(*)(Args...)>(
GetFunctionPointerFromNativeLibrary(library_, function_name))(args...);
}
private:
NativeLibrary library_;
DISALLOW_COPY_AND_ASSIGN(TestLibrary);
};
// Verifies that we can load a native library and resolve its exported symbols.
TEST(NativeLibraryTest, LoadLibrary) {
TestLibrary library;
EXPECT_EQ(5, library.Call<int>("GetSimpleTestValue"));
}
// Android dlopen() requires further investigation, as it might vary across
// versions with respect to symbol resolution scope.
#if !defined(OS_ANDROID)
// Verifies that the |prefer_own_symbols| option satisfies its guarantee that
// a loaded library will always prefer local symbol resolution before
// considering global symbols.
TEST(NativeLibraryTest, LoadLibraryPreferOwnSymbols) {
NativeLibraryOptions options;
options.prefer_own_symbols = true;
TestLibrary library(options);
// Verify that this binary and the DSO use different storage for
// |g_native_library_exported_value|.
g_native_library_exported_value = 1;
library.Call<void>("SetExportedValue", 2);
EXPECT_EQ(1, g_native_library_exported_value);
g_native_library_exported_value = 3;
EXPECT_EQ(2, library.Call<int>("GetExportedValue"));
// Both this binary and the library link against the
// native_library_test_utils source library, which in turn exports the
// NativeLibraryTestIncrement() function whose return value depends on some
// static internal state.
//
// The DSO's GetIncrementValue() forwards to that function inside the DSO.
//
// Here we verify that direct calls to NativeLibraryTestIncrement() in this
// binary return a sequence of values independent from the sequence returned
// by GetIncrementValue(), ensuring that the DSO is calling its own local
// definition of NativeLibraryTestIncrement().
EXPECT_EQ(1, library.Call<int>("GetIncrementValue"));
EXPECT_EQ(1, NativeLibraryTestIncrement());
EXPECT_EQ(2, library.Call<int>("GetIncrementValue"));
EXPECT_EQ(3, library.Call<int>("GetIncrementValue"));
EXPECT_EQ(4, library.Call<int>("NativeLibraryTestIncrement"));
EXPECT_EQ(2, NativeLibraryTestIncrement());
EXPECT_EQ(3, NativeLibraryTestIncrement());
}
#endif // !defined(OS_ANDROID)
#endif // !defined(OS_IOS) && !defined(ADDRESS_SANITIZER)
} // namespace base
......@@ -55,8 +55,9 @@ std::string NativeLibraryLoadError::ToString() const {
}
// static
NativeLibrary LoadNativeLibrary(const FilePath& library_path,
NativeLibraryLoadError* error) {
NativeLibrary LoadNativeLibraryWithOptions(const FilePath& library_path,
const NativeLibraryOptions& options,
NativeLibraryLoadError* error) {
return LoadNativeLibraryHelper(library_path, LoadLibraryW, error);
}
......
......@@ -267,6 +267,28 @@ static_library("run_all_unittests") {
]
}
# These sources are linked into both the base_unittests binary and the test
# shared library target below.
source_set("native_library_test_utils") {
testonly = true
sources = [
"native_library_test_utils.cc",
"native_library_test_utils.h",
]
}
# This shared library is dynamically loaded by NativeLibrary unittests.
shared_library("test_shared_library") {
testonly = true
sources = [
"test_shared_library.cc",
]
deps = [
":native_library_test_utils",
]
}
static_library("run_all_base_unittests") {
# Only targets in base should depend on this, targets outside base
# should depend on run_all_unittests above.
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/test/native_library_test_utils.h"
namespace {
int g_static_value = 0;
} // namespace
extern "C" {
int g_native_library_exported_value = 0;
int NativeLibraryTestIncrement() { return ++g_static_value; }
} // extern "C"
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TEST_NATIVE_LIBRARY_TEST_UTILS_H_
#define BASE_TEST_NATIVE_LIBRARY_TEST_UTILS_H_
#include "build/build_config.h"
#if defined(OS_WIN)
#define NATIVE_LIBRARY_TEST_ALWAYS_EXPORT __declspec(dllexport)
#else
#define NATIVE_LIBRARY_TEST_ALWAYS_EXPORT __attribute__((visibility("default")))
#endif
extern "C" {
extern NATIVE_LIBRARY_TEST_ALWAYS_EXPORT int g_native_library_exported_value;
// A function which increments an internal counter value and returns its value.
// The first call returns 1, then 2, etc.
NATIVE_LIBRARY_TEST_ALWAYS_EXPORT int NativeLibraryTestIncrement();
} // extern "C"
#endif // BASE_TEST_NATIVE_LIBRARY_TEST_UTILS_H_
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/test/native_library_test_utils.h"
extern "C" {
int NATIVE_LIBRARY_TEST_ALWAYS_EXPORT GetExportedValue() {
return g_native_library_exported_value;
}
void NATIVE_LIBRARY_TEST_ALWAYS_EXPORT SetExportedValue(int value) {
g_native_library_exported_value = value;
}
// A test function used only to verify basic dynamic symbol resolution.
int NATIVE_LIBRARY_TEST_ALWAYS_EXPORT GetSimpleTestValue() {
return 5;
}
// When called by |NativeLibraryTest.LoadLibraryPreferOwnSymbols|, this should
// forward to the local definition of NativeLibraryTestIncrement(), even though
// the test module also links in the native_library_test_utils source library
// which exports it.
int NATIVE_LIBRARY_TEST_ALWAYS_EXPORT GetIncrementValue() {
return NativeLibraryTestIncrement();
}
} // extern "C"
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment