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:
parent
c1c6008b78
commit
5db5d198ff
4 changed files with 196 additions and 52 deletions
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue