diff options
-rw-r--r-- | libdexfile/Android.bp | 11 | ||||
-rw-r--r-- | libdexfile/external/dex_file_ext.cc | 273 | ||||
-rw-r--r-- | libdexfile/external/dex_file_ext_c_test.c | 64 | ||||
-rw-r--r-- | libdexfile/external/dex_file_ext_test.cc | 150 | ||||
-rw-r--r-- | libdexfile/external/dex_file_supp.cc | 127 | ||||
-rw-r--r-- | libdexfile/external/dex_file_supp_test.cc | 246 | ||||
-rw-r--r-- | libdexfile/external/dex_file_test_data.h | 48 | ||||
-rw-r--r-- | libdexfile/external/include/art_api/dex_file_external.h | 175 | ||||
-rw-r--r-- | libdexfile/external/include/art_api/dex_file_support.h | 187 | ||||
-rw-r--r-- | libdexfile/external/libdexfile_external.map.txt | 9 | ||||
-rw-r--r-- | libdexfile/libdexfile.map.txt | 14 |
11 files changed, 675 insertions, 629 deletions
diff --git a/libdexfile/Android.bp b/libdexfile/Android.bp index e627ad017c..f1a4c54618 100644 --- a/libdexfile/Android.bp +++ b/libdexfile/Android.bp @@ -201,8 +201,7 @@ art_cc_library { "com.android.art.debug", ], stubs: { - // TODO(b/143978909): Rename and move this file to this directory. - symbol_file: "external/libdexfile_external.map.txt", + symbol_file: "libdexfile.map.txt", versions: ["1"], }, } @@ -251,8 +250,7 @@ art_cc_library { "com.android.art", ], stubs: { - // TODO(b/143978909): Rename and move this file to this directory. - symbol_file: "external/libdexfile_external.map.txt", + symbol_file: "libdexfile.map.txt", versions: ["1"], }, } @@ -336,6 +334,11 @@ art_cc_test { test_suites: ["general-tests"], srcs: [ "external/dex_file_ext_c_test.c", + "external/dex_file_ext_test.cc", + ], + shared_libs: [ + "libartbase", + "libdexfile", ], header_libs: [ "jni_headers", diff --git a/libdexfile/external/dex_file_ext.cc b/libdexfile/external/dex_file_ext.cc index 302e18800f..7c0bf2da7e 100644 --- a/libdexfile/external/dex_file_ext.cc +++ b/libdexfile/external/dex_file_ext.cc @@ -43,37 +43,36 @@ extern "C" { -// Wraps DexFile to add the caching needed by the external interface. This is -// what gets passed over as ExtDexFile*. -struct ExtDexFile { - struct MethodCacheEntry { - uint32_t offset; // Offset relative to the start of the dex file header. - uint32_t len; - uint32_t index; // Method index. - }; - - public: - std::unique_ptr<const art::DexFile> dex_file_; - explicit ExtDexFile(std::unique_ptr<const art::DexFile>&& dex_file) +struct ADexFile_Method { + ADexFile* adex; + uint32_t index; + size_t offset; + size_t size; +}; + +// Opaque implementation of ADexFile for the C interface. +struct ADexFile { + explicit ADexFile(std::unique_ptr<const art::DexFile> dex_file) : dex_file_(std::move(dex_file)) {} - bool GetMethodDefIndex(uint32_t dex_offset, uint32_t* index, uint32_t* addr, uint32_t* size) { + inline bool FindMethod(uint32_t dex_offset, /*out*/ ADexFile_Method* result) { uint32_t class_def_index; if (GetClassDefIndex(dex_offset, &class_def_index)) { art::ClassAccessor accessor(*dex_file_, class_def_index); - for (const art::ClassAccessor::Method& method : accessor.GetMethods()) { art::CodeItemInstructionAccessor code = method.GetInstructions(); if (!code.HasCodeItem()) { continue; } - - uint32_t offset = reinterpret_cast<const uint8_t*>(code.Insns()) - dex_file_->Begin(); - uint32_t len = code.InsnsSizeInBytes(); - if (offset <= dex_offset && dex_offset < offset + len) { - *index = method.GetIndex(); - *addr = offset; - *size = len; + size_t offset = reinterpret_cast<const uint8_t*>(code.Insns()) - dex_file_->Begin(); + size_t size = code.InsnsSizeInBytes(); + if (offset <= dex_offset && dex_offset < offset + size) { + *result = ADexFile_Method { + .adex = this, + .index = method.GetIndex(), + .offset = offset, + .size = size, + }; return true; } } @@ -81,40 +80,43 @@ struct ExtDexFile { return false; } - private: - bool GetClassDefIndex(uint32_t dex_offset, uint32_t* class_def_index) { - if (class_cache_.empty()) { - // Create binary search table with (end_dex_offset, class_def_index) entries. - // That is, we don't assume that dex code of given class is consecutive. - std::deque<std::pair<uint32_t, uint32_t>> cache; - for (art::ClassAccessor accessor : dex_file_->GetClasses()) { - for (const art::ClassAccessor::Method& method : accessor.GetMethods()) { - art::CodeItemInstructionAccessor code = method.GetInstructions(); - if (code.HasCodeItem()) { - int32_t offset = reinterpret_cast<const uint8_t*>(code.Insns()) - dex_file_->Begin(); - DCHECK_NE(offset, 0); - cache.emplace_back(offset + code.InsnsSizeInBytes(), accessor.GetClassDefIndex()); - } + void CreateClassCache() { + // Create binary search table with (end_dex_offset, class_def_index) entries. + // That is, we don't assume that dex code of given class is consecutive. + std::deque<std::pair<uint32_t, uint32_t>> cache; + for (art::ClassAccessor accessor : dex_file_->GetClasses()) { + for (const art::ClassAccessor::Method& method : accessor.GetMethods()) { + art::CodeItemInstructionAccessor code = method.GetInstructions(); + if (code.HasCodeItem()) { + int32_t offset = reinterpret_cast<const uint8_t*>(code.Insns()) - dex_file_->Begin(); + DCHECK_NE(offset, 0); + cache.emplace_back(offset + code.InsnsSizeInBytes(), accessor.GetClassDefIndex()); } } - std::sort(cache.begin(), cache.end()); - - // If two consecutive methods belong to same class, we can merge them. - // This tends to reduce the number of entries (used memory) by 10x. - size_t num_entries = cache.size(); - if (cache.size() > 1) { - for (auto it = std::next(cache.begin()); it != cache.end(); it++) { - if (std::prev(it)->second == it->second) { - std::prev(it)->first = 0; // Clear entry with lower end_dex_offset (mark to remove). - num_entries--; - } + } + std::sort(cache.begin(), cache.end()); + + // If two consecutive methods belong to same class, we can merge them. + // This tends to reduce the number of entries (used memory) by 10x. + size_t num_entries = cache.size(); + if (cache.size() > 1) { + for (auto it = std::next(cache.begin()); it != cache.end(); it++) { + if (std::prev(it)->second == it->second) { + std::prev(it)->first = 0; // Clear entry with lower end_dex_offset (mark to remove). + num_entries--; } } + } + + // The cache is immutable now. Store it as continuous vector to save space. + class_cache_.reserve(num_entries); + auto pred = [](auto it) { return it.first != 0; }; // Entries to copy (not cleared above). + std::copy_if(cache.begin(), cache.end(), std::back_inserter(class_cache_), pred); + } - // The cache is immutable now. Store it as continuous vector to save space. - class_cache_.reserve(num_entries); - auto pred = [](auto it) { return it.first != 0; }; // Entries to copy (not cleared above). - std::copy_if(cache.begin(), cache.end(), std::back_inserter(class_cache_), pred); + inline bool GetClassDefIndex(uint32_t dex_offset, uint32_t* class_def_index) { + if (class_cache_.empty()) { + CreateClassCache(); } // Binary search in the class cache. First element of the pair is the key. @@ -127,20 +129,31 @@ struct ExtDexFile { return false; } + // The underlying ART object. + std::unique_ptr<const art::DexFile> dex_file_; + // Binary search table with (end_dex_offset, class_def_index) entries. std::vector<std::pair<uint32_t, uint32_t>> class_cache_; + + // Used as short lived temporary when needed. Avoids alloc/free. + std::string temporary_qualified_name_; }; -int ExtDexFileOpenFromMemory(const void* addr, - /*inout*/ size_t* size, - const char* location, - /*out*/ ExtDexFile** ext_dex_file) { - if (*size < sizeof(art::DexFile::Header)) { - *size = sizeof(art::DexFile::Header); - return kExtDexFileNotEnoughData; +ADexFile_Error ADexFile_create(const void* _Nonnull address, + size_t size, + size_t* _Nullable new_size, + const char* _Nonnull location, + /*out*/ ADexFile* _Nullable * _Nonnull out_dex_file) { + *out_dex_file = nullptr; + + if (size < sizeof(art::DexFile::Header)) { + if (new_size != nullptr) { + *new_size = sizeof(art::DexFile::Header); + } + return ADEXFILE_ERROR_NOT_ENOUGH_DATA; } - const art::DexFile::Header* header = reinterpret_cast<const art::DexFile::Header*>(addr); + const art::DexFile::Header* header = reinterpret_cast<const art::DexFile::Header*>(address); uint32_t file_size = header->file_size_; if (art::CompactDexFile::IsMagicValid(header->magic_)) { // Compact dex files store the data section separately so that it can be shared. @@ -149,25 +162,27 @@ int ExtDexFileOpenFromMemory(const void* addr, // In practice, this should be fine, as such sharing only happens on disk. uint32_t computed_file_size; if (__builtin_add_overflow(header->data_off_, header->data_size_, &computed_file_size)) { - return kExtDexFileInvalidHeader; + return ADEXFILE_ERROR_INVALID_HEADER; } if (computed_file_size > file_size) { file_size = computed_file_size; } } else if (!art::StandardDexFile::IsMagicValid(header->magic_)) { - return kExtDexFileInvalidHeader; + return ADEXFILE_ERROR_INVALID_HEADER; } - if (*size < file_size) { - *size = file_size; - return kExtDexFileNotEnoughData; + if (size < file_size) { + if (new_size != nullptr) { + *new_size = file_size; + } + return ADEXFILE_ERROR_NOT_ENOUGH_DATA; } std::string loc_str(location); art::DexFileLoader loader; std::string error_msg; - std::unique_ptr<const art::DexFile> dex_file = loader.Open(static_cast<const uint8_t*>(addr), - *size, + std::unique_ptr<const art::DexFile> dex_file = loader.Open(static_cast<const uint8_t*>(address), + size, loc_str, header->checksum_, /*oat_dex_file=*/nullptr, @@ -175,78 +190,118 @@ int ExtDexFileOpenFromMemory(const void* addr, /*verify_checksum=*/false, &error_msg); if (dex_file == nullptr) { - LOG(ERROR) << "Can not opend dex file " << loc_str << ": " << error_msg; - return kExtDexFileError; + LOG(ERROR) << "Can not open dex file " << loc_str << ": " << error_msg; + return ADEXFILE_ERROR_INVALID_DEX; } - *ext_dex_file = new ExtDexFile(std::move(dex_file)); - return kExtDexFileOk; + *out_dex_file = new ADexFile(std::move(dex_file)); + return ADEXFILE_ERROR_OK; +} + +void ADexFile_destroy(ADexFile* self) { + delete self; } -int ExtDexFileGetMethodInfoForOffset(ExtDexFile* ext_dex_file, - uint32_t dex_offset, - uint32_t flags, - ExtDexFileMethodInfoCallback* method_info_cb, - void* user_data) { - if (!ext_dex_file->dex_file_->IsInDataSection(ext_dex_file->dex_file_->Begin() + dex_offset)) { - return false; // The DEX offset is not within the bytecode of this dex file. +size_t ADexFile_findMethodAtOffset(ADexFile* self, + size_t dex_offset, + ADexFile_MethodCallback* callback, + void* callback_data) { + const art::DexFile* dex_file = self->dex_file_.get(); + if (!dex_file->IsInDataSection(dex_file->Begin() + dex_offset)) { + return 0; // The DEX offset is not within the bytecode of this dex file. } - if (ext_dex_file->dex_file_->IsCompactDexFile()) { + if (dex_file->IsCompactDexFile()) { // The data section of compact dex files might be shared. // Check the subrange unique to this compact dex. const art::CompactDexFile::Header& cdex_header = - ext_dex_file->dex_file_->AsCompactDexFile()->GetHeader(); + dex_file->AsCompactDexFile()->GetHeader(); uint32_t begin = cdex_header.data_off_ + cdex_header.OwnedDataBegin(); uint32_t end = cdex_header.data_off_ + cdex_header.OwnedDataEnd(); if (dex_offset < begin || dex_offset >= end) { - return false; // The DEX offset is not within the bytecode of this dex file. + return 0; // The DEX offset is not within the bytecode of this dex file. } } - uint32_t method_index, addr, size; - if (!ext_dex_file->GetMethodDefIndex(dex_offset, &method_index, &addr, &size)) { - return false; + ADexFile_Method info; + if (!self->FindMethod(dex_offset, &info)) { + return 0; } - bool with_signature = flags & kExtDexFileWithSignature; - std::string name = ext_dex_file->dex_file_->PrettyMethod(method_index, with_signature); - ExtDexFileMethodInfo info { - .sizeof_struct = sizeof(ExtDexFileMethodInfo), - .addr = addr, - .size = size, - .name = name.c_str(), - .name_size = name.size() - }; - method_info_cb(user_data, &info); - return true; + callback(callback_data, &info); + return 1; } -void ExtDexFileGetAllMethodInfos(ExtDexFile* ext_dex_file, - uint32_t flags, - ExtDexFileMethodInfoCallback* method_info_cb, - void* user_data) { - const art::DexFile* dex_file = ext_dex_file->dex_file_.get(); - for (art::ClassAccessor accessor : ext_dex_file->dex_file_->GetClasses()) { +size_t ADexFile_forEachMethod(ADexFile* self, + ADexFile_MethodCallback* callback, + void* callback_data) { + size_t count = 0; + for (art::ClassAccessor accessor : self->dex_file_->GetClasses()) { for (const art::ClassAccessor::Method& method : accessor.GetMethods()) { art::CodeItemInstructionAccessor code = method.GetInstructions(); if (code.HasCodeItem()) { - const uint8_t* insns = reinterpret_cast<const uint8_t*>(code.Insns()); - bool with_signature = flags & kExtDexFileWithSignature; - std::string name = dex_file->PrettyMethod(method.GetIndex(), with_signature); - ExtDexFileMethodInfo info { - .sizeof_struct = sizeof(ExtDexFileMethodInfo), - .addr = static_cast<uint32_t>(insns - dex_file->Begin()), + size_t offset = reinterpret_cast<const uint8_t*>(code.Insns()) - self->dex_file_->Begin(); + ADexFile_Method info { + .adex = self, + .index = method.GetIndex(), + .offset = offset, .size = code.InsnsSizeInBytes(), - .name = name.c_str(), - .name_size = name.size() }; - method_info_cb(user_data, &info); + callback(callback_data, &info); + count++; } } } + return count; +} + +size_t ADexFile_Method_getCodeOffset(const ADexFile_Method* self, + size_t* out_size) { + if (out_size != nullptr) { + *out_size = self->size; + } + return self->offset; +} + +const char* ADexFile_Method_getName(const ADexFile_Method* self, + size_t* out_size) { + const char* name = self->adex->dex_file_->GetMethodName(self->index); + if (out_size != nullptr) { + *out_size = strlen(name); + } + return name; } -void ExtDexFileClose(ExtDexFile* ext_dex_file) { delete (ext_dex_file); } +const char* ADexFile_Method_getQualifiedName(const ADexFile_Method* self, + int with_params, + size_t* out_size) { + std::string& temp = self->adex->temporary_qualified_name_; + temp.clear(); + self->adex->dex_file_->AppendPrettyMethod(self->index, with_params, &temp); + if (out_size != nullptr) { + *out_size = temp.size(); + } + return temp.data(); +} + +const char* ADexFile_Method_getClassDescriptor(const ADexFile_Method* self, + size_t* out_size) { + const art::dex::MethodId& method_id = self->adex->dex_file_->GetMethodId(self->index); + const char* name = self->adex->dex_file_->GetMethodDeclaringClassDescriptor(method_id); + if (out_size != nullptr) { + *out_size = strlen(name); + } + return name; +} + +const char* ADexFile_Error_toString(ADexFile_Error self) { + switch (self) { + case ADEXFILE_ERROR_OK: return "Ok"; + case ADEXFILE_ERROR_INVALID_DEX: return "Dex file is invalid."; + case ADEXFILE_ERROR_NOT_ENOUGH_DATA: return "Not enough data. Incomplete dex file."; + case ADEXFILE_ERROR_INVALID_HEADER: return "Invalid dex file header."; + } + return nullptr; +} } // extern "C" diff --git a/libdexfile/external/dex_file_ext_c_test.c b/libdexfile/external/dex_file_ext_c_test.c index e1139718dd..656a6cb9b1 100644 --- a/libdexfile/external/dex_file_ext_c_test.c +++ b/libdexfile/external/dex_file_ext_c_test.c @@ -14,66 +14,6 @@ * limitations under the License. */ -#include <errno.h> -#include <stdio.h> -#include <string.h> - -/* The main purpose of this test is to ensure this C header compiles in C, so - * that no C++ features inadvertently leak into the C ABI. */ +/* The main purpose of this file is to ensure this C header compiles in C, + * so that no C++ features inadvertently leak into the C ABI. */ #include "art_api/dex_file_external.h" - -static const char gtest_output_arg[] = "--gtest_output=xml:"; -static const char gtest_output_xml[] = "\ -<?xml version=\"1.0\"?>\n\ -<testsuites tests=\"1\" failures=\"0\" disabled=\"0\" errors=\"0\" name=\"AllTests\">\n\ - <testsuite tests=\"1\" failures=\"0\" disabled=\"0\" errors=\"0\" name=\"NopTest\">\n\ - <testcase name=\"nop\" status=\"run\" />\n\ - </testsuite>\n\ -</testsuites>"; - -static const char canned_stdout[] = "\ -[==========] Running 1 test from 1 test suite.\n\ -[----------] 1 test from NopTest\n\ -[ RUN ] NopTest.nop\n\ -[ OK ] NopTest.nop (0 ms)\n\ -[----------] 1 test from NopTest (0 ms total)\n\ -\n\ -[==========] 1 test from 1 test suite ran. (0 ms total)\n\ -[ PASSED ] 1 test.\n\ -"; - -/* Writes a fake gtest xml report to the given path. */ -static int write_gtest_output_xml(char* gtest_output_path) { - FILE* output_fd = fopen(gtest_output_path, "w"); - if (output_fd == NULL) { - fprintf(stderr, "Failed to open %s: %s\n", gtest_output_path, strerror(errno)); - return 1; - } - if (fprintf(output_fd, gtest_output_xml) != sizeof(gtest_output_xml) - 1) { - fprintf(stderr, "Failed to write %s: %s\n", gtest_output_path, strerror(errno)); - fclose(output_fd); - return 1; - } - if (fclose(output_fd) != 0) { - fprintf(stderr, "Failed to close %s: %s\n", gtest_output_path, strerror(errno)); - return 1; - } - return 0; -} - -int main(int argc, char** argv) { - int i; - for (i = 1; i < argc; ++i) { - if (strncmp(argv[i], gtest_output_arg, sizeof(gtest_output_arg) - 1) == 0) { - /* The ART gtest framework expects all tests to understand --gtest_output. */ - if (write_gtest_output_xml(argv[i] + sizeof(gtest_output_arg) - 1)) { - return 1; - } - } - } - /* Tradefed parses the output, so send something passable there. */ - if (fputs(canned_stdout, stdout) < 0) { - return 1; - } - return 0; -} diff --git a/libdexfile/external/dex_file_ext_test.cc b/libdexfile/external/dex_file_ext_test.cc new file mode 100644 index 0000000000..54aaa3b1d0 --- /dev/null +++ b/libdexfile/external/dex_file_ext_test.cc @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2021 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 <sys/types.h> + +#include <memory> +#include <string> +#include <string_view> + +#include <android-base/file.h> +#include <dex/dex_file.h> +#include <gtest/gtest.h> + +#include "art_api/dex_file_external.h" +#include "dex_file_test_data.h" + +namespace art_api { +namespace dex { + +class ADexFileTest : public ::testing::Test { + protected: + void TearDown() override { + ADexFile_destroy(dex_); + } + + ADexFile* dex_ = nullptr; +}; + +TEST_F(ADexFileTest, create) { + size_t size = sizeof(kDexData); + EXPECT_EQ(ADexFile_create(kDexData, size, &size, "", &dex_), ADEXFILE_ERROR_OK); + EXPECT_EQ(size, sizeof(kDexData)); + EXPECT_NE(dex_, nullptr); +} + +TEST_F(ADexFileTest, create_null_new_size) { + const size_t size = sizeof(kDexData); + EXPECT_EQ(ADexFile_create(kDexData, size, nullptr, "", &dex_), ADEXFILE_ERROR_OK); + EXPECT_NE(dex_, nullptr); +} + +TEST_F(ADexFileTest, create_header_too_small) { + size_t size = sizeof(art::DexFile::Header) - 1; + EXPECT_EQ(ADexFile_create(kDexData, size, &size, "", &dex_), ADEXFILE_ERROR_NOT_ENOUGH_DATA); + EXPECT_EQ(size, sizeof(art::DexFile::Header)); + EXPECT_EQ(dex_, nullptr); +} + +TEST_F(ADexFileTest, create_file_too_small) { + size_t size = sizeof(art::DexFile::Header); + EXPECT_EQ(ADexFile_create(kDexData, size, &size, "", &dex_), ADEXFILE_ERROR_NOT_ENOUGH_DATA); + EXPECT_EQ(size, sizeof(kDexData)); + EXPECT_EQ(dex_, nullptr); +} + +static ADexFile* GetTestDexData() { + size_t size = sizeof(kDexData); + ADexFile* dex; + EXPECT_EQ(ADexFile_create(kDexData, size, &size, "", &dex), ADEXFILE_ERROR_OK); + EXPECT_EQ(size, sizeof(kDexData)); + EXPECT_NE(dex, nullptr); + return dex; +} + +TEST_F(ADexFileTest, findMethodAtOffset) { + dex_ = GetTestDexData(); + ASSERT_NE(dex_, nullptr); + + bool found_init = false; + auto check_init = [](void* ctx, const ADexFile_Method* method) { + size_t size; + size_t offset = ADexFile_Method_getCodeOffset(method, &size); + EXPECT_EQ(offset, 0x100u); + EXPECT_EQ(size, 8u); + EXPECT_STREQ(ADexFile_Method_getName(method, &size), "<init>"); + EXPECT_STREQ(ADexFile_Method_getQualifiedName(method, false, &size), "Main.<init>"); + EXPECT_STREQ(ADexFile_Method_getQualifiedName(method, true, &size), "void Main.<init>()"); + EXPECT_STREQ(ADexFile_Method_getClassDescriptor(method, &size), "LMain;"); + *reinterpret_cast<bool*>(ctx) = true; + }; + EXPECT_EQ(ADexFile_findMethodAtOffset(dex_, 0x102, check_init, &found_init), 1u); + EXPECT_TRUE(found_init); + + bool found_main = false; + auto check_main = [](void* ctx, const ADexFile_Method* method) { + size_t size; + size_t offset = ADexFile_Method_getCodeOffset(method, &size); + EXPECT_EQ(offset, 0x118u); + EXPECT_EQ(size, 2u); + EXPECT_STREQ(ADexFile_Method_getName(method, &size), "main"); + EXPECT_STREQ(ADexFile_Method_getQualifiedName(method, false, &size), "Main.main"); + EXPECT_STREQ(ADexFile_Method_getQualifiedName(method, true, &size), "void Main.main(java.lang.String[])"); + EXPECT_STREQ(ADexFile_Method_getClassDescriptor(method, &size), "LMain;"); + *reinterpret_cast<bool*>(ctx) = true; + }; + EXPECT_EQ(ADexFile_findMethodAtOffset(dex_, 0x118, check_main, &found_main), 1u); + EXPECT_TRUE(found_main); +} + +TEST_F(ADexFileTest, findMethodAtOffset_for_offset_boundaries) { + dex_ = GetTestDexData(); + ASSERT_NE(dex_, nullptr); + + auto no_cb = [](void*, const ADexFile_Method*) {}; + EXPECT_EQ(ADexFile_findMethodAtOffset(dex_, 0x99, no_cb, nullptr), 0); + EXPECT_EQ(ADexFile_findMethodAtOffset(dex_, 0x100, no_cb, nullptr), 1); + EXPECT_EQ(ADexFile_findMethodAtOffset(dex_, 0x107, no_cb, nullptr), 1); + EXPECT_EQ(ADexFile_findMethodAtOffset(dex_, 0x108, no_cb, nullptr), 0); + EXPECT_EQ(ADexFile_findMethodAtOffset(dex_, 0x100000, no_cb, nullptr), 0); +} + +TEST_F(ADexFileTest, forEachMethod) { + dex_ = GetTestDexData(); + ASSERT_NE(dex_, nullptr); + + std::vector<std::string> names; + auto add = [](void* ctx, const ADexFile_Method* method) { + reinterpret_cast<decltype(names)*>(ctx)-> + push_back(ADexFile_Method_getQualifiedName(method, false, nullptr)); + }; + EXPECT_EQ(ADexFile_forEachMethod(dex_, add, &names), 2u); + EXPECT_EQ(names, std::vector<std::string>({"Main.<init>", "Main.main"})); +} + +TEST_F(ADexFileTest, Error_toString) { + constexpr size_t kNumErrors = 4; + for (size_t i = 0; i < kNumErrors; i++) { + const char* err = ADexFile_Error_toString(static_cast<ADexFile_Error>(i)); + ASSERT_NE(err, nullptr); + ASSERT_FALSE(std::string_view(err).empty()); + } + const char* err = ADexFile_Error_toString(static_cast<ADexFile_Error>(kNumErrors)); + ASSERT_EQ(err, nullptr); +} + +} // namespace dex +} // namespace art_api diff --git a/libdexfile/external/dex_file_supp.cc b/libdexfile/external/dex_file_supp.cc index 6313afea06..23fc88a3a9 100644 --- a/libdexfile/external/dex_file_supp.cc +++ b/libdexfile/external/dex_file_supp.cc @@ -33,21 +33,15 @@ namespace art_api { namespace dex { -#define FOR_ALL_DLFUNCS(MACRO) \ - MACRO(DexFile, ExtDexFileOpenFromMemory) \ - MACRO(DexFile, ExtDexFileGetMethodInfoForOffset) \ - MACRO(DexFile, ExtDexFileGetAllMethodInfos) \ - MACRO(DexFile, ExtDexFileClose) - -#ifdef STATIC_LIB -#define DEFINE_DLFUNC_PTR(CLASS, DLFUNC) decltype(DLFUNC)* CLASS::g_##DLFUNC = DLFUNC; +#if defined(STATIC_LIB) +#define DEFINE_ADEX_FILE_SYMBOL(DLFUNC) decltype(DLFUNC)* g_##DLFUNC = DLFUNC; #else -#define DEFINE_DLFUNC_PTR(CLASS, DLFUNC) decltype(DLFUNC)* CLASS::g_##DLFUNC = nullptr; +#define DEFINE_ADEX_FILE_SYMBOL(DLFUNC) decltype(DLFUNC)* g_##DLFUNC = nullptr; #endif -FOR_ALL_DLFUNCS(DEFINE_DLFUNC_PTR) -#undef DEFINE_DLFUNC_PTR +FOR_EACH_ADEX_FILE_SYMBOL(DEFINE_ADEX_FILE_SYMBOL) +#undef DEFINE_ADEX_FILE_SYMBOL -bool TryLoadLibdexfileExternal([[maybe_unused]] std::string* err_msg) { +bool TryLoadLibdexfile([[maybe_unused]] std::string* err_msg) { #if defined(STATIC_LIB) // Nothing to do here since all function pointers are initialised statically. return true; @@ -76,18 +70,18 @@ bool TryLoadLibdexfileExternal([[maybe_unused]] std::string* err_msg) { return false; } -#define RESOLVE_DLFUNC_PTR(CLASS, DLFUNC) \ - decltype(DLFUNC)* DLFUNC##_ptr = reinterpret_cast<decltype(DLFUNC)*>(dlsym(handle, #DLFUNC)); \ - if ((DLFUNC) == nullptr) { \ +#define RESOLVE_ADEX_FILE_SYMBOL(DLFUNC) \ + auto DLFUNC##_ptr = reinterpret_cast<decltype(DLFUNC)*>(dlsym(handle, #DLFUNC)); \ + if (DLFUNC##_ptr == nullptr) { \ *err_msg = dlerror(); \ return false; \ } - FOR_ALL_DLFUNCS(RESOLVE_DLFUNC_PTR); -#undef RESOLVE_DLFUNC_PTR +FOR_EACH_ADEX_FILE_SYMBOL(RESOLVE_ADEX_FILE_SYMBOL) +#undef RESOLVE_ADEX_FILE_SYMBOL -#define SET_DLFUNC_PTR(CLASS, DLFUNC) CLASS::g_##DLFUNC = DLFUNC##_ptr; - FOR_ALL_DLFUNCS(SET_DLFUNC_PTR); -#undef SET_DLFUNC_PTR +#define SET_ADEX_FILE_SYMBOL(DLFUNC) g_##DLFUNC = DLFUNC##_ptr; + FOR_EACH_ADEX_FILE_SYMBOL(SET_ADEX_FILE_SYMBOL); +#undef SET_ADEX_FILE_SYMBOL is_loaded = true; } @@ -96,102 +90,13 @@ bool TryLoadLibdexfileExternal([[maybe_unused]] std::string* err_msg) { #endif // !defined(NO_DEXFILE_SUPPORT) && !defined(STATIC_LIB) } -void LoadLibdexfileExternal() { +void LoadLibdexfile() { #ifndef STATIC_LIB - if (std::string err_msg; !TryLoadLibdexfileExternal(&err_msg)) { + if (std::string err_msg; !TryLoadLibdexfile(&err_msg)) { LOG_ALWAYS_FATAL("%s", err_msg.c_str()); } #endif } -DexFile::~DexFile() { g_ExtDexFileClose(ext_dex_file_); } - -std::unique_ptr<DexFile> DexFile::OpenFromMemory(const void* addr, - size_t* size, - const std::string& location, - /*out*/ std::string* error_msg) { - if (UNLIKELY(g_ExtDexFileOpenFromMemory == nullptr)) { - // Load libdexfile_external.so in this factory function, so instance - // methods don't need to check this. - LoadLibdexfileExternal(); - } - ExtDexFile* ext_dex_file; - int res = g_ExtDexFileOpenFromMemory(addr, size, location.c_str(), &ext_dex_file); - switch (static_cast<ExtDexFileError>(res)) { - case kExtDexFileOk: - return std::unique_ptr<DexFile>(new DexFile(ext_dex_file)); - case kExtDexFileInvalidHeader: - *error_msg = std::string("Invalid DexFile header ") + location; - return nullptr; - case kExtDexFileNotEnoughData: - *error_msg = std::string("Not enough data"); - return nullptr; - case kExtDexFileError: - break; - } - *error_msg = std::string("Failed to open DexFile ") + location; - return nullptr; -} - -std::unique_ptr<DexFile> DexFile::OpenFromFd(int fd, - off_t offset, - const std::string& location, - /*out*/ std::string* error_msg) { - using android::base::StringPrintf; - size_t length; - { - struct stat sbuf; - std::memset(&sbuf, 0, sizeof(sbuf)); - if (fstat(fd, &sbuf) == -1) { - *error_msg = StringPrintf("fstat '%s' failed: %s", location.c_str(), std::strerror(errno)); - return nullptr; - } - if (S_ISDIR(sbuf.st_mode)) { - *error_msg = StringPrintf("Attempt to mmap directory '%s'", location.c_str()); - return nullptr; - } - length = sbuf.st_size; - } - - if (static_cast<off_t>(length) < offset) { - *error_msg = StringPrintf( - "Offset %" PRId64 " too large for '%s' of size %zu", - int64_t{offset}, - location.c_str(), - length); - return nullptr; - } - length -= offset; - - std::unique_ptr<android::base::MappedFile> map; - map = android::base::MappedFile::FromFd(fd, offset, length, PROT_READ); - if (map == nullptr) { - *error_msg = StringPrintf("mmap '%s' failed: %s", location.c_str(), std::strerror(errno)); - return nullptr; - } - - std::unique_ptr<DexFile> dex = OpenFromMemory(map->data(), &length, location, error_msg); - if (dex != nullptr) { - dex->map_ = std::move(map); // Ensure the map gets freed with the dex file. - } - return dex; -} - -MethodInfo DexFile::GetMethodInfoForOffset(int64_t dex_offset, bool with_signature) { - MethodInfo res{}; - auto set_method = [&res](ExtDexFileMethodInfo* info) { res = AbsorbMethodInfo(info); }; - uint32_t flags = with_signature ? kExtDexFileWithSignature : 0; - GetMethodInfoForOffset(dex_offset, set_method, flags); - return res; -} - -std::vector<MethodInfo> DexFile::GetAllMethodInfos(bool with_signature) { - std::vector<MethodInfo> res; - auto add_method = [&res](ExtDexFileMethodInfo* info) { res.push_back(AbsorbMethodInfo(info)); }; - uint32_t flags = with_signature ? kExtDexFileWithSignature : 0; - GetAllMethodInfos(add_method, flags); - return res; -} - } // namespace dex } // namespace art_api diff --git a/libdexfile/external/dex_file_supp_test.cc b/libdexfile/external/dex_file_supp_test.cc index ff263fc539..3285edf435 100644 --- a/libdexfile/external/dex_file_supp_test.cc +++ b/libdexfile/external/dex_file_supp_test.cc @@ -25,218 +25,104 @@ #include <gtest/gtest.h> #include "art_api/dex_file_support.h" +#include "dex_file_test_data.h" namespace art_api { namespace dex { -static constexpr uint32_t kDexData[] = { - 0x0a786564, 0x00383330, 0xc98b3ab8, 0xf3749d94, 0xaecca4d8, 0xffc7b09a, 0xdca9ca7f, 0x5be5deab, - 0x00000220, 0x00000070, 0x12345678, 0x00000000, 0x00000000, 0x0000018c, 0x00000008, 0x00000070, - 0x00000004, 0x00000090, 0x00000002, 0x000000a0, 0x00000000, 0x00000000, 0x00000003, 0x000000b8, - 0x00000001, 0x000000d0, 0x00000130, 0x000000f0, 0x00000122, 0x0000012a, 0x00000132, 0x00000146, - 0x00000151, 0x00000154, 0x00000158, 0x0000016d, 0x00000001, 0x00000002, 0x00000004, 0x00000006, - 0x00000004, 0x00000002, 0x00000000, 0x00000005, 0x00000002, 0x0000011c, 0x00000000, 0x00000000, - 0x00010000, 0x00000007, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000, - 0x00000003, 0x00000000, 0x0000017e, 0x00000000, 0x00010001, 0x00000001, 0x00000173, 0x00000004, - 0x00021070, 0x000e0000, 0x00010001, 0x00000000, 0x00000178, 0x00000001, 0x0000000e, 0x00000001, - 0x3c060003, 0x74696e69, 0x4c06003e, 0x6e69614d, 0x4c12003b, 0x6176616a, 0x6e616c2f, 0x624f2f67, - 0x7463656a, 0x4d09003b, 0x2e6e6961, 0x6176616a, 0x00560100, 0x004c5602, 0x6a4c5b13, 0x2f617661, - 0x676e616c, 0x7274532f, 0x3b676e69, 0x616d0400, 0x01006e69, 0x000e0700, 0x07000103, 0x0000000e, - 0x81000002, 0x01f00480, 0x02880901, 0x0000000c, 0x00000000, 0x00000001, 0x00000000, 0x00000001, - 0x00000008, 0x00000070, 0x00000002, 0x00000004, 0x00000090, 0x00000003, 0x00000002, 0x000000a0, - 0x00000005, 0x00000003, 0x000000b8, 0x00000006, 0x00000001, 0x000000d0, 0x00002001, 0x00000002, - 0x000000f0, 0x00001001, 0x00000001, 0x0000011c, 0x00002002, 0x00000008, 0x00000122, 0x00002003, - 0x00000002, 0x00000173, 0x00002000, 0x00000001, 0x0000017e, 0x00001000, 0x00000001, 0x0000018c, -}; - -TEST(DexFileTest, from_memory_header_too_small) { +TEST(DexFileTest, create) { + size_t size = sizeof(kDexData); + std::unique_ptr<DexFile> dex_file; + EXPECT_TRUE(DexFile::Create(kDexData, size, &size, "", &dex_file).Ok()); + EXPECT_EQ(size, sizeof(kDexData)); + EXPECT_NE(dex_file, nullptr); +} + +TEST(DexFileTest, create_header_too_small) { size_t size = sizeof(art::DexFile::Header) - 1; - std::string error_msg; - EXPECT_EQ(DexFile::OpenFromMemory(kDexData, &size, "", &error_msg), nullptr); + std::unique_ptr<DexFile> dex_file; + DexFile::Error error = DexFile::Create(kDexData, size, &size, "", &dex_file); + EXPECT_FALSE(error.Ok()); + EXPECT_EQ(error.Code(), ADEXFILE_ERROR_NOT_ENOUGH_DATA); + EXPECT_STREQ(error.ToString(), "Not enough data. Incomplete dex file."); EXPECT_EQ(size, sizeof(art::DexFile::Header)); - EXPECT_FALSE(error_msg.empty()); + EXPECT_EQ(dex_file, nullptr); } -TEST(DexFileTest, from_memory_file_too_small) { +TEST(DexFileTest, create_file_too_small) { size_t size = sizeof(art::DexFile::Header); - std::string error_msg; - EXPECT_EQ(DexFile::OpenFromMemory(kDexData, &size, "", &error_msg), nullptr); + std::unique_ptr<DexFile> dex_file; + DexFile::Error error = DexFile::Create(kDexData, size, &size, "", &dex_file); + EXPECT_FALSE(error.Ok()); + EXPECT_EQ(error.Code(), ADEXFILE_ERROR_NOT_ENOUGH_DATA); + EXPECT_STREQ(error.ToString(), "Not enough data. Incomplete dex file."); EXPECT_EQ(size, sizeof(kDexData)); - EXPECT_FALSE(error_msg.empty()); + EXPECT_EQ(dex_file, nullptr); } static std::unique_ptr<DexFile> GetTestDexData() { size_t size = sizeof(kDexData); - std::string error_msg; - std::unique_ptr<DexFile> dex_file = DexFile::OpenFromMemory(kDexData, &size, "", &error_msg); - EXPECT_TRUE(error_msg.empty()); + std::unique_ptr<DexFile> dex_file; + EXPECT_TRUE(DexFile::Create(kDexData, size, &size, "", &dex_file).Ok()); + EXPECT_EQ(size, sizeof(kDexData)); + EXPECT_NE(dex_file, nullptr); return dex_file; } -TEST(DexFileTest, from_memory) { - EXPECT_NE(GetTestDexData(), nullptr); -} - -TEST(DexFileTest, from_fd_header_too_small) { - TemporaryFile tf; - ASSERT_NE(tf.fd, -1); - ASSERT_EQ(sizeof(art::DexFile::Header) - 1, - static_cast<size_t>( - TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(art::DexFile::Header) - 1)))); - - std::string error_msg; - EXPECT_EQ(DexFile::OpenFromFd(tf.fd, 0, tf.path, &error_msg), nullptr); - EXPECT_FALSE(error_msg.empty()); -} - -TEST(DexFileTest, from_fd_file_too_small) { - TemporaryFile tf; - ASSERT_NE(tf.fd, -1); - ASSERT_EQ(sizeof(art::DexFile::Header), - static_cast<size_t>( - TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(art::DexFile::Header))))); - - std::string error_msg; - EXPECT_EQ(DexFile::OpenFromFd(tf.fd, 0, tf.path, &error_msg), nullptr); - EXPECT_FALSE(error_msg.empty()); -} - -TEST(DexFileTest, from_fd) { - TemporaryFile tf; - ASSERT_NE(tf.fd, -1); - ASSERT_EQ(sizeof(kDexData), - static_cast<size_t>(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData))))); - - std::string error_msg; - EXPECT_NE(DexFile::OpenFromFd(tf.fd, 0, tf.path, &error_msg), nullptr); - EXPECT_TRUE(error_msg.empty()); -} - -TEST(DexFileTest, from_fd_non_zero_offset) { - TemporaryFile tf; - ASSERT_NE(tf.fd, -1); - ASSERT_EQ(0x100, lseek(tf.fd, 0x100, SEEK_SET)); - ASSERT_EQ(sizeof(kDexData), - static_cast<size_t>(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData))))); - - std::string error_msg; - EXPECT_NE(DexFile::OpenFromFd(tf.fd, 0x100, tf.path, &error_msg), nullptr); - EXPECT_TRUE(error_msg.empty()); -} - -TEST(DexFileTest, get_method_info_for_offset_without_signature) { +TEST(DexFileTest, findMethodAtOffset) { std::unique_ptr<DexFile> dex_file = GetTestDexData(); ASSERT_NE(dex_file, nullptr); - MethodInfo info = dex_file->GetMethodInfoForOffset(0x102, false); - EXPECT_EQ(info.offset, int32_t{0x100}); - EXPECT_EQ(info.len, int32_t{8}); - EXPECT_STREQ(info.name.data(), "Main.<init>"); - - info = dex_file->GetMethodInfoForOffset(0x118, false); - EXPECT_EQ(info.offset, int32_t{0x118}); - EXPECT_EQ(info.len, int32_t{2}); - EXPECT_STREQ(info.name.data(), "Main.main"); - - // Retrieve a cached result. - info = dex_file->GetMethodInfoForOffset(0x104, false); - EXPECT_EQ(info.offset, int32_t{0x100}); - EXPECT_EQ(info.len, int32_t{8}); - EXPECT_STREQ(info.name.data(), "Main.<init>"); -} - -TEST(DexFileTest, get_method_info_for_offset_with_signature) { - std::unique_ptr<DexFile> dex_file = GetTestDexData(); - ASSERT_NE(dex_file, nullptr); - - MethodInfo info = dex_file->GetMethodInfoForOffset(0x102, true); - EXPECT_EQ(info.offset, int32_t{0x100}); - EXPECT_EQ(info.len, int32_t{8}); - EXPECT_STREQ(info.name.data(), "void Main.<init>()"); - - info = dex_file->GetMethodInfoForOffset(0x118, true); - EXPECT_EQ(info.offset, int32_t{0x118}); - EXPECT_EQ(info.len, int32_t{2}); - EXPECT_STREQ(info.name.data(), "void Main.main(java.lang.String[])"); - - // Retrieve a cached result. - info = dex_file->GetMethodInfoForOffset(0x104, true); - EXPECT_EQ(info.offset, int32_t{0x100}); - EXPECT_EQ(info.len, int32_t{8}); - EXPECT_STREQ(info.name.data(), "void Main.<init>()"); - - // with_signature doesn't affect the cache. - info = dex_file->GetMethodInfoForOffset(0x104, false); - EXPECT_EQ(info.offset, int32_t{0x100}); - EXPECT_EQ(info.len, int32_t{8}); - EXPECT_STREQ(info.name.data(), "Main.<init>"); + bool found_init = false; + auto check_init = [&](const DexFile::Method& method) { + size_t size; + size_t offset = method.GetCodeOffset(&size); + EXPECT_EQ(offset, 0x100u); + EXPECT_EQ(size, 8u); + EXPECT_STREQ(method.GetName(), "<init>"); + EXPECT_STREQ(method.GetQualifiedName(), "Main.<init>"); + EXPECT_STREQ(method.GetQualifiedName(true), "void Main.<init>()"); + EXPECT_STREQ(method.GetClassDescriptor(), "LMain;"); + found_init = true; + }; + EXPECT_EQ(dex_file->FindMethodAtOffset(0x102, check_init), 1u); + EXPECT_TRUE(found_init); + + bool found_main = false; + auto check_main = [&](const DexFile::Method& method) { + size_t size; + size_t offset = method.GetCodeOffset(&size); + EXPECT_EQ(offset, 0x118u); + EXPECT_EQ(size, 2u); + EXPECT_STREQ(method.GetName(), "main"); + EXPECT_STREQ(method.GetQualifiedName(), "Main.main"); + EXPECT_STREQ(method.GetQualifiedName(true), "void Main.main(java.lang.String[])"); + EXPECT_STREQ(method.GetClassDescriptor(), "LMain;"); + found_main = true; + }; + EXPECT_EQ(dex_file->FindMethodAtOffset(0x118, check_main), 1u); + EXPECT_TRUE(found_main); } TEST(DexFileTest, get_method_info_for_offset_boundaries) { std::unique_ptr<DexFile> dex_file = GetTestDexData(); ASSERT_NE(dex_file, nullptr); - MethodInfo info = dex_file->GetMethodInfoForOffset(0x100000, false); - EXPECT_EQ(info.offset, int32_t{0}); - - info = dex_file->GetMethodInfoForOffset(0x99, false); - EXPECT_EQ(info.offset, int32_t{0}); - info = dex_file->GetMethodInfoForOffset(0x100, false); - EXPECT_EQ(info.offset, int32_t{0x100}); - info = dex_file->GetMethodInfoForOffset(0x107, false); - EXPECT_EQ(info.offset, int32_t{0x100}); - info = dex_file->GetMethodInfoForOffset(0x108, false); - EXPECT_EQ(info.offset, int32_t{0}); - - // Make sure that once the whole dex file has been cached, no problems occur. - info = dex_file->GetMethodInfoForOffset(0x98, false); - EXPECT_EQ(info.offset, int32_t{0}); - - // Choose a value that is in the cached map, but not in a valid method. - info = dex_file->GetMethodInfoForOffset(0x110, false); - EXPECT_EQ(info.offset, int32_t{0}); + EXPECT_EQ(dex_file->FindMethodAtOffset(0x99, [](auto){}), 0); + EXPECT_EQ(dex_file->FindMethodAtOffset(0x100, [](auto){}), 1); + EXPECT_EQ(dex_file->FindMethodAtOffset(0x107, [](auto){}), 1); + EXPECT_EQ(dex_file->FindMethodAtOffset(0x108, [](auto){}), 0); + EXPECT_EQ(dex_file->FindMethodAtOffset(0x100000, [](auto){}), 0); } TEST(DexFileTest, get_all_method_infos_without_signature) { std::unique_ptr<DexFile> dex_file = GetTestDexData(); ASSERT_NE(dex_file, nullptr); - std::vector<MethodInfo> infos; - infos.emplace_back(MethodInfo{0x100, 8, std::string("Main.<init>")}); - infos.emplace_back(MethodInfo{0x118, 2, std::string("Main.main")}); - ASSERT_EQ(dex_file->GetAllMethodInfos(false), infos); -} - -TEST(DexFileTest, get_all_method_infos_with_signature) { - std::unique_ptr<DexFile> dex_file = GetTestDexData(); - ASSERT_NE(dex_file, nullptr); - - std::vector<MethodInfo> infos; - infos.emplace_back(MethodInfo{0x100, 8, std::string("void Main.<init>()")}); - infos.emplace_back(MethodInfo{0x118, 2, std::string("void Main.main(java.lang.String[])")}); - ASSERT_EQ(dex_file->GetAllMethodInfos(true), infos); -} - -TEST(DexFileTest, move_construct) { - std::unique_ptr<DexFile> dex_file = GetTestDexData(); - ASSERT_NE(dex_file, nullptr); - - auto df1 = DexFile(std::move(*dex_file)); - auto df2 = DexFile(std::move(df1)); - - MethodInfo info = df2.GetMethodInfoForOffset(0x100, false); - EXPECT_EQ(info.offset, int32_t{0x100}); -} - -TEST(DexFileTest, pointer_construct) { - std::unique_ptr<DexFile> dex_file = GetTestDexData(); - ASSERT_NE(dex_file, nullptr); - - auto new_dex = DexFile(dex_file); - ASSERT_TRUE(dex_file.get() == nullptr); - - MethodInfo info = new_dex.GetMethodInfoForOffset(0x100, false); - EXPECT_EQ(info.offset, int32_t{0x100}); + std::vector<std::string> names; + auto add = [&](const DexFile::Method& method) { names.push_back(method.GetQualifiedName()); }; + EXPECT_EQ(dex_file->ForEachMethod(add), 2u); + EXPECT_EQ(names, std::vector<std::string>({"Main.<init>", "Main.main"})); } } // namespace dex diff --git a/libdexfile/external/dex_file_test_data.h b/libdexfile/external/dex_file_test_data.h new file mode 100644 index 0000000000..fd6b80b6c0 --- /dev/null +++ b/libdexfile/external/dex_file_test_data.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021 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. + */ + +#ifndef ART_LIBDEXFILE_EXTERNAL_DEX_FILE_TEST_DATA_H_ +#define ART_LIBDEXFILE_EXTERNAL_DEX_FILE_TEST_DATA_H_ + +#include <stdint.h> + +namespace art_api { +namespace dex { + +static constexpr uint32_t kDexData[] = { + 0x0a786564, 0x00383330, 0xc98b3ab8, 0xf3749d94, 0xaecca4d8, 0xffc7b09a, 0xdca9ca7f, 0x5be5deab, + 0x00000220, 0x00000070, 0x12345678, 0x00000000, 0x00000000, 0x0000018c, 0x00000008, 0x00000070, + 0x00000004, 0x00000090, 0x00000002, 0x000000a0, 0x00000000, 0x00000000, 0x00000003, 0x000000b8, + 0x00000001, 0x000000d0, 0x00000130, 0x000000f0, 0x00000122, 0x0000012a, 0x00000132, 0x00000146, + 0x00000151, 0x00000154, 0x00000158, 0x0000016d, 0x00000001, 0x00000002, 0x00000004, 0x00000006, + 0x00000004, 0x00000002, 0x00000000, 0x00000005, 0x00000002, 0x0000011c, 0x00000000, 0x00000000, + 0x00010000, 0x00000007, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000, + 0x00000003, 0x00000000, 0x0000017e, 0x00000000, 0x00010001, 0x00000001, 0x00000173, 0x00000004, + 0x00021070, 0x000e0000, 0x00010001, 0x00000000, 0x00000178, 0x00000001, 0x0000000e, 0x00000001, + 0x3c060003, 0x74696e69, 0x4c06003e, 0x6e69614d, 0x4c12003b, 0x6176616a, 0x6e616c2f, 0x624f2f67, + 0x7463656a, 0x4d09003b, 0x2e6e6961, 0x6176616a, 0x00560100, 0x004c5602, 0x6a4c5b13, 0x2f617661, + 0x676e616c, 0x7274532f, 0x3b676e69, 0x616d0400, 0x01006e69, 0x000e0700, 0x07000103, 0x0000000e, + 0x81000002, 0x01f00480, 0x02880901, 0x0000000c, 0x00000000, 0x00000001, 0x00000000, 0x00000001, + 0x00000008, 0x00000070, 0x00000002, 0x00000004, 0x00000090, 0x00000003, 0x00000002, 0x000000a0, + 0x00000005, 0x00000003, 0x000000b8, 0x00000006, 0x00000001, 0x000000d0, 0x00002001, 0x00000002, + 0x000000f0, 0x00001001, 0x00000001, 0x0000011c, 0x00002002, 0x00000008, 0x00000122, 0x00002003, + 0x00000002, 0x00000173, 0x00002000, 0x00000001, 0x0000017e, 0x00001000, 0x00000001, 0x0000018c, +}; + +} // namespace dex +} // namespace art_api + +#endif // ART_LIBDEXFILE_EXTERNAL_DEX_FILE_TEST_DATA_H_ diff --git a/libdexfile/external/include/art_api/dex_file_external.h b/libdexfile/external/include/art_api/dex_file_external.h index b880335b15..360be926d7 100644 --- a/libdexfile/external/include/art_api/dex_file_external.h +++ b/libdexfile/external/include/art_api/dex_file_external.h @@ -18,67 +18,140 @@ #define ART_LIBDEXFILE_EXTERNAL_INCLUDE_ART_API_DEX_FILE_EXTERNAL_H_ // Dex file external API - -#include <sys/types.h> #include <stdint.h> +#include <sys/cdefs.h> +#include <sys/types.h> -#ifdef __cplusplus -extern "C" { -#endif +__BEGIN_DECLS // This is the stable C ABI that backs art_api::dex below. Structs and functions // may only be added here. C++ users should use dex_file_support.h instead. -struct ExtDexFileMethodInfo { - size_t sizeof_struct; // Size of this structure (to allow future extensions). - uint32_t addr; // Start of dex byte-code relative to the start of the dex file. - uint32_t size; // Size of the dex byte-code in bytes. - const char* name; - size_t name_size; -}; +struct ADexFile; +typedef struct ADexFile ADexFile; -enum ExtDexFileError { - kExtDexFileOk = 0, - kExtDexFileError = 1, // Unspecified error. - kExtDexFileNotEnoughData = 2, - kExtDexFileInvalidHeader = 3, -}; +struct ADexFile_Method; +typedef struct ADexFile_Method ADexFile_Method; -enum ExtDexFileMethodFlags { - kExtDexFileWithSignature = 1, +enum ADexFile_Error : uint32_t { + ADEXFILE_ERROR_OK = 0, + ADEXFILE_ERROR_INVALID_DEX = 1, + ADEXFILE_ERROR_INVALID_HEADER = 2, + ADEXFILE_ERROR_NOT_ENOUGH_DATA = 3, }; - -struct ExtDexFile; - -// Try to open a dex file in the given memory range. -// If the memory range is too small, larger suggest size is written to the argument. -int ExtDexFileOpenFromMemory(const void* addr, - /*inout*/ size_t* size, - const char* location, - /*out*/ struct ExtDexFile** ext_dex_file); +typedef enum ADexFile_Error ADexFile_Error; // Callback used to return information about a dex method. -typedef void ExtDexFileMethodInfoCallback(void* user_data, - struct ExtDexFileMethodInfo* method_info); - -// Find a single dex method based on the given dex offset. -int ExtDexFileGetMethodInfoForOffset(struct ExtDexFile* ext_dex_file, - uint32_t dex_offset, - uint32_t flags, - ExtDexFileMethodInfoCallback* method_info_cb, - void* user_data); - -// Return all dex methods in the dex file. -void ExtDexFileGetAllMethodInfos(struct ExtDexFile* ext_dex_file, - uint32_t flags, - ExtDexFileMethodInfoCallback* method_info_cb, - void* user_data); - -// Release all associated memory. -void ExtDexFileClose(struct ExtDexFile* ext_dex_file); - -#ifdef __cplusplus -} // extern "C" -#endif +// The method information is valid only during the callback. +typedef void ADexFile_MethodCallback(void* _Nullable callback_data, + const ADexFile_Method* _Nonnull method); + +// Interprets a chunk of memory as a dex file. +// +// @param address Pointer to the start of dex file data. +// The caller must retain the memory until the object is destroyed. +// @param size Size of the memory range. If the size is too small, the method returns +// ADEXFILE_ERROR_NOT_ENOUGH_DATA and sets new_size to some larger size +// (which still might large enough, so several retries might be needed). +// @param new_size On successful load, this contains exact dex file size from header. +// @param location A string that describes the dex file. Preferably its path. +// It is mostly used just for log messages and may be "". +// @param dex_file The created dex file object, or nullptr on error. +// It must be later freed with ADexFile_Destroy. +// +// @return ADEXFILE_ERROR_OK if successful. +// @return ADEXFILE_ERROR_NOT_ENOUGH_DATA if the provided dex file is too short (truncated). +// @return ADEXFILE_ERROR_INVALID_HEADER if the memory does not seem to represent DEX file. +// @return ADEXFILE_ERROR_INVALID_DEX if any other non-specific error occurs. +// +// Thread-safe (creates new object). +ADexFile_Error ADexFile_create(const void* _Nonnull address, + size_t size, + size_t* _Nullable new_size, + const char* _Nonnull location, + /*out*/ ADexFile* _Nullable * _Nonnull out_dex_file); + +// Find method at given offset and call callback with information about the method. +// +// @param dex_offset Offset relative to the start of the dex file header. +// @param callback The callback to call when method is found. Any data that needs to +// outlive the execution of the callback must be copied by the user. +// @param callback_data Extra user-specified argument for the callback. +// +// @return Number of methods found (0 or 1). +// +// Not thread-safe for calls on the same ADexFile instance. +size_t ADexFile_findMethodAtOffset(ADexFile* _Nonnull self, + size_t dex_offset, + ADexFile_MethodCallback* _Nonnull callback, + void* _Nullable callback_data); + +// Call callback for all methods in the DEX file. +// +// @param flags Specifies which information should be obtained. +// @param callback The callback to call for all methods. Any data that needs to +// outlive the execution of the callback must be copied by the user. +// @param callback_data Extra user-specified argument for the callback. +// +// @return Number of methods found. +// +// Not thread-safe for calls on the same ADexFile instance. +size_t ADexFile_forEachMethod(ADexFile* _Nonnull self, + ADexFile_MethodCallback* _Nonnull callback, + void* _Nullable callback_data); + +// Free the given object. +// +// Thread-safe, can be called only once for given instance. +void ADexFile_destroy(ADexFile* _Nullable self); + +// @return Offset of byte-code of the method relative to start of the dex file. +// @param out_size Optionally return size of byte-code in bytes. +// Not thread-safe for calls on the same ADexFile instance. +size_t ADexFile_Method_getCodeOffset(const ADexFile_Method* _Nonnull self, + size_t* _Nullable out_size); + +// @return Method name only (without class). +// The encoding is slightly modified UTF8 (see Dex specification). +// @param out_size Optionally return string size (excluding null-terminator). +// +// Returned data may be short lived: it must be copied before calling +// this method again within the same ADexFile. +// (it is currently long lived, but this is not guaranteed in the future). +// +// Not thread-safe for calls on the same ADexFile instance. +const char* _Nonnull ADexFile_Method_getName(const ADexFile_Method* _Nonnull self, + size_t* _Nullable out_size); + +// @return Method name (with class name). +// The encoding is slightly modified UTF8 (see Dex specification). +// @param out_size Optionally return string size (excluding null-terminator). +// @param with_params Whether to include method parameters and return type. +// +// Returned data may be short lived: it must be copied before calling +// this method again within the same ADexFile. +// (it points to pretty printing buffer within the ADexFile instance) +// +// Not thread-safe for calls on the same ADexFile instance. +const char* _Nonnull ADexFile_Method_getQualifiedName(const ADexFile_Method* _Nonnull self, + int with_params, + size_t* _Nullable out_size); + +// @return Class descriptor (mangled class name). +// The encoding is slightly modified UTF8 (see Dex specification). +// @param out_size Optionally return string size (excluding null-terminator). +// +// Returned data may be short lived: it must be copied before calling +// this method again within the same ADexFile. +// (it is currently long lived, but this is not guaranteed in the future). +// +// Not thread-safe for calls on the same ADexFile instance. +const char* _Nonnull ADexFile_Method_getClassDescriptor(const ADexFile_Method* _Nonnull self, + size_t* _Nullable out_size); + +// @return Compile-time literal or nullptr on error. +const char* _Nullable ADexFile_Error_toString(ADexFile_Error self); + +__END_DECLS #endif // ART_LIBDEXFILE_EXTERNAL_INCLUDE_ART_API_DEX_FILE_EXTERNAL_H_ diff --git a/libdexfile/external/include/art_api/dex_file_support.h b/libdexfile/external/include/art_api/dex_file_support.h index 7c4ff5c13a..2361bf9211 100644 --- a/libdexfile/external/include/art_api/dex_file_support.h +++ b/libdexfile/external/include/art_api/dex_file_support.h @@ -19,133 +19,114 @@ // C++ wrapper for the dex file external API. -#include <cstring> #include <memory> #include <string> -#include <string_view> -#include <utility> -#include <vector> #include <android-base/macros.h> -#include <android-base/mapped_file.h> #include "art_api/dex_file_external.h" namespace art_api { namespace dex { -// Returns true if libdexfile_external.so is already loaded. Otherwise tries to +#define FOR_EACH_ADEX_FILE_SYMBOL(MACRO) \ + MACRO(ADexFile_Error_toString) \ + MACRO(ADexFile_Method_getClassDescriptor) \ + MACRO(ADexFile_Method_getCodeOffset) \ + MACRO(ADexFile_Method_getName) \ + MACRO(ADexFile_Method_getQualifiedName) \ + MACRO(ADexFile_create) \ + MACRO(ADexFile_destroy) \ + MACRO(ADexFile_findMethodAtOffset) \ + MACRO(ADexFile_forEachMethod) \ + +#define DEFINE_ADEX_FILE_SYMBOL(DLFUNC) extern decltype(DLFUNC)* g_##DLFUNC; +FOR_EACH_ADEX_FILE_SYMBOL(DEFINE_ADEX_FILE_SYMBOL) +#undef DEFINE_ADEX_FILE_SYMBOL + +// Returns true if libdexfile.so is already loaded. Otherwise tries to // load it and returns true if successful. Otherwise returns false and sets -// *error_msg. If false is returned then calling any function below may abort -// the process. Thread safe. -bool TryLoadLibdexfileExternal(std::string* error_msg); +// *error_msg. Thread safe. +bool TryLoadLibdexfile(std::string* error_msg); -// Loads the libdexfile_external.so library and sets up function pointers. -// Aborts with a fatal error on any error. For internal use by the classes -// below. -void LoadLibdexfileExternal(); +// TryLoadLibdexfile and fatally abort process if unsuccessful. +void LoadLibdexfile(); -using DexString = std::string; - -struct MethodInfo { - uint32_t offset = 0; // Code offset relative to the start of the dex file header - uint32_t len = 0; // Code length - DexString name; -}; - -inline bool operator==(const MethodInfo& s1, const MethodInfo& s2) { - return s1.offset == s2.offset && s1.len == s2.len && s1.name == s2.name; -} - -// External stable API to access ordinary dex files and CompactDex. This wraps -// the stable C ABI and handles instance ownership. Thread-compatible but not -// thread-safe. +// API for reading ordinary dex files and CompactDex files. +// It is minimal 1:1 C++ wrapper around the C ABI. +// See documentation in dex_file_external.h class DexFile { public: - DexFile(DexFile&& dex_file) noexcept { - std::swap(ext_dex_file_, dex_file.ext_dex_file_); - std::swap(map_, dex_file.map_); - } - - explicit DexFile(std::unique_ptr<DexFile>& dex_file) noexcept { - std::swap(ext_dex_file_, dex_file->ext_dex_file_); - std::swap(map_, dex_file->map_); - dex_file.reset(); + struct Method { + size_t GetCodeOffset(size_t* out_size = nullptr) const { + return g_ADexFile_Method_getCodeOffset(self, out_size); + } + + const char* GetName(size_t* out_size = nullptr) const { + return g_ADexFile_Method_getName(self, out_size); + } + + const char* GetQualifiedName(bool with_params = false, size_t* out_size = nullptr) const { + return g_ADexFile_Method_getQualifiedName(self, with_params, out_size); + } + + const char* GetClassDescriptor(size_t* out_size = nullptr) const { + return g_ADexFile_Method_getClassDescriptor(self, out_size); + } + + const ADexFile_Method* const self; + }; + + struct Error { + const char* ToString() const { + return g_ADexFile_Error_toString(self); + } + + bool Ok() const { + return self == ADEXFILE_ERROR_OK; + } + + ADexFile_Error Code() { + return self; + } + + ADexFile_Error const self; + }; + + static Error Create(const void* address, + size_t size, + size_t* new_size, + const char* location, + /*out*/ std::unique_ptr<DexFile>* out_dex_file) { + LoadLibdexfile(); + ADexFile* adex = nullptr; + ADexFile_Error error = g_ADexFile_create(address, size, new_size, location, &adex); + if (adex != nullptr) { + *out_dex_file = std::unique_ptr<DexFile>(new DexFile{adex}); + } + return Error{error}; } - virtual ~DexFile(); - - // Interprets a chunk of memory as a dex file. As long as *size is too small, - // returns nullptr, sets *size to a new size to try again with, and sets - // *error_msg. That might happen repeatedly. Also returns nullptr - // on error in which case *error_msg is set to a nonempty string. - // - // location is a string that describes the dex file, and is preferably its - // path. It is mostly used to make error messages better, and may be "". - // - // The caller must retain the memory. - static std::unique_ptr<DexFile> OpenFromMemory(const void* addr, - size_t* size, - const std::string& location, - /*out*/ std::string* error_msg); - - // mmaps the given file offset in the open fd and reads a dexfile from there. - // Returns nullptr on error in which case *error_msg is set. - // - // location is a string that describes the dex file, and is preferably its - // path. It is mostly used to make error messages better, and may be "". - static std::unique_ptr<DexFile> OpenFromFd(int fd, - off_t offset, - const std::string& location, - /*out*/ std::string* error_msg); - - // Given an offset relative to the start of the dex file header, if there is a - // method whose instruction range includes that offset then calls the provided - // callback with ExtDexFileMethodInfo* (which is live only during the callback). - template<typename T /* lambda taking (ExtDexFileMethodInfo*) */> - void GetMethodInfoForOffset(int64_t dex_offset, T& callback, uint32_t flags = 0) { - auto cb = [](void* ctx, ExtDexFileMethodInfo* info) { (*reinterpret_cast<T*>(ctx))(info); }; - g_ExtDexFileGetMethodInfoForOffset(ext_dex_file_, dex_offset, flags, cb, &callback); + virtual ~DexFile() { + g_ADexFile_destroy(self_); } - // Given an offset relative to the start of the dex file header, if there is a - // method whose instruction range includes that offset then returns info about - // it, otherwise returns a struct with offset == 0. MethodInfo.name receives - // the full function signature if with_signature is set, otherwise it gets the - // class and method name only. - MethodInfo GetMethodInfoForOffset(int64_t dex_offset, bool with_signature); - - // Call the provided callback for all dex methods. - template<typename T /* lambda taking (ExtDexFileMethodInfo*) */> - void GetAllMethodInfos(T& callback, uint32_t flags = 0) { - auto cb = [](void* ctx, ExtDexFileMethodInfo* info) { (*reinterpret_cast<T*>(ctx))(info); }; - g_ExtDexFileGetAllMethodInfos(ext_dex_file_, flags, cb, &callback); + template<typename T /* lambda which takes (const DexFile::Method&) as argument */> + inline size_t FindMethodAtOffset(uint32_t dex_offset, T callback) { + auto cb = [](void* ctx, const ADexFile_Method* m) { (*reinterpret_cast<T*>(ctx))(Method{m}); }; + return g_ADexFile_findMethodAtOffset(self_, dex_offset, cb, &callback); } - // Returns info structs about all methods in the dex file. MethodInfo.name - // receives the full function signature if with_signature is set, otherwise it - // gets the class and method name only. - std::vector<MethodInfo> GetAllMethodInfos(bool with_signature); - - private: - static inline MethodInfo AbsorbMethodInfo(const ExtDexFileMethodInfo* info) { - return { - .offset = info->addr, - .len = info->size, - .name = std::string(info->name, info->name_size) - }; + template<typename T /* lambda which takes (const DexFile::Method&) as argument */> + inline size_t ForEachMethod(T callback) { + auto cb = [](void* ctx, const ADexFile_Method* m) { (*reinterpret_cast<T*>(ctx))(Method{m}); }; + return g_ADexFile_forEachMethod(self_, cb, &callback); } - friend bool TryLoadLibdexfileExternal(std::string* error_msg); - explicit DexFile(ExtDexFile* ext_dex_file) : ext_dex_file_(ext_dex_file) {} - ExtDexFile* ext_dex_file_ = nullptr; // Owned instance. nullptr only in moved-from zombies. - std::unique_ptr<android::base::MappedFile> map_; // Owned map (if we allocated one). + protected: + explicit DexFile(ADexFile* self) : self_(self) {} - // These are initialized by TryLoadLibdexfileExternal. - static decltype(ExtDexFileOpenFromMemory)* g_ExtDexFileOpenFromMemory; - static decltype(ExtDexFileGetMethodInfoForOffset)* g_ExtDexFileGetMethodInfoForOffset; - static decltype(ExtDexFileGetAllMethodInfos)* g_ExtDexFileGetAllMethodInfos; - static decltype(ExtDexFileClose)* g_ExtDexFileClose; + ADexFile* const self_; DISALLOW_COPY_AND_ASSIGN(DexFile); }; diff --git a/libdexfile/external/libdexfile_external.map.txt b/libdexfile/external/libdexfile_external.map.txt deleted file mode 100644 index e9d12fbee6..0000000000 --- a/libdexfile/external/libdexfile_external.map.txt +++ /dev/null @@ -1,9 +0,0 @@ -LIBDEXFILE_EXTERNAL_1 { - global: - ExtDexFileClose; - ExtDexFileGetAllMethodInfos; - ExtDexFileGetMethodInfoForOffset; - ExtDexFileOpenFromMemory; - local: - *; -}; diff --git a/libdexfile/libdexfile.map.txt b/libdexfile/libdexfile.map.txt new file mode 100644 index 0000000000..9cbdc13edf --- /dev/null +++ b/libdexfile/libdexfile.map.txt @@ -0,0 +1,14 @@ +LIBDEXFILE_1 { + global: + ADexFile_Error_toString; # apex + ADexFile_Method_getClassDescriptor; # apex + ADexFile_Method_getCodeOffset; # apex + ADexFile_Method_getName; # apex + ADexFile_Method_getQualifiedName; # apex + ADexFile_create; # apex + ADexFile_destroy; # apex + ADexFile_findMethodAtOffset; # apex + ADexFile_forEachMethod; # apex + local: + *; +}; |