diff options
| -rw-r--r-- | openjdkjvmti/ti_redefine.cc | 26 | ||||
| -rw-r--r-- | runtime/class_linker_test.cc | 4 | ||||
| -rw-r--r-- | runtime/hidden_api.cc | 143 | ||||
| -rw-r--r-- | runtime/hidden_api.h | 12 | ||||
| -rw-r--r-- | runtime/mirror/class_ext.cc | 12 | ||||
| -rw-r--r-- | runtime/mirror/class_ext.h | 18 | ||||
| -rwxr-xr-x | test/999-redefine-hiddenapi/src-redefine/gen.sh | 6 | ||||
| -rw-r--r-- | test/999-redefine-hiddenapi/src/Main.java | 59 |
8 files changed, 166 insertions, 114 deletions
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc index cabb758912..6ca4e3846c 100644 --- a/openjdkjvmti/ti_redefine.cc +++ b/openjdkjvmti/ti_redefine.cc @@ -1427,11 +1427,6 @@ void Redefiner::ClassRedefinition::UpdateMethods(art::ObjPtr<art::mirror::Class> method.SetCodeItemOffset(dex_file_->FindCodeItemOffset(class_def, dex_method_idx)); // Clear all the intrinsics related flags. method.SetNotIntrinsic(); - // Disable hiddenapi checks when accessing this method. - // Redefining hiddenapi flags is unsupported for the same reasons as redefining - // access flags. Moreover, ArtMethod loses pointer to the old dex file, so just - // disable the checks completely for consistency. - method.SetAccessFlags(method.GetAccessFlags() | art::kAccPublicApi); } } @@ -1450,11 +1445,6 @@ void Redefiner::ClassRedefinition::UpdateFields(art::ObjPtr<art::mirror::Class> CHECK(new_field_id != nullptr); // We only need to update the index since the other data in the ArtField cannot be updated. field.SetDexFieldIndex(dex_file_->GetIndexForFieldId(*new_field_id)); - // Disable hiddenapi checks when accessing this method. - // Redefining hiddenapi flags is unsupported for the same reasons as redefining - // access flags. Moreover, ArtField loses pointer to the old dex file, so just - // disable the checks completely for consistency. - field.SetAccessFlags(field.GetAccessFlags() | art::kAccPublicApi); } } } @@ -1469,15 +1459,25 @@ void Redefiner::ClassRedefinition::UpdateClass( UpdateMethods(mclass, class_def); UpdateFields(mclass); + art::ObjPtr<art::mirror::ClassExt> ext(mclass->GetExtData()); + CHECK(!ext.IsNull()); + ext->SetOriginalDexFile(original_dex_file); + + // If this is the first time the class is being redefined, store + // the native DexFile pointer and initial ClassDef index in ClassExt. + // This preserves the pointer for hiddenapi access checks which need + // to read access flags from the initial DexFile. + if (ext->GetPreRedefineDexFile() == nullptr) { + ext->SetPreRedefineDexFile(&mclass->GetDexFile()); + ext->SetPreRedefineClassDefIndex(mclass->GetDexClassDefIndex()); + } + // Update the class fields. // Need to update class last since the ArtMethod gets its DexFile from the class (which is needed // to call GetReturnTypeDescriptor and GetParameterTypeList above). mclass->SetDexCache(new_dex_cache.Ptr()); mclass->SetDexClassDefIndex(dex_file_->GetIndexForClassDef(class_def)); mclass->SetDexTypeIndex(dex_file_->GetIndexForTypeId(*dex_file_->FindTypeId(class_sig_.c_str()))); - art::ObjPtr<art::mirror::ClassExt> ext(mclass->GetExtData()); - CHECK(!ext.IsNull()); - ext->SetOriginalDexFile(original_dex_file); // Notify the jit that all the methods in this class were redefined. Need to do this last since // the jit relies on the dex_file_ being correct (for native methods at least) to find the method diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc index 56fdd06ff2..fe45b9e1f0 100644 --- a/runtime/class_linker_test.cc +++ b/runtime/class_linker_test.cc @@ -611,6 +611,10 @@ struct ClassExtOffsets : public CheckOffsets<mirror::ClassExt> { addOffset(OFFSETOF_MEMBER(mirror::ClassExt, obsolete_dex_caches_), "obsoleteDexCaches"); addOffset(OFFSETOF_MEMBER(mirror::ClassExt, obsolete_methods_), "obsoleteMethods"); addOffset(OFFSETOF_MEMBER(mirror::ClassExt, original_dex_file_), "originalDexFile"); + addOffset(OFFSETOF_MEMBER(mirror::ClassExt, pre_redefine_class_def_index_), + "preRedefineClassDefIndex"); + addOffset(OFFSETOF_MEMBER(mirror::ClassExt, pre_redefine_dex_file_ptr_), + "preRedefineDexFilePtr"); addOffset(OFFSETOF_MEMBER(mirror::ClassExt, verify_error_), "verifyError"); } }; diff --git a/runtime/hidden_api.cc b/runtime/hidden_api.cc index 6cdba73c30..e0939ddbdb 100644 --- a/runtime/hidden_api.cc +++ b/runtime/hidden_api.cc @@ -21,7 +21,10 @@ #include "art_field-inl.h" #include "art_method-inl.h" #include "base/dumpable.h" +#include "class_root.h" #include "dex/class_accessor-inl.h" +#include "dex/dex_file_loader.h" +#include "mirror/class_ext.h" #include "scoped_thread_state_change.h" #include "thread-inl.h" #include "well_known_classes.h" @@ -93,6 +96,24 @@ MemberSignature::MemberSignature(ArtMethod* method) { type_ = kMethod; } +MemberSignature::MemberSignature(const ClassAccessor::Field& field) { + const DexFile& dex_file = field.GetDexFile(); + const DexFile::FieldId& field_id = dex_file.GetFieldId(field.GetIndex()); + class_name_ = dex_file.GetFieldDeclaringClassDescriptor(field_id); + member_name_ = dex_file.GetFieldName(field_id); + type_signature_ = dex_file.GetFieldTypeDescriptor(field_id); + type_ = kField; +} + +MemberSignature::MemberSignature(const ClassAccessor::Method& method) { + const DexFile& dex_file = method.GetDexFile(); + const DexFile::MethodId& method_id = dex_file.GetMethodId(method.GetIndex()); + class_name_ = dex_file.GetMethodDeclaringClassDescriptor(method_id); + member_name_ = dex_file.GetMethodName(method_id); + type_signature_ = dex_file.GetMethodSignature(method_id).ToString(); + type_ = kMethod; +} + inline std::vector<const char*> MemberSignature::GetSignatureParts() const { if (type_ == kField) { return { class_name_.c_str(), "->", member_name_.c_str(), ":", type_signature_.c_str() }; @@ -137,6 +158,17 @@ void MemberSignature::WarnAboutAccess(AccessMethod access_method, hiddenapi::Api << Dumpable<MemberSignature>(*this) << " (" << list << ", " << access_method << ")"; } +bool MemberSignature::Equals(const MemberSignature& other) { + return type_ == other.type_ && + class_name_ == other.class_name_ && + member_name_ == other.member_name_ && + type_signature_ == other.type_signature_; +} + +bool MemberSignature::MemberNameAndTypeMatch(const MemberSignature& other) { + return member_name_ == other.member_name_ && type_signature_ == other.type_signature_; +} + #ifdef ART_TARGET_ANDROID // Convert an AccessMethod enum to a value for logging from the proto enum. // This method may look odd (the enum values are current the same), but it @@ -238,63 +270,88 @@ static ALWAYS_INLINE void MaybeWhitelistMember(Runtime* runtime, T* member) static constexpr uint32_t kNoDexFlags = 0u; static constexpr uint32_t kInvalidDexFlags = static_cast<uint32_t>(-1); -uint32_t GetDexFlags(ArtField* field) REQUIRES_SHARED(Locks::mutator_lock_) { - ObjPtr<mirror::Class> declaring_class = field->GetDeclaringClass(); - DCHECK(declaring_class != nullptr) << "Fields always have a declaring class"; - - const DexFile::ClassDef* class_def = declaring_class->GetClassDef(); - if (class_def == nullptr) { - return kNoDexFlags; - } +static ALWAYS_INLINE uint32_t GetMemberDexIndex(ArtField* field) { + return field->GetDexFieldIndex(); +} - uint32_t flags = kInvalidDexFlags; - DCHECK(!AreValidDexFlags(flags)); +static ALWAYS_INLINE uint32_t GetMemberDexIndex(ArtMethod* method) + REQUIRES_SHARED(Locks::mutator_lock_) { + // Use the non-obsolete method to avoid DexFile mismatch between + // the method index and the declaring class. + return method->GetNonObsoleteMethod()->GetDexMethodIndex(); +} - ClassAccessor accessor(declaring_class->GetDexFile(), - *class_def, - /* parse_hiddenapi_class_data= */ true); - auto fn_visit = [&](const ClassAccessor::Field& dex_field) { - if (dex_field.GetIndex() == field->GetDexFieldIndex()) { - flags = dex_field.GetHiddenapiFlags(); - } - }; +static void VisitMembers(const DexFile& dex_file, + const DexFile::ClassDef& class_def, + const std::function<void(const ClassAccessor::Field&)>& fn_visit) { + ClassAccessor accessor(dex_file, class_def, /* parse_hiddenapi_class_data= */ true); accessor.VisitFields(fn_visit, fn_visit); +} - CHECK_NE(flags, kInvalidDexFlags) << "Could not find flags for field " << field->PrettyField(); - DCHECK(AreValidDexFlags(flags)); - return flags; +static void VisitMembers(const DexFile& dex_file, + const DexFile::ClassDef& class_def, + const std::function<void(const ClassAccessor::Method&)>& fn_visit) { + ClassAccessor accessor(dex_file, class_def, /* parse_hiddenapi_class_data= */ true); + accessor.VisitMethods(fn_visit, fn_visit); } -uint32_t GetDexFlags(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) { - ObjPtr<mirror::Class> declaring_class = method->GetDeclaringClass(); - if (declaring_class.IsNull()) { - DCHECK(method->IsRuntimeMethod()); - return kNoDexFlags; - } +template<typename T> +uint32_t GetDexFlags(T* member) REQUIRES_SHARED(Locks::mutator_lock_) { + static_assert(std::is_same<T, ArtField>::value || std::is_same<T, ArtMethod>::value); + using AccessorType = typename std::conditional<std::is_same<T, ArtField>::value, + ClassAccessor::Field, ClassAccessor::Method>::type; - const DexFile::ClassDef* class_def = declaring_class->GetClassDef(); - if (class_def == nullptr) { + ObjPtr<mirror::Class> declaring_class = member->GetDeclaringClass(); + if (declaring_class.IsNull()) { return kNoDexFlags; } uint32_t flags = kInvalidDexFlags; DCHECK(!AreValidDexFlags(flags)); - // Use the non-obsolete method to avoid DexFile mismatch between - // the method index and the declaring class. - uint32_t method_index = method->GetNonObsoleteMethod()->GetDexMethodIndex(); - - ClassAccessor accessor(declaring_class->GetDexFile(), - *class_def, - /* parse_hiddenapi_class_data= */ true); - auto fn_visit = [&](const ClassAccessor::Method& dex_method) { - if (dex_method.GetIndex() == method_index) { - flags = dex_method.GetHiddenapiFlags(); + // Check if the declaring class has ClassExt allocated. If it does, check if + // the pre-JVMTI redefine dex file has been set to determine if the declaring + // class has been JVMTI-redefined. + ObjPtr<mirror::ClassExt> ext(declaring_class->GetExtData()); + const DexFile* original_dex = ext.IsNull() ? nullptr : ext->GetPreRedefineDexFile(); + if (LIKELY(original_dex == nullptr)) { + // Class is not redefined. Find the class def, iterate over its members and + // find the entry corresponding to this `member`. + const DexFile::ClassDef* class_def = declaring_class->GetClassDef(); + if (class_def == nullptr) { + flags = kNoDexFlags; + } else { + uint32_t member_index = GetMemberDexIndex(member); + auto fn_visit = [&](const AccessorType& dex_member) { + if (dex_member.GetIndex() == member_index) { + flags = dex_member.GetHiddenapiFlags(); + } + }; + VisitMembers(declaring_class->GetDexFile(), *class_def, fn_visit); } - }; - accessor.VisitMethods(fn_visit, fn_visit); + } else { + // Class was redefined using JVMTI. We have a pointer to the original dex file + // and the class def index of this class in that dex file, but the field/method + // indices are lost. Iterate over all members of the class def and find the one + // corresponding to this `member` by name and type string comparison. + // This is obviously very slow, but it is only used when non-exempt code tries + // to access a hidden member of a JVMTI-redefined class. + uint16_t class_def_idx = ext->GetPreRedefineClassDefIndex(); + DCHECK_NE(class_def_idx, DexFile::kDexNoIndex16); + const DexFile::ClassDef& original_class_def = original_dex->GetClassDef(class_def_idx); + MemberSignature member_signature(member); + auto fn_visit = [&](const AccessorType& dex_member) { + MemberSignature cur_signature(dex_member); + if (member_signature.MemberNameAndTypeMatch(cur_signature)) { + DCHECK(member_signature.Equals(cur_signature)); + flags = dex_member.GetHiddenapiFlags(); + } + }; + VisitMembers(*original_dex, original_class_def, fn_visit); + } - CHECK_NE(flags, kInvalidDexFlags) << "Could not find flags for method " << method->PrettyMethod(); + CHECK_NE(flags, kInvalidDexFlags) << "Could not find hiddenapi flags for " + << Dumpable<MemberSignature>(MemberSignature(member)); DCHECK(AreValidDexFlags(flags)); return flags; } @@ -356,6 +413,8 @@ bool ShouldDenyAccessToMemberImpl(T* member, } // Need to instantiate this. +template uint32_t GetDexFlags<ArtField>(ArtField* member); +template uint32_t GetDexFlags<ArtMethod>(ArtMethod* member); template bool ShouldDenyAccessToMemberImpl<ArtField>(ArtField* member, hiddenapi::ApiList api_list, AccessMethod access_method); diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h index eea58e9880..614154c7a0 100644 --- a/runtime/hidden_api.h +++ b/runtime/hidden_api.h @@ -137,9 +137,14 @@ class MemberSignature { public: explicit MemberSignature(ArtField* field) REQUIRES_SHARED(Locks::mutator_lock_); explicit MemberSignature(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_); + explicit MemberSignature(const ClassAccessor::Field& field); + explicit MemberSignature(const ClassAccessor::Method& method); void Dump(std::ostream& os) const; + bool Equals(const MemberSignature& other); + bool MemberNameAndTypeMatch(const MemberSignature& other); + // Performs prefix match on this member. Since the full member signature is // composed of several parts, we match each part in turn (rather than // building the entire thing in memory and performing a simple prefix match) @@ -160,11 +165,8 @@ class MemberSignature { // Locates hiddenapi flags for `field` in the corresponding dex file. // NB: This is an O(N) operation, linear with the number of members in the class def. -uint32_t GetDexFlags(ArtField* field) REQUIRES_SHARED(Locks::mutator_lock_); - -// Locates hiddenapi flags for `method` in the corresponding dex file. -// NB: This is an O(N) operation, linear with the number of members in the class def. -uint32_t GetDexFlags(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_); +template<typename T> +uint32_t GetDexFlags(T* member) REQUIRES_SHARED(Locks::mutator_lock_); template<typename T> bool ShouldDenyAccessToMemberImpl(T* member, ApiList api_list, AccessMethod access_method) diff --git a/runtime/mirror/class_ext.cc b/runtime/mirror/class_ext.cc index 67126307dc..146adc978c 100644 --- a/runtime/mirror/class_ext.cc +++ b/runtime/mirror/class_ext.cc @@ -119,5 +119,17 @@ void ClassExt::SetOriginalDexFile(ObjPtr<Object> bytes) { SetFieldObject<false>(OFFSET_OF_OBJECT_MEMBER(ClassExt, original_dex_file_), bytes); } +void ClassExt::SetPreRedefineClassDefIndex(uint16_t index) { + DCHECK(!Runtime::Current()->IsActiveTransaction()); + SetField32<false>(OFFSET_OF_OBJECT_MEMBER(ClassExt, pre_redefine_class_def_index_), + static_cast<int32_t>(index)); +} + +void ClassExt::SetPreRedefineDexFile(const DexFile* dex_file) { + DCHECK(!Runtime::Current()->IsActiveTransaction()); + SetField64<false>(OFFSET_OF_OBJECT_MEMBER(ClassExt, pre_redefine_dex_file_ptr_), + static_cast<int64_t>(reinterpret_cast<uintptr_t>(dex_file))); +} + } // namespace mirror } // namespace art diff --git a/runtime/mirror/class_ext.h b/runtime/mirror/class_ext.h index 612fd0f256..126f94a61d 100644 --- a/runtime/mirror/class_ext.h +++ b/runtime/mirror/class_ext.h @@ -64,6 +64,20 @@ class MANAGED ClassExt : public Object { void SetOriginalDexFile(ObjPtr<Object> bytes) REQUIRES_SHARED(Locks::mutator_lock_); + uint16_t GetPreRedefineClassDefIndex() REQUIRES_SHARED(Locks::mutator_lock_) { + return static_cast<uint16_t>( + GetField32(OFFSET_OF_OBJECT_MEMBER(ClassExt, pre_redefine_class_def_index_))); + } + + void SetPreRedefineClassDefIndex(uint16_t index) REQUIRES_SHARED(Locks::mutator_lock_); + + const DexFile* GetPreRedefineDexFile() REQUIRES_SHARED(Locks::mutator_lock_) { + return reinterpret_cast<const DexFile*>(static_cast<uintptr_t>( + GetField64(OFFSET_OF_OBJECT_MEMBER(ClassExt, pre_redefine_dex_file_ptr_)))); + } + + void SetPreRedefineDexFile(const DexFile* dex_file) REQUIRES_SHARED(Locks::mutator_lock_); + void SetObsoleteArrays(ObjPtr<PointerArray> methods, ObjPtr<ObjectArray<DexCache>> dex_caches) REQUIRES_SHARED(Locks::mutator_lock_); @@ -88,6 +102,10 @@ class MANAGED ClassExt : public Object { // The saved verification error of this class. HeapReference<Object> verify_error_; + // Native pointer to DexFile and ClassDef index of this class before it was JVMTI-redefined. + int64_t pre_redefine_dex_file_ptr_; + int32_t pre_redefine_class_def_index_; + friend struct art::ClassExtOffsets; // for verifying offset information DISALLOW_IMPLICIT_CONSTRUCTORS(ClassExt); }; diff --git a/test/999-redefine-hiddenapi/src-redefine/gen.sh b/test/999-redefine-hiddenapi/src-redefine/gen.sh index b5e2aea0bb..f92d79784b 100755 --- a/test/999-redefine-hiddenapi/src-redefine/gen.sh +++ b/test/999-redefine-hiddenapi/src-redefine/gen.sh @@ -23,16 +23,10 @@ CLASS="art/Test999" (cd "$TMP" && \ javac -d "${TMP}" "$DIR/${CLASS}.java" && \ d8 --output . "$TMP/${CLASS}.class" && - hiddenapi encode --input-dex="$TMP/classes.dex" \ - --output-dex="$TMP/classes-hiddenapi.dex" \ - --api-flags="$DIR/../hiddenapi-flags.csv" \ - --no-force-assign-all) echo ' private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(' base64 "${TMP}/${CLASS}.class" | sed -E 's/^/ "/' | sed ':a;N;$!ba;s/\n/" +\n/g' | sed -E '$ s/$/");/' echo ' private static final byte[] DEX_BYTES = Base64.getDecoder().decode(' base64 "${TMP}/classes.dex" | sed -E 's/^/ "/' | sed ':a;N;$!ba;s/\n/" +\n/g' | sed -E '$ s/$/");/' -echo ' private static final byte[] DEX_BYTES_HIDDEN = Base64.getDecoder().decode(' -base64 "${TMP}/classes-hiddenapi.dex" | sed -E 's/^/ "/' | sed ':a;N;$!ba;s/\n/" +\n/g' | sed -E '$ s/$/");/' rm -rf "$TMP" diff --git a/test/999-redefine-hiddenapi/src/Main.java b/test/999-redefine-hiddenapi/src/Main.java index 4627b4fd22..3d9bbda801 100644 --- a/test/999-redefine-hiddenapi/src/Main.java +++ b/test/999-redefine-hiddenapi/src/Main.java @@ -31,53 +31,33 @@ public class Main { // Find the test class in boot class loader and verify that its members are hidden. Class<?> klass = Class.forName("art.Test999", true, BOOT_CLASS_LOADER); - assertMethodIsHidden(true, klass, "before redefinition"); - assertFieldIsHidden(true, klass, "before redefinition"); + assertFieldIsHidden(klass, "before redefinition"); + assertMethodIsHidden(klass, "before redefinition"); // Redefine the class using JVMTI. Use dex file without hiddenapi flags. art.Redefinition.setTestConfiguration(art.Redefinition.Config.COMMON_REDEFINE); art.Redefinition.doCommonClassRedefinition(klass, CLASS_BYTES, DEX_BYTES); - // Verify that the class members are not hidden anymore. - assertMethodIsHidden(false, klass, "after first redefinition"); - assertFieldIsHidden(false, klass, "after first redefinition"); - - // Redefine the class using JVMTI, this time with a dex file with hiddenapi flags. - art.Redefinition.setTestConfiguration(art.Redefinition.Config.COMMON_REDEFINE); - art.Redefinition.doCommonClassRedefinition(klass, CLASS_BYTES, DEX_BYTES_HIDDEN); - - // Verify that the class members are still accessible. - assertMethodIsHidden(false, klass, "after second redefinition"); - assertFieldIsHidden(false, klass, "after second redefinition"); + // Verify that the class members are still hidden. + assertFieldIsHidden(klass, "after first redefinition"); + assertMethodIsHidden(klass, "after first redefinition"); } - private static void assertMethodIsHidden(boolean expectedHidden, Class<?> klass, String msg) { + private static void assertMethodIsHidden(Class<?> klass, String msg) { try { klass.getDeclaredMethod("foo"); - if (expectedHidden) { - // Unexpected. Should have thrown NoSuchMethodException. - throw new RuntimeException("Method should not be accessible " + msg); - } + // Unexpected. Should have thrown NoSuchMethodException. + throw new RuntimeException("Method should not be accessible " + msg); } catch (NoSuchMethodException ex) { - if (!expectedHidden) { - // Unexpected. Should not have thrown NoSuchMethodException. - throw new RuntimeException("Method should be accessible " + msg); - } } } - private static void assertFieldIsHidden(boolean expectedHidden, Class<?> klass, String msg) { + private static void assertFieldIsHidden(Class<?> klass, String msg) { try { klass.getDeclaredField("bar"); - if (expectedHidden) { - // Unexpected. Should have thrown NoSuchFieldException. - throw new RuntimeException("Field should not be accessible " + msg); - } + // Unexpected. Should have thrown NoSuchFieldException. + throw new RuntimeException("Field should not be accessible " + msg); } catch (NoSuchFieldException ex) { - if (!expectedHidden) { - // Unexpected. Should not have thrown NoSuchFieldException. - throw new RuntimeException("Field should be accessible " + msg); - } } } @@ -127,21 +107,4 @@ public class Main { "AAAABAAAAAIAAADkAAAABQAAAAQAAAD0AAAABgAAAAEAAAAUAQAAASAAAAIAAAA0AQAAAyAAAAIA" + "AAB0AQAAARAAAAEAAACAAQAAAiAAABAAAACGAQAAACAAAAEAAACgAgAAAxAAAAEAAACwAgAAABAA" + "AAEAAAC0AgAA"); - private static final byte[] DEX_BYTES_HIDDEN = Base64.getDecoder().decode( - "ZGV4CjAzNQDsgG5ufKulToQpDF+P4dsgeOkgfzzH+5l4AwAAcAAAAHhWNBIAAAAAAAAAAMACAAAQ" + - "AAAAcAAAAAcAAACwAAAAAgAAAMwAAAACAAAA5AAAAAQAAAD0AAAAAQAAABQBAABEAgAANAEAAIYB" + - "AACOAQAAlwEAAJoBAACpAQAAwAEAANQBAADoAQAA/AEAAAoCAAANAgAAEQIAABYCAAAbAgAAIAIA" + - "ACkCAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAJAAAACQAAAAYAAAAAAAAACgAAAAYAAACAAQAA" + - "AQAAAAsAAAAFAAIADQAAAAEAAAAAAAAAAQAAAAwAAAACAAEADgAAAAMAAAAAAAAAAQAAAAEAAAAD" + - "AAAAAAAAAAgAAAAAAAAAoAIAAAAAAAACAAEAAQAAAHQBAAAIAAAAcBADAAEAEwBAAFkQAAAOAAMA" + - "AQACAAAAeQEAAAgAAABiAAEAGgEBAG4gAgAQAA4AEwAOQAAVAA54AAAAAQAAAAQABjxpbml0PgAH" + - "R29vZGJ5ZQABSQANTGFydC9UZXN0OTk5OwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABJMamF2YS9s" + - "YW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEvbGFuZy9TeXN0ZW07AAxUZXN0" + - "OTk5LmphdmEAAVYAAlZMAANiYXIAA2ZvbwADb3V0AAdwcmludGxuAHV+fkQ4eyJjb21waWxhdGlv" + - "bi1tb2RlIjoiZGVidWciLCJtaW4tYXBpIjoxLCJzaGEtMSI6ImQyMmFiNGYxOWI3NTYxNDQ3NTI4" + - "NTdjYTg2YjJjZWU0ZGQ5Y2ExNjYiLCJ2ZXJzaW9uIjoiMS40LjktZGV2In0AAAEBAQABAIGABLQC" + - "AQHUAgAAAAALAAAACAAAAAIAAgAPAAAAAAAAAAEAAAAAAAAAAQAAABAAAABwAAAAAgAAAAcAAACw" + - "AAAAAwAAAAIAAADMAAAABAAAAAIAAADkAAAABQAAAAQAAAD0AAAABgAAAAEAAAAUAQAAASAAAAIA" + - "AAA0AQAAAyAAAAIAAAB0AQAAARAAAAEAAACAAQAAAiAAABAAAACGAQAAACAAAAEAAACgAgAAAxAA" + - "AAEAAACwAgAAAPAAAAEAAAC0AgAAABAAAAEAAADAAgAA"); } |