Initial support for debugging in the v4 interpreter

This adds breakpoint support to the Debugger, a helper function in the engine
for enabling debugging (which will switch from JIT to the interpreter) and a
DebuggingAgent interface, for use by v4 clients.

Change-Id: I78e17a6cbe7196b0dfe4ee157fc028532131caa3
Reviewed-by: Erik Verbruggen <erik.verbruggen@digia.com>
This commit is contained in:
Simon Hausmann 2013-07-24 10:29:04 +02:00 committed by The Qt Project
parent 993bc84f49
commit 07860794da
15 changed files with 602 additions and 300 deletions

View File

@ -112,9 +112,11 @@ QT_BEGIN_NAMESPACE
#define MOTH_INSTR_ALIGN_MASK (Q_ALIGNOF(QQmlJS::Moth::Instr) - 1)
#ifdef MOTH_THREADED_INTERPRETER
# define MOTH_INSTR_HEADER void *code;
# define MOTH_INSTR_HEADER void *code; \
unsigned int breakPoint : 1;
#else
# define MOTH_INSTR_HEADER quint8 instructionType;
# define MOTH_INSTR_HEADER quint8 instructionType; \
unsigned int breakPoint : 1;
#endif
#define MOTH_INSTR_ENUM(I, FMT) I,

View File

