mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-09 03:48:08 +02:00
LazDebuggerFPDServer: Added new package to debug applications using fpdserver
git-svn-id: trunk@49022 -
This commit is contained in:
parent
673bafe26d
commit
670c24309d
4
.gitattributes
vendored
4
.gitattributes
vendored
@ -2308,6 +2308,10 @@ components/lazdebuggergdbmi/test/testwatches.pas svneol=native#text/pascal
|
||||
components/lazdebuggers/lazdebuggerfp/fpdebugdebugger.pas svneol=native#text/plain
|
||||
components/lazdebuggers/lazdebuggerfp/lazdebuggerfp.lpk svneol=native#text/plain
|
||||
components/lazdebuggers/lazdebuggerfp/lazdebuggerfp.pas svneol=native#text/plain
|
||||
components/lazdebuggers/lazdebuggerfpdserver/fpdserverdebugger.pas svneol=native#text/plain
|
||||
components/lazdebuggers/lazdebuggerfpdserver/fpdserverdebuggercommands.inc svneol=native#text/plain
|
||||
components/lazdebuggers/lazdebuggerfpdserver/lazdebuggerfpdserver.lpk svneol=native#text/plain
|
||||
components/lazdebuggers/lazdebuggerfpdserver/lazdebuggerfpdserver.pas svneol=native#text/plain
|
||||
components/lazdebuggers/lazdebuggerfpgdbmi/fpgdbmidebugger.pp svneol=native#text/pascal
|
||||
components/lazdebuggers/lazdebuggerfpgdbmi/lazdebuggerfpgdbmi.lpk svneol=native#text/pascal
|
||||
components/lazdebuggers/lazdebuggerfpgdbmi/lazdebuggerfpgdbmi.pas svneol=native#text/pascal
|
||||
|
@ -0,0 +1,866 @@
|
||||
unit FPDServerDebugger;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes,
|
||||
ssockets,
|
||||
fgl,
|
||||
forms,
|
||||
DbgIntfDebuggerBase,
|
||||
DbgIntfBaseTypes,
|
||||
fpjson,
|
||||
jsonparser,
|
||||
BaseUnix,
|
||||
LazLogger,
|
||||
dialogs,
|
||||
syncobjs,
|
||||
lazCollections,
|
||||
strutils,
|
||||
SysUtils;
|
||||
|
||||
type
|
||||
TThreadedQueueString = specialize TLazThreadedQueue<string>;
|
||||
TFPDServerDebugger = class;
|
||||
|
||||
{ TFPDSendCommand }
|
||||
|
||||
TFPDSendCommand = class
|
||||
protected
|
||||
FCommandUID: integer;
|
||||
FServerDebugger: TFPDServerDebugger;
|
||||
function GetAsString: string; virtual;
|
||||
procedure ComposeJSon(AJsonObject: TJSONObject); virtual;
|
||||
public
|
||||
constructor create; virtual;
|
||||
procedure DoOnCommandSuccesfull(ACommandResponse: TJSonObject); virtual;
|
||||
procedure DoOnCommandReceived(ACommandResponse: TJSonObject); virtual;
|
||||
procedure DoOnCommandFailed(ACommandResponse: TJSonObject); virtual;
|
||||
property CommandUID: integer read FCommandUID;
|
||||
property AsString: string read GetAsString;
|
||||
end;
|
||||
|
||||
{ TFPDSendCommandList }
|
||||
|
||||
TFPDCustomSendCommandList = specialize TFPGObjectList<TFPDSendCommand>;
|
||||
TFPDSendCommandList = class(TFPDCustomSendCommandList)
|
||||
public
|
||||
function SearchByUID(const ACommandUID: integer): TFPDSendCommand;
|
||||
end;
|
||||
|
||||
{ TFPDSendRunCommand }
|
||||
|
||||
TFPDSendRunCommand = class(TFPDSendCommand)
|
||||
protected
|
||||
procedure ComposeJSon(AJsonObject: TJSONObject); override;
|
||||
public
|
||||
procedure DoOnCommandFailed(ACommandResponse: TJSonObject); override;
|
||||
end;
|
||||
|
||||
{ TFPDSendContinueCommand }
|
||||
|
||||
TFPDSendContinueCommand = class(TFPDSendCommand)
|
||||
protected
|
||||
procedure ComposeJSon(AJsonObject: TJSONObject); override;
|
||||
end;
|
||||
|
||||
{ TFPDSendNextCommand }
|
||||
|
||||
TFPDSendNextCommand = class(TFPDSendCommand)
|
||||
protected
|
||||
procedure ComposeJSon(AJsonObject: TJSONObject); override;
|
||||
end;
|
||||
|
||||
{ TFPDSendStepCommand }
|
||||
|
||||
TFPDSendStepCommand = class(TFPDSendCommand)
|
||||
protected
|
||||
procedure ComposeJSon(AJsonObject: TJSONObject); override;
|
||||
end;
|
||||
|
||||
{ TFPDSendStepIntoInstrCommand }
|
||||
|
||||
TFPDSendStepIntoInstrCommand = class(TFPDSendCommand)
|
||||
protected
|
||||
procedure ComposeJSon(AJsonObject: TJSONObject); override;
|
||||
end;
|
||||
|
||||
{ TFPDSendStepOverInstrCommand }
|
||||
|
||||
TFPDSendStepOverInstrCommand = class(TFPDSendCommand)
|
||||
protected
|
||||
procedure ComposeJSon(AJsonObject: TJSONObject); override;
|
||||
end;
|
||||
|
||||
{ TFPDSendStopCommand }
|
||||
|
||||
TFPDSendStopCommand = class(TFPDSendCommand)
|
||||
protected
|
||||
procedure ComposeJSon(AJsonObject: TJSONObject); override;
|
||||
end;
|
||||
|
||||
{ TFPDSendStepOutCommand }
|
||||
|
||||
TFPDSendStepOutCommand = class(TFPDSendCommand)
|
||||
protected
|
||||
procedure ComposeJSon(AJsonObject: TJSONObject); override;
|
||||
end;
|
||||
|
||||
{ TFPDSendFilenameCommand }
|
||||
|
||||
TFPDSendFilenameCommand = class(TFPDSendCommand)
|
||||
private
|
||||
FFileName: string;
|
||||
protected
|
||||
procedure ComposeJSon(AJsonObject: TJSONObject); override;
|
||||
public
|
||||
constructor create(AFileName: string); virtual;
|
||||
end;
|
||||
|
||||
{ TFPDSendAddBreakpointCommand }
|
||||
|
||||
TFPDSendAddBreakpointCommand = class(TFPDSendCommand)
|
||||
private
|
||||
FFileName: string;
|
||||
FLineNr: integer;
|
||||
FLocation: TDBGPtr;
|
||||
protected
|
||||
procedure ComposeJSon(AJsonObject: TJSONObject); override;
|
||||
public
|
||||
procedure DoOnCommandFailed(ACommandResponse: TJSonObject); override;
|
||||
procedure DoOnCommandSuccesfull(ACommandResponse: TJSonObject); override;
|
||||
constructor create(AFileName: string; ALineNr: integer); virtual;
|
||||
constructor create(ALocation: TDBGPtr); virtual;
|
||||
end;
|
||||
|
||||
{ TFPDSendRemoveBreakpointCommand }
|
||||
|
||||
TFPDSendRemoveBreakpointCommand = class(TFPDSendCommand)
|
||||
private
|
||||
FLocation: TDBGPtr;
|
||||
protected
|
||||
procedure ComposeJSon(AJsonObject: TJSONObject); override;
|
||||
public
|
||||
constructor create(ALocation: TDBGPtr); virtual;
|
||||
end;
|
||||
|
||||
{ TFPDSendDoCurrentCommand }
|
||||
|
||||
TFPDSendDoCurrentCommand = class(TFPDSendCommand)
|
||||
protected
|
||||
procedure ComposeJSon(AJsonObject: TJSONObject); override;
|
||||
public
|
||||
procedure DoOnCommandSuccesfull(ACommandResponse: TJSonObject); override;
|
||||
end;
|
||||
|
||||
{ TInterruptableInetSocket }
|
||||
|
||||
TInterruptableInetSocket = class(TInetSocket)
|
||||
private
|
||||
FIntHandler: TSocketHandler;
|
||||
public
|
||||
constructor Create(const AHost: String; APort: Word; AHandler: TSocketHandler=nil); Overload;
|
||||
procedure ShutDown;
|
||||
end;
|
||||
|
||||
{ TFPDSocketThread }
|
||||
|
||||
TFPDSocketThread = class(TThread)
|
||||
private
|
||||
FConnectionIdentifier: integer;
|
||||
FSocket: TInetSocket;
|
||||
FDebugger: TFPDServerDebugger;
|
||||
FSendQueue: TThreadedQueueString;
|
||||
protected
|
||||
procedure ReceivedCommand(Data: PtrInt);
|
||||
procedure Execute; override;
|
||||
public
|
||||
constructor Create(ADebugger: TFPDServerDebugger);
|
||||
procedure SendString(AString: string);
|
||||
destructor Destroy; override;
|
||||
procedure CloseConnection;
|
||||
property ConnectionIdentifier: integer read FConnectionIdentifier;
|
||||
end;
|
||||
|
||||
{ TFPDServerDebugger }
|
||||
|
||||
TFPDServerDebugger = class(TDebuggerIntf)
|
||||
private
|
||||
FSocketThread: TFPDSocketThread;
|
||||
FCommandList: TFPDSendCommandList;
|
||||
protected
|
||||
// Overrides of TDebuggerIntf methods.
|
||||
function GetSupportedCommands: TDBGCommands; override;
|
||||
// Handle Notifications received from the FPDebug-server
|
||||
procedure HandleNotification(ANotification: TJSONObject);
|
||||
// Handle log-messages received from the FPDebug-server
|
||||
procedure HandleLog(ALog: TJSONObject);
|
||||
// Handle Events received from the FPDebug-server
|
||||
procedure HandleEvent(ANotification: TJSONObject);
|
||||
// Event-handlers for events received from the FPDebug-server
|
||||
procedure DoHandleCreateProcessEvent(AnEvent: TJSONObject);
|
||||
procedure DoHandleExitProcessEvent(AnEvent: TJSONObject);
|
||||
procedure DoHandleBreakpointEvent(AnEvent: TJSONObject);
|
||||
public
|
||||
constructor Create(const AExternalDebugger: String); override;
|
||||
destructor Destroy; override;
|
||||
// This function is called (in the main thread) by the TFPDSocketThread on each JSon-object received
|
||||
// from the FPD-Server.
|
||||
procedure ReceivedCommand(ACommand: TJSONObject);
|
||||
// Queue a command for sending to the FPDebug-server.
|
||||
procedure QueueCommand(ACommand: TFPDSendCommand);
|
||||
// Overrides of TDebuggerIntf methods.
|
||||
class function Caption: String; override;
|
||||
function CreateBreakPoints: TDBGBreakPoints; override;
|
||||
function RequestCommand(const ACommand: TDBGCommand; const AParams: array of const): Boolean; override;
|
||||
// These methods are called by several TFPDSendCommands after success or failure of a command. (Most common
|
||||
// because the TFPDSendCommands do not have access to TFPDServerDebugger's protected methods theirself)
|
||||
procedure DoOnRunFailed;
|
||||
procedure DoOnDoCurrentSuccessfull(ALocRec: TDBGLocationRec);
|
||||
end;
|
||||
|
||||
procedure Register;
|
||||
|
||||
implementation
|
||||
|
||||
type
|
||||
|
||||
{ TFPBreakpoint }
|
||||
|
||||
TFPBreakpoint = class(TDBGBreakPoint)
|
||||
private
|
||||
FSetBreakFlag: boolean;
|
||||
FResetBreakFlag: boolean;
|
||||
FIsSet: boolean;
|
||||
FUID: integer;
|
||||
procedure SetBreak;
|
||||
procedure ResetBreak;
|
||||
protected
|
||||
procedure DoStateChange(const AOldState: TDBGState); override;
|
||||
procedure DoEnableChange; override;
|
||||
procedure DoChanged; override;
|
||||
// Used in the succes or failure callbacks of the TFPDSendAddBreakpointCommand command to set the
|
||||
// validity of the underlying breakpoint.
|
||||
procedure SetValid;
|
||||
procedure SetInvalid;
|
||||
public
|
||||
destructor Destroy; override;
|
||||
property UID: integer read FUID;
|
||||
end;
|
||||
|
||||
{ TFPBreakpoints }
|
||||
|
||||
TFPBreakpoints = class(TDBGBreakPoints)
|
||||
public
|
||||
function FindByUID(AnUID: integer): TFPBreakpoint;
|
||||
end;
|
||||
|
||||
procedure Register;
|
||||
begin
|
||||
RegisterDebugger(TFPDServerDebugger);
|
||||
end;
|
||||
|
||||
{ TFPDSendCommand }
|
||||
|
||||
var GCommandUID: integer = 0;
|
||||
|
||||
{ TFPDSendRemoveBreakpointCommand }
|
||||
|
||||
procedure TFPDSendRemoveBreakpointCommand.ComposeJSon(AJsonObject: TJSONObject);
|
||||
begin
|
||||
inherited ComposeJSon(AJsonObject);
|
||||
AJsonObject.Add('command','removebreakpoint');
|
||||
AJsonObject.Add('location', Dec2Numb(FLocation, 8, 16));
|
||||
end;
|
||||
|
||||
constructor TFPDSendRemoveBreakpointCommand.create(ALocation: TDBGPtr);
|
||||
begin
|
||||
inherited create;
|
||||
FLocation:=ALocation;
|
||||
end;
|
||||
|
||||
{ TFPDSendResetBreakpointCommand }
|
||||
|
||||
function TFPDSendCommand.GetAsString: string;
|
||||
var
|
||||
AJsonObject: TJSONObject;
|
||||
begin
|
||||
AJsonObject := TJSONObject.Create;
|
||||
try
|
||||
ComposeJSon(AJsonObject);
|
||||
result := AJsonObject.AsJSON;
|
||||
finally
|
||||
AJsonObject.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TFPDSendCommand.ComposeJSon(AJsonObject: TJSONObject);
|
||||
begin
|
||||
AJsonObject.Add('uid', FCommandUID);
|
||||
end;
|
||||
|
||||
constructor TFPDSendCommand.create;
|
||||
begin
|
||||
inc(GCommandUID);
|
||||
FCommandUID := GCommandUID;
|
||||
end;
|
||||
|
||||
procedure TFPDSendCommand.DoOnCommandSuccesfull(ACommandResponse: TJSonObject);
|
||||
begin
|
||||
// Do nothing
|
||||
end;
|
||||
|
||||
procedure TFPDSendCommand.DoOnCommandReceived(ACommandResponse: TJSonObject);
|
||||
begin
|
||||
// Do nothing
|
||||
end;
|
||||
|
||||
procedure TFPDSendCommand.DoOnCommandFailed(ACommandResponse: TJSonObject);
|
||||
begin
|
||||
// Do nothing;
|
||||
end;
|
||||
|
||||
{ TFPDSendCommandList }
|
||||
|
||||
function TFPDSendCommandList.SearchByUID(const ACommandUID: integer): TFPDSendCommand;
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
for i := 0 to count -1 do
|
||||
begin
|
||||
if Items[i].CommandUID = ACommandUID then
|
||||
begin
|
||||
result := items[i];
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
result := nil;
|
||||
end;
|
||||
|
||||
{$I fpdserverdebuggercommands.inc}
|
||||
|
||||
{ TFPBreakpoint }
|
||||
|
||||
procedure TFPBreakpoint.SetBreak;
|
||||
var
|
||||
ASendCommand: TFPDSendCommand;
|
||||
begin
|
||||
ASendCommand := nil;
|
||||
case Kind of
|
||||
bpkAddress: ASendCommand := TFPDSendAddBreakpointCommand.create(Address);
|
||||
bpkSource: ASendCommand := TFPDSendAddBreakpointCommand.create(Source, Line);
|
||||
else
|
||||
Raise Exception.Create('Breakpoints of this kind are not suported.');
|
||||
end;
|
||||
if assigned(ASendCommand) then
|
||||
begin
|
||||
FUID:=ASendCommand.CommandUID;
|
||||
TFPDServerDebugger(Debugger).QueueCommand(ASendCommand);
|
||||
end;
|
||||
FIsSet:=true;
|
||||
end;
|
||||
|
||||
procedure TFPBreakpoint.ResetBreak;
|
||||
begin
|
||||
TFPDServerDebugger(Debugger).QueueCommand(TFPDSendRemoveBreakpointCommand.create(Address));
|
||||
FIsSet:=false;
|
||||
end;
|
||||
|
||||
procedure TFPBreakpoint.DoStateChange(const AOldState: TDBGState);
|
||||
begin
|
||||
if (Debugger.State in [dsPause, dsInternalPause]) then
|
||||
begin
|
||||
if Enabled and not FIsSet then
|
||||
begin
|
||||
FSetBreakFlag:=true;
|
||||
Changed;
|
||||
end
|
||||
else if not enabled and FIsSet then
|
||||
begin
|
||||
FResetBreakFlag:=true;
|
||||
Changed;
|
||||
end;
|
||||
end
|
||||
else if Debugger.State = dsStop then
|
||||
begin
|
||||
FIsSet:=false;
|
||||
end;
|
||||
inherited DoStateChange(AOldState);
|
||||
end;
|
||||
|
||||
procedure TFPBreakpoint.DoEnableChange;
|
||||
begin
|
||||
if (Debugger.State in [dsPause, dsInit, dsRun]) then
|
||||
begin
|
||||
if FEnabled and not FIsSet then
|
||||
FSetBreakFlag := True
|
||||
else if not FEnabled and FIsSet then
|
||||
FResetBreakFlag := True;
|
||||
end;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
procedure TFPBreakpoint.DoChanged;
|
||||
begin
|
||||
if FResetBreakFlag and not FSetBreakFlag then
|
||||
ResetBreak
|
||||
else if FSetBreakFlag then
|
||||
SetBreak;
|
||||
|
||||
FSetBreakFlag := false;
|
||||
FResetBreakFlag := false;
|
||||
|
||||
inherited DoChanged;
|
||||
end;
|
||||
|
||||
procedure TFPBreakpoint.SetValid;
|
||||
begin
|
||||
FValid:=vsValid;
|
||||
end;
|
||||
|
||||
procedure TFPBreakpoint.SetInvalid;
|
||||
begin
|
||||
FValid:=vsInvalid;
|
||||
end;
|
||||
|
||||
destructor TFPBreakpoint.Destroy;
|
||||
begin
|
||||
if FIsSet then
|
||||
ResetBreak;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
function TFPBreakpoints.FindByUID(AnUID: integer): TFPBreakpoint;
|
||||
var
|
||||
i: integer;
|
||||
begin
|
||||
for i := 0 to Count-1 do
|
||||
if TFPBreakpoint(Items[i]).UID=AnUID then
|
||||
begin
|
||||
result := TFPBreakpoint(Items[i]);
|
||||
exit;
|
||||
end;
|
||||
result := nil;
|
||||
end;
|
||||
|
||||
{ TInterruptableInetSocket }
|
||||
|
||||
constructor TInterruptableInetSocket.Create(const AHost: String; APort: Word; AHandler: TSocketHandler);
|
||||
begin
|
||||
if not assigned(AHandler) then
|
||||
FIntHandler := TSocketHandler.Create
|
||||
else
|
||||
FIntHandler := AHandler;
|
||||
inherited create(AHost, APort, AHandler)
|
||||
end;
|
||||
|
||||
procedure TInterruptableInetSocket.ShutDown;
|
||||
begin
|
||||
FIntHandler.Shutdown(true);
|
||||
end;
|
||||
|
||||
{ TFPDSocketThread }
|
||||
|
||||
procedure TFPDSocketThread.ReceivedCommand(Data: PtrInt);
|
||||
var
|
||||
ACommand: TJSONObject;
|
||||
begin
|
||||
ACommand := TObject(data) as TJSONObject;;
|
||||
FDebugger.ReceivedCommand(ACommand);
|
||||
ACommand.Free;
|
||||
end;
|
||||
|
||||
procedure TFPDSocketThread.Execute;
|
||||
const
|
||||
InputBufferSize = 1024;
|
||||
var
|
||||
SendStr: string;
|
||||
s: string;
|
||||
i: integer;
|
||||
InputStr: string;
|
||||
JSonData: TJSONData;
|
||||
|
||||
function ReadString: string;
|
||||
var
|
||||
InputBuffer: array[0..InputBufferSize-1] of char;
|
||||
s: string;
|
||||
begin
|
||||
// First see if there is a string left in the input-buffer.
|
||||
i := pos(#10, InputStr);
|
||||
if i > 0 then
|
||||
begin
|
||||
s := copy(InputStr, 1, i-1);
|
||||
delete(InputStr,1,i);
|
||||
result := s;
|
||||
exit;
|
||||
end;
|
||||
|
||||
result := '';
|
||||
i := FSocket.Read(InputBuffer[0], InputBufferSize-1);
|
||||
if i=0 then
|
||||
begin
|
||||
// Connection closed
|
||||
DebugLn('Lost Connection with FPDebug-server.');
|
||||
Terminate;
|
||||
end
|
||||
else if i<0 then
|
||||
begin
|
||||
if FSocket.LastError<>35 {EAGAIN} then
|
||||
begin
|
||||
debugln('Error during write. Socket-error: '+inttostr(FSocket.LastError));
|
||||
Terminate;
|
||||
end;
|
||||
end
|
||||
else if i > 0 then
|
||||
begin
|
||||
setlength(s,i);
|
||||
move(InputBuffer[0],s[1],i);
|
||||
InputStr:=InputStr+s;
|
||||
i := pos(#10, InputStr);
|
||||
if i > 0 then
|
||||
begin
|
||||
s := copy(InputStr, 1, i-1);
|
||||
delete(InputStr,1,i);
|
||||
result := s;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
function ReadSTringTimeout(ATimeout: integer): string;
|
||||
var
|
||||
tc: int64;
|
||||
begin
|
||||
tc := GetTickCount64;
|
||||
result := ReadString;
|
||||
while not terminated and (result='') and ((GetTickCount64-tc)<ATimeout) do
|
||||
result := ReadString;
|
||||
end;
|
||||
|
||||
begin
|
||||
try
|
||||
FSocket := TInterruptableInetSocket.Create('127.0.0.1',9001);
|
||||
|
||||
// Set non-blocking
|
||||
fpfcntl(FSocket.Handle,F_SETFL,O_NONBLOCK);
|
||||
|
||||
// Read and check FPDebug Server greeting
|
||||
s := ReadSTringTimeout(100);
|
||||
if s<>'Welcome to FPDebug-server.' then
|
||||
Exit;
|
||||
// Read connection-identifier
|
||||
s := ReadSTringTimeout(100);
|
||||
delete(s,length(s),1);
|
||||
s := copy(s, rpos(' ',s)+1, 5);
|
||||
FConnectionIdentifier:=StrToIntDef(s,-1);
|
||||
if FConnectionIdentifier=-1 then
|
||||
raise Exception.Create('Failed to retreive connection-identifier');
|
||||
|
||||
// Skip help-message
|
||||
s := ReadSTringTimeout(100);
|
||||
|
||||
while not terminated do
|
||||
begin
|
||||
s:=ReadString;
|
||||
if s<>'' then
|
||||
begin
|
||||
JSonData := GetJSON(s);
|
||||
if JSonData is TJSONObject then
|
||||
Application.QueueAsyncCall(@ReceivedCommand, ptrint(JSonData))
|
||||
else
|
||||
raise exception.CreateFmt('JSon-command %s is not a JSON-Object.',[s]);
|
||||
end;
|
||||
|
||||
if not terminated and (FSendQueue.PopItem(SendStr) = wrSignaled) then
|
||||
begin
|
||||
SendStr := SendStr + #10;
|
||||
i := FSocket.Write(SendStr[1], length(SendStr));
|
||||
|
||||
if i < 0 then
|
||||
begin
|
||||
if FSocket.LastError=32 then
|
||||
begin
|
||||
// Lost connection
|
||||
end
|
||||
else
|
||||
DebugLn(Format('Error during write. Socket-error: %d',[FSocket.LastError]));
|
||||
Terminate;
|
||||
end
|
||||
else if i < length(SendStr) then
|
||||
raise exception.create('Message has not been send to client entirely');
|
||||
end;
|
||||
end;
|
||||
except
|
||||
on E: Exception do
|
||||
DebugLn('Exception in SocketThread: '+E.Message);
|
||||
end;
|
||||
end;
|
||||
|
||||
constructor TFPDSocketThread.Create(ADebugger: TFPDServerDebugger);
|
||||
begin
|
||||
FDebugger := ADebugger;
|
||||
FSendQueue:=TThreadedQueueString.create(100, INFINITE, 100);
|
||||
inherited create(false);
|
||||
end;
|
||||
|
||||
procedure TFPDSocketThread.SendString(AString: string);
|
||||
begin
|
||||
if Assigned(FDebugger.OnDbgOutput) then
|
||||
FDebugger.OnDbgOutput(Self, 'send: '+AString);
|
||||
FSendQueue.PushItem(AString);
|
||||
end;
|
||||
|
||||
destructor TFPDSocketThread.Destroy;
|
||||
begin
|
||||
FSendQueue.Free;
|
||||
FSocket.Free;
|
||||
inherited destroy;
|
||||
end;
|
||||
|
||||
procedure TFPDSocketThread.CloseConnection;
|
||||
begin
|
||||
Terminate;
|
||||
(FSocket as TInterruptableInetSocket).ShutDown;
|
||||
end;
|
||||
|
||||
{ TFPDServerDebugger }
|
||||
|
||||
function TFPDServerDebugger.GetSupportedCommands: TDBGCommands;
|
||||
begin
|
||||
Result:=[dcRun, dcStepOver, dcStepInto, dcStepOut, dcStepOverInstr, dcStepIntoInstr, dcStop];
|
||||
end;
|
||||
|
||||
procedure TFPDServerDebugger.DoHandleCreateProcessEvent(AnEvent: TJSONObject);
|
||||
begin
|
||||
SetState(dsInternalPause);
|
||||
QueueCommand(TFPDSendContinueCommand.create);
|
||||
end;
|
||||
|
||||
procedure TFPDServerDebugger.DoHandleExitProcessEvent(AnEvent: TJSONObject);
|
||||
begin
|
||||
SetState(dsStop);
|
||||
end;
|
||||
|
||||
procedure TFPDServerDebugger.DoHandleBreakpointEvent(AnEvent: TJSONObject);
|
||||
var
|
||||
BrkLocation: string;
|
||||
Brk: TDBGBreakPoint;
|
||||
Continue: boolean;
|
||||
begin
|
||||
BrkLocation:=AnEvent.Get('breakpointLocation','');
|
||||
if BrkLocation<>'' then
|
||||
begin
|
||||
Brk := BreakPoints.Find(Hex2Dec(BrkLocation));
|
||||
if not assigned(brk) then
|
||||
debugln('Break on unknown breakpoint')
|
||||
else
|
||||
begin
|
||||
brk.Hit(Continue);
|
||||
if Continue then
|
||||
begin
|
||||
QueueCommand(TFPDSendContinueCommand.create);
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
SetState(dsPause);
|
||||
QueueCommand(TFPDSendDoCurrentCommand.create);
|
||||
end;
|
||||
|
||||
procedure TFPDServerDebugger.HandleNotification(ANotification: TJSONObject);
|
||||
var
|
||||
NotificationType: string;
|
||||
UID: integer;
|
||||
SendCommand: TFPDSendCommand;
|
||||
begin
|
||||
// Ignore notifications from other connections
|
||||
if ANotification.get('connIdentifier',-1)=FSocketThread.ConnectionIdentifier then
|
||||
begin
|
||||
NotificationType:=ANotification.Get('notificationType','');
|
||||
case NotificationType of
|
||||
'InvalidCommand':
|
||||
raise exception.CreateFmt('The FPD-Server complains about an invalid command: %s',[ANotification.get('message', '-')]);
|
||||
'ExecutedCommand', 'FailedCommand', 'ReceivedCommand':
|
||||
begin
|
||||
uid := ANotification.get('uid',-1);
|
||||
if uid > -1 then
|
||||
begin
|
||||
SendCommand := FCommandList.SearchByUID(uid);
|
||||
if assigned(SendCommand) then
|
||||
begin
|
||||
case NotificationType of
|
||||
'ExecutedCommand':
|
||||
begin
|
||||
SendCommand.DoOnCommandSuccesfull(ANotification);
|
||||
FCommandList.Remove(SendCommand);
|
||||
end;
|
||||
'FailedCommand' :
|
||||
begin
|
||||
SendCommand.DoOnCommandFailed(ANotification);
|
||||
FCommandList.Remove(SendCommand);
|
||||
end;
|
||||
'ReceivedCommand':
|
||||
SendCommand.DoOnCommandReceived(ANotification);
|
||||
end; {case}
|
||||
end
|
||||
else
|
||||
debugln('Received command-notification for unknown command-uid '+inttostr(UID));
|
||||
end
|
||||
else
|
||||
debugln('Received command notification without UID');
|
||||
end;
|
||||
end; {case}
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TFPDServerDebugger.HandleLog(ALog: TJSONObject);
|
||||
var
|
||||
LogType: string;
|
||||
Message: string;
|
||||
begin
|
||||
LogType:=ALog.get('logType','');
|
||||
Message:=ALog.Get('message','');
|
||||
case LogType of
|
||||
'debug' : DebugLn(Message);
|
||||
'info' : ShowMessage(Message);
|
||||
'error' : raise Exception.Create(Message);
|
||||
else
|
||||
raise Exception.CreateFmt('Received unknown log-type from FPDebug-server. (%s)', [LogType]);
|
||||
end; {case}
|
||||
end;
|
||||
|
||||
procedure TFPDServerDebugger.HandleEvent(ANotification: TJSONObject);
|
||||
var
|
||||
EventName: string;
|
||||
begin
|
||||
EventName:=ANotification.get('eventName','');
|
||||
case EventName of
|
||||
'CreateProcess' : DoHandleCreateProcessEvent(ANotification);
|
||||
'ExitProcess' : DoHandleExitProcessEvent(ANotification);
|
||||
'BreakPoint' : DoHandleBreakPointEvent(ANotification)
|
||||
else
|
||||
debugln('Received unknown event: '+EventName);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TFPDServerDebugger.QueueCommand(ACommand: TFPDSendCommand);
|
||||
begin
|
||||
ACommand.FServerDebugger := self;
|
||||
FCommandList.Add(ACommand);
|
||||
FSocketThread.SendString(ACommand.AsString);
|
||||
end;
|
||||
|
||||
constructor TFPDServerDebugger.Create(const AExternalDebugger: String);
|
||||
begin
|
||||
inherited Create(AExternalDebugger);
|
||||
FSocketThread := TFPDSocketThread.Create(Self);
|
||||
FCommandList := TFPDSendCommandList.Create(true);
|
||||
end;
|
||||
|
||||
destructor TFPDServerDebugger.Destroy;
|
||||
begin
|
||||
FSocketThread.CloseConnection;
|
||||
FSocketThread.WaitFor;
|
||||
FSocketThread.Free;
|
||||
FCommandList.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
procedure TFPDServerDebugger.ReceivedCommand(ACommand: TJSONObject);
|
||||
var
|
||||
TypeStr: string;
|
||||
begin
|
||||
DoDbgOutput('recv: '+ACommand.AsJSON);
|
||||
|
||||
TypeStr := ACommand.Get('type','');
|
||||
case TypeStr of
|
||||
'event' : HandleEvent(ACommand);
|
||||
'log' : HandleLog(ACommand);
|
||||
'notification' : HandleNotification(ACommand);
|
||||
else
|
||||
raise Exception.CreateFmt('Received unknown event-type. (%s)',[TypeStr]);
|
||||
end;
|
||||
end;
|
||||
|
||||
class function TFPDServerDebugger.Caption: String;
|
||||
begin
|
||||
Result:='FpDebug external Dwarf-debugger (fpdserver, alpha)';
|
||||
end;
|
||||
|
||||
function TFPDServerDebugger.CreateBreakPoints: TDBGBreakPoints;
|
||||
begin
|
||||
Result := TFPBreakPoints.Create(Self, TFPBreakpoint);
|
||||
end;
|
||||
|
||||
function TFPDServerDebugger.RequestCommand(const ACommand: TDBGCommand; const AParams: array of const): Boolean;
|
||||
begin
|
||||
result := true;
|
||||
case ACommand of
|
||||
dcRun:
|
||||
begin
|
||||
if State in [dsPause, dsInternalPause] then
|
||||
begin
|
||||
QueueCommand(TFPDSendContinueCommand.create);
|
||||
SetState(dsRun);
|
||||
end
|
||||
else
|
||||
begin
|
||||
QueueCommand(TFPDSendFilenameCommand.create(FileName));
|
||||
QueueCommand(TFPDSendRunCommand.create);
|
||||
SetState(dsInit);
|
||||
end;
|
||||
end;
|
||||
dcStepOver:
|
||||
begin
|
||||
QueueCommand(TFPDSendNextCommand.create);
|
||||
SetState(dsRun);
|
||||
end;
|
||||
dcStepInto:
|
||||
begin
|
||||
QueueCommand(TFPDSendStepCommand.create);
|
||||
SetState(dsRun);
|
||||
end;
|
||||
dcStepIntoInstr:
|
||||
begin
|
||||
QueueCommand(TFPDSendStepIntoInstrCommand.create);
|
||||
SetState(dsRun);
|
||||
end;
|
||||
dcStepOverInstr:
|
||||
begin
|
||||
QueueCommand(TFPDSendStepOverInstrCommand.create);
|
||||
SetState(dsRun);
|
||||
end;
|
||||
dcStepOut:
|
||||
begin
|
||||
QueueCommand(TFPDSendStepOutCommand.create);
|
||||
SetState(dsRun);
|
||||
end;
|
||||
dcStop:
|
||||
begin
|
||||
QueueCommand(TFPDSendStopCommand.create);
|
||||
if state=dsPause then
|
||||
SetState(dsRun);
|
||||
end
|
||||
else
|
||||
result := false;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TFPDServerDebugger.DoOnRunFailed;
|
||||
begin
|
||||
// TDebuggerIntf.SetFileName has set the state to dsStop, to make sure
|
||||
// that dcRun could be requested. Reset the filename so that the state
|
||||
// is set to dsIdle again and is set to dsStop on the next try
|
||||
// to run.
|
||||
FileName := ''
|
||||
end;
|
||||
|
||||
procedure TFPDServerDebugger.DoOnDoCurrentSuccessfull(ALocRec: TDBGLocationRec);
|
||||
begin
|
||||
DoCurrent(ALocRec);
|
||||
end;
|
||||
|
||||
end.
|
||||
|
@ -0,0 +1,165 @@
|
||||
{ TFPDSendFilenameCommand }
|
||||
|
||||
procedure TFPDSendFilenameCommand.ComposeJSon(AJsonObject: TJSONObject);
|
||||
begin
|
||||
inherited ComposeJSon(AJsonObject);
|
||||
AJsonObject.Add('command','filename');
|
||||
AJsonObject.Add('filename',FFileName);
|
||||
end;
|
||||
|
||||
constructor TFPDSendFilenameCommand.create(AFileName: string);
|
||||
begin
|
||||
inherited create;
|
||||
FFileName:=AFileName;
|
||||
end;
|
||||
|
||||
{ TFPDSendRunCommand }
|
||||
|
||||
procedure TFPDSendRunCommand.DoOnCommandFailed(ACommandResponse: TJSonObject);
|
||||
begin
|
||||
FServerDebugger.DoOnRunFailed;
|
||||
end;
|
||||
|
||||
procedure TFPDSendRunCommand.ComposeJSon(AJsonObject: TJSONObject);
|
||||
begin
|
||||
inherited ComposeJSon(AJsonObject);
|
||||
AJsonObject.Add('command','run');
|
||||
end;
|
||||
|
||||
{ TFPDSendStepOutCommand }
|
||||
|
||||
procedure TFPDSendStepOutCommand.ComposeJSon(AJsonObject: TJSONObject);
|
||||
begin
|
||||
inherited ComposeJSon(AJsonObject);
|
||||
AJsonObject.Add('command','stepout');
|
||||
end;
|
||||
|
||||
{ TFPDSendStepOverInstrCommand }
|
||||
|
||||
procedure TFPDSendStepOverInstrCommand.ComposeJSon(AJsonObject: TJSONObject);
|
||||
begin
|
||||
inherited ComposeJSon(AJsonObject);
|
||||
AJsonObject.Add('command','stepoverinstr');
|
||||
end;
|
||||
|
||||
{ TFPDSendStepIntoInstrCommand }
|
||||
|
||||
procedure TFPDSendStepIntoInstrCommand.ComposeJSon(AJsonObject: TJSONObject);
|
||||
begin
|
||||
inherited ComposeJSon(AJsonObject);
|
||||
AJsonObject.Add('command','stepintoinstr');
|
||||
end;
|
||||
|
||||
{ TFPDSendStepCommand }
|
||||
|
||||
procedure TFPDSendStepCommand.ComposeJSon(AJsonObject: TJSONObject);
|
||||
begin
|
||||
inherited ComposeJSon(AJsonObject);
|
||||
AJsonObject.Add('command','step');
|
||||
end;
|
||||
|
||||
{ TFPDSendStopCommand }
|
||||
|
||||
procedure TFPDSendStopCommand.ComposeJSon(AJsonObject: TJSONObject);
|
||||
begin
|
||||
inherited ComposeJSon(AJsonObject);
|
||||
AJsonObject.Add('command','stop');
|
||||
end;
|
||||
|
||||
{ TFPDSendNextCommand }
|
||||
|
||||
procedure TFPDSendNextCommand.ComposeJSon(AJsonObject: TJSONObject);
|
||||
begin
|
||||
inherited ComposeJSon(AJsonObject);
|
||||
AJsonObject.Add('command','next');
|
||||
end;
|
||||
|
||||
{ TFPDSendDoCurrentCommand }
|
||||
|
||||
procedure TFPDSendDoCurrentCommand.ComposeJSon(AJsonObject: TJSONObject);
|
||||
begin
|
||||
inherited ComposeJSon(AJsonObject);
|
||||
AJsonObject.Add('command','getlocationinfo');
|
||||
end;
|
||||
|
||||
procedure TFPDSendDoCurrentCommand.DoOnCommandSuccesfull(ACommandResponse: TJSonObject);
|
||||
var
|
||||
LocRecJSon: TJSONObject;
|
||||
LocRec: TDBGLocationRec;
|
||||
begin
|
||||
LocRecJSon := ACommandResponse.Find('locationRec') as TJSONObject;
|
||||
if assigned(LocRecJSon) then
|
||||
begin
|
||||
LocRec.Address:=Hex2Dec(LocRecJSon.Get('address','0'));
|
||||
LocRec.FuncName:=LocRecJSon.Get('funcName','');
|
||||
LocRec.SrcFile:=LocRecJSon.Get('srcFile','');
|
||||
LocRec.SrcFullName:=LocRecJSon.Get('srcFullName','');
|
||||
LocRec.SrcLine:=LocRecJSon.Get('srcLine',-1);
|
||||
FServerDebugger.DoOnDoCurrentSuccessfull(LocRec);
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TFPDSendAddBreakpointCommand }
|
||||
|
||||
procedure TFPDSendAddBreakpointCommand.DoOnCommandFailed(ACommandResponse: TJSonObject);
|
||||
var
|
||||
ABreakpoint: TFPBreakpoint;
|
||||
begin
|
||||
ABreakpoint := TFPBreakpoints(FServerDebugger.BreakPoints).FindByUID(CommandUID);
|
||||
if assigned(ABreakpoint) then
|
||||
begin
|
||||
ABreakpoint.Address:=0;
|
||||
ABreakpoint.SetInvalid;
|
||||
ABreakpoint.DoChanged;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TFPDSendAddBreakpointCommand.DoOnCommandSuccesfull(ACommandResponse: TJSonObject);
|
||||
var
|
||||
ABreakpoint: TFPBreakpoint;
|
||||
begin
|
||||
ABreakpoint := TFPBreakpoints(FServerDebugger.BreakPoints).FindByUID(CommandUID);
|
||||
if assigned(ABreakpoint) then
|
||||
begin
|
||||
ABreakpoint.Address:=Hex2Dec(ACommandResponse.get('breakpointLocation','0'));
|
||||
ABreakpoint.SetValid;
|
||||
ABreakpoint.DoChanged;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TFPDSendAddBreakpointCommand.ComposeJSon(AJsonObject: TJSONObject);
|
||||
begin
|
||||
inherited ComposeJSon(AJsonObject);
|
||||
AJsonObject.Add('command','addbreakpoint');
|
||||
if FFileName<>'' then
|
||||
begin
|
||||
AJsonObject.Add('filename',FFileName);
|
||||
AJsonObject.Add('line',FLineNr);
|
||||
end
|
||||
else
|
||||
begin
|
||||
AJsonObject.Add('location', Dec2Numb(FLocation, 8, 16));
|
||||
end;
|
||||
end;
|
||||
|
||||
constructor TFPDSendAddBreakpointCommand.create(AFileName: string; ALineNr: integer);
|
||||
begin
|
||||
inherited create;
|
||||
FFileName:=AFileName;
|
||||
FLineNr:=ALineNr;
|
||||
end;
|
||||
|
||||
constructor TFPDSendAddBreakpointCommand.create(ALocation: TDBGPtr);
|
||||
begin
|
||||
inherited create;
|
||||
FLocation:=ALocation;
|
||||
end;
|
||||
|
||||
{ TFPDSendContinueCommand }
|
||||
|
||||
procedure TFPDSendContinueCommand.ComposeJSon(AJsonObject: TJSONObject);
|
||||
begin
|
||||
inherited ComposeJSon(AJsonObject);
|
||||
AJsonObject.Add('command','continue');
|
||||
end;
|
||||
|
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CONFIG>
|
||||
<Package Version="4">
|
||||
<Name Value="LazDebuggerFPDServer"/>
|
||||
<Type Value="DesignTime"/>
|
||||
<Author Value="Joost van der Sluis"/>
|
||||
<CompilerOptions>
|
||||
<Version Value="11"/>
|
||||
<SearchPaths>
|
||||
<UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
|
||||
</SearchPaths>
|
||||
</CompilerOptions>
|
||||
<Description Value="Use fpdserver to debug applications"/>
|
||||
<Version Minor="1"/>
|
||||
<Files Count="2">
|
||||
<Item1>
|
||||
<Filename Value="fpdserverdebugger.pas"/>
|
||||
<HasRegisterProc Value="True"/>
|
||||
<UnitName Value="FPDServerDebugger"/>
|
||||
</Item1>
|
||||
<Item2>
|
||||
<Filename Value="fpdserverdebuggercommands.inc"/>
|
||||
<Type Value="Binary"/>
|
||||
</Item2>
|
||||
</Files>
|
||||
<RequiredPkgs Count="2">
|
||||
<Item1>
|
||||
<PackageName Value="DebuggerIntf"/>
|
||||
</Item1>
|
||||
<Item2>
|
||||
<PackageName Value="FCL"/>
|
||||
</Item2>
|
||||
</RequiredPkgs>
|
||||
<UsageOptions>
|
||||
<UnitPath Value="$(PkgOutDir)"/>
|
||||
</UsageOptions>
|
||||
<PublishOptions>
|
||||
<Version Value="2"/>
|
||||
</PublishOptions>
|
||||
<CustomOptions Items="ExternHelp" Version="2">
|
||||
<_ExternHelp Items="Count"/>
|
||||
</CustomOptions>
|
||||
</Package>
|
||||
</CONFIG>
|
@ -0,0 +1,21 @@
|
||||
{ This file was automatically created by Lazarus. Do not edit!
|
||||
This source is only used to compile and install the package.
|
||||
}
|
||||
|
||||
unit LazDebuggerFPDServer;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
FPDServerDebugger, LazarusPackageIntf;
|
||||
|
||||
implementation
|
||||
|
||||
procedure Register;
|
||||
begin
|
||||
RegisterUnit('FPDServerDebugger', @FPDServerDebugger.Register);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterPackage('LazDebuggerFPDServer', @Register);
|
||||
end.
|
Loading…
Reference in New Issue
Block a user