⛏️ index : haiku.git

/*
 * Copyright 2012-2016, Rene Gollent, rene@gollent.com.
 * Distributed under the terms of the MIT License.
 */


#include "DebugReportGenerator.h"

#include <cpu_type.h>

#include <AutoLocker.h>
#include <Entry.h>
#include <File.h>
#include <Path.h>
#include <StringForSize.h>

#include "Architecture.h"
#include "AreaInfo.h"
#include "AutoDeleter.h"
#include "CpuState.h"
#include "DebuggerInterface.h"
#include "DisassembledCode.h"
#include "FunctionInstance.h"
#include "Image.h"
#include "ImageDebugInfo.h"
#include "MessageCodes.h"
#include "Register.h"
#include "SemaphoreInfo.h"
#include "StackFrame.h"
#include "StackTrace.h"
#include "Statement.h"
#include "SystemInfo.h"
#include "Team.h"
#include "TeamDebugInfo.h"
#include "Thread.h"
#include "Type.h"
#include "UiUtils.h"
#include "UserInterface.h"
#include "Value.h"
#include "ValueLoader.h"
#include "ValueLocation.h"
#include "ValueNode.h"
#include "ValueNodeManager.h"


#define WRITE_AND_CHECK(output, data) \
	{ \
		ssize_t error = output.Write(data.String(), data.Length()); \
		if (error < 0) \
			return error; \
	}


DebugReportGenerator::DebugReportGenerator(::Team* team,
	UserInterfaceListener* listener, DebuggerInterface* interface)
	:
	BLooper("DebugReportGenerator"),
	fTeam(team),
	fArchitecture(team->GetArchitecture()),
	fDebuggerInterface(interface),
	fTeamDataSem(-1),
	fNodeManager(NULL),
	fListener(listener),
	fWaitingNode(NULL),
	fCurrentBlock(NULL),
	fBlockRetrievalStatus(B_OK),
	fTraceWaitingThread(NULL),
	fSourceWaitingFunction(NULL)
{
	fTeam->AddListener(this);
	fArchitecture->AcquireReference();
}


DebugReportGenerator::~DebugReportGenerator()
{
	fTeam->RemoveListener(this);
	fArchitecture->ReleaseReference();
	if (fNodeManager != NULL) {
		fNodeManager->RemoveListener(this);
		fNodeManager->ReleaseReference();
	}

	if (fCurrentBlock != NULL)
		fCurrentBlock->ReleaseReference();

	if (fTeamDataSem >= 0)
		delete_sem(fTeamDataSem);
}


status_t
DebugReportGenerator::Init()
{
	fTeamDataSem = create_sem(0, "debug_controller_data_wait");
	if (fTeamDataSem < B_OK)
		return fTeamDataSem;

	fNodeManager = new(std::nothrow) ValueNodeManager();
	if (fNodeManager == NULL)
		return B_NO_MEMORY;

	fNodeManager->AddListener(this);

	Run();

	return B_OK;
}


DebugReportGenerator*
DebugReportGenerator::Create(::Team* team, UserInterfaceListener* listener,
	DebuggerInterface* interface)
{
	DebugReportGenerator* self = new DebugReportGenerator(team, listener,
		interface);

	try {
		self->Init();
	} catch (...) {
		delete self;
		throw;
	}

	return self;
}


status_t
DebugReportGenerator::_GenerateReport(const char* outputPath)
{
	BFile file(outputPath, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
	status_t result = file.InitCheck();
	if (result != B_OK)
		return result;

	result = _GenerateReportHeader(file);
	if (result != B_OK)
		return result;

	result = _DumpRunningThreads(file);
	if (result != B_OK)
		return result;

	result = _DumpLoadedImages(file);
	if (result != B_OK)
		return result;

	result = _DumpAreas(file);
	if (result != B_OK)
		return result;

	result = _DumpSemaphores(file);
	if (result != B_OK)
		return result;

	AutoLocker< ::Team> teamLocker(fTeam);
	fTeam->NotifyDebugReportChanged(outputPath, B_OK);

	return B_OK;
}


void
DebugReportGenerator::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case MSG_GENERATE_DEBUG_REPORT:
		{
			entry_ref ref;
			if (message->FindRef("target", &ref) == B_OK) {
				BPath path(&ref);
				status_t error = _GenerateReport(path.Path());
				if (error != B_OK)
					fTeam->NotifyDebugReportChanged(path.Path(), error);
			}
			break;
		}

		default:
			BLooper::MessageReceived(message);
			break;
	}
}


