Changeset View
Changeset View
Standalone View
Standalone View
debuggers/gdb/variablecontroller.cpp
- This file was copied to debuggers/common/mivariablecontroller.cpp.
1 | /* | 1 | /* | ||
---|---|---|---|---|---|
2 | * GDB Debugger Support | 2 | * GDB-specific variable controller implementation | ||
3 | * Copyright 2016 Aetf <aetf@unlimitedcodeworks.xyz> | ||||
3 | * | 4 | * | ||
4 | * Copyright 2007 Hamish Rodda <rodda@kde.org> | 5 | * This program is free software; you can redistribute it and/or | ||
5 | * Copyright 2008 Vladimir Prus <ghost@cs.msu.su> | 6 | * modify it under the terms of the GNU General Public License as | ||
6 | * Copyright 2009 Niko Sams <niko.sams@gmail.com> | 7 | * published by the Free Software Foundation; either version 2 of | ||
7 | * | 8 | * the License or (at your option) version 3 or any later version | ||
8 | * This program is free software; you can redistribute it and/or modify | 9 | * accepted by the membership of KDE e.V. (or its successor approved | ||
9 | * it under the terms of the GNU General Public License as | 10 | * by the membership of KDE e.V.), which shall act as a proxy | ||
10 | * published by the Free Software Foundation; either version 2 of the | 11 | * defined in Section 14 of version 3 of the license. | ||
11 | * License, or (at your option) any later version. | | |||
12 | * | 12 | * | ||
13 | * 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, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | 16 | * GNU General Public License for more details. | ||
17 | * | 17 | * | ||
18 | * You should have received a copy of the GNU General Public | 18 | * You should have received a copy of the GNU General Public License | ||
19 | * License along with this program; if not, write to the | 19 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
20 | * Free Software Foundation, Inc., | 20 | * | ||
21 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | | |||
22 | */ | 21 | */ | ||
23 | 22 | | |||
24 | #include "variablecontroller.h" | 23 | #include "variablecontroller.h" | ||
25 | 24 | | |||
26 | #include <debugger/variable/variablecollection.h> | | |||
27 | #include <debugger/breakpoint/breakpointmodel.h> | | |||
28 | #include <interfaces/icore.h> | | |||
29 | #include <interfaces/idebugcontroller.h> | | |||
30 | #include <debugger/interfaces/iframestackmodel.h> | | |||
31 | | ||||
32 | #include "gdbcommand.h" | | |||
33 | #include "debugsession.h" | 25 | #include "debugsession.h" | ||
34 | #include "stringhelpers.h" | | |||
35 | #include "gdbvariable.h" | | |||
36 | #include "debug.h" | | |||
37 | 26 | | |||
38 | #include <KTextEditor/Document> | 27 | using namespace KDevMI::GDB; | ||
39 | | ||||
40 | using namespace GDBDebugger; | | |||
41 | using namespace KDevelop; | | |||
42 | 28 | | |||
43 | VariableController::VariableController(DebugSession* parent) | 29 | VariableController::VariableController(DebugSession *parent) | ||
44 | : KDevelop::IVariableController(parent) | 30 | : MIVariableController(parent) | ||
45 | { | 31 | { | ||
46 | Q_ASSERT(parent); | | |||
47 | connect(parent, &DebugSession::programStopped, this, &VariableController::programStopped); | | |||
48 | connect(parent, &DebugSession::stateChanged, this, &VariableController::stateChanged); | | |||
49 | } | 32 | } | ||
50 | 33 | | |||
51 | DebugSession *VariableController::debugSession() const | 34 | DebugSession *VariableController::debugSession() const | ||
52 | { | 35 | { | ||
53 | return static_cast<DebugSession*>(const_cast<QObject*>(QObject::parent())); | 36 | return static_cast<DebugSession*>(const_cast<QObject*>(QObject::parent())); | ||
54 | } | 37 | } | ||
55 | | ||||
56 | void VariableController::programStopped(const GDBMI::AsyncRecord& r) | | |||
57 | { | | |||
58 | if (debugSession()->stateIsOn(s_shuttingDown)) return; | | |||
59 | | ||||
60 | if (r.hasField("reason") && r["reason"].literal() == "function-finished" | | |||
61 | && r.hasField("gdb-result-var")) | | |||
62 | { | | |||
63 | variableCollection()->watches()->addFinishResult(r["gdb-result-var"].literal()); | | |||
64 | } else { | | |||
65 | variableCollection()->watches()->removeFinishResult(); | | |||
66 | } | | |||
67 | } | | |||
68 | | ||||
69 | void VariableController::update() | | |||
70 | { | | |||
71 | qCDebug(DEBUGGERGDB) << autoUpdate(); | | |||
72 | if (autoUpdate() & UpdateWatches) { | | |||
73 | variableCollection()->watches()->reinstall(); | | |||
74 | } | | |||
75 | | ||||
76 | if (autoUpdate() & UpdateLocals) { | | |||
77 | updateLocals(); | | |||
78 | } | | |||
79 | | ||||
80 | if ((autoUpdate() & UpdateLocals) || | | |||
81 | ((autoUpdate() & UpdateWatches) && variableCollection()->watches()->childCount() > 0)) | | |||
82 | { | | |||
83 | debugSession()->addCommand( | | |||
84 | new GDBCommand(GDBMI::VarUpdate, "--all-values *", this, | | |||
85 | &VariableController::handleVarUpdate)); | | |||
86 | } | | |||
87 | } | | |||
88 | | ||||
89 | void VariableController::handleVarUpdate(const GDBMI::ResultRecord& r) | | |||
90 | { | | |||
91 | const GDBMI::Value& changed = r["changelist"]; | | |||
92 | for (int i = 0; i < changed.size(); ++i) | | |||
93 | { | | |||
94 | const GDBMI::Value& var = changed[i]; | | |||
95 | GdbVariable* v = GdbVariable::findByVarobjName(var["name"].literal()); | | |||
96 | // v can be NULL here if we've already removed locals after step, | | |||
97 | // but the corresponding -var-delete command is still in the queue. | | |||
98 | if (v) { | | |||
99 | v->handleUpdate(var); | | |||
100 | } | | |||
101 | } | | |||
102 | } | | |||
103 | class StackListArgumentsHandler : public GDBCommandHandler | | |||
104 | { | | |||
105 | public: | | |||
106 | StackListArgumentsHandler(QStringList localsName) | | |||
107 | : m_localsName(localsName) | | |||
108 | {} | | |||
109 | | ||||
110 | void handle(const GDBMI::ResultRecord &r) override | | |||
111 | { | | |||
112 | if (!KDevelop::ICore::self()->debugController()) return; //happens on shutdown | | |||
113 | | ||||
114 | // FIXME: handle error. | | |||
115 | const GDBMI::Value& locals = r["stack-args"][0]["args"]; | | |||
116 | | ||||
117 | for (int i = 0; i < locals.size(); i++) { | | |||
118 | m_localsName << locals[i].literal(); | | |||
119 | } | | |||
120 | QList<Variable*> variables = KDevelop::ICore::self()->debugController()->variableCollection() | | |||
121 | ->locals()->updateLocals(m_localsName); | | |||
122 | foreach (Variable *v, variables) { | | |||
123 | v->attachMaybe(); | | |||
124 | } | | |||
125 | } | | |||
126 | | ||||
127 | private: | | |||
128 | QStringList m_localsName; | | |||
129 | }; | | |||
130 | | ||||
131 | class StackListLocalsHandler : public GDBCommandHandler | | |||
132 | { | | |||
133 | public: | | |||
134 | StackListLocalsHandler(DebugSession *session) | | |||
135 | : m_session(session) | | |||
136 | {} | | |||
137 | | ||||
138 | void handle(const GDBMI::ResultRecord &r) override | | |||
139 | { | | |||
140 | // FIXME: handle error. | | |||
141 | | ||||
142 | const GDBMI::Value& locals = r["locals"]; | | |||
143 | | ||||
144 | QStringList localsName; | | |||
145 | for (int i = 0; i < locals.size(); i++) { | | |||
146 | const GDBMI::Value& var = locals[i]; | | |||
147 | localsName << var["name"].literal(); | | |||
148 | } | | |||
149 | int frame = m_session->frameStackModel()->currentFrame(); | | |||
150 | m_session->addCommand( //dont'show value, low-frame, high-frame | | |||
151 | new GDBCommand(GDBMI::StackListArguments, QString("0 %1 %2").arg(frame).arg(frame), | | |||
152 | new StackListArgumentsHandler(localsName))); | | |||
153 | } | | |||
154 | | ||||
155 | private: | | |||
156 | DebugSession *m_session; | | |||
157 | }; | | |||
158 | | ||||
159 | void VariableController::updateLocals() | | |||
160 | { | | |||
161 | debugSession()->addCommand( | | |||
162 | new GDBCommand(GDBMI::StackListLocals, "--simple-values", | | |||
163 | new StackListLocalsHandler(debugSession()))); | | |||
164 | } | | |||
165 | | ||||
166 | KTextEditor::Range VariableController::expressionRangeUnderCursor(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor) | | |||
167 | { | | |||
168 | QString line = doc->line(cursor.line()); | | |||
169 | int index = cursor.column(); | | |||
170 | QChar c = line[index]; | | |||
171 | if (!c.isLetterOrNumber() && c != '_') | | |||
172 | return {}; | | |||
173 | | ||||
174 | int start = Utils::expressionAt(line, index+1); | | |||
175 | int end = index; | | |||
176 | for (; end < line.size(); ++end) | | |||
177 | { | | |||
178 | QChar c = line[end]; | | |||
179 | if (!(c.isLetterOrNumber() || c == '_')) | | |||
180 | break; | | |||
181 | } | | |||
182 | if (!(start < end)) | | |||
183 | return {}; | | |||
184 | | ||||
185 | return { KTextEditor::Cursor{cursor.line(), start}, KTextEditor::Cursor{cursor.line(), end} }; | | |||
186 | } | | |||
187 | | ||||
188 | | ||||
189 | void VariableController::addWatch(KDevelop::Variable* variable) | | |||
190 | { | | |||
191 | // FIXME: should add async 'get full expression' method | | |||
192 | // to GdbVariable, not poke at varobj. In that case, | | |||
193 | // we will be able to make addWatch a generic method, not | | |||
194 | // gdb-specific one. | | |||
195 | if (GdbVariable *gv = dynamic_cast<GdbVariable*>(variable)) | | |||
196 | { | | |||
197 | debugSession()->addCommand( | | |||
198 | new GDBCommand(GDBMI::VarInfoPathExpression, | | |||
199 | gv->varobj(), | | |||
200 | this, | | |||
201 | &VariableController::addWatch)); | | |||
202 | } | | |||
203 | } | | |||
204 | | ||||
205 | void VariableController::addWatchpoint(KDevelop::Variable* variable) | | |||
206 | { | | |||
207 | // FIXME: should add async 'get full expression' method | | |||
208 | // to GdbVariable, not poke at varobj. In that case, | | |||
209 | // we will be able to make addWatchpoint a generic method, not | | |||
210 | // gdb-specific one. | | |||
211 | if (GdbVariable *gv = dynamic_cast<GdbVariable*>(variable)) | | |||
212 | { | | |||
213 | debugSession()->addCommand( | | |||
214 | new GDBCommand(GDBMI::VarInfoPathExpression, | | |||
215 | gv->varobj(), | | |||
216 | this, | | |||
217 | &VariableController::addWatchpoint)); | | |||
218 | } | | |||
219 | } | | |||
220 | | ||||
221 | void VariableController::addWatch(const GDBMI::ResultRecord& r) | | |||
222 | { | | |||
223 | // FIXME: handle error. | | |||
224 | if (r.reason == "done" && !r["path_expr"].literal().isEmpty()) { | | |||
225 | variableCollection()->watches()->add(r["path_expr"].literal()); | | |||
226 | } | | |||
227 | } | | |||
228 | | ||||
229 | void VariableController::addWatchpoint(const GDBMI::ResultRecord& r) | | |||
230 | { | | |||
231 | if (r.reason == "done" && !r["path_expr"].literal().isEmpty()) { | | |||
232 | KDevelop::ICore::self()->debugController()->breakpointModel()->addWatchpoint(r["path_expr"].literal()); | | |||
233 | } | | |||
234 | } | | |||
235 | | ||||
236 | KDevelop::Variable* VariableController:: | | |||
237 | createVariable(TreeModel* model, TreeItem* parent, | | |||
238 | const QString& expression, const QString& display) | | |||
239 | { | | |||
240 | return new GdbVariable(model, parent, expression, display); | | |||
241 | } | | |||
242 | | ||||
243 | void VariableController::handleEvent(IDebugSession::event_t event) | | |||
244 | { | | |||
245 | IVariableController::handleEvent(event); | | |||
246 | } | | |||
247 | | ||||
248 | void VariableController::stateChanged(IDebugSession::DebuggerState state) | | |||
249 | { | | |||
250 | if (state == IDebugSession::EndedState) { | | |||
251 | GdbVariable::markAllDead(); | | |||
252 | } | | |||
253 | } | | |||
254 | | ||||
255 | | ||||
256 | |