From c27a463fe71d0eef0dc2c4931d6ab102cd7d216f Mon Sep 17 00:00:00 2001 From: Friedemann Kleint <Friedemann.Kleint@nokia.com> Date: Wed, 1 Feb 2012 17:44:07 +0100 Subject: [PATCH] Debugger: Add Disassembling of functions. - Add "Disassemble function..." action with dialog for name to stack window. - Add "Disassemble" with function name from code model to the Editor context menu. - Change the engines to be able to disassemble a function without address. Change-Id: I812f4672d97d9a866ee7f5a38dbd18b2876bccfa Reviewed-by: hjk <qthjk@ovi.com> --- src/plugins/debugger/cdb/cdbengine.cpp | 83 ++++++++++++++++++++----- src/plugins/debugger/debuggerplugin.cpp | 26 +++++++- src/plugins/debugger/gdb/gdbengine.cpp | 41 ++++++++---- src/plugins/debugger/stackwindow.cpp | 40 +++++++++++- 4 files changed, 157 insertions(+), 33 deletions(-) diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp index e35e0157831..72745753286 100644 --- a/src/plugins/debugger/cdb/cdbengine.cpp +++ b/src/plugins/debugger/cdb/cdbengine.cpp @@ -1555,24 +1555,32 @@ void CdbEngine::selectThread(int index) // Default address range for showing disassembly. enum { DisassemblerRange = 512 }; -/* Try to emulate gdb's behaviour: When passed an address, display - * the disassembled function. CDB's 'u' (disassemble) command takes a symbol, - * but does not display the whole function, only 10 lines per default. - * So, to ensure the agent's +/* Called with a stack frame (address and function) or just a function + * name from the context menu. When address and function are + * passed, try to emulate gdb's behaviour to display the whole function. + * CDB's 'u' (disassemble) command takes a symbol, + * but displays only 10 lines per default. So, to ensure the agent's * address is in that range, resolve the function symbol, cache it and * request the disassembly for a range that contains the agent's address. */ void CdbEngine::fetchDisassembler(DisassemblerAgent *agent) { QTC_ASSERT(m_accessible, return;) - const QString function = agent->location().functionName(); - const QString module = agent->location().from(); const QVariant cookie = qVariantFromValue<DisassemblerAgent*>(agent); - if (function.isEmpty() || module.isEmpty()) { + const Location location = agent->location(); + if (debug) + qDebug() << "CdbEngine::fetchDisassembler 0x" + << QString::number(location.address(), 16) + << location.from() << '!' << location.functionName(); + if (!location.functionName().isEmpty()) { + // Resolve function (from stack frame with function and address + // or just function from editor). + postResolveSymbol(location.from(), location.functionName(), cookie); + } else if (location.address()) { // No function, display a default range. - postDisassemblerCommand(agent->address(), cookie); + postDisassemblerCommand(location.address(), cookie); } else { - postResolveSymbol(module, function, cookie); + QTC_ASSERT(false, return); } } @@ -1594,12 +1602,14 @@ void CdbEngine::postDisassemblerCommand(quint64 address, quint64 endAddress, void CdbEngine::postResolveSymbol(const QString &module, const QString &function, const QVariant &cookie) { - const QString symbol = module + QLatin1Char('!') + function; + QString symbol = module.isEmpty() ? QString(QLatin1Char('*')) : module; + symbol += QLatin1Char('!'); + symbol += function; const QList<quint64> addresses = m_symbolAddressCache.values(symbol); if (addresses.isEmpty()) { QVariantList cookieList; cookieList << QVariant(symbol) << cookie; - showMessage(QLatin1String("Resolving symbol: ") + symbol, LogMisc); + showMessage(QLatin1String("Resolving symbol: ") + symbol + QLatin1String("..."), LogMisc); postBuiltinCommand(QByteArray("x ") + symbol.toLatin1(), 0, &CdbEngine::handleResolveSymbol, 0, QVariant(cookieList)); @@ -1674,6 +1684,26 @@ static inline quint64 findClosestFunctionAddress(const QList<quint64> &addresses return addresses.at(closestIndex); } +static inline QString msgAmbiguousFunction(const QString &functionName, + quint64 address, + const QList<quint64> &addresses) +{ + QString result; + QTextStream str(&result); + str.setIntegerBase(16); + str.setNumberFlags(str.numberFlags() | QTextStream::ShowBase); + str << "Several overloads of function '" << functionName + << "()' were found ("; + for (int i = 0; i < addresses.size(); ++i) { + if (i) + str << ", "; + str << addresses.at(i); + + } + str << "), using " << address << '.'; + return result; +} + void CdbEngine::handleResolveSymbol(const QList<quint64> &addresses, const QVariant &cookie) { // Disassembly mode: Determine suitable range containing the @@ -1681,18 +1711,37 @@ void CdbEngine::handleResolveSymbol(const QList<quint64> &addresses, const QVari if (qVariantCanConvert<DisassemblerAgent*>(cookie)) { DisassemblerAgent *agent = cookie.value<DisassemblerAgent *>(); const quint64 agentAddress = agent->address(); - const quint64 functionAddress - = findClosestFunctionAddress(addresses, agentAddress); - if (functionAddress > 0 && functionAddress <= agentAddress) { - quint64 endAddress = agentAddress + DisassemblerRange / 2; + quint64 functionAddress = 0; + quint64 endAddress = 0; + if (agentAddress) { + // We have an address from the agent, find closest. + if (const quint64 closest = findClosestFunctionAddress(addresses, agentAddress)) { + if (closest <= agentAddress) { + functionAddress = closest; + endAddress = agentAddress + DisassemblerRange / 2; + } + } + } else { + // No agent address, disassembly was started with a function name only. + if (!addresses.isEmpty()) { + functionAddress = addresses.first(); + endAddress = functionAddress + DisassemblerRange / 2; + if (addresses.size() > 1) + showMessage(msgAmbiguousFunction(agent->location().functionName(), functionAddress, addresses), LogMisc); + } + } + // Disassemble a function, else use default range around agent address + if (functionAddress) { if (const quint64 remainder = endAddress % 8) endAddress += 8 - remainder; postDisassemblerCommand(functionAddress, endAddress, cookie); - } else { + } else if (agentAddress) { postDisassemblerCommand(agentAddress, cookie); + } else { + QTC_ASSERT(false, return); } return; - } + } // DisassemblerAgent } // Parse: "00000000`77606060 cc int 3" diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp index 094c0181418..34501726e58 100644 --- a/src/plugins/debugger/debuggerplugin.cpp +++ b/src/plugins/debugger/debuggerplugin.cpp @@ -1022,6 +1022,15 @@ public slots: currentEngine()->executeJumpToLine(data); } + void slotDisassembleFunction() + { + const QAction *action = qobject_cast<const QAction *>(sender()); + QTC_ASSERT(action, return); + const StackFrame frame = action->data().value<StackFrame>(); + QTC_ASSERT(!frame.function.isEmpty(), return); + currentEngine()->openDisassemblerView(Location(frame)); + } + void handleAddToWatchWindow() { // Requires a selection, but that's the only case we want anyway. @@ -1939,8 +1948,9 @@ void DebuggerPluginPrivate::requestContextMenu(ITextEditor *editor, bool contextUsable = true; BreakpointModelId id = BreakpointModelId(); + const QString fileName = editor->file()->fileName(); if (editor->property("DisassemblerView").toBool()) { - args.fileName = editor->file()->fileName(); + args.fileName = fileName; QString line = editor->contents() .section(QLatin1Char('\n'), lineNumber - 1, lineNumber - 1); BreakpointResponse needle; @@ -2031,6 +2041,20 @@ void DebuggerPluginPrivate::requestContextMenu(ITextEditor *editor, connect(jumpToLineAction, SIGNAL(triggered()), SLOT(slotJumpToLine())); menu->addAction(jumpToLineAction); } + // Disassemble current function in stopped state. + if (currentEngine()->state() == InferiorStopOk + && currentEngine()->hasCapability(DisassemblerCapability)) { + StackFrame frame; + frame.function = cppFunctionAt(fileName, lineNumber); + frame.line = 42; // trick gdb into mixed mode. + if (!frame.function.isEmpty()) { + const QString text = tr("Disassemble '%1()'").arg(frame.function); + QAction *disassembleAction = new QAction(text, menu); + disassembleAction->setData(QVariant::fromValue(frame)); + connect(disassembleAction, SIGNAL(triggered()), SLOT(slotDisassembleFunction())); + menu->addAction(disassembleAction ); + } + } } } diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index 57e0a912544..d25ea82ac46 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -4325,13 +4325,28 @@ void GdbEngine::fetchDisassemblerByMiRangePlain(const DisassemblerAgentCookie &a } #endif +static inline QByteArray disassemblerCommand(const Location &location, bool mixed) +{ + QByteArray command = "disassemble "; + if (mixed) + command += "/m "; + if (const quint64 address = location.address()) { + command += "0x"; + command += QByteArray::number(address, 16); + } else if (!location.functionName().isEmpty()) { + command += location.functionName().toLatin1(); + } else { + QTC_ASSERT(false, return QByteArray(); ); + } + return command; +} + void GdbEngine::fetchDisassemblerByCliPointMixed(const DisassemblerAgentCookie &ac0) { DisassemblerAgentCookie ac = ac0; QTC_ASSERT(ac.agent, return); - const quint64 address = ac.agent->address(); - QByteArray cmd = "disassemble /m 0x" + QByteArray::number(address, 16); - postCommand(cmd, Discardable, CB(handleFetchDisassemblerByCliPointMixed), + postCommand(disassemblerCommand(ac.agent->location(), true), Discardable, + CB(handleFetchDisassemblerByCliPointMixed), QVariant::fromValue(ac)); } @@ -4339,9 +4354,8 @@ void GdbEngine::fetchDisassemblerByCliPointPlain(const DisassemblerAgentCookie & { DisassemblerAgentCookie ac = ac0; QTC_ASSERT(ac.agent, return); - const quint64 address = ac.agent->address(); - QByteArray cmd = "disassemble 0x" + QByteArray::number(address, 16); - postCommand(cmd, Discardable, CB(handleFetchDisassemblerByCliPointPlain), + postCommand(disassemblerCommand(ac.agent->location(), false), Discardable, + CB(handleFetchDisassemblerByCliPointPlain), QVariant::fromValue(ac)); } @@ -4463,18 +4477,21 @@ void GdbEngine::handleFetchDisassemblerByCliPointPlain(const GdbResponse &respon { DisassemblerAgentCookie ac = response.cookie.value<DisassemblerAgentCookie>(); QTC_ASSERT(ac.agent, return); - + // Agent address is 0 when disassembling a function name only + const quint64 agentAddress = ac.agent->address(); if (response.resultClass == GdbResultDone) { DisassemblerLines dlines = parseDisassembler(response); - if (dlines.coversAddress(ac.agent->address())) { + if (!agentAddress || dlines.coversAddress(agentAddress)) { ac.agent->setContents(dlines); return; } } - if (ac.agent->isMixed()) - fetchDisassemblerByCliRangeMixed(ac); - else - fetchDisassemblerByCliRangePlain(ac); + if (agentAddress) { + if (ac.agent->isMixed()) + fetchDisassemblerByCliRangeMixed(ac); + else + fetchDisassemblerByCliRangePlain(ac); + } } void GdbEngine::handleFetchDisassemblerByCliRangeMixed(const GdbResponse &response) diff --git a/src/plugins/debugger/stackwindow.cpp b/src/plugins/debugger/stackwindow.cpp index f54d35a6081..1e15027e4e7 100644 --- a/src/plugins/debugger/stackwindow.cpp +++ b/src/plugins/debugger/stackwindow.cpp @@ -48,6 +48,7 @@ #include <QtGui/QClipboard> #include <QtGui/QContextMenuEvent> #include <QtGui/QHeaderView> +#include <QtGui/QInputDialog> #include <QtGui/QMenu> namespace Debugger { @@ -91,6 +92,33 @@ void StackWindow::setModel(QAbstractItemModel *model) showAddressColumn(debuggerCore()->action(UseAddressInStackView)->isChecked()); } +// Input a function to be disassembled. Accept CDB syntax +// 'Module!function' for module specification + +static inline StackFrame inputFunctionForDisassembly() +{ + StackFrame frame; + QInputDialog dialog; + dialog.setInputMode(QInputDialog::TextInput); + dialog.setLabelText(StackWindow::tr("Function:")); + dialog.setWindowTitle(StackWindow::tr("Disassemble Function")); + dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); + if (dialog.exec() != QDialog::Accepted) + return frame; + const QString function = dialog.textValue(); + if (function.isEmpty()) + return frame; + const int bangPos = function.indexOf(QLatin1Char('!')); + if (bangPos != -1) { + frame.from = function.left(bangPos); + frame.function = function.mid(bangPos + 1); + } else { + frame.function = function; + } + frame.line = 42; // trick gdb into mixed mode. + return frame; +} + void StackWindow::contextMenuEvent(QContextMenuEvent *ev) { DebuggerEngine *engine = currentEngine(); @@ -124,11 +152,13 @@ void StackWindow::contextMenuEvent(QContextMenuEvent *ev) } QAction *actShowDisassemblerAt = 0; - QAction *actShowDisassembler = 0; + QAction *actShowDisassemblerAtAddress = 0; + QAction *actShowDisassemblerAtFunction = 0; if (engine->hasCapability(DisassemblerCapability)) { actShowDisassemblerAt = menu.addAction(QString()); - actShowDisassembler = menu.addAction(tr("Open Disassembler...")); + actShowDisassemblerAtAddress = menu.addAction(tr("Open Disassembler at address...")); + actShowDisassemblerAtFunction = menu.addAction(tr("Disassemble Function...")); if (address == 0) { actShowDisassemblerAt->setText(tr("Open Disassembler")); actShowDisassemblerAt->setEnabled(false); @@ -165,12 +195,16 @@ void StackWindow::contextMenuEvent(QContextMenuEvent *ev) ml.push_back(MemoryMarkup(address, 1, QColor(Qt::blue).lighter(), tr("Frame #%1 (%2)").arg(row).arg(frame.function))); engine->openMemoryView(address, 0, ml, QPoint(), title); - } else if (act == actShowDisassembler) { + } else if (act == actShowDisassemblerAtAddress) { AddressDialog dialog; if (address) dialog.setAddress(address); if (dialog.exec() == QDialog::Accepted) currentEngine()->openDisassemblerView(Location(dialog.address())); + } else if (act == actShowDisassemblerAtFunction) { + const StackFrame frame = inputFunctionForDisassembly(); + if (!frame.function.isEmpty()) + currentEngine()->openDisassemblerView(Location(frame)); } else if (act == actShowDisassemblerAt) engine->openDisassemblerView(frame); else if (act == actLoadSymbols) -- GitLab