void
DebugReportGenerator::ThreadStackTraceChanged(const ::Team::ThreadEvent& event)
{
	if (fTraceWaitingThread == event.GetThread()) {
		fTraceWaitingThread = NULL;
		release_sem(fTeamDataSem);
	}
}


void
DebugReportGenerator::MemoryBlockRetrieved(TeamMemoryBlock* block)
{
	_HandleMemoryBlockRetrieved(block, B_OK);
}


void
DebugReportGenerator::MemoryBlockRetrievalFailed(TeamMemoryBlock* block,
	status_t result)
{
	_HandleMemoryBlockRetrieved(block, result);
}


void
DebugReportGenerator::ValueNodeValueChanged(ValueNode* node)
{
	if (node == fWaitingNode) {
		fWaitingNode = NULL;
		release_sem(fTeamDataSem);
	}
}


void
DebugReportGenerator::FunctionSourceCodeChanged(Function* function)
{
	AutoLocker< ::Team> teamLocker(fTeam);
	if (function == fSourceWaitingFunction) {
		function_source_state state = function->SourceCodeState();

		switch (state) {
			case FUNCTION_SOURCE_LOADED:
			case FUNCTION_SOURCE_SUPPRESSED:
			case FUNCTION_SOURCE_UNAVAILABLE:
			{
				fSourceWaitingFunction->RemoveListener(this);
				fSourceWaitingFunction = NULL;
				release_sem(fTeamDataSem);
				// fall through
			}
			default:
				break;
		}
	}
}

status_t
DebugReportGenerator::_GenerateReportHeader(BFile& _output)
{
	AutoLocker< ::Team> locker(fTeam);

	BString data;
	data.SetToFormat("Debug information for team %s (%" B_PRId32 "):\n",
		fTeam->Name(), fTeam->ID());
	WRITE_AND_CHECK(_output, data);

	cpu_platform platform = B_CPU_UNKNOWN;
	cpu_vendor cpuVendor = B_CPU_VENDOR_UNKNOWN;
	uint32 cpuModel = 0;
	uint32 topologyNodeCount = 0;
	cpu_topology_node_info* topology = NULL;
	get_cpu_topology_info(NULL, &topologyNodeCount);
	if (topologyNodeCount != 0) {
		topology = new(std::nothrow) cpu_topology_node_info[topologyNodeCount];
		if (topology == NULL)
			return B_NO_MEMORY;

		BPrivate::ArrayDeleter<cpu_topology_node_info> deleter(topology);
		get_cpu_topology_info(topology, &topologyNodeCount);

		for (uint32 i = 0; i < topologyNodeCount; i++) {
			switch (topology[i].type) {
				case B_TOPOLOGY_ROOT:
					platform = topology[i].data.root.platform;
					break;

				case B_TOPOLOGY_PACKAGE:
					cpuVendor = topology[i].data.package.vendor;
					break;

				case B_TOPOLOGY_CORE:
					cpuModel = topology[i].data.core.model;
					break;

				default:
					break;
			}
		}
	}

	SystemInfo sysInfo;
	if (fDebuggerInterface->GetSystemInfo(sysInfo) == B_OK) {
		const system_info &info = sysInfo.GetSystemInfo();
		const char* vendor = get_cpu_vendor_string(cpuVendor);
		const char* model = get_cpu_model_string(platform, cpuVendor, cpuModel);

		data.SetToFormat("CPU(s): %" B_PRId32 "x %s %s\n",
			info.cpu_count, vendor != NULL ? vendor : "unknown",
			model != NULL ? model : "unknown");
		WRITE_AND_CHECK(_output, data);

		char maxSize[32];
		char usedSize[32];

		data.SetToFormat("Memory: %s total, %s used\n",
			BPrivate::string_for_size((int64)info.max_pages * B_PAGE_SIZE,
				maxSize, sizeof(maxSize)),
			BPrivate::string_for_size((int64)info.used_pages * B_PAGE_SIZE,
				usedSize, sizeof(usedSize)));
		WRITE_AND_CHECK(_output, data);

		const utsname& name = sysInfo.GetSystemName();
		data.SetToFormat("Haiku revision: %s (%s)\n", name.version,
			name.machine);
		WRITE_AND_CHECK(_output, data);
	}

	return B_OK;
}


