GDB support, can start debugging

This commit is contained in:
Vadim Lopatin 2015-12-11 16:05:42 +03:00
parent 30757f1440
commit 3a4d49c293
10 changed files with 641 additions and 116 deletions

View File

@ -204,6 +204,7 @@
<Compile Include="src\dlangide\tools\d\dparser.d" /> <Compile Include="src\dlangide\tools\d\dparser.d" />
<Compile Include="src\dlangide\tools\d\dsyntax.d" /> <Compile Include="src\dlangide\tools\d\dsyntax.d" />
<Compile Include="src\dlangide\ui\commands.d" /> <Compile Include="src\dlangide\ui\commands.d" />
<Compile Include="src\dlangide\ui\debuggerui.d" />
<Compile Include="src\dlangide\ui\dsourceedit.d" /> <Compile Include="src\dlangide\ui\dsourceedit.d" />
<Compile Include="src\dlangide\ui\frame.d" /> <Compile Include="src\dlangide\ui\frame.d" />
<Compile Include="src\dlangide\ui\homescreen.d" /> <Compile Include="src\dlangide\ui\homescreen.d" />

View File

@ -113,6 +113,7 @@
<Compile Include="src\dlangide\workspace\workspacesettings.d" /> <Compile Include="src\dlangide\workspace\workspacesettings.d" />
<Compile Include="src\dlangide\ui\commands.d" /> <Compile Include="src\dlangide\ui\commands.d" />
<Compile Include="src\dlangide\ui\dsourceedit.d" /> <Compile Include="src\dlangide\ui\dsourceedit.d" />
<Compile Include="src\dlangide\ui\debuggerui.d" />
<Compile Include="src\dlangide\ui\frame.d" /> <Compile Include="src\dlangide\ui\frame.d" />
<Compile Include="src\dlangide\ui\homescreen.d" /> <Compile Include="src\dlangide\ui\homescreen.d" />
<Compile Include="src\dlangide\ui\newfile.d" /> <Compile Include="src\dlangide\ui\newfile.d" />

View File

@ -425,7 +425,7 @@
<File path="src\ddebug\common\nodebug.d" /> <File path="src\ddebug\common\nodebug.d" />
<File path="src\ddebug\common\queue.d" /> <File path="src\ddebug\common\queue.d" />
</Folder> </Folder>
<Folder name="gdbinterface"> <Folder name="gdb">
<File path="src\ddebug\gdb\gdbinterface.d" /> <File path="src\ddebug\gdb\gdbinterface.d" />
</Folder> </Folder>
<Folder name="windows"> <Folder name="windows">
@ -452,6 +452,7 @@
</Folder> </Folder>
<Folder name="ui"> <Folder name="ui">
<File path="src\dlangide\ui\commands.d" /> <File path="src\dlangide\ui\commands.d" />
<File path="src\dlangide\ui\debuggerui.d" />
<File path="src\dlangide\ui\dsourceedit.d" /> <File path="src\dlangide\ui\dsourceedit.d" />
<File path="src\dlangide\ui\frame.d" /> <File path="src\dlangide\ui\frame.d" />
<File path="src\dlangide\ui\homescreen.d" /> <File path="src\dlangide\ui\homescreen.d" />

View File

