add nopreload option in public.libraries.txt

A lib with 'nopreload' option in public.libraries.txt is not preloaded
during zygote. This is useful for seldom used public libraries; they
don't contribute to the zygote startup time and only affect the apps
that they are used.

Bug: 132911956
Test: libnativeloader_test
Change-Id: I6f97c90e6721aec7f2f96c8fc7b963b34f8edd3e
This commit is contained in:
Jiyong Park 2019-07-22 20:29:08 +09:00
parent c1c6008b78
commit 5db5d198ff
4 changed files with 196 additions and 52 deletions

View file

@ -124,7 +124,7 @@ void LibraryNamespaces::Initialize() {
// we might as well end up loading them from /system/lib or /product/lib
// For now we rely on CTS test to catch things like this but
// it should probably be addressed in the future.
for (const auto& soname : android::base::Split(default_public_libraries(), ":")) {
for (const auto& soname : android::base::Split(preloadable_public_libraries(), ":")) {
LOG_ALWAYS_FATAL_IF(dlopen(soname.c_str(), RTLD_NOW | RTLD_NODELETE) == nullptr,
"Error preloading public library %s: %s", soname.c_str(), dlerror());
}

View file

@ -29,6 +29,7 @@
#include "public_libraries.h"
using namespace ::testing;
using namespace ::android::nativeloader::internal;
namespace android {
namespace nativeloader {
@ -289,7 +290,7 @@ class NativeLoaderTest : public ::testing::TestWithParam<bool> {
void SetExpectations() {
std::vector<std::string> default_public_libs =
android::base::Split(default_public_libraries(), ":");
android::base::Split(preloadable_public_libraries(), ":");
for (auto l : default_public_libs) {
EXPECT_CALL(*mock, dlopen(StrEq(l.c_str()), RTLD_NOW | RTLD_NODELETE))
.WillOnce(Return(any_nonnull));
@ -576,5 +577,87 @@ TEST_P(NativeLoaderTest_Create, TwoApks) {
INSTANTIATE_TEST_SUITE_P(NativeLoaderTests_Create, NativeLoaderTest_Create, testing::Bool());
const std::function<Result<bool>(const struct ConfigEntry&)> always_true =
[](const struct ConfigEntry&) -> Result<bool> { return true; };
TEST(NativeLoaderConfigParser, NamesAndComments) {
const char file_content[] = R"(
######
libA.so
#libB.so
libC.so
libD.so
#### libE.so
)";
const std::vector<std::string> expected_result = {"libA.so", "libC.so", "libD.so"};
Result<std::vector<std::string>> result = ParseConfig(file_content, always_true);
ASSERT_TRUE(result) << result.error().message();
ASSERT_EQ(expected_result, *result);
}
TEST(NativeLoaderConfigParser, WithBitness) {
const char file_content[] = R"(
libA.so 32
libB.so 64
libC.so
)";
#if defined(__LP64__)
const std::vector<std::string> expected_result = {"libB.so", "libC.so"};
#else
const std::vector<std::string> expected_result = {"libA.so", "libC.so"};
#endif
Result<std::vector<std::string>> result = ParseConfig(file_content, always_true);
ASSERT_TRUE(result) << result.error().message();
ASSERT_EQ(expected_result, *result);
}
TEST(NativeLoaderConfigParser, WithNoPreload) {
const char file_content[] = R"(
libA.so nopreload
libB.so nopreload
libC.so
)";
const std::vector<std::string> expected_result = {"libC.so"};
Result<std::vector<std::string>> result =
ParseConfig(file_content,
[](const struct ConfigEntry& entry) -> Result<bool> { return !entry.nopreload; });
ASSERT_TRUE(result) << result.error().message();
ASSERT_EQ(expected_result, *result);
}
TEST(NativeLoaderConfigParser, WithNoPreloadAndBitness) {
const char file_content[] = R"(
libA.so nopreload 32
libB.so 64 nopreload
libC.so 32
libD.so 64
libE.so nopreload
)";
#if defined(__LP64__)
const std::vector<std::string> expected_result = {"libD.so"};
#else
const std::vector<std::string> expected_result = {"libC.so"};
#endif
Result<std::vector<std::string>> result =
ParseConfig(file_content,
[](const struct ConfigEntry& entry) -> Result<bool> { return !entry.nopreload; });
ASSERT_TRUE(result) << result.error().message();
ASSERT_EQ(expected_result, *result);
}
TEST(NativeLoaderConfigParser, RejectMalformed) {
ASSERT_FALSE(ParseConfig("libA.so 32 64", always_true));
ASSERT_FALSE(ParseConfig("libA.so 32 32", always_true));
ASSERT_FALSE(ParseConfig("libA.so 32 nopreload 64", always_true));
ASSERT_FALSE(ParseConfig("32 libA.so nopreload", always_true));
ASSERT_FALSE(ParseConfig("nopreload libA.so 32", always_true));
ASSERT_FALSE(ParseConfig("libA.so nopreload # comment", always_true));
}
} // namespace nativeloader
} // namespace android

View file

@ -34,7 +34,8 @@
namespace android::nativeloader {
using namespace std::string_literals;
using namespace internal;
using namespace ::std::string_literals;
using android::base::ErrnoError;
using android::base::Errorf;
using android::base::Result;
@ -95,53 +96,21 @@ void InsertVndkVersionStr(std::string* file_name) {
file_name->insert(insert_pos, vndk_version_str());
}
const std::function<Result<void>(const std::string&)> always_true =
[](const std::string&) -> Result<void> { return {}; };
const std::function<Result<bool>(const struct ConfigEntry&)> always_true =
[](const struct ConfigEntry&) -> Result<bool> { return true; };
Result<std::vector<std::string>> ReadConfig(
const std::string& configFile,
const std::function<Result<void>(const std::string& /* soname */)>& check_soname) {
// Read list of public native libraries from the config file.
const std::function<Result<bool>(const ConfigEntry& /* entry */)>& filter_fn) {
std::string file_content;
if (!base::ReadFileToString(configFile, &file_content)) {
return ErrnoError();
}
std::vector<std::string> lines = base::Split(file_content, "\n");
std::vector<std::string> sonames;
for (auto& line : lines) {
auto trimmed_line = base::Trim(line);
if (trimmed_line[0] == '#' || trimmed_line.empty()) {
continue;
}
size_t space_pos = trimmed_line.rfind(' ');
if (space_pos != std::string::npos) {
std::string type = trimmed_line.substr(space_pos + 1);
if (type != "32" && type != "64") {
return Errorf("Malformed line: {}", line);
}
#if defined(__LP64__)
// Skip 32 bit public library.
if (type == "32") {
continue;
}
#else
// Skip 64 bit public library.
if (type == "64") {
continue;
}
#endif
trimmed_line.resize(space_pos);
}
auto ret = check_soname(trimmed_line);
if (!ret) {
return ret.error();
}
sonames.push_back(trimmed_line);
Result<std::vector<std::string>> result = ParseConfig(file_content, filter_fn);
if (!result) {
return Errorf("Cannot parse {}: {}", configFile, result.error().message());
}
return sonames;
return result;
}
void ReadExtensionLibraries(const char* dirname, std::vector<std::string>* sonames) {
@ -165,13 +134,13 @@ void ReadExtensionLibraries(const char* dirname, std::vector<std::string>* sonam
config_file_path.c_str());
auto ret = ReadConfig(
config_file_path, [&company_name](const std::string& soname) -> Result<void> {
if (android::base::StartsWith(soname, "lib") &&
android::base::EndsWith(soname, "." + company_name + ".so")) {
return {};
config_file_path, [&company_name](const struct ConfigEntry& entry) -> Result<bool> {
if (android::base::StartsWith(entry.soname, "lib") &&
android::base::EndsWith(entry.soname, "." + company_name + ".so")) {
return true;
} else {
return Errorf("Library name \"{}\" does not end with the company name {}.", soname,
company_name);
return Errorf("Library name \"{}\" does not end with the company name {}.",
entry.soname, company_name);
}
});
if (ret) {
@ -185,9 +154,16 @@ void ReadExtensionLibraries(const char* dirname, std::vector<std::string>* sonam
}
}
static std::string InitDefaultPublicLibraries() {
static std::string InitDefaultPublicLibraries(bool for_preload) {
std::string config_file = root_dir() + kDefaultPublicLibrariesFile;
auto sonames = ReadConfig(config_file, always_true);
auto sonames =
ReadConfig(config_file, [&for_preload](const struct ConfigEntry& entry) -> Result<bool> {
if (for_preload) {
return !entry.nopreload;
} else {
return true;
}
});
if (!sonames) {
LOG_ALWAYS_FATAL("Error reading public native library list from \"%s\": %s",
config_file.c_str(), sonames.error().message().c_str());
@ -290,8 +266,13 @@ static std::string InitNeuralNetworksPublicLibraries() {
} // namespace
const std::string& preloadable_public_libraries() {
static std::string list = InitDefaultPublicLibraries(/*for_preload*/ true);
return list;
}
const std::string& default_public_libraries() {
static std::string list = InitDefaultPublicLibraries();
static std::string list = InitDefaultPublicLibraries(/*for_preload*/ false);
return list;
}
@ -325,4 +306,61 @@ const std::string& vndksp_libraries() {
return list;
}
namespace internal {
// Exported for testing
Result<std::vector<std::string>> ParseConfig(
const std::string& file_content,
const std::function<Result<bool>(const ConfigEntry& /* entry */)>& filter_fn) {
std::vector<std::string> lines = base::Split(file_content, "\n");
std::vector<std::string> sonames;
for (auto& line : lines) {
auto trimmed_line = base::Trim(line);
if (trimmed_line[0] == '#' || trimmed_line.empty()) {
continue;
}
std::vector<std::string> tokens = android::base::Split(trimmed_line, " ");
if (tokens.size() < 1 || tokens.size() > 3) {
return Errorf("Malformed line \"{}\"", line);
}
struct ConfigEntry entry = {.soname = "", .nopreload = false, .bitness = ALL};
size_t i = tokens.size();
while (i-- > 0) {
if (tokens[i] == "nopreload") {
entry.nopreload = true;
} else if (tokens[i] == "32" || tokens[i] == "64") {
if (entry.bitness != ALL) {
return Errorf("Malformed line \"{}\": bitness can be specified only once", line);
}
entry.bitness = tokens[i] == "32" ? ONLY_32 : ONLY_64;
} else {
if (i != 0) {
return Errorf("Malformed line \"{}\"", line);
}
entry.soname = tokens[i];
}
}
// skip 32-bit lib on 64-bit process and vice versa
#if defined(__LP64__)
if (entry.bitness == ONLY_32) continue;
#else
if (entry.bitness == ONLY_64) continue;
#endif
Result<bool> ret = filter_fn(entry);
if (!ret) {
return ret.error();
}
if (*ret) {
// filter_fn has returned true.
sonames.push_back(entry.soname);
}
}
return sonames;
}
} // namespace internal
} // namespace android::nativeloader

View file

@ -15,13 +15,19 @@
*/
#pragma once
#include <algorithm>
#include <string>
#include <android-base/result.h>
namespace android::nativeloader {
using android::base::Result;
// These provide the list of libraries that are available to the namespace for apps.
// Not all of the libraries are available to apps. Depending on the context,
// e.g., if it is a vendor app or not, different set of libraries are made available.
const std::string& preloadable_public_libraries();
const std::string& default_public_libraries();
const std::string& runtime_public_libraries();
const std::string& vendor_public_libraries();
@ -30,4 +36,21 @@ const std::string& neuralnetworks_public_libraries();
const std::string& llndk_libraries();
const std::string& vndksp_libraries();
}; // namespace android::nativeloader
// These are exported for testing
namespace internal {
enum Bitness { ALL = 0, ONLY_32, ONLY_64 };
struct ConfigEntry {
std::string soname;
bool nopreload;
Bitness bitness;
};
Result<std::vector<std::string>> ParseConfig(
const std::string& file_content,
const std::function<Result<bool>(const ConfigEntry& /* entry */)>& filter_fn);
} // namespace internal
} // namespace android::nativeloader