status_t
DebugReportGenerator::_DumpLoadedImages(BFile& _output)
{
	AutoLocker< ::Team> locker(fTeam);

	BString data("\nLoaded Images:\n");
	WRITE_AND_CHECK(_output, data);
	BObjectList<Image> images;
	for (ImageList::ConstIterator it = fTeam->Images().GetIterator();
		 Image* image = it.Next();) {
		 images.AddItem(image);
	}

	images.SortItems(&_CompareImages);

	Image* image = NULL;
	data.SetToFormat("\tID\t\tText Base\tText End\tData Base\tData"
		" End\tType\tName\n\t");
	WRITE_AND_CHECK(_output, data);
	data.Truncate(0L);
	data.Append('-', 80);
	data.Append("\n");
	WRITE_AND_CHECK(_output, data);
	for (int32 i = 0; (image = images.ItemAt(i)) != NULL; i++) {
		const ImageInfo& info = image->Info();
		char buffer[32];
		try {
			target_addr_t textBase = info.TextBase();
			target_addr_t dataBase = info.DataBase();

			data.SetToFormat("\t%" B_PRId32 "\t0x%08" B_PRIx64 "\t"
				"0x%08" B_PRIx64 "\t0x%08" B_PRIx64 "\t0x%08" B_PRIx64 "\t"
				"%-7s\t%s\n", info.ImageID(), textBase, textBase + info.TextSize(),
				dataBase, dataBase + info.DataSize(),
				UiUtils::ImageTypeToString(info.Type(), buffer,
					sizeof(buffer)), info.Name().String());

			WRITE_AND_CHECK(_output, data);
		} catch (...) {
			return B_NO_MEMORY;
		}
	}

	return B_OK;
}


status_t
DebugReportGenerator::_DumpAreas(BFile& _output)
{
	BObjectList<AreaInfo, true> areas(20);
	status_t result = fDebuggerInterface->GetAreaInfos(areas);
	if (result != B_OK)
		return result;

	areas.SortItems(&_CompareAreas);

	BString data("\nAreas:\n");
	WRITE_AND_CHECK(_output, data);
	data.SetToFormat("\tID\t\tBase\t\tEnd\t\t\tSize (KiB)\tProtection\tLocking\t\t\tName\n\t");
	WRITE_AND_CHECK(_output, data);
	data.Truncate(0L);
	data.Append('-', 80);
	data.Append("\n");
	WRITE_AND_CHECK(_output, data);
	AreaInfo* info;
	BString protectionBuffer;
	char lockingBuffer[32];
	for (int32 i = 0; (info = areas.ItemAt(i)) != NULL; i++) {
		try {
			data.SetToFormat("\t%" B_PRId32 "\t0x%08" B_PRIx64 "\t"
				"0x%08" B_PRIx64 "\t%10" B_PRId64 "\t%-11s\t%-14s\t%s\n",
				info->AreaID(), info->BaseAddress(), info->BaseAddress()
					+ info->Size(), info->Size() / 1024,
				UiUtils::AreaProtectionFlagsToString(info->Protection(),
					protectionBuffer).String(),
				UiUtils::AreaLockingFlagsToString(info->Lock(), lockingBuffer,
					sizeof(lockingBuffer)), info->Name().String());

			WRITE_AND_CHECK(_output, data);
		} catch (...) {
			return B_NO_MEMORY;
		}
	}

	data = "\nProtection Flags: r - read, w - write, x - execute, "
		"s - stack, o - overcommit, c - cloneable, S - shared, k - kernel\n";
	WRITE_AND_CHECK(_output, data);

	return B_OK;
}


