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/breakpoint.cpp

720 lines
20 KiB

/***************************************************************************
begin : Tue May 13 2003
copyright : (C) 2003 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 "breakpoint.h"
#include "gdbcontroller.h"
#include "gdbcommand.h"
#include <klocale.h>
#include <kdebug.h>
#include <qfileinfo.h>
#include <qfontmetrics.h>
#include <qpainter.h>
#include <qregexp.h>
#include <qstring.h>
#include <stdio.h>
#include <typeinfo>
/***************************************************************************/
/***************************************************************************/
/***************************************************************************/
namespace GDBDebugger
{
static int BPKey_ = 0;
/***************************************************************************/
/***************************************************************************/
/***************************************************************************/
Breakpoint::Breakpoint(bool temporary, bool enabled)
: s_pending_(true),
s_actionAdd_(true),
s_actionClear_(false),
s_actionModify_(false),
s_actionDie_(false),
s_dbgProcessing_(false),
s_enabled_(enabled),
s_temporary_(temporary),
s_hardwareBP_(false),
s_tracingEnabled_(false),
s_traceFormatStringEnabled_(false),
dbgId_(-1),
hits_(0),
key_(BPKey_++),
active_(-1),
ignoreCount_(0),
condition_("")
{
}
/***************************************************************************/
Breakpoint::~Breakpoint()
{
}
void Breakpoint::sendToGdb(GDBController* controller)
{
// Need to issue 'modifyBreakpoint' when setting breakpoint is done
controller_ = controller;
// FIXME: should either make sure this widget is disabled
// when needed, or implement simular logic.
if (controller->stateIsOn(s_dbgNotStarted))
{
// Can't modify breakpoint now, will try again later.
setPending(true);
return;
}
setPending(false);
bool restart = false;
// FIXME: this will only catch command for which gdb
// produces the "^running" marker.
// FIXME: this probably won't work if there are other
// run commands in the thread already.
if (controller->stateIsOn(s_appRunning)
&& !controller->stateIsOn(s_explicitBreakInto))
{
kdDebug(9012) << "PAUSING APP\n";
controller->pauseApp();
restart = true;
}
if (isActionAdd())
{
// This prevents us from sending breakpoint command to
// gdb for empty breakpoints, when user haven't even
// typed function name, or address, or variable.
//
// Check for isDbgProcessing makes sure we don't issue
// several -break-insert commands before getting
// output from the first one.
if (isValid() && !isDbgProcessing())
{
setBreakpoint(controller);
}
}
else
{
if (isActionClear())
{
clearBreakpoint(controller);
}
else
{
if (isActionModify())
{
modifyBreakpoint(controller);
}
}
}
if (restart) {
kdDebug(9012) << "RESTARING APP\n";
GDBCommand *cmd = new GDBCommand("-exec-continue");
cmd->setRun(true);
controller->addCommand(cmd);
}
}
void Breakpoint::clearBreakpoint(GDBController* /*c*/)
{
controller()->addCommandBeforeRun(
new GDBCommand(dbgRemoveCommand(),
this,
&Breakpoint::handleDeleted));
}
void Breakpoint::applicationExited(GDBController*)
{
}
void Breakpoint::setBreakpoint(GDBController* controller)
{
setDbgProcessing(true);
// Don't use handler mechanism yet, because the reply
// should contain internal id of breakpoint (not gdb id), so that we
// can match gdb id with the breakpoint instance we've set.
// Note that at startup we issue several breakpoint commands, so can't
// just store last breakpoint. Need to stack of last breakpoint commands,
// but that for later.
//
// When this command is finished, slotParseGDBBreakpointSet
// will be called by the controller.
controller->addCommandBeforeRun(
new GDBCommand(dbgSetCommand(controller),
this,
&Breakpoint::handleSet, true));
}
void Breakpoint::modifyBreakpoint(GDBController* controller)
{
controller->
addCommandBeforeRun(
new ModifyBreakpointCommand(QString("-break-condition %1 ") +
conditional(), this));
controller->
addCommandBeforeRun(
new ModifyBreakpointCommand(QString("-break-after %1 ") +
QString::number(ignoreCount()), this));
controller->
addCommandBeforeRun(
new ModifyBreakpointCommand(isEnabled() ?
QString("-break-enable %1")
: QString("-break-disable %1"), this));
}
void Breakpoint::removedInGdb()
{
setActionDie();
emit modified(this);
}
bool Breakpoint::match(const Breakpoint* breakpoint) const
{
// simple case
if (this == breakpoint)
return true;
// Type case
if (typeid(*this) != typeid(*breakpoint))
return false;
return match_data(breakpoint);
}
/***************************************************************************/
QString Breakpoint::dbgRemoveCommand() const
{
if (dbgId_>0)
return QString("-break-delete %1").arg(dbgId_); // gdb command - not translatable
return QString();
}
/***************************************************************************/
// called when debugger ends
void Breakpoint::reset()
{
dbgId_ = -1;
s_pending_ = true;
s_actionAdd_ = true; // waiting for the debugger to start
s_actionClear_ = false;
// All breakpoint properties will be automatically sent to
// gdb when breakpoint is first added, no matter what value
// this field has.
s_actionModify_ = false;
s_dbgProcessing_ = false;
s_hardwareBP_ = false;
hits_ = 0;
active_ = -1;
}
/***************************************************************************/
void Breakpoint::setActive(int active, int id)
{
active_ = active;
dbgId_ = id;
if (s_pending_ && !(s_actionAdd_ && s_actionModify_)) {
s_pending_ = false;
s_actionModify_ = false;
}
s_actionAdd_ = false;
s_actionClear_ = false;
s_actionDie_ = false;
s_dbgProcessing_ = false;
}
/***************************************************************************/
QString Breakpoint::statusDisplay(int activeFlag) const
{
QString status="";
if (!s_enabled_)
status = i18n("Disabled");
else
if (s_pending_)
{
if (s_actionAdd_)
status = i18n("Pending (add)");
if (s_actionClear_)
status = i18n("Pending (clear)");
if (s_actionModify_)
status = i18n("Pending (modify)");
}
else
if (isActive(activeFlag))
status = i18n("Active");
return status;
}
QString Breakpoint::traceRealFormatString() const
{
QString result;
if (traceFormatStringEnabled())
{
result = traceFormatString();
}
else
{
result = "Tracepoint";
if (const FilePosBreakpoint* fb
= dynamic_cast<const FilePosBreakpoint*>(this))
{
result += " at " + fb->location() + ": ";
}
else
{
result += " " + QString::number(key()) + ": ";
}
for(QStringList::const_iterator i = tracedExpressions_.begin(),
e = tracedExpressions_.end(); i != e; ++i)
{
result += " " + *i + " = %d";
}
}
// Quote the thing
result = "\"" + result + "\\n\"";
for(QStringList::const_iterator i = tracedExpressions_.begin(),
e = tracedExpressions_.end(); i != e; ++i)
{
result += ", " + *i;
}
return result;
}
void Breakpoint::handleSet(const GDBMI::ResultRecord& r)
{
// Try to find gdb id. It's a bit harder that it should be,
// because field names differ depending on breakpoint type.
int id = -1;
if (r.hasField("bkpt"))
id = r["bkpt"]["number"].literal().toInt();
else if (r.hasField("wpt"))
id = r["wpt"]["number"].literal().toInt();
else if (r.hasField("hw-rwpt"))
id = r["hw-rwpt"]["number"].literal().toInt();
// We don't have access watchpoints in UI yet, but
// for future.
else if (r.hasField("hw-awpt"))
id = r["hw-awpt"]["number"].literal().toInt();
if (id == -1)
{
// If can't set because file not found yet,
// will need to try later.
setPending(true);
}
else
{
setActive(0 /* unused m_activeFlag */, id);
}
// Need to do this so that if breakpoint is not set
// (because the file is not found)
// we unset isDbgProcessing flag, so that breakpoint can
// be set on next stop.
setDbgProcessing(false);
// Immediately call modifyBreakpoint to set all breakpoint
// properties, such as condition.
modifyBreakpoint(controller_);
emit modified(this);
}
void Breakpoint::handleDeleted(const GDBMI::ResultRecord& /*r*/)
{
kdDebug(9012) << "inside handleDeleted\n";
setActionDie();
if (FilePosBreakpoint* fp = dynamic_cast<FilePosBreakpoint*>(this))
{
kdDebug(9012) << "handleDeleted, line is " << fp->lineNum() << "\n";
}
emit modified(this);
}
/***************************************************************************/
/***************************************************************************/
/***************************************************************************/
FilePosBreakpoint::FilePosBreakpoint()
: subtype_(filepos),
line_(-1)
{}
FilePosBreakpoint::FilePosBreakpoint(const QString &fileName, int lineNum,
bool temporary, bool enabled)
: Breakpoint(temporary, enabled)
{
// Sets 'subtype'
setLocation(QString("%1:%2").arg(fileName).arg(lineNum));
}
FilePosBreakpoint::~FilePosBreakpoint()
{
}
QString FilePosBreakpoint::dbgSetCommand(GDBController *c) const
{
QString cmdStr = "-break-insert";
if (isTemporary())
cmdStr = cmdStr + " -t";
if (c->miPendingBreakpoints())
cmdStr = cmdStr + " -f";
return cmdStr + " " + location_;
}
bool FilePosBreakpoint::match_data(const Breakpoint *xb) const
{
const FilePosBreakpoint* b = static_cast<const FilePosBreakpoint*>(xb);
if (b)
return location_ == b->location_;
else
return false;
}
QString FilePosBreakpoint::displayType() const
{
return i18n("Code breakpoint", "Code");
}
bool FilePosBreakpoint::isValid() const
{
return !location_.isEmpty();
}
bool FilePosBreakpoint::hasFileAndLine() const
{
return line_ != -1;
}
QString FilePosBreakpoint::fileName() const
{
return fileName_;
}
unsigned FilePosBreakpoint::lineNum() const
{
return line_;
}
QString FilePosBreakpoint::location(bool compact) const
{
if (subtype_ == filepos && hasFileAndLine() && compact)
{
return QFileInfo(fileName_).fileName()+":"+QString::number(line_);
}
else
{
return location_;
}
}
/***************************************************************************/
void FilePosBreakpoint::setLocation(const QString& location)
{
location_ = location;
QRegExp regExp1("(.*):(\\d+)$");
regExp1.setMinimal(true);
if ( regExp1.search(location, 0) >= 0 )
{
subtype_ = filepos;
QString t = regExp1.cap(1);
QString dirPath = QFileInfo(t).dirPath();
if ( dirPath == "." )
{
QString existingDirPath = QFileInfo(fileName_).dirPath();
if (existingDirPath != ".")
fileName_ = existingDirPath+"/"+regExp1.cap(1);
else
fileName_ = regExp1.cap(1);
}
else
fileName_ = regExp1.cap(1);
line_ = regExp1.cap(2).toInt();
location_ = QString("%1:%2").arg(fileName_).arg(regExp1.cap(2));
}
else
{
// Could be address as well, but it's treated absolutely
// the same everywhere.
subtype_ = function;
}
}
void FilePosBreakpoint::handleSet(const GDBMI::ResultRecord& r)
{
// Below logic gets filename and line from gdb response, and
// allows us to show breakpoint marker even for function
// breakpoints. Unfortunately, 'fullname' field is available only in
// post-6.4 versions of gdb and if we try to use 'file', then
// KDevelop won't be able to find that file to show the marker.
if (r.hasField("bkpt"))
{
const GDBMI::Value& v = r["bkpt"];
if (v.hasField("fullname") && v.hasField("line"))
{
fileName_ = v["fullname"].literal();
line_ = v["line"].literal().toInt();
}
}
Breakpoint::handleSet(r);
}
/***************************************************************************/
/***************************************************************************/
/***************************************************************************/
Watchpoint::Watchpoint(const QString& varName, bool temporary, bool enabled)
: Breakpoint(temporary, enabled),
varName_(varName)
{
}
/***************************************************************************/
Watchpoint::~Watchpoint()
{
}
void Watchpoint::setBreakpoint(GDBController* controller)
{
if (isEnabled())
{
setDbgProcessing(true);
controller->addCommandBeforeRun(
new GDBCommand(
QString("-data-evaluate-expression &%1").arg(varName_),
this,
&Watchpoint::handleAddressComputed));
}
}
void Watchpoint::handleAddressComputed(const GDBMI::ResultRecord& r)
{
address_ = r["value"].literal().toULongLong(0, 16);
controller()->addCommandBeforeRun(
new GDBCommand(
QString("-break-watch *%1").arg(r["value"].literal()),
static_cast<Breakpoint*>(this),
&Watchpoint::handleSet));
}
void Watchpoint::applicationExited(GDBController* c)
{
if (!c->stateIsOn(s_dbgNotStarted))
{
// Note: not using 'clearBreakpoint' as it will delete breakpoint
// completely.
controller()->addCommand(
new GDBCommand(dbgRemoveCommand()));
setDbgId(-1);
setEnabled(false);
setActionAdd(true);
address_ = static_cast<unsigned long long>(-1);
emit modified(this);
}
}
void Watchpoint::removedInGdb()
{
// Do nothing. Watchpoints must be preserved
// even if they are gone in gdb.
}
/***************************************************************************/
QString Watchpoint::dbgSetCommand(GDBController *) const
{
return QString("-break-watch ")+varName_; // gdb command - not translatable
}
/***************************************************************************/
bool Watchpoint::match_data(const Breakpoint* xb) const
{
const Watchpoint* b = static_cast<const Watchpoint*>(xb);
return (varName_ == b->varName_);
}
ReadWatchpoint::ReadWatchpoint(const QString& varName, bool temporary, bool enabled)
: Watchpoint(varName, temporary, enabled)
{
}
QString ReadWatchpoint::dbgSetCommand(GDBController *) const
{
return QString("-break-watch -r ")+varName();
}
bool ReadWatchpoint::match_data(const Breakpoint* xb) const
{
const ReadWatchpoint* b = static_cast<const ReadWatchpoint*>(xb);
return (varName() == b->varName());
}
/***************************************************************************/
/***************************************************************************/
/***************************************************************************/
//ExitBreakpoint::ExitBreakpoint(bool temporary, bool enabled) :
// Breakpoint(temporary, enabled)
//{
//}
//
///***************************************************************************/
//
//ExitBreakpoint::~ExitBreakpoint()
//{
//}
//
///***************************************************************************/
//
//QString ExitBreakpoint::dbgSetCommand() const
//{
// return "";
//}
//
///***************************************************************************/
//
//bool ExitBreakpoint::match(const Breakpoint* brkpt) const
//{
// // simple case
// if (this == brkpt)
// return true;
//
// // Type case
// const ExitBreakpoint* check = dynamic_cast<const ExitBreakpoint*>(brkpt);
// if (!check)
// return false;
//
// // member case
// return true;
//}
//
/***************************************************************************/
/***************************************************************************/
/***************************************************************************/
//
// These are implemented in gdb but can cause a lot of breakpoints
// to be set. This needs more thought before being implemented
//RegExpBreakpoint::RegExpBreakpoint(bool temporary, bool enabled) :
// Breakpoint(temporary, enabled)
//{
//}
//
///***************************************************************************/
//
//RegExpBreakpoint::~RegExpBreakpoint()
//{
//}
//
///***************************************************************************/
//
//QString RegExpBreakpoint::dbgSetCommand() const
//{
// return "";
//}
//
///***************************************************************************/
//
////QString RegExpBreakpoint::dbgRemoveCommand() const
////{
//// return "";
////}
//
/***************************************************************************/
/***************************************************************************/
/***************************************************************************/
// Most catch options arn't implemented in gdb so ignore this for now.
//CatchBreakpoint::CatchBreakpoint(bool temporary, bool enabled) :
// Breakpoint(temporary, enabled)
//{
//}
//
///***************************************************************************/
//
//CatchBreakpoint::~CatchBreakpoint()
//{
//}
//
///***************************************************************************/
//
//QString CatchBreakpoint::dbgSetCommand() const
//{
// return "";
//}
//
///***************************************************************************/
//
////QString CatchBreakpoint::dbgRemoveCommand() const
////{
//// return "";
////}
//
/***************************************************************************/
/***************************************************************************/
/***************************************************************************/
}
#include "breakpoint.moc"