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\dsyntax.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\frame.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\ui\commands.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\homescreen.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\queue.d" />
</Folder>
<Folder name="gdbinterface">
<Folder name="gdb">
<File path="src\ddebug\gdb\gdbinterface.d" />
</Folder>
<Folder name="windows">
@ -452,6 +452,7 @@
</Folder>
<Folder name="ui">
<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\frame.d" />
<File path="src\dlangide\ui\homescreen.d" />

View File

@ -3,6 +3,37 @@ module ddebug.common.debugger;
import core.thread;
import dlangui.core.logger;
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 {
/// Operation finished successfully
@ -21,49 +52,135 @@ enum ResponseCode : int {
}
alias Runnable = void delegate();
alias DebuggerResponse = void delegate(ResponseCode code, string msg);
interface Debugger {
/// start debugging
void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response);
}
//interface Debugger {
// /// start debugging
// void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response);
//}
/// proxy for debugger interface implementing async calls
class DebuggerProxy : Debugger {
class DebuggerProxy : Debugger, DebuggerCallback {
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;
_callbackDelegate = callbackDelegate;
}
void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response) {
_debugger.postRequest(delegate() {
_debugger.startDebugging(debuggerExecutable, executable, args, workingDir,
delegate(ResponseCode code, string msg) {
_callbackDelegate( delegate() { response(code, msg); } );
}
);
});
}
/// returns true if it's debugger
@property bool isDebugger() { return true; }
/// executable file
@property string executableFile() { return _debugger.executableFile; }
/// 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 _finished;
protected string _debuggerExecutable;
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) {
_queue.put(request);
}
this() {
super(&run);
super(&threadFunc);
_queue = new BlockingQueue!Runnable();
}
@ -73,8 +190,23 @@ class DebuggerBase : Thread, Debugger {
_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() {
Log.i("Debugger.stop()");
if (_stopRequested)
return;
_stopRequested = true;
_queue.close();
}
@ -83,25 +215,26 @@ class DebuggerBase : Thread, Debugger {
}
protected void onDebuggerThreadFinished() {
_callback.onProgramExecutionStatus(this, _status, _exitCode);
}
/// thread func: execute all tasks from queue
private void run() {
private void threadFunc() {
onDebuggerThreadStarted();
Log.i("Debugger thread started");
while (!_stopRequested) {
Runnable task;
if (_queue.get(task, 0)) {
task();
}
}
try {
while (!_stopRequested) {
Runnable task;
if (_queue.get(task, 0)) {
task();
}
}
} catch (Exception e) {
Log.e("Exception in debugger thread");
}
Log.i("Debugger thread finished");
_finished = true;
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);
}
// Interface to run program and control program execution
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
@property bool isDebugger();
/// executable file
@property string executableFile();
/// returns execution status
@property ExecutionStatus status();
//@property ExecutionStatus status();
/// start execution
bool run();
void run();
/// 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 {
// parameters
protected string _executableFile;
protected string[] _args;
protected string _workDir;
protected string _externalConsole;
protected ProgramExecutionStatusListener _listener;
/// provides _executableFile, _executableArgs, _executableWorkingDir, _executableEnvVars parameters and setter function setExecutableParams
mixin ExecutableParams;
/// provides _terminalExecutable and setTerminalExecutable setter
mixin TerminalParams;
protected ProgramExecutionStatusListener _listener;
void setProgramExecutionStatusListener(ProgramExecutionStatusListener listener) {
_listener = listener;
}
// status
protected Pid _pid;
protected ExecutionStatus _status = ExecutionStatus.NotStarted;
protected int _exitCode = 0;
/// initialize but do not run
this(string executable, string[] args, string workDir, string externalConsole, ProgramExecutionStatusListener listener) {
this() {
super(&threadFunc);
_executableFile = executable;
_args = args;
_workDir = workDir;
_externalConsole = externalConsole;
_listener = listener;
assert(_listener !is null);
}
~this() {
@ -75,13 +70,13 @@ class ProgramExecutionNoDebug : Thread, ProgramExecution {
// prepare parameter list
string[] params;
params ~= _executableFile;
params ~= _args;
params ~= _executableArgs;
// external console support
if (!_externalConsole.empty) {
if (!_terminalExecutable.empty) {
string cmdline = escapeShellCommand(params);
params.length = 0;
params ~= _externalConsole;
params ~= _terminalExecutable;
params ~= "-e";
params ~= cmdline;
}
@ -96,7 +91,7 @@ class ProgramExecutionNoDebug : Thread, ProgramExecution {
newstderr = stderr;
}
try {
_pid = spawnProcess(params, newstdin, newstdout, newstderr, null, Config.none, _workDir);
_pid = spawnProcess(params, newstdin, newstdout, newstderr, null, Config.none, _executableWorkingDir);
} catch (Exception e) {
Log.e("ProgramExecutionNoDebug: Failed to spawn process: ", e);
killProcess();
@ -135,28 +130,27 @@ class ProgramExecutionNoDebug : Thread, ProgramExecution {
@property ExecutionStatus status() { return _status; }
/// start execution
bool run() {
void run() {
if (_runRequested)
return false; // already running
return; // already running
assert(_listener !is null);
_runRequested = true;
_threadStarted = true;
_status = ExecutionStatus.Running;
start();
return true;
}
/// stop execution (call from GUI thread)
bool stop() {
void stop() {
if (!_runRequested)
return false;
return;
if (_stopRequested)
return true;
return;
_stopRequested = true;
if (_threadStarted && !_threadJoined) {
_threadJoined = true;
join();
}
return true;
}
protected bool _threadStarted;

View File

@ -1,13 +1,16 @@
module ddebug.gdb.gdbinterface;
public import ddebug.common.debugger;
import ddebug.common.execution;
import dlangui.core.logger;
import ddebug.common.queue;
import dlangide.builders.extprocess;
import std.utf;
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 ExternalProcessState runDebuggerProcess(string executable, string[]args, string dir) {
@ -20,8 +23,13 @@ class ConsoleDebuggerInterface : DebuggerBase, TextWriter {
private char[] _stdoutBuf;
/// return true to clear lines list
protected bool onDebuggerStdoutLines(string[] lines) {
foreach(line; lines) {
onDebuggerStdoutLine(line);
}
return true;
}
protected void onDebuggerStdoutLine(string line) {
}
private void onStdoutText(string text) {
_stdoutBuf ~= text;
// pass full lines
@ -74,14 +82,16 @@ class GDBInterface : ConsoleDebuggerInterface {
int sendCommand(string text) {
commandId++;
sendLine(to!string(commandId) ~ text);
string cmd = to!string(commandId) ~ text;
Log.d("GDB command[", commandId, "]> ", text);
sendLine(cmd);
return commandId;
}
Pid terminalPid;
string terminalTty;
string startTerminal(string termExecutable) {
string startTerminal() {
Log.d("Starting terminal");
import std.random;
import std.file;
@ -94,7 +104,7 @@ class GDBInterface : ConsoleDebuggerInterface {
Log.d("temp file for tty name: ", termfile);
try {
terminalPid = spawnProcess([
termExecutable,
_terminalExecutable,
"-title",
"DLangIDE External Console",
"-e",
@ -133,6 +143,8 @@ class GDBInterface : ConsoleDebuggerInterface {
}
bool isTerminalActive() {
if (_terminalExecutable.empty)
return true;
if (terminalPid is null)
return false;
auto res = tryWait(terminalPid);
@ -147,6 +159,8 @@ class GDBInterface : ConsoleDebuggerInterface {
}
void killTerminal() {
if (_terminalExecutable.empty)
return;
if (terminalPid is null)
return;
try {
@ -162,44 +176,253 @@ class GDBInterface : ConsoleDebuggerInterface {
}
}
string terminalExecutableFileName = "xterm";
override void startDebugging(string debuggerExecutable, string executable, string[] args, string workingDir, DebuggerResponse response) {
override void startDebugging() {
Log.d("GDBInterface.startDebugging()");
string[] debuggerArgs;
terminalTty = startTerminal(terminalExecutableFileName);
if (terminalTty.length == 0) {
response(ResponseCode.CannotRunDebugger, "Cannot start terminal");
return;
}
debuggerArgs ~= "-tty";
debuggerArgs ~= terminalTty;
if (!_terminalExecutable.empty) {
terminalTty = startTerminal();
if (terminalTty.length == 0) {
_callback.onResponse(ResponseCode.CannotRunDebugger, "Cannot start terminal");
_status = ExecutionStatus.Error;
_callback.onProgramExecutionStatus(this, _status, _exitCode);
return;
}
debuggerArgs ~= "-tty";
debuggerArgs ~= terminalTty;
}
debuggerArgs ~= "--interpreter=mi";
debuggerArgs ~= "--silent";
debuggerArgs ~= "--args";
debuggerArgs ~= executable;
foreach(arg; args)
debuggerArgs ~= _executableFile;
foreach(arg; _executableArgs)
debuggerArgs ~= arg;
ExternalProcessState state = runDebuggerProcess(debuggerExecutable, debuggerArgs, workingDir);
ExternalProcessState state = runDebuggerProcess(_debuggerExecutable, debuggerArgs, _executableWorkingDir);
Log.i("Debugger process state:");
if (state == ExternalProcessState.Running) {
response(ResponseCode.Ok, "Started");
_callback.onProgramLoaded(true, true);
//sendCommand("-break-insert main");
sendCommand("-exec-run");
} else {
response(ResponseCode.CannotRunDebugger, "Error while trying to run debugger process");
_status = ExecutionStatus.Error;
_callback.onProgramExecutionStatus(this, _status, _exitCode);
return;
}
}
}
override void stop() {
Log.d("GDBInterface.run()");
if (_debuggerProcess !is null)
_debuggerProcess.kill();
killTerminal();
super.stop();
}
/// return true to clear lines list
override protected bool onDebuggerStdoutLines(string[] lines) {
Log.d("onDebuggerStdout ", lines);
return true;
}
/// start program execution, can be called after program is loaded
int _startRequestId;
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 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;
//runTokenizerTest();
debug(DebugInfo) {
version(USE_MAGO) {
import ddebug.windows.mago;
testMago();
}
version(Windows) {
import ddebug.windows.debuginfo;
import std.file;
//debugInfoTest(thisExePath);
}
}
//debug(DebugInfo) {
// version(USE_MAGO) {
// import ddebug.windows.mago;
// testMago();
// }
// version(Windows) {
// import ddebug.windows.debuginfo;
// import std.file;
// //debugInfoTest(thisExePath);
// }
//}
version(USE_WIN_DEBUG) {
debuggerTest();
}
version(USE_GDB_DEBUG) {
debuggerTestGDB();
}
//version(USE_WIN_DEBUG) {
// debuggerTest();
//}
//version(USE_GDB_DEBUG) {
// debuggerTestGDB();
//}
// create window
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();
}
/*
version(USE_WIN_DEBUG) {
void debuggerTest() {
import ddebug.windows.windebug;
@ -129,6 +130,7 @@ version(USE_GDB_DEBUG) {
Log.d("Testing of GDB debugger is finished");
}
}
*/
unittest {
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.homescreen;
import dlangide.ui.settings;
import dlangide.ui.debuggerui;
import dlangide.tools.d.dcdserver;
import dlangide.workspace.workspace;
import dlangide.workspace.project;
@ -33,6 +34,8 @@ import dlangide.tools.editorTool;
import ddebug.common.execution;
import ddebug.common.nodebug;
import ddebug.common.debugger;
import ddebug.gdb.gdbinterface;
import std.conv;
import std.utf;
@ -88,6 +91,8 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener {
applySettings(_settings);
}
@property OutputPanel logPanel() { return _logPanel; }
/// stop current program execution
void stopExecution() {
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) {
if (!currentWorkspace)
return;
@ -139,28 +208,32 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener {
}
buildProject(BuildOperation.Build, project, delegate(int result) {
if (!result) {
runProject();
Log.i("Build completed successfully. Running program...");
runProject(project);
}
});
}
protected void runProject() {
protected void runProject(Project project) {
import std.file;
stopExecution();
if (!currentWorkspace)
return;
Project project = currentWorkspace.startupProject;
if (!project) {
window.showMessageBox(UIString("Cannot run project"d), UIString("Startup project is not specified"d));
return;
}
// build project
// TODO
string executableFileName = project.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;
}
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 externalConsoleExecutable = null;
string workingDirectory = project.workingDirectory;
@ -170,11 +243,15 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener {
externalConsoleExecutable = _settings.terminalExecutable;
}
}
// TODO: provide thread safe listener
_logPanel.logLine("Starting " ~ executableFileName);
_statusLine.setBackgroundOperationStatus("debug-run", "running..."d);
_execution = new ProgramExecutionNoDebug(executableFileName, args, workingDirectory, externalConsoleExecutable, this);
_execution.run();
if (!program.isDebugger)
_logPanel.logLine("Starting " ~ executableFileName);
else
_logPanel.logLine("Starting debugger for " ~ executableFileName);
_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() {
@ -689,8 +766,10 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener {
case IDEActions.CleanWorkspace:
buildProject(BuildOperation.Clean, cast(Project)a.objectParam);
return true;
case IDEActions.DebugStart:
case IDEActions.DebugStartNoDebug:
case IDEActions.DebugStart:
buildAndDebugProject(cast(Project)a.objectParam);
return true;
case IDEActions.DebugContinue:
buildAndRunProject(cast(Project)a.objectParam);
return true;