Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5db5d198 authored by Jiyong Park's avatar Jiyong Park
Browse files

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
parent c1c6008b
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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());
  }
+84 −1
Original line number Diff line number Diff line
@@ -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
+87 −49
Original line number Diff line number Diff line
@@ -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;
  Result<std::vector<std::string>> result = ParseConfig(file_content, filter_fn);
  if (!result) {
    return Errorf("Cannot parse {}: {}", configFile, result.error().message());
  }
#endif
      trimmed_line.resize(space_pos);
    }

    auto ret = check_soname(trimmed_line);
    if (!ret) {
      return ret.error();
    }
    sonames.push_back(trimmed_line);
  }
  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
+24 −1
Original line number Diff line number Diff line
@@ -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