1 module ddebug.common.debugger;
2 
3 import core.thread;
4 import dlangui.core.logger;
5 import ddebug.common.queue;
6 import ddebug.common.execution;
7 import std.array : empty;
8 import std.algorithm : startsWith, endsWith, equal;
9 import std.string : format;
10 
11 enum DebuggingState {
12     loaded,
13     running,
14     paused,
15     stopped
16 }
17 
18 enum StateChangeReason {
19     unknown,
20     breakpointHit,
21     endSteppingRange,
22     exception,
23     exited,
24 }
25 
26 class LocationBase {
27     string file;
28     string fullFilePath;
29     string projectFilePath;
30     string from;
31     int line;
32     this() {}
33     this(LocationBase v) {
34         file = v.file;
35         fullFilePath = v.fullFilePath;
36         projectFilePath = v.projectFilePath;
37         line = v.line;
38         from = v.from;
39     }
40     LocationBase clone() { return new LocationBase(this); }
41 }
42 
43 class Breakpoint : LocationBase {
44     int id;
45     bool enabled = true;
46     string projectName;
47     this() {
48         id = _nextBreakpointId++;
49     }
50     this(Breakpoint v) {
51         super(v);
52         id = v.id;
53         enabled = v.enabled;
54         projectName = v.projectName;
55     }
56     override Breakpoint clone() {
57         return new Breakpoint(this);
58     }
59 }
60 
61 class DebugFrame : LocationBase {
62     ulong address;
63     string func;
64     int level;
65     DebugVariableList locals;
66 
67     @property string formattedAddress() {
68         if (address < 0x100000000) {
69             return "%08x".format(address);
70         } else {
71             return "%016x".format(address);
72         }
73     }
74 
75     this() {}
76     this(DebugFrame v) {
77         super(v);
78         address = v.address;
79         func = v.func;
80         level = v.level;
81         if (v.locals)
82             locals = new DebugVariableList(v.locals);
83     }
84     override DebugFrame clone() { return new DebugFrame(this); }
85 
86     void fillMissingFields(LocationBase v) {
87         if (file.empty)
88             file = v.file;
89         if (fullFilePath.empty)
90             fullFilePath = v.fullFilePath;
91         if (projectFilePath.empty)
92             projectFilePath = v.projectFilePath;
93         if (!line)
94             line = v.line;
95     }
96 }
97 
98 class DebugThread {
99     ulong id;
100     string name;
101     DebugFrame frame;
102     DebuggingState state;
103     DebugStack stack;
104 
105     this() {
106     }
107     this(DebugThread v) {
108         id = v.id;
109         name = v.name;
110         if (v.frame)
111             frame = new DebugFrame(v.frame);
112         state = v.state;
113         if (v.stack)
114             stack = new DebugStack(v.stack);
115     }
116     DebugThread clone() { return new DebugThread(this); }
117 
118     @property string displayName() {
119         return "%u: %s".format(id, name);
120     }
121 
122     @property int length() { 
123         if (stack && stack.length > 0)
124             return stack.length;
125         if (frame)
126             return 1;
127         return 0; 
128     }
129     DebugFrame opIndex(int index) { 
130         if (index < 0 || index > length)
131             return null;
132         if (stack && stack.length > 0)
133             return stack[index];
134         if (frame && index == 0)
135             return frame;
136         return null; 
137     }
138 }
139 
140 class DebugThreadList {
141     DebugThread[] threads;
142     ulong currentThreadId;
143     this() {}
144     this(DebugThreadList v) {
145         currentThreadId = v.currentThreadId;
146         foreach(t; v.threads)
147             threads ~= new DebugThread(t);
148     }
149     DebugThreadList clone() { return new DebugThreadList(this); }
150 
151     @property DebugThread currentThread() {
152         return findThread(currentThreadId);
153     }
154     DebugThread findThread(ulong id) {
155         foreach(t; threads)
156             if (t.id == id)
157                 return t;
158         return null;
159     }
160     @property int length() { return cast(int)threads.length; }
161     DebugThread opIndex(int index) { return threads[index]; }
162 }
163 
164 class DebugStack {
165     DebugFrame[] frames;
166 
167     this() {}
168     this(DebugStack v) {
169         foreach(t; v.frames)
170             frames ~= new DebugFrame(t);
171     }
172 
173     @property int length() { return cast(int)frames.length; }
174     DebugFrame opIndex(int index) { return frames[index]; }
175 }
176 
177 class DebugVariable {
178     string name;
179     string type;
180     string value;
181     DebugVariable[] children;
182 
183     this() {}
184     /// deep copy
185     this(DebugVariable v) {
186         name = v.name;
187         type = v.type;
188         value = v.value;
189         // deep copy of child vars
190         foreach(item; v.children)
191             children ~= new DebugVariable(item);
192     }
193 }
194 
195 class DebugVariableList {
196     DebugVariable[] variables;
197     this() {}
198     this(DebugVariableList v) {
199         foreach(t; v.variables)
200             variables ~= new DebugVariable(t);
201     }
202 
203     @property int length() { return variables ? cast(int)variables.length : 0; }
204 
205     DebugVariable opIndex(int index) { 
206         if (!variables || index < 0 || index > variables.length)
207             return null;
208         return variables[index];
209     }
210 }
211 
212 static __gshared _nextBreakpointId = 1;
213 
214 interface Debugger : ProgramExecution {
215     void setDebuggerCallback(DebuggerCallback callback);
216     void setDebuggerExecutable(string debuggerExecutable);
217 
218     /// can be called after program is loaded
219     void execStart();
220     /// continue execution
221     void execContinue();
222     /// stop program execution
223     void execStop();
224     /// interrupt execution
225     void execPause();
226     /// step over
227     void execStepOver(ulong threadId);
228     /// step in
229     void execStepIn(ulong threadId);
230     /// step out
231     void execStepOut(ulong threadId);
232     /// restart
233     void execRestart();
234 
235     /// update list of breakpoints
236     void setBreakpoints(Breakpoint[] bp);
237 
238     /// request stack trace and local vars for thread and frame
239     void requestDebugContextInfo(ulong threadId, int frame);
240 }
241 
242 interface DebuggerCallback : ProgramExecutionStatusListener {
243     /// debugger message line
244     void onDebuggerMessage(string msg);
245 
246     /// debugger is started and loaded program, you can set breakpoints at this time
247     void onProgramLoaded(bool successful, bool debugInfoLoaded);
248 
249     /// state changed: running / paused / stopped
250     void onDebugState(DebuggingState state, StateChangeReason reason, DebugFrame location, Breakpoint bp);
251 
252     void onResponse(ResponseCode code, string msg);
253 
254     /// send debug context (threads, stack frames, local vars...)
255     void onDebugContextInfo(DebugThreadList info, ulong threadId, int frame);
256 }
257 
258 enum ResponseCode : int {
259     /// Operation finished successfully
260     Ok = 0,
261 
262     // more success codes here
263 
264     /// General purpose failure code
265     Fail = 1000,
266     /// method is not implemented
267     NotImplemented,
268     /// error running debugger
269     CannotRunDebugger,
270 
271     // more error codes here
272 }
273 
274 alias Runnable = void delegate();
275 
276 //interface Debugger {
277 //    /// start debugging
278 //    void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response);
279 //}
280 
281 
282 
283 /// proxy for debugger interface implementing async calls
284 class DebuggerProxy : Debugger, DebuggerCallback {
285     private DebuggerBase _debugger;
286     private void delegate(void delegate() runnable) _callbackDelegate;
287 
288     this(DebuggerBase debugger, void delegate(void delegate() runnable) callbackDelegate) {
289         _debugger = debugger;
290         _callbackDelegate = callbackDelegate;
291     }
292 
293     /// returns true if it's debugger
294     @property bool isDebugger() { return true; }
295     /// returns true if it's mago debugger
296     @property bool isMagoDebugger() { return _debugger.isMagoDebugger; }
297 
298     /// executable file
299     @property string executableFile() { return _debugger.executableFile; }
300     /// returns execution status
301     //@property ExecutionStatus status();
302 
303     void setExecutableParams(string executableFile, string[] args, string workingDir, string[string] envVars) {
304         _debugger.setExecutableParams(executableFile, args, workingDir, envVars);
305     }
306 
307     /// set external terminal parameters before execution
308     void setTerminalExecutable(string terminalExecutable) {
309         _debugger.setTerminalExecutable(terminalExecutable);
310     }
311 
312     /// set terminal device name before execution
313     void setTerminalTty(string terminalTty) {
314         _debugger.setTerminalTty(terminalTty);
315     }
316 
317     /// set debugger executable
318     void setDebuggerExecutable(string debuggerExecutable) {
319         _debugger.setDebuggerExecutable(debuggerExecutable);
320     }
321 
322     protected DebuggerCallback _callback;
323     /// set debugger callback
324     void setDebuggerCallback(DebuggerCallback callback) {
325         _callback = callback;
326         _debugger.setDebuggerCallback(this);
327     }
328 
329     /// called when program execution is stopped
330     void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode) {
331         DebuggerProxy proxy = this;
332         _callbackDelegate( delegate() { _callback.onProgramExecutionStatus(proxy, status, exitCode); } );
333     }
334 
335     /// debugger is started and loaded program, you can set breakpoints at this time
336     void onProgramLoaded(bool successful, bool debugInfoLoaded) {
337         _callbackDelegate( delegate() { _callback.onProgramLoaded(successful, debugInfoLoaded); } );
338     }
339 
340     /// state changed: running / paused / stopped
341     void onDebugState(DebuggingState state, StateChangeReason reason, DebugFrame location, Breakpoint bp) {
342         _callbackDelegate( delegate() { _callback.onDebugState(state, reason, location, bp); } );
343     }
344 
345     /// send debug context (threads, stack frames, local vars...)
346     void onDebugContextInfo(DebugThreadList info, ulong threadId, int frame) {
347         _callbackDelegate( delegate() { _callback.onDebugContextInfo(info, threadId, frame); } );
348     }
349 
350     void onResponse(ResponseCode code, string msg) {
351         _callbackDelegate( delegate() { _callback.onResponse(code, msg); } );
352     }
353 
354     void onDebuggerMessage(string msg) {
355         _callbackDelegate( delegate() { _callback.onDebuggerMessage(msg); } );
356     }
357 
358     /// start execution
359     void run() {
360         Log.d("DebuggerProxy.run()");
361         _debugger.run();
362         //_debugger.postRequest(delegate() { _debugger.run();    });
363     }
364     /// stop execution
365     void stop() {
366         Log.d("DebuggerProxy.stop()");
367         _debugger.stop();
368         //_debugger.postRequest(delegate() { _debugger.stop(); });
369     }
370 
371     /// start execution, can be called after program is loaded
372     void execStart() {
373         _debugger.postRequest(delegate() { _debugger.execStart(); });
374     }
375     /// continue program
376     void execContinue() {
377         _debugger.postRequest(delegate() { _debugger.execContinue(); });
378     }
379     /// stop program execution
380     void execStop() {
381         _debugger.postRequest(delegate() { _debugger.execStop(); });
382     }
383     /// interrupt execution
384     void execPause() {
385         _debugger.postRequest(delegate() { _debugger.execPause(); });
386     }
387     /// step over
388     void execStepOver(ulong threadId) {
389         _debugger.postRequest(delegate() { _debugger.execStepOver(threadId); });
390     }
391     /// step in
392     void execStepIn(ulong threadId) {
393         _debugger.postRequest(delegate() { _debugger.execStepIn(threadId); });
394     }
395     /// step out
396     void execStepOut(ulong threadId) {
397         _debugger.postRequest(delegate() { _debugger.execStepOut(threadId); });
398     }
399     /// restart
400     void execRestart() {
401         _debugger.postRequest(delegate() { _debugger.execRestart(); });
402     }
403     /// request stack trace and local vars for thread and frame
404     void requestDebugContextInfo(ulong threadId, int frame) {
405         _debugger.postRequest(delegate() { _debugger.requestDebugContextInfo(threadId, frame); });
406     }
407     /// update list of breakpoints
408     void setBreakpoints(Breakpoint[] breakpoints) {
409         Breakpoint[] cloned;
410         foreach(bp; breakpoints)
411             cloned ~= bp.clone;
412         _debugger.postRequest(delegate() { _debugger.setBreakpoints(cloned); });
413     }
414 }
415 
416 abstract class DebuggerBase : Thread, Debugger {
417     protected bool _runRequested;
418     protected bool _stopRequested;
419     private bool _finished;
420     protected BlockingQueue!Runnable _queue;
421 
422     protected ExecutionStatus _status = ExecutionStatus.NotStarted;
423     protected int _exitCode = 0;
424 
425     /// provides _executableFile, _executableArgs, _executableWorkingDir, _executableEnvVars parameters and setter function setExecutableParams
426     mixin ExecutableParams;
427     /// provides _terminalExecutable, _terminalTty, setTerminalExecutable, and setTerminalTty
428     mixin TerminalParams;
429 
430     protected DebuggerCallback _callback;
431     void setDebuggerCallback(DebuggerCallback callback) {
432         _callback = callback;
433     }
434 
435     protected string _debuggerExecutable;
436     void setDebuggerExecutable(string debuggerExecutable) {
437         _debuggerExecutable = debuggerExecutable;
438     }
439 
440     /// returns true if it's mago debugger
441     @property bool isMagoDebugger() {
442         import std.string;
443         return _debuggerExecutable.indexOf("mago-mi") >= 0;
444     }
445 
446 
447     @property bool isDebugger() { return true; }
448 
449     @property string executableFile() {
450         return _executableFile;
451     }
452 
453     void postRequest(Runnable request) {
454         _queue.put(request);
455     }
456 
457     this() {
458         super(&threadFunc);
459         _queue = new BlockingQueue!Runnable();
460     }
461 
462     ~this() {
463         //stop();
464         //destroy(_queue);
465         _queue = null;
466     }
467 
468     // call from GUI thread
469     void run() {
470         Log.d("DebuggerBase.run()");
471         assert(!_runRequested);
472         _runRequested = true;
473         postRequest(&startDebugging);
474         start();
475     }
476 
477     void startDebugging() {
478         // override to implement
479     }
480 
481     void stop() {
482         Log.i("Debugger.stop()");
483         if (_stopRequested)
484             return;
485         _stopRequested = true;
486         _queue.close();
487     }
488 
489     bool _threadStarted;
490     protected void onDebuggerThreadStarted() {
491         _threadStarted = true;
492     }
493 
494     protected void onDebuggerThreadFinished() {
495         _callback.onProgramExecutionStatus(this, _status, _exitCode);
496     }
497     
498     /// thread func: execute all tasks from queue
499     private void threadFunc() {
500         onDebuggerThreadStarted();
501         Log.i("Debugger thread started");
502         try {
503             while (!_stopRequested) {
504                 Runnable task;
505                 if (_queue.get(task, 0)) {
506                     task();
507                 }
508             }
509         } catch (Exception e) {
510             Log.e("Exception in debugger thread", e);
511         }
512         Log.i("Debugger thread finished");
513         _finished = true;
514         onDebuggerThreadFinished();
515     }
516 
517 }
518 
519 /// helper for removing class array item by ref
520 T removeItem(T)(ref T[]array, T item) {
521     for (int i = cast(int)array.length - 1; i >= 0; i--) {
522         if (array[i] is item) {
523             for (int j = i; j < array.length - 1; j++)
524                 array[j] = array[j + 1];
525             array.length = array.length - 1;
526             return item;
527         }
528     }
529     return null;
530 }
531 
532 /// helper for removing array item by index
533 T removeItem(T)(ref T[]array, ulong index) {
534     if (index >= 0 && index < array.length) {
535         T res = array[index];
536         for (int j = cast(int)index; j < array.length - 1; j++)
537             array[j] = array[j + 1];
538         array.length = array.length - 1;
539         return res;
540     }
541     return null;
542 }
543