@ -3,6 +3,37 @@ module ddebug.common.debugger;
import core.thread; import core.thread;
import dlangui.core.logger; import dlangui.core.logger;
import ddebug.common.queue; import ddebug.common.queue;
import ddebug.common.execution;
enum DebuggingState {
loaded,
running,
paused,
stopped
}
interface DebuggerCallback : ProgramExecutionStatusListener {
/// debugger message line
void onDebuggerMessage(string msg);
/// debugger is started and loaded program, you can set breakpoints at this time
void onProgramLoaded(bool successful, bool debugInfoLoaded);
/// state changed: running / paused / stopped
void onDebugState(DebuggingState state, string msg, int param);
void onResponse(ResponseCode code, string msg);
}
interface Debugger : ProgramExecution {
void setDebuggerCallback(DebuggerCallback callback);
void setDebuggerExecutable(string debuggerExecutable);
/// can be called after program is loaded
void execStart();
/// continue execution
void execContinue();
}
enum ResponseCode : int { enum ResponseCode : int {
/// Operation finished successfully /// Operation finished successfully
@ -21,49 +52,135 @@ enum ResponseCode : int {
} }
alias Runnable = void delegate(); alias Runnable = void delegate();
alias DebuggerResponse = void delegate(ResponseCode code, string msg);
interface Debugger { //interface Debugger {
/// start debugging // /// start debugging
void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response); // void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response);
} //}
/// proxy for debugger interface implementing async calls /// proxy for debugger interface implementing async calls
class DebuggerProxy : Debugger { class DebuggerProxy : Debugger, DebuggerCallback {
private DebuggerBase _debugger; private DebuggerBase _debugger;
private void delegate(Runnable runnable) _callbackDelegate; private void delegate(void delegate() runnable) _callbackDelegate;
this(DebuggerBase debugger, void delegate(Runnable runnable) callbackDelegate) { this(DebuggerBase debugger, void delegate(void delegate() runnable) callbackDelegate) {
_debugger = debugger; _debugger = debugger;
_callbackDelegate = callbackDelegate; _callbackDelegate = callbackDelegate;
} }
void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response) { /// returns true if it's debugger
_debugger.postRequest(delegate() { @property bool isDebugger() { return true; }
_debugger.startDebugging(debuggerExecutable, executable, args, workingDir, /// executable file
delegate(ResponseCode code, string msg) { @property string executableFile() { return _debugger.executableFile; }
_callbackDelegate( delegate() { response(code, msg); } ); /// returns execution status
} //@property ExecutionStatus status();
);
}); void setExecutableParams(string executableFile, string[] args, string workingDir, string[string] envVars) {
_debugger.setExecutableParams(executableFile, args, workingDir, envVars);
} }
/// set external terminal parameters before execution
void setTerminalExecutable(string terminalExecutable) {
_debugger.setTerminalExecutable(terminalExecutable);
}
/// set debugger executable
void setDebuggerExecutable(string debuggerExecutable) {
_debugger.setDebuggerExecutable(debuggerExecutable);
}
protected DebuggerCallback _callback;
/// set debugger callback
void setDebuggerCallback(DebuggerCallback callback) {
_callback = callback;
_debugger.setDebuggerCallback(this);
}
/// called when program execution is stopped
void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode) {
DebuggerProxy proxy = this;
_callbackDelegate( delegate() { _callback.onProgramExecutionStatus(proxy, status, exitCode); } );
}
/// debugger is started and loaded program, you can set breakpoints at this time
void onProgramLoaded(bool successful, bool debugInfoLoaded) {
_callbackDelegate( delegate() { _callback.onProgramLoaded(successful, debugInfoLoaded); } );
}
/// state changed: running / paused / stopped
void onDebugState(DebuggingState state, string msg, int param) {
_callbackDelegate( delegate() { _callback.onDebugState(state, msg, param); } );
}
void onResponse(ResponseCode code, string msg) {
_callbackDelegate( delegate() { _callback.onResponse(code, msg); } );
}
void onDebuggerMessage(string msg) {
_callbackDelegate( delegate() { _callback.onDebuggerMessage(msg); } );
}
/// start execution
void run() {
Log.d("DebuggerProxy.run()");
_debugger.run();
//_debugger.postRequest(delegate() { _debugger.run(); });
}
/// stop execution
void stop() {
Log.d("DebuggerProxy.stop()");
_debugger.stop();
//_debugger.postRequest(delegate() { _debugger.stop(); });
}
/// start execution, can be called after program is loaded
void execStart() {
_debugger.postRequest(delegate() { _debugger.execStart(); });
}
/// continue program
void execContinue() {
_debugger.postRequest(delegate() { _debugger.execContinue(); });
}
} }
class DebuggerBase : Thread, Debugger { abstract class DebuggerBase : Thread, Debugger {
private bool _runRequested;
private bool _stopRequested; private bool _stopRequested;
private bool _finished; private bool _finished;
protected string _debuggerExecutable;
protected BlockingQueue!Runnable _queue; protected BlockingQueue!Runnable _queue;
protected ExecutionStatus _status = ExecutionStatus.NotStarted;
protected int _exitCode = 0;
/// provides _executableFile, _executableArgs, _executableWorkingDir, _executableEnvVars parameters and setter function setExecutableParams
mixin ExecutableParams;
/// provides _terminalExecutable and setTerminalExecutable setter
mixin TerminalParams;
protected DebuggerCallback _callback;
void setDebuggerCallback(DebuggerCallback callback) {
_callback = callback;
}
protected string _debuggerExecutable;
void setDebuggerExecutable(string debuggerExecutable) {
_debuggerExecutable = debuggerExecutable;
}
@property bool isDebugger() { return true; }
@property string executableFile() {
return _executableFile;
}
void postRequest(Runnable request) { void postRequest(Runnable request) {
_queue.put(request); _queue.put(request);
} }
this() { this() {
super(&run); super(&threadFunc);
_queue = new BlockingQueue!Runnable(); _queue = new BlockingQueue!Runnable();
} }
@ -73,8 +190,23 @@ class DebuggerBase : Thread, Debugger {
_queue = null; _queue = null;
} }
// call from GUI thread
void run() {
Log.d("DebuggerBase.run()");
assert(!_runRequested);
_runRequested = true;
postRequest(&startDebugging);
start();
}
void startDebugging() {
// override to implement
}
void stop() { void stop() {
Log.i("Debugger.stop()"); Log.i("Debugger.stop()");
if (_stopRequested)
return;
_stopRequested = true; _stopRequested = true;
_queue.close(); _queue.close();
} }
@ -83,25 +215,26 @@ class DebuggerBase : Thread, Debugger {
} }
protected void onDebuggerThreadFinished() { protected void onDebuggerThreadFinished() {
_callback.onProgramExecutionStatus(this, _status, _exitCode);
} }
/// thread func: execute all tasks from queue /// thread func: execute all tasks from queue
private void run() { private void threadFunc() {
onDebuggerThreadStarted(); onDebuggerThreadStarted();
Log.i("Debugger thread started"); Log.i("Debugger thread started");
try {
while (!_stopRequested) { while (!_stopRequested) {
Runnable task; Runnable task;
if (_queue.get(task, 0)) { if (_queue.get(task, 0)) {
task(); task();
} }
} }
} catch (Exception e) {
Log.e("Exception in debugger thread");
}
Log.i("Debugger thread finished"); Log.i("Debugger thread finished");
_finished = true; _finished = true;
onDebuggerThreadFinished(); onDebuggerThreadFinished();
} }
void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response) {
response(ResponseCode.NotImplemented, "Not Implemented");
}
} }