status_t
DebugReportGenerator::_DumpSemaphores(BFile& _output)
{
	BObjectList<SemaphoreInfo, true> semaphores(20);
	status_t error = fDebuggerInterface->GetSemaphoreInfos(semaphores);
	if (error != B_OK)
		return error;

	semaphores.SortItems(&_CompareSemaphores);

	BString data = "\nSemaphores:\n";
	WRITE_AND_CHECK(_output, data);
	data.SetToFormat("\tID\t\tCount\tLast Holder\tName\n\t");
	WRITE_AND_CHECK(_output, data);
	data.Truncate(0L);
	data.Append('-', 60);
	data.Append("\n");
	WRITE_AND_CHECK(_output, data);
	SemaphoreInfo* info;
	for (int32 i = 0; (info = semaphores.ItemAt(i)) != NULL; i++) {
		try {
			data.SetToFormat("\t%" B_PRId32 "\t%5" B_PRId32 "\t%11" B_PRId32
				"\t%s\n", info->SemID(), info->Count(),
				info->LatestHolder(), info->Name().String());

			WRITE_AND_CHECK(_output, data);
		} catch (...) {
			return B_NO_MEMORY;
		}
	}

	return B_OK;
}


status_t
DebugReportGenerator::_DumpRunningThreads(BFile& _output)
{
	AutoLocker< ::Team> locker(fTeam);

	BString data("\nActive Threads:\n");
	WRITE_AND_CHECK(_output, data);
	BObjectList< ::Thread> threads;
	::Thread* thread;
	for (ThreadList::ConstIterator it = fTeam->Threads().GetIterator();
		  (thread = it.Next());) {
		 threads.AddItem(thread);
		 thread->AcquireReference();
	}

	status_t error = B_OK;
	threads.SortItems(&_CompareThreads);
	for (int32 i = 0; (thread = threads.ItemAt(i)) != NULL; i++) {
		try {
			data.SetToFormat("\tthread %" B_PRId32 ": %s %s\n", thread->ID(),
					thread->Name(), thread->IsMainThread()
						? "(main)" : "");
			WRITE_AND_CHECK(_output, data);

			if (thread->State() == THREAD_STATE_STOPPED) {
				data.SetToFormat("\t\tstate: %s",
					UiUtils::ThreadStateToString(thread->State(),
							thread->StoppedReason()));
				const BString& stoppedInfo = thread->StoppedReasonInfo();
				if (stoppedInfo.Length() != 0)
					data << " (" << stoppedInfo << ")";
				data << "\n\n";
				WRITE_AND_CHECK(_output, data);

				// we need to release our lock on the team here
				// since we might need to block and wait
				// on the stack trace.
				locker.Unlock();
				error = _DumpDebuggedThreadInfo(_output, thread);
				locker.Lock();
				if (error != B_OK)
					break;
			}
		} catch (...) {
			error = B_NO_MEMORY;
		}
	}

	for (int32 i = 0; (thread = threads.ItemAt(i)) != NULL; i++)
		thread->ReleaseReference();

	return error;
}


