/* * Copyright (C) 2015 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_DEX2OAT_LINKER_RELATIVE_PATCHER_TEST_H_ #define ART_DEX2OAT_LINKER_RELATIVE_PATCHER_TEST_H_ #include #include "arch/instruction_set.h" #include "arch/instruction_set_features.h" #include "base/array_ref.h" #include "base/globals.h" #include "base/macros.h" #include "compiled_method-inl.h" #include "dex/method_reference.h" #include "dex/string_reference.h" #include "driver/compiled_method_storage.h" #include "linker/relative_patcher.h" #include "oat_quick_method_header.h" #include "stream/vector_output_stream.h" namespace art { namespace linker { // Base class providing infrastructure for architecture-specific tests. class RelativePatcherTest : public testing::Test { protected: RelativePatcherTest(InstructionSet instruction_set, const std::string& variant) : storage_(/*swap_fd=*/ -1), instruction_set_(instruction_set), instruction_set_features_(nullptr), method_offset_map_(), patcher_(nullptr), bss_begin_(0u), compiled_method_refs_(), compiled_methods_(), patched_code_(), output_(), out_(nullptr) { std::string error_msg; instruction_set_features_ = InstructionSetFeatures::FromVariant(instruction_set, variant, &error_msg); CHECK(instruction_set_features_ != nullptr) << error_msg; patched_code_.reserve(16 * KB); } void SetUp() override { Reset(); } void TearDown() override { thunk_provider_.Reset(); compiled_methods_.clear(); patcher_.reset(); bss_begin_ = 0u; string_index_to_offset_map_.clear(); compiled_method_refs_.clear(); compiled_methods_.clear(); patched_code_.clear(); output_.clear(); out_.reset(); } // Reset the helper to start another test. Creating and tearing down the Runtime is expensive, // so we merge related tests together. void Reset() { thunk_provider_.Reset(); method_offset_map_.map.clear(); patcher_ = RelativePatcher::Create(instruction_set_, instruction_set_features_.get(), &thunk_provider_, &method_offset_map_); bss_begin_ = 0u; string_index_to_offset_map_.clear(); compiled_method_refs_.clear(); compiled_methods_.clear(); patched_code_.clear(); output_.clear(); out_.reset(new VectorOutputStream("test output stream", &output_)); } MethodReference MethodRef(uint32_t method_idx) { CHECK_NE(method_idx, 0u); return MethodReference(nullptr, method_idx); } void AddCompiledMethod( MethodReference method_ref, const ArrayRef& code, const ArrayRef& patches = ArrayRef()) { compiled_method_refs_.push_back(method_ref); compiled_methods_.emplace_back(new CompiledMethod( &storage_, instruction_set_, code, /* vmap_table */ ArrayRef(), /* cfi_info */ ArrayRef(), patches)); } uint32_t CodeAlignmentSize(uint32_t header_offset_to_align) { // We want to align the code rather than the preheader. uint32_t unaligned_code_offset = header_offset_to_align + sizeof(OatQuickMethodHeader); uint32_t aligned_code_offset = CompiledMethod::AlignCode(unaligned_code_offset, instruction_set_); return aligned_code_offset - unaligned_code_offset; } void Link() { // Reserve space. static_assert(kTrampolineOffset == 0u, "Unexpected trampoline offset."); uint32_t offset = kTrampolineSize; size_t idx = 0u; for (auto& compiled_method : compiled_methods_) { offset = patcher_->ReserveSpace(offset, compiled_method.get(), compiled_method_refs_[idx]); uint32_t alignment_size = CodeAlignmentSize(offset); offset += alignment_size; offset += sizeof(OatQuickMethodHeader); uint32_t quick_code_offset = offset + compiled_method->CodeDelta(); const auto code = compiled_method->GetQuickCode(); offset += code.size(); method_offset_map_.map.Put(compiled_method_refs_[idx], quick_code_offset); ++idx; } offset = patcher_->ReserveSpaceEnd(offset); uint32_t output_size = offset; output_.reserve(output_size); // Write data. DCHECK(output_.empty()); uint8_t fake_trampoline[kTrampolineSize]; memset(fake_trampoline, 0, sizeof(fake_trampoline)); out_->WriteFully(fake_trampoline, kTrampolineSize); offset = kTrampolineSize; static const uint8_t kPadding[] = { 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u }; uint8_t fake_header[sizeof(OatQuickMethodHeader)]; memset(fake_header, 0, sizeof(fake_header)); for (auto& compiled_method : compiled_methods_) { offset = patcher_->WriteThunks(out_.get(), offset); uint32_t alignment_size = CodeAlignmentSize(offset); CHECK_LE(alignment_size, sizeof(kPadding)); out_->WriteFully(kPadding, alignment_size); offset += alignment_size; out_->WriteFully(fake_header, sizeof(OatQuickMethodHeader)); offset += sizeof(OatQuickMethodHeader); ArrayRef code = compiled_method->GetQuickCode(); if (!compiled_method->GetPatches().empty()) { patched_code_.assign(code.begin(), code.end()); code = ArrayRef(patched_code_); for (const LinkerPatch& patch : compiled_method->GetPatches()) { if (patch.GetType() == LinkerPatch::Type::kCallRelative) { auto result = method_offset_map_.FindMethodOffset(patch.TargetMethod()); uint32_t target_offset = result.first ? result.second : kTrampolineOffset + compiled_method->CodeDelta(); patcher_->PatchCall(&patched_code_, patch.LiteralOffset(), offset + patch.LiteralOffset(), target_offset); } else if (patch.GetType() == LinkerPatch::Type::kStringBssEntry) { uint32_t target_offset = bss_begin_ + string_index_to_offset_map_.Get(patch.TargetStringIndex().index_); patcher_->PatchPcRelativeReference(&patched_code_, patch, offset + patch.LiteralOffset(), target_offset); } else if (patch.GetType() == LinkerPatch::Type::kStringRelative) { uint32_t target_offset = string_index_to_offset_map_.Get(patch.TargetStringIndex().index_); patcher_->PatchPcRelativeReference(&patched_code_, patch, offset + patch.LiteralOffset(), target_offset); } else if (patch.GetType() == LinkerPatch::Type::kCallEntrypoint) { patcher_->PatchEntrypointCall(&patched_code_, patch, offset + patch.LiteralOffset()); } else if (patch.GetType() == LinkerPatch::Type::kBakerReadBarrierBranch) { patcher_->PatchBakerReadBarrierBranch(&patched_code_, patch, offset + patch.LiteralOffset()); } else { LOG(FATAL) << "Bad patch type. " << patch.GetType(); UNREACHABLE(); } } } out_->WriteFully(&code[0], code.size()); offset += code.size(); } offset = patcher_->WriteThunks(out_.get(), offset); CHECK_EQ(offset, output_size); CHECK_EQ(output_.size(), output_size); } bool CheckLinkedMethod(MethodReference method_ref, const ArrayRef& expected_code) { // Check that the original code size must match linked_code.size(). size_t idx = 0u; for (auto ref : compiled_method_refs_) { if (ref == method_ref) { break; } ++idx; } CHECK_NE(idx, compiled_method_refs_.size()); CHECK_EQ(compiled_methods_[idx]->GetQuickCode().size(), expected_code.size()); auto result = method_offset_map_.FindMethodOffset(method_ref); CHECK(result.first); // Must have been linked. size_t offset = result.second - compiled_methods_[idx]->CodeDelta(); CHECK_LT(offset, output_.size()); CHECK_LE(offset + expected_code.size(), output_.size()); ArrayRef linked_code(&output_[offset], expected_code.size()); if (linked_code == expected_code) { return true; } // Log failure info. DumpDiff(expected_code, linked_code); return false; } void DumpDiff(const ArrayRef& expected_code, const ArrayRef& linked_code) { std::ostringstream expected_hex; std::ostringstream linked_hex; std::ostringstream diff_indicator; static const char digits[] = "0123456789abcdef"; bool found_diff = false; for (size_t i = 0; i != expected_code.size(); ++i) { expected_hex << " " << digits[expected_code[i] >> 4] << digits[expected_code[i] & 0xf]; linked_hex << " " << digits[linked_code[i] >> 4] << digits[linked_code[i] & 0xf]; if (!found_diff) { found_diff = (expected_code[i] != linked_code[i]); diff_indicator << (found_diff ? " ^^" : " "); } } CHECK(found_diff); std::string expected_hex_str = expected_hex.str(); std::string linked_hex_str = linked_hex.str(); std::string diff_indicator_str = diff_indicator.str(); if (diff_indicator_str.length() > 60) { CHECK_EQ(diff_indicator_str.length() % 3u, 0u); size_t remove = diff_indicator_str.length() / 3 - 5; std::ostringstream oss; oss << "[stripped " << remove << "]"; std::string replacement = oss.str(); expected_hex_str.replace(0u, remove * 3u, replacement); linked_hex_str.replace(0u, remove * 3u, replacement); diff_indicator_str.replace(0u, remove * 3u, replacement); } LOG(ERROR) << "diff expected_code linked_code"; LOG(ERROR) << "<" << expected_hex_str; LOG(ERROR) << ">" << linked_hex_str; LOG(ERROR) << " " << diff_indicator_str; } class ThunkProvider : public RelativePatcherThunkProvider { public: ThunkProvider() {} void SetThunkCode(const LinkerPatch& patch, ArrayRef code, const std::string& debug_name) { thunk_map_.emplace(ThunkKey(patch), ThunkValue(code, debug_name)); } void GetThunkCode(const LinkerPatch& patch, /*out*/ ArrayRef* code, /*out*/ std::string* debug_name) override { auto it = thunk_map_.find(ThunkKey(patch)); CHECK(it != thunk_map_.end()); const ThunkValue& value = it->second; CHECK(code != nullptr); *code = value.GetCode(); CHECK(debug_name != nullptr); *debug_name = value.GetDebugName(); } void Reset() { thunk_map_.clear(); } private: class ThunkKey { public: explicit ThunkKey(const LinkerPatch& patch) : type_(patch.GetType()), custom_value1_(CustomValue1(patch)), custom_value2_(CustomValue2(patch)) { CHECK(patch.GetType() == LinkerPatch::Type::kCallEntrypoint || patch.GetType() == LinkerPatch::Type::kBakerReadBarrierBranch || patch.GetType() == LinkerPatch::Type::kCallRelative); } bool operator<(const ThunkKey& other) const { if (custom_value1_ != other.custom_value1_) { return custom_value1_ < other.custom_value1_; } if (custom_value2_ != other.custom_value2_) { return custom_value2_ < other.custom_value2_; } return type_ < other.type_; } private: static uint32_t CustomValue1(const LinkerPatch& patch) { switch (patch.GetType()) { case LinkerPatch::Type::kCallEntrypoint: return patch.EntrypointOffset(); case LinkerPatch::Type::kBakerReadBarrierBranch: return patch.GetBakerCustomValue1(); default: return 0; } } static uint32_t CustomValue2(const LinkerPatch& patch) { switch (patch.GetType()) { case LinkerPatch::Type::kBakerReadBarrierBranch: return patch.GetBakerCustomValue2(); default: return 0; } } const LinkerPatch::Type type_; const uint32_t custom_value1_; const uint32_t custom_value2_; }; class ThunkValue { public: ThunkValue(ArrayRef code, const std::string& debug_name) : code_(code.begin(), code.end()), debug_name_(debug_name) {} ArrayRef GetCode() const { return ArrayRef(code_); } const std::string& GetDebugName() const { return debug_name_; } private: const std::vector code_; const std::string debug_name_; }; std::map thunk_map_; }; // Map method reference to assinged offset. // Wrap the map in a class implementing RelativePatcherTargetProvider. class MethodOffsetMap final : public RelativePatcherTargetProvider { public: std::pair FindMethodOffset(MethodReference ref) override { auto it = map.find(ref); if (it == map.end()) { return std::pair(false, 0u); } else { return std::pair(true, it->second); } } SafeMap map; }; static const uint32_t kTrampolineSize = 4u; static const uint32_t kTrampolineOffset = 0u; CompiledMethodStorage storage_; InstructionSet instruction_set_; std::unique_ptr instruction_set_features_; ThunkProvider thunk_provider_; MethodOffsetMap method_offset_map_; std::unique_ptr patcher_; uint32_t bss_begin_; SafeMap string_index_to_offset_map_; std::vector compiled_method_refs_; std::vector> compiled_methods_; std::vector patched_code_; std::vector output_; std::unique_ptr out_; }; } // namespace linker } // namespace art #endif // ART_DEX2OAT_LINKER_RELATIVE_PATCHER_TEST_H_