mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-02 02:23:44 +02:00
363 lines
9.6 KiB
ObjectPascal
363 lines
9.6 KiB
ObjectPascal
unit DebugTCPServer;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes,
|
|
SysUtils,
|
|
ssockets,
|
|
{$IFDEF UNIX}
|
|
BaseUnix,
|
|
{$ENDIF}
|
|
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
|
|
FPort: integer;
|
|
FSensePorts: integer;
|
|
FTCPConnection: TInetServer;
|
|
FConnectionList: TConnectionList;
|
|
FDebugThread: TFpDebugThread;
|
|
FInitializationFinished: PRTLEvent;
|
|
function CreateInetServer: TInetServer;
|
|
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 WaitForInitialization(out Port: integer);
|
|
procedure StopListening;
|
|
constructor create(ADebugThread: TFpDebugThread; APort, ASensePorts: integer);
|
|
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
|
|
{$IFDEF UNIX}
|
|
fpfcntl(FData.Handle,F_SETFL,O_NONBLOCK);
|
|
{$ENDIF}
|
|
|
|
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;
|
|
|
|
function TFpDebugTcpServer.CreateInetServer: TInetServer;
|
|
var
|
|
Conn: boolean;
|
|
InetServer: TInetServer;
|
|
FFirstError: string;
|
|
i: Integer;
|
|
begin
|
|
result := nil;
|
|
for i := 0 to FSensePorts-1 do
|
|
begin
|
|
conn := false;
|
|
InetServer := TInetServer.Create(FPort+i);
|
|
try
|
|
InetServer.Listen;
|
|
Conn:=true;
|
|
Break;
|
|
except
|
|
on E: Exception do
|
|
begin
|
|
InetServer.Free;
|
|
if (E is ESocketError) and (ESocketError(E).Code=seBindFailed) then
|
|
begin
|
|
// Ignore, try next port
|
|
if FFirstError='' then
|
|
FFirstError:=e.Message;
|
|
end
|
|
else
|
|
Raise;
|
|
end;
|
|
end;
|
|
end;
|
|
if conn then
|
|
begin
|
|
result := InetServer;
|
|
FPort:=result.Port;
|
|
FDebugThread.SendNotification(-1, ntListenerMessage, null, 'Listening for incoming TCP-connections on port %d', '', [FPort])
|
|
end
|
|
else
|
|
begin
|
|
FPort:=-1;
|
|
FDebugThread.SendNotification(-1, ntConnectionProblem, null, 'Failed to start listening for incoming TCP-connections: %s', '', [FFirstError])
|
|
end;
|
|
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 := CreateInetServer;
|
|
RTLeventSetEvent(FInitializationFinished);
|
|
if assigned(FTCPConnection) then
|
|
begin
|
|
try
|
|
FTCPConnection.OnConnect:=@FTCPConnectionConnect;
|
|
FTCPConnection.OnConnectQuery:=@FTCPConnectionConnectQuery;
|
|
FTCPConnection.OnAcceptError:=@FTCPConnectionAcceptError;
|
|
FTCPConnection.StartAccepting;
|
|
finally
|
|
AConnection:=FTCPConnection;
|
|
FTCPConnection := nil;
|
|
AConnection.Free;
|
|
end;
|
|
end
|
|
Except
|
|
on E: Exception do
|
|
begin
|
|
WriteLn('Exception: '+e.Message);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TFpDebugTcpServer.WaitForInitialization(out Port: integer);
|
|
begin
|
|
RTLeventWaitFor(FInitializationFinished);
|
|
Port := FPort;
|
|
end;
|
|
|
|
procedure TFpDebugTcpServer.StopListening;
|
|
begin
|
|
Terminate;
|
|
if assigned(FTCPConnection) then
|
|
FTCPConnection.StopAccepting(true);
|
|
end;
|
|
|
|
constructor TFpDebugTcpServer.create(ADebugThread: TFpDebugThread; APort, ASensePorts: integer);
|
|
begin
|
|
FPort:=APort;
|
|
if ASensePorts<1 then
|
|
ASensePorts:=1;
|
|
FSensePorts:=ASensePorts;
|
|
FDebugThread:=ADebugThread;
|
|
FConnectionList:=TConnectionList.Create(false);
|
|
FInitializationFinished:=RTLEventCreate;
|
|
inherited Create(false);
|
|
end;
|
|
|
|
procedure TFpDebugTcpServer.RemoveConnection(ADebugTcpConnectionThread: TFpDebugTcpConnectionThread);
|
|
begin
|
|
FConnectionList.Remove(ADebugTcpConnectionThread);
|
|
end;
|
|
|
|
destructor TFpDebugTcpServer.Destroy;
|
|
var
|
|
i: integer;
|
|
begin
|
|
RTLeventdestroy(FInitializationFinished);
|
|
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.
|
|
|