View File

@ -17,15 +17,51 @@ interface ProgramExecutionStatusListener {
void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode); void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode);
} }
// Interface to run program and control program execution
interface ProgramExecution { interface ProgramExecution {
/// set executable parameters before execution
void setExecutableParams(string executableFile, string[] args, string workingDir, string[string] envVars);
/// set external terminal parameters before execution
void setTerminalExecutable(string terminalExecutable);
/// returns true if it's debugger /// returns true if it's debugger
@property bool isDebugger(); @property bool isDebugger();
/// executable file /// executable file
@property string executableFile(); @property string executableFile();
/// returns execution status /// returns execution status
@property ExecutionStatus status(); //@property ExecutionStatus status();
/// start execution /// start execution
bool run(); void run();
/// stop execution /// stop execution
bool stop(); void stop();
} }
/// provides _executableFile, _executableArgs, _executableWorkingDir, _executableEnvVars parameters and setter function setExecutableParams
mixin template ExecutableParams() {
protected string _executableFile;
protected string[] _executableArgs;
protected string _executableWorkingDir;
protected string[string] _executableEnvVars;
/// set executable parameters before execution
void setExecutableParams(string executableFile, string[] args, string workingDir, string[string] envVars) {
_executableFile = executableFile;
_executableArgs = args;
_executableWorkingDir = workingDir;
_executableEnvVars = envVars;
}
}
/// provides _terminalExecutable and setTerminalExecutable setter
mixin template TerminalParams() {
/// executable file name for external console/terminal
protected string _terminalExecutable;
/// set external terminal parameters before execution
void setTerminalExecutable(string terminalExecutable) {
_terminalExecutable = terminalExecutable;
}
}

View File

