diff options
Diffstat (limited to 'tools/hiddenapi/hiddenapi.cc')
| -rw-r--r-- | tools/hiddenapi/hiddenapi.cc | 378 | 
1 files changed, 378 insertions, 0 deletions
diff --git a/tools/hiddenapi/hiddenapi.cc b/tools/hiddenapi/hiddenapi.cc new file mode 100644 index 0000000000..c893da646d --- /dev/null +++ b/tools/hiddenapi/hiddenapi.cc @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fstream> +#include <iostream> +#include <unordered_set> + +#include "android-base/stringprintf.h" +#include "android-base/strings.h" + +#include "base/unix_file/fd_file.h" +#include "dex/art_dex_file_loader.h" +#include "dex/dex_file-inl.h" +#include "hidden_api_access_flags.h" +#include "mem_map.h" +#include "os.h" + +namespace art { + +static int original_argc; +static char** original_argv; + +static std::string CommandLine() { +  std::vector<std::string> command; +  for (int i = 0; i < original_argc; ++i) { +    command.push_back(original_argv[i]); +  } +  return android::base::Join(command, ' '); +} + +static void UsageErrorV(const char* fmt, va_list ap) { +  std::string error; +  android::base::StringAppendV(&error, fmt, ap); +  LOG(ERROR) << error; +} + +static void UsageError(const char* fmt, ...) { +  va_list ap; +  va_start(ap, fmt); +  UsageErrorV(fmt, ap); +  va_end(ap); +} + +NO_RETURN static void Usage(const char* fmt, ...) { +  va_list ap; +  va_start(ap, fmt); +  UsageErrorV(fmt, ap); +  va_end(ap); + +  UsageError("Command: %s", CommandLine().c_str()); +  UsageError("Usage: hiddenapi [options]..."); +  UsageError(""); +  UsageError("  --dex=<filename>: specify dex file whose members' access flags are to be set."); +  UsageError("      At least one --dex parameter must be specified."); +  UsageError(""); +  UsageError("  --light-greylist=<filename>:"); +  UsageError("  --dark-greylist=<filename>:"); +  UsageError("  --blacklist=<filename>: text files with signatures of methods/fields to be marked"); +  UsageError("      greylisted/blacklisted respectively. At least one list must be provided."); +  UsageError(""); +  UsageError("  --print-hidden-api: dump a list of marked methods/fields to the standard output."); +  UsageError("      There is no indication which API category they belong to."); +  UsageError(""); + +  exit(EXIT_FAILURE); +} + +class DexClass { + public: +  DexClass(const DexFile& dex_file, uint32_t idx) +      : dex_file_(dex_file), class_def_(dex_file.GetClassDef(idx)) {} + +  const DexFile& GetDexFile() const { return dex_file_; } + +  const dex::TypeIndex GetClassIndex() const { return class_def_.class_idx_; } + +  const uint8_t* GetData() const { return dex_file_.GetClassData(class_def_); } + +  const char* GetDescriptor() const { return dex_file_.GetClassDescriptor(class_def_); } + + private: +  const DexFile& dex_file_; +  const DexFile::ClassDef& class_def_; +}; + +class DexMember { + public: +  DexMember(const DexClass& klass, const ClassDataItemIterator& it) +      : klass_(klass), it_(it) { +    DCHECK_EQ(it_.IsAtMethod() ? GetMethodId().class_idx_ : GetFieldId().class_idx_, +              klass_.GetClassIndex()); +  } + +  // Sets hidden bits in access flags and writes them back into the DEX in memory. +  // Note that this will not update the cached data of ClassDataItemIterator +  // until it iterates over this item again and therefore will fail a CHECK if +  // it is called multiple times on the same DexMember. +  void SetHidden(HiddenApiAccessFlags::ApiList value) { +    const uint32_t old_flags = it_.GetRawMemberAccessFlags(); +    const uint32_t new_flags = HiddenApiAccessFlags::EncodeForDex(old_flags, value); +    CHECK_EQ(UnsignedLeb128Size(new_flags), UnsignedLeb128Size(old_flags)); + +    // Locate the LEB128-encoded access flags in class data. +    // `ptr` initially points to the next ClassData item. We iterate backwards +    // until we hit the terminating byte of the previous Leb128 value. +    const uint8_t* ptr = it_.DataPointer(); +    if (it_.IsAtMethod()) { +      ptr = ReverseSearchUnsignedLeb128(ptr); +      DCHECK_EQ(DecodeUnsignedLeb128WithoutMovingCursor(ptr), it_.GetMethodCodeItemOffset()); +    } +    ptr = ReverseSearchUnsignedLeb128(ptr); +    DCHECK_EQ(DecodeUnsignedLeb128WithoutMovingCursor(ptr), old_flags); + +    // Overwrite the access flags. +    UpdateUnsignedLeb128(const_cast<uint8_t*>(ptr), new_flags); +  } + +  // Returns true if this member's API entry is in `list`. +  bool IsOnApiList(const std::unordered_set<std::string>& list) const { +    return list.find(GetApiEntry()) != list.end(); +  } + +  // Constructs a string with a unique signature of this class member. +  std::string GetApiEntry() const { +    std::stringstream ss; +    ss << klass_.GetDescriptor() << "->"; +    if (it_.IsAtMethod()) { +      const DexFile::MethodId& mid = GetMethodId(); +      ss << klass_.GetDexFile().GetMethodName(mid) +         << klass_.GetDexFile().GetMethodSignature(mid).ToString(); +    } else { +      const DexFile::FieldId& fid = GetFieldId(); +      ss << klass_.GetDexFile().GetFieldName(fid) << ":" +         << klass_.GetDexFile().GetFieldTypeDescriptor(fid); +    } +    return ss.str(); +  } + + private: +  inline const DexFile::MethodId& GetMethodId() const { +    DCHECK(it_.IsAtMethod()); +    return klass_.GetDexFile().GetMethodId(it_.GetMemberIndex()); +  } + +  inline const DexFile::FieldId& GetFieldId() const { +    DCHECK(!it_.IsAtMethod()); +    return klass_.GetDexFile().GetFieldId(it_.GetMemberIndex()); +  } + +  const DexClass& klass_; +  const ClassDataItemIterator& it_; +}; + +class HiddenApi FINAL { + public: +  HiddenApi() : print_hidden_api_(false) {} + +  void ParseArgs(int argc, char** argv) { +    original_argc = argc; +    original_argv = argv; + +    android::base::InitLogging(argv); + +    // Skip over the command name. +    argv++; +    argc--; + +    if (argc == 0) { +      Usage("No arguments specified"); +    } + +    for (int i = 0; i < argc; ++i) { +      const StringPiece option(argv[i]); +      const bool log_options = false; +      if (log_options) { +        LOG(INFO) << "hiddenapi: option[" << i << "]=" << argv[i]; +      } +      if (option == "--print-hidden-api") { +        print_hidden_api_ = true; +      } else if (option.starts_with("--dex=")) { +        dex_paths_.push_back(option.substr(strlen("--dex=")).ToString()); +      } else if (option.starts_with("--light-greylist=")) { +        light_greylist_path_ = option.substr(strlen("--light-greylist=")).ToString(); +      } else if (option.starts_with("--dark-greylist=")) { +        dark_greylist_path_ = option.substr(strlen("--dark-greylist=")).ToString(); +      } else if (option.starts_with("--blacklist=")) { +        blacklist_path_ = option.substr(strlen("--blacklist=")).ToString(); +      } else { +        Usage("Unknown argument '%s'", option.data()); +      } +    } +  } + +  bool ProcessDexFiles() { +    if (dex_paths_.empty()) { +      Usage("No DEX files specified"); +    } + +    if (light_greylist_path_.empty() && dark_greylist_path_.empty() && blacklist_path_.empty()) { +      Usage("No API file specified"); +    } + +    if (!light_greylist_path_.empty() && !OpenApiFile(light_greylist_path_, &light_greylist_)) { +      return false; +    } + +    if (!dark_greylist_path_.empty() && !OpenApiFile(dark_greylist_path_, &dark_greylist_)) { +      return false; +    } + +    if (!blacklist_path_.empty() && !OpenApiFile(blacklist_path_, &blacklist_)) { +      return false; +    } + +    MemMap::Init(); +    if (!OpenDexFiles()) { +      return false; +    } + +    DCHECK(!dex_files_.empty()); +    for (auto& dex_file : dex_files_) { +      CategorizeAllClasses(*dex_file.get()); +    } + +    UpdateDexChecksums(); +    return true; +  } + + private: +  bool OpenApiFile(const std::string& path, std::unordered_set<std::string>* list) { +    DCHECK(list->empty()); +    DCHECK(!path.empty()); + +    std::ifstream api_file(path, std::ifstream::in); +    if (api_file.fail()) { +      LOG(ERROR) << "Unable to open file '" << path << "' " << strerror(errno); +      return false; +    } + +    for (std::string line; std::getline(api_file, line);) { +      list->insert(line); +    } + +    api_file.close(); +    return true; +  } + +  bool OpenDexFiles() { +    ArtDexFileLoader dex_loader; +    DCHECK(dex_files_.empty()); + +    for (const std::string& filename : dex_paths_) { +      std::string error_msg; + +      File fd(filename.c_str(), O_RDWR, /* check_usage */ false); +      if (fd.Fd() == -1) { +        LOG(ERROR) << "Unable to open file '" << filename << "': " << strerror(errno); +        return false; +      } + +      // Memory-map the dex file with MAP_SHARED flag so that changes in memory +      // propagate to the underlying file. We run dex file verification as if +      // the dex file was not in boot claass path to check basic assumptions, +      // such as that at most one of public/private/protected flag is set. +      // We do those checks here and skip them when loading the processed file +      // into boot class path. +      std::unique_ptr<const DexFile> dex_file(dex_loader.OpenDex(fd.Release(), +                                                                 /* location */ filename, +                                                                 /* verify */ true, +                                                                 /* verify_checksum */ true, +                                                                 /* mmap_shared */ true, +                                                                 &error_msg)); +      if (dex_file.get() == nullptr) { +        LOG(ERROR) << "Open failed for '" << filename << "' " << error_msg; +        return false; +      } + +      if (!dex_file->IsStandardDexFile()) { +        LOG(ERROR) << "Expected a standard dex file '" << filename << "'"; +        return false; +      } + +      // Change the protection of the memory mapping to read-write. +      if (!dex_file->EnableWrite()) { +        LOG(ERROR) << "Failed to enable write permission for '" << filename << "'"; +        return false; +      } + +      dex_files_.push_back(std::move(dex_file)); +    } +    return true; +  } + +  void CategorizeAllClasses(const DexFile& dex_file) { +    for (uint32_t class_idx = 0; class_idx < dex_file.NumClassDefs(); ++class_idx) { +      DexClass klass(dex_file, class_idx); +      const uint8_t* klass_data = klass.GetData(); +      if (klass_data == nullptr) { +        continue; +      } + +      for (ClassDataItemIterator it(klass.GetDexFile(), klass_data); it.HasNext(); it.Next()) { +        DexMember member(klass, it); + +        // Catagorize member and overwrite its access flags. +        // Note that if a member appears on multiple API lists, it will be categorized +        // as the strictest. +        bool is_hidden = true; +        if (member.IsOnApiList(blacklist_)) { +          member.SetHidden(HiddenApiAccessFlags::kBlacklist); +        } else if (member.IsOnApiList(dark_greylist_)) { +          member.SetHidden(HiddenApiAccessFlags::kDarkGreylist); +        } else if (member.IsOnApiList(light_greylist_)) { +          member.SetHidden(HiddenApiAccessFlags::kLightGreylist); +        } else { +          member.SetHidden(HiddenApiAccessFlags::kWhitelist); +          is_hidden = false; +        } + +        if (print_hidden_api_ && is_hidden) { +          std::cout << member.GetApiEntry() << std::endl; +        } +      } +    } +  } + +  void UpdateDexChecksums() { +    for (auto& dex_file : dex_files_) { +      // Obtain a writeable pointer to the dex header. +      DexFile::Header* header = const_cast<DexFile::Header*>(&dex_file->GetHeader()); +      // Recalculate checksum and overwrite the value in the header. +      header->checksum_ = dex_file->CalculateChecksum(); +    } +  } + +  // Print signatures of APIs which have been grey-/blacklisted. +  bool print_hidden_api_; + +  // Paths to DEX files which should be processed. +  std::vector<std::string> dex_paths_; + +  // Paths to text files which contain the lists of API members. +  std::string light_greylist_path_; +  std::string dark_greylist_path_; +  std::string blacklist_path_; + +  // Opened DEX files. Note that these are opened as `const` but eventually will be written into. +  std::vector<std::unique_ptr<const DexFile>> dex_files_; + +  // Signatures of DEX members loaded from `light_greylist_path_`, `dark_greylist_path_`, +  // `blacklist_path_`. +  std::unordered_set<std::string> light_greylist_; +  std::unordered_set<std::string> dark_greylist_; +  std::unordered_set<std::string> blacklist_; +}; + +}  // namespace art + +int main(int argc, char** argv) { +  art::HiddenApi hiddenapi; + +  // Parse arguments. Argument mistakes will lead to exit(EXIT_FAILURE) in UsageError. +  hiddenapi.ParseArgs(argc, argv); +  return hiddenapi.ProcessDexFiles() ? EXIT_SUCCESS : EXIT_FAILURE; +}  |