status_t
DebugReportGenerator::_DumpDebuggedThreadInfo(BFile& _output,
	::Thread* thread)
{
	AutoLocker< ::Team> locker;
	if (thread->State() != THREAD_STATE_STOPPED)
		return B_OK;

	status_t error;
	StackTrace* trace = NULL;
	for (;;) {
		trace = thread->GetStackTrace();
		if (trace != NULL)
			break;

		locker.Unlock();
		fTraceWaitingThread = thread;
		do {
			error = acquire_sem(fTeamDataSem);
		} while (error == B_INTERRUPTED);

		if (error != B_OK)
			return error;

		locker.Lock();
	}

	BString data("\t\tFrame\t\tIP\t\t\tFunction Name\n");
	WRITE_AND_CHECK(_output, data);
	data = "\t\t-----------------------------------------------\n";
	WRITE_AND_CHECK(_output, data);
	for (int32 i = 0; StackFrame* frame = trace->FrameAt(i); i++) {
		char functionName[512];
		BString sourcePath;

		target_addr_t ip = frame->InstructionPointer();
		Image* image = fTeam->ImageByAddress(ip);
		FunctionInstance* functionInstance = NULL;
		if (image != NULL && image->ImageDebugInfoState()
				== IMAGE_DEBUG_INFO_LOADED) {
			ImageDebugInfo* info = image->GetImageDebugInfo();
			functionInstance = info->FunctionAtAddress(ip);
		}

		if (functionInstance != NULL) {
			Function* function = functionInstance->GetFunction();
			if (function->SourceCodeState() == FUNCTION_SOURCE_NOT_LOADED
				&& functionInstance->SourceCodeState()
					== FUNCTION_SOURCE_NOT_LOADED) {
				fSourceWaitingFunction = function;
				fSourceWaitingFunction->AddListener(this);
				fListener->FunctionSourceCodeRequested(functionInstance);

				locker.Unlock();

				do {
					error = acquire_sem(fTeamDataSem);
				} while (error == B_INTERRUPTED);

				if (error != B_OK)
					return error;

				locker.Lock();
			}
		}

		Statement* statement;
		if (fTeam->GetStatementAtAddress(ip,
				functionInstance, statement) == B_OK) {
			BReference<Statement> statementReference(statement, true);

			int32 line = statement->StartSourceLocation().Line();
			LocatableFile* sourceFile = functionInstance->GetFunction()
				->SourceFile();
			if (sourceFile != NULL) {
				sourceFile->GetPath(sourcePath);
				sourcePath.SetToFormat("(%s:%" B_PRId32 ")",
					sourcePath.String(), line);
			}
		}


		data.SetToFormat("\t\t%#08" B_PRIx64 "\t%#08" B_PRIx64 "\t%s %s\n",
			frame->FrameAddress(), ip, UiUtils::FunctionNameForFrame(
				frame, functionName, sizeof(functionName)),
				sourcePath.String());

		WRITE_AND_CHECK(_output, data);

		// only dump the topmost frame
		if (i == 0) {
			locker.Unlock();
			error = _DumpFunctionDisassembly(_output,
				frame->InstructionPointer());
			if (error != B_OK)
				return error;
			error = _DumpStackFrameMemory(_output, thread->GetCpuState(),
				frame->FrameAddress(), thread->GetTeam()->GetArchitecture()
					->StackGrowthDirection());
			if (error != B_OK)
				return error;
			locker.Lock();
		}

		if (frame->CountParameters() == 0 && frame->CountLocalVariables() == 0)
			continue;

		data = "\t\t\tVariables:\n";
		WRITE_AND_CHECK(_output, data);
		error = fNodeManager->SetStackFrame(thread, frame);
		if (error != B_OK)
			continue;

		ValueNodeContainer* container = fNodeManager->GetContainer();
		AutoLocker<ValueNodeContainer> containerLocker(container);
		for (int32 i = 0; i < container->CountChildren(); i++) {
			data.Truncate(0L);
			ValueNodeChild* child = container->ChildAt(i);
			containerLocker.Unlock();
			_ResolveValueIfNeeded(child->Node(), frame, 1);
			containerLocker.Lock();
			UiUtils::PrintValueNodeGraph(data, child, 3, 1);
			WRITE_AND_CHECK(_output, data);
		}
		data = "\n";
		WRITE_AND_CHECK(_output, data);
	}

	data = "\n\t\tRegisters:\n";
	WRITE_AND_CHECK(_output, data);

	CpuState* state = thread->GetCpuState();
	BVariant value;
	const Register* reg = NULL;
	for (int32 i = 0; i < fArchitecture->CountRegisters(); i++) {
		reg = fArchitecture->Registers() + i;
		state->GetRegisterValue(reg, value);

		if (reg->Format() == REGISTER_FORMAT_SIMD) {
			data.SetToFormat("\t\t\t%5s:\t%s\n", reg->Name(),
				UiUtils::FormatSIMDValue(value, reg->BitSize(),
					SIMD_RENDER_FORMAT_INT16, data).String());
		} else {
			char buffer[64];
			data.SetToFormat("\t\t\t%5s:\t%s\n", reg->Name(),
				UiUtils::VariantToString(value, buffer, sizeof(buffer)));
		}

		WRITE_AND_CHECK(_output, data);
	}

	return B_OK;
}


