You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
646 lines
18 KiB
646 lines
18 KiB
/***************************************************************************
|
|
begin : Sun Aug 8 1999
|
|
copyright : (C) 1999 by John Birch
|
|
email : jbb@tdevelop.org
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "framestackwidget.h"
|
|
#include "gdbparser.h"
|
|
#include "gdbcommand.h"
|
|
|
|
#include <klocale.h>
|
|
#include <kdebug.h>
|
|
#include <kglobalsettings.h>
|
|
|
|
#include <tqheader.h>
|
|
#include <tqlistbox.h>
|
|
#include <tqregexp.h>
|
|
#include <tqstrlist.h>
|
|
#include <tqpainter.h>
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
|
|
/***************************************************************************/
|
|
/***************************************************************************/
|
|
/***************************************************************************/
|
|
|
|
namespace GDBDebugger
|
|
{
|
|
|
|
FramestackWidget::FramestackWidget(GDBController* controller,
|
|
TQWidget *parent,
|
|
const char *name, WFlags f)
|
|
: TQListView(parent, name, f),
|
|
viewedThread_(0),
|
|
controller_(controller),
|
|
mayUpdate_( false )
|
|
{
|
|
setRootIsDecorated(true);
|
|
setSorting(-1);
|
|
setSelectionMode(Single);
|
|
addColumn(TQString()); // Frame number
|
|
addColumn(TQString()); // function name/address
|
|
addColumn(TQString()); // source
|
|
header()->hide();
|
|
|
|
|
|
// FIXME: maybe, all debugger components should derive from
|
|
// a base class that does this connect.
|
|
connect(controller, TQT_SIGNAL(event(GDBController::event_t)),
|
|
this, TQT_SLOT(slotEvent(GDBController::event_t)));
|
|
|
|
connect( this, TQT_SIGNAL(clicked(TQListViewItem*)),
|
|
this, TQT_SLOT(slotSelectionChanged(TQListViewItem*)) );
|
|
}
|
|
|
|
|
|
/***************************************************************************/
|
|
|
|
FramestackWidget::~FramestackWidget()
|
|
{}
|
|
|
|
/***************************************************************************/
|
|
|
|
TQListViewItem *FramestackWidget::lastChild() const
|
|
{
|
|
TQListViewItem* child = firstChild();
|
|
if (child)
|
|
while (TQListViewItem* nextChild = child->nextSibling())
|
|
child = nextChild;
|
|
|
|
return child;
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void FramestackWidget::clear()
|
|
{
|
|
viewedThread_ = 0;
|
|
|
|
TQListView::clear();
|
|
}
|
|
|
|
/***************************************************************************/
|
|
|
|
void FramestackWidget::slotSelectionChanged(TQListViewItem *thisItem)
|
|
{
|
|
ThreadStackItem *thread = dynamic_cast<ThreadStackItem*> (thisItem);
|
|
if (thread)
|
|
{
|
|
controller_->selectFrame(0, thread->threadNo());
|
|
}
|
|
else
|
|
{
|
|
FrameStackItem *frame = dynamic_cast<FrameStackItem*> (thisItem);
|
|
if (frame)
|
|
{
|
|
if (frame->text(0) == "...")
|
|
{
|
|
// Switch to the target thread.
|
|
if (frame->threadNo() != -1)
|
|
controller_->addCommand(
|
|
new GDBCommand(TQString("-thread-select %1")
|
|
.arg(frame->threadNo()).ascii()));
|
|
|
|
viewedThread_ = findThread(frame->threadNo());
|
|
getBacktrace(frame->frameNo(), frame->frameNo() + frameChunk_);
|
|
}
|
|
else
|
|
{
|
|
controller_->
|
|
selectFrame(frame->frameNo(), frame->threadNo());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************/
|
|
|
|
void FramestackWidget::slotEvent(GDBController::event_t e)
|
|
{
|
|
switch(e)
|
|
{
|
|
case GDBController::program_state_changed:
|
|
|
|
kdDebug(9012) << "Clearning framestack\n";
|
|
clear();
|
|
|
|
if ( isVisible() )
|
|
{
|
|
controller_->addCommand(
|
|
new GDBCommand("-thread-list-ids",
|
|
this, &FramestackWidget::handleThreadList));
|
|
mayUpdate_ = false;
|
|
}
|
|
else mayUpdate_ = true;
|
|
|
|
break;
|
|
|
|
|
|
case GDBController::thread_or_frame_changed:
|
|
|
|
if (viewedThread_)
|
|
{
|
|
// For non-threaded programs frame switch is no-op
|
|
// as far as framestack is concerned.
|
|
// FIXME: but need to highlight the current frame.
|
|
|
|
if (ThreadStackItem* item
|
|
= findThread(controller_->currentThread()))
|
|
{
|
|
viewedThread_ = item;
|
|
|
|
if (!item->firstChild())
|
|
{
|
|
// No backtrace for this thread yet.
|
|
getBacktrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case GDBController::program_exited:
|
|
case GDBController::debugger_exited:
|
|
{
|
|
clear();
|
|
break;
|
|
}
|
|
case GDBController::debugger_busy:
|
|
case GDBController::debugger_ready:
|
|
case GDBController::shared_library_loaded:
|
|
case GDBController::program_running:
|
|
case GDBController::connected_to_program:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void FramestackWidget::showEvent(TQShowEvent*)
|
|
{
|
|
if (controller_->stateIsOn(s_appRunning|s_dbgBusy|s_dbgNotStarted|s_shuttingDown))
|
|
return;
|
|
|
|
if ( mayUpdate_ )
|
|
{
|
|
clear();
|
|
|
|
controller_->addCommand(
|
|
new GDBCommand( "-thread-list-ids", this, &FramestackWidget::handleThreadList ) );
|
|
|
|
mayUpdate_ = false;
|
|
}
|
|
}
|
|
|
|
void FramestackWidget::getBacktrace(int min_frame, int max_frame)
|
|
{
|
|
minFrame_ = min_frame;
|
|
maxFrame_ = max_frame;
|
|
|
|
controller_->addCommand(
|
|
new GDBCommand(TQString("-stack-info-depth %1").arg(max_frame+1),
|
|
this,
|
|
&FramestackWidget::handleStackDepth));
|
|
}
|
|
|
|
void FramestackWidget::handleStackDepth(const GDBMI::ResultRecord& r)
|
|
{
|
|
int existing_frames = r["depth"].literal().toInt();
|
|
|
|
has_more_frames = (existing_frames > maxFrame_);
|
|
|
|
if (existing_frames < maxFrame_)
|
|
maxFrame_ = existing_frames;
|
|
//add the following command to the front, so noone switches threads in between
|
|
controller_->addCommandToFront(
|
|
new GDBCommand(TQString("-stack-list-frames %1 %2")
|
|
.arg(minFrame_).arg(maxFrame_),
|
|
this, &FramestackWidget::parseGDBBacktraceList));
|
|
}
|
|
|
|
void FramestackWidget::getBacktraceForThread(int threadNo)
|
|
{
|
|
unsigned currentThread = controller_->currentThread();
|
|
if (viewedThread_)
|
|
{
|
|
// Switch to the target thread.
|
|
controller_->addCommand(
|
|
new GDBCommand(TQString("-thread-select %1")
|
|
.arg(threadNo).ascii()));
|
|
|
|
viewedThread_ = findThread(threadNo);
|
|
}
|
|
|
|
getBacktrace();
|
|
|
|
if (viewedThread_)
|
|
{
|
|
// Switch back to the original thread.
|
|
controller_->addCommand(
|
|
new GDBCommand(TQString("-thread-select %1")
|
|
.arg(currentThread).ascii()));
|
|
}
|
|
}
|
|
|
|
void FramestackWidget::handleThreadList(const GDBMI::ResultRecord& r)
|
|
{
|
|
// Gdb reply is:
|
|
// ^done,thread-ids={thread-id="3",thread-id="2",thread-id="1"},
|
|
// which syntactically is a tuple, but one has to access it
|
|
// by index anyway.
|
|
const GDBMI::TupleValue& ids =
|
|
dynamic_cast<const GDBMI::TupleValue&>(r["thread-ids"]);
|
|
|
|
if (ids.results.size() > 1)
|
|
{
|
|
// Need to iterate over all threads to figure out where each one stands.
|
|
// Note that this sequence of command will be executed in strict
|
|
// sequences, so no other view can add its command in between and
|
|
// get state for a wrong thread.
|
|
|
|
// Really threaded program.
|
|
for(unsigned i = 0, e = ids.results.size(); i != e; ++i)
|
|
{
|
|
TQString id = ids.results[i]->value->literal();
|
|
|
|
controller_->addCommand(
|
|
new GDBCommand(TQString("-thread-select %1").arg(id).ascii(),
|
|
this, &FramestackWidget::handleThread));
|
|
}
|
|
|
|
controller_->addCommand(
|
|
new GDBCommand(TQString("-thread-select %1")
|
|
.arg(controller_->currentThread()).ascii()));
|
|
}
|
|
|
|
// Get backtrace for the current thread. We need to do this
|
|
// here, and not in event handler for program_state_changed,
|
|
// viewedThread_ is initialized by 'handleThread' before
|
|
// backtrace handler is called.
|
|
getBacktrace();
|
|
}
|
|
|
|
void FramestackWidget::handleThread(const GDBMI::ResultRecord& r)
|
|
{
|
|
TQString id = r["new-thread-id"].literal();
|
|
int id_num = id.toInt();
|
|
|
|
TQString name_column;
|
|
TQString func_column;
|
|
TQString args_column;
|
|
TQString source_column;
|
|
|
|
formatFrame(r["frame"], func_column, source_column);
|
|
|
|
ThreadStackItem* thread = new ThreadStackItem(this, id_num);
|
|
thread->setText(1, func_column);
|
|
thread->setText(2, source_column);
|
|
|
|
// The thread with a '*' is always the viewedthread
|
|
|
|
if (id_num == controller_->currentThread())
|
|
{
|
|
viewedThread_ = thread;
|
|
setSelected(viewedThread_, true);
|
|
}
|
|
}
|
|
|
|
|
|
void FramestackWidget::parseGDBBacktraceList(const GDBMI::ResultRecord& r)
|
|
{
|
|
if (!r.hasField("stack"))
|
|
return;
|
|
|
|
const GDBMI::Value& frames = r["stack"];
|
|
|
|
if (frames.empty())
|
|
return;
|
|
|
|
Q_ASSERT(dynamic_cast<const GDBMI::ListValue*>(&frames));
|
|
|
|
// Remove "..." item, if there's one.
|
|
TQListViewItem* last;
|
|
if (viewedThread_)
|
|
{
|
|
last = viewedThread_->firstChild();
|
|
if (last)
|
|
while(last->nextSibling())
|
|
last = last->nextSibling();
|
|
}
|
|
else
|
|
{
|
|
last = lastItem();
|
|
}
|
|
if (last && last->text(0) == "...")
|
|
delete last;
|
|
|
|
int lastLevel;
|
|
for(unsigned i = 0, e = frames.size(); i != e; ++i)
|
|
{
|
|
const GDBMI::Value& frame = frames[i];
|
|
|
|
// For now, just produce string simular to gdb
|
|
// console output. In future we might have a table,
|
|
// or something better.
|
|
TQString frameDesc;
|
|
|
|
TQString name_column;
|
|
TQString func_column;
|
|
TQString source_column;
|
|
|
|
TQString level_s = frame["level"].literal();
|
|
int level = level_s.toInt();
|
|
|
|
name_column = "#" + level_s;
|
|
|
|
formatFrame(frame, func_column, source_column);
|
|
|
|
FrameStackItem* item;
|
|
if (viewedThread_)
|
|
item = new FrameStackItem(viewedThread_, level, name_column);
|
|
else
|
|
item = new FrameStackItem(this, level, name_column);
|
|
lastLevel = level;
|
|
|
|
item->setText(1, func_column);
|
|
item->setText(2, source_column);
|
|
}
|
|
if (has_more_frames)
|
|
{
|
|
TQListViewItem* item;
|
|
if (viewedThread_)
|
|
item = new FrameStackItem(viewedThread_, lastLevel+1, "...");
|
|
else
|
|
item = new FrameStackItem(this, lastLevel+1, "...");
|
|
item->setText(1, "(click to get more frames)");
|
|
}
|
|
|
|
currentFrame_ = 0;
|
|
// Make sure the first frame in the stopped backtrace is selected
|
|
// and open
|
|
if (viewedThread_)
|
|
viewedThread_->setOpen(true);
|
|
else
|
|
{
|
|
if (FrameStackItem* frame = (FrameStackItem*) firstChild())
|
|
{
|
|
frame->setOpen(true);
|
|
setSelected(frame, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
ThreadStackItem *FramestackWidget::findThread(int threadNo)
|
|
{
|
|
TQListViewItem *sibling = firstChild();
|
|
while (sibling)
|
|
{
|
|
ThreadStackItem *thread = dynamic_cast<ThreadStackItem*> (sibling);
|
|
if (thread && thread->threadNo() == threadNo)
|
|
{
|
|
return thread;
|
|
}
|
|
sibling = sibling->nextSibling();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
FrameStackItem *FramestackWidget::findFrame(int frameNo, int threadNo)
|
|
{
|
|
TQListViewItem* frameItem = 0;
|
|
|
|
if (threadNo != -1)
|
|
{
|
|
ThreadStackItem *thread = findThread(threadNo);
|
|
if (thread == 0)
|
|
return 0; // no matching thread?
|
|
frameItem = thread->firstChild();
|
|
}
|
|
else
|
|
frameItem = firstChild();
|
|
|
|
while (frameItem)
|
|
{
|
|
if (((FrameStackItem*)frameItem)->frameNo() == frameNo)
|
|
break;
|
|
|
|
frameItem = frameItem->nextSibling();
|
|
}
|
|
return (FrameStackItem*)frameItem;
|
|
}
|
|
|
|
void FramestackWidget::formatFrame(const GDBMI::Value& frame,
|
|
TQString& func_column,
|
|
TQString& source_column)
|
|
{
|
|
func_column = source_column = "";
|
|
|
|
if (frame.hasField("func"))
|
|
{
|
|
func_column += " " + frame["func"].literal();
|
|
}
|
|
else
|
|
{
|
|
func_column += " " + frame["address"].literal();
|
|
}
|
|
|
|
|
|
if (frame.hasField("file"))
|
|
{
|
|
source_column = frame["file"].literal();
|
|
|
|
if (frame.hasField("line"))
|
|
{
|
|
source_column += ":" + frame["line"].literal();
|
|
}
|
|
}
|
|
else if (frame.hasField("from"))
|
|
{
|
|
source_column = frame["from"].literal();
|
|
}
|
|
}
|
|
|
|
|
|
void FramestackWidget::drawContentsOffset( TQPainter * p, int ox, int oy,
|
|
int cx, int cy, int cw, int ch )
|
|
{
|
|
TQListView::drawContentsOffset(p, ox, oy, cx, cy, cw, ch);
|
|
|
|
int s1_x = header()->sectionPos(1);
|
|
int s1_w = header()->sectionSize(1);
|
|
|
|
TQRect section1(s1_x, contentsHeight(), s1_w, viewport()->height());
|
|
|
|
p->fillRect(section1, KGlobalSettings::alternateBackgroundColor());
|
|
}
|
|
|
|
// **************************************************************************
|
|
// **************************************************************************
|
|
// **************************************************************************
|
|
|
|
FrameStackItem::FrameStackItem(FramestackWidget *parent,
|
|
unsigned frameNo,
|
|
const TQString &name)
|
|
: TQListViewItem(parent, parent->lastChild()),
|
|
frameNo_(frameNo),
|
|
threadNo_(-1)
|
|
{
|
|
setText(0, name);
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
FrameStackItem::FrameStackItem(ThreadStackItem *parent,
|
|
unsigned frameNo,
|
|
const TQString &name)
|
|
: TQListViewItem(parent, parent->lastChild()),
|
|
frameNo_(frameNo),
|
|
threadNo_(parent->threadNo())
|
|
{
|
|
setText(0, name);
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
FrameStackItem::~FrameStackItem()
|
|
{}
|
|
|
|
// **************************************************************************
|
|
|
|
TQListViewItem *FrameStackItem::lastChild() const
|
|
{
|
|
TQListViewItem* child = firstChild();
|
|
if (child)
|
|
while (TQListViewItem* nextChild = child->nextSibling())
|
|
child = nextChild;
|
|
|
|
return child;
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void FrameStackItem::setOpen(bool open)
|
|
{
|
|
#if 0
|
|
if (open)
|
|
{
|
|
FramestackWidget* owner = (FramestackWidget*)listView();
|
|
if (this->threadNo() != owner->viewedThread() &&
|
|
this->frameNo() != owner->currentFrame_)
|
|
{
|
|
((FramestackWidget*)listView())->slotSelectFrame(0, threadNo());
|
|
}
|
|
}
|
|
#endif
|
|
TQListViewItem::setOpen(open);
|
|
}
|
|
|
|
// **************************************************************************
|
|
// **************************************************************************
|
|
// **************************************************************************
|
|
|
|
ThreadStackItem::ThreadStackItem(FramestackWidget *parent, unsigned threadNo)
|
|
: TQListViewItem(parent),
|
|
threadNo_(threadNo)
|
|
{
|
|
setText(0, i18n("Thread %1").arg(threadNo_));
|
|
setExpandable(true);
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
ThreadStackItem::~ThreadStackItem()
|
|
{}
|
|
|
|
// **************************************************************************
|
|
|
|
TQListViewItem *ThreadStackItem::lastChild() const
|
|
{
|
|
TQListViewItem* child = firstChild();
|
|
if (child)
|
|
while (TQListViewItem* nextChild = child->nextSibling())
|
|
child = nextChild;
|
|
|
|
return child;
|
|
}
|
|
|
|
// **************************************************************************
|
|
|
|
void ThreadStackItem::setOpen(bool open)
|
|
{
|
|
// If we're openining, and have no child yet, get backtrace from
|
|
// gdb.
|
|
if (open && !firstChild())
|
|
{
|
|
// Not that this will not switch to another thread (and won't show
|
|
// position in that other thread). This will only get the frames.
|
|
|
|
// Imagine you have 20 frames and you want to find one blocked on
|
|
// mutex. You don't want a new source file to be opened for each
|
|
// thread you open to find if that's the one you want to debug.
|
|
((FramestackWidget*)listView())->getBacktraceForThread(threadNo());
|
|
}
|
|
|
|
if (open)
|
|
{
|
|
savedFunc_ = text(1);
|
|
setText(1, "");
|
|
savedSource_ = text(2);
|
|
setText(2, "");
|
|
}
|
|
else
|
|
{
|
|
setText(1, savedFunc_);
|
|
setText(2, savedSource_);
|
|
}
|
|
|
|
TQListViewItem::setOpen(open);
|
|
}
|
|
|
|
void FrameStackItem::paintCell(TQPainter * p, const TQColorGroup & cg,
|
|
int column, int width, int align )
|
|
{
|
|
TQColorGroup cg2(cg);
|
|
if (column % 2)
|
|
{
|
|
cg2.setColor(TQColorGroup::Base,
|
|
KGlobalSettings::alternateBackgroundColor());
|
|
}
|
|
TQListViewItem::paintCell(p, cg2, column, width, align);
|
|
}
|
|
|
|
void ThreadStackItem::paintCell(TQPainter * p, const TQColorGroup & cg,
|
|
int column, int width, int align )
|
|
{
|
|
TQColorGroup cg2(cg);
|
|
if (column % 2)
|
|
{
|
|
cg2.setColor(TQColorGroup::Base,
|
|
KGlobalSettings::alternateBackgroundColor());
|
|
}
|
|
TQListViewItem::paintCell(p, cg2, column, width, align);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
/***************************************************************************/
|
|
/***************************************************************************/
|
|
|
|
#include "framestackwidget.moc"
|