1 module ddebug.gdb.gdbinterface;
2 
3 public import ddebug.common.debugger;
4 import ddebug.common.execution;
5 import dlangui.core.logger;
6 import ddebug.common.queue;
7 import dlangide.builders.extprocess;
8 import ddebug.gdb.gdbmiparser;
9 import std.utf;
10 import std.conv : to;
11 import std.array : empty;
12 import std.algorithm : startsWith, endsWith, equal;
13 import core.thread;
14 
15 abstract class ConsoleDebuggerInterface : DebuggerBase, TextWriter {
16     protected ExternalProcess _debuggerProcess;
17 
18     protected ExternalProcessState runDebuggerProcess(string executable, string[]args, string dir) {
19         _debuggerProcess = new ExternalProcess();
20         ExternalProcessState state = _debuggerProcess.run(executable, args, dir, this);
21         return state;
22     }
23 
24     private string[] _stdoutLines;
25     private char[] _stdoutBuf;
26     /// return true to clear lines list
27     protected bool onDebuggerStdoutLines(string[] lines) {
28         foreach(line; lines) {
29             onDebuggerStdoutLine(line);
30         }
31         return true;
32     }
33     protected void onDebuggerStdoutLine(string line) {
34     }
35     private void onStdoutText(string text) {
36         Log.v("onStdoutText: ", text);
37         _stdoutBuf ~= text;
38         // pass full lines
39         int startPos = 0;
40         bool fullLinesFound = false;
41         for (int i = 0; i < _stdoutBuf.length; i++) {
42             if (_stdoutBuf[i] == '\n' || _stdoutBuf[i] == '\r') {
43                 if (i <= startPos)
44                     _stdoutLines ~= "";
45                 else
46                     _stdoutLines ~= _stdoutBuf[startPos .. i].dup;
47                 fullLinesFound = true;
48                 if (i + 1 < _stdoutBuf.length) {
49                     if ((_stdoutBuf[i] == '\n' && _stdoutBuf[i + 1] == '\r')
50                             || (_stdoutBuf[i] == '\r' && _stdoutBuf[i + 1] == '\n'))
51                         i++;
52                 }
53                 startPos = i + 1;
54             }
55         }
56         if (fullLinesFound) {
57             //Log.v("onStdoutText: full lines found");
58             for (int i = 0; i + startPos < _stdoutBuf.length; i++)
59                 _stdoutBuf[i] = _stdoutBuf[i + startPos];
60             _stdoutBuf.length = _stdoutBuf.length - startPos;
61             if (onDebuggerStdoutLines(_stdoutLines)) {
62                 _stdoutLines.length = 0;
63             }
64         }
65     }
66 
67     bool sendLine(string text) {
68         return _debuggerProcess.write(text ~ "\n");
69     }
70 
71     /// log lines
72     override void writeText(dstring text) {
73         string text8 = toUTF8(text);
74         postRequest(delegate() {
75                 onStdoutText(text8);
76         });
77     }
78 
79 }
80 
81 interface TextCommandTarget {
82     /// send command as a text string
83     int sendCommand(string text, int commandId = 0);
84     /// reserve next command id
85     int reserveCommandId();
86 }
87 
88 import std.process;
89 class GDBInterface : ConsoleDebuggerInterface, TextCommandTarget {
90 
91     this() {
92         _requests.setTarget(this);
93     }
94 
95     // last command id
96     private int _commandId;
97 
98     int reserveCommandId() {
99         _commandId++;
100         return _commandId;
101     }
102 
103     int sendCommand(string text, int id = 0) {
104         ExternalProcessState state = _debuggerProcess.poll();
105         if (state != ExternalProcessState.Running) {
106             _stopRequested = true;
107             return 0;
108         }
109         if (!id)
110             id = reserveCommandId();
111         string cmd = to!string(id) ~ text;
112         Log.d("GDB command[", id, "]> ", text);
113         sendLine(cmd);
114         return id;
115     }
116 
117     Pid terminalPid;
118     string externalTerminalTty;
119 
120     string startTerminal() {
121         if (!_terminalTty.empty)
122             return _terminalTty;
123         Log.d("Starting terminal ", _terminalExecutable);
124         import std.random;
125         import std.file;
126         import std.path;
127         import std..string;
128         import core.thread;
129         uint n = uniform(0, 0x10000000, rndGen());
130         externalTerminalTty = null;
131         string termfile = buildPath(tempDir, format("dlangide-term-name-%07x.tmp", n));
132         Log.d("temp file for tty name: ", termfile);
133         try {
134             string[] args = [
135                 _terminalExecutable,
136                 "-title",
137                 "DLangIDE External Console",
138                 "-e",
139                 "echo 'DlangIDE External Console' && tty > " ~ termfile ~ " && sleep 1000000"
140             ];
141             Log.d("Terminal command line: ", args);
142             terminalPid = spawnProcess(args);
143             for (int i = 0; i < 80; i++) {
144                 Thread.sleep(dur!"msecs"(100));
145                 if (!isTerminalActive) {
146                     Log.e("Failed to get terminal TTY");
147                     return null;
148                 }
149                 if (exists(termfile)) {
150                     Thread.sleep(dur!"msecs"(20));
151                     break;
152                 }
153             }
154             // read TTY from file
155             if (exists(termfile)) {
156                 externalTerminalTty = readText(termfile);
157                 if (externalTerminalTty.endsWith("\n"))
158                     externalTerminalTty = externalTerminalTty[0 .. $-1];
159                 // delete file
160                 remove(termfile);
161                 Log.d("Terminal tty: ", externalTerminalTty);
162             }
163         } catch (Exception e) {
164             Log.e("Failed to start terminal ", e);
165             killTerminal();
166         }
167         if (externalTerminalTty.length == 0) {
168             Log.i("Cannot start terminal");
169             killTerminal();
170         } else {
171             Log.i("Terminal: ", externalTerminalTty);
172         }
173         return externalTerminalTty;
174     }
175 
176     bool isTerminalActive() {
177         if (!_terminalTty.empty)
178             return true;
179         if (_terminalExecutable.empty)
180             return true;
181         if (terminalPid is null)
182             return false;
183         auto res = tryWait(terminalPid);
184         if (res.terminated) {
185             Log.d("isTerminalActive: Terminal is stopped");
186             wait(terminalPid);
187             terminalPid = Pid.init;
188             return false;
189         } else {
190             return true;
191         }
192     }
193 
194     void killTerminal() {
195         if (!_terminalTty.empty)
196             return;
197         if (_terminalExecutable.empty)
198             return;
199         if (terminalPid is null)
200             return;
201         try {
202             Log.d("Trying to kill terminal");
203             kill(terminalPid, 9);
204             Log.d("Waiting for terminal process termination");
205             wait(terminalPid);
206             terminalPid = Pid.init;
207             Log.d("Killed");
208         } catch (Exception e) {
209             Log.d("Exception while killing terminal", e);
210             terminalPid = Pid.init;
211         }
212     }
213 
214     override void startDebugging() {
215         Log.d("GDBInterface.startDebugging()");
216         string[] debuggerArgs;
217         if (!_terminalExecutable.empty || !_terminalTty.empty) {
218             externalTerminalTty = startTerminal();
219             if (externalTerminalTty.length == 0) {
220                 //_callback.onResponse(ResponseCode.CannotRunDebugger, "Cannot start terminal");
221                 _status = ExecutionStatus.Error;
222                 _stopRequested = true;
223                 return;
224             }
225             if (!USE_INIT_SEQUENCE) {
226                 debuggerArgs ~= "-tty";
227                 debuggerArgs ~= externalTerminalTty;
228             }
229         }
230         debuggerArgs ~= "--interpreter";
231         debuggerArgs ~= "mi2";
232         debuggerArgs ~= "--silent";
233         if (!USE_INIT_SEQUENCE) {
234             debuggerArgs ~= "--args";
235             debuggerArgs ~= _executableFile;
236             foreach(arg; _executableArgs)
237                 debuggerArgs ~= arg;
238         }
239         ExternalProcessState state = runDebuggerProcess(_debuggerExecutable, debuggerArgs, _executableWorkingDir);
240         Log.i("Debugger process state:", state);
241         if (USE_INIT_SEQUENCE) {
242             if (state == ExternalProcessState.Running) {
243                 submitInitRequests();
244             } else {
245                 _status = ExecutionStatus.Error;
246                 _stopRequested = true;
247                 return;
248             }
249         } else {
250             if (state == ExternalProcessState.Running) {
251                 Thread.sleep(dur!"seconds"(1));
252                 _callback.onProgramLoaded(true, true);
253                 //sendCommand("-break-insert main");
254             } else {
255                 _status = ExecutionStatus.Error;
256                 _stopRequested = true;
257                 return;
258             }
259         }
260     }
261 
262     immutable bool USE_INIT_SEQUENCE = true;
263 
264     override protected void onDebuggerThreadFinished() {
265         Log.d("Debugger thread finished");
266         if (_debuggerProcess !is null) {
267             Log.d("Killing debugger process");
268             _debuggerProcess.kill();
269             Log.d("Waiting for debugger process finishing");
270             //_debuggerProcess.wait();
271         }
272         killTerminal();
273         Log.d("Sending execution status");
274         _callback.onProgramExecutionStatus(this, _status, _exitCode);
275     }
276 
277     bool _threadJoined = false;
278     override void stop() {
279         if (_stopRequested) {
280             Log.w("GDBInterface.stop() - _stopRequested flag already set");
281             return;
282         }
283         _stopRequested = true;
284         Log.d("GDBInterface.stop()");
285         postRequest(delegate() {
286             Log.d("GDBInterface.stop() processing in queue");
287             execStop();
288         });
289         Thread.sleep(dur!"msecs"(200));
290         postRequest(delegate() {
291         });
292         _queue.close();
293         if (!_threadJoined) {
294             _threadJoined = true;
295             if (_threadStarted) {
296                 try {
297                     join();
298                 } catch (Exception e) {
299                     Log.e("Exception while trying to join debugger thread");
300                 }
301             }
302         }
303     }
304 
305     /// start program execution, can be called after program is loaded
306     int _startRequestId;
307     void execStart() {
308         submitRequest("handle SIGUSR1 nostop noprint");
309         submitRequest("handle SIGUSR2 nostop noprint");
310         _startRequestId = submitRequest("-exec-run");
311     }
312 
313     void execAbort() {
314         _startRequestId = submitRequest("-exec-abort");
315     }
316 
317     /// start program execution, can be called after program is loaded
318     int _continueRequestId;
319     void execContinue() {
320         _continueRequestId = submitRequest("-exec-continue");
321     }
322 
323     /// stop program execution
324     int _stopRequestId;
325     void execStop() {
326         _continueRequestId = submitRequest("-gdb-exit");
327     }
328     /// interrupt execution
329     int _pauseRequestId;
330     void execPause() {
331         _pauseRequestId = submitRequest("-exec-interrupt", true);
332     }
333 
334     /// step over
335     int _stepOverRequestId;
336     void execStepOver(ulong threadId) {
337         _stepOverRequestId = submitRequest("-exec-next".appendThreadParam(threadId));
338     }
339     /// step in
340     int _stepInRequestId;
341     void execStepIn(ulong threadId) {
342         _stepInRequestId = submitRequest("-exec-step".appendThreadParam(threadId));
343     }
344     /// step out
345     int _stepOutRequestId;
346     void execStepOut(ulong threadId) {
347         _stepOutRequestId = submitRequest("-exec-finish".appendThreadParam(threadId));
348     }
349     /// restart
350     int _restartRequestId;
351     void execRestart() {
352         //_restartRequestId = sendCommand("-exec-restart");
353     }
354 
355     private GDBBreakpoint[] _breakpoints;
356     private static class GDBBreakpoint {
357         Breakpoint bp;
358         string number;
359         int createRequestId;
360     }
361     private GDBBreakpoint findBreakpoint(Breakpoint bp) {
362         foreach(gdbbp; _breakpoints) {
363             if (gdbbp.bp.id == bp.id)
364                 return gdbbp;
365         }
366         return null;
367     }
368 
369     private GDBBreakpoint findBreakpointByNumber(string number) {
370         if (number.empty)
371             return null;
372         foreach(gdbbp; _breakpoints) {
373             if (gdbbp.number.equal(number))
374                 return gdbbp;
375         }
376         return null;
377     }
378 
379     static string quotePathIfNeeded(string s) {
380         char[] buf;
381         buf.assumeSafeAppend();
382         bool hasSpaces = false;
383         for(uint i = 0; i < s.length; i++) {
384             if (s[i] == ' ')
385                 hasSpaces = true;
386         }
387         if (hasSpaces)
388             buf ~= '\"';
389         for(uint i = 0; i < s.length; i++) {
390             char ch = s[i];
391             if (ch == '\t')
392                 buf ~= "\\t";
393             else if (ch == '\n')
394                 buf ~= "\\n";
395             else if (ch == '\r')
396                 buf ~= "\\r";
397             else if (ch == '\\')
398                 buf ~= "\\\\";
399             else 
400                 buf ~= ch;
401         }
402         if (hasSpaces)
403             buf ~= '\"';
404         return buf.dup;
405     }
406 
407     class AddBreakpointRequest : GDBRequest {
408         GDBBreakpoint gdbbp;
409         this(Breakpoint bp) { 
410             gdbbp = new GDBBreakpoint();
411             gdbbp.bp = bp;
412             char[] cmd;
413             cmd ~= "-break-insert ";
414             if (!bp.enabled)
415                 cmd ~= "-d "; // create disabled
416             cmd ~= quotePathIfNeeded(bp.fullFilePath);
417             cmd ~= ":";
418             cmd ~= to!string(bp.line);
419             command = cmd.dup; 
420             _breakpoints ~= gdbbp;
421         }
422 
423         override void onResult() {
424             if (MIValue bkpt = params["bkpt"]) {
425                 string number = bkpt.getString("number");
426                 gdbbp.number = number;
427                 Log.d("GDB number for breakpoint " ~ gdbbp.bp.id.to!string ~ " assigned is " ~ number);
428             }
429         }
430     }
431 
432     /// update list of breakpoints
433     void setBreakpoints(Breakpoint[] breakpoints) {
434         char[] breakpointsToDelete;
435         char[] breakpointsToEnable;
436         char[] breakpointsToDisable;
437         // checking for removed breakpoints
438         for (int i = cast(int)_breakpoints.length - 1; i >= 0; i--) {
439             bool found = false;
440             foreach(bp; breakpoints)
441                 if (bp.id == _breakpoints[i].bp.id) {
442                     found = true;
443                     break;
444                 }
445             if (!found) {
446                 for (int j = i; j < _breakpoints.length - 1; j++)
447                     _breakpoints[j] = _breakpoints[j + 1];
448                 if (breakpointsToDelete.length)
449                     breakpointsToDelete ~= ",";
450                 breakpointsToDelete ~= _breakpoints[i].number;
451                 _breakpoints.length = _breakpoints.length - 1;
452             }
453         }
454         // checking for added or updated breakpoints
455         foreach(bp; breakpoints) {
456             GDBBreakpoint existing = findBreakpoint(bp);
457             if (!existing) {
458                 submitRequest(new AddBreakpointRequest(bp));
459             } else {
460                 if (bp.enabled && !existing.bp.enabled) {
461                     if (breakpointsToEnable.length)
462                         breakpointsToEnable ~= ",";
463                     breakpointsToEnable ~= existing.number;
464                     existing.bp.enabled = true;
465                 } else if (!bp.enabled && existing.bp.enabled) {
466                     if (breakpointsToDisable.length)
467                         breakpointsToDisable ~= ",";
468                     breakpointsToDisable ~= existing.number;
469                     existing.bp.enabled = false;
470                 }
471             }
472         }
473         if (breakpointsToDelete.length) {
474             Log.d("Deleting breakpoints: " ~ breakpointsToDelete);
475             submitRequest(("-break-delete " ~ breakpointsToDelete).dup);
476         }
477         if (breakpointsToEnable.length) {
478             Log.d("Enabling breakpoints: " ~ breakpointsToEnable);
479             submitRequest(("-break-enable " ~ breakpointsToEnable).dup);
480         }
481         if (breakpointsToDisable.length) {
482             Log.d("Disabling breakpoints: " ~ breakpointsToDisable);
483             submitRequest(("-break-disable " ~ breakpointsToDisable).dup);
484         }
485     }
486 
487 
488     // ~message
489     void handleStreamLineCLI(string s) {
490         Log.d("GDB CLI: ", s);
491         if (s.length >= 2 && s.startsWith('\"') && s.endsWith('\"'))
492             s = parseCString(s);
493         _callback.onDebuggerMessage(s);
494     }
495 
496     // @message
497     void handleStreamLineProgram(string s) {
498         Log.d("GDB program stream: ", s);
499         //_callback.onDebuggerMessage(s);
500     }
501 
502     // &message
503     void handleStreamLineGDBDebug(string s) {
504         Log.d("GDB internal debug message: ", s);
505     }
506 
507     long _stoppedThreadId = 0;
508 
509     // *stopped,reason="exited-normally"
510     // *running,thread-id="all"
511     // *asyncclass,result
512     void handleExecAsyncMessage(uint token, string s) {
513         string msgType = parseIdentAndSkipComma(s);
514         AsyncClass msgId = asyncByName(msgType);
515         if (msgId == AsyncClass.other)
516             Log.d("GDB WARN unknown async class type: ", msgType);
517         MIValue params = parseMI(s);
518         if (!params) {
519             Log.e("Failed to parse exec state params");
520             return;
521         }
522         Log.v("GDB async *[", token, "] ", msgType, " params: ", params.toString);
523         string reason = params.getString("reason");
524         if (msgId == AsyncClass.running) {
525             _callback.onDebugState(DebuggingState.running, StateChangeReason.unknown, null, null);
526         } else if (msgId == AsyncClass.stopped) {
527             StateChangeReason reasonId = StateChangeReason.unknown;
528             DebugFrame location = parseFrame(params["frame"]);
529             string threadId = params.getString("thread-id");
530             _stoppedThreadId = params.getUlong("thread-id", 0);
531             string stoppedThreads = params.getString("all");
532             Breakpoint bp = null;
533             if (reason.equal("end-stepping-range")) {
534                 updateState();
535                 _callback.onDebugState(DebuggingState.paused, StateChangeReason.endSteppingRange, location, bp);
536             } else if (reason.equal("breakpoint-hit")) {
537 		        Log.v("handling breakpoint-hit");
538                 if (GDBBreakpoint gdbbp = findBreakpointByNumber(params.getString("bkptno"))) {
539                     bp = gdbbp.bp;
540                     if (!location && bp) {
541                         location = new DebugFrame();
542                         location.fillMissingFields(bp);
543                     }
544                 }
545                 //_requests.targetIsReady();
546                 updateState();
547                 _callback.onDebugState(DebuggingState.paused, StateChangeReason.breakpointHit, location, bp);
548             } else if (reason.equal("signal-received")) {
549                 //_requests.targetIsReady();
550                 updateState();
551                 _callback.onDebugState(DebuggingState.paused, StateChangeReason.exception, location, bp);
552             } else if (reason.equal("exited-normally")) {
553                 _exitCode = 0;
554                 Log.i("Program exited. Exit code ", _exitCode);
555                 _callback.onDebugState(DebuggingState.stopped, StateChangeReason.exited, null, null);
556             } else if (reason.equal("exited")) {
557                 _exitCode = params.getInt("exit-code");
558                 Log.i("Program exited. Exit code ", _exitCode);
559                 _callback.onDebugState(DebuggingState.stopped, StateChangeReason.exited, null, null);
560             } else if (reason.equal("exited-signalled")) {
561                 _exitCode = -2; //params.getInt("exit-code");
562                 string signalName = params.getString("signal-name");
563                 string signalMeaning = params.getString("signal-meaning");
564                 Log.i("Program exited by signal. Signal code: ", signalName, " Signal meaning: ", signalMeaning);
565                 _callback.onDebugState(DebuggingState.stopped, StateChangeReason.exited, null, null);
566             } else {
567                 _exitCode = -1;
568                 _callback.onDebugState(DebuggingState.stopped, StateChangeReason.exited, null, null);
569             }
570         } else {
571 	        Log.e("unknown async type `", msgType, "`");
572         }
573     }
574 
575     int _stackListLocalsRequest;
576     DebugThreadList _currentState;
577     void updateState() {
578         _currentState = null;
579         submitRequest(new ThreadInfoRequest());
580     }
581 
582     // +asyncclass,result
583     void handleStatusAsyncMessage(uint token, string s) {
584         string msgType = parseIdentAndSkipComma(s);
585         AsyncClass msgId = asyncByName(msgType);
586         if (msgId == AsyncClass.other)
587             Log.d("GDB WARN unknown async class type: ", msgType);
588         Log.v("GDB async +[", token, "] ", msgType, " params: ", s);
589     }
590 
591     // =asyncclass,result
592     void handleNotifyAsyncMessage(uint token, string s) {
593         string msgType = parseIdentAndSkipComma(s);
594         AsyncClass msgId = asyncByName(msgType);
595         if (msgId == AsyncClass.other)
596             Log.d("GDB WARN unknown async class type: ", msgType);
597         Log.v("GDB async =[", token, "] ", msgType, " params: ", s);
598     }
599 
600     // ^resultClass,result
601     void handleResultMessage(uint token, string s) {
602         Log.v("GDB result ^[", token, "] ", s);
603         string msgType = parseIdentAndSkipComma(s);
604         ResultClass msgId = resultByName(msgType);
605         if (msgId == ResultClass.other)
606             Log.d("GDB WARN unknown result class type: ", msgType);
607         MIValue params = parseMI(s);
608         Log.v("GDB result ^[", token, "] ", msgType, " params: ", (params ? params.toString : "unparsed: " ~ s));
609         if (_requests.handleResult(token, msgId, params)) {
610             // handled using result list
611         } else {
612             Log.w("received results for unknown request");
613         }
614     }
615 
616     GDBRequestList _requests;
617     /// submit single request or request chain
618     void submitRequest(GDBRequest[] requests ... ) {
619         for (int i = 0; i + 1 < requests.length; i++)
620             requests[i].chain(requests[i + 1]);
621         _requests.submit(requests[0]);
622     }
623 
624     /// submit simple text command request
625     int submitRequest(string text, bool forceNoWaitDebuggerReady = false) {
626         auto request = new GDBRequest(text);
627         _requests.submit(request, forceNoWaitDebuggerReady);
628         return request.id;
629     }
630 
631     /// request stack trace and local vars for thread and frame
632     void requestDebugContextInfo(ulong threadId, int frame) {
633         Log.d("requestDebugContextInfo threadId=", threadId, " frame=", frame);
634         submitRequest(new StackListFramesRequest(threadId, frame));
635     }
636 
637     private int initRequestsSuccessful = 0;
638     private int initRequestsError = 0;
639     private int initRequestsWarnings = 0;
640     private int totalInitRequests = 0;
641     private int finishedInitRequests = 0;
642     class GDBInitRequest : GDBRequest {
643         bool _mandatory;
644         this(string cmd, bool mandatory) { 
645             command = cmd; 
646             _mandatory = mandatory;
647             totalInitRequests++;
648         }
649 
650         override void onOtherResult() {
651             initRequestsSuccessful++;
652             finishedInitRequests++;
653             checkFinished();
654         }
655         
656         override void onResult() {
657             initRequestsSuccessful++;
658             finishedInitRequests++;
659             checkFinished();
660         }
661 
662         /// called if resultClass is error
663         override void onError() {
664             if (_mandatory)
665                 initRequestsError++;
666             else
667                 initRequestsWarnings++;
668             finishedInitRequests++;
669             checkFinished();
670         }
671 
672         void checkFinished() {
673             if (initRequestsError)
674                 initRequestsCompleted(false);
675             else if (finishedInitRequests == totalInitRequests)
676                 initRequestsCompleted(true);
677         }
678     }
679 
680     void initRequestsCompleted(bool successful = true) {
681         Log.d("Init sequence complection result: ", successful);
682         if (successful) {
683             // ok
684             _callback.onProgramLoaded(true, true);
685         } else {
686             // error
687             _requests.cancelPendingRequests();
688             _status = ExecutionStatus.Error;
689             _stopRequested = true;
690         }
691     }
692 
693     void submitInitRequests() {
694         initRequestsSuccessful = 0;
695         initRequestsError = 0;
696         totalInitRequests = 0;
697         initRequestsWarnings = 0;
698         finishedInitRequests = 0;
699         submitRequest(new GDBInitRequest("-environment-cd " ~ quotePathIfNeeded(_executableWorkingDir), true));
700         if (externalTerminalTty)
701             submitRequest(new GDBInitRequest("-inferior-tty-set " ~ quotePathIfNeeded(externalTerminalTty), true));
702         
703         submitRequest(new GDBInitRequest("-gdb-set breakpoint pending on", false));
704         //submitRequest(new GDBInitRequest("-enable-pretty-printing", false));
705         submitRequest(new GDBInitRequest("-gdb-set print object on", false));
706         submitRequest(new GDBInitRequest("-gdb-set print sevenbit-strings on", false));
707         submitRequest(new GDBInitRequest("-gdb-set host-charset UTF-8", false));
708         //11-gdb-set target-charset WINDOWS-1252
709         //12-gdb-set target-wide-charset UTF-16
710         //13source .gdbinit
711         submitRequest(new GDBInitRequest("-gdb-set target-async off", false));
712         submitRequest(new GDBInitRequest("-gdb-set auto-solib-add on", false));
713         if (_executableArgs.length) {
714             char[] buf;
715             for(uint i = 0; i < _executableArgs.length; i++) {
716                 if (i > 0) 
717                     buf ~= " ";
718                 buf ~= quotePathIfNeeded(_executableArgs[i]);
719             }
720             submitRequest(new GDBInitRequest(("-exec-arguments " ~ buf).dup, true));
721         }
722         submitRequest(new GDBInitRequest("-file-exec-and-symbols " ~ quotePathIfNeeded(_executableFile), true));
723 
724         //debuggerArgs ~= _executableFile;
725         //foreach(arg; _executableArgs)
726         //    debuggerArgs ~= arg;
727         //ExternalProcessState state = runDebuggerProcess(_debuggerExecutable, debuggerArgs, _executableWorkingDir);
728         //17-gdb-show language
729         //18-gdb-set language c
730         //19-interpreter-exec console "p/x (char)-1"
731         //20-data-evaluate-expression "sizeof (void*)"
732         //21-gdb-set language auto
733 
734     }
735 
736     class ThreadInfoRequest : GDBRequest {
737         this() { command = "-thread-info"; }
738         override void onResult() {
739             _currentState = parseThreadList(params);
740 			if (_currentState) {
741                 // TODO
742                 Log.d("Thread list is parsed");
743                 if (!_currentState.currentThreadId)
744                 	_currentState.currentThreadId = _stoppedThreadId;
745                 submitRequest(new StackListFramesRequest(_currentState.currentThreadId, 0));
746             }
747         }
748     }
749 
750     class StackListFramesRequest : GDBRequest {
751         private ulong _threadId;
752         private int _frameId;
753         this(ulong threadId, int frameId) {
754             _threadId = threadId;
755             _frameId = frameId;
756             if (!_threadId)
757                 _threadId = _currentState ? _currentState.currentThreadId : 0;
758             command = "-stack-list-frames --thread " ~ to!string(_threadId); 
759         }
760         override void onResult() {
761             DebugStack stack = parseStack(params);
762             if (stack) {
763                 // TODO
764                 Log.d("Stack frames list is parsed: " ~ to!string(stack));
765                 if (_currentState) {
766                     if (DebugThread currentThread = _currentState.findThread(_threadId)) {
767                         currentThread.stack = stack;
768                         Log.d("Setting stack frames for current thread");
769                     }
770                     submitRequest(new LocalVariableListRequest(_threadId, _frameId));
771                 }
772             }
773         }
774     }
775 
776     class LocalVariableListRequest : GDBRequest {
777         ulong _threadId;
778         int _frameId;
779         this(ulong threadId, int frameId) {
780             _threadId = threadId;
781             _frameId = frameId;
782             //command = "-stack-list-variables --thread " ~ to!string(_threadId) ~ " --frame " ~ to!string(_frameId) ~ " --simple-values"; 
783             command = "-stack-list-locals --thread " ~ to!string(_threadId) ~ " --frame " ~ to!string(_frameId) ~ " 1"; 
784         }
785         override void onResult() {
786             DebugVariableList variables = parseVariableList(params, "locals");
787             if (variables) {
788                 // TODO
789                 Log.d("Variable list is parsed: " ~ to!string(variables));
790                 if (_currentState) {
791                     if (DebugThread currentThread = _currentState.findThread(_threadId)) {
792                         if (currentThread.length > 0) {
793                             if (_frameId > currentThread.length)
794                                 _frameId = 0;
795                             if (_frameId < currentThread.length)
796                                 currentThread[_frameId].locals = variables;
797                             Log.d("Setting variables for current thread top frame");
798                             _callback.onDebugContextInfo(_currentState.clone(), _threadId, _frameId);
799                         }
800                     }
801                 }
802             }
803         }
804     }
805 
806     bool _firstIdle = true;
807     // (gdb)
808     void onDebuggerIdle() {
809         Log.d("GDB idle");
810         _requests.targetIsReady();
811         if (_firstIdle) {
812             _firstIdle = false;
813             return;
814         }
815     }
816 
817     override protected void onDebuggerStdoutLine(string gdbLine) {
818         Log.d("GDB stdout: '", gdbLine, "'");
819         string line = gdbLine;
820         if (line.empty)
821             return;
822         // parse token (sequence of digits at the beginning of message)
823         uint tokenId = 0;
824         int tokenLen = 0;
825         while (tokenLen < line.length && line[tokenLen] >= '0' && line[tokenLen] <= '9')
826             tokenLen++;
827         if (tokenLen > 0) {
828             tokenId = to!uint(line[0..tokenLen]);
829             line = line[tokenLen .. $];
830         }
831         if (line.length == 0)
832             return; // token only, no message!
833         char firstChar = line[0];
834         string restLine = line.length > 1 ? line[1..$] : "";
835         if (firstChar == '~') {
836             handleStreamLineCLI(restLine);
837             return;
838         } else if (firstChar == '@') {
839             handleStreamLineProgram(restLine);
840             return;
841         } else if (firstChar == '&') {
842             handleStreamLineGDBDebug(restLine);
843             return;
844         } else if (firstChar == '*') {
845             handleExecAsyncMessage(tokenId, restLine);
846             return;
847         } else if (firstChar == '+') {
848             handleStatusAsyncMessage(tokenId, restLine);
849             return;
850         } else if (firstChar == '=') {
851             handleNotifyAsyncMessage(tokenId, restLine);
852             return;
853         } else if (firstChar == '^') {
854             handleResultMessage(tokenId, restLine);
855             return;
856         } else if (line.startsWith("(gdb)")) {
857             onDebuggerIdle();
858             return;
859         } else {
860             Log.d("GDB unprocessed: ", gdbLine);
861         }
862     }
863 
864 }
865 
866 
867 class GDBRequest {
868     int id;
869     string command;
870     ResultClass resultClass;
871     MIValue params;
872     GDBRequest next;
873 
874     this() {
875     }
876 
877     this(string cmdtext) {
878         command = cmdtext;
879     }
880 
881     /// called if resultClass is done
882     void onResult() {
883     }
884     /// called if resultClass is error
885     void onError() {
886     }
887     /// called on other result types
888     void onOtherResult() {
889     }
890 
891     /// chain additional request, for case when previous finished ok
892     GDBRequest chain(GDBRequest next) {
893         this.next = next;
894         return this;
895     }
896 }
897 
898 struct GDBRequestList {
899 
900     private bool _synchronousMode = false;
901 
902     void setSynchronousMode(bool flg) {
903         _synchronousMode = flg;
904         _ready = _ready | _synchronousMode;
905     }
906 
907     private TextCommandTarget _target;
908     private GDBRequest[int] _activeRequests;
909     private GDBRequest[] _pendingRequests;
910 
911     private bool _ready = false;
912 
913     void setTarget(TextCommandTarget target) {
914         _target = target;
915     }
916 
917     private void executeRequest(GDBRequest request) {
918         request.id = _target.sendCommand(request.command, request.id);
919         if (request.id)
920             _activeRequests[request.id] = request;
921     }
922 
923     int submit(GDBRequest request, bool forceNoWaitDebuggerReady = false) {
924         if (!request.id)
925             request.id = _target.reserveCommandId();
926         if (Log.traceEnabled)
927             Log.v("submitting request " ~ to!string(request.id) ~ " " ~ request.command);
928         if (_ready || _synchronousMode || forceNoWaitDebuggerReady) {
929             if (!forceNoWaitDebuggerReady)
930                 _ready = _synchronousMode;
931             executeRequest(request);
932         } else
933             _pendingRequests ~= request;
934         return request.id;
935     }
936 
937     // (gdb) prompt received
938     void targetIsReady() {
939         _ready = true;
940         if (_pendingRequests.length) {
941             // execute next pending request
942             GDBRequest next = _pendingRequests[0];
943             for (uint i = 0; i + 1 < _pendingRequests.length; i++)
944                 _pendingRequests[i] = _pendingRequests[i + 1];
945             _pendingRequests[$ - 1] = null;
946             _pendingRequests.length = _pendingRequests.length - 1;
947             executeRequest(next);
948         }
949     }
950 
951     void cancelPendingRequests() {
952         foreach(ref r; _pendingRequests)
953             r = null; // just to help GC
954         _pendingRequests.length = 0;
955     }
956 
957     bool handleResult(int token, ResultClass resultClass, MIValue params) {
958         if (token in _activeRequests) {
959             GDBRequest r = _activeRequests[token];
960             _activeRequests.remove(token);
961             r.resultClass = resultClass;
962             r.params = params;
963             if (resultClass == ResultClass.done) {
964                 r.onResult();
965                 if (r.next)
966                     submit(r.next);
967             } else if (resultClass == ResultClass.error) {
968                 r.onError();
969             } else {
970                 r.onOtherResult();
971             }
972             return true;
973         }
974         return false;
975     }
976 }
977 
978 /// appends --thread parameter to command text if threadId != 0
979 string appendThreadParam(string src, ulong threadId) {
980     if (!threadId)
981         return src;
982     return src ~= " --thread " ~ to!string(threadId);
983 }