@ -9,29 +9,24 @@ import dlangui.core.logger;
class ProgramExecutionNoDebug : Thread, ProgramExecution { class ProgramExecutionNoDebug : Thread, ProgramExecution {
// parameters // parameters
protected string _executableFile; /// provides _executableFile, _executableArgs, _executableWorkingDir, _executableEnvVars parameters and setter function setExecutableParams
protected string[] _args; mixin ExecutableParams;
protected string _workDir; /// provides _terminalExecutable and setTerminalExecutable setter
protected string _externalConsole; mixin TerminalParams;
protected ProgramExecutionStatusListener _listener;
protected ProgramExecutionStatusListener _listener;
void setProgramExecutionStatusListener(ProgramExecutionStatusListener listener) {
_listener = listener;
}
// status // status
protected Pid _pid; protected Pid _pid;
protected ExecutionStatus _status = ExecutionStatus.NotStarted; protected ExecutionStatus _status = ExecutionStatus.NotStarted;
protected int _exitCode = 0; protected int _exitCode = 0;
/// initialize but do not run /// initialize but do not run
this(string executable, string[] args, string workDir, string externalConsole, ProgramExecutionStatusListener listener) { this() {
super(&threadFunc); super(&threadFunc);
_executableFile = executable;
_args = args;
_workDir = workDir;
_externalConsole = externalConsole;
_listener = listener;
assert(_listener !is null);
} }
~this() { ~this() {
@ -75,13 +70,13 @@ class ProgramExecutionNoDebug : Thread, ProgramExecution {
// prepare parameter list // prepare parameter list
string[] params; string[] params;
params ~= _executableFile; params ~= _executableFile;
params ~= _args; params ~= _executableArgs;
// external console support // external console support
if (!_externalConsole.empty) { if (!_terminalExecutable.empty) {
string cmdline = escapeShellCommand(params); string cmdline = escapeShellCommand(params);
params.length = 0; params.length = 0;
params ~= _externalConsole; params ~= _terminalExecutable;
params ~= "-e"; params ~= "-e";
params ~= cmdline; params ~= cmdline;
} }
@ -96,7 +91,7 @@ class ProgramExecutionNoDebug : Thread, ProgramExecution {
newstderr = stderr; newstderr = stderr;
} }
try { try {
_pid = spawnProcess(params, newstdin, newstdout, newstderr, null, Config.none, _workDir); _pid = spawnProcess(params, newstdin, newstdout, newstderr, null, Config.none, _executableWorkingDir);
} catch (Exception e) { } catch (Exception e) {
Log.e("ProgramExecutionNoDebug: Failed to spawn process: ", e); Log.e("ProgramExecutionNoDebug: Failed to spawn process: ", e);
killProcess(); killProcess();
@ -135,28 +130,27 @@ class ProgramExecutionNoDebug : Thread, ProgramExecution {
@property ExecutionStatus status() { return _status; } @property ExecutionStatus status() { return _status; }
/// start execution /// start execution
bool run() { void run() {
if (_runRequested) if (_runRequested)
return false; // already running return; // already running
assert(_listener !is null);
_runRequested = true; _runRequested = true;
_threadStarted = true; _threadStarted = true;
_status = ExecutionStatus.Running; _status = ExecutionStatus.Running;
start(); start();
return true;
} }
/// stop execution (call from GUI thread) /// stop execution (call from GUI thread)
bool stop() { void stop() {
if (!_runRequested) if (!_runRequested)
return false; return;
if (_stopRequested) if (_stopRequested)
return true; return;
_stopRequested = true; _stopRequested = true;
if (_threadStarted && !_threadJoined) { if (_threadStarted && !_threadJoined) {
_threadJoined = true; _threadJoined = true;
join(); join();
} }
return true;
} }
protected bool _threadStarted; protected bool _threadStarted;

View File

@ -1,13 +1,16 @@
module ddebug.gdb.gdbinterface; module ddebug.gdb.gdbinterface;
public import ddebug.common.debugger; public import ddebug.common.debugger;
import ddebug.common.execution;
import dlangui.core.logger; import dlangui.core.logger;
import ddebug.common.queue; import ddebug.common.queue;
import dlangide.builders.extprocess; import dlangide.builders.extprocess;
import std.utf; import std.utf;
import std.conv : to; import std.conv : to;
import std.array : empty;
import std.algorithm : startsWith, equal;
class ConsoleDebuggerInterface : DebuggerBase, TextWriter { abstract class ConsoleDebuggerInterface : DebuggerBase, TextWriter {
protected ExternalProcess _debuggerProcess; protected ExternalProcess _debuggerProcess;
protected ExternalProcessState runDebuggerProcess(string executable, string[]args, string dir) { protected ExternalProcessState runDebuggerProcess(string executable, string[]args, string dir) {
@ -20,7 +23,12 @@ class ConsoleDebuggerInterface : DebuggerBase, TextWriter {
private char[] _stdoutBuf; private char[] _stdoutBuf;
/// return true to clear lines list /// return true to clear lines list
protected bool onDebuggerStdoutLines(string[] lines) { protected bool onDebuggerStdoutLines(string[] lines) {
foreach(line; lines) {
onDebuggerStdoutLine(line);
}
return true; return true;
}
protected void onDebuggerStdoutLine(string line) {
} }
private void onStdoutText(string text) { private void onStdoutText(string text) {
_stdoutBuf ~= text; _stdoutBuf ~= text;
@ -74,14 +82,16 @@ class GDBInterface : ConsoleDebuggerInterface {
int sendCommand(string text) { int sendCommand(string text) {
commandId++; commandId++;
sendLine(to!string(commandId) ~ text); string cmd = to!string(commandId) ~ text;
Log.d("GDB command[", commandId, "]> ", text);
sendLine(cmd);
return commandId; return commandId;
} }
Pid terminalPid; Pid terminalPid;
string terminalTty; string terminalTty;
string startTerminal(string termExecutable) { string startTerminal() {
Log.d("Starting terminal"); Log.d("Starting terminal");
import std.random; import std.random;
import std.file; import std.file;
@ -94,7 +104,7 @@ class GDBInterface : ConsoleDebuggerInterface {
Log.d("temp file for tty name: ", termfile); Log.d("temp file for tty name: ", termfile);
try { try {
terminalPid = spawnProcess([ terminalPid = spawnProcess([
termExecutable, _terminalExecutable,
"-title", "-title",
"DLangIDE External Console", "DLangIDE External Console",
"-e", "-e",
@ -133,6 +143,8 @@ class GDBInterface : ConsoleDebuggerInterface {
} }
bool isTerminalActive() { bool isTerminalActive() {
if (_terminalExecutable.empty)
return true;
if (terminalPid is null) if (terminalPid is null)
return false; return false;
auto res = tryWait(terminalPid); auto res = tryWait(terminalPid);
@ -147,6 +159,8 @@ class GDBInterface : ConsoleDebuggerInterface {
} }
void killTerminal() { void killTerminal() {
if (_terminalExecutable.empty)
return;
if (terminalPid is null) if (terminalPid is null)
return; return;
try { try {
@ -162,44 +176,253 @@ class GDBInterface : ConsoleDebuggerInterface {
} }
} }
string terminalExecutableFileName = "xterm"; override void startDebugging() {
override void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response) { Log.d("GDBInterface.startDebugging()");
string[] debuggerArgs; string[] debuggerArgs;
terminalTty = startTerminal(terminalExecutableFileName); if (!_terminalExecutable.empty) {
terminalTty = startTerminal();
if (terminalTty.length == 0) { if (terminalTty.length == 0) {
response(ResponseCode.CannotRunDebugger, "Cannot start terminal"); _callback.onResponse(ResponseCode.CannotRunDebugger, "Cannot start terminal");
_status = ExecutionStatus.Error;
_callback.onProgramExecutionStatus(this, _status, _exitCode);
return; return;
} }
debuggerArgs ~= "-tty"; debuggerArgs ~= "-tty";
debuggerArgs ~= terminalTty; debuggerArgs ~= terminalTty;
}
debuggerArgs ~= "--interpreter=mi"; debuggerArgs ~= "--interpreter=mi";
debuggerArgs ~= "--silent"; debuggerArgs ~= "--silent";
debuggerArgs ~= "--args"; debuggerArgs ~= "--args";
debuggerArgs ~= executable; debuggerArgs ~= _executableFile;
foreach(arg; args) foreach(arg; _executableArgs)
debuggerArgs ~= arg; debuggerArgs ~= arg;
ExternalProcessState state = runDebuggerProcess(debuggerExecutable, debuggerArgs, workingDir); ExternalProcessState state = runDebuggerProcess(_debuggerExecutable, debuggerArgs, _executableWorkingDir);
Log.i("Debugger process state:"); Log.i("Debugger process state:");
if (state == ExternalProcessState.Running) { if (state == ExternalProcessState.Running) {
response(ResponseCode.Ok, "Started"); _callback.onProgramLoaded(true, true);
//sendCommand("-break-insert main"); //sendCommand("-break-insert main");
sendCommand("-exec-run");
} else { } else {
response(ResponseCode.CannotRunDebugger, "Error while trying to run debugger process"); _status = ExecutionStatus.Error;
_callback.onProgramExecutionStatus(this, _status, _exitCode);
return; return;
} }
} }
override void stop() { override void stop() {
Log.d("GDBInterface.run()");
if (_debuggerProcess !is null) if (_debuggerProcess !is null)
_debuggerProcess.kill(); _debuggerProcess.kill();
killTerminal(); killTerminal();
super.stop(); super.stop();
} }
/// return true to clear lines list /// start program execution, can be called after program is loaded
override protected bool onDebuggerStdoutLines(string[] lines) { int _startRequestId;
Log.d("onDebuggerStdout ", lines); void execStart() {
_startRequestId = sendCommand("-exec-run");
}
/// start program execution, can be called after program is loaded
int _continueRequestId;
void execContinue() {
_continueRequestId = sendCommand("-exec-continue");
}
// ~message
void handleStreamLineCLI(string s) {
Log.d("GDB CLI: ", s);
_callback.onDebuggerMessage(s);
}
// @message
void handleStreamLineProgram(string s) {
Log.d("GDB program stream: ", s);
//_callback.onDebuggerMessage(s);
}
// &message
void handleStreamLineGDBDebug(string s) {
Log.d("GDB internal debug message: ", s);
}
// *stopped,reason="exited-normally"
// *running,thread-id="all"
// *asyncclass,result
void handleExecAsyncMessage(uint token, string s) {
string msgType = parseIdentAndSkipComma(s);
AsyncClass msgId = asyncByName(msgType);
if (msgId == AsyncClass.other)
Log.d("GDB WARN unknown async class type: ", msgType);
Log.v("GDB async *[", token, "] ", msgType, " params: ", s);
if (msgId == AsyncClass.running) {
_callback.onDebugState(DebuggingState.running, s, 0);
} else if (msgId == AsyncClass.stopped) {
_callback.onDebugState(DebuggingState.stopped, s, 0);
}
}
// +asyncclass,result
void handleStatusAsyncMessage(uint token, string s) {
string msgType = parseIdentAndSkipComma(s);
AsyncClass msgId = asyncByName(msgType);
if (msgId == AsyncClass.other)
Log.d("GDB WARN unknown async class type: ", msgType);
Log.v("GDB async +[", token, "] ", msgType, " params: ", s);
}
// =asyncclass,result
void handleNotifyAsyncMessage(uint token, string s) {
string msgType = parseIdentAndSkipComma(s);
AsyncClass msgId = asyncByName(msgType);
if (msgId == AsyncClass.other)
Log.d("GDB WARN unknown async class type: ", msgType);
Log.v("GDB async =[", token, "] ", msgType, " params: ", s);
}
// ^resultClass,result
void handleResultMessage(uint token, string s) {
string msgType = parseIdentAndSkipComma(s);
ResultClass msgId = resultByName(msgType);
if (msgId == ResultClass.other)
Log.d("GDB WARN unknown result class type: ", msgType);
Log.v("GDB result ^[", token, "] ", msgType, " params: ", s);
}
bool _firstIdle = true;
// (gdb)
void onDebuggerIdle() {
Log.d("GDB idle");
if (_firstIdle) {
_firstIdle = false;
return;
}
}
override protected void onDebuggerStdoutLine(string gdbLine) {
//Log.d("GDB stdout: '", line, "'");
string line = gdbLine;
if (line.empty)
return;
// parse token (sequence of digits at the beginning of message)
uint tokenId = 0;
int tokenLen = 0;
while (tokenLen < line.length && line[tokenLen] >= '0' && line[tokenLen] <= '9')
tokenLen++;
if (tokenLen > 0) {
tokenId = to!uint(line[0..tokenLen]);
line = line[tokenLen .. $];
}
if (line.length == 0)
return; // token only, no message!
char firstChar = line[0];
string restLine = line.length > 1 ? line[1..$] : "";
if (firstChar == '~') {
handleStreamLineCLI(restLine);
return;
} else if (firstChar == '@') {
handleStreamLineProgram(restLine);
return;
} else if (firstChar == '&') {
handleStreamLineGDBDebug(restLine);
return;
} else if (firstChar == '*') {
handleExecAsyncMessage(tokenId, restLine);
return;
} else if (firstChar == '+') {
handleStatusAsyncMessage(tokenId, restLine);
return;
} else if (firstChar == '=') {
handleNotifyAsyncMessage(tokenId, restLine);
return;
} else if (firstChar == '^') {
handleResultMessage(tokenId, restLine);
return;
} else if (line.startsWith("(gdb)")) {
onDebuggerIdle();
return;
} else {
Log.d("GDB unprocessed: ", gdbLine);
}
}
}
string parseIdent(ref string s) {
string res = null;
int len = 0;
for(; len < s.length; len++) {
char ch = s[len];
if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '-'))
break;
}
if (len > 0) {
res = s[0..len];
s = s[len .. $];
}
return res;
}
bool skipComma(ref string s) {
if (s.length > 0 && s[0] == ',') {
s = s[1 .. $];
return true; return true;
} }
return false;
}
string parseIdentAndSkipComma(ref string s) {
string res = parseIdent(s);
skipComma(s);
return res;
}
ResultClass resultByName(string s) {
if (s.equal("done")) return ResultClass.done;
if (s.equal("running")) return ResultClass.running;
if (s.equal("connected")) return ResultClass.connected;
if (s.equal("error")) return ResultClass.error;
if (s.equal("exit")) return ResultClass.exit;
return ResultClass.other;
}
enum ResultClass {
done,
running,
connected,
error,
exit,
other
}
AsyncClass asyncByName(string s) {
if (s.equal("stopped")) return AsyncClass.stopped;
if (s.equal("running")) return AsyncClass.running;
if (s.equal("library-loaded")) return AsyncClass.library_loaded;
if (s.equal("library-unloaded")) return AsyncClass.library_unloaded;
if (s.equal("thread-group-added")) return AsyncClass.thread_group_added;
if (s.equal("thread-group-started")) return AsyncClass.thread_group_started;
if (s.equal("thread-group-exited")) return AsyncClass.thread_group_exited;
if (s.equal("thread-created")) return AsyncClass.thread_created;
if (s.equal("thread-exited")) return AsyncClass.thread_exited;
return AsyncClass.other;
}
enum AsyncClass {
running,
stopped,
library_loaded,
library_unloaded,
thread_group_added,
thread_group_started,
thread_group_exited,
thread_created,
thread_exited,
other
}
enum MITokenType {
str,
}
struct MIToken {
} }

View File

@ -44,24 +44,24 @@ extern (C) int UIAppMain(string[] args) {
//import ddc.lexer.tokenizer; //import ddc.lexer.tokenizer;
//runTokenizerTest(); //runTokenizerTest();
debug(DebugInfo) { //debug(DebugInfo) {
version(USE_MAGO) { // version(USE_MAGO) {
import ddebug.windows.mago; // import ddebug.windows.mago;
testMago(); // testMago();
} // }
version(Windows) { // version(Windows) {
import ddebug.windows.debuginfo; // import ddebug.windows.debuginfo;
import std.file; // import std.file;
//debugInfoTest(thisExePath); // //debugInfoTest(thisExePath);
} // }
} //}
version(USE_WIN_DEBUG) { //version(USE_WIN_DEBUG) {
debuggerTest(); // debuggerTest();
} //}
version(USE_GDB_DEBUG) { //version(USE_GDB_DEBUG) {
debuggerTestGDB(); // debuggerTestGDB();
} //}
// create window // create window
Window window = Platform.instance.createWindow("Dlang IDE", null, WindowFlag.Resizable, 800, 600); Window window = Platform.instance.createWindow("Dlang IDE", null, WindowFlag.Resizable, 800, 600);
@ -88,6 +88,7 @@ extern (C) int UIAppMain(string[] args) {
return Platform.instance.enterMessageLoop(); return Platform.instance.enterMessageLoop();
} }
/*
version(USE_WIN_DEBUG) { version(USE_WIN_DEBUG) {
void debuggerTest() { void debuggerTest() {
import ddebug.windows.windebug; import ddebug.windows.windebug;
@ -129,6 +130,7 @@ version(USE_GDB_DEBUG) {
Log.d("Testing of GDB debugger is finished"); Log.d("Testing of GDB debugger is finished");
} }
} }
*/
unittest { unittest {
void jsonTest() { void jsonTest() {

View File

@ -0,0 +1,55 @@
module dlangide.ui.debuggerui;
import dlangui.core.logger;
import dlangide.ui.frame;
import ddebug.common.execution;
import ddebug.common.debugger;
class DebuggerUIHandler : DebuggerCallback {
IDEFrame _ide;
Debugger _debugger;
DebuggingState _state = DebuggingState.loaded;
this(IDEFrame ide, Debugger debugger) {
_ide = ide;
_debugger = debugger;
_debugger.setDebuggerCallback(this);
}
/// called when program execution is stopped
void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode) {
Log.d("Debugger exit status: ", status, " ", exitCode);
_ide.debugFinished(process, status, exitCode);
//_callbackDelegate( delegate() { _callback.onProgramExecutionStatus(this, status, exitCode); } );
}
void onResponse(ResponseCode code, string msg) {
Log.d("Debugger response: ", code, " ", msg);
//_callbackDelegate( delegate() { _callback.onResponse(code, msg); } );
}
void onDebuggerMessage(string msg) {
_ide.logPanel.logLine("DBG: " ~ msg);
}
/// debugger is started and loaded program, you can set breakpoints at this time
void onProgramLoaded(bool successful, bool debugInfoLoaded) {
_ide.logPanel.logLine("Program is loaded");
// TODO: check succes status and debug info
_debugger.execStart();
}
/// state changed: running / paused / stopped
void onDebugState(DebuggingState state, string msg, int param) {
Log.d("onDebugState: ", state, " ", msg, " param=", param);
_state = state;
if (state == DebuggingState.stopped) {
_ide.logPanel.logLine("Program is stopped: " ~ msg);
_debugger.stop();
}
}
void run() {
_debugger.run();
}
}

View File

@ -25,6 +25,7 @@ import dlangide.ui.newproject;
import dlangide.ui.dsourceedit; import dlangide.ui.dsourceedit;
import dlangide.ui.homescreen; import dlangide.ui.homescreen;
import dlangide.ui.settings; import dlangide.ui.settings;
import dlangide.ui.debuggerui;
import dlangide.tools.d.dcdserver; import dlangide.tools.d.dcdserver;
import dlangide.workspace.workspace; import dlangide.workspace.workspace;
import dlangide.workspace.project; import dlangide.workspace.project;
@ -33,6 +34,8 @@ import dlangide.tools.editorTool;
import ddebug.common.execution; import ddebug.common.execution;
import ddebug.common.nodebug; import ddebug.common.nodebug;
import ddebug.common.debugger;
import ddebug.gdb.gdbinterface;
import std.conv; import std.conv;
import std.utf; import std.utf;
@ -88,6 +91,8 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener {
applySettings(_settings); applySettings(_settings);
} }
@property OutputPanel logPanel() { return _logPanel; }
/// stop current program execution /// stop current program execution
void stopExecution() { void stopExecution() {
if (_execution) { if (_execution) {
@ -128,6 +133,70 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener {
}); });
} }
protected void buildAndDebugProject(Project project) {
if (!currentWorkspace)
return;
if (!project)
project = currentWorkspace.startupProject;
if (!project) {
window.showMessageBox(UIString("Cannot debug project"d), UIString("Startup project is not specified"d));
return;
}
buildProject(BuildOperation.Build, project, delegate(int result) {
if (!result) {
Log.i("Build completed successfully. Starting debug for project.");
debugProject(project);
}
});
}
void debugFinished(ProgramExecution process, ExecutionStatus status, int exitCode) {
_execution = null;
switch(status) {
case ExecutionStatus.Error:
_logPanel.logLine("Cannot run program " ~ process.executableFile);
break;
case ExecutionStatus.Finished:
_logPanel.logLine("Program " ~ process.executableFile ~ " finished with exit code " ~ to!string(exitCode));
break;
case ExecutionStatus.Killed:
_logPanel.logLine("Program " ~ process.executableFile ~ " is killed");
break;
default:
_logPanel.logLine("Program " ~ process.executableFile ~ " is finished");
break;
}
_statusLine.setBackgroundOperationStatus(null, null);
}
DebuggerUIHandler _debugHandler;
protected void debugProject(Project project) {
import std.file;
stopExecution();
if (!project) {
window.showMessageBox(UIString("Cannot debug project"d), UIString("Startup project is not specified"d));
return;
}
string executableFileName = project.executableFileName;
if (!executableFileName || !exists(executableFileName) || !isFile(executableFileName)) {
window.showMessageBox(UIString("Cannot debug project"d), UIString("Cannot find executable file"d));
return;
}
string debuggerExecutable = _settings.debuggerExecutable;
if (debuggerExecutable.empty) {
window.showMessageBox(UIString("Cannot debug project"d), UIString("No debugger executable specified in settings"d));
return;
}
GDBInterface program = new GDBInterface();
DebuggerProxy debuggerProxy = new DebuggerProxy(program, &executeInUiThread);
setExecutableParameters(debuggerProxy, project, executableFileName);
debuggerProxy.setDebuggerExecutable(debuggerExecutable);
_execution = debuggerProxy;
_debugHandler = new DebuggerUIHandler(this, debuggerProxy);
_debugHandler.run();
}
protected void buildAndRunProject(Project project) { protected void buildAndRunProject(Project project) {
if (!currentWorkspace) if (!currentWorkspace)
return; return;
@ -139,28 +208,32 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener {
} }
buildProject(BuildOperation.Build, project, delegate(int result) { buildProject(BuildOperation.Build, project, delegate(int result) {
if (!result) { if (!result) {
runProject(); Log.i("Build completed successfully. Running program...");
runProject(project);
} }
}); });
} }
protected void runProject() { protected void runProject(Project project) {
import std.file; import std.file;
stopExecution(); stopExecution();
if (!currentWorkspace)
return;
Project project = currentWorkspace.startupProject;
if (!project) { if (!project) {
window.showMessageBox(UIString("Cannot run project"d), UIString("Startup project is not specified"d)); window.showMessageBox(UIString("Cannot run project"d), UIString("Startup project is not specified"d));
return; return;
} }
// build project
// TODO
string executableFileName = project.executableFileName; string executableFileName = project.executableFileName;
if (!executableFileName || !exists(executableFileName) || !isFile(executableFileName)) { if (!executableFileName || !exists(executableFileName) || !isFile(executableFileName)) {
window.showMessageBox(UIString("Cannot run project"d), UIString("Cannot find executable"d)); window.showMessageBox(UIString("Cannot run project"d), UIString("Cannot find executable file"d));
return; return;
} }
ProgramExecutionNoDebug program = new ProgramExecutionNoDebug();
setExecutableParameters(program, project, executableFileName);
program.setProgramExecutionStatusListener(this);
_execution = program;
program.run();
}
bool setExecutableParameters(ProgramExecution program, Project project, string executableFileName) {
string[] args; string[] args;
string externalConsoleExecutable = null; string externalConsoleExecutable = null;
string workingDirectory = project.workingDirectory; string workingDirectory = project.workingDirectory;
@ -170,11 +243,15 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener {
externalConsoleExecutable = _settings.terminalExecutable; externalConsoleExecutable = _settings.terminalExecutable;
} }
} }
// TODO: provide thread safe listener if (!program.isDebugger)
_logPanel.logLine("Starting " ~ executableFileName); _logPanel.logLine("Starting " ~ executableFileName);
_statusLine.setBackgroundOperationStatus("debug-run", "running..."d); else
_execution = new ProgramExecutionNoDebug(executableFileName, args, workingDirectory, externalConsoleExecutable, this); _logPanel.logLine("Starting debugger for " ~ executableFileName);
_execution.run(); _statusLine.setBackgroundOperationStatus("debug-run", program.isDebugger ? "debugging..."d : "running..."d);
string[string] env;
program.setExecutableParams(executableFileName, args, workingDirectory, env);
program.setTerminalExecutable(externalConsoleExecutable);
return true;
} }
override protected void init() { override protected void init() {
@ -689,8 +766,10 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener {
case IDEActions.CleanWorkspace: case IDEActions.CleanWorkspace:
buildProject(BuildOperation.Clean, cast(Project)a.objectParam); buildProject(BuildOperation.Clean, cast(Project)a.objectParam);
return true; return true;
case IDEActions.DebugStart:
case IDEActions.DebugStartNoDebug: case IDEActions.DebugStartNoDebug:
case IDEActions.DebugStart:
buildAndDebugProject(cast(Project)a.objectParam);
return true;
case IDEActions.DebugContinue: case IDEActions.DebugContinue:
buildAndRunProject(cast(Project)a.objectParam); buildAndRunProject(cast(Project)a.objectParam);
return true; return true;