@ -214,6 +214,7 @@ void InstructionSelection::run(QV4::Function *vmFunction, V4IR::Function *functi
int codeSize = 4096;
uchar *codeStart = new uchar[codeSize];
memset(codeStart, 0, codeSize);
uchar *codeNext = codeStart;
uchar *codeEnd = codeStart + codeSize;
@ -278,6 +279,9 @@ void InstructionSelection::run(QV4::Function *vmFunction, V4IR::Function *functi
_vmFunction->code = VME::exec;
_vmFunction->codeData = squeezeCode();
if (QV4::Debugging::Debugger *debugger = engine()->debugger)
debugger->setPendingBreakpoints(_vmFunction);
qSwap(_currentStatement, cs);
qSwap(_stackSlotAllocator, stackSlotAllocator);
delete stackSlotAllocator;
@ -990,6 +994,7 @@ ptrdiff_t InstructionSelection::addInstructionHelper(Instr::Type type, Instr &in
#else
instr.common.instructionType = type;
#endif
instr.common.breakPoint = 0;
int instructionSize = Instr::size(type);
if (_codeEnd - _codeNext < instructionSize) {

View File

@ -60,38 +60,11 @@
using namespace QQmlJS;
using namespace QQmlJS::Moth;
class FunctionState: public Debugging::FunctionState
{
public:
FunctionState(QV4::ExecutionContext *context, const uchar **code)
: Debugging::FunctionState(context)
, stack(0)
, stackSize(0)
, code(code)
{
previousInstructionPointer = context->interpreterInstructionPointer;
context->interpreterInstructionPointer = code;
}
~FunctionState()
{
context()->interpreterInstructionPointer = previousInstructionPointer;
}
virtual QV4::Value *temp(unsigned idx) { return stack + idx; }
void setStack(QV4::Value *stack, unsigned stackSize)
{ this->stack = stack; this->stackSize = stackSize; }
private:
QV4::Value *stack;
unsigned stackSize;
const uchar **code;
const uchar **previousInstructionPointer;
};
#define MOTH_BEGIN_INSTR_COMMON(I) { \
const InstrMeta<(int)Instr::I>::DataType &instr = InstrMeta<(int)Instr::I>::data(*genericInstr); \
code += InstrMeta<(int)Instr::I>::Size; \
if (context->engine->debugger && (instr.breakPoint || context->engine->debugger->pauseAtNextOpportunity())) \
context->engine->debugger->maybeBreakAtInstruction(code, instr.breakPoint); \
Q_UNUSED(instr); \
TRACE_INSTR(I)
@ -265,8 +238,6 @@ QV4::Value VME::run(QV4::ExecutionContext *context, const uchar *&code,
}
#endif
FunctionState state(context, &code);
#ifdef MOTH_THREADED_INTERPRETER
const Instr *genericInstr = reinterpret_cast<const Instr *>(code);
goto *genericInstr->common.code;
@ -320,7 +291,6 @@ QV4::Value VME::run(QV4::ExecutionContext *context, const uchar *&code,
stackSize = instr.value;
stack = static_cast<QV4::Value *>(alloca(stackSize * sizeof(QV4::Value)));
memset(stack, 0, stackSize * sizeof(QV4::Value));
state.setStack(stack, stackSize);
MOTH_END_INSTR(Push)
MOTH_BEGIN_INSTR(CallValue)

View File

@ -459,7 +459,6 @@ Codegen::Codegen(QV4::ExecutionContext *context, bool strict)
, _scopeAndFinally(0)
, _context(context)
, _strictMode(strict)
, _debugger(context->engine->debugger)
, _errorHandler(0)
{
}
@ -478,7 +477,6 @@ Codegen::Codegen(ErrorHandler *errorHandler, bool strictMode)
, _scopeAndFinally(0)
, _context(0)
, _strictMode(strictMode)
, _debugger(0)
, _errorHandler(errorHandler)
{
}
@ -501,13 +499,6 @@ V4IR::Function *Codegen::operator()(const QString &fileName,
V4IR::Function *globalCode = defineFunction(QStringLiteral("%entry"), node, 0,
node->elements, mode, inheritedLocals);
if (_debugger) {
if (node->elements->element) {
SourceLocation loc = node->elements->element->firstSourceLocation();
_debugger->setSourceLocation(globalCode, loc.startLine, loc.startColumn);
}
}
qDeleteAll(_envMap);
_envMap.clear();
@ -530,8 +521,6 @@ V4IR::Function *Codegen::operator()(const QString &fileName,
scan.leaveEnvironment();
V4IR::Function *function = defineFunction(ast->name.toString(), ast, ast->formals, ast->body ? ast->body->elements : 0);
if (_debugger)
_debugger->setSourceLocation(function, ast->functionToken.startLine, ast->functionToken.startColumn);
qDeleteAll(_envMap);
_envMap.clear();
@ -1410,8 +1399,6 @@ bool Codegen::visit(FieldMemberExpression *ast)
bool Codegen::visit(FunctionExpression *ast)
{
V4IR::Function *function = defineFunction(ast->name.toString(), ast, ast->formals, ast->body ? ast->body->elements : 0);
if (_debugger)
_debugger->setSourceLocation(function, ast->functionToken.startLine, ast->functionToken.startColumn);
_expr.code = _block->CLOSURE(function);
return false;
}
@ -1546,8 +1533,6 @@ bool Codegen::visit(ObjectLiteral *ast)
} else if (PropertyGetterSetter *gs = AST::cast<AST::PropertyGetterSetter *>(it->assignment)) {
QString name = propertyName(gs->name);
V4IR::Function *function = defineFunction(name, gs, gs->formals, gs->functionBody ? gs->functionBody->elements : 0);
if (_debugger)
_debugger->setSourceLocation(function, gs->getSetToken.startLine, gs->getSetToken.startColumn);
ObjectPropertyValue &v = valueMap[name];
if (v.value ||
(gs->type == PropertyGetterSetter::Getter && v.getter) ||
@ -1825,8 +1810,6 @@ V4IR::Function *Codegen::defineFunction(const QString &name, AST::Node *ast,
V4IR::Function *function = _module->newFunction(name, _function);
function->sourceFile = _fileName;
if (_debugger)
_debugger->addFunction(function);
V4IR::BasicBlock *entryBlock = function->newBasicBlock(groupStartBlock());
V4IR::BasicBlock *exitBlock = function->newBasicBlock(groupStartBlock(), V4IR::Function::DontInsertBlock);
V4IR::BasicBlock *throwBlock = function->newBasicBlock(groupStartBlock());
@ -1899,8 +1882,6 @@ V4IR::Function *Codegen::defineFunction(const QString &name, AST::Node *ast,
if (member.function) {
V4IR::Function *function = defineFunction(member.function->name.toString(), member.function, member.function->formals,
member.function->body ? member.function->body->elements : 0);
if (_debugger)
_debugger->setSourceLocation(function, member.function->functionToken.startLine, member.function->functionToken.startColumn);
if (! _env->parent) {
move(_block->NAME(member.function->name.toString(), member.function->identifierToken.startLine, member.function->identifierToken.startColumn),
_block->CLOSURE(function));

View File

@ -59,9 +59,6 @@ namespace QQmlJS {
namespace AST {
class UiParameterList;
}
namespace Debugging {
class Debugger;
} // namespace Debugging
@ -442,7 +439,6 @@ private:
QHash<AST::FunctionExpression *, int> _functionMap;
QV4::ExecutionContext *_context;
bool _strictMode;
Debugging::Debugger *_debugger;
ErrorHandler *_errorHandler;
class ScanFunctions;

View File

@ -42,188 +42,163 @@
#include "qv4debugging_p.h"
#include "qv4object_p.h"
#include "qv4functionobject_p.h"
#include "qv4function_p.h"
#include "moth/qv4instr_moth_p.h"
#include <iostream>
#define LOW_LEVEL_DEBUGGING_HELPERS
using namespace QQmlJS;
using namespace QQmlJS::Debugging;
FunctionState::FunctionState(QV4::ExecutionContext *context)
: _context(context)
{
if (debugger())
debugger()->enterFunction(this);
}
FunctionState::~FunctionState()
{
if (debugger())
debugger()->leaveFunction(this);
}
QV4::Value *FunctionState::argument(unsigned idx)
{
QV4::CallContext *c = _context->asCallContext();
if (!c || idx >= c->argumentCount)
return 0;
return c->arguments + idx;
}
QV4::Value *FunctionState::local(unsigned idx)
{
QV4::CallContext *c = _context->asCallContext();
if (c && idx < c->variableCount())
return c->locals + idx;
return 0;
}
#ifdef LOW_LEVEL_DEBUGGING_HELPERS
Debugger *globalInstance = 0;
void printStackTrace()
{
if (globalInstance)
globalInstance->printStackTrace();
else
std::cerr << "No debugger." << std::endl;
}
#endif // DO_TRACE_INSTR
using namespace QV4;
using namespace QV4::Debugging;
Debugger::Debugger(QV4::ExecutionEngine *engine)
: _engine(engine)
, m_agent(0)
, m_state(Running)
, m_pauseRequested(false)
, m_currentInstructionPointer(0)
{
#ifdef LOW_LEVEL_DEBUGGING_HELPERS
globalInstance = this;
#endif // DO_TRACE_INSTR
qMetaTypeId<Debugger*>();
}
Debugger::~Debugger()
{
#ifdef LOW_LEVEL_DEBUGGING_HELPERS
globalInstance = 0;
#endif // DO_TRACE_INSTR
qDeleteAll(_functionInfo.values());
detachFromAgent();
}
void Debugger::addFunction(V4IR::Function *function)
void Debugger::attachToAgent(DebuggerAgent *agent)
{
_functionInfo.insert(function, new FunctionDebugInfo(function));
Q_ASSERT(!m_agent);
m_agent = agent;
}
void Debugger::setSourceLocation(V4IR::Function *function, unsigned line, unsigned column)
void Debugger::detachFromAgent()
{
_functionInfo[function]->setSourceLocation(line, column);
DebuggerAgent *agent = 0;
{
QMutexLocker locker(&m_lock);
agent = m_agent;
m_agent = 0;
}
if (agent)
agent->removeDebugger(this);
}
void Debugger::mapFunction(QV4::Function *vmf, V4IR::Function *irf)
void Debugger::pause()
{
_vmToIr.insert(vmf, irf);
QMutexLocker locker(&m_lock);
if (m_state == Paused)
return;
m_pauseRequested = true;
}
FunctionDebugInfo *Debugger::debugInfo(QV4::FunctionObject *function) const
void Debugger::resume()
{
if (!function)
return 0;
if (function->function)
return _functionInfo[irFunction(function->function)];
else
return 0;
QMutexLocker locker(&m_lock);
Q_ASSERT(m_state == Paused);
m_runningCondition.wakeAll();
}
QString Debugger::name(QV4::FunctionObject *function) const
void Debugger::addBreakPoint(const QString &fileName, int lineNumber)
{
if (FunctionDebugInfo *i = debugInfo(function))
return i->name;
return QString();
QMutexLocker locker(&m_lock);
if (!m_pendingBreakPointsToRemove.remove(fileName, lineNumber))
m_pendingBreakPointsToAdd.add(fileName, lineNumber);
m_havePendingBreakPoints = !m_pendingBreakPointsToAdd.isEmpty() || !m_pendingBreakPointsToRemove.isEmpty();
}
void Debugger::aboutToCall(QV4::FunctionObject *function, QV4::ExecutionContext *context)
void Debugger::removeBreakPoint(const QString &fileName, int lineNumber)
{
_callStack.append(CallInfo(context, function));
QMutexLocker locker(&m_lock);
if (!m_pendingBreakPointsToAdd.remove(fileName, lineNumber))
m_pendingBreakPointsToRemove.add(fileName, lineNumber);
m_havePendingBreakPoints = !m_pendingBreakPointsToAdd.isEmpty() || !m_pendingBreakPointsToRemove.isEmpty();
}
void Debugger::justLeft(QV4::ExecutionContext *context)
Debugger::ExecutionState Debugger::currentExecutionState(const uchar *code) const
{
int idx = callIndex(context);
if (idx < 0)
qDebug() << "Oops, leaving a function that was not registered...?";
else
_callStack.resize(idx);
if (!code)
code = m_currentInstructionPointer;
// ### Locking
ExecutionState state;
QV4::ExecutionContext *context = _engine->current;
QV4::Function *function = 0;
if (CallContext *callCtx = context->asCallContext())
function = callCtx->function->function;
else {
Q_ASSERT(context->type == QV4::ExecutionContext::Type_GlobalContext);
function = context->engine->globalCode;
}
state.function = function;
state.fileName = function->sourceFile;
qptrdiff relativeProgramCounter = code - function->codeData;
state.lineNumber = function->lineNumberForProgramCounter(relativeProgramCounter);
return state;
}
void Debugger::enterFunction(FunctionState *state)
void Debugger::setPendingBreakpoints(Function *function)
{
_callStack[callIndex(state->context())].state = state;
#ifdef DO_TRACE_INSTR
QString n = name(_callStack[callIndex(state->context())].function);
std::cerr << "*** Entering \"" << qPrintable(n) << "\" with " << state->context()->argumentCount << " args" << std::endl;
// for (unsigned i = 0; i < state->context()->variableEnvironment->argumentCount; ++i)
// std::cerr << " " << i << ": " << currentArg(i) << std::endl;
#endif // DO_TRACE_INSTR
m_pendingBreakPointsToAddToFutureCode.applyToFunction(function, /*removeBreakPoints*/ false);
}
void Debugger::leaveFunction(FunctionState *state)
void Debugger::maybeBreakAtInstruction(const uchar *code, bool breakPointHit)
{
_callStack[callIndex(state->context())].state = 0;
QMutexLocker locker(&m_lock);
m_currentInstructionPointer = code;
// Do debugger internal work
if (m_havePendingBreakPoints) {
if (breakPointHit) {
ExecutionState state = currentExecutionState();
breakPointHit = !m_pendingBreakPointsToRemove.contains(state.fileName, state.lineNumber);
}
applyPendingBreakPoints();
}
// Serve debugging requests from the agent
if (m_pauseRequested) {
m_pauseRequested = false;
pauseAndWait();
} else if (breakPointHit)
pauseAndWait();
if (!m_pendingBreakPointsToAdd.isEmpty() || !m_pendingBreakPointsToRemove.isEmpty())
applyPendingBreakPoints();
}
void Debugger::aboutToThrow(const QV4::Value &value)
{
qDebug() << "*** We are about to throw...:" << value.toString(currentState()->context())->toQString();
qDebug() << "*** We are about to throw...";
}
FunctionState *Debugger::currentState() const
void Debugger::pauseAndWait()
{
if (_callStack.isEmpty())
return 0;
else
return _callStack.last().state;
m_state = Paused;
QMetaObject::invokeMethod(m_agent, "debuggerPaused", Qt::QueuedConnection, Q_ARG(QV4::Debugging::Debugger*, this));
m_runningCondition.wait(&m_lock);
m_state = Running;
}
const char *Debugger::currentArg(unsigned idx) const
void Debugger::applyPendingBreakPoints()
{
FunctionState *state = currentState();
return qPrintable(state->argument(idx)->toString(state->context())->toQString());
}
const char *Debugger::currentLocal(unsigned idx) const
{
FunctionState *state = currentState();
return qPrintable(state->local(idx)->toString(state->context())->toQString());
}
const char *Debugger::currentTemp(unsigned idx) const
{
FunctionState *state = currentState();
return qPrintable(state->temp(idx)->toString(state->context())->toQString());
}
void Debugger::printStackTrace() const
{
for (int i = _callStack.size() - 1; i >=0; --i) {
QString n = name(_callStack[i].function);
std::cerr << "\tframe #" << i << ": " << qPrintable(n) << std::endl;
}
}
int Debugger::callIndex(QV4::ExecutionContext *context)
{
for (int idx = _callStack.size() - 1; idx >= 0; --idx) {
if (_callStack[idx].context == context)
return idx;
foreach (Function *function, _engine->functions) {
m_pendingBreakPointsToAdd.applyToFunction(function, /*removeBreakPoints*/false);
m_pendingBreakPointsToRemove.applyToFunction(function, /*removeBreakPoints*/true);
}
return -1;
}
for (BreakPoints::ConstIterator it = m_pendingBreakPointsToAdd.constBegin(),
end = m_pendingBreakPointsToAdd.constEnd(); it != end; ++it) {
foreach (int lineNumber, it.value())
m_pendingBreakPointsToAddToFutureCode.add(it.key(), lineNumber);
}
V4IR::Function *Debugger::irFunction(QV4::Function *vmf) const
{
return _vmToIr[vmf];
m_pendingBreakPointsToAdd.clear();
m_pendingBreakPointsToRemove.clear();
m_havePendingBreakPoints = false;
}
static void realDumpValue(QV4::Value v, QV4::ExecutionContext *ctx, std::string prefix)
@ -306,3 +281,92 @@ void dumpValue(QV4::Value v, QV4::ExecutionContext *ctx)
{
realDumpValue(v, ctx, std::string(""));
}
void DebuggerAgent::addDebugger(Debugger *debugger)
{
Q_ASSERT(!m_debuggers.contains(debugger));
m_debuggers << debugger;
debugger->attachToAgent(this);
}
void DebuggerAgent::removeDebugger(Debugger *debugger)
{
m_debuggers.removeAll(debugger);
debugger->detachFromAgent();
}
void DebuggerAgent::pause(Debugger *debugger)
{
debugger->pause();
}
void DebuggerAgent::addBreakPoint(Debugger *debugger, const QString &fileName, int lineNumber)
{
debugger->addBreakPoint(fileName, lineNumber);
}
void DebuggerAgent::removeBreakPoint(Debugger *debugger, const QString &fileName, int lineNumber)
{
debugger->removeBreakPoint(fileName, lineNumber);
}
DebuggerAgent::~DebuggerAgent()
{
Q_ASSERT(m_debuggers.isEmpty());
}
void Debugger::BreakPoints::add(const QString &fileName, int lineNumber)
{
QList<int> &lines = (*this)[fileName];
if (!lines.contains(lineNumber)) {
lines.append(lineNumber);
qSort(lines);
}
}
bool Debugger::BreakPoints::remove(const QString &fileName, int lineNumber)
{
Iterator breakPoints = find(fileName);
if (breakPoints == constEnd())
return false;
return breakPoints->removeAll(lineNumber) > 0;
}
bool Debugger::BreakPoints::contains(const QString &fileName, int lineNumber) const
{
ConstIterator breakPoints = find(fileName);
if (breakPoints == constEnd())
return false;
return breakPoints->contains(lineNumber);
}
void Debugger::BreakPoints::applyToFunction(Function *function, bool removeBreakPoints)
{
Iterator breakPointsForFile = find(function->sourceFile);
if (breakPointsForFile == end())
return;
QList<int>::Iterator breakPoint = breakPointsForFile->begin();
while (breakPoint != breakPointsForFile->end()) {
bool breakPointFound = false;
for (QVector<LineNumberMapping>::ConstIterator mapping = function->lineNumberMappings.constBegin(),
end = function->lineNumberMappings.constEnd(); mapping != end; ++mapping) {
if (mapping->lineNumber == *breakPoint) {
uchar *codePtr = const_cast<uchar *>(function->codeData) + mapping->codeOffset;
QQmlJS::Moth::Instr *instruction = reinterpret_cast<QQmlJS::Moth::Instr*>(codePtr);
instruction->common.breakPoint = !removeBreakPoints;
// Continue setting the next break point.
breakPointFound = true;
break;
}
}
if (breakPointFound)
breakPoint = breakPointsForFile->erase(breakPoint);
else
++breakPoint;
}
if (breakPointsForFile->isEmpty())
erase(breakPointsForFile);
}

View File

@ -45,113 +45,117 @@
#include "qv4global_p.h"
#include "qv4engine_p.h"
#include "qv4context_p.h"
#include "qv4jsir_p.h"
#include <QHash>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
QT_BEGIN_NAMESPACE
namespace QQmlJS {
namespace QV4 {
namespace V4IR {
struct BasicBlock;
struct Function;
} // namespace IR
namespace Debugging {
class Debugger;
struct FunctionDebugInfo { // TODO: use opaque d-pointers here
QString name;
unsigned startLine, startColumn;
FunctionDebugInfo(V4IR::Function *function):
startLine(0), startColumn(0)
{
if (function->name)
name = *function->name;
}
void setSourceLocation(unsigned line, unsigned column)
{ startLine = line; startColumn = column; }
};
class FunctionState
{
public:
FunctionState(QV4::ExecutionContext *context);
virtual ~FunctionState();
virtual QV4::Value *argument(unsigned idx);
virtual QV4::Value *local(unsigned idx);
virtual QV4::Value *temp(unsigned idx) = 0;
QV4::ExecutionContext *context() const
{ return _context; }
Debugger *debugger() const
{ return _context->engine->debugger; }
private:
QV4::ExecutionContext *_context;
};
struct CallInfo
{
QV4::ExecutionContext *context;
QV4::FunctionObject *function;
FunctionState *state;
CallInfo(QV4::ExecutionContext *context = 0, QV4::FunctionObject *function = 0, FunctionState *state = 0)
: context(context)
, function(function)
, state(state)
{}
};
class DebuggerAgent;
class Q_QML_EXPORT Debugger
{
public:
Debugger(QV4::ExecutionEngine *_engine);
enum State {
Running,
Paused
};
Debugger(ExecutionEngine *_engine);
~Debugger();
public: // compile-time interface
void addFunction(V4IR::Function *function);
void setSourceLocation(V4IR::Function *function, unsigned line, unsigned column);
void mapFunction(QV4::Function *vmf, V4IR::Function *irf);
void attachToAgent(DebuggerAgent *agent);
void detachFromAgent();
public: // run-time querying interface
FunctionDebugInfo *debugInfo(QV4::FunctionObject *function) const;
QString name(QV4::FunctionObject *function) const;
void pause();
void resume();
State state() const { return m_state; }
void addBreakPoint(const QString &fileName, int lineNumber);
void removeBreakPoint(const QString &fileName, int lineNumber);
struct ExecutionState
{
ExecutionState() : lineNumber(-1), function(0) {}
QString fileName;
int lineNumber;
Function *function;
};
ExecutionState currentExecutionState(const uchar *code = 0) const;
bool pauseAtNextOpportunity() const {
return m_pauseRequested || m_havePendingBreakPoints;
}
void setPendingBreakpoints(Function *function);
public: // compile-time interface
void maybeBreakAtInstruction(const uchar *code, bool breakPointHit);
public: // execution hooks
void aboutToCall(QV4::FunctionObject *function, QV4::ExecutionContext *context);
void justLeft(QV4::ExecutionContext *context);
void enterFunction(FunctionState *state);
void leaveFunction(FunctionState *state);
void aboutToThrow(const QV4::Value &value);
public: // debugging hooks
FunctionState *currentState() const;
const char *currentArg(unsigned idx) const;
const char *currentLocal(unsigned idx) const;
const char *currentTemp(unsigned idx) const;
void printStackTrace() const;
void aboutToThrow(const Value &value);
private:
int callIndex(QV4::ExecutionContext *context);
V4IR::Function *irFunction(QV4::Function *vmf) const;
// requires lock to be held
void pauseAndWait();
void applyPendingBreakPoints();
struct BreakPoints : public QHash<QString, QList<int> >
{
void add(const QString &fileName, int lineNumber);
bool remove(const QString &fileName, int lineNumber);
bool contains(const QString &fileName, int lineNumber) const;
void applyToFunction(Function *function, bool removeBreakPoints);
};
private: // TODO: use opaque d-pointers here
QV4::ExecutionEngine *_engine;
QHash<V4IR::Function *, FunctionDebugInfo *> _functionInfo;
QHash<QV4::Function *, V4IR::Function *> _vmToIr;
QVector<CallInfo> _callStack;
DebuggerAgent *m_agent;
QMutex m_lock;
QWaitCondition m_runningCondition;
State m_state;
bool m_pauseRequested;
bool m_havePendingBreakPoints;
BreakPoints m_pendingBreakPointsToAdd;
BreakPoints m_pendingBreakPointsToAddToFutureCode;
BreakPoints m_pendingBreakPointsToRemove;
const uchar *m_currentInstructionPointer;
};
class Q_QML_EXPORT DebuggerAgent : public QObject
{
Q_OBJECT
public:
~DebuggerAgent();
void addDebugger(Debugger *debugger);
void removeDebugger(Debugger *debugger);
void pause(Debugger *debugger);
void addBreakPoint(Debugger *debugger, const QString &fileName, int lineNumber);
void removeBreakPoint(Debugger *debugger, const QString &fileName, int lineNumber);
Q_INVOKABLE virtual void debuggerPaused(QV4::Debugging::Debugger *debugger) = 0;
protected:
QList<Debugger *> m_debuggers;
};
} // namespace Debugging
} // namespace QQmlJS
} // namespace QV4
QT_END_NAMESPACE
Q_DECLARE_METATYPE(QV4::Debugging::Debugger*)
#endif // DEBUGGING_H

View File

@ -68,11 +68,11 @@
#include "qv4stacktrace_p.h"
#ifdef V4_ENABLE_JIT
# include "qv4isel_masm_p.h"
#else // !V4_ENABLE_JIT
# include "qv4isel_moth_p.h"
#include "qv4isel_masm_p.h"
#endif // V4_ENABLE_JIT
#include "qv4isel_moth_p.h"
QT_BEGIN_NAMESPACE
using namespace QV4;
@ -270,6 +270,7 @@ ExecutionEngine::ExecutionEngine(QQmlJS::EvalISelFactory *factory)
ExecutionEngine::~ExecutionEngine()
{
delete debugger;
delete m_multiplyWrappedQObjects;
m_multiplyWrappedQObjects = 0;
delete memoryManager;
@ -284,6 +285,13 @@ ExecutionEngine::~ExecutionEngine()
delete executableAllocator;
}
void ExecutionEngine::enableDebugger()
{
Q_ASSERT(!debugger);
debugger = new Debugging::Debugger(this);
iselFactory.reset(new QQmlJS::Moth::ISelFactory);
}
void ExecutionEngine::initRootContext()
{
rootContext = static_cast<GlobalContext *>(memoryManager->allocContext(sizeof(GlobalContext)));

View File

@ -56,7 +56,7 @@ QT_BEGIN_NAMESPACE
class QV8Engine;
namespace QQmlJS {
namespace QV4 {
namespace Debugging {
class Debugger;
} // namespace Debugging
@ -121,7 +121,7 @@ struct Q_QML_EXPORT ExecutionEngine
IdentifierTable *identifierTable;
QQmlJS::Debugging::Debugger *debugger;
QV4::Debugging::Debugger *debugger;
Object *globalObject;
@ -227,6 +227,8 @@ struct Q_QML_EXPORT ExecutionEngine
ExecutionEngine(QQmlJS::EvalISelFactory *iselFactory = 0);
~ExecutionEngine();
void enableDebugger();
WithContext *newWithContext(Object *with);
CatchContext *newCatchContext(String* exceptionVarName, const QV4::Value &exceptionValue);
CallContext *newCallContext(FunctionObject *f, const QV4::Value &thisObject, QV4::Value *args, int argc);

View File

@ -103,9 +103,6 @@ QV4::Function *EvalInstructionSelection::createFunctionMapping(QV4::Function *ou
foreach (V4IR::Function *function, irFunction->nestedFunctions)
createFunctionMapping(vmFunction, function);
if (_engine->debugger)
_engine->debugger->mapFunction(vmFunction, irFunction);
return vmFunction;
}

View File

@ -192,9 +192,6 @@ Value Script::run()
QV4::ExecutionEngine *engine = scope->engine;
if (engine->debugger)
engine->debugger->aboutToCall(0, scope);
if (qml.isEmpty()) {
TemporaryAssignment<Function*> savedGlobalCode(engine->globalCode, vmFunction);
@ -204,9 +201,6 @@ Value Script::run()
scope->strictMode = vmFunction->isStrict;
scope->lookups = vmFunction->lookups;
if (engine->debugger)
engine->debugger->aboutToCall(0, scope);
QV4::Value result;
try {
result = vmFunction->code(scope, vmFunction->codeData);
@ -216,8 +210,6 @@ Value Script::run()
throw;
}
if (engine->debugger)
engine->debugger->justLeft(scope);
return result;
} else {

View File

@ -54,7 +54,8 @@ PRIVATETESTS += \
qqmlbundle \
qrcqml \
qqmltimer \
qqmlinstantiator
qqmlinstantiator \
qv4debugger
qtHaveModule(widgets) {
PUBLICTESTS += \

View File

@ -0,0 +1,7 @@
CONFIG += testcase
TARGET = tst_qv4debugger
macx:CONFIG -= app_bundle
SOURCES += tst_qv4debugger.cpp
QT += core-private gui-private qml-private network testlib

View File

@ -0,0 +1,287 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <QJSEngine>
#include <private/qv4engine_p.h>
#include <private/qv4debugging_p.h>
#include <private/qv8engine_p.h>
static bool waitForSignal(QObject* obj, const char* signal, int timeout = 10000)
{
QEventLoop loop;
QObject::connect(obj, signal, &loop, SLOT(quit()));
QTimer timer;
QSignalSpy timeoutSpy(&timer, SIGNAL(timeout()));
if (timeout > 0) {
QObject::connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
timer.setSingleShot(true);
timer.start(timeout);
}
loop.exec();
return timeoutSpy.isEmpty();
}
class TestEngine : public QJSEngine
{
Q_OBJECT
public:
TestEngine()
{
qMetaTypeId<InjectedFunction>();
}
Q_INVOKABLE void evaluate(const QString &script, const QString &fileName, int lineNumber = 1)
{
QJSEngine::evaluate(script, fileName, lineNumber);
emit evaluateFinished();
}
QV4::ExecutionEngine *v4Engine() { return QV8Engine::getV4(this); }
typedef QV4::Value (*InjectedFunction)(QV4::SimpleCallContext*);
Q_INVOKABLE void injectFunction(const QString &functionName, TestEngine::InjectedFunction injectedFunction)
{
QV4::ExecutionEngine *v4 = v4Engine();
QV4::String *name = v4->newString(functionName);
QV4::Value function = QV4::Value::fromObject(v4->newBuiltinFunction(v4->rootContext, name, injectedFunction));
v4->globalObject->put(name, function);
}
signals:
void evaluateFinished();
};
Q_DECLARE_METATYPE(TestEngine::InjectedFunction)
class TestAgent : public QV4::Debugging::DebuggerAgent
{
Q_OBJECT
public:
TestAgent()
: m_wasPaused(false)
{
}
virtual void debuggerPaused(QV4::Debugging::Debugger *debugger)
{
Q_ASSERT(m_debuggers.count() == 1 && m_debuggers.first() == debugger);
m_wasPaused = true;
m_statesWhenPaused << debugger->currentExecutionState();
foreach (const TestBreakPoint &bp, m_breakPointsToAddWhenPaused)
debugger->addBreakPoint(bp.fileName, bp.lineNumber);
m_breakPointsToAddWhenPaused.clear();
debugger->resume();
}
int debuggerCount() const { return m_debuggers.count(); }
struct TestBreakPoint
{
TestBreakPoint() : lineNumber(-1) {}
TestBreakPoint(const QString &fileName, int lineNumber)
: fileName(fileName), lineNumber(lineNumber) {}
QString fileName;
int lineNumber;
};
bool m_wasPaused;
QList<QV4::Debugging::Debugger::ExecutionState> m_statesWhenPaused;
QList<TestBreakPoint> m_breakPointsToAddWhenPaused;
};
class tst_qv4debugger : public QObject
{
Q_OBJECT
private slots:
void init();
void cleanup();
void breakAnywhere();
void pendingBreakpoint();
void liveBreakPoint();
void removePendingBreakPoint();
void addBreakPointWhilePaused();
void removeBreakPointForNextInstruction();
private:
void evaluateJavaScript(const QString &script, const QString &fileName, int lineNumber = 1)
{
QMetaObject::invokeMethod(m_engine, "evaluate", Qt::QueuedConnection,
Q_ARG(QString, script), Q_ARG(QString, fileName),
Q_ARG(int, lineNumber));
waitForSignal(m_engine, SIGNAL(evaluateFinished()), /*timeout*/0);
}
TestEngine *m_engine;
QV4::ExecutionEngine *m_v4;
TestAgent *m_debuggerAgent;
QThread *m_javaScriptThread;
};
void tst_qv4debugger::init()
{
m_javaScriptThread = new QThread;
m_engine = new TestEngine;
m_v4 = m_engine->v4Engine();
m_v4->enableDebugger();
m_engine->moveToThread(m_javaScriptThread);
m_javaScriptThread->start();
m_debuggerAgent = new TestAgent;
m_debuggerAgent->addDebugger(m_v4->debugger);
}
void tst_qv4debugger::cleanup()
{
m_javaScriptThread->exit();
waitForSignal(m_javaScriptThread, SIGNAL(finished()), /*timeout*/ 0);
delete m_engine;
delete m_javaScriptThread;
m_engine = 0;
m_v4 = 0;
QCOMPARE(m_debuggerAgent->debuggerCount(), 0);
delete m_debuggerAgent;
m_debuggerAgent = 0;
}
void tst_qv4debugger::breakAnywhere()
{
QString script =
"var i = 42;\n"
"var j = i + 1\n"
"var k = i\n";
m_debuggerAgent->pause(m_v4->debugger);
evaluateJavaScript(script, "testFile");
QVERIFY(m_debuggerAgent->m_wasPaused);
}
void tst_qv4debugger::pendingBreakpoint()
{
QString script =
"var i = 42;\n"
"var j = i + 1\n"
"var k = i\n";
m_debuggerAgent->addBreakPoint(m_v4->debugger, "testfile", 2);
evaluateJavaScript(script, "testfile");
QVERIFY(m_debuggerAgent->m_wasPaused);
QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 1);
QV4::Debugging::Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.first();
QCOMPARE(state.fileName, QString("testfile"));
QCOMPARE(state.lineNumber, 2);
}
void tst_qv4debugger::liveBreakPoint()
{
QString script =
"var i = 42;\n"
"var j = i + 1\n"
"var k = i\n";
m_debuggerAgent->m_breakPointsToAddWhenPaused << TestAgent::TestBreakPoint("liveBreakPoint", 3);
m_debuggerAgent->pause(m_v4->debugger);
evaluateJavaScript(script, "liveBreakPoint");
QVERIFY(m_debuggerAgent->m_wasPaused);
QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 2);
QV4::Debugging::Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.at(1);
QCOMPARE(state.fileName, QString("liveBreakPoint"));
QCOMPARE(state.lineNumber, 3);
}
void tst_qv4debugger::removePendingBreakPoint()
{
QString script =
"var i = 42;\n"
"var j = i + 1\n"
"var k = i\n";
m_debuggerAgent->addBreakPoint(m_v4->debugger, "removePendingBreakPoint", 2);
m_debuggerAgent->removeBreakPoint(m_v4->debugger, "removePendingBreakPoint", 2);
evaluateJavaScript(script, "removePendingBreakPoint");
QVERIFY(!m_debuggerAgent->m_wasPaused);
}
void tst_qv4debugger::addBreakPointWhilePaused()
{
QString script =
"var i = 42;\n"
"var j = i + 1\n"
"var k = i\n";
m_debuggerAgent->addBreakPoint(m_v4->debugger, "addBreakPointWhilePaused", 1);
m_debuggerAgent->m_breakPointsToAddWhenPaused << TestAgent::TestBreakPoint("addBreakPointWhilePaused", 2);
evaluateJavaScript(script, "addBreakPointWhilePaused");
QVERIFY(m_debuggerAgent->m_wasPaused);
QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 2);
QV4::Debugging::Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.at(0);
QCOMPARE(state.fileName, QString("addBreakPointWhilePaused"));
QCOMPARE(state.lineNumber, 1);
state = m_debuggerAgent->m_statesWhenPaused.at(1);
QCOMPARE(state.fileName, QString("addBreakPointWhilePaused"));
QCOMPARE(state.lineNumber, 2);
}
static QV4::Value someCall(QV4::SimpleCallContext *ctx)
{
ctx->engine->debugger->removeBreakPoint("removeBreakPointForNextInstruction", 2);
return QV4::Value::undefinedValue();
}
void tst_qv4debugger::removeBreakPointForNextInstruction()
{
QString script =
"someCall();\n"
"var i = 42;";
QMetaObject::invokeMethod(m_engine, "injectFunction", Qt::BlockingQueuedConnection,
Q_ARG(QString, "someCall"), Q_ARG(TestEngine::InjectedFunction, someCall));
m_debuggerAgent->addBreakPoint(m_v4->debugger, "removeBreakPointForNextInstruction", 2);
evaluateJavaScript(script, "removeBreakPointForNextInstruction");
QVERIFY(!m_debuggerAgent->m_wasPaused);
}
QTEST_MAIN(tst_qv4debugger)
#include "tst_qv4debugger.moc"

View File

@ -43,7 +43,6 @@
# include "private/qv4_llvm_p.h"
#endif // QMLJS_WITH_LLVM
#include "private/qv4debugging_p.h"
#include "private/qv4object_p.h"
#include "private/qv4runtime_p.h"
#include "private/qv4functionobject_p.h"
@ -287,16 +286,8 @@ int main(int argc, char *argv[])
#ifdef QMLJS_WITH_LLVM
QQmlJS::LLVMOutputType fileType = QQmlJS::LLVMOutputObject;
#endif // QMLJS_WITH_LLVM
bool enableDebugging = false;
bool runAsQml = false;
if (!args.isEmpty()) {
if (args.first() == QLatin1String("-d") || args.first() == QLatin1String("--debug")) {
enableDebugging = true;
args.removeFirst();
}
}
if (!args.isEmpty()) {
if (args.first() == QLatin1String("--jit")) {
mode = use_masm;
@ -375,11 +366,6 @@ int main(int argc, char *argv[])
QV4::ExecutionEngine vm(iSelFactory);
QScopedPointer<QQmlJS::Debugging::Debugger> debugger;
if (enableDebugging)
debugger.reset(new QQmlJS::Debugging::Debugger(&vm));
vm.debugger = debugger.data();
QV4::ExecutionContext *ctx = vm.rootContext;
QV4::Object *globalObject = vm.globalObject;