From 5db5d198ffbf0e4b187a7b6a741168bd7471b8a4 Mon Sep 17 00:00:00 2001 From: Jiyong Park Date: Mon, 22 Jul 2019 20:29:08 +0900 Subject: [PATCH] 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 --- libnativeloader/library_namespaces.cpp | 2 +- libnativeloader/native_loader_test.cpp | 85 +++++++++++++++- libnativeloader/public_libraries.cpp | 136 ++++++++++++++++--------- libnativeloader/public_libraries.h | 25 ++++- 4 files changed, 196 insertions(+), 52 deletions(-) diff --git a/libnativeloader/library_namespaces.cpp b/libnativeloader/library_namespaces.cpp index 7f5768c42..9a33b5576 100644 --- a/libnativeloader/library_namespaces.cpp +++ b/libnativeloader/library_namespaces.cpp @@ -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()); } diff --git a/libnativeloader/native_loader_test.cpp b/libnativeloader/native_loader_test.cpp index a641109bc..75255b696 100644 --- a/libnativeloader/native_loader_test.cpp +++ b/libnativeloader/native_loader_test.cpp @@ -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 { void SetExpectations() { std::vector 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(const struct ConfigEntry&)> always_true = + [](const struct ConfigEntry&) -> Result { return true; }; + +TEST(NativeLoaderConfigParser, NamesAndComments) { + const char file_content[] = R"( +###### + +libA.so +#libB.so + + + libC.so +libD.so + #### libE.so +)"; + const std::vector expected_result = {"libA.so", "libC.so", "libD.so"}; + Result> 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 expected_result = {"libB.so", "libC.so"}; +#else + const std::vector expected_result = {"libA.so", "libC.so"}; +#endif + Result> 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 expected_result = {"libC.so"}; + Result> result = + ParseConfig(file_content, + [](const struct ConfigEntry& entry) -> Result { 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 expected_result = {"libD.so"}; +#else + const std::vector expected_result = {"libC.so"}; +#endif + Result> result = + ParseConfig(file_content, + [](const struct ConfigEntry& entry) -> Result { 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 diff --git a/libnativeloader/public_libraries.cpp b/libnativeloader/public_libraries.cpp index 6cee668a1..369436058 100644 --- a/libnativeloader/public_libraries.cpp +++ b/libnativeloader/public_libraries.cpp @@ -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(const std::string&)> always_true = - [](const std::string&) -> Result { return {}; }; +const std::function(const struct ConfigEntry&)> always_true = + [](const struct ConfigEntry&) -> Result { return true; }; Result> ReadConfig( const std::string& configFile, - const std::function(const std::string& /* soname */)>& check_soname) { - // Read list of public native libraries from the config file. + const std::function(const ConfigEntry& /* entry */)>& filter_fn) { std::string file_content; if (!base::ReadFileToString(configFile, &file_content)) { return ErrnoError(); } - - std::vector lines = base::Split(file_content, "\n"); - - std::vector 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> 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* sonames) { @@ -165,13 +134,13 @@ void ReadExtensionLibraries(const char* dirname, std::vector* sonam config_file_path.c_str()); auto ret = ReadConfig( - config_file_path, [&company_name](const std::string& soname) -> Result { - if (android::base::StartsWith(soname, "lib") && - android::base::EndsWith(soname, "." + company_name + ".so")) { - return {}; + config_file_path, [&company_name](const struct ConfigEntry& entry) -> Result { + 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* 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 { + 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> ParseConfig( + const std::string& file_content, + const std::function(const ConfigEntry& /* entry */)>& filter_fn) { + std::vector lines = base::Split(file_content, "\n"); + + std::vector sonames; + for (auto& line : lines) { + auto trimmed_line = base::Trim(line); + if (trimmed_line[0] == '#' || trimmed_line.empty()) { + continue; + } + + std::vector 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 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 diff --git a/libnativeloader/public_libraries.h b/libnativeloader/public_libraries.h index 9bb3366da..2de417218 100644 --- a/libnativeloader/public_libraries.h +++ b/libnativeloader/public_libraries.h @@ -15,13 +15,19 @@ */ #pragma once +#include #include +#include + 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> ParseConfig( + const std::string& file_content, + const std::function(const ConfigEntry& /* entry */)>& filter_fn); + +} // namespace internal + +} // namespace android::nativeloader