summaryrefslogtreecommitdiff
path: root/tools/hiddenapi/hiddenapi.cc
diff options
context:
space:
mode:
Diffstat (limited to 'tools/hiddenapi/hiddenapi.cc')
-rw-r--r--tools/hiddenapi/hiddenapi.cc378
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;
+}