// Limit alloc_record_count to the 2BE value (64k-1) that is the limit of the current protocol. static uint16_t CappedAllocRecordCount(size_t alloc_record_count) { const size_t cap = 0xffff; if (alloc_record_count > cap) { return cap;
} return alloc_record_count;
}
// JDWP is allowed unless the Zygote forbids it. staticbool gJdwpAllowed = true;
// Do we need to deoptimize the stack to handle an exception? bool Dbg::IsForcedInterpreterNeededForExceptionImpl(Thread* thread) { // Deoptimization is required if at least one method in the stack needs it. However we // skip frames that will be unwound (thus not executed). bool needs_deoptimization = false;
StackVisitor::WalkStack(
[&](art::StackVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_) { // The visitor is meant to be used when handling exception from compiled code only.
CHECK(!visitor->IsShadowFrame()) << "We only expect to visit compiled frame: "
<< ArtMethod::PrettyMethod(visitor->GetMethod());
ArtMethod* method = visitor->GetMethod(); if (method == nullptr) { // We reach an upcall and don't need to deoptimize this part of the stack (ManagedFragment) // so we can stop the visit.
DCHECK(!needs_deoptimization); returnfalse;
} if (Runtime::Current()->GetInstrumentation()->InterpretOnly()) { // We found a compiled frame in the stack but instrumentation is set to interpret // everything: we need to deoptimize.
needs_deoptimization = true; returnfalse;
} if (Runtime::Current()->GetInstrumentation()->IsDeoptimized(method)) { // We found a deoptimized method in the stack.
needs_deoptimization = true; returnfalse;
}
ShadowFrame* frame = visitor->GetThread()->FindDebuggerShadowFrame(visitor->GetFrameId()); if (frame != nullptr) { // The debugger allocated a ShadowFrame to update a variable in the stack: we need to // deoptimize the stack to execute (and deallocate) this frame.
needs_deoptimization = true; returnfalse;
} returntrue;
},
thread, /* context= */ nullptr,
art::StackVisitor::StackWalkKind::kIncludeInlinedFrames, /* check_suspended */ true, /* include_transitions */ true); return needs_deoptimization;
}
Thread* self = Thread::Current(); if (self->GetState() != ThreadState::kRunnable) {
LOG(ERROR) << "DDM broadcast in thread state " << self->GetState(); /* try anyway? */
}
// TODO: Can we really get here while not `Runnable`? If not, we do not need the `soa`.
ScopedObjectAccessUnchecked soa(self);
JNIEnv* env = self->GetJniEnv();
jint event = connect ? 1/*DdmServer.CONNECTED*/ : 2 /*DdmServer.DISCONNECTED*/;
ArtMethod* broadcast = WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_broadcast;
broadcast->InvokeStatic<'V', 'I'>(self, event); if (self->IsExceptionPending()) {
LOG(ERROR) << "DdmServer.broadcast " << event << " failed";
env->ExceptionDescribe();
env->ExceptionClear();
}
}
void Dbg::DdmSetThreadNotification(bool enable) { // Enable/disable thread notifications.
gDdmThreadNotification = enable; if (enable) { // Use a Checkpoint to cause every currently running thread to send their own notification when // able. We then wait for every thread thread active at the time to post the creation // notification. Threads created later will send this themselves.
Thread* self = Thread::Current();
ScopedObjectAccess soa(self);
Barrier finish_barrier(0);
FunctionClosure fc([&](Thread* thread) REQUIRES_SHARED(Locks::mutator_lock_) {
Thread* cls_self = Thread::Current();
Locks::mutator_lock_->AssertSharedHeld(cls_self);
Dbg::DdmSendThreadNotification(thread, CHUNK_TYPE("THCR"));
finish_barrier.Pass(cls_self);
}); // TODO(b/253671779): The above eventually results in calls to EventHandler::DispatchEvent, // which does a ScopedThreadStateChange, which amounts to a thread state change inside the // checkpoint run method. Hence the normal check would fail, and thus we specify Unchecked // here.
size_t checkpoints = Runtime::Current()->GetThreadList()->RunCheckpointUnchecked(&fc);
ScopedThreadSuspension sts(self, ThreadState::kWaitingForCheckPointsToRun);
finish_barrier.Increment(self, checkpoints);
}
}
void EnsureHeader(constvoid* chunk_ptr) { if (!needHeader_) { return;
}
// Start a new HPSx chunk.
Write4BE(&p_, 1); // Heap id (bogus; we only have one heap).
Write1BE(&p_, 8); // Size of allocation unit, in bytes.
Write4BE(&p_, reinterpret_cast<uintptr_t>(chunk_ptr)); // virtual address of segment start.
Write4BE(&p_, 0); // offset of this piece (relative to the virtual address). // [u4]: length of piece, in allocation units // We won't know this until we're done, so save the offset and stuff in a fake value.
pieceLenField_ = p_;
Write4BE(&p_, 0x55555555);
needHeader_ = false;
}
void Flush() REQUIRES_SHARED(Locks::mutator_lock_) { if (pieceLenField_ == nullptr) { // Flush immediately post Reset (maybe back-to-back Flush). Ignore.
CHECK(needHeader_); return;
} // Patch the "length of piece" field.
CHECK_LE(&buf_[0], pieceLenField_);
CHECK_LE(pieceLenField_, p_);
Set4BE(pieceLenField_, totalAllocationUnits_);
// Returns true if the object is not an empty chunk. bool ProcessRecord(void* start, size_t used_bytes) REQUIRES_SHARED(Locks::mutator_lock_) { // Note: heap call backs cannot manipulate the heap upon which they are crawling, care is taken // in the following code not to allocate memory, by ensuring buf_ is of the correct size if (used_bytes == 0) { if (start == nullptr) { // Reset for start of new heap.
startOfNextMemoryChunk_ = nullptr;
Flush();
} // Only process in use memory so that free region information // also includes dlmalloc book keeping. returnfalse;
} if (startOfNextMemoryChunk_ != nullptr) { // Transmit any pending free memory. Native free memory of over kMaxFreeLen could be because // of the use of mmaps, so don't report. If not free memory then start a new segment. bool flush = true; if (start > startOfNextMemoryChunk_) { const size_t kMaxFreeLen = 2 * gPageSize; void* free_start = startOfNextMemoryChunk_; void* free_end = start; const size_t free_len = reinterpret_cast<uintptr_t>(free_end) - reinterpret_cast<uintptr_t>(free_start); if (!IsNative() || free_len < kMaxFreeLen) {
AppendChunk(HPSG_STATE(SOLIDITY_FREE, 0), free_start, free_len, IsNative());
flush = false;
}
} if (flush) {
startOfNextMemoryChunk_ = nullptr;
Flush();
}
} returntrue;
}
void HeapChunkJavaCallback(void* start, void* /*end*/, size_t used_bytes)
REQUIRES_SHARED(Locks::heap_bitmap_lock_, Locks::mutator_lock_) { if (ProcessRecord(start, used_bytes)) { // Determine the type of this chunk. // OLD-TODO: if context.merge, see if this chunk is different from the last chunk. // If it's the same, we should combine them.
uint8_t state = ExamineJavaObject(reinterpret_cast<mirror::Object*>(start));
AppendChunk(state, start, used_bytes + chunk_overhead_, /*is_native=*/ false);
startOfNextMemoryChunk_ = reinterpret_cast<char*>(start) + used_bytes + chunk_overhead_;
}
}
void AppendChunk(uint8_t state, void* ptr, size_t length, bool is_native)
REQUIRES_SHARED(Locks::mutator_lock_) { // Make sure there's enough room left in the buffer. // We need to use two bytes for every fractional 256 allocation units used by the chunk plus // 17 bytes for any header. const size_t needed = ((RoundUp(length / ALLOCATION_UNIT_SIZE, 256) / 256) * 2) + 17;
size_t byte_left = &buf_.back() - p_; if (byte_left < needed) { if (is_native) { // Cannot trigger memory allocation while walking native heap. return;
}
Flush();
}
byte_left = &buf_.back() - p_; if (byte_left < needed) {
LOG(WARNING) << "Chunk is too big to transmit (chunk_len=" << length << ", "
<< needed << " bytes)"; return;
}
EnsureHeader(ptr); // Write out the chunk description.
length /= ALLOCATION_UNIT_SIZE; // Convert to allocation units.
totalAllocationUnits_ += length; while (length > 256) {
*p_++ = state | HPSG_PARTIAL;
*p_++ = 255; // length - 1
length -= 256;
}
*p_++ = state;
*p_++ = length - 1;
}
void Dbg::DdmSendHeapSegments(bool native) {
Dbg::HpsgWhen when = native ? gDdmNhsgWhen : gDdmHpsgWhen;
Dbg::HpsgWhat what = native ? gDdmNhsgWhat : gDdmHpsgWhat; if (when == HPSG_WHEN_NEVER) { return;
}
RuntimeCallbacks* cb = Runtime::Current()->GetRuntimeCallbacks(); // Figure out what kind of chunks we'll be sending.
CHECK(what == HPSG_WHAT_MERGED_OBJECTS || what == HPSG_WHAT_DISTINCT_OBJECTS)
<< static_cast<int>(what);
// First, send a heap start chunk.
uint8_t heap_id[4];
Set4BE(&heap_id[0], 1); // Heap id (bogus; we only have one heap).
cb->DdmPublishChunk(native ? CHUNK_TYPE("NHST") : CHUNK_TYPE("HPST"),
ArrayRef<const uint8_t>(heap_id));
Thread* self = Thread::Current();
Locks::mutator_lock_->AssertSharedHeld(self);
// Send a series of heap segment chunks.
HeapChunkContext context(what == HPSG_WHAT_MERGED_OBJECTS, native); auto bump_pointer_space_visitor = [&](mirror::Object* obj)
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_) { const size_t size = RoundUp(obj->SizeOf(), kObjectAlignment);
HeapChunkContext::HeapChunkJavaCallback(
obj, reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(obj) + size), size, &context);
}; if (native) {
UNIMPLEMENTED(WARNING) << "Native heap inspection is not supported";
} else {
gc::Heap* heap = Runtime::Current()->GetHeap(); for (constauto& space : heap->GetContinuousSpaces()) { if (space->IsDlMallocSpace()) {
ReaderMutexLock mu(self, *Locks::heap_bitmap_lock_); // dlmalloc's chunk header is 2 * sizeof(size_t), but if the previous chunk is in use for an // allocation then the first sizeof(size_t) may belong to it.
context.SetChunkOverhead(sizeof(size_t));
space->AsDlMallocSpace()->Walk(HeapChunkContext::HeapChunkJavaCallback, &context);
} elseif (space->IsRosAllocSpace()) {
context.SetChunkOverhead(0); // Need to acquire the mutator lock before the heap bitmap lock with exclusive access since // RosAlloc's internal logic doesn't know to release and reacquire the heap bitmap lock.
ScopedThreadSuspension sts(self, ThreadState::kSuspended);
ScopedSuspendAll ssa(__FUNCTION__);
ReaderMutexLock mu(self, *Locks::heap_bitmap_lock_);
space->AsRosAllocSpace()->Walk(HeapChunkContext::HeapChunkJavaCallback, &context);
} elseif (space->IsBumpPointerSpace()) {
ReaderMutexLock mu(self, *Locks::heap_bitmap_lock_);
context.SetChunkOverhead(0);
space->AsBumpPointerSpace()->Walk(bump_pointer_space_visitor);
HeapChunkContext::HeapChunkJavaCallback(nullptr, nullptr, 0, &context);
} elseif (space->IsRegionSpace()) {
heap->IncrementDisableMovingGC(self);
{
ScopedThreadSuspension sts(self, ThreadState::kSuspended);
ScopedSuspendAll ssa(__FUNCTION__);
ReaderMutexLock mu(self, *Locks::heap_bitmap_lock_);
context.SetChunkOverhead(0);
space->AsRegionSpace()->Walk(bump_pointer_space_visitor);
HeapChunkContext::HeapChunkJavaCallback(nullptr, nullptr, 0, &context);
}
heap->DecrementDisableMovingGC(self);
} else {
UNIMPLEMENTED(WARNING) << "Not counting objects in space " << *space;
}
context.ResetStartOfNextChunk();
}
ReaderMutexLock mu(self, *Locks::heap_bitmap_lock_); // Walk the large objects, these are not in the AllocSpace. if (heap->GetLargeObjectsSpace() != nullptr) {
context.SetChunkOverhead(0);
heap->GetLargeObjectsSpace()->Walk(HeapChunkContext::HeapChunkJavaCallback, &context);
}
}
// Finally, send a heap end chunk.
cb->DdmPublishChunk(native ? CHUNK_TYPE("NHEN") : CHUNK_TYPE("HPEN"),
ArrayRef<const uint8_t>(heap_id));
}
// Update all entries and give them an index. Note that this is likely not the insertion order, // as the set will with high likelihood reorder elements. Thus, Add must not be called after // Finish, and Finish must be called before IndexOf. In that case, WriteTo will walk in // the same order as Finish, and indices will agree. The order invariant, as well as indices, // are enforced through debug checks. void Finish() {
DCHECK(!finished_);
finished_ = true;
uint32_t index = 0; for (auto& entry : table_) {
entry.index = index;
++index;
}
}
size_t IndexOf(constchar* s) const {
DCHECK(finished_);
Entry entry(s); auto it = table_.find(entry); if (it == table_.end()) {
LOG(FATAL) << "IndexOf(\"" << s << "\") failed";
} return it->index;
}
Thread* self = Thread::Current();
std::vector<uint8_t> bytes;
{
MutexLock mu(self, *Locks::alloc_tracker_lock_);
gc::AllocRecordObjectMap* records = Runtime::Current()->GetHeap()->GetAllocationRecords(); // In case this method is called when allocation tracker is not enabled, // we should still send some data back.
gc::AllocRecordObjectMap fallback_record_map; if (records == nullptr) {
CHECK(!Runtime::Current()->GetHeap()->IsAllocTrackingEnabled());
records = &fallback_record_map;
} // We don't need to wait on the condition variable records->new_record_condition_, because this // function only reads the class objects, which are already marked so it doesn't change their // reachability.
LOG(INFO) << "recent allocation records: " << capped_count;
LOG(INFO) << "allocation records all objects: " << records->Size();
// // Part 2: Generate the output and store it in the buffer. //
// (1b) message header len (to allow future expansion); includes itself // (1b) entry header len // (1b) stack frame len constint kMessageHeaderLen = 15; constint kEntryHeaderLen = 9; constint kStackFrameLen = 8;
Append1BE(bytes, kMessageHeaderLen);
Append1BE(bytes, kEntryHeaderLen);
Append1BE(bytes, kStackFrameLen);
// (2b) number of entries // (4b) offset to string table from start of message // (2b) number of class name strings // (2b) number of method name strings // (2b) number of source file name strings
Append2BE(bytes, capped_count);
size_t string_table_offset = bytes.size();
Append4BE(bytes, 0); // We'll patch this later...
Append2BE(bytes, class_names.Size());
Append2BE(bytes, method_names.Size());
Append2BE(bytes, filenames.Size());
VLOG(jdwp) << "Dumping allocations with stacks";
// Enlarge the vector for the allocation data.
size_t reserve_size = bytes.size() + alloc_byte_count;
bytes.reserve(reserve_size);
std::string temp;
count = capped_count; // The last "count" number of allocation records in "records" are the most recent "count" number // of allocations. Reverse iterate to get them. The most recent allocation is sent first. for (auto it = records->RBegin(), end = records->REnd();
count > 0 && it != end; count--, it++) { // For each entry: // (4b) total allocation size // (2b) thread id // (2b) allocated object's class name index // (1b) stack depth const gc::AllocRecord* record = &it->second;
size_t stack_depth = record->GetDepth();
size_t allocated_object_class_name_index =
class_names.IndexOf(record->GetClassDescriptor(&temp));
Append4BE(bytes, record->ByteCount());
Append2BE(bytes, static_cast<uint16_t>(record->GetTid()));
Append2BE(bytes, allocated_object_class_name_index);
Append1BE(bytes, stack_depth);
for (size_t stack_frame = 0; stack_frame < stack_depth; ++stack_frame) { // For each stack frame: // (2b) method's class name // (2b) method name // (2b) method source file // (2b) line number, clipped to 32767; -2 if native; -1 if no source
ArtMethod* m = record->StackElement(stack_frame).GetMethod();
size_t class_name_index = class_names.IndexOf(m->GetDeclaringClassDescriptor());
size_t method_name_index = method_names.IndexOf(m->GetName());
size_t file_name_index = filenames.IndexOf(GetMethodSourceFile(m));
Append2BE(bytes, class_name_index);
Append2BE(bytes, method_name_index);
Append2BE(bytes, file_name_index);
Append2BE(bytes, record->StackElement(stack_frame).ComputeLineNumber());
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.