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.
tdevelop/languages/cpp/debugger/framestackwidget.cpp

646 lines
18 KiB

/***************************************************************************
begin : Sun Aug 8 1999
copyright : (C) 1999 by John Birch
email : jbb@kdevelop.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 <qheader.h>
#include <qlistbox.h>
#include <qregexp.h>
#include <qstrlist.h>
#include <qpainter.h>
#include <ctype.h>
/***************************************************************************/
/***************************************************************************/
/***************************************************************************/
namespace GDBDebugger
{
FramestackWidget::FramestackWidget(GDBController* controller,
QWidget *parent,
const char *name, WFlags f)
: QListView(parent, name, f),
viewedThread_(0),
controller_(controller),
mayUpdate_( false )
{
setRootIsDecorated(true);
setSorting(-1);
setSelectionMode(Single);
addColumn(QString::null); // Frame number
addColumn(QString::null); // function name/address
addColumn(QString::null); // source
header()->hide();
// FIXME: maybe, all debugger components should derive from
// a base class that does this connect.
connect(controller, SIGNAL(event(GDBController::event_t)),
this, SLOT(slotEvent(GDBController::event_t)));
connect( this, SIGNAL(clicked(QListViewItem*)),
this, SLOT(slotSelectionChanged(QListViewItem*)) );
}
/***************************************************************************/
FramestackWidget::~FramestackWidget()
{}
/***************************************************************************/
QListViewItem *FramestackWidget::lastChild() const
{
QListViewItem* child = firstChild();
if (child)
while (QListViewItem* nextChild = child->nextSibling())
child = nextChild;
return child;
}
// **************************************************************************
void FramestackWidget::clear()
{
viewedThread_ = 0;
QListView::clear();
}
/***************************************************************************/
void FramestackWidget::slotSelectionChanged(QListViewItem *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(QString("-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(QShowEvent*)
{
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(QString("-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(QString("-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(QString("-thread-select %1")
.arg(threadNo).ascii()));
viewedThread_ = findThread(threadNo);
}
getBacktrace();
if (viewedThread_)
{
// Switch back to the original thread.
controller_->addCommand(
new GDBCommand(QString("-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)
{
QString id = ids.results[i]->value->literal();
controller_->addCommand(
new GDBCommand(QString("-thread-select %1").arg(id).ascii(),
this, &FramestackWidget::handleThread));
}
controller_->addCommand(
new GDBCommand(QString("-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)
{
QString id = r["new-thread-id"].literal();
int id_num = id.toInt();
QString name_column;
QString func_column;
QString args_column;
QString 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.
QListViewItem* 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.
QString frameDesc;
QString name_column;
QString func_column;
QString source_column;
QString 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)
{
QListViewItem* 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)
{
QListViewItem *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)
{
QListViewItem* 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,
QString& func_column,
QString& 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( QPainter * p, int ox, int oy,
int cx, int cy, int cw, int ch )
{
QListView::drawContentsOffset(p, ox, oy, cx, cy, cw, ch);
int s1_x = header()->sectionPos(1);
int s1_w = header()->sectionSize(1);
QRect section1(s1_x, contentsHeight(), s1_w, viewport()->height());
p->fillRect(section1, KGlobalSettings::alternateBackgroundColor());
}
// **************************************************************************
// **************************************************************************
// **************************************************************************
FrameStackItem::FrameStackItem(FramestackWidget *parent,
unsigned frameNo,
const QString &name)
: QListViewItem(parent, parent->lastChild()),
frameNo_(frameNo),
threadNo_(-1)
{
setText(0, name);
}
// **************************************************************************
FrameStackItem::FrameStackItem(ThreadStackItem *parent,
unsigned frameNo,
const QString &name)
: QListViewItem(parent, parent->lastChild()),
frameNo_(frameNo),
threadNo_(parent->threadNo())
{
setText(0, name);
}
// **************************************************************************
FrameStackItem::~FrameStackItem()
{}
// **************************************************************************
QListViewItem *FrameStackItem::lastChild() const
{
QListViewItem* child = firstChild();
if (child)
while (QListViewItem* 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
QListViewItem::setOpen(open);
}
// **************************************************************************
// **************************************************************************
// **************************************************************************
ThreadStackItem::ThreadStackItem(FramestackWidget *parent, unsigned threadNo)
: QListViewItem(parent),
threadNo_(threadNo)
{
setText(0, i18n("Thread %1").arg(threadNo_));
setExpandable(true);
}
// **************************************************************************
ThreadStackItem::~ThreadStackItem()
{}
// **************************************************************************
QListViewItem *ThreadStackItem::lastChild() const
{
QListViewItem* child = firstChild();
if (child)
while (QListViewItem* 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_);
}
QListViewItem::setOpen(open);
}
void FrameStackItem::paintCell(QPainter * p, const QColorGroup & cg,
int column, int width, int align )
{
QColorGroup cg2(cg);
if (column % 2)
{
cg2.setColor(QColorGroup::Base,
KGlobalSettings::alternateBackgroundColor());
}
QListViewItem::paintCell(p, cg2, column, width, align);
}
void ThreadStackItem::paintCell(QPainter * p, const QColorGroup & cg,
int column, int width, int align )
{
QColorGroup cg2(cg);
if (column % 2)
{
cg2.setColor(QColorGroup::Base,
KGlobalSettings::alternateBackgroundColor());
}
QListViewItem::paintCell(p, cg2, column, width, align);
}
}
/***************************************************************************/
/***************************************************************************/
#include "framestackwidget.moc"