diff options
83 files changed, 4103 insertions, 1376 deletions
diff --git a/compiler/optimizing/intrinsics_mips.cc b/compiler/optimizing/intrinsics_mips.cc index e9c6615870..f1ae549928 100644 --- a/compiler/optimizing/intrinsics_mips.cc +++ b/compiler/optimizing/intrinsics_mips.cc @@ -744,14 +744,55 @@ void IntrinsicCodeGeneratorMIPS::VisitLongBitCount(HInvoke* invoke) { GenBitCount(invoke->GetLocations(), Primitive::kPrimLong, IsR6(), GetAssembler()); } -static void MathAbsFP(LocationSummary* locations, bool is64bit, MipsAssembler* assembler) { +static void MathAbsFP(LocationSummary* locations, + bool is64bit, + bool isR2OrNewer, + bool isR6, + MipsAssembler* assembler) { FRegister in = locations->InAt(0).AsFpuRegister<FRegister>(); FRegister out = locations->Out().AsFpuRegister<FRegister>(); - if (is64bit) { - __ AbsD(out, in); + // Note, as a "quality of implementation", rather than pure "spec compliance", we require that + // Math.abs() clears the sign bit (but changes nothing else) for all numbers, including NaN + // (signaling NaN may become quiet though). + // + // The ABS.fmt instructions (abs.s and abs.d) do exactly that when NAN2008=1 (R6). For this case, + // both regular floating point numbers and NAN values are treated alike, only the sign bit is + // affected by this instruction. + // But when NAN2008=0 (R2 and before), the ABS.fmt instructions can't be used. For this case, any + // NaN operand signals invalid operation. This means that other bits (not just sign bit) might be + // changed when doing abs(NaN). Because of that, we clear sign bit in a different way. + if (isR6) { + if (is64bit) { + __ AbsD(out, in); + } else { + __ AbsS(out, in); + } } else { - __ AbsS(out, in); + if (is64bit) { + if (in != out) { + __ MovD(out, in); + } + __ MoveFromFpuHigh(TMP, in); + // ins instruction is not available for R1. + if (isR2OrNewer) { + __ Ins(TMP, ZERO, 31, 1); + } else { + __ Sll(TMP, TMP, 1); + __ Srl(TMP, TMP, 1); + } + __ MoveToFpuHigh(TMP, out); + } else { + __ Mfc1(TMP, in); + // ins instruction is not available for R1. + if (isR2OrNewer) { + __ Ins(TMP, ZERO, 31, 1); + } else { + __ Sll(TMP, TMP, 1); + __ Srl(TMP, TMP, 1); + } + __ Mtc1(TMP, out); + } } } @@ -761,7 +802,7 @@ void IntrinsicLocationsBuilderMIPS::VisitMathAbsDouble(HInvoke* invoke) { } void IntrinsicCodeGeneratorMIPS::VisitMathAbsDouble(HInvoke* invoke) { - MathAbsFP(invoke->GetLocations(), /* is64bit */ true, GetAssembler()); + MathAbsFP(invoke->GetLocations(), /* is64bit */ true, IsR2OrNewer(), IsR6(), GetAssembler()); } // float java.lang.Math.abs(float) @@ -770,7 +811,7 @@ void IntrinsicLocationsBuilderMIPS::VisitMathAbsFloat(HInvoke* invoke) { } void IntrinsicCodeGeneratorMIPS::VisitMathAbsFloat(HInvoke* invoke) { - MathAbsFP(invoke->GetLocations(), /* is64bit */ false, GetAssembler()); + MathAbsFP(invoke->GetLocations(), /* is64bit */ false, IsR2OrNewer(), IsR6(), GetAssembler()); } static void GenAbsInteger(LocationSummary* locations, bool is64bit, MipsAssembler* assembler) { diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S index 4d4ebdcad8..61d1607112 100644 --- a/runtime/arch/arm/quick_entrypoints_arm.S +++ b/runtime/arch/arm/quick_entrypoints_arm.S @@ -1221,7 +1221,7 @@ END art_quick_alloc_object_resolved_rosalloc #endif ldrd r12, r3, [r9, #THREAD_LOCAL_POS_OFFSET] sub r12, r3, r12 // Compute the remaining buf size. - ldr r3, [r2, #MIRROR_CLASS_OBJECT_SIZE_ALLOC_FAST_PATH_OFFSET] // Load the object size (r3). + ldr r3, [r0, #MIRROR_CLASS_OBJECT_SIZE_ALLOC_FAST_PATH_OFFSET] // Load the object size (r3). cmp r3, r12 // Check if it fits. bhi \slowPathLabel // "Point of no slow path". Won't go to the slow path from here on. OK to clobber r0 and r1. diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 8586b783a2..035ceadeb7 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -6091,6 +6091,41 @@ ArtMethod* ClassLinker::AddMethodToConflictTable(ObjPtr<mirror::Class> klass, return new_conflict_method; } +bool ClassLinker::AllocateIfTableMethodArrays(Thread* self, + Handle<mirror::Class> klass, + Handle<mirror::IfTable> iftable) { + DCHECK(!klass->IsInterface()); + const bool has_superclass = klass->HasSuperClass(); + const bool extend_super_iftable = has_superclass; + const size_t ifcount = klass->GetIfTableCount(); + const size_t super_ifcount = has_superclass ? klass->GetSuperClass()->GetIfTableCount() : 0U; + for (size_t i = 0; i < ifcount; ++i) { + size_t num_methods = iftable->GetInterface(i)->NumDeclaredVirtualMethods(); + if (num_methods > 0) { + const bool is_super = i < super_ifcount; + // This is an interface implemented by a super-class. Therefore we can just copy the method + // array from the superclass. + const bool super_interface = is_super && extend_super_iftable; + ObjPtr<mirror::PointerArray> method_array; + if (super_interface) { + ObjPtr<mirror::IfTable> if_table = klass->GetSuperClass()->GetIfTable(); + DCHECK(if_table != nullptr); + DCHECK(if_table->GetMethodArray(i) != nullptr); + // If we are working on a super interface, try extending the existing method array. + method_array = down_cast<mirror::PointerArray*>(if_table->GetMethodArray(i)->Clone(self)); + } else { + method_array = AllocPointerArray(self, num_methods); + } + if (UNLIKELY(method_array == nullptr)) { + self->AssertPendingOOMException(); + return false; + } + iftable->SetMethodArray(i, method_array); + } + } + return true; +} + void ClassLinker::SetIMTRef(ArtMethod* unimplemented_method, ArtMethod* imt_conflict_method, ArtMethod* current_method, @@ -6630,6 +6665,490 @@ void ClassLinker::FillImtFromSuperClass(Handle<mirror::Class> klass, } } +class ClassLinker::LinkInterfaceMethodsHelper { + public: + LinkInterfaceMethodsHelper(ClassLinker* class_linker, + Handle<mirror::Class> klass, + Thread* self, + Runtime* runtime) + : class_linker_(class_linker), + klass_(klass), + method_alignment_(ArtMethod::Alignment(class_linker->GetImagePointerSize())), + method_size_(ArtMethod::Size(class_linker->GetImagePointerSize())), + self_(self), + stack_(runtime->GetLinearAlloc()->GetArenaPool()), + allocator_(&stack_), + default_conflict_methods_(allocator_.Adapter()), + overriding_default_conflict_methods_(allocator_.Adapter()), + miranda_methods_(allocator_.Adapter()), + default_methods_(allocator_.Adapter()), + overriding_default_methods_(allocator_.Adapter()), + move_table_(allocator_.Adapter()) { + } + + ArtMethod* FindMethod(ArtMethod* interface_method, + MethodNameAndSignatureComparator& interface_name_comparator, + ArtMethod* vtable_impl) + REQUIRES_SHARED(Locks::mutator_lock_); + + ArtMethod* GetOrCreateMirandaMethod(ArtMethod* interface_method, + MethodNameAndSignatureComparator& interface_name_comparator) + REQUIRES_SHARED(Locks::mutator_lock_); + + bool HasNewVirtuals() const { + return !(miranda_methods_.empty() && + default_methods_.empty() && + overriding_default_methods_.empty() && + overriding_default_conflict_methods_.empty() && + default_conflict_methods_.empty()); + } + + void ReallocMethods() REQUIRES_SHARED(Locks::mutator_lock_); + + ObjPtr<mirror::PointerArray> UpdateVtable( + const std::unordered_map<size_t, ClassLinker::MethodTranslation>& default_translations, + ObjPtr<mirror::PointerArray> old_vtable) REQUIRES_SHARED(Locks::mutator_lock_); + + void UpdateIfTable(Handle<mirror::IfTable> iftable) REQUIRES_SHARED(Locks::mutator_lock_); + + void UpdateIMT(ArtMethod** out_imt); + + void CheckNoStaleMethodsInDexCache() REQUIRES_SHARED(Locks::mutator_lock_) { + if (kIsDebugBuild) { + PointerSize pointer_size = class_linker_->GetImagePointerSize(); + // Check that there are no stale methods are in the dex cache array. + auto* resolved_methods = klass_->GetDexCache()->GetResolvedMethods(); + for (size_t i = 0, count = klass_->GetDexCache()->NumResolvedMethods(); i < count; ++i) { + auto* m = mirror::DexCache::GetElementPtrSize(resolved_methods, i, pointer_size); + CHECK(move_table_.find(m) == move_table_.end() || + // The original versions of copied methods will still be present so allow those too. + // Note that if the first check passes this might fail to GetDeclaringClass(). + std::find_if(m->GetDeclaringClass()->GetMethods(pointer_size).begin(), + m->GetDeclaringClass()->GetMethods(pointer_size).end(), + [m] (ArtMethod& meth) { + return &meth == m; + }) != m->GetDeclaringClass()->GetMethods(pointer_size).end()) + << "Obsolete method " << m->PrettyMethod() << " is in dex cache!"; + } + } + } + + void ClobberOldMethods(LengthPrefixedArray<ArtMethod>* old_methods, + LengthPrefixedArray<ArtMethod>* methods) { + if (kIsDebugBuild) { + CHECK(methods != nullptr); + // Put some random garbage in old methods to help find stale pointers. + if (methods != old_methods && old_methods != nullptr) { + // Need to make sure the GC is not running since it could be scanning the methods we are + // about to overwrite. + ScopedThreadStateChange tsc(self_, kSuspended); + gc::ScopedGCCriticalSection gcs(self_, + gc::kGcCauseClassLinker, + gc::kCollectorTypeClassLinker); + const size_t old_size = LengthPrefixedArray<ArtMethod>::ComputeSize(old_methods->size(), + method_size_, + method_alignment_); + memset(old_methods, 0xFEu, old_size); + } + } + } + + private: + size_t NumberOfNewVirtuals() const { + return miranda_methods_.size() + + default_methods_.size() + + overriding_default_conflict_methods_.size() + + overriding_default_methods_.size() + + default_conflict_methods_.size(); + } + + bool FillTables() REQUIRES_SHARED(Locks::mutator_lock_) { + return !klass_->IsInterface(); + } + + void LogNewVirtuals() const REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(!klass_->IsInterface() || (default_methods_.empty() && miranda_methods_.empty())) + << "Interfaces should only have default-conflict methods appended to them."; + VLOG(class_linker) << mirror::Class::PrettyClass(klass_.Get()) << ": miranda_methods=" + << miranda_methods_.size() + << " default_methods=" << default_methods_.size() + << " overriding_default_methods=" << overriding_default_methods_.size() + << " default_conflict_methods=" << default_conflict_methods_.size() + << " overriding_default_conflict_methods=" + << overriding_default_conflict_methods_.size(); + } + + ClassLinker* class_linker_; + Handle<mirror::Class> klass_; + size_t method_alignment_; + size_t method_size_; + Thread* const self_; + + // These are allocated on the heap to begin, we then transfer to linear alloc when we re-create + // the virtual methods array. + // Need to use low 4GB arenas for compiler or else the pointers wont fit in 32 bit method array + // during cross compilation. + // Use the linear alloc pool since this one is in the low 4gb for the compiler. + ArenaStack stack_; + ScopedArenaAllocator allocator_; + + ScopedArenaVector<ArtMethod*> default_conflict_methods_; + ScopedArenaVector<ArtMethod*> overriding_default_conflict_methods_; + ScopedArenaVector<ArtMethod*> miranda_methods_; + ScopedArenaVector<ArtMethod*> default_methods_; + ScopedArenaVector<ArtMethod*> overriding_default_methods_; + + ScopedArenaUnorderedMap<ArtMethod*, ArtMethod*> move_table_; +}; + +ArtMethod* ClassLinker::LinkInterfaceMethodsHelper::FindMethod( + ArtMethod* interface_method, + MethodNameAndSignatureComparator& interface_name_comparator, + ArtMethod* vtable_impl) { + ArtMethod* current_method = nullptr; + switch (class_linker_->FindDefaultMethodImplementation(self_, + interface_method, + klass_, + /*out*/¤t_method)) { + case DefaultMethodSearchResult::kDefaultConflict: { + // Default method conflict. + DCHECK(current_method == nullptr); + ArtMethod* default_conflict_method = nullptr; + if (vtable_impl != nullptr && vtable_impl->IsDefaultConflicting()) { + // We can reuse the method from the superclass, don't bother adding it to virtuals. + default_conflict_method = vtable_impl; + } else { + // See if we already have a conflict method for this method. + ArtMethod* preexisting_conflict = FindSameNameAndSignature( + interface_name_comparator, + default_conflict_methods_, + overriding_default_conflict_methods_); + if (LIKELY(preexisting_conflict != nullptr)) { + // We already have another conflict we can reuse. + default_conflict_method = preexisting_conflict; + } else { + // Note that we do this even if we are an interface since we need to create this and + // cannot reuse another classes. + // Create a new conflict method for this to use. + default_conflict_method = reinterpret_cast<ArtMethod*>(allocator_.Alloc(method_size_)); + new(default_conflict_method) ArtMethod(interface_method, + class_linker_->GetImagePointerSize()); + if (vtable_impl == nullptr) { + // Save the conflict method. We need to add it to the vtable. + default_conflict_methods_.push_back(default_conflict_method); + } else { + // Save the conflict method but it is already in the vtable. + overriding_default_conflict_methods_.push_back(default_conflict_method); + } + } + } + current_method = default_conflict_method; + break; + } // case kDefaultConflict + case DefaultMethodSearchResult::kDefaultFound: { + DCHECK(current_method != nullptr); + // Found a default method. + if (vtable_impl != nullptr && + current_method->GetDeclaringClass() == vtable_impl->GetDeclaringClass()) { + // We found a default method but it was the same one we already have from our + // superclass. Don't bother adding it to our vtable again. + current_method = vtable_impl; + } else if (LIKELY(FillTables())) { + // Interfaces don't need to copy default methods since they don't have vtables. + // Only record this default method if it is new to save space. + // TODO It might be worthwhile to copy default methods on interfaces anyway since it + // would make lookup for interface super much faster. (We would only need to scan + // the iftable to find if there is a NSME or AME.) + ArtMethod* old = FindSameNameAndSignature(interface_name_comparator, + default_methods_, + overriding_default_methods_); + if (old == nullptr) { + // We found a default method implementation and there were no conflicts. + if (vtable_impl == nullptr) { + // Save the default method. We need to add it to the vtable. + default_methods_.push_back(current_method); + } else { + // Save the default method but it is already in the vtable. + overriding_default_methods_.push_back(current_method); + } + } else { + CHECK(old == current_method) << "Multiple default implementations selected!"; + } + } + break; + } // case kDefaultFound + case DefaultMethodSearchResult::kAbstractFound: { + DCHECK(current_method == nullptr); + // Abstract method masks all defaults. + if (vtable_impl != nullptr && + vtable_impl->IsAbstract() && + !vtable_impl->IsDefaultConflicting()) { + // We need to make this an abstract method but the version in the vtable already is so + // don't do anything. + current_method = vtable_impl; + } + break; + } // case kAbstractFound + } + return current_method; +} + +ArtMethod* ClassLinker::LinkInterfaceMethodsHelper::GetOrCreateMirandaMethod( + ArtMethod* interface_method, + MethodNameAndSignatureComparator& interface_name_comparator) { + // Find out if there is already a miranda method we can use. + ArtMethod* miranda_method = FindSameNameAndSignature(interface_name_comparator, + miranda_methods_); + if (miranda_method == nullptr) { + DCHECK(interface_method->IsAbstract()) << interface_method->PrettyMethod(); + miranda_method = reinterpret_cast<ArtMethod*>(allocator_.Alloc(method_size_)); + CHECK(miranda_method != nullptr); + // Point the interface table at a phantom slot. + new(miranda_method) ArtMethod(interface_method, class_linker_->GetImagePointerSize()); + miranda_methods_.push_back(miranda_method); + } + return miranda_method; +} + +void ClassLinker::LinkInterfaceMethodsHelper::ReallocMethods() { + LogNewVirtuals(); + + const size_t old_method_count = klass_->NumMethods(); + const size_t new_method_count = old_method_count + NumberOfNewVirtuals(); + DCHECK_NE(old_method_count, new_method_count); + + // Attempt to realloc to save RAM if possible. + LengthPrefixedArray<ArtMethod>* old_methods = klass_->GetMethodsPtr(); + // The Realloced virtual methods aren't visible from the class roots, so there is no issue + // where GCs could attempt to mark stale pointers due to memcpy. And since we overwrite the + // realloced memory with out->CopyFrom, we are guaranteed to have objects in the to space since + // CopyFrom has internal read barriers. + // + // TODO We should maybe move some of this into mirror::Class or at least into another method. + const size_t old_size = LengthPrefixedArray<ArtMethod>::ComputeSize(old_method_count, + method_size_, + method_alignment_); + const size_t new_size = LengthPrefixedArray<ArtMethod>::ComputeSize(new_method_count, + method_size_, + method_alignment_); + const size_t old_methods_ptr_size = (old_methods != nullptr) ? old_size : 0; + auto* methods = reinterpret_cast<LengthPrefixedArray<ArtMethod>*>( + Runtime::Current()->GetLinearAlloc()->Realloc( + self_, old_methods, old_methods_ptr_size, new_size)); + CHECK(methods != nullptr); // Native allocation failure aborts. + + PointerSize pointer_size = class_linker_->GetImagePointerSize(); + if (methods != old_methods) { + // Maps from heap allocated miranda method to linear alloc miranda method. + StrideIterator<ArtMethod> out = methods->begin(method_size_, method_alignment_); + // Copy over the old methods. + for (auto& m : klass_->GetMethods(pointer_size)) { + move_table_.emplace(&m, &*out); + // The CopyFrom is only necessary to not miss read barriers since Realloc won't do read + // barriers when it copies. + out->CopyFrom(&m, pointer_size); + ++out; + } + } + StrideIterator<ArtMethod> out(methods->begin(method_size_, method_alignment_) + old_method_count); + // Copy over miranda methods before copying vtable since CopyOf may cause thread suspension and + // we want the roots of the miranda methods to get visited. + for (size_t i = 0; i < miranda_methods_.size(); ++i) { + ArtMethod* mir_method = miranda_methods_[i]; + ArtMethod& new_method = *out; + new_method.CopyFrom(mir_method, pointer_size); + new_method.SetAccessFlags(new_method.GetAccessFlags() | kAccMiranda | kAccCopied); + DCHECK_NE(new_method.GetAccessFlags() & kAccAbstract, 0u) + << "Miranda method should be abstract!"; + move_table_.emplace(mir_method, &new_method); + // Update the entry in the method array, as the array will be used for future lookups, + // where thread suspension is allowed. + // As such, the array should not contain locally allocated ArtMethod, otherwise the GC + // would not see them. + miranda_methods_[i] = &new_method; + ++out; + } + // We need to copy the default methods into our own method table since the runtime requires that + // every method on a class's vtable be in that respective class's virtual method table. + // NOTE This means that two classes might have the same implementation of a method from the same + // interface but will have different ArtMethod*s for them. This also means we cannot compare a + // default method found on a class with one found on the declaring interface directly and must + // look at the declaring class to determine if they are the same. + for (ScopedArenaVector<ArtMethod*>* methods_vec : {&default_methods_, + &overriding_default_methods_}) { + for (size_t i = 0; i < methods_vec->size(); ++i) { + ArtMethod* def_method = (*methods_vec)[i]; + ArtMethod& new_method = *out; + new_method.CopyFrom(def_method, pointer_size); + // Clear the kAccSkipAccessChecks flag if it is present. Since this class hasn't been + // verified yet it shouldn't have methods that are skipping access checks. + // TODO This is rather arbitrary. We should maybe support classes where only some of its + // methods are skip_access_checks. + constexpr uint32_t kSetFlags = kAccDefault | kAccCopied; + constexpr uint32_t kMaskFlags = ~kAccSkipAccessChecks; + new_method.SetAccessFlags((new_method.GetAccessFlags() | kSetFlags) & kMaskFlags); + move_table_.emplace(def_method, &new_method); + // Update the entry in the method array, as the array will be used for future lookups, + // where thread suspension is allowed. + // As such, the array should not contain locally allocated ArtMethod, otherwise the GC + // would not see them. + (*methods_vec)[i] = &new_method; + ++out; + } + } + for (ScopedArenaVector<ArtMethod*>* methods_vec : {&default_conflict_methods_, + &overriding_default_conflict_methods_}) { + for (size_t i = 0; i < methods_vec->size(); ++i) { + ArtMethod* conf_method = (*methods_vec)[i]; + ArtMethod& new_method = *out; + new_method.CopyFrom(conf_method, pointer_size); + // This is a type of default method (there are default method impls, just a conflict) so + // mark this as a default, non-abstract method, since thats what it is. Also clear the + // kAccSkipAccessChecks bit since this class hasn't been verified yet it shouldn't have + // methods that are skipping access checks. + constexpr uint32_t kSetFlags = kAccDefault | kAccDefaultConflict | kAccCopied; + constexpr uint32_t kMaskFlags = ~(kAccAbstract | kAccSkipAccessChecks); + new_method.SetAccessFlags((new_method.GetAccessFlags() | kSetFlags) & kMaskFlags); + DCHECK(new_method.IsDefaultConflicting()); + // The actual method might or might not be marked abstract since we just copied it from a + // (possibly default) interface method. We need to set it entry point to be the bridge so + // that the compiler will not invoke the implementation of whatever method we copied from. + EnsureThrowsInvocationError(class_linker_, &new_method); + move_table_.emplace(conf_method, &new_method); + // Update the entry in the method array, as the array will be used for future lookups, + // where thread suspension is allowed. + // As such, the array should not contain locally allocated ArtMethod, otherwise the GC + // would not see them. + (*methods_vec)[i] = &new_method; + ++out; + } + } + methods->SetSize(new_method_count); + class_linker_->UpdateClassMethods(klass_.Get(), methods); +} + +ObjPtr<mirror::PointerArray> ClassLinker::LinkInterfaceMethodsHelper::UpdateVtable( + const std::unordered_map<size_t, ClassLinker::MethodTranslation>& default_translations, + ObjPtr<mirror::PointerArray> old_vtable) { + // Update the vtable to the new method structures. We can skip this for interfaces since they + // do not have vtables. + const size_t old_vtable_count = old_vtable->GetLength(); + const size_t new_vtable_count = old_vtable_count + + miranda_methods_.size() + + default_methods_.size() + + default_conflict_methods_.size(); + + ObjPtr<mirror::PointerArray> vtable = + down_cast<mirror::PointerArray*>(old_vtable->CopyOf(self_, new_vtable_count)); + if (UNLIKELY(vtable == nullptr)) { + self_->AssertPendingOOMException(); + return nullptr; + } + + size_t vtable_pos = old_vtable_count; + PointerSize pointer_size = class_linker_->GetImagePointerSize(); + // Update all the newly copied method's indexes so they denote their placement in the vtable. + for (const ScopedArenaVector<ArtMethod*>& methods_vec : {default_methods_, + default_conflict_methods_, + miranda_methods_}) { + // These are the functions that are not already in the vtable! + for (ArtMethod* new_vtable_method : methods_vec) { + // Leave the declaring class alone the method's dex_code_item_offset_ and dex_method_index_ + // fields are references into the dex file the method was defined in. Since the ArtMethod + // does not store that information it uses declaring_class_->dex_cache_. + new_vtable_method->SetMethodIndex(0xFFFF & vtable_pos); + vtable->SetElementPtrSize(vtable_pos, new_vtable_method, pointer_size); + ++vtable_pos; + } + } + DCHECK_EQ(vtable_pos, new_vtable_count); + + // Update old vtable methods. We use the default_translations map to figure out what each + // vtable entry should be updated to, if they need to be at all. + for (size_t i = 0; i < old_vtable_count; ++i) { + ArtMethod* translated_method = vtable->GetElementPtrSize<ArtMethod*>(i, pointer_size); + // Try and find what we need to change this method to. + auto translation_it = default_translations.find(i); + if (translation_it != default_translations.end()) { + if (translation_it->second.IsInConflict()) { + // Find which conflict method we are to use for this method. + MethodNameAndSignatureComparator old_method_comparator( + translated_method->GetInterfaceMethodIfProxy(pointer_size)); + // We only need to look through overriding_default_conflict_methods since this is an + // overridden method we are fixing up here. + ArtMethod* new_conflict_method = FindSameNameAndSignature( + old_method_comparator, overriding_default_conflict_methods_); + CHECK(new_conflict_method != nullptr) << "Expected a conflict method!"; + translated_method = new_conflict_method; + } else if (translation_it->second.IsAbstract()) { + // Find which miranda method we are to use for this method. + MethodNameAndSignatureComparator old_method_comparator( + translated_method->GetInterfaceMethodIfProxy(pointer_size)); + ArtMethod* miranda_method = FindSameNameAndSignature(old_method_comparator, + miranda_methods_); + DCHECK(miranda_method != nullptr); + translated_method = miranda_method; + } else { + // Normal default method (changed from an older default or abstract interface method). + DCHECK(translation_it->second.IsTranslation()); + translated_method = translation_it->second.GetTranslation(); + auto it = move_table_.find(translated_method); + DCHECK(it != move_table_.end()); + translated_method = it->second; + } + } else { + auto it = move_table_.find(translated_method); + translated_method = (it != move_table_.end()) ? it->second : nullptr; + } + + if (translated_method != nullptr) { + // Make sure the new_methods index is set. + if (translated_method->GetMethodIndexDuringLinking() != i) { + if (kIsDebugBuild) { + auto* methods = klass_->GetMethodsPtr(); + CHECK_LE(reinterpret_cast<uintptr_t>(&*methods->begin(method_size_, method_alignment_)), + reinterpret_cast<uintptr_t>(translated_method)); + CHECK_LT(reinterpret_cast<uintptr_t>(translated_method), + reinterpret_cast<uintptr_t>(&*methods->end(method_size_, method_alignment_))); + } + translated_method->SetMethodIndex(0xFFFF & i); + } + vtable->SetElementPtrSize(i, translated_method, pointer_size); + } + } + klass_->SetVTable(vtable.Ptr()); + return vtable; +} + +void ClassLinker::LinkInterfaceMethodsHelper::UpdateIfTable(Handle<mirror::IfTable> iftable) { + PointerSize pointer_size = class_linker_->GetImagePointerSize(); + const size_t ifcount = klass_->GetIfTableCount(); + // Go fix up all the stale iftable pointers. + for (size_t i = 0; i < ifcount; ++i) { + for (size_t j = 0, count = iftable->GetMethodArrayCount(i); j < count; ++j) { + auto* method_array = iftable->GetMethodArray(i); + auto* m = method_array->GetElementPtrSize<ArtMethod*>(j, pointer_size); + DCHECK(m != nullptr) << klass_->PrettyClass(); + auto it = move_table_.find(m); + if (it != move_table_.end()) { + auto* new_m = it->second; + DCHECK(new_m != nullptr) << klass_->PrettyClass(); + method_array->SetElementPtrSize(j, new_m, pointer_size); + } + } + } +} + +void ClassLinker::LinkInterfaceMethodsHelper::UpdateIMT(ArtMethod** out_imt) { + // Fix up IMT next. + for (size_t i = 0; i < ImTable::kSize; ++i) { + auto it = move_table_.find(out_imt[i]); + if (it != move_table_.end()) { + out_imt[i] = it->second; + } + } +} + // TODO This method needs to be split up into several smaller methods. bool ClassLinker::LinkInterfaceMethods( Thread* self, @@ -6644,25 +7163,9 @@ bool ClassLinker::LinkInterfaceMethods( const bool has_superclass = klass->HasSuperClass(); const bool fill_tables = !is_interface; const size_t super_ifcount = has_superclass ? klass->GetSuperClass()->GetIfTableCount() : 0U; - const size_t method_alignment = ArtMethod::Alignment(image_pointer_size_); - const size_t method_size = ArtMethod::Size(image_pointer_size_); const size_t ifcount = klass->GetIfTableCount(); - MutableHandle<mirror::IfTable> iftable(hs.NewHandle(klass->GetIfTable())); - - // These are allocated on the heap to begin, we then transfer to linear alloc when we re-create - // the virtual methods array. - // Need to use low 4GB arenas for compiler or else the pointers wont fit in 32 bit method array - // during cross compilation. - // Use the linear alloc pool since this one is in the low 4gb for the compiler. - ArenaStack stack(runtime->GetLinearAlloc()->GetArenaPool()); - ScopedArenaAllocator allocator(&stack); - - ScopedArenaVector<ArtMethod*> default_conflict_methods(allocator.Adapter()); - ScopedArenaVector<ArtMethod*> overriding_default_conflict_methods(allocator.Adapter()); - ScopedArenaVector<ArtMethod*> miranda_methods(allocator.Adapter()); - ScopedArenaVector<ArtMethod*> default_methods(allocator.Adapter()); - ScopedArenaVector<ArtMethod*> overriding_default_methods(allocator.Adapter()); + Handle<mirror::IfTable> iftable(hs.NewHandle(klass->GetIfTable())); MutableHandle<mirror::PointerArray> vtable(hs.NewHandle(klass->GetVTableDuringLinking())); ArtMethod* const unimplemented_method = runtime->GetImtUnimplementedMethod(); @@ -6679,32 +7182,13 @@ bool ClassLinker::LinkInterfaceMethods( // Allocate method arrays before since we don't want miss visiting miranda method roots due to // thread suspension. if (fill_tables) { - for (size_t i = 0; i < ifcount; ++i) { - size_t num_methods = iftable->GetInterface(i)->NumDeclaredVirtualMethods(); - if (num_methods > 0) { - const bool is_super = i < super_ifcount; - // This is an interface implemented by a super-class. Therefore we can just copy the method - // array from the superclass. - const bool super_interface = is_super && extend_super_iftable; - ObjPtr<mirror::PointerArray> method_array; - if (super_interface) { - ObjPtr<mirror::IfTable> if_table = klass->GetSuperClass()->GetIfTable(); - DCHECK(if_table != nullptr); - DCHECK(if_table->GetMethodArray(i) != nullptr); - // If we are working on a super interface, try extending the existing method array. - method_array = down_cast<mirror::PointerArray*>(if_table->GetMethodArray(i)->Clone(self)); - } else { - method_array = AllocPointerArray(self, num_methods); - } - if (UNLIKELY(method_array == nullptr)) { - self->AssertPendingOOMException(); - return false; - } - iftable->SetMethodArray(i, method_array); - } + if (!AllocateIfTableMethodArrays(self, klass, iftable)) { + return false; } } + LinkInterfaceMethodsHelper helper(this, klass, self, runtime); + auto* old_cause = self->StartAssertNoThreadSuspension( "Copying ArtMethods for LinkInterfaceMethods"); // Going in reverse to ensure that we will hit abstract methods that override defaults before the @@ -6855,109 +7339,16 @@ bool ClassLinker::LinkInterfaceMethods( } } // If we haven't found it yet we should search through the interfaces for default methods. - ArtMethod* current_method = nullptr; - switch (FindDefaultMethodImplementation(self, - interface_method, - klass, - /*out*/¤t_method)) { - case DefaultMethodSearchResult::kDefaultConflict: { - // Default method conflict. - DCHECK(current_method == nullptr); - ArtMethod* default_conflict_method = nullptr; - if (vtable_impl != nullptr && vtable_impl->IsDefaultConflicting()) { - // We can reuse the method from the superclass, don't bother adding it to virtuals. - default_conflict_method = vtable_impl; - } else { - // See if we already have a conflict method for this method. - ArtMethod* preexisting_conflict = FindSameNameAndSignature( - interface_name_comparator, - default_conflict_methods, - overriding_default_conflict_methods); - if (LIKELY(preexisting_conflict != nullptr)) { - // We already have another conflict we can reuse. - default_conflict_method = preexisting_conflict; - } else { - // Note that we do this even if we are an interface since we need to create this and - // cannot reuse another classes. - // Create a new conflict method for this to use. - default_conflict_method = - reinterpret_cast<ArtMethod*>(allocator.Alloc(method_size)); - new(default_conflict_method) ArtMethod(interface_method, image_pointer_size_); - if (vtable_impl == nullptr) { - // Save the conflict method. We need to add it to the vtable. - default_conflict_methods.push_back(default_conflict_method); - } else { - // Save the conflict method but it is already in the vtable. - overriding_default_conflict_methods.push_back(default_conflict_method); - } - } - } - current_method = default_conflict_method; - break; - } // case kDefaultConflict - case DefaultMethodSearchResult::kDefaultFound: { - DCHECK(current_method != nullptr); - // Found a default method. - if (vtable_impl != nullptr && - current_method->GetDeclaringClass() == vtable_impl->GetDeclaringClass()) { - // We found a default method but it was the same one we already have from our - // superclass. Don't bother adding it to our vtable again. - current_method = vtable_impl; - } else if (LIKELY(fill_tables)) { - // Interfaces don't need to copy default methods since they don't have vtables. - // Only record this default method if it is new to save space. - // TODO It might be worthwhile to copy default methods on interfaces anyway since it - // would make lookup for interface super much faster. (We would only need to scan - // the iftable to find if there is a NSME or AME.) - ArtMethod* old = FindSameNameAndSignature(interface_name_comparator, - default_methods, - overriding_default_methods); - if (old == nullptr) { - // We found a default method implementation and there were no conflicts. - if (vtable_impl == nullptr) { - // Save the default method. We need to add it to the vtable. - default_methods.push_back(current_method); - } else { - // Save the default method but it is already in the vtable. - overriding_default_methods.push_back(current_method); - } - } else { - CHECK(old == current_method) << "Multiple default implementations selected!"; - } - } - break; - } // case kDefaultFound - case DefaultMethodSearchResult::kAbstractFound: { - DCHECK(current_method == nullptr); - // Abstract method masks all defaults. - if (vtable_impl != nullptr && - vtable_impl->IsAbstract() && - !vtable_impl->IsDefaultConflicting()) { - // We need to make this an abstract method but the version in the vtable already is so - // don't do anything. - current_method = vtable_impl; - } - break; - } // case kAbstractFound - } + ArtMethod* current_method = helper.FindMethod(interface_method, + interface_name_comparator, + vtable_impl); if (LIKELY(fill_tables)) { if (current_method == nullptr && !super_interface) { // We could not find an implementation for this method and since it is a brand new // interface we searched the entire vtable (and all default methods) for an // implementation but couldn't find one. We therefore need to make a miranda method. - // - // Find out if there is already a miranda method we can use. - ArtMethod* miranda_method = FindSameNameAndSignature(interface_name_comparator, - miranda_methods); - if (miranda_method == nullptr) { - DCHECK(interface_method->IsAbstract()) << interface_method->PrettyMethod(); - miranda_method = reinterpret_cast<ArtMethod*>(allocator.Alloc(method_size)); - CHECK(miranda_method != nullptr); - // Point the interface table at a phantom slot. - new(miranda_method) ArtMethod(interface_method, image_pointer_size_); - miranda_methods.push_back(miranda_method); - } - current_method = miranda_method; + current_method = helper.GetOrCreateMirandaMethod(interface_method, + interface_name_comparator); } if (current_method != nullptr) { @@ -6973,266 +7364,28 @@ bool ClassLinker::LinkInterfaceMethods( } // For each method in interface end. } // if (num_methods > 0) } // For each interface. - const bool has_new_virtuals = !(miranda_methods.empty() && - default_methods.empty() && - overriding_default_methods.empty() && - overriding_default_conflict_methods.empty() && - default_conflict_methods.empty()); // TODO don't extend virtuals of interface unless necessary (when is it?). - if (has_new_virtuals) { - DCHECK(!is_interface || (default_methods.empty() && miranda_methods.empty())) - << "Interfaces should only have default-conflict methods appended to them."; - VLOG(class_linker) << mirror::Class::PrettyClass(klass.Get()) << ": miranda_methods=" - << miranda_methods.size() - << " default_methods=" << default_methods.size() - << " overriding_default_methods=" << overriding_default_methods.size() - << " default_conflict_methods=" << default_conflict_methods.size() - << " overriding_default_conflict_methods=" - << overriding_default_conflict_methods.size(); - const size_t old_method_count = klass->NumMethods(); - const size_t new_method_count = old_method_count + - miranda_methods.size() + - default_methods.size() + - overriding_default_conflict_methods.size() + - overriding_default_methods.size() + - default_conflict_methods.size(); - // Attempt to realloc to save RAM if possible. - LengthPrefixedArray<ArtMethod>* old_methods = klass->GetMethodsPtr(); - // The Realloced virtual methods aren't visible from the class roots, so there is no issue - // where GCs could attempt to mark stale pointers due to memcpy. And since we overwrite the - // realloced memory with out->CopyFrom, we are guaranteed to have objects in the to space since - // CopyFrom has internal read barriers. - // - // TODO We should maybe move some of this into mirror::Class or at least into another method. - const size_t old_size = LengthPrefixedArray<ArtMethod>::ComputeSize(old_method_count, - method_size, - method_alignment); - const size_t new_size = LengthPrefixedArray<ArtMethod>::ComputeSize(new_method_count, - method_size, - method_alignment); - const size_t old_methods_ptr_size = (old_methods != nullptr) ? old_size : 0; - auto* methods = reinterpret_cast<LengthPrefixedArray<ArtMethod>*>( - runtime->GetLinearAlloc()->Realloc(self, old_methods, old_methods_ptr_size, new_size)); - if (UNLIKELY(methods == nullptr)) { - self->AssertPendingOOMException(); - self->EndAssertNoThreadSuspension(old_cause); - return false; - } - ScopedArenaUnorderedMap<ArtMethod*, ArtMethod*> move_table(allocator.Adapter()); - if (methods != old_methods) { - // Maps from heap allocated miranda method to linear alloc miranda method. - StrideIterator<ArtMethod> out = methods->begin(method_size, method_alignment); - // Copy over the old methods. - for (auto& m : klass->GetMethods(image_pointer_size_)) { - move_table.emplace(&m, &*out); - // The CopyFrom is only necessary to not miss read barriers since Realloc won't do read - // barriers when it copies. - out->CopyFrom(&m, image_pointer_size_); - ++out; - } - } - StrideIterator<ArtMethod> out(methods->begin(method_size, method_alignment) + old_method_count); - // Copy over miranda methods before copying vtable since CopyOf may cause thread suspension and - // we want the roots of the miranda methods to get visited. - for (ArtMethod* mir_method : miranda_methods) { - ArtMethod& new_method = *out; - new_method.CopyFrom(mir_method, image_pointer_size_); - new_method.SetAccessFlags(new_method.GetAccessFlags() | kAccMiranda | kAccCopied); - DCHECK_NE(new_method.GetAccessFlags() & kAccAbstract, 0u) - << "Miranda method should be abstract!"; - move_table.emplace(mir_method, &new_method); - ++out; - } - // We need to copy the default methods into our own method table since the runtime requires that - // every method on a class's vtable be in that respective class's virtual method table. - // NOTE This means that two classes might have the same implementation of a method from the same - // interface but will have different ArtMethod*s for them. This also means we cannot compare a - // default method found on a class with one found on the declaring interface directly and must - // look at the declaring class to determine if they are the same. - for (const ScopedArenaVector<ArtMethod*>& methods_vec : {default_methods, - overriding_default_methods}) { - for (ArtMethod* def_method : methods_vec) { - ArtMethod& new_method = *out; - new_method.CopyFrom(def_method, image_pointer_size_); - // Clear the kAccSkipAccessChecks flag if it is present. Since this class hasn't been - // verified yet it shouldn't have methods that are skipping access checks. - // TODO This is rather arbitrary. We should maybe support classes where only some of its - // methods are skip_access_checks. - constexpr uint32_t kSetFlags = kAccDefault | kAccCopied; - constexpr uint32_t kMaskFlags = ~kAccSkipAccessChecks; - new_method.SetAccessFlags((new_method.GetAccessFlags() | kSetFlags) & kMaskFlags); - move_table.emplace(def_method, &new_method); - ++out; - } - } - for (const ScopedArenaVector<ArtMethod*>& methods_vec : {default_conflict_methods, - overriding_default_conflict_methods}) { - for (ArtMethod* conf_method : methods_vec) { - ArtMethod& new_method = *out; - new_method.CopyFrom(conf_method, image_pointer_size_); - // This is a type of default method (there are default method impls, just a conflict) so - // mark this as a default, non-abstract method, since thats what it is. Also clear the - // kAccSkipAccessChecks bit since this class hasn't been verified yet it shouldn't have - // methods that are skipping access checks. - constexpr uint32_t kSetFlags = kAccDefault | kAccDefaultConflict | kAccCopied; - constexpr uint32_t kMaskFlags = ~(kAccAbstract | kAccSkipAccessChecks); - new_method.SetAccessFlags((new_method.GetAccessFlags() | kSetFlags) & kMaskFlags); - DCHECK(new_method.IsDefaultConflicting()); - // The actual method might or might not be marked abstract since we just copied it from a - // (possibly default) interface method. We need to set it entry point to be the bridge so - // that the compiler will not invoke the implementation of whatever method we copied from. - EnsureThrowsInvocationError(this, &new_method); - move_table.emplace(conf_method, &new_method); - ++out; - } - } - methods->SetSize(new_method_count); - UpdateClassMethods(klass.Get(), methods); + if (helper.HasNewVirtuals()) { + LengthPrefixedArray<ArtMethod>* old_methods = kIsDebugBuild ? klass->GetMethodsPtr() : nullptr; + helper.ReallocMethods(); // No return value to check. Native allocation failure aborts. + LengthPrefixedArray<ArtMethod>* methods = kIsDebugBuild ? klass->GetMethodsPtr() : nullptr; + // Done copying methods, they are all roots in the class now, so we can end the no thread // suspension assert. self->EndAssertNoThreadSuspension(old_cause); if (fill_tables) { - // Update the vtable to the new method structures. We can skip this for interfaces since they - // do not have vtables. - const size_t old_vtable_count = vtable->GetLength(); - const size_t new_vtable_count = old_vtable_count + - miranda_methods.size() + - default_methods.size() + - default_conflict_methods.size(); - - vtable.Assign(down_cast<mirror::PointerArray*>(vtable->CopyOf(self, new_vtable_count))); + vtable.Assign(helper.UpdateVtable(default_translations, vtable.Get())); if (UNLIKELY(vtable.Get() == nullptr)) { - self->AssertPendingOOMException(); + // The helper has already called self->AssertPendingOOMException(); return false; } - size_t vtable_pos = old_vtable_count; - // Update all the newly copied method's indexes so they denote their placement in the vtable. - for (const ScopedArenaVector<ArtMethod*>& methods_vec : {default_methods, - default_conflict_methods, - miranda_methods}) { - // These are the functions that are not already in the vtable! - for (ArtMethod* new_method : methods_vec) { - auto translated_method_it = move_table.find(new_method); - CHECK(translated_method_it != move_table.end()) - << "We must have a translation for methods added to the classes methods_ array! We " - << "could not find the ArtMethod added for " << ArtMethod::PrettyMethod(new_method); - ArtMethod* new_vtable_method = translated_method_it->second; - // Leave the declaring class alone the method's dex_code_item_offset_ and dex_method_index_ - // fields are references into the dex file the method was defined in. Since the ArtMethod - // does not store that information it uses declaring_class_->dex_cache_. - new_vtable_method->SetMethodIndex(0xFFFF & vtable_pos); - vtable->SetElementPtrSize(vtable_pos, new_vtable_method, image_pointer_size_); - ++vtable_pos; - } - } - CHECK_EQ(vtable_pos, new_vtable_count); - // Update old vtable methods. We use the default_translations map to figure out what each - // vtable entry should be updated to, if they need to be at all. - for (size_t i = 0; i < old_vtable_count; ++i) { - ArtMethod* translated_method = vtable->GetElementPtrSize<ArtMethod*>( - i, image_pointer_size_); - // Try and find what we need to change this method to. - auto translation_it = default_translations.find(i); - bool found_translation = false; - if (translation_it != default_translations.end()) { - if (translation_it->second.IsInConflict()) { - // Find which conflict method we are to use for this method. - MethodNameAndSignatureComparator old_method_comparator( - translated_method->GetInterfaceMethodIfProxy(image_pointer_size_)); - // We only need to look through overriding_default_conflict_methods since this is an - // overridden method we are fixing up here. - ArtMethod* new_conflict_method = FindSameNameAndSignature( - old_method_comparator, overriding_default_conflict_methods); - CHECK(new_conflict_method != nullptr) << "Expected a conflict method!"; - translated_method = new_conflict_method; - } else if (translation_it->second.IsAbstract()) { - // Find which miranda method we are to use for this method. - MethodNameAndSignatureComparator old_method_comparator( - translated_method->GetInterfaceMethodIfProxy(image_pointer_size_)); - ArtMethod* miranda_method = FindSameNameAndSignature(old_method_comparator, - miranda_methods); - DCHECK(miranda_method != nullptr); - translated_method = miranda_method; - } else { - // Normal default method (changed from an older default or abstract interface method). - DCHECK(translation_it->second.IsTranslation()); - translated_method = translation_it->second.GetTranslation(); - } - found_translation = true; - } - DCHECK(translated_method != nullptr); - auto it = move_table.find(translated_method); - if (it != move_table.end()) { - auto* new_method = it->second; - DCHECK(new_method != nullptr); - // Make sure the new_methods index is set. - if (new_method->GetMethodIndexDuringLinking() != i) { - DCHECK_LE(reinterpret_cast<uintptr_t>(&*methods->begin(method_size, method_alignment)), - reinterpret_cast<uintptr_t>(new_method)); - DCHECK_LT(reinterpret_cast<uintptr_t>(new_method), - reinterpret_cast<uintptr_t>(&*methods->end(method_size, method_alignment))); - new_method->SetMethodIndex(0xFFFF & i); - } - vtable->SetElementPtrSize(i, new_method, image_pointer_size_); - } else { - // If it was not going to be updated we wouldn't have put it into the default_translations - // map. - CHECK(!found_translation) << "We were asked to update this vtable entry. Must not fail."; - } - } - klass->SetVTable(vtable.Get()); - - // Go fix up all the stale iftable pointers. - for (size_t i = 0; i < ifcount; ++i) { - for (size_t j = 0, count = iftable->GetMethodArrayCount(i); j < count; ++j) { - auto* method_array = iftable->GetMethodArray(i); - auto* m = method_array->GetElementPtrSize<ArtMethod*>(j, image_pointer_size_); - DCHECK(m != nullptr) << klass->PrettyClass(); - auto it = move_table.find(m); - if (it != move_table.end()) { - auto* new_m = it->second; - DCHECK(new_m != nullptr) << klass->PrettyClass(); - method_array->SetElementPtrSize(j, new_m, image_pointer_size_); - } - } - } - - // Fix up IMT next - for (size_t i = 0; i < ImTable::kSize; ++i) { - auto it = move_table.find(out_imt[i]); - if (it != move_table.end()) { - out_imt[i] = it->second; - } - } + helper.UpdateIfTable(iftable); + helper.UpdateIMT(out_imt); } - // Check that there are no stale methods are in the dex cache array. - if (kIsDebugBuild) { - auto* resolved_methods = klass->GetDexCache()->GetResolvedMethods(); - for (size_t i = 0, count = klass->GetDexCache()->NumResolvedMethods(); i < count; ++i) { - auto* m = mirror::DexCache::GetElementPtrSize(resolved_methods, i, image_pointer_size_); - CHECK(move_table.find(m) == move_table.end() || - // The original versions of copied methods will still be present so allow those too. - // Note that if the first check passes this might fail to GetDeclaringClass(). - std::find_if(m->GetDeclaringClass()->GetMethods(image_pointer_size_).begin(), - m->GetDeclaringClass()->GetMethods(image_pointer_size_).end(), - [m] (ArtMethod& meth) { - return &meth == m; - }) != m->GetDeclaringClass()->GetMethods(image_pointer_size_).end()) - << "Obsolete methods " << m->PrettyMethod() << " is in dex cache!"; - } - } - // Put some random garbage in old methods to help find stale pointers. - if (methods != old_methods && old_methods != nullptr && kIsDebugBuild) { - // Need to make sure the GC is not running since it could be scanning the methods we are - // about to overwrite. - ScopedThreadStateChange tsc(self, kSuspended); - gc::ScopedGCCriticalSection gcs(self, - gc::kGcCauseClassLinker, - gc::kCollectorTypeClassLinker); - memset(old_methods, 0xFEu, old_size); - } + helper.CheckNoStaleMethodsInDexCache(); + helper.ClobberOldMethods(old_methods, methods); } else { self->EndAssertNoThreadSuspension(old_cause); } diff --git a/runtime/class_linker.h b/runtime/class_linker.h index 6ef882a66a..77322ede08 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -650,6 +650,8 @@ class ClassLinker { }; private: + class LinkInterfaceMethodsHelper; + struct ClassLoaderData { jweak weak_root; // Weak root to enable class unloading. ClassTable* class_table; @@ -1087,6 +1089,12 @@ class ClassLinker { REQUIRES(!Locks::dex_lock_) REQUIRES_SHARED(Locks::mutator_lock_); + // Allocate method arrays for interfaces. + bool AllocateIfTableMethodArrays(Thread* self, + Handle<mirror::Class> klass, + Handle<mirror::IfTable> iftable) + REQUIRES_SHARED(Locks::mutator_lock_); + // Sets imt_ref appropriately for LinkInterfaceMethods. // If there is no method in the imt location of imt_ref it will store the given method there. // Otherwise it will set the conflict method which will figure out which method to use during diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc index e1117e6ea3..7b86339663 100644 --- a/runtime/gc/collector/concurrent_copying.cc +++ b/runtime/gc/collector/concurrent_copying.cc @@ -2406,16 +2406,29 @@ void ConcurrentCopying::FinishPhase() { } } -bool ConcurrentCopying::IsMarkedHeapReference(mirror::HeapReference<mirror::Object>* field) { +bool ConcurrentCopying::IsNullOrMarkedHeapReference(mirror::HeapReference<mirror::Object>* field, + bool do_atomic_update) { mirror::Object* from_ref = field->AsMirrorPtr(); + if (from_ref == nullptr) { + return true; + } mirror::Object* to_ref = IsMarked(from_ref); if (to_ref == nullptr) { return false; } if (from_ref != to_ref) { - QuasiAtomic::ThreadFenceRelease(); - field->Assign(to_ref); - QuasiAtomic::ThreadFenceSequentiallyConsistent(); + if (do_atomic_update) { + do { + if (field->AsMirrorPtr() != from_ref) { + // Concurrently overwritten by a mutator. + break; + } + } while (!field->CasWeakRelaxed(from_ref, to_ref)); + } else { + QuasiAtomic::ThreadFenceRelease(); + field->Assign(to_ref); + QuasiAtomic::ThreadFenceSequentiallyConsistent(); + } } return true; } diff --git a/runtime/gc/collector/concurrent_copying.h b/runtime/gc/collector/concurrent_copying.h index 5b8a557375..844bb450cc 100644 --- a/runtime/gc/collector/concurrent_copying.h +++ b/runtime/gc/collector/concurrent_copying.h @@ -183,7 +183,8 @@ class ConcurrentCopying : public GarbageCollector { REQUIRES_SHARED(Locks::mutator_lock_); bool IsMarkedInUnevacFromSpace(mirror::Object* from_ref) REQUIRES_SHARED(Locks::mutator_lock_); - virtual bool IsMarkedHeapReference(mirror::HeapReference<mirror::Object>* field) OVERRIDE + virtual bool IsNullOrMarkedHeapReference(mirror::HeapReference<mirror::Object>* field, + bool do_atomic_update) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_); void SweepSystemWeaks(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::heap_bitmap_lock_); diff --git a/runtime/gc/collector/garbage_collector.h b/runtime/gc/collector/garbage_collector.h index 5b513991d1..0177e2a1ad 100644 --- a/runtime/gc/collector/garbage_collector.h +++ b/runtime/gc/collector/garbage_collector.h @@ -187,7 +187,10 @@ class GarbageCollector : public RootVisitor, public IsMarkedVisitor, public Mark // and will be used for reading system weaks while the GC is running. virtual mirror::Object* IsMarked(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) = 0; - virtual bool IsMarkedHeapReference(mirror::HeapReference<mirror::Object>* obj) + // Returns true if the given heap reference is null or is already marked. If it's already marked, + // update the reference (uses a CAS if do_atomic_update is true. Otherwise, returns false. + virtual bool IsNullOrMarkedHeapReference(mirror::HeapReference<mirror::Object>* obj, + bool do_atomic_update) REQUIRES_SHARED(Locks::mutator_lock_) = 0; // Used by reference processor. virtual void ProcessMarkStack() REQUIRES_SHARED(Locks::mutator_lock_) = 0; diff --git a/runtime/gc/collector/mark_compact.cc b/runtime/gc/collector/mark_compact.cc index ddcb6c0698..85e6783599 100644 --- a/runtime/gc/collector/mark_compact.cc +++ b/runtime/gc/collector/mark_compact.cc @@ -472,9 +472,15 @@ mirror::Object* MarkCompact::IsMarked(mirror::Object* object) { return mark_bitmap_->Test(object) ? object : nullptr; } -bool MarkCompact::IsMarkedHeapReference(mirror::HeapReference<mirror::Object>* ref_ptr) { +bool MarkCompact::IsNullOrMarkedHeapReference(mirror::HeapReference<mirror::Object>* ref_ptr, + // MarkCompact does the GC in a pause. No CAS needed. + bool do_atomic_update ATTRIBUTE_UNUSED) { // Side effect free since we call this before ever moving objects. - return IsMarked(ref_ptr->AsMirrorPtr()) != nullptr; + mirror::Object* obj = ref_ptr->AsMirrorPtr(); + if (obj == nullptr) { + return true; + } + return IsMarked(obj) != nullptr; } void MarkCompact::SweepSystemWeaks() { diff --git a/runtime/gc/collector/mark_compact.h b/runtime/gc/collector/mark_compact.h index 564f85b3f8..6d52d5d515 100644 --- a/runtime/gc/collector/mark_compact.h +++ b/runtime/gc/collector/mark_compact.h @@ -175,7 +175,8 @@ class MarkCompact : public GarbageCollector { virtual mirror::Object* IsMarked(mirror::Object* obj) OVERRIDE REQUIRES_SHARED(Locks::heap_bitmap_lock_) REQUIRES(Locks::mutator_lock_); - virtual bool IsMarkedHeapReference(mirror::HeapReference<mirror::Object>* obj) OVERRIDE + virtual bool IsNullOrMarkedHeapReference(mirror::HeapReference<mirror::Object>* obj, + bool do_atomic_update) OVERRIDE REQUIRES_SHARED(Locks::heap_bitmap_lock_) REQUIRES(Locks::mutator_lock_); void ForwardObject(mirror::Object* obj) REQUIRES(Locks::heap_bitmap_lock_, diff --git a/runtime/gc/collector/mark_sweep.cc b/runtime/gc/collector/mark_sweep.cc index 06ed0290a9..f00da73458 100644 --- a/runtime/gc/collector/mark_sweep.cc +++ b/runtime/gc/collector/mark_sweep.cc @@ -390,8 +390,13 @@ inline void MarkSweep::MarkObjectNonNullParallel(mirror::Object* obj) { } } -bool MarkSweep::IsMarkedHeapReference(mirror::HeapReference<mirror::Object>* ref) { - return IsMarked(ref->AsMirrorPtr()); +bool MarkSweep::IsNullOrMarkedHeapReference(mirror::HeapReference<mirror::Object>* ref, + bool do_atomic_update ATTRIBUTE_UNUSED) { + mirror::Object* obj = ref->AsMirrorPtr(); + if (obj == nullptr) { + return true; + } + return IsMarked(obj); } class MarkSweep::MarkObjectSlowPath { diff --git a/runtime/gc/collector/mark_sweep.h b/runtime/gc/collector/mark_sweep.h index 02cf462bd3..a6e2d61f6d 100644 --- a/runtime/gc/collector/mark_sweep.h +++ b/runtime/gc/collector/mark_sweep.h @@ -188,7 +188,8 @@ class MarkSweep : public GarbageCollector { void VerifyIsLive(const mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_, Locks::heap_bitmap_lock_); - virtual bool IsMarkedHeapReference(mirror::HeapReference<mirror::Object>* ref) OVERRIDE + virtual bool IsNullOrMarkedHeapReference(mirror::HeapReference<mirror::Object>* ref, + bool do_atomic_update) OVERRIDE REQUIRES(Locks::heap_bitmap_lock_) REQUIRES_SHARED(Locks::mutator_lock_); diff --git a/runtime/gc/collector/semi_space.cc b/runtime/gc/collector/semi_space.cc index f2aa5a7599..cb9e7e2c15 100644 --- a/runtime/gc/collector/semi_space.cc +++ b/runtime/gc/collector/semi_space.cc @@ -765,8 +765,13 @@ mirror::Object* SemiSpace::IsMarked(mirror::Object* obj) { return mark_bitmap_->Test(obj) ? obj : nullptr; } -bool SemiSpace::IsMarkedHeapReference(mirror::HeapReference<mirror::Object>* object) { +bool SemiSpace::IsNullOrMarkedHeapReference(mirror::HeapReference<mirror::Object>* object, + // SemiSpace does the GC in a pause. No CAS needed. + bool do_atomic_update ATTRIBUTE_UNUSED) { mirror::Object* obj = object->AsMirrorPtr(); + if (obj == nullptr) { + return true; + } mirror::Object* new_obj = IsMarked(obj); if (new_obj == nullptr) { return false; diff --git a/runtime/gc/collector/semi_space.h b/runtime/gc/collector/semi_space.h index 4cebcc3044..52b5e5fe30 100644 --- a/runtime/gc/collector/semi_space.h +++ b/runtime/gc/collector/semi_space.h @@ -166,7 +166,8 @@ class SemiSpace : public GarbageCollector { REQUIRES(Locks::mutator_lock_) REQUIRES_SHARED(Locks::heap_bitmap_lock_); - virtual bool IsMarkedHeapReference(mirror::HeapReference<mirror::Object>* object) OVERRIDE + virtual bool IsNullOrMarkedHeapReference(mirror::HeapReference<mirror::Object>* object, + bool do_atomic_update) OVERRIDE REQUIRES(Locks::mutator_lock_) REQUIRES_SHARED(Locks::heap_bitmap_lock_); diff --git a/runtime/gc/reference_processor.cc b/runtime/gc/reference_processor.cc index 081be968eb..c1548365c7 100644 --- a/runtime/gc/reference_processor.cc +++ b/runtime/gc/reference_processor.cc @@ -203,7 +203,9 @@ void ReferenceProcessor::DelayReferenceReferent(ObjPtr<mirror::Class> klass, DCHECK(klass != nullptr); DCHECK(klass->IsTypeOfReferenceClass()); mirror::HeapReference<mirror::Object>* referent = ref->GetReferentReferenceAddr(); - if (referent->AsMirrorPtr() != nullptr && !collector->IsMarkedHeapReference(referent)) { + // do_atomic_update needs to be true because this happens outside of the reference processing + // phase. + if (!collector->IsNullOrMarkedHeapReference(referent, /*do_atomic_update*/true)) { Thread* self = Thread::Current(); // TODO: Remove these locks, and use atomic stacks for storing references? // We need to check that the references haven't already been enqueued since we can end up diff --git a/runtime/gc/reference_queue.cc b/runtime/gc/reference_queue.cc index a0eb197bd5..734caea371 100644 --- a/runtime/gc/reference_queue.cc +++ b/runtime/gc/reference_queue.cc @@ -129,8 +129,9 @@ void ReferenceQueue::ClearWhiteReferences(ReferenceQueue* cleared_references, while (!IsEmpty()) { ObjPtr<mirror::Reference> ref = DequeuePendingReference(); mirror::HeapReference<mirror::Object>* referent_addr = ref->GetReferentReferenceAddr(); - if (referent_addr->AsMirrorPtr() != nullptr && - !collector->IsMarkedHeapReference(referent_addr)) { + // do_atomic_update is false because this happens during the reference processing phase where + // Reference.clear() would block. + if (!collector->IsNullOrMarkedHeapReference(referent_addr, /*do_atomic_update*/false)) { // Referent is white, clear it. if (Runtime::Current()->IsActiveTransaction()) { ref->ClearReferent<true>(); @@ -147,8 +148,9 @@ void ReferenceQueue::EnqueueFinalizerReferences(ReferenceQueue* cleared_referenc while (!IsEmpty()) { ObjPtr<mirror::FinalizerReference> ref = DequeuePendingReference()->AsFinalizerReference(); mirror::HeapReference<mirror::Object>* referent_addr = ref->GetReferentReferenceAddr(); - if (referent_addr->AsMirrorPtr() != nullptr && - !collector->IsMarkedHeapReference(referent_addr)) { + // do_atomic_update is false because this happens during the reference processing phase where + // Reference.clear() would block. + if (!collector->IsNullOrMarkedHeapReference(referent_addr, /*do_atomic_update*/false)) { ObjPtr<mirror::Object> forward_address = collector->MarkObject(referent_addr->AsMirrorPtr()); // Move the updated referent to the zombie field. if (Runtime::Current()->IsActiveTransaction()) { diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc index f80c43d80c..e0f28adc4f 100644 --- a/runtime/java_vm_ext.cc +++ b/runtime/java_vm_ext.cc @@ -566,7 +566,10 @@ jweak JavaVMExt::AddWeakGlobalRef(Thread* self, ObjPtr<mirror::Object> obj) { return nullptr; } MutexLock mu(self, *Locks::jni_weak_globals_lock_); - while (UNLIKELY(!MayAccessWeakGlobals(self))) { + // CMS needs this to block for concurrent reference processing because an object allocated during + // the GC won't be marked and concurrent reference processing would incorrectly clear the JNI weak + // ref. But CC (kUseReadBarrier == true) doesn't because of the to-space invariant. + while (!kUseReadBarrier && UNLIKELY(!MayAccessWeakGlobals(self))) { // Check and run the empty checkpoint before blocking so the empty checkpoint will work in the // presence of threads blocking for weak ref access. self->CheckEmptyCheckpoint(); diff --git a/runtime/mirror/object_reference-inl.h b/runtime/mirror/object_reference-inl.h index e70b93607e..22fb83cb5c 100644 --- a/runtime/mirror/object_reference-inl.h +++ b/runtime/mirror/object_reference-inl.h @@ -34,6 +34,15 @@ HeapReference<MirrorType> HeapReference<MirrorType>::FromObjPtr(ObjPtr<MirrorTyp return HeapReference<MirrorType>(ptr.Ptr()); } +template<class MirrorType> +bool HeapReference<MirrorType>::CasWeakRelaxed(MirrorType* expected_ptr, MirrorType* new_ptr) { + HeapReference<Object> expected_ref(HeapReference<Object>::FromMirrorPtr(expected_ptr)); + HeapReference<Object> new_ref(HeapReference<Object>::FromMirrorPtr(new_ptr)); + Atomic<uint32_t>* atomic_reference = reinterpret_cast<Atomic<uint32_t>*>(&this->reference_); + return atomic_reference->CompareExchangeWeakRelaxed(expected_ref.reference_, + new_ref.reference_); +} + } // namespace mirror } // namespace art diff --git a/runtime/mirror/object_reference.h b/runtime/mirror/object_reference.h index 71f34c66e2..a96a120d68 100644 --- a/runtime/mirror/object_reference.h +++ b/runtime/mirror/object_reference.h @@ -94,6 +94,9 @@ class MANAGED HeapReference : public ObjectReference<kPoisonHeapReferences, Mirr static HeapReference<MirrorType> FromObjPtr(ObjPtr<MirrorType> ptr) REQUIRES_SHARED(Locks::mutator_lock_); + bool CasWeakRelaxed(MirrorType* old_ptr, MirrorType* new_ptr) + REQUIRES_SHARED(Locks::mutator_lock_); + private: explicit HeapReference(MirrorType* mirror_ptr) REQUIRES_SHARED(Locks::mutator_lock_) : ObjectReference<kPoisonHeapReferences, MirrorType>(mirror_ptr) {} diff --git a/runtime/monitor.cc b/runtime/monitor.cc index 893abd5462..9c0927584e 100644 --- a/runtime/monitor.cc +++ b/runtime/monitor.cc @@ -1361,8 +1361,10 @@ void MonitorList::BroadcastForNewMonitors() { void MonitorList::Add(Monitor* m) { Thread* self = Thread::Current(); MutexLock mu(self, monitor_list_lock_); - while (UNLIKELY((!kUseReadBarrier && !allow_new_monitors_) || - (kUseReadBarrier && !self->GetWeakRefAccessEnabled()))) { + // CMS needs this to block for concurrent reference processing because an object allocated during + // the GC won't be marked and concurrent reference processing would incorrectly clear the JNI weak + // ref. But CC (kUseReadBarrier == true) doesn't because of the to-space invariant. + while (!kUseReadBarrier && UNLIKELY(!allow_new_monitors_)) { // Check and run the empty checkpoint before blocking so the empty checkpoint will work in the // presence of threads blocking for weak ref access. self->CheckEmptyCheckpoint(); diff --git a/runtime/native/dalvik_system_VMStack.cc b/runtime/native/dalvik_system_VMStack.cc index 36825cb870..268d71ac65 100644 --- a/runtime/native/dalvik_system_VMStack.cc +++ b/runtime/native/dalvik_system_VMStack.cc @@ -17,6 +17,7 @@ #include "dalvik_system_VMStack.h" #include "art_method-inl.h" +#include "gc/task_processor.h" #include "jni_internal.h" #include "nth_caller_visitor.h" #include "mirror/class-inl.h" @@ -31,9 +32,18 @@ namespace art { static jobject GetThreadStack(const ScopedFastNativeObjectAccess& soa, jobject peer) REQUIRES_SHARED(Locks::mutator_lock_) { jobject trace = nullptr; - if (soa.Decode<mirror::Object>(peer) == soa.Self()->GetPeer()) { + ObjPtr<mirror::Object> decoded_peer = soa.Decode<mirror::Object>(peer); + if (decoded_peer == soa.Self()->GetPeer()) { trace = soa.Self()->CreateInternalStackTrace<false>(soa); } else { + // Never allow suspending the heap task thread since it may deadlock if allocations are + // required for the stack trace. + Thread* heap_task_thread = + Runtime::Current()->GetHeap()->GetTaskProcessor()->GetRunningThread(); + // heap_task_thread could be null if the daemons aren't yet started. + if (heap_task_thread != nullptr && decoded_peer == heap_task_thread->GetPeer()) { + return nullptr; + } // Suspend thread to build stack trace. ScopedThreadSuspension sts(soa.Self(), kNative); ThreadList* thread_list = Runtime::Current()->GetThreadList(); diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp index be06dd7b4c..4bd21b4c2f 100644 --- a/runtime/openjdkjvmti/Android.bp +++ b/runtime/openjdkjvmti/Android.bp @@ -24,10 +24,12 @@ cc_defaults { "ti_field.cc", "ti_heap.cc", "ti_method.cc", + "ti_monitor.cc", "ti_object.cc", "ti_properties.cc", "ti_stack.cc", "ti_redefine.cc", + "ti_thread.cc", "transform.cc"], include_dirs: ["art/runtime"], shared_libs: [ diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 936049fe3d..d6592600d4 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -50,10 +50,12 @@ #include "ti_field.h" #include "ti_heap.h" #include "ti_method.h" +#include "ti_monitor.h" #include "ti_object.h" #include "ti_properties.h" #include "ti_redefine.h" #include "ti_stack.h" +#include "ti_thread.h" #include "transform.h" // TODO Remove this at some point by annotating all the methods. It was put in to make the skeleton @@ -116,11 +118,11 @@ class JvmtiFunctions { } static jvmtiError GetThreadState(jvmtiEnv* env, jthread thread, jint* thread_state_ptr) { - return ERR(NOT_IMPLEMENTED); + return ThreadUtil::GetThreadState(env, thread, thread_state_ptr); } static jvmtiError GetCurrentThread(jvmtiEnv* env, jthread* thread_ptr) { - return ERR(NOT_IMPLEMENTED); + return ThreadUtil::GetCurrentThread(env, thread_ptr); } static jvmtiError GetAllThreads(jvmtiEnv* env, jint* threads_count_ptr, jthread** threads_ptr) { @@ -158,7 +160,7 @@ class JvmtiFunctions { } static jvmtiError GetThreadInfo(jvmtiEnv* env, jthread thread, jvmtiThreadInfo* info_ptr) { - return ERR(NOT_IMPLEMENTED); + return ThreadUtil::GetThreadInfo(env, thread, info_ptr); } static jvmtiError GetOwnedMonitorInfo(jvmtiEnv* env, @@ -236,7 +238,7 @@ class JvmtiFunctions { jint max_frame_count, jvmtiStackInfo** stack_info_ptr, jint* thread_count_ptr) { - return ERR(NOT_IMPLEMENTED); + return StackUtil::GetAllStackTraces(env, max_frame_count, stack_info_ptr, thread_count_ptr); } static jvmtiError GetThreadListStackTraces(jvmtiEnv* env, @@ -244,7 +246,11 @@ class JvmtiFunctions { const jthread* thread_list, jint max_frame_count, jvmtiStackInfo** stack_info_ptr) { - return ERR(NOT_IMPLEMENTED); + return StackUtil::GetThreadListStackTraces(env, + thread_count, + thread_list, + max_frame_count, + stack_info_ptr); } static jvmtiError GetFrameCount(jvmtiEnv* env, jthread thread, jint* count_ptr) { @@ -748,31 +754,31 @@ class JvmtiFunctions { } static jvmtiError CreateRawMonitor(jvmtiEnv* env, const char* name, jrawMonitorID* monitor_ptr) { - return ERR(NOT_IMPLEMENTED); + return MonitorUtil::CreateRawMonitor(env, name, monitor_ptr); } static jvmtiError DestroyRawMonitor(jvmtiEnv* env, jrawMonitorID monitor) { - return ERR(NOT_IMPLEMENTED); + return MonitorUtil::DestroyRawMonitor(env, monitor); } static jvmtiError RawMonitorEnter(jvmtiEnv* env, jrawMonitorID monitor) { - return ERR(NOT_IMPLEMENTED); + return MonitorUtil::RawMonitorEnter(env, monitor); } static jvmtiError RawMonitorExit(jvmtiEnv* env, jrawMonitorID monitor) { - return ERR(NOT_IMPLEMENTED); + return MonitorUtil::RawMonitorExit(env, monitor); } static jvmtiError RawMonitorWait(jvmtiEnv* env, jrawMonitorID monitor, jlong millis) { - return ERR(NOT_IMPLEMENTED); + return MonitorUtil::RawMonitorWait(env, monitor, millis); } static jvmtiError RawMonitorNotify(jvmtiEnv* env, jrawMonitorID monitor) { - return ERR(NOT_IMPLEMENTED); + return MonitorUtil::RawMonitorNotify(env, monitor); } static jvmtiError RawMonitorNotifyAll(jvmtiEnv* env, jrawMonitorID monitor) { - return ERR(NOT_IMPLEMENTED); + return MonitorUtil::RawMonitorNotifyAll(env, monitor); } static jvmtiError SetJNIFunctionTable(jvmtiEnv* env, const jniNativeInterface* function_table) { diff --git a/runtime/openjdkjvmti/ti_monitor.cc b/runtime/openjdkjvmti/ti_monitor.cc new file mode 100644 index 0000000000..b82768397b --- /dev/null +++ b/runtime/openjdkjvmti/ti_monitor.cc @@ -0,0 +1,302 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "ti_monitor.h" + +#include <atomic> +#include <chrono> +#include <condition_variable> +#include <mutex> + +#include "art_jvmti.h" +#include "runtime.h" +#include "scoped_thread_state_change-inl.h" +#include "thread-inl.h" + +namespace openjdkjvmti { + +// We cannot use ART monitors, as they require the mutator lock for contention locking. We +// also cannot use pthread mutexes and condition variables (or C++11 abstractions) directly, +// as the do not have the right semantics for recursive mutexes and waiting (wait only unlocks +// the mutex once). +// So go ahead and use a wrapper that does the counting explicitly. + +class JvmtiMonitor { + public: + JvmtiMonitor() : owner_(nullptr), count_(0) { + } + + static bool Destroy(art::Thread* self, JvmtiMonitor* monitor) { + // Check whether this thread holds the monitor, or nobody does. + art::Thread* owner_thread = monitor->owner_.load(std::memory_order_relaxed); + if (owner_thread != nullptr && self != owner_thread) { + return false; + } + + if (monitor->count_ > 0) { + monitor->count_ = 0; + monitor->owner_.store(nullptr, std::memory_order_relaxed); + monitor->mutex_.unlock(); + } + + delete monitor; + return true; + } + + void MonitorEnter(art::Thread* self) { + // Check for recursive enter. + if (IsOwner(self)) { + count_++; + return; + } + + mutex_.lock(); + + DCHECK(owner_.load(std::memory_order_relaxed) == nullptr); + owner_.store(self, std::memory_order_relaxed); + DCHECK_EQ(0u, count_); + count_ = 1; + } + + bool MonitorExit(art::Thread* self) { + if (!IsOwner(self)) { + return false; + } + + --count_; + if (count_ == 0u) { + owner_.store(nullptr, std::memory_order_relaxed); + mutex_.unlock(); + } + + return true; + } + + bool Wait(art::Thread* self) { + auto wait_without_timeout = [&](std::unique_lock<std::mutex>& lk) { + cond_.wait(lk); + }; + return Wait(self, wait_without_timeout); + } + + bool Wait(art::Thread* self, uint64_t timeout_in_ms) { + auto wait_with_timeout = [&](std::unique_lock<std::mutex>& lk) { + cond_.wait_for(lk, std::chrono::milliseconds(timeout_in_ms)); + }; + return Wait(self, wait_with_timeout); + } + + bool Notify(art::Thread* self) { + return Notify(self, [&]() { cond_.notify_one(); }); + } + + bool NotifyAll(art::Thread* self) { + return Notify(self, [&]() { cond_.notify_all(); }); + } + + private: + bool IsOwner(art::Thread* self) { + // There's a subtle correctness argument here for a relaxed load outside the critical section. + // A thread is guaranteed to see either its own latest store or another thread's store. If a + // thread sees another thread's store than it cannot be holding the lock. + art::Thread* owner_thread = owner_.load(std::memory_order_relaxed); + return self == owner_thread; + } + + template <typename T> + bool Wait(art::Thread* self, T how_to_wait) { + if (!IsOwner(self)) { + return false; + } + + size_t old_count = count_; + + count_ = 0; + owner_.store(nullptr, std::memory_order_relaxed); + + { + std::unique_lock<std::mutex> lk(mutex_, std::adopt_lock); + how_to_wait(lk); + lk.release(); // Do not unlock the mutex. + } + + DCHECK(owner_.load(std::memory_order_relaxed) == nullptr); + owner_.store(self, std::memory_order_relaxed); + DCHECK_EQ(0u, count_); + count_ = old_count; + + return true; + } + + template <typename T> + bool Notify(art::Thread* self, T how_to_notify) { + if (!IsOwner(self)) { + return false; + } + + how_to_notify(); + + return true; + } + + std::mutex mutex_; + std::condition_variable cond_; + std::atomic<art::Thread*> owner_; + size_t count_; +}; + +static jrawMonitorID EncodeMonitor(JvmtiMonitor* monitor) { + return reinterpret_cast<jrawMonitorID>(monitor); +} + +static JvmtiMonitor* DecodeMonitor(jrawMonitorID id) { + return reinterpret_cast<JvmtiMonitor*>(id); +} + +jvmtiError MonitorUtil::CreateRawMonitor(jvmtiEnv* env ATTRIBUTE_UNUSED, + const char* name, + jrawMonitorID* monitor_ptr) { + if (name == nullptr || monitor_ptr == nullptr) { + return ERR(NULL_POINTER); + } + + JvmtiMonitor* monitor = new JvmtiMonitor(); + *monitor_ptr = EncodeMonitor(monitor); + + return ERR(NONE); +} + +jvmtiError MonitorUtil::DestroyRawMonitor(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) { + if (id == nullptr) { + return ERR(INVALID_MONITOR); + } + + JvmtiMonitor* monitor = DecodeMonitor(id); + art::Thread* self = art::Thread::Current(); + + if (!JvmtiMonitor::Destroy(self, monitor)) { + return ERR(NOT_MONITOR_OWNER); + } + + return ERR(NONE); +} + +jvmtiError MonitorUtil::RawMonitorEnter(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) { + if (id == nullptr) { + return ERR(INVALID_MONITOR); + } + + JvmtiMonitor* monitor = DecodeMonitor(id); + art::Thread* self = art::Thread::Current(); + + monitor->MonitorEnter(self); + + return ERR(NONE); +} + +jvmtiError MonitorUtil::RawMonitorExit(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) { + if (id == nullptr) { + return ERR(INVALID_MONITOR); + } + + JvmtiMonitor* monitor = DecodeMonitor(id); + art::Thread* self = art::Thread::Current(); + + if (!monitor->MonitorExit(self)) { + return ERR(NOT_MONITOR_OWNER); + } + + return ERR(NONE); +} + +jvmtiError MonitorUtil::RawMonitorWait(jvmtiEnv* env ATTRIBUTE_UNUSED, + jrawMonitorID id, + jlong millis) { + if (id == nullptr) { + return ERR(INVALID_MONITOR); + } + + JvmtiMonitor* monitor = DecodeMonitor(id); + art::Thread* self = art::Thread::Current(); + + // This is not in the spec, but it's the only thing that makes sense (and agrees with + // Object.wait). + if (millis < 0) { + return ERR(ILLEGAL_ARGUMENT); + } + + bool result = (millis > 0) + ? monitor->Wait(self, static_cast<uint64_t>(millis)) + : monitor->Wait(self); + + if (!result) { + return ERR(NOT_MONITOR_OWNER); + } + + // TODO: Make sure that is really what we should be checking here. + if (self->IsInterrupted()) { + return ERR(INTERRUPT); + } + + return ERR(NONE); +} + +jvmtiError MonitorUtil::RawMonitorNotify(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) { + if (id == nullptr) { + return ERR(INVALID_MONITOR); + } + + JvmtiMonitor* monitor = DecodeMonitor(id); + art::Thread* self = art::Thread::Current(); + + if (!monitor->Notify(self)) { + return ERR(NOT_MONITOR_OWNER); + } + + return ERR(NONE); +} + +jvmtiError MonitorUtil::RawMonitorNotifyAll(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) { + if (id == nullptr) { + return ERR(INVALID_MONITOR); + } + + JvmtiMonitor* monitor = DecodeMonitor(id); + art::Thread* self = art::Thread::Current(); + + if (!monitor->NotifyAll(self)) { + return ERR(NOT_MONITOR_OWNER); + } + + return ERR(NONE); +} + +} // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_monitor.h b/runtime/openjdkjvmti/ti_monitor.h new file mode 100644 index 0000000000..96ccb0d1c7 --- /dev/null +++ b/runtime/openjdkjvmti/ti_monitor.h @@ -0,0 +1,59 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_MONITOR_H_ +#define ART_RUNTIME_OPENJDKJVMTI_TI_MONITOR_H_ + +#include "jni.h" +#include "jvmti.h" + +namespace openjdkjvmti { + +class MonitorUtil { + public: + static jvmtiError CreateRawMonitor(jvmtiEnv* env, const char* name, jrawMonitorID* monitor_ptr); + + static jvmtiError DestroyRawMonitor(jvmtiEnv* env, jrawMonitorID monitor); + + static jvmtiError RawMonitorEnter(jvmtiEnv* env, jrawMonitorID monitor); + + static jvmtiError RawMonitorExit(jvmtiEnv* env, jrawMonitorID monitor); + + static jvmtiError RawMonitorWait(jvmtiEnv* env, jrawMonitorID monitor, jlong millis); + + static jvmtiError RawMonitorNotify(jvmtiEnv* env, jrawMonitorID monitor); + + static jvmtiError RawMonitorNotifyAll(jvmtiEnv* env, jrawMonitorID monitor); +}; + +} // namespace openjdkjvmti + +#endif // ART_RUNTIME_OPENJDKJVMTI_TI_MONITOR_H_ diff --git a/runtime/openjdkjvmti/ti_stack.cc b/runtime/openjdkjvmti/ti_stack.cc index 579fb50ecc..8bd8d09b5e 100644 --- a/runtime/openjdkjvmti/ti_stack.cc +++ b/runtime/openjdkjvmti/ti_stack.cc @@ -31,29 +31,37 @@ #include "ti_stack.h" +#include <algorithm> +#include <list> +#include <unordered_map> +#include <vector> + #include "art_jvmti.h" #include "art_method-inl.h" +#include "base/bit_utils.h" #include "base/enums.h" +#include "base/mutex.h" #include "dex_file.h" #include "dex_file_annotations.h" +#include "handle_scope-inl.h" #include "jni_env_ext.h" #include "jni_internal.h" #include "mirror/class.h" #include "mirror/dex_cache.h" #include "scoped_thread_state_change-inl.h" +#include "ScopedLocalRef.h" #include "stack.h" -#include "thread.h" +#include "thread-inl.h" +#include "thread_list.h" #include "thread_pool.h" namespace openjdkjvmti { struct GetStackTraceVisitor : public art::StackVisitor { GetStackTraceVisitor(art::Thread* thread_in, - art::ScopedObjectAccessAlreadyRunnable& soa_, size_t start_, size_t stop_) : StackVisitor(thread_in, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), - soa(soa_), start(start_), stop(stop_) {} @@ -85,7 +93,6 @@ struct GetStackTraceVisitor : public art::StackVisitor { return true; } - art::ScopedObjectAccessAlreadyRunnable& soa; std::vector<jvmtiFrameInfo> frames; size_t start; size_t stop; @@ -99,10 +106,8 @@ struct GetStackTraceClosure : public art::Closure { start_result(0), stop_result(0) {} - void Run(art::Thread* self) OVERRIDE { - art::ScopedObjectAccess soa(art::Thread::Current()); - - GetStackTraceVisitor visitor(self, soa, start_input, stop_input); + void Run(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { + GetStackTraceVisitor visitor(self, start_input, stop_input); visitor.WalkStack(false); frames.swap(visitor.frames); @@ -118,6 +123,44 @@ struct GetStackTraceClosure : public art::Closure { size_t stop_result; }; +static jvmtiError TranslateFrameVector(const std::vector<jvmtiFrameInfo>& frames, + jint start_depth, + size_t start_result, + jint max_frame_count, + jvmtiFrameInfo* frame_buffer, + jint* count_ptr) { + size_t collected_frames = frames.size(); + + // Assume we're here having collected something. + DCHECK_GT(max_frame_count, 0); + + // Frames from the top. + if (start_depth >= 0) { + if (start_result != 0) { + // Not enough frames. + return ERR(ILLEGAL_ARGUMENT); + } + DCHECK_LE(collected_frames, static_cast<size_t>(max_frame_count)); + if (frames.size() > 0) { + memcpy(frame_buffer, frames.data(), collected_frames * sizeof(jvmtiFrameInfo)); + } + *count_ptr = static_cast<jint>(frames.size()); + return ERR(NONE); + } + + // Frames from the bottom. + if (collected_frames < static_cast<size_t>(-start_depth)) { + return ERR(ILLEGAL_ARGUMENT); + } + + size_t count = std::min(static_cast<size_t>(-start_depth), static_cast<size_t>(max_frame_count)); + memcpy(frame_buffer, + &frames.data()[collected_frames + start_depth], + count * sizeof(jvmtiFrameInfo)); + *count_ptr = static_cast<jint>(count); + return ERR(NONE); +} + jvmtiError StackUtil::GetStackTrace(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED, jthread java_thread, jint start_depth, @@ -157,35 +200,366 @@ jvmtiError StackUtil::GetStackTrace(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED, } GetStackTraceClosure closure(start_depth >= 0 ? static_cast<size_t>(start_depth) : 0, - start_depth >= 0 ?static_cast<size_t>(max_frame_count) : 0); + start_depth >= 0 ? static_cast<size_t>(max_frame_count) : 0); thread->RequestSynchronousCheckpoint(&closure); - size_t collected_frames = closure.frames.size(); + return TranslateFrameVector(closure.frames, + start_depth, + closure.start_result, + max_frame_count, + frame_buffer, + count_ptr); +} - // Frames from the top. - if (start_depth >= 0) { - if (closure.start_result != 0) { - // Not enough frames. - return ERR(ILLEGAL_ARGUMENT); +struct GetAllStackTraceClosure : public art::Closure { + public: + explicit GetAllStackTraceClosure(size_t stop) + : start_input(0), + stop_input(stop), + frames_lock("GetAllStackTraceGuard", art::LockLevel::kAbortLock), + start_result(0), + stop_result(0) {} + + void Run(art::Thread* self) + OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) REQUIRES(!frames_lock) { + // self should be live here (so it could be suspended). No need to filter. + + art::Thread* current = art::Thread::Current(); + std::vector<jvmtiFrameInfo> self_frames; + + GetStackTraceVisitor visitor(self, start_input, stop_input); + visitor.WalkStack(false); + + self_frames.swap(visitor.frames); + + art::MutexLock mu(current, frames_lock); + frames.emplace(self, self_frames); + } + + const size_t start_input; + const size_t stop_input; + + art::Mutex frames_lock; + std::unordered_map<art::Thread*, std::vector<jvmtiFrameInfo>> frames GUARDED_BY(frames_lock); + size_t start_result; + size_t stop_result; +}; + + + +jvmtiError StackUtil::GetAllStackTraces(jvmtiEnv* env, + jint max_frame_count, + jvmtiStackInfo** stack_info_ptr, + jint* thread_count_ptr) { + if (max_frame_count < 0) { + return ERR(ILLEGAL_ARGUMENT); + } + if (stack_info_ptr == nullptr || thread_count_ptr == nullptr) { + return ERR(NULL_POINTER); + } + + + art::Thread* current = art::Thread::Current(); + art::ScopedObjectAccess soa(current); // Now we know we have the shared lock. + art::ScopedThreadSuspension sts(current, art::kWaitingForDebuggerSuspension); + art::ScopedSuspendAll ssa("GetAllStackTraces"); + + std::vector<art::Thread*> threads; + std::vector<std::vector<jvmtiFrameInfo>> frames; + { + std::list<art::Thread*> thread_list; + { + art::MutexLock mu(current, *art::Locks::thread_list_lock_); + thread_list = art::Runtime::Current()->GetThreadList()->GetList(); + } + + for (art::Thread* thread : thread_list) { + // Skip threads that are still starting. + if (thread->IsStillStarting()) { + continue; + } + + GetStackTraceClosure closure(0u, static_cast<size_t>(max_frame_count)); + thread->RequestSynchronousCheckpoint(&closure); + + threads.push_back(thread); + frames.emplace_back(); + frames.back().swap(closure.frames); + } + } + + // Convert the data into our output format. Note: we need to keep the threads suspended, + // as we need to access them for their peers. + + // Note: we use an array of jvmtiStackInfo for convenience. The spec says we need to + // allocate one big chunk for this and the actual frames, which means we need + // to either be conservative or rearrange things later (the latter is implemented). + std::unique_ptr<jvmtiStackInfo[]> stack_info_array(new jvmtiStackInfo[frames.size()]); + std::vector<std::unique_ptr<jvmtiFrameInfo[]>> frame_infos; + frame_infos.reserve(frames.size()); + + // Now run through and add data for each thread. + size_t sum_frames = 0; + for (size_t index = 0; index < frames.size(); ++index) { + jvmtiStackInfo& stack_info = stack_info_array.get()[index]; + memset(&stack_info, 0, sizeof(jvmtiStackInfo)); + + art::Thread* self = threads[index]; + const std::vector<jvmtiFrameInfo>& thread_frames = frames[index]; + + // For the time being, set the thread to null. We don't have good ScopedLocalRef + // infrastructure. + DCHECK(self->GetPeer() != nullptr); + stack_info.thread = nullptr; + stack_info.state = JVMTI_THREAD_STATE_SUSPENDED; + + size_t collected_frames = thread_frames.size(); + if (max_frame_count == 0 || collected_frames == 0) { + stack_info.frame_count = 0; + stack_info.frame_buffer = nullptr; + continue; } DCHECK_LE(collected_frames, static_cast<size_t>(max_frame_count)); - if (closure.frames.size() > 0) { - memcpy(frame_buffer, closure.frames.data(), collected_frames * sizeof(jvmtiFrameInfo)); + + jvmtiFrameInfo* frame_info = new jvmtiFrameInfo[collected_frames]; + frame_infos.emplace_back(frame_info); + + jint count; + jvmtiError translate_result = TranslateFrameVector(thread_frames, + 0, + 0, + static_cast<jint>(collected_frames), + frame_info, + &count); + DCHECK(translate_result == JVMTI_ERROR_NONE); + stack_info.frame_count = static_cast<jint>(collected_frames); + stack_info.frame_buffer = frame_info; + sum_frames += static_cast<size_t>(count); + } + + // No errors, yet. Now put it all into an output buffer. + size_t rounded_stack_info_size = art::RoundUp(sizeof(jvmtiStackInfo) * frames.size(), + alignof(jvmtiFrameInfo)); + size_t chunk_size = rounded_stack_info_size + sum_frames * sizeof(jvmtiFrameInfo); + unsigned char* chunk_data; + jvmtiError alloc_result = env->Allocate(chunk_size, &chunk_data); + if (alloc_result != ERR(NONE)) { + return alloc_result; + } + + jvmtiStackInfo* stack_info = reinterpret_cast<jvmtiStackInfo*>(chunk_data); + // First copy in all the basic data. + memcpy(stack_info, stack_info_array.get(), sizeof(jvmtiStackInfo) * frames.size()); + + // Now copy the frames and fix up the pointers. + jvmtiFrameInfo* frame_info = reinterpret_cast<jvmtiFrameInfo*>( + chunk_data + rounded_stack_info_size); + for (size_t i = 0; i < frames.size(); ++i) { + jvmtiStackInfo& old_stack_info = stack_info_array.get()[i]; + jvmtiStackInfo& new_stack_info = stack_info[i]; + + jthread thread_peer = current->GetJniEnv()->AddLocalReference<jthread>(threads[i]->GetPeer()); + new_stack_info.thread = thread_peer; + + if (old_stack_info.frame_count > 0) { + // Only copy when there's data - leave the nullptr alone. + size_t frames_size = static_cast<size_t>(old_stack_info.frame_count) * sizeof(jvmtiFrameInfo); + memcpy(frame_info, old_stack_info.frame_buffer, frames_size); + new_stack_info.frame_buffer = frame_info; + frame_info += old_stack_info.frame_count; } - *count_ptr = static_cast<jint>(closure.frames.size()); - return ERR(NONE); } - // Frames from the bottom. - if (collected_frames < static_cast<size_t>(-start_depth)) { + *stack_info_ptr = stack_info; + *thread_count_ptr = static_cast<jint>(frames.size()); + + return ERR(NONE); +} + +jvmtiError StackUtil::GetThreadListStackTraces(jvmtiEnv* env, + jint thread_count, + const jthread* thread_list, + jint max_frame_count, + jvmtiStackInfo** stack_info_ptr) { + if (max_frame_count < 0) { + return ERR(ILLEGAL_ARGUMENT); + } + if (thread_count < 0) { return ERR(ILLEGAL_ARGUMENT); } + if (thread_count == 0) { + *stack_info_ptr = nullptr; + return ERR(NONE); + } + if (stack_info_ptr == nullptr || stack_info_ptr == nullptr) { + return ERR(NULL_POINTER); + } + + art::Thread* current = art::Thread::Current(); + art::ScopedObjectAccess soa(current); // Now we know we have the shared lock. + + // Decode all threads to raw pointers. Put them into a handle scope to avoid any moving GC bugs. + art::VariableSizedHandleScope hs(current); + std::vector<art::Handle<art::mirror::Object>> handles; + for (jint i = 0; i != thread_count; ++i) { + if (thread_list[i] == nullptr) { + return ERR(INVALID_THREAD); + } + if (!soa.Env()->IsInstanceOf(thread_list[i], art::WellKnownClasses::java_lang_Thread)) { + return ERR(INVALID_THREAD); + } + handles.push_back(hs.NewHandle(soa.Decode<art::mirror::Object>(thread_list[i]))); + } + + std::vector<art::Thread*> threads; + std::vector<size_t> thread_list_indices; + std::vector<std::vector<jvmtiFrameInfo>> frames; + + { + art::ScopedThreadSuspension sts(current, art::kWaitingForDebuggerSuspension); + art::ScopedSuspendAll ssa("GetThreadListStackTraces"); + + { + std::list<art::Thread*> art_thread_list; + { + art::MutexLock mu(current, *art::Locks::thread_list_lock_); + art_thread_list = art::Runtime::Current()->GetThreadList()->GetList(); + } + + for (art::Thread* thread : art_thread_list) { + if (thread->IsStillStarting()) { + // Skip this. We can't get the jpeer, and if it is for a thread in the thread_list, + // we'll just report STARTING. + continue; + } + + // Get the peer, and check whether we know it. + art::ObjPtr<art::mirror::Object> peer = thread->GetPeer(); + for (size_t index = 0; index != handles.size(); ++index) { + if (peer == handles[index].Get()) { + // Found the thread. + GetStackTraceClosure closure(0u, static_cast<size_t>(max_frame_count)); + thread->RequestSynchronousCheckpoint(&closure); + + threads.push_back(thread); + thread_list_indices.push_back(index); + frames.emplace_back(); + frames.back().swap(closure.frames); + + continue; + } + } + + // Must be not started, or dead. We'll deal with it at the end. + } + } + } + + // Convert the data into our output format. + + // Note: we use an array of jvmtiStackInfo for convenience. The spec says we need to + // allocate one big chunk for this and the actual frames, which means we need + // to either be conservative or rearrange things later (the latter is implemented). + std::unique_ptr<jvmtiStackInfo[]> stack_info_array(new jvmtiStackInfo[frames.size()]); + std::vector<std::unique_ptr<jvmtiFrameInfo[]>> frame_infos; + frame_infos.reserve(frames.size()); + + // Now run through and add data for each thread. + size_t sum_frames = 0; + for (size_t index = 0; index < frames.size(); ++index) { + jvmtiStackInfo& stack_info = stack_info_array.get()[index]; + memset(&stack_info, 0, sizeof(jvmtiStackInfo)); + + art::Thread* self = threads[index]; + const std::vector<jvmtiFrameInfo>& thread_frames = frames[index]; + + // For the time being, set the thread to null. We don't have good ScopedLocalRef + // infrastructure. + DCHECK(self->GetPeer() != nullptr); + stack_info.thread = nullptr; + stack_info.state = JVMTI_THREAD_STATE_SUSPENDED; + + size_t collected_frames = thread_frames.size(); + if (max_frame_count == 0 || collected_frames == 0) { + stack_info.frame_count = 0; + stack_info.frame_buffer = nullptr; + continue; + } + DCHECK_LE(collected_frames, static_cast<size_t>(max_frame_count)); + + jvmtiFrameInfo* frame_info = new jvmtiFrameInfo[collected_frames]; + frame_infos.emplace_back(frame_info); + + jint count; + jvmtiError translate_result = TranslateFrameVector(thread_frames, + 0, + 0, + static_cast<jint>(collected_frames), + frame_info, + &count); + DCHECK(translate_result == JVMTI_ERROR_NONE); + stack_info.frame_count = static_cast<jint>(collected_frames); + stack_info.frame_buffer = frame_info; + sum_frames += static_cast<size_t>(count); + } + + // No errors, yet. Now put it all into an output buffer. Note that this is not frames.size(), + // potentially. + size_t rounded_stack_info_size = art::RoundUp(sizeof(jvmtiStackInfo) * thread_count, + alignof(jvmtiFrameInfo)); + size_t chunk_size = rounded_stack_info_size + sum_frames * sizeof(jvmtiFrameInfo); + unsigned char* chunk_data; + jvmtiError alloc_result = env->Allocate(chunk_size, &chunk_data); + if (alloc_result != ERR(NONE)) { + return alloc_result; + } + + jvmtiStackInfo* stack_info = reinterpret_cast<jvmtiStackInfo*>(chunk_data); + jvmtiFrameInfo* frame_info = reinterpret_cast<jvmtiFrameInfo*>( + chunk_data + rounded_stack_info_size); + + for (size_t i = 0; i < static_cast<size_t>(thread_count); ++i) { + // Check whether we found a running thread for this. + // Note: For simplicity, and with the expectation that the list is usually small, use a simple + // search. (The list is *not* sorted!) + auto it = std::find(thread_list_indices.begin(), thread_list_indices.end(), i); + if (it == thread_list_indices.end()) { + // No native thread. Must be new or dead. We need to fill out the stack info now. + // (Need to read the Java "started" field to know whether this is starting or terminated.) + art::ObjPtr<art::mirror::Object> peer = soa.Decode<art::mirror::Object>(thread_list[i]); + art::ObjPtr<art::mirror::Class> klass = peer->GetClass(); + art::ArtField* started_field = klass->FindDeclaredInstanceField("started", "Z"); + CHECK(started_field != nullptr); + bool started = started_field->GetBoolean(peer) != 0; + constexpr jint kStartedState = JVMTI_JAVA_LANG_THREAD_STATE_NEW; + constexpr jint kTerminatedState = JVMTI_THREAD_STATE_TERMINATED | + JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED; + stack_info[i].thread = reinterpret_cast<JNIEnv*>(soa.Env())->NewLocalRef(thread_list[i]); + stack_info[i].state = started ? kTerminatedState : kStartedState; + stack_info[i].frame_count = 0; + stack_info[i].frame_buffer = nullptr; + } else { + // Had a native thread and frames. + size_t f_index = it - thread_list_indices.begin(); + + jvmtiStackInfo& old_stack_info = stack_info_array.get()[f_index]; + jvmtiStackInfo& new_stack_info = stack_info[i]; + + memcpy(&new_stack_info, &old_stack_info, sizeof(jvmtiStackInfo)); + new_stack_info.thread = reinterpret_cast<JNIEnv*>(soa.Env())->NewLocalRef(thread_list[i]); + if (old_stack_info.frame_count > 0) { + // Only copy when there's data - leave the nullptr alone. + size_t frames_size = + static_cast<size_t>(old_stack_info.frame_count) * sizeof(jvmtiFrameInfo); + memcpy(frame_info, old_stack_info.frame_buffer, frames_size); + new_stack_info.frame_buffer = frame_info; + frame_info += old_stack_info.frame_count; + } + } + } + + * stack_info_ptr = stack_info; - size_t count = std::min(static_cast<size_t>(-start_depth), static_cast<size_t>(max_frame_count)); - memcpy(frame_buffer, - &closure.frames.data()[collected_frames + start_depth], - count * sizeof(jvmtiFrameInfo)); - *count_ptr = static_cast<jint>(count); return ERR(NONE); } diff --git a/runtime/openjdkjvmti/ti_stack.h b/runtime/openjdkjvmti/ti_stack.h index 1931ed3113..a5b391c5d2 100644 --- a/runtime/openjdkjvmti/ti_stack.h +++ b/runtime/openjdkjvmti/ti_stack.h @@ -32,18 +32,33 @@ #ifndef ART_RUNTIME_OPENJDKJVMTI_TI_STACK_H_ #define ART_RUNTIME_OPENJDKJVMTI_TI_STACK_H_ +#include "jni.h" #include "jvmti.h" +#include "base/mutex.h" + namespace openjdkjvmti { class StackUtil { public: + static jvmtiError GetAllStackTraces(jvmtiEnv* env, + jint max_frame_count, + jvmtiStackInfo** stack_info_ptr, + jint* thread_count_ptr) + REQUIRES(!art::Locks::thread_list_lock_); + static jvmtiError GetStackTrace(jvmtiEnv* env, jthread thread, jint start_depth, jint max_frame_count, jvmtiFrameInfo* frame_buffer, jint* count_ptr); + + static jvmtiError GetThreadListStackTraces(jvmtiEnv* env, + jint thread_count, + const jthread* thread_list, + jint max_frame_count, + jvmtiStackInfo** stack_info_ptr); }; } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc new file mode 100644 index 0000000000..e20f5605d8 --- /dev/null +++ b/runtime/openjdkjvmti/ti_thread.cc @@ -0,0 +1,357 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "ti_thread.h" + +#include "art_field.h" +#include "art_jvmti.h" +#include "base/logging.h" +#include "base/mutex.h" +#include "jni_internal.h" +#include "mirror/class.h" +#include "mirror/object-inl.h" +#include "mirror/string.h" +#include "obj_ptr.h" +#include "scoped_thread_state_change-inl.h" +#include "thread-inl.h" +#include "well_known_classes.h" + +namespace openjdkjvmti { + +jvmtiError ThreadUtil::GetCurrentThread(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread* thread_ptr) { + art::Thread* self = art::Thread::Current(); + + art::ScopedObjectAccess soa(self); + + jthread thread_peer; + if (self->IsStillStarting()) { + thread_peer = nullptr; + } else { + thread_peer = soa.AddLocalReference<jthread>(self->GetPeer()); + } + + *thread_ptr = thread_peer; + return ERR(NONE); +} + +// Read the context classloader from a Java thread object. This is a lazy implementation +// that assumes GetThreadInfo isn't called too often. If we instead cache the ArtField, +// we will have to add synchronization as this can't be cached on startup (which is +// potentially runtime startup). +static art::ObjPtr<art::mirror::Object> GetContextClassLoader(art::ObjPtr<art::mirror::Object> peer) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (peer == nullptr) { + return nullptr; + } + art::ObjPtr<art::mirror::Class> klass = peer->GetClass(); + art::ArtField* cc_field = klass->FindDeclaredInstanceField("contextClassLoader", + "Ljava/lang/ClassLoader;"); + CHECK(cc_field != nullptr); + return cc_field->GetObject(peer); +} + +// Get the native thread. The spec says a null object denotes the current thread. +static art::Thread* GetNativeThread(jthread thread, + const art::ScopedObjectAccessAlreadyRunnable& soa) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (thread == nullptr) { + return art::Thread::Current(); + } + + art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_); + return art::Thread::FromManagedThread(soa, thread); +} + +jvmtiError ThreadUtil::GetThreadInfo(jvmtiEnv* env, jthread thread, jvmtiThreadInfo* info_ptr) { + if (info_ptr == nullptr) { + return ERR(NULL_POINTER); + } + + art::ScopedObjectAccess soa(art::Thread::Current()); + + art::Thread* self = GetNativeThread(thread, soa); + if (self == nullptr && thread == nullptr) { + return ERR(INVALID_THREAD); + } + + JvmtiUniquePtr name_uptr; + if (self != nullptr) { + // Have a native thread object, this thread is alive. + std::string name; + self->GetThreadName(name); + jvmtiError name_result = CopyString( + env, name.c_str(), reinterpret_cast<unsigned char**>(&info_ptr->name)); + if (name_result != ERR(NONE)) { + return name_result; + } + name_uptr = MakeJvmtiUniquePtr(env, info_ptr->name); + + info_ptr->priority = self->GetNativePriority(); + + info_ptr->is_daemon = self->IsDaemon(); + + art::ObjPtr<art::mirror::Object> peer = self->GetPeer(); + + // ThreadGroup. + if (peer != nullptr) { + art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_group); + CHECK(f != nullptr); + art::ObjPtr<art::mirror::Object> group = f->GetObject(peer); + info_ptr->thread_group = group == nullptr + ? nullptr + : soa.AddLocalReference<jthreadGroup>(group); + } else { + info_ptr->thread_group = nullptr; + } + + // Context classloader. + art::ObjPtr<art::mirror::Object> ccl = GetContextClassLoader(peer); + info_ptr->context_class_loader = ccl == nullptr + ? nullptr + : soa.AddLocalReference<jobject>(ccl); + } else { + // Only the peer. This thread has either not been started, or is dead. Read things from + // the Java side. + art::ObjPtr<art::mirror::Object> peer = soa.Decode<art::mirror::Object>(thread); + + // Name. + { + art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_name); + CHECK(f != nullptr); + art::ObjPtr<art::mirror::Object> name = f->GetObject(peer); + std::string name_cpp; + const char* name_cstr; + if (name != nullptr) { + name_cpp = name->AsString()->ToModifiedUtf8(); + name_cstr = name_cpp.c_str(); + } else { + name_cstr = ""; + } + jvmtiError name_result = CopyString( + env, name_cstr, reinterpret_cast<unsigned char**>(&info_ptr->name)); + if (name_result != ERR(NONE)) { + return name_result; + } + name_uptr = MakeJvmtiUniquePtr(env, info_ptr->name); + } + + // Priority. + { + art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_priority); + CHECK(f != nullptr); + info_ptr->priority = static_cast<jint>(f->GetInt(peer)); + } + + // Daemon. + { + art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_daemon); + CHECK(f != nullptr); + info_ptr->is_daemon = f->GetBoolean(peer) == 0 ? JNI_FALSE : JNI_TRUE; + } + + // ThreadGroup. + { + art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_group); + CHECK(f != nullptr); + art::ObjPtr<art::mirror::Object> group = f->GetObject(peer); + info_ptr->thread_group = group == nullptr + ? nullptr + : soa.AddLocalReference<jthreadGroup>(group); + } + + // Context classloader. + art::ObjPtr<art::mirror::Object> ccl = GetContextClassLoader(peer); + info_ptr->context_class_loader = ccl == nullptr + ? nullptr + : soa.AddLocalReference<jobject>(ccl); + } + + name_uptr.release(); + + return ERR(NONE); +} + +// Return the thread's (or current thread, if null) thread state. Return kStarting in case +// there's no native counterpart (thread hasn't been started, yet, or is dead). +static art::ThreadState GetNativeThreadState(jthread thread, + const art::ScopedObjectAccessAlreadyRunnable& soa, + art::Thread** native_thread) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::Thread* self = nullptr; + art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_); + if (thread == nullptr) { + self = art::Thread::Current(); + } else { + self = art::Thread::FromManagedThread(soa, thread); + } + *native_thread = self; + if (self == nullptr || self->IsStillStarting()) { + return art::ThreadState::kStarting; + } + return self->GetState(); +} + +static jint GetJvmtiThreadStateFromInternal(art::ThreadState internal_thread_state) { + jint jvmti_state = JVMTI_THREAD_STATE_ALIVE; + + if (internal_thread_state == art::ThreadState::kSuspended) { + jvmti_state |= JVMTI_THREAD_STATE_SUSPENDED; + // Note: We do not have data about the previous state. Otherwise we should load the previous + // state here. + } + + if (internal_thread_state == art::ThreadState::kNative) { + jvmti_state |= JVMTI_THREAD_STATE_IN_NATIVE; + } + + if (internal_thread_state == art::ThreadState::kRunnable || + internal_thread_state == art::ThreadState::kWaitingWeakGcRootRead || + internal_thread_state == art::ThreadState::kSuspended) { + jvmti_state |= JVMTI_THREAD_STATE_RUNNABLE; + } else if (internal_thread_state == art::ThreadState::kBlocked) { + jvmti_state |= JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER; + } else { + // Should be in waiting state. + jvmti_state |= JVMTI_THREAD_STATE_WAITING; + + if (internal_thread_state == art::ThreadState::kTimedWaiting || + internal_thread_state == art::ThreadState::kSleeping) { + jvmti_state |= JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT; + } else { + jvmti_state |= JVMTI_THREAD_STATE_WAITING_INDEFINITELY; + } + + if (internal_thread_state == art::ThreadState::kSleeping) { + jvmti_state |= JVMTI_THREAD_STATE_SLEEPING; + } + + if (internal_thread_state == art::ThreadState::kTimedWaiting || + internal_thread_state == art::ThreadState::kWaiting) { + jvmti_state |= JVMTI_THREAD_STATE_IN_OBJECT_WAIT; + } + + // TODO: PARKED. We'll have to inspect the stack. + } + + return jvmti_state; +} + +static jint GetJavaStateFromInternal(art::ThreadState internal_thread_state) { + switch (internal_thread_state) { + case art::ThreadState::kTerminated: + return JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED; + + case art::ThreadState::kRunnable: + case art::ThreadState::kNative: + case art::ThreadState::kWaitingWeakGcRootRead: + case art::ThreadState::kSuspended: + return JVMTI_JAVA_LANG_THREAD_STATE_RUNNABLE; + + case art::ThreadState::kTimedWaiting: + case art::ThreadState::kSleeping: + return JVMTI_JAVA_LANG_THREAD_STATE_TIMED_WAITING; + + case art::ThreadState::kBlocked: + return JVMTI_JAVA_LANG_THREAD_STATE_BLOCKED; + + case art::ThreadState::kStarting: + return JVMTI_JAVA_LANG_THREAD_STATE_NEW; + + case art::ThreadState::kWaiting: + case art::ThreadState::kWaitingForGcToComplete: + case art::ThreadState::kWaitingPerformingGc: + case art::ThreadState::kWaitingForCheckPointsToRun: + case art::ThreadState::kWaitingForDebuggerSend: + case art::ThreadState::kWaitingForDebuggerToAttach: + case art::ThreadState::kWaitingInMainDebuggerLoop: + case art::ThreadState::kWaitingForDebuggerSuspension: + case art::ThreadState::kWaitingForDeoptimization: + case art::ThreadState::kWaitingForGetObjectsAllocated: + case art::ThreadState::kWaitingForJniOnLoad: + case art::ThreadState::kWaitingForSignalCatcherOutput: + case art::ThreadState::kWaitingInMainSignalCatcherLoop: + case art::ThreadState::kWaitingForMethodTracingStart: + case art::ThreadState::kWaitingForVisitObjects: + case art::ThreadState::kWaitingForGcThreadFlip: + return JVMTI_JAVA_LANG_THREAD_STATE_WAITING; + } + LOG(FATAL) << "Unreachable"; + UNREACHABLE(); +} + +jvmtiError ThreadUtil::GetThreadState(jvmtiEnv* env ATTRIBUTE_UNUSED, + jthread thread, + jint* thread_state_ptr) { + if (thread_state_ptr == nullptr) { + return ERR(NULL_POINTER); + } + + art::ScopedObjectAccess soa(art::Thread::Current()); + art::Thread* native_thread = nullptr; + art::ThreadState internal_thread_state = GetNativeThreadState(thread, soa, &native_thread); + + if (internal_thread_state == art::ThreadState::kStarting) { + if (thread == nullptr) { + // No native thread, and no Java thread? We must be starting up. Report as wrong phase. + return ERR(WRONG_PHASE); + } + + // Need to read the Java "started" field to know whether this is starting or terminated. + art::ObjPtr<art::mirror::Object> peer = soa.Decode<art::mirror::Object>(thread); + art::ObjPtr<art::mirror::Class> klass = peer->GetClass(); + art::ArtField* started_field = klass->FindDeclaredInstanceField("started", "Z"); + CHECK(started_field != nullptr); + bool started = started_field->GetBoolean(peer) != 0; + constexpr jint kStartedState = JVMTI_JAVA_LANG_THREAD_STATE_NEW; + constexpr jint kTerminatedState = JVMTI_THREAD_STATE_TERMINATED | + JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED; + *thread_state_ptr = started ? kTerminatedState : kStartedState; + return ERR(NONE); + } + DCHECK(native_thread != nullptr); + + // Translate internal thread state to JVMTI and Java state. + jint jvmti_state = GetJvmtiThreadStateFromInternal(internal_thread_state); + if (native_thread->IsInterrupted()) { + jvmti_state |= JVMTI_THREAD_STATE_INTERRUPTED; + } + + // Java state is derived from nativeGetState. + // Note: Our implementation assigns "runnable" to suspended. As such, we will have slightly + // different mask. However, this is for consistency with the Java view. + jint java_state = GetJavaStateFromInternal(internal_thread_state); + + *thread_state_ptr = jvmti_state | java_state; + + return ERR(NONE); +} + +} // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_thread.h b/runtime/openjdkjvmti/ti_thread.h new file mode 100644 index 0000000000..b6ffbb5f65 --- /dev/null +++ b/runtime/openjdkjvmti/ti_thread.h @@ -0,0 +1,51 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_THREAD_H_ +#define ART_RUNTIME_OPENJDKJVMTI_TI_THREAD_H_ + +#include "jni.h" +#include "jvmti.h" + +namespace openjdkjvmti { + +class ThreadUtil { + public: + static jvmtiError GetCurrentThread(jvmtiEnv* env, jthread* thread_ptr); + + static jvmtiError GetThreadInfo(jvmtiEnv* env, jthread thread, jvmtiThreadInfo* info_ptr); + + static jvmtiError GetThreadState(jvmtiEnv* env, jthread thread, jint* thread_state_ptr); +}; + +} // namespace openjdkjvmti + +#endif // ART_RUNTIME_OPENJDKJVMTI_TI_THREAD_H_ diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 2086d70791..df5fc5ce8a 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -1364,6 +1364,39 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { return true; } +static bool EnsureJvmtiPlugin(Runtime* runtime, + std::vector<Plugin>* plugins, + std::string* error_msg) { + constexpr const char* plugin_name = kIsDebugBuild ? "libopenjdkjvmtid.so" : "libopenjdkjvmti.so"; + + // Is the plugin already loaded? + for (Plugin p : *plugins) { + if (p.GetLibrary() == plugin_name) { + return true; + } + } + + // Is the process debuggable? Otherwise, do not attempt to load the plugin. + if (!runtime->IsDebuggable()) { + *error_msg = "Process is not debuggable."; + return false; + } + + Plugin new_plugin = Plugin::Create(plugin_name); + + // Suspend all threads to protect ourself somewhat. + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); // Now we know we have the shared lock. + ScopedThreadSuspension sts(self, art::kWaitingForDebuggerToAttach); + ScopedSuspendAll ssa("EnsureJvmtiPlugin"); + if (!new_plugin.Load(error_msg)) { + return false; + } + + plugins->push_back(std::move(new_plugin)); + return true; +} + // Attach a new agent and add it to the list of runtime agents // // TODO: once we decide on the threading model for agents, @@ -1371,18 +1404,25 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { // (and we synchronize access to any shared data structures like "agents_") // void Runtime::AttachAgent(const std::string& agent_arg) { + std::string error_msg; + if (!EnsureJvmtiPlugin(this, &plugins_, &error_msg)) { + LOG(WARNING) << "Could not load plugin: " << error_msg; + ScopedObjectAccess soa(Thread::Current()); + ThrowIOException("%s", error_msg.c_str()); + return; + } + ti::Agent agent(agent_arg); int res = 0; - std::string err; - ti::Agent::LoadError result = agent.Attach(&res, &err); + ti::Agent::LoadError result = agent.Attach(&res, &error_msg); if (result == ti::Agent::kNoError) { agents_.push_back(std::move(agent)); } else { - LOG(ERROR) << "Agent attach failed (result=" << result << ") : " << err; + LOG(WARNING) << "Agent attach failed (result=" << result << ") : " << error_msg; ScopedObjectAccess soa(Thread::Current()); - ThrowWrappedIOException("%s", err.c_str()); + ThrowIOException("%s", error_msg.c_str()); } } diff --git a/test/129-ThreadGetId/expected.txt b/test/129-ThreadGetId/expected.txt index 134d8d0b47..aadf90d9d7 100644 --- a/test/129-ThreadGetId/expected.txt +++ b/test/129-ThreadGetId/expected.txt @@ -1 +1,2 @@ +HeapTaskDaemon depth 0 Finishing diff --git a/test/129-ThreadGetId/src/Main.java b/test/129-ThreadGetId/src/Main.java index 9934bba95f..6ba01ff0b5 100644 --- a/test/129-ThreadGetId/src/Main.java +++ b/test/129-ThreadGetId/src/Main.java @@ -14,6 +14,7 @@ * limitations under the License. */ +import java.lang.reflect.Field; import java.util.Map; public class Main implements Runnable { @@ -22,6 +23,7 @@ public class Main implements Runnable { public static void main(String[] args) throws Exception { final Thread[] threads = new Thread[numberOfThreads]; + test_getStackTraces(); for (int t = 0; t < threads.length; t++) { threads[t] = new Thread(new Main()); threads[t].start(); @@ -32,6 +34,43 @@ public class Main implements Runnable { System.out.println("Finishing"); } + static Thread getHeapTaskDaemon() throws Exception { + Field f = ThreadGroup.class.getDeclaredField("systemThreadGroup"); + f.setAccessible(true); + ThreadGroup systemThreadGroup = (ThreadGroup) f.get(null); + + while (true) { + int activeCount = systemThreadGroup.activeCount(); + Thread[] array = new Thread[activeCount]; + systemThreadGroup.enumerate(array); + for (Thread thread : array) { + if (thread.getName().equals("HeapTaskDaemon") && + thread.getState() != Thread.State.NEW) { + return thread; + } + } + // Yield to eventually get the daemon started. + Thread.sleep(10); + } + } + + static void test_getStackTraces() throws Exception { + Thread heapDaemon = getHeapTaskDaemon(); + + // Force a GC to ensure the daemon truly started. + Runtime.getRuntime().gc(); + // Check all the current threads for positive IDs. + Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces(); + for (Map.Entry<Thread, StackTraceElement[]> pair : map.entrySet()) { + Thread thread = pair.getKey(); + // Expect empty stack trace since we do not support suspending the GC thread for + // obtaining stack traces. See b/28261069. + if (thread == heapDaemon) { + System.out.println(thread.getName() + " depth " + pair.getValue().length); + } + } + } + public void test_getId() { if (Thread.currentThread().getId() <= 0) { System.out.println("current thread's ID is not positive"); diff --git a/test/566-polymorphic-inlining/polymorphic_inline.cc b/test/566-polymorphic-inlining/polymorphic_inline.cc index 00c1b02675..b75becffcb 100644 --- a/test/566-polymorphic-inlining/polymorphic_inline.cc +++ b/test/566-polymorphic-inlining/polymorphic_inline.cc @@ -35,8 +35,9 @@ static void do_checks(jclass cls, const char* method_name) { OatQuickMethodHeader* header = nullptr; // Infinite loop... Test harness will have its own timeout. while (true) { - header = OatQuickMethodHeader::FromEntryPoint(method->GetEntryPointFromQuickCompiledCode()); - if (code_cache->ContainsPc(header->GetCode())) { + const void* pc = method->GetEntryPointFromQuickCompiledCode(); + if (code_cache->ContainsPc(pc)) { + header = OatQuickMethodHeader::FromEntryPoint(pc); break; } else { // Sleep to yield to the compiler thread. diff --git a/test/903-hello-tagging/tagging.cc b/test/903-hello-tagging/tagging.cc index 60a31bdd2d..f74c1fc2ea 100644 --- a/test/903-hello-tagging/tagging.cc +++ b/test/903-hello-tagging/tagging.cc @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "tagging.h" - #include <iostream> #include <pthread.h> #include <stdio.h> @@ -141,18 +139,6 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getTaggedObjects(JNIEnv* env return resultArray; } -// Don't do anything -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetAllCapabilities(jvmti_env); - return 0; -} - } // namespace Test903HelloTagging } // namespace art diff --git a/test/904-object-allocation/tracking.cc b/test/904-object-allocation/tracking.cc index f993606b42..95eab0c6cc 100644 --- a/test/904-object-allocation/tracking.cc +++ b/test/904-object-allocation/tracking.cc @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "tracking.h" - #include <iostream> #include <pthread.h> #include <stdio.h> @@ -89,19 +87,6 @@ extern "C" JNIEXPORT void JNICALL Java_Main_enableAllocationTracking(JNIEnv* env } } -// Don't do anything -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, nullptr); - SetAllCapabilities(jvmti_env); - return 0; -} - } // namespace Test904ObjectAllocation } // namespace art diff --git a/test/904-object-allocation/tracking.h b/test/904-object-allocation/tracking.h deleted file mode 100644 index 21c1837523..0000000000 --- a/test/904-object-allocation/tracking.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2016 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_TEST_904_OBJECT_ALLOCATION_TRACKING_H_ -#define ART_TEST_904_OBJECT_ALLOCATION_TRACKING_H_ - -#include <jni.h> - -namespace art { -namespace Test904ObjectAllocation { - -jint OnLoad(JavaVM* vm, char* options, void* reserved); - -} // namespace Test904ObjectAllocation -} // namespace art - -#endif // ART_TEST_904_OBJECT_ALLOCATION_TRACKING_H_ diff --git a/test/905-object-free/tracking_free.cc b/test/905-object-free/tracking_free.cc index 7f295accb2..7b26d79edb 100644 --- a/test/905-object-free/tracking_free.cc +++ b/test/905-object-free/tracking_free.cc @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "tracking_free.h" - #include <iostream> #include <pthread.h> #include <stdio.h> @@ -82,17 +80,5 @@ extern "C" JNIEXPORT jlongArray JNICALL Java_Main_getCollectedTags(JNIEnv* env, return ret; } -// Don't do anything -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetAllCapabilities(jvmti_env); - return 0; -} - } // namespace Test905ObjectFree } // namespace art diff --git a/test/905-object-free/tracking_free.h b/test/905-object-free/tracking_free.h deleted file mode 100644 index ba4aa43ffe..0000000000 --- a/test/905-object-free/tracking_free.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2016 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_TEST_905_OBJECT_FREE_TRACKING_FREE_H_ -#define ART_TEST_905_OBJECT_FREE_TRACKING_FREE_H_ - -#include <jni.h> - -namespace art { -namespace Test905ObjectFree { - -jint OnLoad(JavaVM* vm, char* options, void* reserved); - -} // namespace Test905ObjectFree -} // namespace art - -#endif // ART_TEST_905_OBJECT_FREE_TRACKING_FREE_H_ diff --git a/test/906-iterate-heap/iterate_heap.cc b/test/906-iterate-heap/iterate_heap.cc index a2fd59128f..1362d470e6 100644 --- a/test/906-iterate-heap/iterate_heap.cc +++ b/test/906-iterate-heap/iterate_heap.cc @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "iterate_heap.h" - #include <iostream> #include <pthread.h> #include <stdio.h> @@ -174,17 +172,5 @@ extern "C" JNIEXPORT void JNICALL Java_Main_iterateThroughHeapAdd(JNIEnv* env AT Run(heap_filter, klass_filter, &config); } -// Don't do anything -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetAllCapabilities(jvmti_env); - return 0; -} - } // namespace Test906IterateHeap } // namespace art diff --git a/test/906-iterate-heap/iterate_heap.h b/test/906-iterate-heap/iterate_heap.h deleted file mode 100644 index f25cdbaf49..0000000000 --- a/test/906-iterate-heap/iterate_heap.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2016 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_TEST_906_ITERATE_HEAP_ITERATE_HEAP_H_ -#define ART_TEST_906_ITERATE_HEAP_ITERATE_HEAP_H_ - -#include <jni.h> - -namespace art { -namespace Test906IterateHeap { - -jint OnLoad(JavaVM* vm, char* options, void* reserved); - -} // namespace Test906IterateHeap -} // namespace art - -#endif // ART_TEST_906_ITERATE_HEAP_ITERATE_HEAP_H_ diff --git a/test/907-get-loaded-classes/get_loaded_classes.cc b/test/907-get-loaded-classes/get_loaded_classes.cc index 36d33b63cc..5bda7ebac8 100644 --- a/test/907-get-loaded-classes/get_loaded_classes.cc +++ b/test/907-get-loaded-classes/get_loaded_classes.cc @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "get_loaded_classes.h" - #include <iostream> #include <pthread.h> #include <stdio.h> @@ -65,17 +63,5 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getLoadedClasses( return ret; } -// Don't do anything -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetAllCapabilities(jvmti_env); - return 0; -} - } // namespace Test907GetLoadedClasses } // namespace art diff --git a/test/907-get-loaded-classes/get_loaded_classes.h b/test/907-get-loaded-classes/get_loaded_classes.h deleted file mode 100644 index 4d27f898cc..0000000000 --- a/test/907-get-loaded-classes/get_loaded_classes.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2016 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_TEST_907_GET_LOADED_CLASSES_GET_LOADED_CLASSES_H_ -#define ART_TEST_907_GET_LOADED_CLASSES_GET_LOADED_CLASSES_H_ - -#include <jni.h> - -namespace art { -namespace Test907GetLoadedClasses { - -jint OnLoad(JavaVM* vm, char* options, void* reserved); - -} // namespace Test907GetLoadedClasses -} // namespace art - -#endif // ART_TEST_907_GET_LOADED_CLASSES_GET_LOADED_CLASSES_H_ diff --git a/test/908-gc-start-finish/gc_callbacks.cc b/test/908-gc-start-finish/gc_callbacks.cc index 1fab79dcb1..59801ff648 100644 --- a/test/908-gc-start-finish/gc_callbacks.cc +++ b/test/908-gc-start-finish/gc_callbacks.cc @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "gc_callbacks.h" - #include <stdio.h> #include <string.h> @@ -94,17 +92,5 @@ extern "C" JNIEXPORT jint JNICALL Java_Main_getGcFinishes(JNIEnv* env ATTRIBUTE_ return result; } -// Don't do anything -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetAllCapabilities(jvmti_env); - return 0; -} - } // namespace Test908GcStartFinish } // namespace art diff --git a/test/908-gc-start-finish/gc_callbacks.h b/test/908-gc-start-finish/gc_callbacks.h deleted file mode 100644 index 177a4eb7b2..0000000000 --- a/test/908-gc-start-finish/gc_callbacks.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2016 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_TEST_908_GC_START_FINISH_GC_CALLBACKS_H_ -#define ART_TEST_908_GC_START_FINISH_GC_CALLBACKS_H_ - -#include <jni.h> - -namespace art { -namespace Test908GcStartFinish { - -jint OnLoad(JavaVM* vm, char* options, void* reserved); - -} // namespace Test908GcStartFinish -} // namespace art - -#endif // ART_TEST_908_GC_START_FINISH_GC_CALLBACKS_H_ diff --git a/test/909-attach-agent/expected.txt b/test/909-attach-agent/expected.txt index eacc595aaf..c0bccd6486 100644 --- a/test/909-attach-agent/expected.txt +++ b/test/909-attach-agent/expected.txt @@ -1,3 +1,11 @@ Hello, world! Attached Agent for test 909-attach-agent Goodbye! +Hello, world! +Attached Agent for test 909-attach-agent +Goodbye! +Hello, world! +java.io.IOException: Process is not debuggable. + at dalvik.system.VMDebug.attachAgent(Native Method) + at Main.main(Main.java:27) +Goodbye! diff --git a/test/909-attach-agent/run b/test/909-attach-agent/run index aed6e83d67..985341bd4f 100755 --- a/test/909-attach-agent/run +++ b/test/909-attach-agent/run @@ -24,4 +24,14 @@ fi ./default-run "$@" --experimental agents \ --experimental runtime-plugins \ --android-runtime-option -Xplugin:${plugin} \ + --android-runtime-option -Xfully-deoptable \ + --args agent:${agent}=909-attach-agent + +./default-run "$@" --experimental agents \ + --experimental runtime-plugins \ + --android-runtime-option -Xfully-deoptable \ + --args agent:${agent}=909-attach-agent + +./default-run "$@" --experimental agents \ + --experimental runtime-plugins \ --args agent:${agent}=909-attach-agent diff --git a/test/910-methods/methods.cc b/test/910-methods/methods.cc index fa9679db4b..f60fabb1df 100644 --- a/test/910-methods/methods.cc +++ b/test/910-methods/methods.cc @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "methods.h" - #include <stdio.h> #include "base/macros.h" @@ -207,17 +205,5 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_isMethodSynthetic( return is_synthetic; } -// Don't do anything -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetAllCapabilities(jvmti_env); - return 0; -} - } // namespace Test910Methods } // namespace art diff --git a/test/910-methods/methods.h b/test/910-methods/methods.h deleted file mode 100644 index 93d18741ed..0000000000 --- a/test/910-methods/methods.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2016 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_TEST_910_METHODS_METHODS_H_ -#define ART_TEST_910_METHODS_METHODS_H_ - -#include <jni.h> - -namespace art { -namespace Test910Methods { - -jint OnLoad(JavaVM* vm, char* options, void* reserved); - -} // namespace Test910Methods -} // namespace art - -#endif // ART_TEST_910_METHODS_METHODS_H_ diff --git a/test/911-get-stack-trace/expected.txt b/test/911-get-stack-trace/expected.txt index f8c97ce475..061101c02f 100644 --- a/test/911-get-stack-trace/expected.txt +++ b/test/911-get-stack-trace/expected.txt @@ -4,72 +4,72 @@ From top --------- getStackTrace (Ljava/lang/Thread;II)[[Ljava/lang/String; -1 -2 - print (Ljava/lang/Thread;II)V 0 124 - printOrWait (IILMain$ControlData;)V 6 151 - baz (IIILMain$ControlData;)Ljava/lang/Object; 2 142 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - doTest ()V 38 34 - main ([Ljava/lang/String;)V 6 24 ---------- - print (Ljava/lang/Thread;II)V 0 124 - printOrWait (IILMain$ControlData;)V 6 151 - baz (IIILMain$ControlData;)Ljava/lang/Object; 2 142 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - doTest ()V 42 35 - main ([Ljava/lang/String;)V 6 24 + print (Ljava/lang/Thread;II)V 0 34 + printOrWait (IILControlData;)V 6 39 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + doTest ()V 38 23 + main ([Ljava/lang/String;)V 6 21 +--------- + print (Ljava/lang/Thread;II)V 0 34 + printOrWait (IILControlData;)V 6 39 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + doTest ()V 42 24 + main ([Ljava/lang/String;)V 6 21 --------- getStackTrace (Ljava/lang/Thread;II)[[Ljava/lang/String; -1 -2 - print (Ljava/lang/Thread;II)V 0 124 - printOrWait (IILMain$ControlData;)V 6 151 - baz (IIILMain$ControlData;)Ljava/lang/Object; 2 142 - bar (IIILMain$ControlData;)J 0 136 ---------- - printOrWait (IILMain$ControlData;)V 6 151 - baz (IIILMain$ControlData;)Ljava/lang/Object; 2 142 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 + print (Ljava/lang/Thread;II)V 0 34 + printOrWait (IILControlData;)V 6 39 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 +--------- + printOrWait (IILControlData;)V 6 39 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 From bottom --------- - main ([Ljava/lang/String;)V 6 24 + main ([Ljava/lang/String;)V 6 21 --------- - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - doTest ()V 65 41 - main ([Ljava/lang/String;)V 6 24 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + doTest ()V 65 30 + main ([Ljava/lang/String;)V 6 21 --------- - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 ################################ ### Other thread (suspended) ### @@ -77,132 +77,700 @@ From bottom From top --------- wait ()V -1 -2 - printOrWait (IILMain$ControlData;)V 24 157 - baz (IIILMain$ControlData;)Ljava/lang/Object; 2 142 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - run ()V 4 54 ---------- - printOrWait (IILMain$ControlData;)V 24 157 - baz (IIILMain$ControlData;)Ljava/lang/Object; 2 142 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - run ()V 4 54 ---------- - wait ()V -1 -2 - printOrWait (IILMain$ControlData;)V 24 157 - baz (IIILMain$ControlData;)Ljava/lang/Object; 2 142 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 ---------- - baz (IIILMain$ControlData;)Ljava/lang/Object; 2 142 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 26 +--------- + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 26 +--------- + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 +--------- + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 From bottom --------- - run ()V 4 54 + run ()V 4 26 --------- - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - run ()V 4 54 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 26 --------- - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 ########################### ### Other thread (live) ### ########################### From top --------- - printOrWait (IILMain$ControlData;)V 44 164 - baz (IIILMain$ControlData;)Ljava/lang/Object; 2 142 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - run ()V 4 88 ---------- - baz (IIILMain$ControlData;)Ljava/lang/Object; 2 142 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - run ()V 4 88 ---------- - printOrWait (IILMain$ControlData;)V 44 164 - baz (IIILMain$ControlData;)Ljava/lang/Object; 2 142 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 ---------- - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 + printOrWait (IILControlData;)V 44 52 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 59 +--------- + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 59 +--------- + printOrWait (IILControlData;)V 44 52 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 +--------- + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 From bottom --------- - run ()V 4 88 + run ()V 4 59 +--------- + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 59 +--------- + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + +################################ +### Other threads (suspended) ### +################################ +--------- +FinalizerDaemon +<not printed> +--------- +FinalizerWatchdogDaemon +<not printed> +--------- +HeapTaskDaemon +<not printed> +--------- +ReferenceQueueDaemon +<not printed> +--------- +Signal Catcher + +--------- +Thread-10 + +--------- +Thread-11 + +--------- +Thread-12 + +--------- +Thread-13 + +--------- +Thread-4 + +--------- +Thread-5 + +--------- +Thread-6 + +--------- +Thread-7 + +--------- +Thread-8 + +--------- +Thread-9 + +--------- +main + +--------- +FinalizerDaemon +<not printed> +--------- +FinalizerWatchdogDaemon +<not printed> +--------- +HeapTaskDaemon +<not printed> +--------- +ReferenceQueueDaemon +<not printed> +--------- +Signal Catcher + +--------- +Thread-10 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-11 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-12 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-13 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-4 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-5 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-6 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-7 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-8 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-9 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +main + getAllStackTraces (I)[[Ljava/lang/Object; -1 -2 + printAll (I)V 0 73 + doTest ()V 102 57 + main ([Ljava/lang/String;)V 30 33 + +--------- +FinalizerDaemon +<not printed> +--------- +FinalizerWatchdogDaemon +<not printed> --------- - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - run ()V 4 88 +HeapTaskDaemon +<not printed> --------- - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 - foo (IIILMain$ControlData;)I 0 131 - baz (IIILMain$ControlData;)Ljava/lang/Object; 9 144 - bar (IIILMain$ControlData;)J 0 136 +ReferenceQueueDaemon +<not printed> +--------- +Signal Catcher + +--------- +Thread-10 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 45 + +--------- +Thread-11 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 45 + +--------- +Thread-12 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 45 + +--------- +Thread-13 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 45 + +--------- +Thread-4 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 45 + +--------- +Thread-5 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 45 + +--------- +Thread-6 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 45 + +--------- +Thread-7 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 45 + +--------- +Thread-8 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 45 + +--------- +Thread-9 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 45 + +--------- +main + getAllStackTraces (I)[[Ljava/lang/Object; -1 -2 + printAll (I)V 0 73 + doTest ()V 107 59 + main ([Ljava/lang/String;)V 30 33 + + +######################################## +### Other select threads (suspended) ### +######################################## +--------- +Thread-14 + +--------- +Thread-16 + +--------- +Thread-18 + +--------- +Thread-20 + +--------- +Thread-22 + +--------- +main + +--------- +Thread-14 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-16 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-18 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-20 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-22 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +main + getThreadListStackTraces ([Ljava/lang/Thread;I)[[Ljava/lang/Object; -1 -2 + printList ([Ljava/lang/Thread;I)V 0 66 + doTest ()V 96 52 + main ([Ljava/lang/String;)V 38 37 + +--------- +Thread-14 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 35 + +--------- +Thread-16 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 35 + +--------- +Thread-18 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 35 + +--------- +Thread-20 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 35 + +--------- +Thread-22 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 35 + +--------- +main + getThreadListStackTraces ([Ljava/lang/Thread;I)[[Ljava/lang/Object; -1 -2 + printList ([Ljava/lang/Thread;I)V 0 66 + doTest ()V 101 54 + main ([Ljava/lang/String;)V 38 37 + +Done diff --git a/test/911-get-stack-trace/src/AllTraces.java b/test/911-get-stack-trace/src/AllTraces.java new file mode 100644 index 0000000000..adf6f38f5a --- /dev/null +++ b/test/911-get-stack-trace/src/AllTraces.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2016 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. + */ + +import java.util.ArrayList; +import java.util.List; + +public class AllTraces { + private final static List<Object> RETAIN = new ArrayList<Object>(); + + public static void doTest() throws Exception { + System.out.println("################################"); + System.out.println("### Other threads (suspended) ###"); + System.out.println("################################"); + + // Also create an unstarted and a dead thread. + RETAIN.add(new Thread()); + Thread deadThread = new Thread(); + RETAIN.add(deadThread); + deadThread.start(); + deadThread.join(); + + final int N = 10; + + final ControlData data = new ControlData(N); + data.waitFor = new Object(); + + Thread threads[] = new Thread[N]; + + for (int i = 0; i < N; i++) { + Thread t = new Thread() { + public void run() { + Recurse.foo(4, 0, 0, data); + } + }; + t.start(); + threads[i] = t; + } + data.reached.await(); + Thread.yield(); + Thread.sleep(500); // A little bit of time... + + printAll(0); + + printAll(5); + + printAll(25); + + // Let the thread make progress and die. + synchronized(data.waitFor) { + data.waitFor.notifyAll(); + } + for (int i = 0; i < N; i++) { + threads[i].join(); + } + + RETAIN.clear(); + } + + public static void printAll(int max) { + PrintThread.printAll(getAllStackTraces(max)); + } + + // Get all stack traces. This will return an array with an element for each thread. The element + // is an array itself with the first element being the thread, and the second element a nested + // String array as in getStackTrace. + public static native Object[][] getAllStackTraces(int max); +} diff --git a/test/918-fields/fields.h b/test/911-get-stack-trace/src/ControlData.java index 89bd1614d5..76ac4b8caa 100644 --- a/test/918-fields/fields.h +++ b/test/911-get-stack-trace/src/ControlData.java @@ -14,17 +14,18 @@ * limitations under the License. */ -#ifndef ART_TEST_918_FIELDS_FIELDS_H_ -#define ART_TEST_918_FIELDS_FIELDS_H_ +import java.util.concurrent.CountDownLatch; -#include <jni.h> +public class ControlData { + CountDownLatch reached; + Object waitFor = null; + volatile boolean stop = false; -namespace art { -namespace Test918Fields { + public ControlData() { + this(1); + } -jint OnLoad(JavaVM* vm, char* options, void* reserved); - -} // namespace Test918Fields -} // namespace art - -#endif // ART_TEST_918_FIELDS_FIELDS_H_ + public ControlData(int latchCount) { + reached = new CountDownLatch(latchCount); + } +} diff --git a/test/911-get-stack-trace/src/Main.java b/test/911-get-stack-trace/src/Main.java index 722bee8056..2df5c53907 100644 --- a/test/911-get-stack-trace/src/Main.java +++ b/test/911-get-stack-trace/src/Main.java @@ -14,166 +14,28 @@ * limitations under the License. */ -import java.util.Arrays; -import java.util.concurrent.CountDownLatch; - public class Main { public static void main(String[] args) throws Exception { System.loadLibrary(args[1]); - doTest(); - doTestOtherThreadWait(); - doTestOtherThreadBusyLoop(); - } - - public static void doTest() throws Exception { - System.out.println("###################"); - System.out.println("### Same thread ###"); - System.out.println("###################"); - System.out.println("From top"); - Recurse.foo(4, 0, 25, null); - Recurse.foo(4, 1, 25, null); - Recurse.foo(4, 0, 5, null); - Recurse.foo(4, 2, 5, null); - - System.out.println("From bottom"); - Recurse.foo(4, -1, 25, null); - Recurse.foo(4, -5, 5, null); - Recurse.foo(4, -7, 5, null); - } + SameThread.doTest(); - public static void doTestOtherThreadWait() throws Exception { System.out.println(); - System.out.println("################################"); - System.out.println("### Other thread (suspended) ###"); - System.out.println("################################"); - final ControlData data = new ControlData(); - data.waitFor = new Object(); - Thread t = new Thread() { - public void run() { - Recurse.foo(4, 0, 0, data); - } - }; - t.start(); - data.reached.await(); - Thread.yield(); - Thread.sleep(500); // A little bit of time... - System.out.println("From top"); - print(t, 0, 25); - print(t, 1, 25); - print(t, 0, 5); - print(t, 2, 5); + OtherThread.doTestOtherThreadWait(); - System.out.println("From bottom"); - print(t, -1, 25); - print(t, -5, 5); - print(t, -7, 5); - - // Let the thread make progress and die. - synchronized(data.waitFor) { - data.waitFor.notifyAll(); - } - t.join(); - } - - public static void doTestOtherThreadBusyLoop() throws Exception { System.out.println(); - System.out.println("###########################"); - System.out.println("### Other thread (live) ###"); - System.out.println("###########################"); - final ControlData data = new ControlData(); - Thread t = new Thread() { - public void run() { - Recurse.foo(4, 0, 0, data); - } - }; - t.start(); - data.reached.await(); - Thread.yield(); - Thread.sleep(500); // A little bit of time... - - System.out.println("From top"); - print(t, 0, 25); - print(t, 1, 25); - print(t, 0, 5); - print(t, 2, 5); - - System.out.println("From bottom"); - print(t, -1, 25); - print(t, -5, 5); - print(t, -7, 5); - // Let the thread stop looping and die. - data.stop = true; - t.join(); - } - - public static void print(String[][] stack) { - System.out.println("---------"); - for (String[] stackElement : stack) { - for (String part : stackElement) { - System.out.print(' '); - System.out.print(part); - } - System.out.println(); - } - } - - public static void print(Thread t, int start, int max) { - print(getStackTrace(t, start, max)); - } + OtherThread.doTestOtherThreadBusyLoop(); - // Wrap generated stack traces into a class to separate them nicely. - public static class Recurse { + System.out.println(); - public static int foo(int x, int start, int max, ControlData data) { - bar(x, start, max, data); - return 0; - } + AllTraces.doTest(); - private static long bar(int x, int start, int max, ControlData data) { - baz(x, start, max, data); - return 0; - } + System.out.println(); - private static Object baz(int x, int start, int max, ControlData data) { - if (x == 0) { - printOrWait(start, max, data); - } else { - foo(x - 1, start, max, data); - } - return null; - } + ThreadListTraces.doTest(); - private static void printOrWait(int start, int max, ControlData data) { - if (data == null) { - print(Thread.currentThread(), start, max); - } else { - if (data.waitFor != null) { - synchronized (data.waitFor) { - data.reached.countDown(); - try { - data.waitFor.wait(); // Use wait() as it doesn't have a "hidden" Java call-graph. - } catch (Throwable t) { - throw new RuntimeException(t); - } - } - } else { - data.reached.countDown(); - while (!data.stop) { - // Busy-loop. - } - } - } - } + System.out.println("Done"); } - - public static class ControlData { - CountDownLatch reached = new CountDownLatch(1); - Object waitFor = null; - volatile boolean stop = false; - } - - public static native String[][] getStackTrace(Thread thread, int start, int max); } diff --git a/test/911-get-stack-trace/src/OtherThread.java b/test/911-get-stack-trace/src/OtherThread.java new file mode 100644 index 0000000000..0748433a20 --- /dev/null +++ b/test/911-get-stack-trace/src/OtherThread.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016 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. + */ + +public class OtherThread { + public static void doTestOtherThreadWait() throws Exception { + System.out.println("################################"); + System.out.println("### Other thread (suspended) ###"); + System.out.println("################################"); + final ControlData data = new ControlData(); + data.waitFor = new Object(); + Thread t = new Thread() { + public void run() { + Recurse.foo(4, 0, 0, data); + } + }; + t.start(); + data.reached.await(); + Thread.yield(); + Thread.sleep(500); // A little bit of time... + + System.out.println("From top"); + PrintThread.print(t, 0, 25); + PrintThread.print(t, 1, 25); + PrintThread.print(t, 0, 5); + PrintThread.print(t, 2, 5); + + System.out.println("From bottom"); + PrintThread.print(t, -1, 25); + PrintThread.print(t, -5, 5); + PrintThread.print(t, -7, 5); + + // Let the thread make progress and die. + synchronized(data.waitFor) { + data.waitFor.notifyAll(); + } + t.join(); + } + + public static void doTestOtherThreadBusyLoop() throws Exception { + System.out.println("###########################"); + System.out.println("### Other thread (live) ###"); + System.out.println("###########################"); + final ControlData data = new ControlData(); + Thread t = new Thread() { + public void run() { + Recurse.foo(4, 0, 0, data); + } + }; + t.start(); + data.reached.await(); + Thread.yield(); + Thread.sleep(500); // A little bit of time... + + System.out.println("From top"); + PrintThread.print(t, 0, 25); + PrintThread.print(t, 1, 25); + PrintThread.print(t, 0, 5); + PrintThread.print(t, 2, 5); + + System.out.println("From bottom"); + PrintThread.print(t, -1, 25); + PrintThread.print(t, -5, 5); + PrintThread.print(t, -7, 5); + + // Let the thread stop looping and die. + data.stop = true; + t.join(); + } +} diff --git a/test/911-get-stack-trace/src/PrintThread.java b/test/911-get-stack-trace/src/PrintThread.java new file mode 100644 index 0000000000..97815ccad9 --- /dev/null +++ b/test/911-get-stack-trace/src/PrintThread.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016 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. + */ + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class PrintThread { + public static void print(String[][] stack) { + System.out.println("---------"); + for (String[] stackElement : stack) { + for (String part : stackElement) { + System.out.print(' '); + System.out.print(part); + } + System.out.println(); + } + } + + public static void print(Thread t, int start, int max) { + print(getStackTrace(t, start, max)); + } + + public static void printAll(Object[][] stacks) { + List<String> stringified = new ArrayList<String>(stacks.length); + + for (Object[] stackInfo : stacks) { + Thread t = (Thread)stackInfo[0]; + String name = (t != null) ? t.getName() : "null"; + String stackSerialization; + if (name.contains("Daemon")) { + // Do not print daemon stacks, as they're non-deterministic. + stackSerialization = "<not printed>"; + } else { + StringBuilder sb = new StringBuilder(); + for (String[] stackElement : (String[][])stackInfo[1]) { + for (String part : stackElement) { + sb.append(' '); + sb.append(part); + } + sb.append('\n'); + } + stackSerialization = sb.toString(); + } + stringified.add(name + "\n" + stackSerialization); + } + + Collections.sort(stringified); + + for (String s : stringified) { + System.out.println("---------"); + System.out.println(s); + } + } + + public static native String[][] getStackTrace(Thread thread, int start, int max); +}
\ No newline at end of file diff --git a/test/911-get-stack-trace/src/Recurse.java b/test/911-get-stack-trace/src/Recurse.java new file mode 100644 index 0000000000..439fbaaf84 --- /dev/null +++ b/test/911-get-stack-trace/src/Recurse.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 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. + */ + +public class Recurse { + public static int foo(int x, int start, int max, ControlData data) { + bar(x, start, max, data); + return 0; + } + + private static long bar(int x, int start, int max, ControlData data) { + baz(x, start, max, data); + return 0; + } + + private static Object baz(int x, int start, int max, ControlData data) { + if (x == 0) { + printOrWait(start, max, data); + } else { + foo(x - 1, start, max, data); + } + return null; + } + + private static void printOrWait(int start, int max, ControlData data) { + if (data == null) { + PrintThread.print(Thread.currentThread(), start, max); + } else { + if (data.waitFor != null) { + synchronized (data.waitFor) { + data.reached.countDown(); + try { + data.waitFor.wait(); // Use wait() as it doesn't have a "hidden" Java call-graph. + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + } else { + data.reached.countDown(); + while (!data.stop) { + // Busy-loop. + } + } + } + } +}
\ No newline at end of file diff --git a/test/903-hello-tagging/tagging.h b/test/911-get-stack-trace/src/SameThread.java index f062d44880..f1e19e367a 100644 --- a/test/903-hello-tagging/tagging.h +++ b/test/911-get-stack-trace/src/SameThread.java @@ -14,17 +14,20 @@ * limitations under the License. */ -#ifndef ART_TEST_903_HELLO_TAGGING_TAGGING_H_ -#define ART_TEST_903_HELLO_TAGGING_TAGGING_H_ +public class SameThread { + public static void doTest() throws Exception { + System.out.println("###################"); + System.out.println("### Same thread ###"); + System.out.println("###################"); + System.out.println("From top"); + Recurse.foo(4, 0, 25, null); + Recurse.foo(4, 1, 25, null); + Recurse.foo(4, 0, 5, null); + Recurse.foo(4, 2, 5, null); -#include <jni.h> - -namespace art { -namespace Test903HelloTagging { - -jint OnLoad(JavaVM* vm, char* options, void* reserved); - -} // namespace Test903HelloTagging -} // namespace art - -#endif // ART_TEST_903_HELLO_TAGGING_TAGGING_H_ + System.out.println("From bottom"); + Recurse.foo(4, -1, 25, null); + Recurse.foo(4, -5, 5, null); + Recurse.foo(4, -7, 5, null); + } +} diff --git a/test/911-get-stack-trace/src/ThreadListTraces.java b/test/911-get-stack-trace/src/ThreadListTraces.java new file mode 100644 index 0000000000..f66557f3bd --- /dev/null +++ b/test/911-get-stack-trace/src/ThreadListTraces.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2016 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. + */ + +public class ThreadListTraces { + public static void doTest() throws Exception { + System.out.println("########################################"); + System.out.println("### Other select threads (suspended) ###"); + System.out.println("########################################"); + + final int N = 10; + + final ControlData data = new ControlData(N); + data.waitFor = new Object(); + + Thread threads[] = new Thread[N]; + + Thread list[] = new Thread[N/2 + 1]; + + for (int i = 0; i < N; i++) { + Thread t = new Thread() { + public void run() { + Recurse.foo(4, 0, 0, data); + } + }; + t.start(); + threads[i] = t; + if (i % 2 == 0) { + list[i/2] = t; + } + } + list[list.length - 1] = Thread.currentThread(); + + data.reached.await(); + Thread.yield(); + Thread.sleep(500); // A little bit of time... + + printList(list, 0); + + printList(list, 5); + + printList(list, 25); + + // Let the thread make progress and die. + synchronized(data.waitFor) { + data.waitFor.notifyAll(); + } + for (int i = 0; i < N; i++) { + threads[i].join(); + } + } + + public static void printList(Thread[] threads, int max) { + PrintThread.printAll(getThreadListStackTraces(threads, max)); + } + + // Similar to getAllStackTraces, but restricted to the given threads. + public static native Object[][] getThreadListStackTraces(Thread threads[], int max); +} diff --git a/test/911-get-stack-trace/stack_trace.cc b/test/911-get-stack-trace/stack_trace.cc index b3e8bc3b1f..f853387180 100644 --- a/test/911-get-stack-trace/stack_trace.cc +++ b/test/911-get-stack-trace/stack_trace.cc @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "stack_trace.h" - #include <inttypes.h> #include <memory> #include <stdio.h> @@ -52,33 +50,16 @@ static jint FindLineNumber(jint line_number_count, return line_number; } -extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getStackTrace( - JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thread, jint start, jint max) { - std::unique_ptr<jvmtiFrameInfo[]> frames(new jvmtiFrameInfo[max]); - - jint count; - { - jvmtiError result = jvmti_env->GetStackTrace(thread, start, max, frames.get(), &count); - if (result != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(result, &err); - printf("Failure running GetStackTrace: %s\n", err); - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err)); - return nullptr; - } - } - +static jobjectArray TranslateJvmtiFrameInfoArray(JNIEnv* env, + jvmtiFrameInfo* frames, + jint count) { auto callback = [&](jint method_index) -> jobjectArray { char* name; char* sig; char* gen; { jvmtiError result2 = jvmti_env->GetMethodName(frames[method_index].method, &name, &sig, &gen); - if (result2 != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(result2, &err); - printf("Failure running GetMethodName: %s\n", err); - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err)); + if (JvmtiErrorToException(env, result2)) { return nullptr; } } @@ -142,16 +123,83 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getStackTrace( return CreateObjectArray(env, count, "[Ljava/lang/String;", callback); } -// Don't do anything -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; +extern "C" JNIEXPORT jobjectArray JNICALL Java_PrintThread_getStackTrace( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thread, jint start, jint max) { + std::unique_ptr<jvmtiFrameInfo[]> frames(new jvmtiFrameInfo[max]); + + jint count; + { + jvmtiError result = jvmti_env->GetStackTrace(thread, start, max, frames.get(), &count); + if (JvmtiErrorToException(env, result)) { + return nullptr; + } + } + + return TranslateJvmtiFrameInfoArray(env, frames.get(), count); +} + +extern "C" JNIEXPORT jobjectArray JNICALL Java_AllTraces_getAllStackTraces( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jint max) { + jint thread_count; + jvmtiStackInfo* stack_infos; + { + jvmtiError result = jvmti_env->GetAllStackTraces(max, &stack_infos, &thread_count); + if (JvmtiErrorToException(env, result)) { + return nullptr; + } } - SetAllCapabilities(jvmti_env); - return 0; + + auto callback = [&](jint thread_index) -> jobject { + auto inner_callback = [&](jint index) -> jobject { + if (index == 0) { + return stack_infos[thread_index].thread; + } else { + return TranslateJvmtiFrameInfoArray(env, + stack_infos[thread_index].frame_buffer, + stack_infos[thread_index].frame_count); + } + }; + return CreateObjectArray(env, 2, "java/lang/Object", inner_callback); + }; + jobjectArray ret = CreateObjectArray(env, thread_count, "[Ljava/lang/Object;", callback); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(stack_infos)); + return ret; +} + +extern "C" JNIEXPORT jobjectArray JNICALL Java_ThreadListTraces_getThreadListStackTraces( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobjectArray jthreads, jint max) { + jint thread_count = env->GetArrayLength(jthreads); + std::unique_ptr<jthread[]> threads(new jthread[thread_count]); + for (jint i = 0; i != thread_count; ++i) { + threads[i] = env->GetObjectArrayElement(jthreads, i); + } + + jvmtiStackInfo* stack_infos; + { + jvmtiError result = jvmti_env->GetThreadListStackTraces(thread_count, + threads.get(), + max, + &stack_infos); + if (JvmtiErrorToException(env, result)) { + return nullptr; + } + } + + auto callback = [&](jint thread_index) -> jobject { + auto inner_callback = [&](jint index) -> jobject { + if (index == 0) { + return stack_infos[thread_index].thread; + } else { + return TranslateJvmtiFrameInfoArray(env, + stack_infos[thread_index].frame_buffer, + stack_infos[thread_index].frame_count); + } + }; + return CreateObjectArray(env, 2, "java/lang/Object", inner_callback); + }; + jobjectArray ret = CreateObjectArray(env, thread_count, "[Ljava/lang/Object;", callback); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(stack_infos)); + return ret; } } // namespace Test911GetStackTrace diff --git a/test/911-get-stack-trace/stack_trace.h b/test/911-get-stack-trace/stack_trace.h deleted file mode 100644 index eba2a91da1..0000000000 --- a/test/911-get-stack-trace/stack_trace.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2016 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_TEST_911_GET_STACK_TRACE_STACK_TRACE_H_ -#define ART_TEST_911_GET_STACK_TRACE_STACK_TRACE_H_ - -#include <jni.h> - -namespace art { -namespace Test911GetStackTrace { - -jint OnLoad(JavaVM* vm, char* options, void* reserved); - -} // namespace Test911GetStackTrace -} // namespace art - -#endif // ART_TEST_911_GET_STACK_TRACE_STACK_TRACE_H_ diff --git a/test/912-classes/classes.cc b/test/912-classes/classes.cc index 38a4f0e337..69301c7925 100644 --- a/test/912-classes/classes.cc +++ b/test/912-classes/classes.cc @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "classes.h" - #include <stdio.h> #include "base/macros.h" @@ -224,17 +222,5 @@ extern "C" JNIEXPORT jobject JNICALL Java_Main_getClassLoader( return classloader; } -// Don't do anything -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetAllCapabilities(jvmti_env); - return 0; -} - } // namespace Test912Classes } // namespace art diff --git a/test/912-classes/classes.h b/test/912-classes/classes.h deleted file mode 100644 index 62fb203356..0000000000 --- a/test/912-classes/classes.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2016 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_TEST_912_CLASSES_CLASSES_H_ -#define ART_TEST_912_CLASSES_CLASSES_H_ - -#include <jni.h> - -namespace art { -namespace Test912Classes { - -jint OnLoad(JavaVM* vm, char* options, void* reserved); - -} // namespace Test912Classes -} // namespace art - -#endif // ART_TEST_912_CLASSES_CLASSES_H_ diff --git a/test/913-heaps/heaps.cc b/test/913-heaps/heaps.cc index 0b232af0df..67599192cf 100644 --- a/test/913-heaps/heaps.cc +++ b/test/913-heaps/heaps.cc @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "heaps.h" - #include <inttypes.h> #include <stdio.h> #include <string.h> @@ -495,17 +493,5 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_followReferences(JNIEnv* env return ret; } -// Don't do anything -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetAllCapabilities(jvmti_env); - return 0; -} - } // namespace Test913Heaps } // namespace art diff --git a/test/913-heaps/heaps.h b/test/913-heaps/heaps.h deleted file mode 100644 index bd828aca33..0000000000 --- a/test/913-heaps/heaps.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2016 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_TEST_913_HEAPS_HEAPS_H_ -#define ART_TEST_913_HEAPS_HEAPS_H_ - -#include <jni.h> - -namespace art { -namespace Test913Heaps { - -jint OnLoad(JavaVM* vm, char* options, void* reserved); - -} // namespace Test913Heaps -} // namespace art - -#endif // ART_TEST_913_HEAPS_HEAPS_H_ diff --git a/test/918-fields/fields.cc b/test/918-fields/fields.cc index 4d2b34b94e..7d29912f47 100644 --- a/test/918-fields/fields.cc +++ b/test/918-fields/fields.cc @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "fields.h" - #include <stdio.h> #include "base/macros.h" @@ -132,17 +130,5 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_isFieldSynthetic( return synth; } -// Don't do anything -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetAllCapabilities(jvmti_env); - return 0; -} - } // namespace Test918Fields } // namespace art diff --git a/test/920-objects/objects.cc b/test/920-objects/objects.cc index 886dd0e673..0553a9d007 100644 --- a/test/920-objects/objects.cc +++ b/test/920-objects/objects.cc @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "objects.h" - #include <stdio.h> #include "base/macros.h" @@ -61,17 +59,5 @@ extern "C" JNIEXPORT jint JNICALL Java_Main_getObjectHashCode( return hash; } -// Don't do anything -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetAllCapabilities(jvmti_env); - return 0; -} - } // namespace Test920Objects } // namespace art diff --git a/test/920-objects/objects.h b/test/920-objects/objects.h deleted file mode 100644 index 5f21e7b7cb..0000000000 --- a/test/920-objects/objects.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2016 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_TEST_920_OBJECTS_OBJECTS_H_ -#define ART_TEST_920_OBJECTS_OBJECTS_H_ - -#include <jni.h> - -namespace art { -namespace Test920Objects { - -jint OnLoad(JavaVM* vm, char* options, void* reserved); - -} // namespace Test920Objects -} // namespace art - -#endif // ART_TEST_920_OBJECTS_OBJECTS_H_ diff --git a/test/922-properties/properties.cc b/test/922-properties/properties.cc index b1e7fce3b5..cb732c74f1 100644 --- a/test/922-properties/properties.cc +++ b/test/922-properties/properties.cc @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "properties.h" - #include <stdio.h> #include "base/macros.h" @@ -91,17 +89,5 @@ extern "C" JNIEXPORT void JNICALL Java_Main_setSystemProperty( } } -// Don't do anything -jint OnLoad(JavaVM* vm, - char* options ATTRIBUTE_UNUSED, - void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { - printf("Unable to get jvmti env!\n"); - return 1; - } - SetAllCapabilities(jvmti_env); - return 0; -} - } // namespace Test922Properties } // namespace art diff --git a/test/922-properties/properties.h b/test/922-properties/properties.h deleted file mode 100644 index 84feb10758..0000000000 --- a/test/922-properties/properties.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ART_TEST_922_PROPERTIES_PROPERTIES_H_ -#define ART_TEST_922_PROPERTIES_PROPERTIES_H_ - -#include <jni.h> - -namespace art { -namespace Test922Properties { - -jint OnLoad(JavaVM* vm, char* options, void* reserved); - -} // namespace Test922Properties -} // namespace art - -#endif // ART_TEST_922_PROPERTIES_PROPERTIES_H_ diff --git a/test/923-monitors/build b/test/923-monitors/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/923-monitors/build @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-build "$@" --experimental agents diff --git a/test/923-monitors/expected.txt b/test/923-monitors/expected.txt new file mode 100644 index 0000000000..5fbfb9866e --- /dev/null +++ b/test/923-monitors/expected.txt @@ -0,0 +1,38 @@ +Unlock +JVMTI_ERROR_NOT_MONITOR_OWNER +Lock +Unlock +Unlock +JVMTI_ERROR_NOT_MONITOR_OWNER +Lock +Lock +Unlock +Unlock +Unlock +JVMTI_ERROR_NOT_MONITOR_OWNER +Wait +JVMTI_ERROR_NOT_MONITOR_OWNER +Wait +JVMTI_ERROR_ILLEGAL_ARGUMENT +Wait +JVMTI_ERROR_NOT_MONITOR_OWNER +Lock +Wait +Unlock +Unlock +JVMTI_ERROR_NOT_MONITOR_OWNER +Notify +JVMTI_ERROR_NOT_MONITOR_OWNER +Lock +Notify +Unlock +Unlock +JVMTI_ERROR_NOT_MONITOR_OWNER +NotifyAll +JVMTI_ERROR_NOT_MONITOR_OWNER +Lock +NotifyAll +Unlock +Unlock +JVMTI_ERROR_NOT_MONITOR_OWNER +Done diff --git a/test/923-monitors/info.txt b/test/923-monitors/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/923-monitors/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/923-monitors/monitors.cc b/test/923-monitors/monitors.cc new file mode 100644 index 0000000000..4baa530ec2 --- /dev/null +++ b/test/923-monitors/monitors.cc @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> + +#include "base/macros.h" +#include "jni.h" +#include "openjdkjvmti/jvmti.h" +#include "ScopedUtfChars.h" + +#include "ti-agent/common_helper.h" +#include "ti-agent/common_load.h" + +namespace art { +namespace Test923Monitors { + + +static jlong MonitorToLong(jrawMonitorID id) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(id)); +} + +static jrawMonitorID LongToMonitor(jlong l) { + return reinterpret_cast<jrawMonitorID>(static_cast<uintptr_t>(l)); +} + +extern "C" JNIEXPORT jlong JNICALL Java_Main_createRawMonitor( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) { + jrawMonitorID id; + jvmtiError result = jvmti_env->CreateRawMonitor("dummy", &id); + if (JvmtiErrorToException(env, result)) { + return 0; + } + return MonitorToLong(id); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_destroyRawMonitor( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jlong l) { + jvmtiError result = jvmti_env->DestroyRawMonitor(LongToMonitor(l)); + JvmtiErrorToException(env, result); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_rawMonitorEnter( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jlong l) { + jvmtiError result = jvmti_env->RawMonitorEnter(LongToMonitor(l)); + JvmtiErrorToException(env, result); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_rawMonitorExit( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jlong l) { + jvmtiError result = jvmti_env->RawMonitorExit(LongToMonitor(l)); + JvmtiErrorToException(env, result); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_rawMonitorWait( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jlong l, jlong millis) { + jvmtiError result = jvmti_env->RawMonitorWait(LongToMonitor(l), millis); + JvmtiErrorToException(env, result); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_rawMonitorNotify( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jlong l) { + jvmtiError result = jvmti_env->RawMonitorNotify(LongToMonitor(l)); + JvmtiErrorToException(env, result); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_rawMonitorNotifyAll( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jlong l) { + jvmtiError result = jvmti_env->RawMonitorNotifyAll(LongToMonitor(l)); + JvmtiErrorToException(env, result); +} + +} // namespace Test923Monitors +} // namespace art diff --git a/test/923-monitors/run b/test/923-monitors/run new file mode 100755 index 0000000000..4379349cb2 --- /dev/null +++ b/test/923-monitors/run @@ -0,0 +1,19 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-run "$@" --experimental agents \ + --experimental runtime-plugins \ + --jvmti diff --git a/test/923-monitors/src/Main.java b/test/923-monitors/src/Main.java new file mode 100644 index 0000000000..e35ce12608 --- /dev/null +++ b/test/923-monitors/src/Main.java @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.concurrent.CountDownLatch; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class Main { + public static void main(String[] args) throws Exception { + System.loadLibrary(args[1]); + + doTest(); + } + + private static void doTest() throws Exception { + // Start a watchdog, to make sure on deadlocks etc the test dies. + startWatchdog(); + + sharedId = createRawMonitor(); + + output = new ArrayList<String>(100); + + simpleTests(sharedId); + + for (String s : output) { + System.out.println(s); + } + output.clear(); + + threadTests(sharedId); + + destroyRawMonitor(sharedId); + } + + private static void simpleTests(long id) { + unlock(id); // Should fail. + + lock(id); + unlock(id); + unlock(id); // Should fail. + + lock(id); + lock(id); + unlock(id); + unlock(id); + unlock(id); // Should fail. + + rawWait(id, 0); // Should fail. + rawWait(id, -1); // Should fail. + rawWait(id, 1); // Should fail. + + lock(id); + rawWait(id, 50); + unlock(id); + unlock(id); // Should fail. + + rawNotify(id); // Should fail. + lock(id); + rawNotify(id); + unlock(id); + unlock(id); // Should fail. + + rawNotifyAll(id); // Should fail. + lock(id); + rawNotifyAll(id); + unlock(id); + unlock(id); // Should fail. + } + + private static void threadTests(final long id) throws Exception { + final int N = 10; + + final CountDownLatch waitLatch = new CountDownLatch(N); + final CountDownLatch wait2Latch = new CountDownLatch(1); + + Runnable r = new Runnable() { + @Override + public void run() { + lock(id); + waitLatch.countDown(); + rawWait(id, 0); + firstAwakened = Thread.currentThread(); + appendToLog("Awakened"); + unlock(id); + wait2Latch.countDown(); + } + }; + + List<Thread> threads = new ArrayList<Thread>(); + for (int i = 0; i < N; i++) { + Thread t = new Thread(r); + threads.add(t); + t.start(); + } + + // Wait till all threads have been started. + waitLatch.await(); + + // Hopefully enough time for all the threads to progress into wait. + Thread.yield(); + Thread.sleep(500); + + // Wake up one. + lock(id); + rawNotify(id); + unlock(id); + + wait2Latch.await(); + + // Wait a little bit more to see stragglers. This is flaky - spurious wakeups could + // make the test fail. + Thread.yield(); + Thread.sleep(500); + if (firstAwakened != null) { + firstAwakened.join(); + } + + // Wake up everyone else. + lock(id); + rawNotifyAll(id); + unlock(id); + + // Wait for everyone to die. + for (Thread t : threads) { + t.join(); + } + + // Check threaded output. + Iterator<String> it = output.iterator(); + // 1) Start with N locks and Waits. + { + int locks = 0; + int waits = 0; + for (int i = 0; i < 2*N; i++) { + String s = it.next(); + if (s.equals("Lock")) { + locks++; + } else if (s.equals("Wait")) { + if (locks <= waits) { + System.out.println(output); + throw new RuntimeException("Wait before Lock"); + } + waits++; + } else { + System.out.println(output); + throw new RuntimeException("Unexpected operation: " + s); + } + } + } + + // 2) Expect Lock + Notify + Unlock. + expect("Lock", it, output); + expect("Notify", it, output); + expect("Unlock", it, output); + + // 3) A single thread wakes up, runs, and dies. + expect("Awakened", it, output); + expect("Unlock", it, output); + + // 4) Expect Lock + NotifyAll + Unlock. + expect("Lock", it, output); + expect("NotifyAll", it, output); + expect("Unlock", it, output); + + // 5) N-1 threads wake up, run, and die. + { + int expectedUnlocks = 0; + int ops = 2 * (N-1); + for (int i = 0; i < ops; i++) { + String s = it.next(); + if (s.equals("Awakened")) { + expectedUnlocks++; + } else if (s.equals("Unlock")) { + expectedUnlocks--; + if (expectedUnlocks < 0) { + System.out.println(output); + throw new RuntimeException("Unexpected unlock"); + } + } + } + } + + // 6) That should be it. + if (it.hasNext()) { + System.out.println(output); + throw new RuntimeException("Unexpected trailing output, starting with " + it.next()); + } + + output.clear(); + System.out.println("Done"); + } + + private static void expect(String s, Iterator<String> it, List<String> output) { + String t = it.next(); + if (!s.equals(t)) { + System.out.println(output); + throw new RuntimeException("Expected " + s + " but got " + t); + } + } + + private static void lock(long id) { + appendToLog("Lock"); + rawMonitorEnter(id); + } + + private static void unlock(long id) { + appendToLog("Unlock"); + try { + rawMonitorExit(id); + } catch (RuntimeException e) { + appendToLog(e.getMessage()); + } + } + + private static void rawWait(long id, long millis) { + appendToLog("Wait"); + try { + rawMonitorWait(id, millis); + } catch (RuntimeException e) { + appendToLog(e.getMessage()); + } + } + + private static void rawNotify(long id) { + appendToLog("Notify"); + try { + rawMonitorNotify(id); + } catch (RuntimeException e) { + appendToLog(e.getMessage()); + } + } + + private static void rawNotifyAll(long id) { + appendToLog("NotifyAll"); + try { + rawMonitorNotifyAll(id); + } catch (RuntimeException e) { + appendToLog(e.getMessage()); + } + } + + private static synchronized void appendToLog(String s) { + output.add(s); + } + + private static void startWatchdog() { + Runnable r = new Runnable() { + @Override + public void run() { + long start = System.currentTimeMillis(); + // Give it a minute. + long end = 60 * 1000 + start; + for (;;) { + long delta = end - System.currentTimeMillis(); + if (delta <= 0) { + break; + } + + try { + Thread.currentThread().sleep(delta); + } catch (Exception e) { + } + } + System.out.println("TIMEOUT!"); + System.exit(1); + } + }; + Thread t = new Thread(r); + t.setDaemon(true); + t.start(); + } + + static volatile long sharedId; + static List<String> output; + static Thread firstAwakened; + + private static native long createRawMonitor(); + private static native void destroyRawMonitor(long id); + private static native void rawMonitorEnter(long id); + private static native void rawMonitorExit(long id); + private static native void rawMonitorWait(long id, long millis); + private static native void rawMonitorNotify(long id); + private static native void rawMonitorNotifyAll(long id); +} diff --git a/test/924-threads/build b/test/924-threads/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/924-threads/build @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-build "$@" --experimental agents diff --git a/test/924-threads/expected.txt b/test/924-threads/expected.txt new file mode 100644 index 0000000000..54065223cf --- /dev/null +++ b/test/924-threads/expected.txt @@ -0,0 +1,30 @@ +currentThread OK +main +5 +false +java.lang.ThreadGroup[name=main,maxpri=10] +class dalvik.system.PathClassLoader +main +5 +false +java.lang.ThreadGroup[name=main,maxpri=10] +class dalvik.system.PathClassLoader +Daemon Thread +5 +true +java.lang.ThreadGroup[name=main,maxpri=10] +class dalvik.system.PathClassLoader +Daemon Thread +5 +true +java.lang.ThreadGroup[name=main,maxpri=10] +class dalvik.system.PathClassLoader +5 +5 +0 = NEW +191 = ALIVE|WAITING_INDEFINITELY|WAITING|IN_OBJECT_WAIT +1a1 = ALIVE|WAITING_WITH_TIMEOUT|WAITING|IN_OBJECT_WAIT +401 = ALIVE|BLOCKED_ON_MONITOR_ENTER +e1 = ALIVE|WAITING_WITH_TIMEOUT|SLEEPING|WAITING +5 = ALIVE|RUNNABLE +2 = TERMINATED diff --git a/test/924-threads/info.txt b/test/924-threads/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/924-threads/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/924-threads/run b/test/924-threads/run new file mode 100755 index 0000000000..4379349cb2 --- /dev/null +++ b/test/924-threads/run @@ -0,0 +1,19 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-run "$@" --experimental agents \ + --experimental runtime-plugins \ + --jvmti diff --git a/test/924-threads/src/Main.java b/test/924-threads/src/Main.java new file mode 100644 index 0000000000..048766604f --- /dev/null +++ b/test/924-threads/src/Main.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; +import java.util.concurrent.CountDownLatch; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Main { + public static void main(String[] args) throws Exception { + System.loadLibrary(args[1]); + + doTest(); + } + + private static void doTest() throws Exception { + Thread t1 = Thread.currentThread(); + Thread t2 = getCurrentThread(); + + if (t1 != t2) { + throw new RuntimeException("Expected " + t1 + " but got " + t2); + } + System.out.println("currentThread OK"); + + printThreadInfo(t1); + printThreadInfo(null); + + Thread t3 = new Thread("Daemon Thread"); + t3.setDaemon(true); + // Do not start this thread, yet. + printThreadInfo(t3); + // Start, and wait for it to die. + t3.start(); + t3.join(); + Thread.sleep(500); // Wait a little bit. + // Thread has died, check that we can still get info. + printThreadInfo(t3); + + doStateTests(); + } + + private static class Holder { + volatile boolean flag = false; + } + + private static void doStateTests() throws Exception { + System.out.println(Integer.toHexString(getThreadState(null))); + System.out.println(Integer.toHexString(getThreadState(Thread.currentThread()))); + + final CountDownLatch cdl1 = new CountDownLatch(1); + final CountDownLatch cdl2 = new CountDownLatch(1); + final CountDownLatch cdl3_1 = new CountDownLatch(1); + final CountDownLatch cdl3_2 = new CountDownLatch(1); + final CountDownLatch cdl4 = new CountDownLatch(1); + final CountDownLatch cdl5 = new CountDownLatch(1); + final Holder h = new Holder(); + Runnable r = new Runnable() { + @Override + public void run() { + try { + cdl1.countDown(); + synchronized(cdl1) { + cdl1.wait(); + } + + cdl2.countDown(); + synchronized(cdl2) { + cdl2.wait(1000); // Wait a second. + } + + cdl3_1.await(); + cdl3_2.countDown(); + synchronized(cdl3_2) { + // Nothing, just wanted to block on cdl3. + } + + cdl4.countDown(); + Thread.sleep(1000); + + cdl5.countDown(); + while (!h.flag) { + // Busy-loop. + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + + Thread t = new Thread(r); + printThreadState(t); + t.start(); + + // Waiting. + cdl1.await(); + Thread.yield(); + Thread.sleep(100); + printThreadState(t); + synchronized(cdl1) { + cdl1.notifyAll(); + } + + // Timed waiting. + cdl2.await(); + Thread.yield(); + Thread.sleep(100); + printThreadState(t); + synchronized(cdl2) { + cdl2.notifyAll(); + } + + // Blocked on monitor. + synchronized(cdl3_2) { + cdl3_1.countDown(); + cdl3_2.await(); + Thread.yield(); + Thread.sleep(100); + printThreadState(t); + } + + // Sleeping. + cdl4.await(); + Thread.yield(); + Thread.sleep(100); + printThreadState(t); + + // Running. + cdl5.await(); + Thread.yield(); + Thread.sleep(100); + printThreadState(t); + h.flag = true; + + // Dying. + t.join(); + Thread.yield(); + Thread.sleep(100); + + printThreadState(t); + } + + private final static Map<Integer, String> STATE_NAMES = new HashMap<Integer, String>(); + private final static List<Integer> STATE_KEYS = new ArrayList<Integer>(); + static { + STATE_NAMES.put(0x1, "ALIVE"); + STATE_NAMES.put(0x2, "TERMINATED"); + STATE_NAMES.put(0x4, "RUNNABLE"); + STATE_NAMES.put(0x400, "BLOCKED_ON_MONITOR_ENTER"); + STATE_NAMES.put(0x80, "WAITING"); + STATE_NAMES.put(0x10, "WAITING_INDEFINITELY"); + STATE_NAMES.put(0x20, "WAITING_WITH_TIMEOUT"); + STATE_NAMES.put(0x40, "SLEEPING"); + STATE_NAMES.put(0x100, "IN_OBJECT_WAIT"); + STATE_NAMES.put(0x200, "PARKED"); + STATE_NAMES.put(0x100000, "SUSPENDED"); + STATE_NAMES.put(0x200000, "INTERRUPTED"); + STATE_NAMES.put(0x400000, "IN_NATIVE"); + STATE_KEYS.addAll(STATE_NAMES.keySet()); + Collections.sort(STATE_KEYS); + } + + private static void printThreadState(Thread t) { + int state = getThreadState(t); + + StringBuilder sb = new StringBuilder(); + + for (Integer i : STATE_KEYS) { + if ((state & i) != 0) { + if (sb.length()>0) { + sb.append('|'); + } + sb.append(STATE_NAMES.get(i)); + } + } + + if (sb.length() == 0) { + sb.append("NEW"); + } + + System.out.println(Integer.toHexString(state) + " = " + sb.toString()); + } + + private static void printThreadInfo(Thread t) { + Object[] threadInfo = getThreadInfo(t); + if (threadInfo == null || threadInfo.length != 5) { + System.out.println(Arrays.toString(threadInfo)); + throw new RuntimeException("threadInfo length wrong"); + } + + System.out.println(threadInfo[0]); // Name + System.out.println(threadInfo[1]); // Priority + System.out.println(threadInfo[2]); // Daemon + System.out.println(threadInfo[3]); // Threadgroup + System.out.println(threadInfo[4] == null ? "null" : threadInfo[4].getClass()); // Context CL. + } + + private static native Thread getCurrentThread(); + private static native Object[] getThreadInfo(Thread t); + private static native int getThreadState(Thread t); +} diff --git a/test/924-threads/threads.cc b/test/924-threads/threads.cc new file mode 100644 index 0000000000..4abf8fcf93 --- /dev/null +++ b/test/924-threads/threads.cc @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> + +#include "android-base/stringprintf.h" +#include "base/macros.h" +#include "base/logging.h" +#include "jni.h" +#include "openjdkjvmti/jvmti.h" +#include "ScopedLocalRef.h" + +#include "ti-agent/common_helper.h" +#include "ti-agent/common_load.h" + +namespace art { +namespace Test924Threads { + +// private static native Thread getCurrentThread(); +// private static native Object[] getThreadInfo(Thread t); + +extern "C" JNIEXPORT jthread JNICALL Java_Main_getCurrentThread( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) { + jthread thread = nullptr; + jvmtiError result = jvmti_env->GetCurrentThread(&thread); + if (JvmtiErrorToException(env, result)) { + return nullptr; + } + return thread; +} + +extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getThreadInfo( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jthread thread) { + jvmtiThreadInfo info; + memset(&info, 0, sizeof(jvmtiThreadInfo)); + + jvmtiError result = jvmti_env->GetThreadInfo(thread, &info); + if (JvmtiErrorToException(env, result)) { + return nullptr; + } + + auto callback = [&](jint component_index) -> jobject { + switch (component_index) { + // The name. + case 0: + return (info.name == nullptr) ? nullptr : env->NewStringUTF(info.name); + + // The priority. Use a string for simplicity of construction. + case 1: + return env->NewStringUTF(android::base::StringPrintf("%d", info.priority).c_str()); + + // Whether it's a daemon. Use a string for simplicity of construction. + case 2: + return env->NewStringUTF(info.is_daemon == JNI_TRUE ? "true" : "false"); + + // The thread group; + case 3: + return env->NewLocalRef(info.thread_group); + + // The context classloader. + case 4: + return env->NewLocalRef(info.context_class_loader); + } + LOG(FATAL) << "Should not reach here"; + UNREACHABLE(); + }; + jobjectArray ret = CreateObjectArray(env, 5, "java/lang/Object", callback); + + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(info.name)); + if (info.thread_group != nullptr) { + env->DeleteLocalRef(info.thread_group); + } + if (info.context_class_loader != nullptr) { + env->DeleteLocalRef(info.context_class_loader); + } + + return ret; +} + +extern "C" JNIEXPORT jint JNICALL Java_Main_getThreadState( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jthread thread) { + jint state; + jvmtiError result = jvmti_env->GetThreadState(thread, &state); + if (JvmtiErrorToException(env, result)) { + return 0; + } + return state; +} + +} // namespace Test924Threads +} // namespace art diff --git a/test/Android.bp b/test/Android.bp index f6648d1cdc..b0f0e5a98d 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -263,6 +263,8 @@ art_cc_defaults { "918-fields/fields.cc", "920-objects/objects.cc", "922-properties/properties.cc", + "923-monitors/monitors.cc", + "924-threads/threads.cc", ], shared_libs: [ "libbase", diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index a3f6864883..dd7876f09d 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -295,6 +295,8 @@ TEST_ART_BROKEN_TARGET_TESTS += \ 920-objects \ 921-hello-failure \ 922-properties \ + 923-monitors \ + 924-threads \ ifneq (,$(filter target,$(TARGET_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,target,$(RUN_TYPES),$(PREBUILD_TYPES), \ @@ -720,6 +722,16 @@ endif TEST_ART_BROKEN_OPTIMIZING_HEAP_POISONING_RUN_TESTS := +# Tests that check semantics for a non-debuggable app. +TEST_ART_BROKEN_DEBUGGABLE_RUN_TESTS := \ + 909-attach-agent \ + +ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \ + $(COMPILER_TYPES),$(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES), \ + $(IMAGE_TYPES),$(PICTEST_TYPES),debuggable,$(TEST_ART_BROKEN_DEBUGGABLE_RUN_TESTS),$(ALL_ADDRESS_SIZES)) + +TEST_ART_BROKEN_DEBUGGABLE_RUN_TESTS := + # Tests incompatible with bisection bug search. Sorted by incompatibility reason. # 000 through 595 do not compile anything. 089 tests a build failure. 018 through 137 # run dalvikvm more than once. 115 and 088 assume they are always compiled. diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc index 7451cf97de..1b6fc7033a 100644 --- a/test/common/runtime_state.cc +++ b/test/common/runtime_state.cc @@ -169,10 +169,9 @@ extern "C" JNIEXPORT void JNICALL Java_Main_ensureJitCompiled(JNIEnv* env, } jit::JitCodeCache* code_cache = jit->GetCodeCache(); - OatQuickMethodHeader* header = nullptr; while (true) { - header = OatQuickMethodHeader::FromEntryPoint(method->GetEntryPointFromQuickCompiledCode()); - if (code_cache->ContainsPc(header->GetCode())) { + const void* pc = method->GetEntryPointFromQuickCompiledCode(); + if (code_cache->ContainsPc(pc)) { break; } else { // Sleep to yield to the compiler thread. diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc index e309a8920b..8abd063a01 100644 --- a/test/ti-agent/common_load.cc +++ b/test/ti-agent/common_load.cc @@ -26,20 +26,7 @@ #include "common_helper.h" #include "901-hello-ti-agent/basics.h" -#include "903-hello-tagging/tagging.h" -#include "904-object-allocation/tracking.h" -#include "905-object-free/tracking_free.h" -#include "906-iterate-heap/iterate_heap.h" -#include "907-get-loaded-classes/get_loaded_classes.h" -#include "908-gc-start-finish/gc_callbacks.h" #include "909-attach-agent/attach.h" -#include "910-methods/methods.h" -#include "911-get-stack-trace/stack_trace.h" -#include "912-classes/classes.h" -#include "913-heaps/heaps.h" -#include "918-fields/fields.h" -#include "920-objects/objects.h" -#include "922-properties/properties.h" namespace art { @@ -54,30 +41,30 @@ struct AgentLib { OnAttach attach; }; -// A list of all the agents we have for testing. +// A trivial OnLoad implementation that only initializes the global jvmti_env. +static jint MinimalOnLoad(JavaVM* vm, + char* options ATTRIBUTE_UNUSED, + void* reserved ATTRIBUTE_UNUSED) { + if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { + printf("Unable to get jvmti env!\n"); + return 1; + } + SetAllCapabilities(jvmti_env); + return 0; +} + +// A list of all non-standard the agents we have for testing. All other agents will use +// MinimalOnLoad. AgentLib agents[] = { { "901-hello-ti-agent", Test901HelloTi::OnLoad, nullptr }, { "902-hello-transformation", common_redefine::OnLoad, nullptr }, - { "903-hello-tagging", Test903HelloTagging::OnLoad, nullptr }, - { "904-object-allocation", Test904ObjectAllocation::OnLoad, nullptr }, - { "905-object-free", Test905ObjectFree::OnLoad, nullptr }, - { "906-iterate-heap", Test906IterateHeap::OnLoad, nullptr }, - { "907-get-loaded-classes", Test907GetLoadedClasses::OnLoad, nullptr }, - { "908-gc-start-finish", Test908GcStartFinish::OnLoad, nullptr }, { "909-attach-agent", nullptr, Test909AttachAgent::OnAttach }, - { "910-methods", Test910Methods::OnLoad, nullptr }, - { "911-get-stack-trace", Test911GetStackTrace::OnLoad, nullptr }, - { "912-classes", Test912Classes::OnLoad, nullptr }, - { "913-heaps", Test913Heaps::OnLoad, nullptr }, { "914-hello-obsolescence", common_redefine::OnLoad, nullptr }, { "915-obsolete-2", common_redefine::OnLoad, nullptr }, { "916-obsolete-jit", common_redefine::OnLoad, nullptr }, { "917-fields-transformation", common_redefine::OnLoad, nullptr }, - { "918-fields", Test918Fields::OnLoad, nullptr }, { "919-obsolete-fields", common_redefine::OnLoad, nullptr }, - { "920-objects", Test920Objects::OnLoad, nullptr }, { "921-hello-failure", common_redefine::OnLoad, nullptr }, - { "922-properties", Test922Properties::OnLoad, nullptr }, }; static AgentLib* FindAgent(char* name) { @@ -118,18 +105,21 @@ extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* printf("Unable to find agent name in options: %s\n", options); return -1; } + + SetIsJVM(remaining_options); + AgentLib* lib = FindAgent(name_option); + OnLoad fn = nullptr; if (lib == nullptr) { - printf("Unable to find agent named: %s, add it to the list in test/ti-agent/common_load.cc\n", - name_option); - return -2; - } - if (lib->load == nullptr) { - printf("agent: %s does not include an OnLoad method.\n", name_option); - return -3; + fn = &MinimalOnLoad; + } else { + if (lib->load == nullptr) { + printf("agent: %s does not include an OnLoad method.\n", name_option); + return -3; + } + fn = lib->load; } - SetIsJVM(remaining_options); - return lib->load(vm, remaining_options, reserved); + return fn(vm, remaining_options, reserved); } extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) { |