Debugger: Rework report generation.
Rather than trying to buffer the entire report in memory and write it in a single shot, write it incrementally as the data is gathered. Fixes an issue reported by Diver with generating crash reports for apps that had crashed due to an infinite recursion stack overflow.
This commit is contained in:
@@ -41,6 +41,14 @@
|
||||
#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)
|
||||
:
|
||||
@@ -121,31 +129,26 @@ DebugReportGenerator::_GenerateReport(const entry_ref& outputPath)
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
BString output;
|
||||
result = _GenerateReportHeader(output);
|
||||
result = _GenerateReportHeader(file);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
result = _DumpRunningThreads(output);
|
||||
result = _DumpRunningThreads(file);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
result = _DumpLoadedImages(output);
|
||||
result = _DumpLoadedImages(file);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
result = _DumpAreas(output);
|
||||
result = _DumpAreas(file);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
result = _DumpSemaphores(output);
|
||||
result = _DumpSemaphores(file);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
result = file.Write(output.String(), output.Length());
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
BPath path(&outputPath);
|
||||
|
||||
fTeam->NotifyDebugReportChanged(path.Path());
|
||||
@@ -223,14 +226,14 @@ DebugReportGenerator::FunctionSourceCodeChanged(Function* function)
|
||||
}
|
||||
|
||||
status_t
|
||||
DebugReportGenerator::_GenerateReportHeader(BString& _output)
|
||||
DebugReportGenerator::_GenerateReportHeader(BFile& _output)
|
||||
{
|
||||
AutoLocker< ::Team> locker(fTeam);
|
||||
|
||||
BString data;
|
||||
data.SetToFormat("Debug information for team %s (%" B_PRId32 "):\n",
|
||||
fTeam->Name(), fTeam->ID());
|
||||
_output << data;
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
|
||||
SystemInfo sysInfo;
|
||||
|
||||
@@ -268,7 +271,8 @@ DebugReportGenerator::_GenerateReportHeader(BString& _output)
|
||||
data.SetToFormat("CPU(s): %" B_PRId32 "x %s %s\n",
|
||||
info.cpu_count, get_cpu_vendor_string(cpuVendor),
|
||||
get_cpu_model_string(platform, cpuVendor, cpuModel));
|
||||
_output << data;
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
|
||||
char maxSize[32];
|
||||
char usedSize[32];
|
||||
|
||||
@@ -277,12 +281,12 @@ DebugReportGenerator::_GenerateReportHeader(BString& _output)
|
||||
maxSize, sizeof(maxSize)),
|
||||
BPrivate::string_for_size((int64)info.used_pages * B_PAGE_SIZE,
|
||||
usedSize, sizeof(usedSize)));
|
||||
_output << data;
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
|
||||
const utsname& name = sysInfo.GetSystemName();
|
||||
data.SetToFormat("Haiku revision: %s (%s)\n", name.version,
|
||||
name.machine);
|
||||
_output << data;
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
}
|
||||
|
||||
delete[] topology;
|
||||
@@ -291,13 +295,13 @@ DebugReportGenerator::_GenerateReportHeader(BString& _output)
|
||||
|
||||
|
||||
status_t
|
||||
DebugReportGenerator::_DumpLoadedImages(BString& _output)
|
||||
DebugReportGenerator::_DumpLoadedImages(BFile& _output)
|
||||
{
|
||||
AutoLocker< ::Team> locker(fTeam);
|
||||
|
||||
_output << "\nLoaded Images:\n";
|
||||
BString data("\nLoaded Images:\n");
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
BObjectList<Image> images;
|
||||
BString data;
|
||||
for (ImageList::ConstIterator it = fTeam->Images().GetIterator();
|
||||
Image* image = it.Next();) {
|
||||
images.AddItem(image);
|
||||
@@ -308,9 +312,11 @@ DebugReportGenerator::_DumpLoadedImages(BString& _output)
|
||||
Image* image = NULL;
|
||||
data.SetToFormat("\tID\t\tText Base\tText End\tData Base\tData"
|
||||
" End\tType\tName\n\t");
|
||||
_output << data;
|
||||
_output.Append('-', 80);
|
||||
_output.Append("\n");
|
||||
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];
|
||||
@@ -325,7 +331,7 @@ DebugReportGenerator::_DumpLoadedImages(BString& _output)
|
||||
UiUtils::ImageTypeToString(info.Type(), buffer,
|
||||
sizeof(buffer)), info.Name().String());
|
||||
|
||||
_output << data;
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
} catch (...) {
|
||||
return B_NO_MEMORY;
|
||||
}
|
||||
@@ -336,7 +342,7 @@ DebugReportGenerator::_DumpLoadedImages(BString& _output)
|
||||
|
||||
|
||||
status_t
|
||||
DebugReportGenerator::_DumpAreas(BString& _output)
|
||||
DebugReportGenerator::_DumpAreas(BFile& _output)
|
||||
{
|
||||
BObjectList<AreaInfo> areas(20, true);
|
||||
status_t result = fDebuggerInterface->GetAreaInfos(areas);
|
||||
@@ -345,12 +351,14 @@ DebugReportGenerator::_DumpAreas(BString& _output)
|
||||
|
||||
areas.SortItems(&_CompareAreas);
|
||||
|
||||
_output << "\nAreas:\n";
|
||||
BString data;
|
||||
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");
|
||||
_output << data;
|
||||
_output.Append('-', 80);
|
||||
_output.Append("\n");
|
||||
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];
|
||||
@@ -365,35 +373,38 @@ DebugReportGenerator::_DumpAreas(BString& _output)
|
||||
UiUtils::AreaLockingFlagsToString(info->Lock(), lockingBuffer,
|
||||
sizeof(lockingBuffer)), info->Name().String());
|
||||
|
||||
_output << data;
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
} catch (...) {
|
||||
return B_NO_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
_output << "\nProtection Flags: r - read, w - write, x - execute, "
|
||||
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(BString& _output)
|
||||
DebugReportGenerator::_DumpSemaphores(BFile& _output)
|
||||
{
|
||||
BObjectList<SemaphoreInfo> semaphores(20, true);
|
||||
status_t result = fDebuggerInterface->GetSemaphoreInfos(semaphores);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
status_t error = fDebuggerInterface->GetSemaphoreInfos(semaphores);
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
|
||||
semaphores.SortItems(&_CompareSemaphores);
|
||||
|
||||
_output << "\nSemaphores:\n";
|
||||
BString data;
|
||||
BString data = "\nSemaphores:\n";
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
data.SetToFormat("\tID\t\tCount\tLast Holder\tName\n\t");
|
||||
_output << data;
|
||||
_output.Append('-', 60);
|
||||
_output.Append("\n");
|
||||
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 {
|
||||
@@ -401,7 +412,7 @@ DebugReportGenerator::_DumpSemaphores(BString& _output)
|
||||
"\t%s\n", info->SemID(), info->Count(),
|
||||
info->LatestHolder(), info->Name().String());
|
||||
|
||||
_output << data;
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
} catch (...) {
|
||||
return B_NO_MEMORY;
|
||||
}
|
||||
@@ -412,13 +423,12 @@ DebugReportGenerator::_DumpSemaphores(BString& _output)
|
||||
|
||||
|
||||
status_t
|
||||
DebugReportGenerator::_DumpRunningThreads(BString& _output)
|
||||
DebugReportGenerator::_DumpRunningThreads(BFile& _output)
|
||||
{
|
||||
AutoLocker< ::Team> locker(fTeam);
|
||||
|
||||
_output << "\nActive Threads:\n";
|
||||
BString data;
|
||||
status_t result = B_OK;
|
||||
BString data("\nActive Threads:\n");
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
BObjectList< ::Thread> threads;
|
||||
::Thread* thread;
|
||||
for (ThreadList::ConstIterator it = fTeam->Threads().GetIterator();
|
||||
@@ -429,10 +439,10 @@ DebugReportGenerator::_DumpRunningThreads(BString& _output)
|
||||
threads.SortItems(&_CompareThreads);
|
||||
for (int32 i = 0; (thread = threads.ItemAt(i)) != NULL; i++) {
|
||||
try {
|
||||
data.SetToFormat("\tthread %" B_PRId32 ": %s %s", thread->ID(),
|
||||
data.SetToFormat("\tthread %" B_PRId32 ": %s %s\n", thread->ID(),
|
||||
thread->Name(), thread->IsMainThread()
|
||||
? "(main)" : "");
|
||||
_output << data << "\n";
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
|
||||
if (thread->State() == THREAD_STATE_STOPPED) {
|
||||
data.SetToFormat("\t\tstate: %s",
|
||||
@@ -441,18 +451,19 @@ DebugReportGenerator::_DumpRunningThreads(BString& _output)
|
||||
const BString& stoppedInfo = thread->StoppedReasonInfo();
|
||||
if (stoppedInfo.Length() != 0)
|
||||
data << " (" << stoppedInfo << ")";
|
||||
_output << data << "\n\n";
|
||||
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.
|
||||
BReference< ::Thread> threadRef(thread);
|
||||
locker.Unlock();
|
||||
result = _DumpDebuggedThreadInfo(_output, thread);
|
||||
status_t error = _DumpDebuggedThreadInfo(_output, thread);
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
locker.Lock();
|
||||
}
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
} catch (...) {
|
||||
return B_NO_MEMORY;
|
||||
}
|
||||
@@ -463,13 +474,14 @@ DebugReportGenerator::_DumpRunningThreads(BString& _output)
|
||||
|
||||
|
||||
status_t
|
||||
DebugReportGenerator::_DumpDebuggedThreadInfo(BString& _output,
|
||||
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();
|
||||
@@ -478,16 +490,19 @@ DebugReportGenerator::_DumpDebuggedThreadInfo(BString& _output,
|
||||
|
||||
locker.Unlock();
|
||||
fTraceWaitingThread = thread;
|
||||
status_t result = acquire_sem(fTeamDataSem);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
error = acquire_sem(fTeamDataSem);
|
||||
if (error == B_INTERRUPTED)
|
||||
continue;
|
||||
else if (error != B_OK)
|
||||
return error;
|
||||
|
||||
locker.Lock();
|
||||
}
|
||||
|
||||
_output << "\t\tFrame\t\tIP\t\t\tFunction Name\n";
|
||||
_output << "\t\t-----------------------------------------------\n";
|
||||
BString data;
|
||||
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;
|
||||
@@ -515,39 +530,48 @@ DebugReportGenerator::_DumpDebuggedThreadInfo(BString& _output,
|
||||
frame, functionName, sizeof(functionName)),
|
||||
sourcePath.String());
|
||||
|
||||
_output << data;
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
|
||||
// only dump the topmost frame
|
||||
if (i == 0) {
|
||||
locker.Unlock();
|
||||
_DumpFunctionDisassembly(_output, frame->InstructionPointer());
|
||||
_DumpStackFrameMemory(_output, thread->GetCpuState(),
|
||||
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;
|
||||
|
||||
_output << "\t\t\tVariables:\n";
|
||||
status_t result = fNodeManager->SetStackFrame(thread, frame);
|
||||
if (result != B_OK)
|
||||
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(_output, child, 3, 1);
|
||||
UiUtils::PrintValueNodeGraph(data, child, 3, 1);
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
}
|
||||
_output << "\n";
|
||||
data = "\n";
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
}
|
||||
|
||||
_output << "\n\t\tRegisters:\n";
|
||||
data = "\n\t\tRegisters:\n";
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
|
||||
CpuState* state = thread->GetCpuState();
|
||||
BVariant value;
|
||||
@@ -559,15 +583,15 @@ DebugReportGenerator::_DumpDebuggedThreadInfo(BString& _output,
|
||||
char buffer[64];
|
||||
data.SetToFormat("\t\t\t%5s:\t%s\n", reg->Name(),
|
||||
UiUtils::VariantToString(value, buffer, sizeof(buffer)));
|
||||
_output << data;
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
DebugReportGenerator::_DumpFunctionDisassembly(BString& _output,
|
||||
status_t
|
||||
DebugReportGenerator::_DumpFunctionDisassembly(BFile& _output,
|
||||
target_addr_t instructionPointer)
|
||||
{
|
||||
AutoLocker< ::Team> teamLocker(fTeam);
|
||||
@@ -579,8 +603,8 @@ DebugReportGenerator::_DumpFunctionDisassembly(BString& _output,
|
||||
if (error != B_OK) {
|
||||
data.SetToFormat("Unable to retrieve disassembly for IP %#" B_PRIx64
|
||||
": %s\n", instructionPointer, strerror(error));
|
||||
_output << data;
|
||||
return;
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
DisassembledCode* code = instance->GetSourceCode();
|
||||
@@ -599,18 +623,20 @@ DebugReportGenerator::_DumpFunctionDisassembly(BString& _output,
|
||||
case FUNCTION_SOURCE_LOADING:
|
||||
{
|
||||
teamLocker.Unlock();
|
||||
error = acquire_sem(fTeamDataSem);
|
||||
do {
|
||||
error = acquire_sem(fTeamDataSem);
|
||||
} while (error == B_INTERRUPTED);
|
||||
if (error != B_OK)
|
||||
return;
|
||||
return error;
|
||||
teamLocker.Lock();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
if (instance->SourceCodeState() == FUNCTION_SOURCE_UNAVAILABLE)
|
||||
return;
|
||||
return B_OK;
|
||||
|
||||
error = fTeam->GetStatementAtAddress(instructionPointer, instance,
|
||||
statement);
|
||||
@@ -619,19 +645,25 @@ DebugReportGenerator::_DumpFunctionDisassembly(BString& _output,
|
||||
|
||||
SourceLocation location = statement->StartSourceLocation();
|
||||
|
||||
_output << "\t\t\tDisassembly:\n";
|
||||
data = "\t\t\tDisassembly:\n";
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
for (int32 i = 0; i <= location.Line(); i++) {
|
||||
_output << "\t\t\t\t" << code->LineAt(i);
|
||||
data = "\t\t\t\t";
|
||||
data << code->LineAt(i);
|
||||
if (i == location.Line())
|
||||
_output << " <--";
|
||||
_output << "\n";
|
||||
data << " <--";
|
||||
data << "\n";
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
}
|
||||
_output << "\n";
|
||||
data = "\n";
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
DebugReportGenerator::_DumpStackFrameMemory(BString& _output,
|
||||
status_t
|
||||
DebugReportGenerator::_DumpStackFrameMemory(BFile& _output,
|
||||
CpuState* state, target_addr_t framePointer, uint8 stackDirection)
|
||||
{
|
||||
target_addr_t startAddress;
|
||||
@@ -644,25 +676,29 @@ DebugReportGenerator::_DumpStackFrameMemory(BString& _output,
|
||||
endAddress = framePointer;
|
||||
}
|
||||
|
||||
status_t error;
|
||||
if (fCurrentBlock == NULL || !fCurrentBlock->Contains(startAddress)) {
|
||||
fListener->InspectRequested(startAddress, this);
|
||||
status_t result = B_OK;
|
||||
error = B_OK;
|
||||
do {
|
||||
result = acquire_sem(fTeamDataSem);
|
||||
} while (result == B_INTERRUPTED);
|
||||
error = acquire_sem(fTeamDataSem);
|
||||
} while (error == B_INTERRUPTED);
|
||||
}
|
||||
|
||||
_output << "\t\t\tFrame memory:\n";
|
||||
BString data("\t\t\tFrame memory:\n");
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
if (fBlockRetrievalStatus == B_OK) {
|
||||
UiUtils::DumpMemory(_output, 4 , fCurrentBlock, startAddress, 1, 16,
|
||||
data.Truncate(0L);
|
||||
UiUtils::DumpMemory(data, 4, fCurrentBlock, startAddress, 1, 16,
|
||||
endAddress - startAddress);
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
} else {
|
||||
BString data;
|
||||
data.SetToFormat("\t\t\t\tUnavailable (%s)\n", strerror(
|
||||
fBlockRetrievalStatus));
|
||||
_output += data;
|
||||
WRITE_AND_CHECK(_output, data);
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
class entry_ref;
|
||||
class Architecture;
|
||||
class AreaInfo;
|
||||
class BString;
|
||||
class BFile;
|
||||
class DebuggerInterface;
|
||||
class SemaphoreInfo;
|
||||
class StackFrame;
|
||||
@@ -65,16 +65,16 @@ private:
|
||||
|
||||
private:
|
||||
status_t _GenerateReport(const entry_ref& outputPath);
|
||||
status_t _GenerateReportHeader(BString& _output);
|
||||
status_t _DumpLoadedImages(BString& _output);
|
||||
status_t _DumpAreas(BString& _output);
|
||||
status_t _DumpSemaphores(BString& _output);
|
||||
status_t _DumpRunningThreads(BString& _output);
|
||||
status_t _DumpDebuggedThreadInfo(BString& _output,
|
||||
status_t _GenerateReportHeader(BFile& _output);
|
||||
status_t _DumpLoadedImages(BFile& _output);
|
||||
status_t _DumpAreas(BFile& _output);
|
||||
status_t _DumpSemaphores(BFile& _output);
|
||||
status_t _DumpRunningThreads(BFile& _output);
|
||||
status_t _DumpDebuggedThreadInfo(BFile& _output,
|
||||
::Thread* thread);
|
||||
void _DumpFunctionDisassembly(BString& _output,
|
||||
status_t _DumpFunctionDisassembly(BFile& _output,
|
||||
target_addr_t instructionPointer);
|
||||
void _DumpStackFrameMemory(BString& _output,
|
||||
status_t _DumpStackFrameMemory(BFile& _output,
|
||||
CpuState* state,
|
||||
target_addr_t framePointer,
|
||||
uint8 stackDirection);
|
||||
|
||||
Reference in New Issue
Block a user