status_t
DebugReportGenerator::_DumpFunctionDisassembly(BFile& _output,
	target_addr_t instructionPointer)
{
	AutoLocker< ::Team> teamLocker(fTeam);
	BString data;
	FunctionInstance* instance = NULL;
	Image* image = fTeam->ImageByAddress(instructionPointer);
	if (image == NULL) {
		data.SetToFormat("\t\t\tUnable to retrieve disassembly for IP %#"
			B_PRIx64 ": address not contained in any valid image.\n",
			instructionPointer);
		WRITE_AND_CHECK(_output, data);
		return B_OK;
	}

	ImageDebugInfo* info = image->GetImageDebugInfo();
	if (info == NULL) {
		data.SetToFormat("\t\t\tUnable to retrieve disassembly for IP %#"
			B_PRIx64 ": no debug info available for image '%s'.\n",
			instructionPointer,	image->Name().String());
		WRITE_AND_CHECK(_output, data);
		return B_OK;
	}

	instance = info->FunctionAtAddress(instructionPointer);
	if (instance == NULL) {
		data.SetToFormat("\t\t\tUnable to retrieve disassembly for IP %#"
			B_PRIx64 ": address does not point to a function.\n",
			instructionPointer);
		WRITE_AND_CHECK(_output, data);
		return B_OK;
	}

	Statement* statement = NULL;
	DisassembledCode* code = instance->GetSourceCode();
	BReference<DisassembledCode> codeReference;
	if (code == NULL) {
		status_t error = fTeam->DebugInfo()->DisassembleFunction(instance,
			code);
		if (error != B_OK) {
			data.SetToFormat("\t\t\tUnable to retrieve disassembly for IP %#"
				B_PRIx64 ": %s.\n", instructionPointer, strerror(error));
			WRITE_AND_CHECK(_output, data);
			return B_OK;
		}

		codeReference.SetTo(code, true);
	} else
		codeReference.SetTo(code);

	statement = code->StatementAtAddress(instructionPointer);
	if (statement == NULL) {
		data.SetToFormat("\t\t\tUnable to retrieve disassembly for IP %#"
			B_PRIx64 ": address does not map to a valid instruction.\n",
			instructionPointer);
		WRITE_AND_CHECK(_output, data);
		return B_OK;
	}

	SourceLocation location = statement->StartSourceLocation();

	data = "\t\t\tDisassembly:\n";
	WRITE_AND_CHECK(_output, data);
	for (int32 i = 0; i <= location.Line(); i++) {
		data = "\t\t\t\t";
		data << code->LineAt(i);
		if (i == location.Line())
			data << " <--";
		data << "\n";
		WRITE_AND_CHECK(_output, data);
	}
	data = "\n";
	WRITE_AND_CHECK(_output, data);

	return B_OK;
}


