Changeset View
Changeset View
Standalone View
Standalone View
debuggers/gdb/gdb.cpp
- This file was copied to debuggers/common/midebugger.cpp.
1 | /* | 1 | /* | ||
---|---|---|---|---|---|
2 | * Low level GDB interface. | 2 | * Low level GDB interface. | ||
3 | * | 3 | * | ||
4 | * Copyright 1999 John Birch <jbb@kdevelop.org > | 4 | * Copyright 1999 John Birch <jbb@kdevelop.org > | ||
5 | * Copyright 2007 Vladimir Prus <ghost@cs.msu.su> | 5 | * Copyright 2007 Vladimir Prus <ghost@cs.msu.su> | ||
6 | * Copyright 2016 Aetf <aetf@unlimitedcodeworks.xyz> | ||||
6 | * | 7 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | 8 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as | 9 | * it under the terms of the GNU General Public License as | ||
9 | * published by the Free Software Foundation; either version 2 of the | 10 | * published by the Free Software Foundation; either version 2 of the | ||
10 | * License, or (at your option) any later version. | 11 | * License, or (at your option) any later version. | ||
11 | * | 12 | * | ||
12 | * This program is distributed in the hope that it will be useful, | 13 | * This program is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | 16 | * GNU General Public License for more details. | ||
16 | * | 17 | * | ||
17 | * You should have received a copy of the GNU General Public | 18 | * You should have received a copy of the GNU General Public | ||
18 | * License along with this program; if not, write to the | 19 | * License along with this program; if not, write to the | ||
19 | * Free Software Foundation, Inc., | 20 | * Free Software Foundation, Inc., | ||
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | 21 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
21 | */ | 22 | */ | ||
22 | 23 | | |||
23 | #include "gdb.h" | 24 | #include "gdb.h" | ||
24 | #include "debugsession.h" | 25 | | ||
25 | #include "debug.h" | 26 | #include "dbgglobal.h" | ||
27 | #include "debuglog.h" | ||||
26 | 28 | | |||
27 | #include <KConfig> | 29 | #include <KConfig> | ||
28 | #include <KConfigGroup> | 30 | #include <KConfigGroup> | ||
31 | #include <KLocalizedString> | ||||
29 | #include <KMessageBox> | 32 | #include <KMessageBox> | ||
30 | #include <KShell> | 33 | #include <KShell> | ||
31 | #include <KLocalizedString> | | |||
32 | 34 | | |||
33 | #include <QApplication> | 35 | #include <QApplication> | ||
34 | #include <QFileInfo> | 36 | #include <QFileInfo> | ||
37 | #include <QUrl> | ||||
35 | 38 | | |||
36 | #include <memory> | 39 | using namespace KDevMI::GDB; | ||
37 | 40 | using namespace KDevMI::MI; | |||
38 | #include <sys/types.h> | | |||
39 | #include <signal.h> | | |||
40 | 41 | | |||
41 | // #define DEBUG_NO_TRY //to get a backtrace to where the exception was thrown | 42 | GdbDebugger::GdbDebugger(QObject* parent) | ||
42 | 43 | : MIDebugger(parent) | |||
43 | Q_LOGGING_CATEGORY(DEBUGGERGDB, "kdevelop.debuggers.gdb") | | |||
44 | | ||||
45 | using namespace GDBDebugger; | | |||
46 | | ||||
47 | GDB::GDB(QObject* parent) | | |||
48 | : QObject(parent), process_(0), currentCmd_(0) | | |||
49 | { | 44 | { | ||
50 | } | 45 | } | ||
51 | 46 | | |||
52 | GDB::~GDB() | 47 | GdbDebugger::~GdbDebugger() | ||
53 | { | 48 | { | ||
54 | // prevent Qt warning: QProcess: Destroyed while process is still running. | | |||
55 | if (process_ && process_->state() == QProcess::Running) { | | |||
56 | disconnect(process_, static_cast<void(KProcess::*)(QProcess::ProcessError)>(&KProcess::error), | | |||
57 | this, &GDB::processErrored); | | |||
58 | process_->kill(); | | |||
59 | process_->waitForFinished(10); | | |||
60 | } | | |||
61 | } | 49 | } | ||
62 | 50 | | |||
63 | void GDB::start(KConfigGroup& config, const QStringList& extraArguments) | 51 | void GdbDebugger::start(KConfigGroup& config, const QStringList& extraArguments) | ||
64 | { | 52 | { | ||
65 | // FIXME: verify that default value leads to something sensible | 53 | // FIXME: verify that default value leads to something sensible | ||
66 | QUrl gdbUrl = config.readEntry(GDBDebugger::gdbPathEntry, QUrl()); | 54 | QUrl gdbUrl = config.readEntry(gdbPathEntry, QUrl()); | ||
67 | if (gdbUrl.isEmpty()) { | 55 | if (gdbUrl.isEmpty()) { | ||
68 | gdbBinary_ = "gdb"; | 56 | debuggerBinary_ = "gdb"; | ||
69 | } else { | 57 | } else { | ||
70 | // FIXME: verify its' a local path. | 58 | // FIXME: verify its' a local path. | ||
71 | gdbBinary_ = gdbUrl.url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash); | 59 | debuggerBinary_ = gdbUrl.url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash); | ||
72 | } | 60 | } | ||
73 | process_ = new KProcess(this); | | |||
74 | process_->setOutputChannelMode( KProcess::SeparateChannels ); | | |||
75 | connect(process_, &KProcess::readyReadStandardOutput, | | |||
76 | this, &GDB::readyReadStandardOutput); | | |||
77 | connect(process_, &KProcess::readyReadStandardError, | | |||
78 | this, &GDB::readyReadStandardError); | | |||
79 | connect(process_, | | |||
80 | static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), | | |||
81 | this, &GDB::processFinished); | | |||
82 | connect(process_, static_cast<void(KProcess::*)(QProcess::ProcessError)>(&KProcess::error), | | |||
83 | this, &GDB::processErrored); | | |||
84 | | ||||
85 | 61 | | |||
86 | QStringList arguments = extraArguments; | 62 | QStringList arguments = extraArguments; | ||
87 | arguments << "--interpreter=mi2" << "-quiet"; | 63 | arguments << "--interpreter=mi2" << "-quiet"; | ||
88 | 64 | | |||
89 | QUrl shell = config.readEntry(GDBDebugger::debuggerShellEntry, QUrl()); | 65 | QUrl shell = config.readEntry(debuggerShellEntry, QUrl()); | ||
90 | if( !shell.isEmpty() ) | 66 | if(!shell.isEmpty()) { | ||
91 | { | | |||
92 | qCDebug(DEBUGGERGDB) << "have shell" << shell; | 67 | qCDebug(DEBUGGERGDB) << "have shell" << shell; | ||
93 | QString shell_without_args = shell.toLocalFile().split(QChar(' ')).first(); | 68 | QString shell_without_args = shell.toLocalFile().split(QChar(' ')).first(); | ||
94 | 69 | | |||
95 | QFileInfo info( shell_without_args ); | 70 | QFileInfo info(shell_without_args); | ||
96 | /*if( info.isRelative() ) | 71 | /*if( info.isRelative() ) | ||
97 | { | 72 | { | ||
98 | shell_without_args = build_dir + "/" + shell_without_args; | 73 | shell_without_args = build_dir + "/" + shell_without_args; | ||
99 | info.setFile( shell_without_args ); | 74 | info.setFile( shell_without_args ); | ||
100 | }*/ | 75 | }*/ | ||
101 | if( !info.exists() ) | 76 | if(!info.exists()) { | ||
102 | { | | |||
103 | KMessageBox::information( | 77 | KMessageBox::information( | ||
104 | qApp->activeWindow(), | 78 | qApp->activeWindow(), | ||
105 | i18n("Could not locate the debugging shell '%1'.", shell_without_args ), | 79 | i18n("Could not locate the debugging shell '%1'.", shell_without_args ), | ||
106 | i18n("Debugging Shell Not Found") ); | 80 | i18n("Debugging Shell Not Found") ); | ||
107 | // FIXME: throw, or set some error message. | 81 | // FIXME: throw, or set some error message. | ||
108 | return; | 82 | return; | ||
109 | } | 83 | } | ||
110 | 84 | | |||
111 | arguments.insert(0, gdbBinary_); | 85 | arguments.insert(0, debuggerBinary_); | ||
112 | arguments.insert(0, shell.toLocalFile()); | 86 | arguments.insert(0, shell.toLocalFile()); | ||
113 | process_->setShellCommand( KShell::joinArgs( arguments ) ); | 87 | process_->setShellCommand(KShell::joinArgs(arguments)); | ||
114 | } | | |||
115 | else | | |||
116 | { | | |||
117 | process_->setProgram( gdbBinary_, arguments ); | | |||
118 | } | | |||
119 | | ||||
120 | process_->start(); | | |||
121 | | ||||
122 | qCDebug(DEBUGGERGDB) << "STARTING GDB\n"; | | |||
123 | emit userCommandOutput(shell.toLocalFile() + ' ' + gdbBinary_ | | |||
124 | + " --interpreter=mi2 -quiet\n" ); | | |||
125 | } | | |||
126 | | ||||
127 | | ||||
128 | void GDB::execute(GDBCommand* command) | | |||
129 | { | | |||
130 | currentCmd_ = command; | | |||
131 | QString commandText = currentCmd_->cmdToSend(); | | |||
132 | | ||||
133 | qCDebug(DEBUGGERGDB) << "SEND:" << commandText.trimmed(); | | |||
134 | | ||||
135 | QByteArray commandUtf8 = commandText.toUtf8(); | | |||
136 | | ||||
137 | process_->write(commandUtf8, commandUtf8.length()); | | |||
138 | | ||||
139 | QString prettyCmd = currentCmd_->cmdToSend(); | | |||
140 | prettyCmd.remove( QRegExp("set prompt \032.\n") ); | | |||
141 | prettyCmd = "(gdb) " + prettyCmd; | | |||
142 | | ||||
143 | if (currentCmd_->isUserCommand()) | | |||
144 | emit userCommandOutput(prettyCmd); | | |||
145 | else | | |||
146 | emit internalCommandOutput(prettyCmd); | | |||
147 | } | | |||
148 | | ||||
149 | bool GDB::isReady() const | | |||
150 | { | | |||
151 | return currentCmd_ == 0; | | |||
152 | } | | |||
153 | | ||||
154 | void GDB::interrupt() | | |||
155 | { | | |||
156 | //TODO:win32 Porting needed | | |||
157 | int pid = process_->pid(); | | |||
158 | if (pid != 0) { | | |||
159 | ::kill(pid, SIGINT); | | |||
160 | } | | |||
161 | } | | |||
162 | | ||||
163 | GDBCommand* GDB::currentCommand() const | | |||
164 | { | | |||
165 | return currentCmd_; | | |||
166 | } | | |||
167 | | ||||
168 | void GDB::kill() | | |||
169 | { | | |||
170 | process_->kill(); | | |||
171 | } | | |||
172 | | ||||
173 | void GDB::readyReadStandardOutput() | | |||
174 | { | | |||
175 | process_->setReadChannel(QProcess::StandardOutput); | | |||
176 | | ||||
177 | buffer_ += process_->readAll(); | | |||
178 | for (;;) | | |||
179 | { | | |||
180 | /* In MI mode, all messages are exactly one line. | | |||
181 | See if we have any complete lines in the buffer. */ | | |||
182 | int i = buffer_.indexOf('\n'); | | |||
183 | if (i == -1) | | |||
184 | break; | | |||
185 | QByteArray reply(buffer_.left(i)); | | |||
186 | buffer_ = buffer_.mid(i+1); | | |||
187 | | ||||
188 | processLine(reply); | | |||
189 | } | | |||
190 | } | | |||
191 | | ||||
192 | void GDB::readyReadStandardError() | | |||
193 | { | | |||
194 | process_->setReadChannel(QProcess::StandardOutput); | | |||
195 | emit internalCommandOutput(QString::fromUtf8(process_->readAll())); | | |||
196 | } | | |||
197 | | ||||
198 | void GDB::processLine(const QByteArray& line) | | |||
199 | { | | |||
200 | qCDebug(DEBUGGERGDB) << "GDB output: " << line; | | |||
201 | | ||||
202 | FileSymbol file; | | |||
203 | file.contents = line; | | |||
204 | | ||||
205 | std::unique_ptr<GDBMI::Record> r(mi_parser_.parse(&file)); | | |||
206 | | ||||
207 | if (!r) | | |||
208 | { | | |||
209 | // FIXME: Issue an error! | | |||
210 | qCDebug(DEBUGGERGDB) << "Invalid MI message:" << line; | | |||
211 | // We don't consider the current command done. | | |||
212 | // So, if a command results in unparseable reply, | | |||
213 | // we'll just wait for the "right" reply, which might | | |||
214 | // never come. However, marking the command as | | |||
215 | // done in this case is even more risky. | | |||
216 | // It's probably possible to get here if we're debugging | | |||
217 | // natively without PTY, though this is uncommon case. | | |||
218 | return; | | |||
219 | } | | |||
220 | | ||||
221 | #ifndef DEBUG_NO_TRY | | |||
222 | try | | |||
223 | { | | |||
224 | #endif | | |||
225 | switch(r->kind) | | |||
226 | { | | |||
227 | case GDBMI::Record::Result: { | | |||
228 | GDBMI::ResultRecord& result = static_cast<GDBMI::ResultRecord&>(*r); | | |||
229 | | ||||
230 | emit internalCommandOutput(QString::fromUtf8(line) + '\n'); | | |||
231 | | ||||
232 | // GDB doc: "running" and "exit" are status codes equivalent to "done" | | |||
233 | if (result.reason == "done" || result.reason == "running" || result.reason == "exit") | | |||
234 | { | | |||
235 | if (!currentCmd_) { | | |||
236 | qCDebug(DEBUGGERGDB) << "Received a result without a pending command"; | | |||
237 | } else { | | |||
238 | Q_ASSERT(currentCmd_->token() == result.token); | | |||
239 | currentCmd_->invokeHandler(result); | | |||
240 | } | | |||
241 | } | | |||
242 | else if (result.reason == "error") | | |||
243 | { | | |||
244 | qCDebug(DEBUGGERGDB) << "Handling error"; | | |||
245 | // Some commands want to handle errors themself. | | |||
246 | if (currentCmd_->handlesError() && | | |||
247 | currentCmd_->invokeHandler(result)) | | |||
248 | { | | |||
249 | qCDebug(DEBUGGERGDB) << "Invoked custom handler\n"; | | |||
250 | // Done, nothing more needed | | |||
251 | } | | |||
252 | else | | |||
253 | emit error(result); | | |||
254 | } | | |||
255 | else | | |||
256 | { | | |||
257 | qCDebug(DEBUGGERGDB) << "Unhandled result code: " << result.reason; | | |||
258 | } | | |||
259 | | ||||
260 | delete currentCmd_; | | |||
261 | currentCmd_ = nullptr; | | |||
262 | emit ready(); | | |||
263 | break; | | |||
264 | } | | |||
265 | | ||||
266 | case GDBMI::Record::Async: { | | |||
267 | GDBMI::AsyncRecord& async = dynamic_cast<GDBMI::AsyncRecord&>(*r); | | |||
268 | | ||||
269 | switch (async.subkind) { | | |||
270 | case GDBMI::AsyncRecord::Exec: { | | |||
271 | // Prefix '*'; asynchronous state changes of the target | | |||
272 | if (async.reason == "stopped") | | |||
273 | { | | |||
274 | emit programStopped(async); | | |||
275 | } | | |||
276 | else if (async.reason == "running") | | |||
277 | { | | |||
278 | emit programRunning(); | | |||
279 | } | | |||
280 | else | | |||
281 | { | | |||
282 | qCDebug(DEBUGGERGDB) << "Unhandled exec notification: " << async.reason; | | |||
283 | } | | |||
284 | break; | | |||
285 | } | | |||
286 | | ||||
287 | case GDBMI::AsyncRecord::Notify: { | | |||
288 | // Prefix '='; supplementary information that we should handle (new breakpoint etc.) | | |||
289 | emit notification(async); | | |||
290 | break; | | |||
291 | } | | |||
292 | | ||||
293 | case GDBMI::AsyncRecord::Status: { | | |||
294 | // Prefix '+'; GDB documentation: | | |||
295 | // On-going status information about progress of a slow operation; may be ignored | | |||
296 | break; | | |||
297 | } | | |||
298 | | ||||
299 | default: | | |||
300 | Q_ASSERT(false); | | |||
301 | } | | |||
302 | break; | | |||
303 | } | | |||
304 | | ||||
305 | case GDBMI::Record::Stream: { | | |||
306 | | ||||
307 | GDBMI::StreamRecord& s = dynamic_cast<GDBMI::StreamRecord&>(*r); | | |||
308 | | ||||
309 | if (s.subkind == GDBMI::StreamRecord::Target) { | | |||
310 | emit applicationOutput(s.message); | | |||
311 | } else { | 88 | } else { | ||
312 | if (currentCmd_ && currentCmd_->isUserCommand()) | 89 | process_->setProgram(debuggerBinary_, arguments); | ||
313 | emit userCommandOutput(s.message); | | |||
314 | else if (s.subkind == GDBMI::StreamRecord::Console) { | | |||
315 | emit applicationOutput(s.message); | | |||
316 | } else { | | |||
317 | emit internalCommandOutput(s.message); | | |||
318 | } | | |||
319 | | ||||
320 | if (currentCmd_) | | |||
321 | currentCmd_->newOutput(s.message); | | |||
322 | } | | |||
323 | | ||||
324 | emit streamRecord(s); | | |||
325 | | ||||
326 | break; | | |||
327 | } | | |||
328 | | ||||
329 | case GDBMI::Record::Prompt: | | |||
330 | break; | | |||
331 | } | 90 | } | ||
332 | #ifndef DEBUG_NO_TRY | | |||
333 | } | | |||
334 | catch(const std::exception& e) | | |||
335 | { | | |||
336 | KMessageBox::detailedSorry( | | |||
337 | qApp->activeWindow(), | | |||
338 | i18nc("<b>Internal debugger error</b>", | | |||
339 | "<p>The debugger component encountered internal error while " | | |||
340 | "processing reply from gdb. Please submit a bug report."), | | |||
341 | i18n("The exception is: %1\n" | | |||
342 | "The MI response is: %2", e.what(), | | |||
343 | QString::fromLatin1(line)), | | |||
344 | i18n("Internal debugger error")); | | |||
345 | } | | |||
346 | #endif | | |||
347 | } | | |||
348 | | ||||
349 | // ************************************************************************** | | |||
350 | | ||||
351 | void GDB::processFinished(int exitCode, QProcess::ExitStatus exitStatus) | | |||
352 | { | | |||
353 | Q_UNUSED(exitCode); | | |||
354 | Q_UNUSED(exitStatus); | | |||
355 | qCDebug(DEBUGGERGDB) << "GDB FINISHED\n"; | | |||
356 | /* FIXME: return the status? */ | | |||
357 | emit gdbExited(); | | |||
358 | 91 | | |||
92 | process_->start(); | ||||
359 | 93 | | |||
360 | /* FIXME: revive. Need to arrange for controller to delete us. | 94 | qCDebug(DEBUGGERGDB) << "Starting GDB with command" << shell.toLocalFile() + ' ' + debuggerBinary_ | ||
361 | bool abnormal = exitCode != 0 || exitStatus != QProcess::NormalExit; | 95 | + ' ' + arguments.join(' '); | ||
362 | deleteLater(); | 96 | qCDebug(DEBUGGERGDB) << "GDB process pid:" << process_->pid(); | ||
363 | delete tty_; | 97 | emit userCommandOutput(shell.toLocalFile() + ' ' + debuggerBinary_ | ||
364 | tty_ = 0; | 98 | + ' ' + arguments.join(' ') + '\n'); | ||
365 | | ||||
366 | if (abnormal) | | |||
367 | emit debuggerAbnormalExit(); | | |||
368 | | ||||
369 | raiseEvent(debugger_exited); | | |||
370 | | ||||
371 | destroyCmds(); | | |||
372 | setState(s_dbgNotStarted|s_appNotStarted|s_programExited); | | |||
373 | emit showMessage(i18n("Process exited"), 3000); | | |||
374 | | ||||
375 | emit gdbUserCommandStdout("(gdb) Process exited\n"); | | |||
376 | */ | | |||
377 | } | | |||
378 | | ||||
379 | void GDB::processErrored(QProcess::ProcessError error) | | |||
380 | { | | |||
381 | qCDebug(DEBUGGERGDB) << "GDB ERRORED" << error; | | |||
382 | if( error == QProcess::FailedToStart ) | | |||
383 | { | | |||
384 | KMessageBox::information( | | |||
385 | qApp->activeWindow(), | | |||
386 | i18n("<b>Could not start debugger.</b>" | | |||
387 | "<p>Could not run '%1'. " | | |||
388 | "Make sure that the path name is specified correctly.", | | |||
389 | gdbBinary_), | | |||
390 | i18n("Could not start debugger")); | | |||
391 | | ||||
392 | /* FIXME: make sure the controller gets rids of GDB instance | | |||
393 | emit debuggerAbnormalExit(); | | |||
394 | | ||||
395 | raiseEvent(debugger_exited); */ | | |||
396 | | ||||
397 | /* Used to be before, GDB controller might want to do | | |||
398 | the same. | | |||
399 | destroyCmds(); | | |||
400 | setState(s_dbgNotStarted|s_appNotStarted|s_programExited); | | |||
401 | emit showMessage(i18n("Process didn't start"), 3000); | | |||
402 | */ | | |||
403 | emit userCommandOutput("(gdb) didn't start\n"); | | |||
404 | } else if (error == QProcess::Crashed) { | | |||
405 | KMessageBox::error( | | |||
406 | qApp->activeWindow(), | | |||
407 | i18n("<b>Gdb crashed.</b>" | | |||
408 | "<p>Because of that the debug session has to be ended.<br>" | | |||
409 | "Try to reproduce the crash with plain gdb and report a bug.<br>"), | | |||
410 | i18n("Gdb crashed")); | | |||
411 | } | | |||
412 | } | 99 | } |