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.cc458
1 files changed, 172 insertions, 286 deletions
diff --git a/tools/hiddenapi/hiddenapi.cc b/tools/hiddenapi/hiddenapi.cc
index aee3f9acd3..5c750af4aa 100644
--- a/tools/hiddenapi/hiddenapi.cc
+++ b/tools/hiddenapi/hiddenapi.cc
@@ -16,14 +16,15 @@
#include <fstream>
#include <iostream>
+#include <iterator>
#include <map>
#include <set>
#include <string>
#include <string_view>
+#include <vector>
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
-
#include "base/bit_utils.h"
#include "base/hiddenapi_flags.h"
#include "base/mem_map.h"
@@ -34,6 +35,7 @@
#include "dex/art_dex_file_loader.h"
#include "dex/class_accessor-inl.h"
#include "dex/dex_file-inl.h"
+#include "dex/dex_file_structs.h"
namespace art {
namespace hiddenapi {
@@ -244,8 +246,15 @@ class DexMember {
class ClassPath final {
public:
- ClassPath(const std::vector<std::string>& dex_paths, bool open_writable, bool ignore_empty) {
- OpenDexFiles(dex_paths, open_writable, ignore_empty);
+ ClassPath(const std::vector<std::string>& dex_paths, bool ignore_empty) {
+ OpenDexFiles(dex_paths, ignore_empty);
+ }
+
+ template <typename Fn>
+ void ForEachDexClass(const DexFile* dex_file, Fn fn) {
+ for (ClassAccessor accessor : dex_file->GetClasses()) {
+ fn(DexClass(accessor));
+ }
}
template<typename Fn>
@@ -283,47 +292,18 @@ class ClassPath final {
}
private:
- void OpenDexFiles(const std::vector<std::string>& dex_paths,
- bool open_writable,
- bool ignore_empty) {
- ArtDexFileLoader dex_loader;
+ void OpenDexFiles(const std::vector<std::string>& dex_paths, bool ignore_empty) {
std::string error_msg;
- if (open_writable) {
- for (const std::string& filename : dex_paths) {
- File fd(filename.c_str(), O_RDWR, /* check_usage= */ false);
- CHECK_NE(fd.Fd(), -1) << "Unable to open file '" << filename << "': " << strerror(errno);
-
- // 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));
- CHECK(dex_file.get() != nullptr) << "Open failed for '" << filename << "' " << error_msg;
- CHECK(dex_file->IsStandardDexFile()) << "Expected a standard dex file '" << filename << "'";
- CHECK(dex_file->EnableWrite())
- << "Failed to enable write permission for '" << filename << "'";
- dex_files_.push_back(std::move(dex_file));
- }
- } else {
- for (const std::string& filename : dex_paths) {
- bool success = dex_loader.Open(filename.c_str(),
- /* location= */ filename,
- /* verify= */ true,
- /* verify_checksum= */ true,
- &error_msg,
- &dex_files_);
- // If requested ignore a jar with no classes.dex files.
- if (!success && ignore_empty && error_msg != "Entry not found") {
- CHECK(success) << "Open failed for '" << filename << "' " << error_msg;
- }
+ for (const std::string& filename : dex_paths) {
+ DexFileLoader dex_file_loader(filename);
+ bool success = dex_file_loader.Open(/* verify= */ true,
+ /* verify_checksum= */ true,
+ &error_msg,
+ &dex_files_);
+ // If requested ignore a jar with no classes.dex files.
+ if (!success && ignore_empty && error_msg != "Entry not found") {
+ CHECK(success) << "Open failed for '" << filename << "' " << error_msg;
}
}
}
@@ -669,215 +649,125 @@ class HiddenapiClassDataBuilder final {
// Edits a dex file, inserting a new HiddenapiClassData section.
class DexFileEditor final {
public:
- DexFileEditor(const DexFile& old_dex, const std::vector<uint8_t>& hiddenapi_class_data)
- : old_dex_(old_dex),
- hiddenapi_class_data_(hiddenapi_class_data),
- loaded_dex_header_(nullptr),
- loaded_dex_maplist_(nullptr) {}
-
- // Copies dex file into a backing data vector, appends the given HiddenapiClassData
- // and updates the MapList.
- void Encode() {
+ // Add dex file to copy to output (possibly several files for multi-dex).
+ void Add(const DexFile* dex, const std::vector<uint8_t>&& hiddenapi_data) {
// We do not support non-standard dex encodings, e.g. compact dex.
- CHECK(old_dex_.IsStandardDexFile());
-
- // If there are no data to append, copy the old dex file and return.
- if (hiddenapi_class_data_.empty()) {
- AllocateMemory(old_dex_.Size());
- Append(old_dex_.Begin(), old_dex_.Size(), /* update_header= */ false);
- return;
- }
-
- // Find the old MapList, find its size.
- const dex::MapList* old_map = old_dex_.GetMapList();
- CHECK_LT(old_map->size_, std::numeric_limits<uint32_t>::max());
-
- // Compute the size of the new dex file. We append the HiddenapiClassData,
- // one MapItem and possibly some padding to align the new MapList.
- CHECK(IsAligned<kMapListAlignment>(old_dex_.Size()))
- << "End of input dex file is not 4-byte aligned, possibly because its MapList is not "
- << "at the end of the file.";
- size_t size_delta =
- RoundUp(hiddenapi_class_data_.size(), kMapListAlignment) + sizeof(dex::MapItem);
- size_t new_size = old_dex_.Size() + size_delta;
- AllocateMemory(new_size);
-
- // Copy the old dex file into the backing data vector. Load the copied
- // dex file to obtain pointers to its header and MapList.
- Append(old_dex_.Begin(), old_dex_.Size(), /* update_header= */ false);
- ReloadDex(/* verify= */ false);
-
- // Truncate the new dex file before the old MapList. This assumes that
- // the MapList is the last entry in the dex file. This is currently true
- // for our tooling.
- // TODO: Implement the general case by zero-ing the old MapList (turning
- // it into padding.
- RemoveOldMapList();
-
- // Append HiddenapiClassData.
- size_t payload_offset = AppendHiddenapiClassData();
-
- // Wrute new MapList with an entry for HiddenapiClassData.
- CreateMapListWithNewItem(payload_offset);
-
- // Check that the pre-computed size matches the actual size.
- CHECK_EQ(offset_, new_size);
-
- // Reload to all data structures.
- ReloadDex(/* verify= */ false);
-
- // Update the dex checksum.
- UpdateChecksum();
-
- // Run DexFileVerifier on the new dex file as a CHECK.
- ReloadDex(/* verify= */ true);
+ CHECK(dex->IsStandardDexFile());
+ inputs_.emplace_back(dex, std::move(hiddenapi_data));
}
// Writes the edited dex file into a file.
void WriteTo(const std::string& path) {
- CHECK(!data_.empty());
+ std::vector<uint8_t> output;
+
+ // Copy the old dex files into the backing data vector.
+ size_t truncated_size = 0;
+ std::vector<size_t> header_offset;
+ for (size_t i = 0; i < inputs_.size(); i++) {
+ const DexFile* dex = inputs_[i].first;
+ header_offset.push_back(output.size());
+ std::copy(
+ dex->Begin(), dex->Begin() + dex->GetHeader().file_size_, std::back_inserter(output));
+
+ // Clear the old map list (make it into padding).
+ const dex::MapList* map = dex->GetMapList();
+ size_t map_off = dex->GetHeader().map_off_;
+ size_t map_size = sizeof(map->size_) + map->size_ * sizeof(map->list_[0]);
+ CHECK_LE(map_off, output.size()) << "Map list past the end of file";
+ CHECK_EQ(map_size, output.size() - map_off) << "Map list expected at the end of file";
+ std::fill_n(output.data() + map_off, map_size, 0);
+ truncated_size = output.size() - map_size;
+ }
+ output.resize(truncated_size); // Truncate last map list.
+
+ // Append the hidden api data into the backing data vector.
+ std::vector<size_t> hiddenapi_offset;
+ for (size_t i = 0; i < inputs_.size(); i++) {
+ const std::vector<uint8_t>& hiddenapi_data = inputs_[i].second;
+ output.resize(RoundUp(output.size(), kHiddenapiClassDataAlignment)); // Align.
+ hiddenapi_offset.push_back(output.size());
+ std::copy(hiddenapi_data.begin(), hiddenapi_data.end(), std::back_inserter(output));
+ }
+
+ // Update the dex headers and map lists.
+ for (size_t i = 0; i < inputs_.size(); i++) {
+ output.resize(RoundUp(output.size(), kMapListAlignment)); // Align.
+
+ const DexFile* dex = inputs_[i].first;
+ const dex::MapList* map = dex->GetMapList();
+ std::vector<dex::MapItem> items(map->list_, map->list_ + map->size_);
+
+ // Check the header entry.
+ CHECK(!items.empty());
+ CHECK_EQ(items[0].type_, DexFile::kDexTypeHeaderItem);
+ CHECK_EQ(items[0].offset_, header_offset[i]);
+
+ // Check and remove the old map list entry (it does not have to be last).
+ auto is_map_list = [](auto it) { return it.type_ == DexFile::kDexTypeMapList; };
+ auto it = std::find_if(items.begin(), items.end(), is_map_list);
+ CHECK(it != items.end());
+ CHECK_EQ(it->offset_, dex->GetHeader().map_off_);
+ items.erase(it);
+
+ // Write new map list.
+ if (!inputs_[i].second.empty()) {
+ uint32_t payload_offset = hiddenapi_offset[i];
+ items.push_back(dex::MapItem{DexFile::kDexTypeHiddenapiClassData, 0, 1u, payload_offset});
+ }
+ uint32_t map_offset = output.size();
+ items.push_back(dex::MapItem{DexFile::kDexTypeMapList, 0, 1u, map_offset});
+ uint32_t item_count = items.size();
+ Append(&output, &item_count, 1);
+ Append(&output, items.data(), items.size());
+
+ // Update header.
+ uint8_t* begin = output.data() + header_offset[i];
+ auto* header = reinterpret_cast<DexFile::Header*>(begin);
+ header->map_off_ = map_offset;
+ if (i + 1 < inputs_.size()) {
+ CHECK_EQ(header->file_size_, header_offset[i + 1] - header_offset[i]);
+ } else {
+ // Extend last dex file until the end of the file.
+ header->data_size_ = output.size() - header->data_off_;
+ header->file_size_ = output.size() - header_offset[i];
+ }
+ header->checksum_ = DexFile::CalculateChecksum(begin, header->file_size_);
+ // TODO: We should also update the SHA1 signature.
+ }
+
+ // Write the output file.
+ CHECK(!output.empty());
std::ofstream ofs(path.c_str(), std::ofstream::out | std::ofstream::binary);
- ofs.write(reinterpret_cast<const char*>(data_.data()), data_.size());
+ ofs.write(reinterpret_cast<const char*>(output.data()), output.size());
ofs.flush();
CHECK(ofs.good());
ofs.close();
+
+ ReloadDex(path.c_str());
}
private:
static constexpr size_t kMapListAlignment = 4u;
static constexpr size_t kHiddenapiClassDataAlignment = 4u;
- void ReloadDex(bool verify) {
+ void ReloadDex(const char* filename) {
std::string error_msg;
- DexFileLoader loader;
- loaded_dex_ = loader.Open(
- data_.data(),
- data_.size(),
- "test_location",
- old_dex_.GetLocationChecksum(),
- /* oat_dex_file= */ nullptr,
- /* verify= */ verify,
- /* verify_checksum= */ verify,
- &error_msg);
- if (loaded_dex_.get() == nullptr) {
- LOG(FATAL) << "Failed to load edited dex file: " << error_msg;
- UNREACHABLE();
- }
-
- // Load the location of header and map list before we start editing the file.
- loaded_dex_header_ = const_cast<DexFile::Header*>(&loaded_dex_->GetHeader());
- loaded_dex_maplist_ = const_cast<dex::MapList*>(loaded_dex_->GetMapList());
- }
-
- DexFile::Header& GetHeader() const {
- CHECK(loaded_dex_header_ != nullptr);
- return *loaded_dex_header_;
- }
-
- dex::MapList& GetMapList() const {
- CHECK(loaded_dex_maplist_ != nullptr);
- return *loaded_dex_maplist_;
- }
-
- void AllocateMemory(size_t total_size) {
- data_.clear();
- data_.resize(total_size);
- CHECK(IsAligned<kMapListAlignment>(data_.data()));
- CHECK(IsAligned<kHiddenapiClassDataAlignment>(data_.data()));
- offset_ = 0;
- }
-
- uint8_t* GetCurrentDataPtr() {
- return data_.data() + offset_;
- }
-
- void UpdateDataSize(off_t delta, bool update_header) {
- offset_ += delta;
- if (update_header) {
- DexFile::Header& header = GetHeader();
- header.file_size_ += delta;
- header.data_size_ += delta;
- }
- }
-
- template<typename T>
- T* Append(const T* src, size_t len, bool update_header = true) {
- CHECK_LE(offset_ + len, data_.size());
- uint8_t* dst = GetCurrentDataPtr();
- memcpy(dst, src, len);
- UpdateDataSize(len, update_header);
- return reinterpret_cast<T*>(dst);
- }
-
- void InsertPadding(size_t alignment) {
- size_t len = RoundUp(offset_, alignment) - offset_;
- std::vector<uint8_t> padding(len, 0);
- Append(padding.data(), padding.size());
- }
-
- void RemoveOldMapList() {
- size_t map_size = GetMapList().Size();
- uint8_t* map_start = reinterpret_cast<uint8_t*>(&GetMapList());
- CHECK_EQ(map_start + map_size, GetCurrentDataPtr()) << "MapList not at the end of dex file";
- UpdateDataSize(-static_cast<off_t>(map_size), /* update_header= */ true);
- CHECK_EQ(map_start, GetCurrentDataPtr());
- loaded_dex_maplist_ = nullptr; // do not use this map list any more
- }
-
- void CreateMapListWithNewItem(size_t payload_offset) {
- InsertPadding(/* alignment= */ kMapListAlignment);
-
- size_t new_map_offset = offset_;
- dex::MapList* map = Append(old_dex_.GetMapList(), old_dex_.GetMapList()->Size());
-
- // Check last map entry is a pointer to itself.
- dex::MapItem& old_item = map->list_[map->size_ - 1];
- CHECK(old_item.type_ == DexFile::kDexTypeMapList);
- CHECK_EQ(old_item.size_, 1u);
- CHECK_EQ(old_item.offset_, GetHeader().map_off_);
-
- // Create a new MapItem entry with new MapList details.
- dex::MapItem new_item;
- new_item.type_ = old_item.type_;
- new_item.unused_ = 0u; // initialize to ensure dex output is deterministic (b/119308882)
- new_item.size_ = old_item.size_;
- new_item.offset_ = new_map_offset;
-
- // Update pointer in the header.
- GetHeader().map_off_ = new_map_offset;
-
- // Append a new MapItem and return its pointer.
- map->size_++;
- Append(&new_item, sizeof(dex::MapItem));
-
- // Change penultimate entry to point to metadata.
- old_item.type_ = DexFile::kDexTypeHiddenapiClassData;
- old_item.size_ = 1u; // there is only one section
- old_item.offset_ = payload_offset;
- }
-
- size_t AppendHiddenapiClassData() {
- size_t payload_offset = offset_;
- CHECK_EQ(kMapListAlignment, kHiddenapiClassDataAlignment);
- CHECK(IsAligned<kHiddenapiClassDataAlignment>(payload_offset))
- << "Should not need to align the section, previous data was already aligned";
- Append(hiddenapi_class_data_.data(), hiddenapi_class_data_.size());
- return payload_offset;
+ ArtDexFileLoader loader(filename);
+ std::vector<std::unique_ptr<const DexFile>> dex_files;
+ bool ok = loader.Open(/*verify*/ true,
+ /*verify_checksum*/ true,
+ &error_msg,
+ &dex_files);
+ CHECK(ok) << "Failed to load edited dex file: " << error_msg;
}
- void UpdateChecksum() {
- GetHeader().checksum_ = loaded_dex_->CalculateChecksum();
+ template <typename T>
+ void Append(std::vector<uint8_t>* output, const T* src, size_t len) {
+ const uint8_t* ptr = reinterpret_cast<const uint8_t*>(src);
+ std::copy(ptr, ptr + len * sizeof(T), std::back_inserter(*output));
}
- const DexFile& old_dex_;
- const std::vector<uint8_t>& hiddenapi_class_data_;
-
- std::vector<uint8_t> data_;
- size_t offset_;
-
- std::unique_ptr<const DexFile> loaded_dex_;
- DexFile::Header* loaded_dex_header_;
- dex::MapList* loaded_dex_maplist_;
+ std::vector<std::pair<const DexFile*, const std::vector<uint8_t>>> inputs_;
};
class HiddenApi final {
@@ -991,48 +881,41 @@ class HiddenApi final {
const std::string& input_path = boot_dex_paths_[i];
const std::string& output_path = output_dex_paths_[i];
- ClassPath boot_classpath({ input_path },
- /* open_writable= */ false,
- /* ignore_empty= */ false);
- std::vector<const DexFile*> input_dex_files = boot_classpath.GetDexFiles();
- CHECK_EQ(input_dex_files.size(), 1u);
- const DexFile& input_dex = *input_dex_files[0];
-
- HiddenapiClassDataBuilder builder(input_dex);
- boot_classpath.ForEachDexClass([&](const DexClass& boot_class) {
- builder.BeginClassDef(boot_class.GetClassDefIndex());
- if (boot_class.GetData() != nullptr) {
- auto fn_shared = [&](const DexMember& boot_member) {
- auto signature = boot_member.GetApiEntry();
- auto it = api_list.find(signature);
- bool api_list_found = (it != api_list.end());
- CHECK(!force_assign_all_ || api_list_found)
- << "Could not find hiddenapi flags for dex entry: " << signature;
- if (api_list_found && it->second.GetIntValue() > max_hiddenapi_level_.GetIntValue()) {
- ApiList without_domain(it->second.GetIntValue());
- LOG(ERROR) << "Hidden api flag " << without_domain
- << " for member " << signature
- << " in " << input_path
- << " exceeds maximum allowable flag "
- << max_hiddenapi_level_;
- max_hiddenapi_level_error = true;
- } else {
- builder.WriteFlags(api_list_found ? it->second : ApiList::Sdk());
- }
- };
- auto fn_field = [&](const ClassAccessor::Field& boot_field) {
- fn_shared(DexMember(boot_class, boot_field));
- };
- auto fn_method = [&](const ClassAccessor::Method& boot_method) {
- fn_shared(DexMember(boot_class, boot_method));
- };
- boot_class.VisitFieldsAndMethods(fn_field, fn_field, fn_method, fn_method);
- }
- builder.EndClassDef(boot_class.GetClassDefIndex());
- });
-
- DexFileEditor dex_editor(input_dex, builder.GetData());
- dex_editor.Encode();
+ ClassPath boot_classpath({input_path}, /* ignore_empty= */ false);
+ DexFileEditor dex_editor;
+ for (const DexFile* input_dex : boot_classpath.GetDexFiles()) {
+ HiddenapiClassDataBuilder builder(*input_dex);
+ boot_classpath.ForEachDexClass(input_dex, [&](const DexClass& boot_class) {
+ builder.BeginClassDef(boot_class.GetClassDefIndex());
+ if (boot_class.GetData() != nullptr) {
+ auto fn_shared = [&](const DexMember& boot_member) {
+ auto signature = boot_member.GetApiEntry();
+ auto it = api_list.find(signature);
+ bool api_list_found = (it != api_list.end());
+ CHECK(!force_assign_all_ || api_list_found)
+ << "Could not find hiddenapi flags for dex entry: " << signature;
+ if (api_list_found && it->second.GetIntValue() > max_hiddenapi_level_.GetIntValue()) {
+ ApiList without_domain(it->second.GetIntValue());
+ LOG(ERROR) << "Hidden api flag " << without_domain << " for member " << signature
+ << " in " << input_path << " exceeds maximum allowable flag "
+ << max_hiddenapi_level_;
+ max_hiddenapi_level_error = true;
+ } else {
+ builder.WriteFlags(api_list_found ? it->second : ApiList::Sdk());
+ }
+ };
+ auto fn_field = [&](const ClassAccessor::Field& boot_field) {
+ fn_shared(DexMember(boot_class, boot_field));
+ };
+ auto fn_method = [&](const ClassAccessor::Method& boot_method) {
+ fn_shared(DexMember(boot_class, boot_method));
+ };
+ boot_class.VisitFieldsAndMethods(fn_field, fn_field, fn_method, fn_method);
+ }
+ builder.EndClassDef(boot_class.GetClassDefIndex());
+ });
+ dex_editor.Add(input_dex, std::move(builder.GetData()));
+ }
dex_editor.WriteTo(output_path);
}
@@ -1057,6 +940,7 @@ class HiddenApi final {
std::map<std::string, ApiList> api_flag_map;
size_t line_number = 1;
+ bool errors = false;
for (std::string line; std::getline(api_file, line); line_number++) {
// Every line contains a comma separated list with the signature as the
// first element and the api flags as the rest
@@ -1074,13 +958,21 @@ class HiddenApi final {
std::vector<std::string>::iterator apiListBegin = values.begin() + 1;
std::vector<std::string>::iterator apiListEnd = values.end();
bool success = ApiList::FromNames(apiListBegin, apiListEnd, &membership);
- CHECK(success) << path << ":" << line_number
- << ": Some flags were not recognized: " << line << kErrorHelp;
- CHECK(membership.IsValid()) << path << ":" << line_number
- << ": Invalid combination of flags: " << line << kErrorHelp;
+ if (!success) {
+ LOG(ERROR) << path << ":" << line_number
+ << ": Some flags were not recognized: " << line << kErrorHelp;
+ errors = true;
+ continue;
+ } else if (!membership.IsValid()) {
+ LOG(ERROR) << path << ":" << line_number
+ << ": Invalid combination of flags: " << line << kErrorHelp;
+ errors = true;
+ continue;
+ }
api_flag_map.emplace(signature, membership);
}
+ CHECK(!errors) << "Errors encountered while parsing file " << path;
api_file.close();
return api_flag_map;
@@ -1107,9 +999,7 @@ class HiddenApi final {
std::set<std::string> unresolved;
// Open all dex files.
- ClassPath boot_classpath(boot_dex_paths_,
- /* open_writable= */ false,
- /* ignore_empty= */ false);
+ ClassPath boot_classpath(boot_dex_paths_, /* ignore_empty= */ false);
Hierarchy boot_hierarchy(boot_classpath, fragment_, verbose_);
// Mark all boot dex members private.
@@ -1118,9 +1008,7 @@ class HiddenApi final {
});
// Open all dependency API stub dex files.
- ClassPath dependency_classpath(dependency_stub_dex_paths_,
- /* open_writable= */ false,
- /* ignore_empty= */ false);
+ ClassPath dependency_classpath(dependency_stub_dex_paths_, /* ignore_empty= */ false);
// Mark all dependency API stub dex members as coming from the dependency.
dependency_classpath.ForEachDexMember([&](const DexMember& boot_member) {
@@ -1132,9 +1020,7 @@ class HiddenApi final {
// Ignore any empty stub jars as it just means that they provide no APIs
// for the current kind, e.g. framework-sdkextensions does not provide
// any public APIs.
- ClassPath stub_classpath(android::base::Split(cp_entry.first, ":"),
- /* open_writable= */ false,
- /* ignore_empty= */ true);
+ ClassPath stub_classpath(android::base::Split(cp_entry.first, ":"), /*ignore_empty=*/true);
Hierarchy stub_hierarchy(stub_classpath, fragment_, verbose_);
const ApiStubs::Kind stub_api = cp_entry.second;