mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-31 03:32:45 +02:00
FPDServer: Stand-alone debugger that can be controlled from the console or remote through tcp/ip
git-svn-id: trunk@48991 -
This commit is contained in:
parent
b4b8dab3b4
commit
001be758a5
8
.gitattributes
vendored
8
.gitattributes
vendored
@ -1343,6 +1343,14 @@ components/fpdebug/app/fpd/fpdpeimage.pas svneol=native#text/pascal
|
||||
components/fpdebug/app/fpd/fpdtype.pas svneol=native#text/pascal
|
||||
components/fpdebug/app/fpdd/fpdumpdwarf.lpi svneol=native#text/plain
|
||||
components/fpdebug/app/fpdd/fpdumpdwarf.lpr svneol=native#text/pascal
|
||||
components/fpdebug/app/fpdserver/Info.plist svneol=native#text/plain
|
||||
components/fpdebug/app/fpdserver/debuginoutputprocessor.pas svneol=native#text/plain
|
||||
components/fpdebug/app/fpdserver/debugtcpserver.pas svneol=native#text/plain
|
||||
components/fpdebug/app/fpdserver/debugthread.pas svneol=native#text/plain
|
||||
components/fpdebug/app/fpdserver/debugthreadcommand.pas svneol=native#text/plain
|
||||
components/fpdebug/app/fpdserver/fpdserver.lpi svneol=native#text/plain
|
||||
components/fpdebug/app/fpdserver/fpdserver.lpr svneol=native#text/plain
|
||||
components/fpdebug/app/fpdserver/readme.txt svneol=native#text/plain
|
||||
components/fpdebug/fpdbgclasses.pp svneol=native#text/pascal
|
||||
components/fpdebug/fpdbgcontroller.pas svneol=native#text/plain
|
||||
components/fpdebug/fpdbgdarwinclasses.pas svneol=native#text/plain
|
||||
|
20
components/fpdebug/app/fpdserver/Info.plist
Normal file
20
components/fpdebug/app/fpdserver/Info.plist
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.freepascal.fpdserver</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>fpdserver</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>SecTaskAccess</key>
|
||||
<array>
|
||||
<string>allowed</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
197
components/fpdebug/app/fpdserver/debuginoutputprocessor.pas
Normal file
197
components/fpdebug/app/fpdserver/debuginoutputprocessor.pas
Normal file
@ -0,0 +1,197 @@
|
||||
unit DebugInOutputProcessor;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes,
|
||||
SysUtils,
|
||||
fpjson,
|
||||
FpDbgUtil,
|
||||
DebugThreadCommand,
|
||||
debugthread,
|
||||
FpDbgClasses,
|
||||
typinfo,
|
||||
varutils,
|
||||
variants,
|
||||
jsonparser;
|
||||
|
||||
type
|
||||
|
||||
{ TCustomInOutputProcessor }
|
||||
|
||||
TCustomInOutputProcessor = class
|
||||
private
|
||||
FConnectionIdentifier: integer;
|
||||
protected
|
||||
FOnLog: TOnLog;
|
||||
public
|
||||
constructor create(AConnectionIdentifier: integer; AnOnLog: TOnLog); virtual;
|
||||
function TextToCommand(const ACommandText: string): TFpDebugThreadCommand; virtual; abstract;
|
||||
function EventToText(AnEvent: TFpDebugEvent): string; virtual; abstract;
|
||||
end;
|
||||
|
||||
{ TJSonInOutputProcessor }
|
||||
|
||||
TJSonInOutputProcessor = class(TCustomInOutputProcessor)
|
||||
public
|
||||
function TextToCommand(const ACommandText: string): TFpDebugThreadCommand; override;
|
||||
function EventToText(AnEvent: TFpDebugEvent): string; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
var
|
||||
GJSonInOutputProcessor: TJSonInOutputProcessor = nil;
|
||||
|
||||
{ TCustomInOutputProcessor }
|
||||
|
||||
constructor TCustomInOutputProcessor.create(AConnectionIdentifier: integer; AnOnLog: TOnLog);
|
||||
begin
|
||||
FConnectionIdentifier:=AConnectionIdentifier;
|
||||
FOnLog:=AnOnLog;
|
||||
end;
|
||||
|
||||
{ TJSonInOutputProcessor }
|
||||
|
||||
function TJSonInOutputProcessor.TextToCommand(const ACommandText: string): TFpDebugThreadCommand;
|
||||
var
|
||||
AJSonCommand: TJSONData;
|
||||
AJSonProp: TJSONData;
|
||||
AJSonUID: TJSONData;
|
||||
AnUID: variant;
|
||||
ACommandClass: TFpDebugThreadCommandClass;
|
||||
s: string;
|
||||
i: integer;
|
||||
APropCount: integer;
|
||||
APropList: PPropList;
|
||||
APropName: string;
|
||||
begin
|
||||
result := nil;
|
||||
try
|
||||
AJSonCommand := GetJSON(ACommandText);
|
||||
except
|
||||
on E: Exception do
|
||||
begin
|
||||
TFpDebugThread.Instance.SendNotification(FConnectionIdentifier, ntInvalidCommand, NULL, 'Command "%s" is not a valid JSON string: %s', ACommandText, [ACommandText, e.Message]);
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
if not assigned(AJSonCommand) then
|
||||
begin
|
||||
TFpDebugThread.Instance.SendNotification(FConnectionIdentifier, ntInvalidCommand, NULL, 'Command "%s" is not a valid JSON string.', ACommandText, [ACommandText]);
|
||||
exit;
|
||||
end;
|
||||
|
||||
try
|
||||
if AJSonCommand.JSONType<>jtObject then
|
||||
begin
|
||||
TFpDebugThread.Instance.SendNotification(FConnectionIdentifier, ntInvalidCommand, NULL, 'Command "%s" is not a JSON-object.', ACommandText, [ACommandText]);
|
||||
exit;
|
||||
end;
|
||||
s := TJSONObject(AJSonCommand).Get('command', '');
|
||||
if s = '' then
|
||||
begin
|
||||
TFpDebugThread.Instance.SendNotification(FConnectionIdentifier, ntInvalidCommand, NULL, 'Command "%s" does not contain a "command" entry.', ACommandText,[ACommandText]);
|
||||
exit;
|
||||
end;
|
||||
ACommandClass := TFpDebugThreadCommandList.instance.GetCommandByName(s);
|
||||
if not assigned(ACommandClass) then
|
||||
begin
|
||||
TFpDebugThread.Instance.SendNotification(FConnectionIdentifier, ntInvalidCommand, NULL, 'Command "%s" does not exist.', s, [S]);
|
||||
exit;
|
||||
end;
|
||||
|
||||
AJSonUID := TJSONObject(AJSonCommand).find('uid');
|
||||
if assigned(AJSonUID) then
|
||||
AnUID := AJSonUID.Value
|
||||
else
|
||||
AnUID := null;
|
||||
|
||||
result := ACommandClass.Create(FConnectionIdentifier, AnUID, FOnLog);
|
||||
APropCount := GetPropList(result, APropList);
|
||||
for i := 0 to APropCount-1 do
|
||||
begin
|
||||
APropName := APropList^[i]^.Name;
|
||||
AJSonProp := TJSONObject(AJSonCommand).Find(LowerCase(APropName));
|
||||
|
||||
if assigned(AJSonProp) then
|
||||
begin
|
||||
case APropList^[i]^.PropType^.Kind of
|
||||
tkAString, tkString, tkUString:
|
||||
SetStrProp(result, APropList^[i], AJSonProp.AsString);
|
||||
tkInteger:
|
||||
SetOrdProp(result, APropList^[i], AJSonProp.AsInteger);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
AJSonCommand.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TJSonInOutputProcessor.EventToText(AnEvent: TFpDebugEvent): string;
|
||||
var
|
||||
s: string;
|
||||
JSonEvent: TJSONObject;
|
||||
JSonLocationRec: TJSONObject;
|
||||
begin
|
||||
JSonEvent := TJSONObject.Create;
|
||||
try
|
||||
JSonEvent.Add('type',FpEventTypeNames[AnEvent.EventType]);
|
||||
if AnEvent.BreakpointAddr<>0 then
|
||||
JSonEvent.Add('breakpointLocation', FormatAddress(AnEvent.BreakpointAddr));
|
||||
if AnEvent.SendByConnectionIdentifier>0 then
|
||||
JSonEvent.Add('connIdentifier', AnEvent.SendByConnectionIdentifier);
|
||||
if AnEvent.LocationRec.Address <> 0 then
|
||||
begin
|
||||
JSonLocationRec := TJSONObject.Create;
|
||||
JSonLocationRec.Add('address', FormatAddress(AnEvent.LocationRec.Address));
|
||||
JSonLocationRec.Add('funcName', AnEvent.LocationRec.FuncName);
|
||||
JSonLocationRec.Add('srcFile', AnEvent.LocationRec.SrcFile);
|
||||
JSonLocationRec.Add('srcFullName', AnEvent.LocationRec.SrcFullName);
|
||||
JSonLocationRec.Add('srcLine', AnEvent.LocationRec.SrcLine);
|
||||
JSonEvent.Add('locationRec',JSonLocationRec);
|
||||
end;
|
||||
if not varisnull(AnEvent.AnUID) then
|
||||
begin
|
||||
if VarIsOrdinal(AnEvent.AnUID) then
|
||||
JSonEvent.Add('uid', integer(AnEvent.AnUID))
|
||||
else
|
||||
JSonEvent.Add('uid', VarToStr(AnEvent.AnUID));
|
||||
end;
|
||||
case AnEvent.EventType of
|
||||
etEvent:
|
||||
begin
|
||||
JSonEvent.Add('eventName',AnEvent.EventName);
|
||||
if AnEvent.InstructionPointerRegValue<>0 then
|
||||
JSonEvent.Add('instrPointer', FormatAddress(AnEvent.InstructionPointerRegValue));
|
||||
end;
|
||||
etLog :
|
||||
begin
|
||||
JSonEvent.Add('message',AnEvent.Message);
|
||||
case AnEvent.LogLevel of
|
||||
dllDebug: JSonEvent.Add('logType','debug');
|
||||
dllError: JSonEvent.Add('logType','error');
|
||||
dllInfo: JSonEvent.Add('logType','info');
|
||||
end;
|
||||
end;
|
||||
etNotification:
|
||||
begin
|
||||
JSonEvent.Add('notificationType',FpDebugNotificationTypeNames[AnEvent.NotificationType]);
|
||||
JSonEvent.Add('message',AnEvent.Message);
|
||||
if AnEvent.EventName<>'' then
|
||||
JSonEvent.Add('command',AnEvent.EventName);
|
||||
end;
|
||||
end;
|
||||
result := JSonEvent.AsJSON;
|
||||
finally
|
||||
JSonEvent.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
finalization
|
||||
GJSonInOutputProcessor := nil;
|
||||
end.
|
||||
|
297
components/fpdebug/app/fpdserver/debugtcpserver.pas
Normal file
297
components/fpdebug/app/fpdserver/debugtcpserver.pas
Normal file
@ -0,0 +1,297 @@
|
||||
unit DebugTCPServer;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes,
|
||||
SysUtils,
|
||||
ssockets,
|
||||
BaseUnix,
|
||||
debugthread,
|
||||
sockets,
|
||||
syncobjs,
|
||||
FpDbgClasses,
|
||||
lazfglhash,
|
||||
lazCollections,
|
||||
fpjson,
|
||||
fgl,
|
||||
DebugInOutputProcessor,
|
||||
DebugThreadCommand;
|
||||
|
||||
type
|
||||
|
||||
{ TFpDebugTcpServer }
|
||||
|
||||
TFpDebugTcpConnectionThread = class;
|
||||
TThreadedQueueString = specialize TLazThreadedQueue<string>;
|
||||
TConnectionList = specialize TFPGObjectList<TFpDebugTcpConnectionThread>;
|
||||
|
||||
TFpDebugTcpServer = class(TThread)
|
||||
private
|
||||
FTCPConnection: TInetServer;
|
||||
FConnectionList: TConnectionList;
|
||||
FDebugThread: TFpDebugThread;
|
||||
procedure FTCPConnectionConnect(Sender: TObject; Data: TSocketStream);
|
||||
procedure FTCPConnectionAcceptError(Sender: TObject; ASocket: Longint; E: Exception; var ErrorAction: TAcceptErrorAction);
|
||||
procedure FTCPConnectionConnectQuery(Sender: TObject; ASocket: Longint; var Allow: Boolean);
|
||||
protected
|
||||
procedure Execute; override;
|
||||
public
|
||||
procedure StopListening;
|
||||
constructor create(ADebugThread: TFpDebugThread);
|
||||
procedure RemoveConnection(ADebugTcpConnectionThread: TFpDebugTcpConnectionThread);
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
{ TFpDebugTcpConnectionThread }
|
||||
|
||||
TFpDebugTcpConnectionThread = class(tthread, IFpDebugListener)
|
||||
private
|
||||
FData: TSocketStream;
|
||||
FDebugThread: TFpDebugThread;
|
||||
FResponseQueue: TThreadedQueueString;
|
||||
FDebugTcpServer: TFpDebugTcpServer;
|
||||
FConnectionIdentifier: integer;
|
||||
FInOutputProcessor: TCustomInOutputProcessor;
|
||||
protected
|
||||
procedure Execute; override;
|
||||
procedure SendCommand(ACommandStr: string);
|
||||
public
|
||||
procedure SendEvent(AnEvent: TFpDebugEvent);
|
||||
function GetOrigin: string;
|
||||
constructor create(ADebugThread: TFpDebugThread; ADebugTcpServer: TFpDebugTcpServer; Data: TSocketStream);
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TFpDebugTcpConnectionThread }
|
||||
|
||||
function STrToStr(AStr: string): string;
|
||||
var i : integer;
|
||||
begin
|
||||
result := '';
|
||||
for i := 1 to length(AStr) do
|
||||
if ord(AStr[i])<20 then
|
||||
result := result +'#'+inttostr(ord(AStr[i]))
|
||||
else
|
||||
result := result + Astr[i];
|
||||
end;
|
||||
|
||||
procedure TFpDebugTcpConnectionThread.Execute;
|
||||
|
||||
procedure WriteString(AStr: string);
|
||||
var
|
||||
i: integer;
|
||||
begin
|
||||
AStr := AStr + #10;
|
||||
i := FData.Write(AStr[1], length(AStr));
|
||||
|
||||
if i < 0 then
|
||||
begin
|
||||
if FData.LastError=32 then
|
||||
begin
|
||||
// Lost connection
|
||||
end
|
||||
else
|
||||
FDebugThread.SendNotification(FConnectionIdentifier, ntConnectionProblem, null, 'Error during write. Socket-error: %d', '', [FData.LastError]);
|
||||
Terminate;
|
||||
end
|
||||
else if i < length(AStr) then
|
||||
raise exception.create('Message has not been send to client entirely');
|
||||
end;
|
||||
|
||||
const
|
||||
InputBufferSize = 1024;
|
||||
var
|
||||
s: string;
|
||||
i: integer;
|
||||
InputBuffer: array[0..InputBufferSize-1] of char;
|
||||
InputStr: string;
|
||||
begin
|
||||
WriteString('Welcome to FPDebug-server.');
|
||||
if not Terminated then
|
||||
WriteString('Your connection-idenfifier is '+IntToStr(FConnectionIdentifier)+'.');
|
||||
if not Terminated then
|
||||
WriteString('Send "help<enter>" for more information.');
|
||||
while not terminated do
|
||||
begin
|
||||
i := FData.Read(InputBuffer[0], InputBufferSize);
|
||||
if i > 0 then
|
||||
begin
|
||||
setlength(s,i);
|
||||
move(InputBuffer[0],s[1],i);
|
||||
s := StringReplace(s,#13#10,#10,[rfReplaceAll]);
|
||||
InputStr:=InputStr+s;
|
||||
i := pos(#10, InputStr);
|
||||
while i > 0 do
|
||||
begin
|
||||
s := copy(InputStr, 1, i-1);
|
||||
delete(InputStr,1,i);
|
||||
SendCommand(S);
|
||||
i := pos(#10, InputStr);
|
||||
end;
|
||||
end
|
||||
else if i < 0 then
|
||||
begin
|
||||
if FData.LastError<>35 {EAGAIN} then
|
||||
begin
|
||||
writeln('Error during write. Socket-error: '+inttostr(FData.LastError));
|
||||
Terminate;
|
||||
end;
|
||||
end
|
||||
else if i = 0 then
|
||||
begin
|
||||
// Zero-count -> Connection closed
|
||||
Terminate;
|
||||
end;
|
||||
|
||||
if not terminated and (FResponseQueue.PopItem(s) = wrSignaled) then
|
||||
begin
|
||||
WriteString(s);
|
||||
end;
|
||||
end;
|
||||
FDebugTcpServer.RemoveConnection(self);
|
||||
end;
|
||||
|
||||
procedure TFpDebugTcpConnectionThread.SendCommand(ACommandStr: string);
|
||||
var
|
||||
ACommand: TFpDebugThreadCommand;
|
||||
begin
|
||||
ACommand := FInOutputProcessor.TextToCommand(ACommandStr);
|
||||
if assigned(ACommand) then
|
||||
FDebugThread.QueueCommand(ACommand);
|
||||
end;
|
||||
|
||||
procedure TFpDebugTcpConnectionThread.SendEvent(AnEvent: TFpDebugEvent);
|
||||
var
|
||||
s: string;
|
||||
begin
|
||||
s := FInOutputProcessor.EventToText(AnEvent);
|
||||
FResponseQueue.PushItem(s);
|
||||
end;
|
||||
|
||||
function TFpDebugTcpConnectionThread.GetOrigin: string;
|
||||
begin
|
||||
result := format('%d.%d.%d.%d:%d', [FData.RemoteAddress.sin_addr.s_bytes[1], FData.RemoteAddress.sin_addr.s_bytes[2],FData.RemoteAddress.sin_addr.s_bytes[3], FData.RemoteAddress.sin_addr.s_bytes[4], FData.RemoteAddress.sin_port])
|
||||
end;
|
||||
|
||||
constructor TFpDebugTcpConnectionThread.create(ADebugThread: TFpDebugThread;
|
||||
ADebugTcpServer: TFpDebugTcpServer; Data: TSocketStream);
|
||||
begin
|
||||
FData := data;
|
||||
|
||||
// Set non-blocking
|
||||
fpfcntl(FData.Handle,F_SETFL,O_NONBLOCK);
|
||||
|
||||
FDebugThread := ADebugThread;
|
||||
FDebugTcpServer := ADebugTcpServer;
|
||||
FResponseQueue:=TThreadedQueueString.create(100, INFINITE, 100);
|
||||
FConnectionIdentifier := FDebugThread.AddListener(self);
|
||||
FInOutputProcessor := TJSonInOutputProcessor.create(FConnectionIdentifier, @ADebugThread.SendLogMessage);
|
||||
inherited create(false);
|
||||
end;
|
||||
|
||||
destructor TFpDebugTcpConnectionThread.Destroy;
|
||||
begin
|
||||
FInOutputProcessor.Free;
|
||||
FDebugThread.RemoveListener(self);
|
||||
FResponseQueue.Free;
|
||||
FData.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
{ TFpDebugTcpServer }
|
||||
|
||||
procedure TFpDebugTcpServer.FTCPConnectionAcceptError(Sender: TObject;
|
||||
ASocket: Longint; E: Exception; var ErrorAction: TAcceptErrorAction);
|
||||
begin
|
||||
if E is ESocketError and (ESocketError(E).Code=seAcceptFailed) and (socketerror=53) {ECONNABORTED} then
|
||||
begin
|
||||
// The socket has stopped listening. The TCP-server is shutting down...
|
||||
ErrorAction:=aeaStop;
|
||||
end
|
||||
else
|
||||
writeln('ErrorAction a: '+e.ClassName + ' -- ',ErrorAction, '::',socketerror);
|
||||
end;
|
||||
|
||||
procedure TFpDebugTcpServer.FTCPConnectionConnectQuery(Sender: TObject;
|
||||
ASocket: Longint; var Allow: Boolean);
|
||||
begin
|
||||
Allow:=true;
|
||||
end;
|
||||
|
||||
procedure TFpDebugTcpServer.FTCPConnectionConnect(Sender: TObject; Data: TSocketStream);
|
||||
var
|
||||
AConnectionThread: TFpDebugTcpConnectionThread;
|
||||
begin
|
||||
AConnectionThread:=TFpDebugTcpConnectionThread.create(FDebugThread, Self, data);
|
||||
AConnectionThread.FreeOnTerminate:=true;
|
||||
FConnectionList.Add(AConnectionThread);
|
||||
end;
|
||||
|
||||
procedure TFpDebugTcpServer.Execute;
|
||||
var
|
||||
AConnection: TInetServer;
|
||||
begin
|
||||
try
|
||||
FTCPConnection := TInetServer.Create(9001);
|
||||
try
|
||||
FTCPConnection.OnConnect:=@FTCPConnectionConnect;
|
||||
FTCPConnection.OnConnectQuery:=@FTCPConnectionConnectQuery;
|
||||
FTCPConnection.OnAcceptError:=@FTCPConnectionAcceptError;
|
||||
FTCPConnection.Listen;
|
||||
FTCPConnection.StartAccepting;
|
||||
finally
|
||||
AConnection:=FTCPConnection;
|
||||
FTCPConnection := nil;
|
||||
AConnection.Free;
|
||||
end;
|
||||
Except
|
||||
on E: Exception do
|
||||
begin
|
||||
if (E is ESocketError) and (ESocketError(E).Code=seBindFailed) then
|
||||
FDebugThread.SendNotification(-1, ntConnectionProblem, null, 'Failed to start listening for incoming TCP-connections: %s', '', [e.Message])
|
||||
else
|
||||
WriteLn('Exception: '+e.Message);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TFpDebugTcpServer.StopListening;
|
||||
begin
|
||||
Terminate;
|
||||
if assigned(FTCPConnection) then
|
||||
FTCPConnection.StopAccepting(true);
|
||||
end;
|
||||
|
||||
constructor TFpDebugTcpServer.create(ADebugThread: TFpDebugThread);
|
||||
begin
|
||||
FDebugThread:=ADebugThread;
|
||||
FConnectionList:=TConnectionList.Create(false);
|
||||
inherited Create(false);
|
||||
end;
|
||||
|
||||
procedure TFpDebugTcpServer.RemoveConnection(ADebugTcpConnectionThread: TFpDebugTcpConnectionThread);
|
||||
begin
|
||||
FConnectionList.Remove(ADebugTcpConnectionThread);
|
||||
end;
|
||||
|
||||
destructor TFpDebugTcpServer.Destroy;
|
||||
var
|
||||
i: integer;
|
||||
begin
|
||||
for i := 0 to FConnectionList.Count-1 do
|
||||
FConnectionList[i].Terminate;
|
||||
for i := 0 to FConnectionList.Count-1 do
|
||||
FConnectionList[i].WaitFor;
|
||||
if FConnectionList.Count<>0 then
|
||||
raise exception.create('Not all connections are cleared.');
|
||||
FConnectionList.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
410
components/fpdebug/app/fpdserver/debugthread.pas
Normal file
410
components/fpdebug/app/fpdserver/debugthread.pas
Normal file
@ -0,0 +1,410 @@
|
||||
unit debugthread;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
{$INTERFACES CORBA}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes,
|
||||
SysUtils,
|
||||
FPDbgController,
|
||||
DbgIntfBaseTypes,
|
||||
DbgIntfDebuggerBase,
|
||||
lazfglhash,
|
||||
fpjson,
|
||||
FpDbgClasses;
|
||||
|
||||
type
|
||||
// The debug-thread sends three different kind of messages to it's listeners
|
||||
TFpDebugEventType = (
|
||||
etEvent, // Messages that are send by the debugger. (debuggee has been started, pauzed, stopped, etc.)
|
||||
etLog, // Log-messages send by the debugger. (Fpdebug also uses log-messages to inform users of some
|
||||
// events (the dllInfo-log messages)
|
||||
etNotification // Messages from the debug-thread itself. Including new or lost connections and commands that
|
||||
// are queued or executed.
|
||||
);
|
||||
|
||||
// The different kinds of etNotifications
|
||||
TFpDebugNotificationType = (
|
||||
ntNewConnection,
|
||||
ntLostConnection,
|
||||
ntInvalidCommand,
|
||||
ntConnectionProblem,
|
||||
ntReceivedCommand,
|
||||
ntExecutedCommand,
|
||||
ntFailedCommand
|
||||
);
|
||||
|
||||
// This record is used to pass debugging-events. Not every field is applicable for each type of event.
|
||||
TFpDebugEvent = record
|
||||
SendByConnectionIdentifier: integer;
|
||||
EventType: TFpDebugEventType;
|
||||
NotificationType: TFpDebugNotificationType;
|
||||
Message: string;
|
||||
EventName: string;
|
||||
LogLevel: TFPDLogLevel;
|
||||
InstructionPointerRegValue: TDBGPtr;
|
||||
AnUID: variant;
|
||||
BreakpointAddr: TDBGPtr;
|
||||
LocationRec: TDBGLocationRec;
|
||||
end;
|
||||
|
||||
// Each listener should implement this interface.
|
||||
IFpDebugListener = interface ['{2230763A-672E-4EC1-941D-6B8814D789C8}']
|
||||
// This procedure is called by the debugthread when there is a message for the listener.
|
||||
// Not that this procedure will be called from within the debug-thread, and should not take too much
|
||||
// resources, or ot will slow down the debugging.
|
||||
procedure SendEvent(AnEvent: TFpDebugEvent);
|
||||
// Gives more information about the origin of the listener.
|
||||
function GetOrigin: string;
|
||||
end;
|
||||
|
||||
TFpDebugThread = class;
|
||||
|
||||
{ TFpDebugThreadCommand }
|
||||
|
||||
// The base class for all commands that can be send to the debug-thread.
|
||||
|
||||
TFpDebugThreadCommand = class
|
||||
private
|
||||
FOnLog: TOnLog;
|
||||
protected
|
||||
FListenerIdentifier: integer;
|
||||
FUID: variant;
|
||||
procedure Log(const AString: string; const ALogLevel: TFPDLogLevel);
|
||||
public
|
||||
constructor Create(AListenerIdentifier: integer; AnUID: variant; AOnLog: TOnLog); virtual;
|
||||
// Descendents may override this procedure to add additionol information to the event that will
|
||||
// be send to all listeners when a command has been received succesfully
|
||||
procedure ComposeReceiveEvent(var AnEvent: TFpDebugEvent); virtual;
|
||||
// As above, for commands that has been executed successfully
|
||||
procedure ComposeSuccessEvent(var AnEvent: TFpDebugEvent); virtual;
|
||||
// As above, for commands that has failed to execute.
|
||||
procedure ComposeFailureEvent(var AnEvent: TFpDebugEvent); virtual;
|
||||
// Descendents have to override this function to implement the actual command. This function is called from within
|
||||
// the debug-controller's debug loop. (This means it is only executed when the debuggee is paused or stopped)
|
||||
// Should return tru on success, false on a failure. Set DoProcessLoop to true when the debuggee should continue,
|
||||
// make it false if the debuggee should stay in a paused state.
|
||||
function Execute(AController: TDbgController; out DoProcessLoop: boolean): boolean; virtual; abstract;
|
||||
// The name that is used to identify the command
|
||||
class function TextName: string; virtual; abstract;
|
||||
// The identifier of the Listener that has send this command
|
||||
property ListenerIdentifier: integer read FListenerIdentifier;
|
||||
end;
|
||||
TFpDebugThreadCommandClass = class of TFpDebugThreadCommand;
|
||||
|
||||
{ TFpDebugThread }
|
||||
|
||||
TFpDebugThread = class(TThread)
|
||||
private
|
||||
FCommandQueue: TThreadList;
|
||||
FController: TDbgController;
|
||||
FListenerList: TThreadList;
|
||||
protected
|
||||
// Handlers for the FController-events
|
||||
procedure FControllerHitBreakpointEvent(var continue: boolean; const Breakpoint: TDbgBreakpoint);
|
||||
procedure FControllerProcessExitEvent(ExitCode: DWord);
|
||||
procedure FControllerCreateProcessEvent(var continue: boolean);
|
||||
// Main debug thread-loop
|
||||
procedure Execute; override;
|
||||
// Send an event to all listeners
|
||||
procedure SendEvent(ADebugEvent: TFpDebugEvent);
|
||||
procedure ClearEvent(var AnEvent: TFpDebugEvent);
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
class function Instance: TFpDebugThread;
|
||||
// Sends a command to the command-queue, the command-queue takes ownership of the command.
|
||||
procedure QueueCommand(ACommand: TFpDebugThreadCommand);
|
||||
// Procedures to send notifications and log-messages to the listeners
|
||||
procedure SendNotification(AConnectionIdentifier: integer; ANotificationType: TFpDebugNotificationType; AnUID: variant; AMessage, ACommand: string);
|
||||
procedure SendNotification(AConnectionIdentifier: integer; ANotificationType: TFpDebugNotificationType; AnUID: variant; AMessage, ACommand: string; Arg: array of const); overload;
|
||||
procedure SendLogMessage(const AString: string; const ALogLevel: TFPDLogLevel);
|
||||
// Methods to add and remove listeners
|
||||
function AddListener(AFpDebugListener: IFpDebugListener): integer;
|
||||
procedure RemoveListener(AFpDebugListener: IFpDebugListener);
|
||||
end;
|
||||
|
||||
const
|
||||
FpEventTypeNames: array[TFpDebugEventType] of string = (
|
||||
'event',
|
||||
'log',
|
||||
'notification');
|
||||
FpDebugNotificationTypeNames: array[TFpDebugNotificationType] of string = (
|
||||
'NewConnection',
|
||||
'LostConnection',
|
||||
'InvalidCommand',
|
||||
'ConnectionProblem',
|
||||
'ReceivedCommand',
|
||||
'ExecutedCommand',
|
||||
'FailedCommand');
|
||||
|
||||
|
||||
implementation
|
||||
|
||||
var
|
||||
FFpDebugThread: TFpDebugThread;
|
||||
|
||||
{ TFpDebugThreadCommand }
|
||||
|
||||
procedure TFpDebugThreadCommand.Log(const AString: string; const ALogLevel: TFPDLogLevel);
|
||||
begin
|
||||
if assigned(FOnLog) then
|
||||
FOnLog(AString, ALogLevel);
|
||||
end;
|
||||
|
||||
constructor TFpDebugThreadCommand.Create(AListenerIdentifier: integer;
|
||||
AnUID: variant; AOnLog: TOnLog);
|
||||
begin
|
||||
FListenerIdentifier:=AListenerIdentifier;
|
||||
FUID:=AnUID;
|
||||
FOnLog:=AOnLog;
|
||||
end;
|
||||
|
||||
procedure TFpDebugThreadCommand.ComposeReceiveEvent(var AnEvent: TFpDebugEvent);
|
||||
begin
|
||||
AnEvent.EventType:=etNotification;
|
||||
AnEvent.NotificationType:=ntReceivedCommand;
|
||||
AnEvent.SendByConnectionIdentifier:=ListenerIdentifier;
|
||||
AnEvent.AnUID:=FUID;
|
||||
AnEvent.EventName:=TextName;
|
||||
AnEvent.Message:=Format('Received %s-command.',[TextName]);
|
||||
end;
|
||||
|
||||
procedure TFpDebugThreadCommand.ComposeSuccessEvent(var AnEvent: TFpDebugEvent);
|
||||
begin
|
||||
AnEvent.EventType:=etNotification;
|
||||
AnEvent.NotificationType:=ntExecutedCommand;
|
||||
AnEvent.SendByConnectionIdentifier:=ListenerIdentifier;
|
||||
AnEvent.AnUID:=FUID;
|
||||
AnEvent.EventName:=TextName;
|
||||
AnEvent.Message:=Format('%s-command executed succesfully.',[TextName]);
|
||||
end;
|
||||
|
||||
procedure TFpDebugThreadCommand.ComposeFailureEvent(var AnEvent: TFpDebugEvent);
|
||||
begin
|
||||
AnEvent.EventType:=etNotification;
|
||||
AnEvent.NotificationType:=ntFailedCommand;
|
||||
AnEvent.SendByConnectionIdentifier:=ListenerIdentifier;
|
||||
AnEvent.AnUID:=FUID;
|
||||
AnEvent.EventName:=TextName;
|
||||
AnEvent.Message:=Format('%s-command failed.',[TextName]);
|
||||
end;
|
||||
|
||||
{ TFpDebugThread }
|
||||
|
||||
procedure TFpDebugThread.SendLogMessage(const AString: string; const ALogLevel: TFPDLogLevel);
|
||||
var
|
||||
ADebugEvent: TFpDebugEvent;
|
||||
begin
|
||||
ClearEvent(ADebugEvent);
|
||||
ADebugEvent.EventType:=etLog;
|
||||
ADebugEvent.Message:=AString;
|
||||
ADebugEvent.LogLevel:=ALogLevel;
|
||||
|
||||
SendEvent(ADebugEvent);
|
||||
end;
|
||||
|
||||
procedure TFpDebugThread.ClearEvent(var AnEvent: TFpDebugEvent);
|
||||
begin
|
||||
AnEvent.AnUID:=null;
|
||||
AnEvent.SendByConnectionIdentifier:=-1;
|
||||
AnEvent.InstructionPointerRegValue:=0;
|
||||
AnEvent.BreakpointAddr:=0;
|
||||
AnEvent.LocationRec.Address:=0;
|
||||
end;
|
||||
|
||||
procedure TFpDebugThread.FControllerHitBreakpointEvent(var continue: boolean; const Breakpoint: TDbgBreakpoint);
|
||||
var
|
||||
ADebugEvent: TFpDebugEvent;
|
||||
begin
|
||||
ClearEvent(ADebugEvent);
|
||||
ADebugEvent.EventType:=etEvent;
|
||||
ADebugEvent.EventName:='BreakPoint';
|
||||
ADebugEvent.InstructionPointerRegValue:=FController.CurrentProcess.GetInstructionPointerRegisterValue;
|
||||
if assigned(Breakpoint) then
|
||||
ADebugEvent.BreakpointAddr:=Breakpoint.Location;
|
||||
|
||||
SendEvent(ADebugEvent);
|
||||
continue:=false;
|
||||
end;
|
||||
|
||||
procedure TFpDebugThread.FControllerProcessExitEvent(ExitCode: DWord);
|
||||
var
|
||||
ADebugEvent: TFpDebugEvent;
|
||||
begin
|
||||
ClearEvent(ADebugEvent);
|
||||
ADebugEvent.EventType:=etEvent;
|
||||
ADebugEvent.EventName:='ExitProcess';
|
||||
ADebugEvent.InstructionPointerRegValue:=FController.CurrentProcess.GetInstructionPointerRegisterValue;
|
||||
|
||||
SendEvent(ADebugEvent);
|
||||
end;
|
||||
|
||||
procedure TFpDebugThread.FControllerCreateProcessEvent(var continue: boolean);
|
||||
var
|
||||
ADebugEvent: TFpDebugEvent;
|
||||
begin
|
||||
ClearEvent(ADebugEvent);
|
||||
ADebugEvent.EventType:=etEvent;
|
||||
ADebugEvent.EventName:='CreateProcess';
|
||||
ADebugEvent.InstructionPointerRegValue:=FController.CurrentProcess.GetInstructionPointerRegisterValue;
|
||||
|
||||
SendEvent(ADebugEvent);
|
||||
continue:=false;
|
||||
end;
|
||||
|
||||
procedure TFpDebugThread.Execute;
|
||||
var
|
||||
AList: TList;
|
||||
ACommand: TFpDebugThreadCommand;
|
||||
ARunLoop: boolean;
|
||||
AnEvent: TFpDebugEvent;
|
||||
begin
|
||||
FController := TDbgController.Create;
|
||||
FController.OnCreateProcessEvent:=@FControllerCreateProcessEvent;
|
||||
FController.OnProcessExitEvent:=@FControllerProcessExitEvent;
|
||||
FController.OnHitBreakpointEvent:=@FControllerHitBreakpointEvent;
|
||||
FController.OnLog:=@SendLogMessage;
|
||||
|
||||
try
|
||||
repeat
|
||||
try
|
||||
ACommand:=nil;
|
||||
AList := FCommandQueue.LockList;
|
||||
try
|
||||
if AList.Count>0 then
|
||||
begin
|
||||
ACommand:=TFpDebugThreadCommand(AList.Items[0]);
|
||||
AList.Delete(0);
|
||||
end;
|
||||
finally
|
||||
FCommandQueue.UnlockList;
|
||||
end;
|
||||
|
||||
if assigned(ACommand) then
|
||||
begin
|
||||
try
|
||||
ClearEvent(AnEvent);
|
||||
ACommand.ComposeReceiveEvent(AnEvent);
|
||||
SendEvent(AnEvent);
|
||||
if ACommand.Execute(FController, ARunLoop) then
|
||||
begin
|
||||
ClearEvent(AnEvent);
|
||||
ACommand.ComposeSuccessEvent(AnEvent);
|
||||
SendEvent(AnEvent);
|
||||
end
|
||||
else
|
||||
begin
|
||||
ClearEvent(AnEvent);
|
||||
ACommand.ComposeFailureEvent(AnEvent);
|
||||
SendEvent(AnEvent);
|
||||
end;
|
||||
finally
|
||||
ACommand.Free;
|
||||
end;
|
||||
|
||||
while ARunLoop do
|
||||
begin
|
||||
FController.ProcessLoop;
|
||||
FController.SendEvents(ARunLoop);
|
||||
end;
|
||||
end;
|
||||
|
||||
sleep(100);
|
||||
except
|
||||
on E: Exception do
|
||||
writeln('Exception in debug-thread: '+e.Message); // just continue
|
||||
end;
|
||||
until terminated;
|
||||
|
||||
finally
|
||||
FController.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TFpDebugThread.SendEvent(ADebugEvent: TFpDebugEvent);
|
||||
var
|
||||
i: integer;
|
||||
AList: TList;
|
||||
begin
|
||||
AList:=FListenerList.LockList;
|
||||
try
|
||||
for i := 0 to AList.Count-1 do
|
||||
begin
|
||||
IFpDebugListener(AList[i]).SendEvent(ADebugEvent);
|
||||
end;
|
||||
finally
|
||||
FListenerList.UnlockList;
|
||||
end;
|
||||
end;
|
||||
|
||||
constructor TFpDebugThread.Create;
|
||||
begin
|
||||
inherited create(false);
|
||||
FCommandQueue := TThreadList.Create;
|
||||
FListenerList:=TThreadList.Create;
|
||||
end;
|
||||
|
||||
destructor TFpDebugThread.Destroy;
|
||||
begin
|
||||
FListenerList.Free;
|
||||
FCommandQueue.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
class function TFpDebugThread.Instance: TFpDebugThread;
|
||||
begin
|
||||
if not assigned(FFpDebugThread) then
|
||||
FFpDebugThread:=TFpDebugThread.Create;
|
||||
result := FFpDebugThread;
|
||||
end;
|
||||
|
||||
procedure TFpDebugThread.QueueCommand(ACommand: TFpDebugThreadCommand);
|
||||
begin
|
||||
FCommandQueue.Add(ACommand);
|
||||
end;
|
||||
|
||||
procedure TFpDebugThread.SendNotification(AConnectionIdentifier: integer; ANotificationType: TFpDebugNotificationType; AnUID: variant; AMessage, ACommand: string);
|
||||
var
|
||||
AnEvent: TFpDebugEvent;
|
||||
begin
|
||||
ClearEvent(AnEvent);
|
||||
AnEvent.SendByConnectionIdentifier:=AConnectionIdentifier;
|
||||
AnEvent.EventType:=etNotification;
|
||||
AnEvent.NotificationType:=ANotificationType;
|
||||
anEvent.EventName:=ACommand;
|
||||
AnEvent.Message:=AMessage;
|
||||
AnEvent.AnUID:=AnUID;
|
||||
SendEvent(AnEvent);
|
||||
end;
|
||||
|
||||
procedure TFpDebugThread.SendNotification(AConnectionIdentifier: integer; ANotificationType: TFpDebugNotificationType; AnUID: variant; AMessage, ACommand: string;
|
||||
Arg: array of const);
|
||||
begin
|
||||
SendNotification(AConnectionIdentifier, ANotificationType, AnUID, format(AMessage, Arg), ACommand);
|
||||
end;
|
||||
|
||||
var
|
||||
GIdentifierCount: integer = 0;
|
||||
|
||||
function TFpDebugThread.AddListener(AFpDebugListener: IFpDebugListener): integer;
|
||||
begin
|
||||
inc(GIdentifierCount);
|
||||
result := GIdentifierCount;
|
||||
SendNotification(result, ntNewConnection, null, 'New connection from %s', '',[AFpDebugListener.GetOrigin]);
|
||||
FListenerList.Add(AFpDebugListener);
|
||||
end;
|
||||
|
||||
procedure TFpDebugThread.RemoveListener(AFpDebugListener: IFpDebugListener);
|
||||
begin
|
||||
FListenerList.Remove(AFpDebugListener);
|
||||
end;
|
||||
|
||||
initialization
|
||||
FFpDebugThread := nil;
|
||||
finalization
|
||||
FFpDebugThread.Free;
|
||||
end.
|
||||
|
404
components/fpdebug/app/fpdserver/debugthreadcommand.pas
Normal file
404
components/fpdebug/app/fpdserver/debugthreadcommand.pas
Normal file
@ -0,0 +1,404 @@
|
||||
unit DebugThreadCommand;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes,
|
||||
contnrs,
|
||||
FPDbgController,
|
||||
FpDbgClasses,
|
||||
FpDbgUtil,
|
||||
FpDbgInfo,
|
||||
DbgIntfDebuggerBase,
|
||||
DbgIntfBaseTypes,
|
||||
strutils,
|
||||
debugthread,
|
||||
SysUtils;
|
||||
|
||||
type
|
||||
|
||||
{ TFpDebugThreadCommandList }
|
||||
|
||||
TFpDebugThreadCommandList = class(TFPList)
|
||||
public
|
||||
class function instance: TFpDebugThreadCommandList;
|
||||
function GetCommandByName(ATextName: string): TFpDebugThreadCommandClass;
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadSetFilenameCommand }
|
||||
|
||||
TFpDebugThreadSetFilenameCommand = class(TFpDebugThreadCommand)
|
||||
private
|
||||
FFileName: string;
|
||||
public
|
||||
function Execute(AController: TDbgController; out DoProcessLoop: boolean): boolean; override;
|
||||
class function TextName: string; override;
|
||||
published
|
||||
property Filename: string read FFileName write FFileName;
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadRunCommand }
|
||||
|
||||
TFpDebugThreadRunCommand = class(TFpDebugThreadCommand)
|
||||
public
|
||||
function Execute(AController: TDbgController; out DoProcessLoop: boolean): boolean; override;
|
||||
class function TextName: string; override;
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadContinueCommand }
|
||||
|
||||
TFpDebugThreadContinueCommand = class(TFpDebugThreadCommand)
|
||||
public
|
||||
function Execute(AController: TDbgController; out DoProcessLoop: boolean): boolean; override;
|
||||
class function TextName: string; override;
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadNextCommand }
|
||||
|
||||
TFpDebugThreadNextCommand = class(TFpDebugThreadCommand)
|
||||
public
|
||||
function Execute(AController: TDbgController; out DoProcessLoop: boolean): boolean; override;
|
||||
class function TextName: string; override;
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadStepCommand }
|
||||
|
||||
TFpDebugThreadStepCommand = class(TFpDebugThreadCommand)
|
||||
public
|
||||
function Execute(AController: TDbgController; out DoProcessLoop: boolean): boolean; override;
|
||||
class function TextName: string; override;
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadStepOutCommand }
|
||||
|
||||
TFpDebugThreadStepOutCommand = class(TFpDebugThreadCommand)
|
||||
public
|
||||
function Execute(AController: TDbgController; out DoProcessLoop: boolean): boolean; override;
|
||||
class function TextName: string; override;
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadStepIntoInstrCommand }
|
||||
|
||||
TFpDebugThreadStepIntoInstrCommand = class(TFpDebugThreadCommand)
|
||||
public
|
||||
function Execute(AController: TDbgController; out DoProcessLoop: boolean): boolean; override;
|
||||
class function TextName: string; override;
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadStepOverInstrCommand }
|
||||
|
||||
TFpDebugThreadStepOverInstrCommand = class(TFpDebugThreadCommand)
|
||||
public
|
||||
function Execute(AController: TDbgController; out DoProcessLoop: boolean): boolean; override;
|
||||
class function TextName: string; override;
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadStopCommand }
|
||||
|
||||
TFpDebugThreadStopCommand = class(TFpDebugThreadCommand)
|
||||
public
|
||||
function Execute(AController: TDbgController; out DoProcessLoop: boolean): boolean; override;
|
||||
class function TextName: string; override;
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadAddBreakpointCommand }
|
||||
|
||||
TFpDebugThreadAddBreakpointCommand = class(TFpDebugThreadCommand)
|
||||
private
|
||||
FFileName: string;
|
||||
FLine: integer;
|
||||
FBreakPoint: FpDbgClasses.TDbgBreakpoint;
|
||||
public
|
||||
procedure ComposeSuccessEvent(var AnEvent: TFpDebugEvent); override;
|
||||
function Execute(AController: TDbgController; out DoProcessLoop: boolean): boolean; override;
|
||||
class function TextName: string; override;
|
||||
published
|
||||
property Filename: string read FFileName write FFileName;
|
||||
property Line: integer read FLine write FLine;
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadGetLocationInfoCommand }
|
||||
|
||||
TFpDebugThreadGetLocationInfoCommand = class(TFpDebugThreadCommand)
|
||||
private
|
||||
FLocationRec: TDBGLocationRec;
|
||||
FAddressValue: TDBGPtr;
|
||||
function GetAddress: string;
|
||||
procedure SetAddress(AValue: string);
|
||||
protected
|
||||
procedure ComposeSuccessEvent(var AnEvent: TFpDebugEvent); override;
|
||||
public
|
||||
function Execute(AController: TDbgController; out DoProcessLoop: boolean): boolean; override;
|
||||
class function TextName: string; override;
|
||||
published
|
||||
property Address: string read GetAddress write SetAddress;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TFpDebugThreadCommandList }
|
||||
|
||||
var
|
||||
GFpDebugThreadCommandList: TFpDebugThreadCommandList = nil;
|
||||
|
||||
{ TFpDebugThreadStopCommand }
|
||||
|
||||
function TFpDebugThreadStopCommand.Execute(AController: TDbgController; out
|
||||
DoProcessLoop: boolean): boolean;
|
||||
begin
|
||||
AController.Stop;
|
||||
DoProcessLoop:=true;
|
||||
result := true;
|
||||
end;
|
||||
|
||||
class function TFpDebugThreadStopCommand.TextName: string;
|
||||
begin
|
||||
result := 'stop';
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadStepOutCommand }
|
||||
|
||||
function TFpDebugThreadStepOutCommand.Execute(AController: TDbgController; out
|
||||
DoProcessLoop: boolean): boolean;
|
||||
begin
|
||||
AController.StepOut;
|
||||
DoProcessLoop:=true;
|
||||
result := true;
|
||||
end;
|
||||
|
||||
class function TFpDebugThreadStepOutCommand.TextName: string;
|
||||
begin
|
||||
result := 'stepout';
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadStepOverInstrCommand }
|
||||
|
||||
function TFpDebugThreadStepOverInstrCommand.Execute(
|
||||
AController: TDbgController; out DoProcessLoop: boolean): boolean;
|
||||
begin
|
||||
AController.StepOverInstr;
|
||||
DoProcessLoop:=true;
|
||||
result := true;
|
||||
end;
|
||||
|
||||
class function TFpDebugThreadStepOverInstrCommand.TextName: string;
|
||||
begin
|
||||
result := 'stepoverinstr';
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadStepIntoInstrCommand }
|
||||
|
||||
function TFpDebugThreadStepIntoInstrCommand.Execute(
|
||||
AController: TDbgController; out DoProcessLoop: boolean): boolean;
|
||||
begin
|
||||
AController.StepIntoInstr;
|
||||
DoProcessLoop:=true;
|
||||
result := true;
|
||||
end;
|
||||
|
||||
class function TFpDebugThreadStepIntoInstrCommand.TextName: string;
|
||||
begin
|
||||
result := 'stepintoinstr';
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadStepCommand }
|
||||
|
||||
function TFpDebugThreadStepCommand.Execute(AController: TDbgController; out
|
||||
DoProcessLoop: boolean): boolean;
|
||||
begin
|
||||
AController.Step;
|
||||
DoProcessLoop:=true;
|
||||
result := true;
|
||||
end;
|
||||
|
||||
class function TFpDebugThreadStepCommand.TextName: string;
|
||||
begin
|
||||
result := 'step';
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadNextCommand }
|
||||
|
||||
function TFpDebugThreadNextCommand.Execute(AController: TDbgController; out
|
||||
DoProcessLoop: boolean): boolean;
|
||||
begin
|
||||
AController.Next;
|
||||
DoProcessLoop:=true;
|
||||
result := true;
|
||||
end;
|
||||
|
||||
class function TFpDebugThreadNextCommand.TextName: string;
|
||||
begin
|
||||
result := 'next';
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadGetLocationInfoCommand }
|
||||
|
||||
function TFpDebugThreadGetLocationInfoCommand.GetAddress: string;
|
||||
begin
|
||||
result := FormatAddress(FAddressValue);
|
||||
end;
|
||||
|
||||
procedure TFpDebugThreadGetLocationInfoCommand.SetAddress(AValue: string);
|
||||
begin
|
||||
FAddressValue := Hex2Dec(AValue);
|
||||
end;
|
||||
|
||||
procedure TFpDebugThreadGetLocationInfoCommand.ComposeSuccessEvent(var AnEvent: TFpDebugEvent);
|
||||
begin
|
||||
inherited ComposeSuccessEvent(AnEvent);
|
||||
AnEvent.LocationRec:=FLocationRec;
|
||||
end;
|
||||
|
||||
function TFpDebugThreadGetLocationInfoCommand.Execute(AController: TDbgController; out DoProcessLoop: boolean): boolean;
|
||||
var
|
||||
sym, symproc: TFpDbgSymbol;
|
||||
begin
|
||||
DoProcessLoop:=false;
|
||||
result := false;
|
||||
if Assigned(AController.CurrentProcess) then
|
||||
begin
|
||||
FLocationRec.FuncName:='';
|
||||
FLocationRec.SrcFile:='';
|
||||
FLocationRec.SrcFullName:='';
|
||||
FLocationRec.SrcLine:=0;
|
||||
|
||||
if FAddressValue=0 then
|
||||
FLocationRec.Address := AController.CurrentProcess.GetInstructionPointerRegisterValue
|
||||
else
|
||||
FLocationRec.Address := FAddressValue;
|
||||
|
||||
sym := AController.CurrentProcess.FindSymbol(FLocationRec.Address);
|
||||
if sym = nil then
|
||||
Exit;
|
||||
|
||||
FLocationRec.SrcFile := ExtractFileName(sym.FileName);
|
||||
FLocationRec.SrcLine := sym.Line;
|
||||
FLocationRec.SrcFullName := sym.FileName;
|
||||
|
||||
symproc := sym;
|
||||
while not (symproc.kind in [skProcedure, skFunction]) do
|
||||
symproc := symproc.Parent;
|
||||
|
||||
if assigned(symproc) then
|
||||
FLocationRec.FuncName:=symproc.Name;
|
||||
sym.free;
|
||||
result := true;
|
||||
end;
|
||||
end;
|
||||
|
||||
class function TFpDebugThreadGetLocationInfoCommand.TextName: string;
|
||||
begin
|
||||
result := 'getlocationinfo'
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadAddBreakpointCommand }
|
||||
|
||||
procedure TFpDebugThreadAddBreakpointCommand.ComposeSuccessEvent(var AnEvent: TFpDebugEvent);
|
||||
begin
|
||||
inherited ComposeSuccessEvent(AnEvent);
|
||||
AnEvent.BreakpointAddr:=FBreakPoint.Location;
|
||||
end;
|
||||
|
||||
function TFpDebugThreadAddBreakpointCommand.Execute(AController: TDbgController; out DoProcessLoop: boolean): boolean;
|
||||
begin
|
||||
result := false;
|
||||
DoProcessLoop:=false;
|
||||
if not assigned(AController.CurrentProcess) then
|
||||
begin
|
||||
log('Failed to add breakpoint: No process', dllInfo);
|
||||
exit;
|
||||
end;
|
||||
if (Filename<>'') and (line>-1) then
|
||||
begin
|
||||
FBreakPoint := AController.CurrentProcess.AddBreak(FileName, Line);
|
||||
result := assigned(FBreakPoint);
|
||||
end
|
||||
else
|
||||
log('Failed to add breakpoint: No filename and line-number given', dllInfo);
|
||||
end;
|
||||
|
||||
class function TFpDebugThreadAddBreakpointCommand.TextName: string;
|
||||
begin
|
||||
result := 'breakpoint';
|
||||
end;
|
||||
|
||||
class function TFpDebugThreadCommandList.instance: TFpDebugThreadCommandList;
|
||||
begin
|
||||
if not assigned(GFpDebugThreadCommandList) then
|
||||
GFpDebugThreadCommandList := TFpDebugThreadCommandList.Create;
|
||||
result := GFpDebugThreadCommandList;
|
||||
end;
|
||||
|
||||
function TFpDebugThreadCommandList.GetCommandByName(ATextName: string): TFpDebugThreadCommandClass;
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
result := nil;
|
||||
for i := 0 to count -1 do
|
||||
begin
|
||||
if TFpDebugThreadCommandClass(Items[i]).TextName=ATextName then
|
||||
result := TFpDebugThreadCommandClass(Items[i]);
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadContinueCommand }
|
||||
|
||||
function TFpDebugThreadContinueCommand.Execute(AController: TDbgController; out
|
||||
DoProcessLoop: boolean): boolean;
|
||||
begin
|
||||
DoProcessLoop:=true;
|
||||
result := true;
|
||||
end;
|
||||
|
||||
class function TFpDebugThreadContinueCommand.TextName: string;
|
||||
begin
|
||||
result := 'continue';
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadRunCommand }
|
||||
|
||||
function TFpDebugThreadRunCommand.Execute(AController: TDbgController; out
|
||||
DoProcessLoop: boolean): boolean;
|
||||
begin
|
||||
DoProcessLoop := AController.Run;
|
||||
result := DoProcessLoop;
|
||||
end;
|
||||
|
||||
class function TFpDebugThreadRunCommand.TextName: string;
|
||||
begin
|
||||
result := 'run';
|
||||
end;
|
||||
|
||||
{ TFpDebugThreadSetFilenameCommand }
|
||||
|
||||
function TFpDebugThreadSetFilenameCommand.Execute(AController: TDbgController;
|
||||
out DoProcessLoop: boolean): boolean;
|
||||
begin
|
||||
AController.ExecutableFilename:=FFileName;
|
||||
DoProcessLoop:=false;
|
||||
result:=true;
|
||||
end;
|
||||
|
||||
class function TFpDebugThreadSetFilenameCommand.TextName: string;
|
||||
begin
|
||||
result := 'filename'
|
||||
end;
|
||||
|
||||
initialization
|
||||
TFpDebugThreadCommandList.instance.Add(TFpDebugThreadSetFilenameCommand);
|
||||
TFpDebugThreadCommandList.instance.Add(TFpDebugThreadRunCommand);
|
||||
TFpDebugThreadCommandList.instance.Add(TFpDebugThreadContinueCommand);
|
||||
TFpDebugThreadCommandList.instance.Add(TFpDebugThreadStepOverInstrCommand);
|
||||
TFpDebugThreadCommandList.instance.Add(TFpDebugThreadStepIntoInstrCommand);
|
||||
TFpDebugThreadCommandList.instance.Add(TFpDebugThreadNextCommand);
|
||||
TFpDebugThreadCommandList.instance.Add(TFpDebugThreadStepCommand);
|
||||
TFpDebugThreadCommandList.instance.Add(TFpDebugThreadStepOutCommand);
|
||||
TFpDebugThreadCommandList.instance.Add(TFpDebugThreadStopCommand);
|
||||
TFpDebugThreadCommandList.instance.Add(TFpDebugThreadAddBreakpointCommand);
|
||||
TFpDebugThreadCommandList.instance.Add(TFpDebugThreadGetLocationInfoCommand);
|
||||
finalization
|
||||
GFpDebugThreadCommandList.Free;
|
||||
end.
|
||||
|
105
components/fpdebug/app/fpdserver/fpdserver.lpi
Normal file
105
components/fpdebug/app/fpdserver/fpdserver.lpi
Normal file
@ -0,0 +1,105 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CONFIG>
|
||||
<ProjectOptions>
|
||||
<Version Value="9"/>
|
||||
<General>
|
||||
<Flags>
|
||||
<MainUnitHasCreateFormStatements Value="False"/>
|
||||
</Flags>
|
||||
<SessionStorage Value="InProjectDir"/>
|
||||
<MainUnit Value="0"/>
|
||||
<Title Value="FPD Server"/>
|
||||
<UseAppBundle Value="False"/>
|
||||
<ResourceType Value="res"/>
|
||||
</General>
|
||||
<i18n>
|
||||
<EnableI18N LFM="False"/>
|
||||
</i18n>
|
||||
<VersionInfo>
|
||||
<StringTable ProductVersion=""/>
|
||||
</VersionInfo>
|
||||
<BuildModes Count="1">
|
||||
<Item1 Name="Default" Default="True"/>
|
||||
</BuildModes>
|
||||
<PublishOptions>
|
||||
<Version Value="2"/>
|
||||
</PublishOptions>
|
||||
<RunParams>
|
||||
<local>
|
||||
<FormatVersion Value="1"/>
|
||||
</local>
|
||||
</RunParams>
|
||||
<RequiredPackages Count="2">
|
||||
<Item1>
|
||||
<PackageName Value="LazUtils"/>
|
||||
</Item1>
|
||||
<Item2>
|
||||
<PackageName Value="fpdebug"/>
|
||||
</Item2>
|
||||
</RequiredPackages>
|
||||
<Units Count="5">
|
||||
<Unit0>
|
||||
<Filename Value="fpdserver.lpr"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
</Unit0>
|
||||
<Unit1>
|
||||
<Filename Value="debugthread.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="debugthread"/>
|
||||
</Unit1>
|
||||
<Unit2>
|
||||
<Filename Value="debugthreadcommand.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="DebugThreadCommand"/>
|
||||
</Unit2>
|
||||
<Unit3>
|
||||
<Filename Value="debugtcpserver.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="DebugTCPServer"/>
|
||||
</Unit3>
|
||||
<Unit4>
|
||||
<Filename Value="debuginoutputprocessor.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="DebugInOutputProcessor"/>
|
||||
</Unit4>
|
||||
</Units>
|
||||
</ProjectOptions>
|
||||
<CompilerOptions>
|
||||
<Version Value="11"/>
|
||||
<Target>
|
||||
<Filename Value="fpdserver"/>
|
||||
</Target>
|
||||
<SearchPaths>
|
||||
<IncludeFiles Value="$(ProjOutDir)"/>
|
||||
<UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
|
||||
</SearchPaths>
|
||||
<Linking>
|
||||
<Debugging>
|
||||
<UseHeaptrc Value="True"/>
|
||||
</Debugging>
|
||||
<Options>
|
||||
<PassLinkerOptions Value="True"/>
|
||||
<LinkerOptions Value="-sectcreate __TEXT __info_plist Info.plist"/>
|
||||
</Options>
|
||||
</Linking>
|
||||
<Other>
|
||||
<ExecuteAfter>
|
||||
<Command Value="codesign -s fpdebug fpdserver"/>
|
||||
<CompileReasons Run="False"/>
|
||||
</ExecuteAfter>
|
||||
</Other>
|
||||
</CompilerOptions>
|
||||
<Debugging>
|
||||
<Exceptions Count="3">
|
||||
<Item1>
|
||||
<Name Value="EAbort"/>
|
||||
</Item1>
|
||||
<Item2>
|
||||
<Name Value="ECodetoolError"/>
|
||||
</Item2>
|
||||
<Item3>
|
||||
<Name Value="EFOpenError"/>
|
||||
</Item3>
|
||||
</Exceptions>
|
||||
</Debugging>
|
||||
</CONFIG>
|
197
components/fpdebug/app/fpdserver/fpdserver.lpr
Normal file
197
components/fpdebug/app/fpdserver/fpdserver.lpr
Normal file
@ -0,0 +1,197 @@
|
||||
program fpdserver;
|
||||
|
||||
{ FPDebug server
|
||||
|
||||
Copyright (C) 2015 Joost van der Sluis joost@cnoc.nl
|
||||
|
||||
This source is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
details.
|
||||
|
||||
A copy of the GNU General Public License is available on the World Wide Web at
|
||||
<http://www.gnu.org/copyleft/gpl.html>. You can also obtain it by writing to the Free Software Foundation, Inc., 59
|
||||
Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
}
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
uses
|
||||
{$IFDEF UNIX}
|
||||
cthreads,
|
||||
{$ENDIF}
|
||||
Classes,
|
||||
SysUtils,
|
||||
CustApp,
|
||||
syncobjs,
|
||||
pipes,
|
||||
lazfglhash,
|
||||
debugthread,
|
||||
DebugThreadCommand,
|
||||
lazCollections,
|
||||
DebugInOutputProcessor,
|
||||
DebugTCPServer;
|
||||
|
||||
type
|
||||
|
||||
TFpDebugEventQueue = specialize TLazThreadedQueue<TFpDebugEvent>;
|
||||
|
||||
{ TFPDServerApplication }
|
||||
|
||||
TFPDServerApplication = class(TCustomApplication, IFpDebugListener)
|
||||
private
|
||||
FEventQueue: TFpDebugEventQueue;
|
||||
FInOutputProcessor: TCustomInOutputProcessor;
|
||||
FConnectionIdentifier: integer;
|
||||
protected
|
||||
procedure DoRun; override;
|
||||
public
|
||||
constructor Create(TheOwner: TComponent); override;
|
||||
destructor Destroy; override;
|
||||
procedure WriteHelp; virtual;
|
||||
// IFpDebugListener
|
||||
procedure SendEvent(AnEvent: TFpDebugEvent);
|
||||
function GetOrigin: string;
|
||||
end;
|
||||
|
||||
{ TFPDServerApplication }
|
||||
|
||||
procedure TFPDServerApplication.DoRun;
|
||||
var
|
||||
ErrorMsg: String;
|
||||
DebugThread: TFpDebugThread;
|
||||
DebugEvent: TFpDebugEvent;
|
||||
InputStream: TInputPipeStream;
|
||||
CommandStr: string;
|
||||
TCPServerThread: TFpDebugTcpServer;
|
||||
ACommand: TFpDebugThreadCommand;
|
||||
b: char;
|
||||
begin
|
||||
// quick check parameters
|
||||
ErrorMsg:=CheckOptions('hf', ['help']);
|
||||
if ErrorMsg<>'' then
|
||||
begin
|
||||
ShowException(Exception.Create(ErrorMsg));
|
||||
Terminate;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
// parse parameters
|
||||
if HasOption('h', 'help') then
|
||||
begin
|
||||
WriteHelp;
|
||||
Terminate;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
DebugThread := TFpDebugThread.Instance;
|
||||
TCPServerThread := TFpDebugTcpServer.Create(DebugThread);
|
||||
|
||||
if HasOption('f') then
|
||||
begin
|
||||
CommandStr := GetOptionValue('f');
|
||||
if CommandStr<>'' then
|
||||
begin
|
||||
ACommand := TFpDebugThreadSetFilenameCommand.create(-1, null, @DebugThread.SendLogMessage);
|
||||
TFpDebugThreadSetFilenameCommand(ACommand).Filename:=CommandStr;
|
||||
DebugThread.QueueCommand(ACommand);
|
||||
end
|
||||
else
|
||||
begin
|
||||
WriteHelp;
|
||||
Terminate;
|
||||
end;
|
||||
CommandStr:='';
|
||||
end;
|
||||
|
||||
InputStream:=TInputPipeStream.Create(StdInputHandle);
|
||||
|
||||
FConnectionIdentifier := DebugThread.AddListener(self);
|
||||
FInOutputProcessor := TJSonInOutputProcessor.create(FConnectionIdentifier, @DebugThread.SendLogMessage);
|
||||
try
|
||||
|
||||
while not terminated do
|
||||
begin
|
||||
if FEventQueue.PopItem(DebugEvent) = wrSignaled then
|
||||
begin
|
||||
writeln(FInOutputProcessor.EventToText(DebugEvent));
|
||||
end;
|
||||
while InputStream.NumBytesAvailable>0 do
|
||||
begin
|
||||
InputStream.Read(b,sizeof(b));
|
||||
if b <> #10 then
|
||||
CommandStr:=CommandStr+b
|
||||
else
|
||||
begin
|
||||
if CommandStr='q' then
|
||||
Terminate
|
||||
else
|
||||
begin
|
||||
ACommand := FInOutputProcessor.TextToCommand(CommandStr);
|
||||
if assigned(ACommand) then
|
||||
DebugThread.QueueCommand(ACommand);
|
||||
end;
|
||||
CommandStr:='';
|
||||
end;
|
||||
end;
|
||||
CheckSynchronize;
|
||||
end;
|
||||
|
||||
DebugThread.RemoveListener(self);
|
||||
|
||||
finally
|
||||
FInOutputProcessor.Free;
|
||||
end;
|
||||
|
||||
|
||||
TCPServerThread.StopListening;
|
||||
TCPServerThread.WaitFor;
|
||||
TCPServerThread.Free;
|
||||
DebugThread.Terminate;
|
||||
DebugThread.WaitFor;
|
||||
InputStream.Free;
|
||||
|
||||
terminate;
|
||||
end;
|
||||
|
||||
constructor TFPDServerApplication.Create(TheOwner: TComponent);
|
||||
begin
|
||||
inherited Create(TheOwner);
|
||||
FEventQueue:=TFpDebugEventQueue.Create(100, INFINITE, 100);
|
||||
StopOnException:=True;
|
||||
end;
|
||||
|
||||
destructor TFPDServerApplication.Destroy;
|
||||
begin
|
||||
FEventQueue.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
procedure TFPDServerApplication.SendEvent(AnEvent: TFpDebugEvent);
|
||||
begin
|
||||
FEventQueue.PushItem(AnEvent);
|
||||
end;
|
||||
|
||||
function TFPDServerApplication.GetOrigin: string;
|
||||
begin
|
||||
result := 'console';
|
||||
end;
|
||||
|
||||
procedure TFPDServerApplication.WriteHelp;
|
||||
begin
|
||||
{ add your help code here }
|
||||
writeln('Usage: ', ExeName, ' --help -h -f <executable name>');
|
||||
end;
|
||||
|
||||
var
|
||||
Application: TFPDServerApplication;
|
||||
begin
|
||||
Application:=TFPDServerApplication.Create(nil);
|
||||
Application.Title:='FPD Server';
|
||||
Application.Run;
|
||||
Application.Free;
|
||||
end.
|
||||
|
22
components/fpdebug/app/fpdserver/readme.txt
Normal file
22
components/fpdebug/app/fpdserver/readme.txt
Normal file
@ -0,0 +1,22 @@
|
||||
FPDebug-Server
|
||||
|
||||
The FPDebug-server contains a single fpdebug-thread to be able to debug one application at a time.
|
||||
|
||||
Multiple listeners can attach to this debug-thread so that they will receive messages from the fpdebug-thread about
|
||||
the application being debugged.
|
||||
The listeners can also send commands to the debug-thread, all listeners will receive information about all comands.
|
||||
|
||||
By default there are two listeners, one to communicate with the console. That way the debugger can be controlled from
|
||||
the console. The other listener is setting up a tcp-server listening on port 9001. It's possible to connect to this
|
||||
port using telnet to control the debugging.
|
||||
|
||||
In- and output are in a a json-format, but it is possible to register multiple formats.
|
||||
|
||||
The FPDebug-server could be used as a stand-alone debugger, controlled using the console. Or it could be used by other
|
||||
front-ends using the tcp-listener. Multiple connections can be used to monitor what is happening.
|
||||
|
||||
The goal is to add a Lazarus as a front end. It can be used for remote-debugging, and it can be used to avoid the
|
||||
necessity to sign the Lazarus-executable to be able to debug on OS/X. (Only the FPDebug-server has to be signed)
|
||||
|
||||
Joost van der Sluis, may 2015.
|
||||
|
Loading…
Reference in New Issue
Block a user