status_t
DebugReportGenerator::_DumpStackFrameMemory(BFile& _output,
	CpuState* state, target_addr_t framePointer, uint8 stackDirection)
{
	target_addr_t startAddress;
	target_addr_t endAddress;
	if (stackDirection == STACK_GROWTH_DIRECTION_POSITIVE) {
		startAddress = framePointer;
		endAddress = state->StackPointer();
	} else {
		startAddress = state->StackPointer();
		endAddress = framePointer;
	}

	if (endAddress <= startAddress)
		return B_OK;

	if (fCurrentBlock == NULL || !fCurrentBlock->Contains(startAddress)) {
		status_t error;
		fListener->InspectRequested(startAddress, this);
		do {
			error = acquire_sem(fTeamDataSem);
		} while (error == B_INTERRUPTED);

		if (error != B_OK)
			return error;
	}

	BString data("\t\t\tFrame memory:\n");
	WRITE_AND_CHECK(_output, data);
	if (fBlockRetrievalStatus == B_OK) {
		data.Truncate(0L);
		UiUtils::DumpMemory(data, 4, fCurrentBlock, startAddress, 1, 16,
			endAddress - startAddress);
		WRITE_AND_CHECK(_output, data);
	} else {
		data.SetToFormat("\t\t\t\tUnavailable (%s)\n", strerror(
				fBlockRetrievalStatus));
		WRITE_AND_CHECK(_output, data);
	}

	return B_OK;
}


status_t
DebugReportGenerator::_ResolveValueIfNeeded(ValueNode* node, StackFrame* frame,
	int32 maxDepth)
{
	status_t result = B_OK;
	if (node->LocationAndValueResolutionState() == VALUE_NODE_UNRESOLVED) {
		fWaitingNode = node;
		fListener->ValueNodeValueRequested(frame->GetCpuState(),
			fNodeManager->GetContainer(), node);
		do {
			result = acquire_sem(fTeamDataSem);
		} while (result == B_INTERRUPTED);
	}

	if (node->LocationAndValueResolutionState() == B_OK && maxDepth > 0) {
		AutoLocker<ValueNodeContainer> containerLocker(
			fNodeManager->GetContainer());
		for (int32 i = 0; i < node->CountChildren(); i++) {
			ValueNodeChild* child = node->ChildAt(i);
			containerLocker.Unlock();
			result = fNodeManager->AddChildNodes(child);
			if (result != B_OK)
				continue;

			// since in the case of a pointer to a compound we hide
			// the intervening compound, don't consider the hidden node
			// a level for the purposes of depth traversal
			if (node->GetType()->ResolveRawType(false)->Kind() == TYPE_ADDRESS
				&& child->GetType()->ResolveRawType(false)->Kind()
					== TYPE_COMPOUND) {
				_ResolveValueIfNeeded(child->Node(), frame, maxDepth);
			} else
				_ResolveValueIfNeeded(child->Node(), frame, maxDepth - 1);
			containerLocker.Lock();
		}
	}

	return result;
}


void
DebugReportGenerator::_HandleMemoryBlockRetrieved(TeamMemoryBlock* block,
	status_t result)
{
	if (fCurrentBlock != NULL) {
		fCurrentBlock->ReleaseReference();
		fCurrentBlock = NULL;
	}

	fBlockRetrievalStatus = result;

	fCurrentBlock = block;
	release_sem(fTeamDataSem);
}



/*static*/ int
DebugReportGenerator::_CompareAreas(const AreaInfo* a, const AreaInfo* b)
{
	if (a->BaseAddress() < b->BaseAddress())
		return -1;

	return 1;
}


/*static*/ int
DebugReportGenerator::_CompareImages(const Image* a, const Image* b)
{
	if (a->Info().TextBase() < b->Info().TextBase())
		return -1;

	return 1;
}


/*static*/ int
DebugReportGenerator::_CompareSemaphores(const SemaphoreInfo* a,
	const SemaphoreInfo* b)
{
	if (a->SemID() < b->SemID())
		return -1;

	return 1;
}


/*static*/ int
DebugReportGenerator::_CompareThreads(const ::Thread* a,
	const ::Thread* b)
{
	// sort stopped threads last, otherwise sort by thread ID
	if (a->State() == b->State())
		return a->ID() < b->ID() ? -1 : 1;

	if (a->State() == THREAD_STATE_STOPPED && b->State()
			!= THREAD_STATE_STOPPED) {
		return 1;
	}

	return -1;
}