mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-03 06:03:41 +02:00
594 lines
17 KiB
ObjectPascal
594 lines
17 KiB
ObjectPascal
{
|
|
This unit contains the Commandline debugger class for external commandline
|
|
debuggers.
|
|
|
|
***************************************************************************
|
|
* *
|
|
* 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., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. *
|
|
* *
|
|
***************************************************************************
|
|
}
|
|
unit DebugProcess;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
{ $DEFINE DBG_VERBOSE}
|
|
{ $DEFINE DBG_VERBOSE_FULL_DATA}
|
|
|
|
{$IFDEF MSWindows} // optional gtk
|
|
{$DEFINE NATIVE_ASYNC_PROCESS}
|
|
{$ELSE}
|
|
{$UNDEF NATIVE_ASYNC_PROCESS}
|
|
{$ENDIF}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, sysutils, AsyncProcess, LCLIntf, InterfaceBase, process,
|
|
Pipes, LazLoggerBase, UTF8Process;
|
|
|
|
type
|
|
|
|
TDebugProcessNotification = procedure(Sender: TObject; ALine: String) of object;
|
|
|
|
{ TDebugProcessReadThread }
|
|
|
|
{$IFnDEF NATIVE_ASYNC_PROCESS}
|
|
TDebugProcessReadThread = class(TThread)
|
|
private
|
|
FAsyncLoopWaitEvent: PRTLEvent;
|
|
protected
|
|
FStream: TInputPipeStream;
|
|
FOnDataAvail: TThreadMethod;
|
|
FOnPipeError: TThreadMethod;
|
|
procedure Execute; override;
|
|
public
|
|
constructor Create(CreateSuspended: Boolean; const StackSize: SizeUInt =
|
|
DefaultStackSize);
|
|
destructor Destroy; override;
|
|
property AsyncLoopWaitEvent: PRTLEvent read FAsyncLoopWaitEvent;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
{ TDebugAsyncProcess }
|
|
|
|
TDebugAsyncProcess = class(TProcessUTF8) // TAsyncProcess
|
|
private
|
|
{$IFdef NATIVE_ASYNC_PROCESS}
|
|
FPipeHandler: PPipeEventHandler;
|
|
FProcessHandler: PProcessEventHandler;
|
|
{$ELSE}
|
|
FReadThread: TDebugProcessReadThread;
|
|
{$ENDIF}
|
|
FOnReadData: TNotifyEvent;
|
|
FOnTerminate: TNotifyEvent;
|
|
{$ifNdef NATIVE_ASYNC_PROCESS}
|
|
procedure ThreadDataAvail;
|
|
procedure ThreadPipeError;
|
|
procedure TerminataThread;
|
|
{$ENDIF}
|
|
procedure FinishedReadingOutput;
|
|
protected
|
|
procedure HandlePipeInput(AData: PtrInt; AReasons: TPipeReasons);
|
|
procedure HandleProcessTermination(AData: PtrInt; AReason: TChildExitReason; AInfo: dword);
|
|
procedure UnhookPipeHandle;
|
|
procedure UnhookProcessHandle;
|
|
public
|
|
procedure Execute; override;
|
|
destructor Destroy; override;
|
|
function Terminate(AExitCode: Integer): Boolean; override;
|
|
published
|
|
property OnReadData: TNotifyEvent read FOnReadData write FOnReadData;// You must read all the data in this event. Otherwise it is called again.
|
|
property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;
|
|
end;
|
|
|
|
{ TDebugProcess }
|
|
|
|
TDebugProcess = class
|
|
private const
|
|
DBG_STREAM_READ_LEN = 8192;
|
|
private
|
|
FExternalDebugger: String;
|
|
FDbgProcess: TDebugAsyncProcess;
|
|
FOnLineReceived: TDebugProcessNotification;
|
|
FOnBeginLinesReceived: TNotifyEvent;
|
|
FOnEndLinesReceived: TNotifyEvent;
|
|
FOnLineSent: TDebugProcessNotification;
|
|
FOnSendError: TDebugProcessNotification;
|
|
FOnTerminate: TNotifyEvent;
|
|
FTmpBuffer: String;
|
|
FOutputBuf: String;
|
|
FLockReadData: Boolean;
|
|
procedure DoReadData(Sender: TObject);
|
|
procedure DoTerminate(Sender: TObject);
|
|
function GetDbgProcess: TProcessUTF8;
|
|
function HandleHasData(const AHandle: Integer): Boolean;
|
|
protected
|
|
function GetDebugProcessRunning: Boolean;
|
|
public
|
|
constructor Create(const AExternalDebugger: String);
|
|
destructor Destroy; override;
|
|
function CreateDebugProcess(const AOptions: String; AnEnvironment: TStrings): Boolean;
|
|
procedure StopDebugProcess;
|
|
procedure SendCmdLn(const ACommand: String); overload;
|
|
procedure SendCmdLn(const ACommand: String; Values: array of const); overload;
|
|
public
|
|
property DebugProcess: TProcessUTF8 read GetDbgProcess;
|
|
property DebugProcessRunning: Boolean read GetDebugProcessRunning;
|
|
property OnLineReceived: TDebugProcessNotification read FOnLineReceived write FOnLineReceived;
|
|
property OnBeginLinesReceived: TNotifyEvent read FOnBeginLinesReceived write FOnBeginLinesReceived;
|
|
property OnEndLinesReceived: TNotifyEvent read FOnEndLinesReceived write FOnEndLinesReceived;
|
|
property OnLineSent: TDebugProcessNotification read FOnLineSent write FOnLineSent;
|
|
property OnSendError: TDebugProcessNotification read FOnSendError write FOnSendError;
|
|
// property OnTimeOut: TDebugProcessNotification read FOnTimeOut write FOnTimeOut;
|
|
property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
{$IFdef MSWindows} Windows {$ENDIF}
|
|
{$IFDEF UNIX} Unix, BaseUnix {$ENDIF}
|
|
;
|
|
|
|
var
|
|
DBG_CMD_ECHO, DBG_CMD_ECHO_FULL, DBG_VERBOSE, DBG_WARNINGS: PLazLoggerLogGroup;
|
|
|
|
{ TDebugProcessReadThread }
|
|
|
|
{$IFnDEF NATIVE_ASYNC_PROCESS}
|
|
procedure TDebugProcessReadThread.Execute;
|
|
var
|
|
R: Integer;
|
|
FDS: TFDSet;
|
|
begin
|
|
while (not Terminated) and (FStream.Handle >= 0) do begin
|
|
FpFD_ZERO(FDS);
|
|
FpFD_Set(FStream.Handle, FDS);
|
|
// R = -1 on error, 0 on timeout, >0 on success and is number of handles
|
|
// FDS is changed, and indicates what descriptors have changed
|
|
R := FpSelect(FStream.Handle + 1, @FDS, nil, nil, 50);
|
|
|
|
if Terminated then
|
|
break;
|
|
|
|
if r < 0 then begin
|
|
DebugLn('TTTTT pipe err');
|
|
Queue(FOnPipeError);
|
|
exit;
|
|
end;
|
|
|
|
if (R > 0) and (FpFD_ISSET(FStream.Handle,FDS)=1) then begin
|
|
DebugLn('TTTTT data avail');
|
|
Queue(FOnDataAvail);
|
|
RTLeventWaitFor(FAsyncLoopWaitEvent);
|
|
DebugLn('TTTTT data avail continue');
|
|
end;
|
|
|
|
end;
|
|
DebugLn(['TTTTT loop end ', Terminated]);
|
|
RemoveQueuedEvents(Self, FOnDataAvail);
|
|
RemoveQueuedEvents(Self, FOnPipeError);
|
|
end;
|
|
|
|
constructor TDebugProcessReadThread.Create(CreateSuspended: Boolean;
|
|
const StackSize: SizeUInt);
|
|
begin
|
|
FAsyncLoopWaitEvent := RTLEventCreate;
|
|
inherited;
|
|
end;
|
|
|
|
destructor TDebugProcessReadThread.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
RTLeventdestroy(FAsyncLoopWaitEvent);
|
|
end;
|
|
|
|
{$ENDIF}
|
|
|
|
{ TDebugAsyncProcess }
|
|
|
|
procedure TDebugAsyncProcess.FinishedReadingOutput;
|
|
begin
|
|
{$ifNdef NATIVE_ASYNC_PROCESS}
|
|
// Either finished reading, or TThread.Terminate was called
|
|
if FReadThread <> nil then
|
|
RTLeventSetEvent(FReadThread.AsyncLoopWaitEvent);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{$ifNdef NATIVE_ASYNC_PROCESS}
|
|
procedure TDebugAsyncProcess.ThreadDataAvail;
|
|
begin
|
|
if not Running then begin
|
|
//HandlePipeInput(0, [prBroken]);
|
|
HandleProcessTermination(0, cerExit, 0);
|
|
exit;
|
|
end;
|
|
|
|
HandlePipeInput(0, [prDataAvailable]);
|
|
// SELF may have been destroyed, during read or handle-termination
|
|
end;
|
|
|
|
procedure TDebugAsyncProcess.ThreadPipeError;
|
|
begin
|
|
DebugLn(['got pipe err / is running ', Running]);
|
|
Terminate(0);
|
|
HandleProcessTermination(0, cerExit, 0);
|
|
end;
|
|
|
|
procedure TDebugAsyncProcess.TerminataThread;
|
|
begin
|
|
FReadThread.Terminate;
|
|
FinishedReadingOutput;
|
|
TThread.RemoveQueuedEvents(FReadThread, @ThreadDataAvail);
|
|
TThread.RemoveQueuedEvents(FReadThread, @ThreadPipeError);
|
|
end;
|
|
|
|
{$ENDIF}
|
|
|
|
procedure TDebugAsyncProcess.HandlePipeInput(AData: PtrInt;
|
|
AReasons: TPipeReasons);
|
|
begin
|
|
if prBroken in AReasons then
|
|
UnhookPipeHandle;
|
|
if prDataAvailable in AReasons then
|
|
if FOnReadData <> nil then
|
|
FOnReadData(Self);
|
|
end;
|
|
|
|
procedure TDebugAsyncProcess.HandleProcessTermination(AData: PtrInt;
|
|
AReason: TChildExitReason; AInfo: dword);
|
|
begin
|
|
DebugLn('HandleProcessTermination');
|
|
UnhookProcessHandle;
|
|
UnhookPipeHandle;
|
|
if FOnTerminate <> nil then
|
|
FOnTerminate(Self);
|
|
end;
|
|
|
|
procedure TDebugAsyncProcess.UnhookPipeHandle;
|
|
begin
|
|
{$IFdef NATIVE_ASYNC_PROCESS}
|
|
if FPipeHandler <> nil then
|
|
RemovePipeEventHandler(FPipeHandler);
|
|
{$ELSE}
|
|
if FReadThread <> nil then begin
|
|
TerminataThread;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TDebugAsyncProcess.UnhookProcessHandle;
|
|
begin
|
|
{$IFdef NATIVE_ASYNC_PROCESS}
|
|
if FProcessHandler <> nil then
|
|
RemoveProcessEventHandler(FProcessHandler);
|
|
{$ELSE} // should be enough in UnhookPipeHandle;
|
|
if FReadThread <> nil then begin
|
|
TerminataThread;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TDebugAsyncProcess.Execute;
|
|
begin
|
|
inherited Execute;
|
|
|
|
{$IFdef NATIVE_ASYNC_PROCESS}
|
|
if poUsePipes in Options then
|
|
FPipeHandler := AddPipeEventHandler(Output.Handle, @HandlePipeInput, 0);
|
|
FProcessHandler := AddProcessEventHandler(ProcessHandle, @HandleProcessTermination, 0);
|
|
{$ELSE}
|
|
if FReadThread = nil then
|
|
FReadThread := TDebugProcessReadThread.Create(false);
|
|
FReadThread.FStream := Output;
|
|
FReadThread.FOnDataAvail := @ThreadDataAvail;
|
|
FReadThread.FOnPipeError := @ThreadPipeError;
|
|
FReadThread.Start;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
destructor TDebugAsyncProcess.Destroy;
|
|
begin
|
|
{$IFdef NATIVE_ASYNC_PROCESS}
|
|
UnhookProcessHandle;
|
|
UnhookPipeHandle;
|
|
{$ELSE}
|
|
if FReadThread <> nil then begin
|
|
TerminataThread;
|
|
FReadThread.WaitFor;
|
|
debugln(['DESTROY thread destroying']);
|
|
FreeAndNil(FReadThread);
|
|
debugln(['DESTROY thread destroyed']);
|
|
end;
|
|
{$ENDIF}
|
|
inherited;
|
|
end;
|
|
|
|
function TDebugAsyncProcess.Terminate(AExitCode: Integer): Boolean;
|
|
begin
|
|
{$ifdef NATIVE_ASYNC_PROCESS}
|
|
UnhookProcessHandle;
|
|
UnhookPipeHandle;
|
|
{$ELSE}
|
|
if FReadThread <> nil then begin
|
|
TerminataThread;
|
|
FReadThread.WaitFor;
|
|
debugln(['DESTROY thread destroying']);
|
|
FreeAndNil(FReadThread);
|
|
debugln(['DESTROY thread destroyed']);
|
|
end;
|
|
{$ENDIF}
|
|
Result := inherited Terminate(AExitCode);
|
|
end;
|
|
|
|
{ TDebugProcess }
|
|
|
|
procedure TDebugProcess.DoReadData(Sender: TObject);
|
|
function ReadData(const AStream: TInputPipeStream; var ABuffer: String): Integer;
|
|
var
|
|
c: LongInt;
|
|
begin
|
|
Result := 0;
|
|
c := AStream.Read(FTmpBuffer[1], DBG_STREAM_READ_LEN);
|
|
while c > 0 do begin
|
|
SetLength(ABuffer, Length(ABuffer)+c);
|
|
Move(FTmpBuffer[1], ABuffer[1 + Result], c);
|
|
Result := Result + c;
|
|
if (c = DBG_STREAM_READ_LEN) and HandleHasData(AStream.Handle) then begin
|
|
c := AStream.Read(FTmpBuffer[1], DBG_STREAM_READ_LEN);
|
|
end
|
|
else
|
|
c := 0;
|
|
end;
|
|
end;
|
|
function LineEndPos(const s: string; out LineEndLen: integer): integer;
|
|
var
|
|
n, idx: Integer;
|
|
begin
|
|
LineEndLen := 0;
|
|
Result := pos(#10, s);
|
|
n := pos(#13, s);
|
|
if (n > 0) and (n < Result) then
|
|
Result := n;
|
|
|
|
if Result = 0 then exit;
|
|
LineEndLen := 1;
|
|
if Result < Length(s) then begin
|
|
if (s[Result+1] in [#10,#13]) and (s[Result+1] <> s[Result]) then
|
|
LineEndLen := 2;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
LineEndIdx, LineEndLen: Integer;
|
|
Line: String;
|
|
begin
|
|
if not DebugProcessRunning then begin
|
|
StopDebugProcess;
|
|
exit;
|
|
end;
|
|
|
|
if (FDbgProcess.Output <> nil) then
|
|
ReadData(FDbgProcess.Output, Line);
|
|
FOutputBuf := FOutputBuf + Line;
|
|
|
|
FDbgProcess.FinishedReadingOutput; // Allow new reads, while we are processing
|
|
|
|
if FLockReadData or (FOutputBuf = '') then
|
|
exit;
|
|
|
|
try
|
|
FLockReadData := True;
|
|
if FOnBeginLinesReceived <> nil then // use to UnlockRelease
|
|
FOnBeginLinesReceived(Self);
|
|
|
|
LineEndIdx := LineEndPos(FOutputBuf, LineEndLen);
|
|
while (LineEndIdx > 0) do begin
|
|
Dec(LineEndIdx);
|
|
Line := Copy(FOutputBuf, 1, LineEndIdx);
|
|
Delete(FOutputBuf, 1, LineEndIdx + LineEndLen);
|
|
|
|
if ((DBG_CMD_ECHO_FULL <> nil) and (DBG_CMD_ECHO_FULL^. Enabled))
|
|
then debugln(DBG_CMD_ECHO_FULL, '<< << TCmdLineDebugger.ReadLn "',Line,'"')
|
|
else if (length(Line) < 300)
|
|
then debugln(DBG_CMD_ECHO, '<< << TCmdLineDebugger.ReadLn "',Line,'"')
|
|
else debugln(DBG_CMD_ECHO, ['<< << TCmdLineDebugger.ReadLn "',copy(Line, 1, 200), '" ..(',length(Line)-300,').. "',copy(Line, length(Line)-99, 100),'"']);
|
|
|
|
if FOnLineReceived <> nil then
|
|
FOnLineReceived(Self, Line);
|
|
|
|
LineEndIdx := LineEndPos(FOutputBuf, LineEndLen);
|
|
end;
|
|
|
|
finally
|
|
FLockReadData := False;
|
|
if FOnEndLinesReceived <> nil then // use to LockRelease
|
|
FOnEndLinesReceived(Self);
|
|
// Debugger and Self may get destroyed at this point
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugProcess.DoTerminate(Sender: TObject);
|
|
begin
|
|
if FOnTerminate <> nil then
|
|
FOnTerminate(Self);
|
|
end;
|
|
|
|
function TDebugProcess.GetDbgProcess: TProcessUTF8;
|
|
begin
|
|
Result := FDbgProcess;
|
|
end;
|
|
|
|
function TDebugProcess.HandleHasData(const AHandle: Integer): Boolean;
|
|
{$IFDEF UNIX}
|
|
var
|
|
R: Integer;
|
|
FDS: TFDSet;
|
|
begin
|
|
Result := False;
|
|
if AHandle < 0 then
|
|
exit;
|
|
|
|
FpFD_ZERO(FDS);
|
|
FpFD_Set(AHandle, FDS);
|
|
// R = -1 on error, 0 on timeout, >0 on success and is number of handles
|
|
// FDS is changed, and indicates what descriptors have changed
|
|
R := FpSelect(AHandle + 1, @FDS, nil, nil, 1);
|
|
|
|
Result := (R > 0) and (FpFD_ISSET(AHandle,FDS)=1);
|
|
end;
|
|
{$ELSE linux}
|
|
{$IFdef MSWindows}
|
|
var
|
|
TotalBytesAvailable: dword;
|
|
R: LongBool;
|
|
begin
|
|
R := Windows.PeekNamedPipe(AHandle, nil, 0, nil, @TotalBytesAvailable, nil);
|
|
if not R then begin
|
|
// PeekNamedPipe failed
|
|
DebugLn(DBG_WARNINGS, ['PeekNamedPipe failed, GetLastError is ', GetLastError]);
|
|
Exit;
|
|
end;
|
|
Result := TotalBytesAvailable > 0;
|
|
end;
|
|
{$ELSE win32}
|
|
begin
|
|
DebugLn('ToDo: implement WaitForHandles for this OS');
|
|
Result := 0;
|
|
end;
|
|
{$ENDIF win32}
|
|
{$ENDIF linux}
|
|
|
|
|
|
function TDebugProcess.GetDebugProcessRunning: Boolean;
|
|
begin
|
|
Result := (FDbgProcess <> nil) and FDbgProcess.Running;
|
|
end;
|
|
|
|
constructor TDebugProcess.Create(const AExternalDebugger: String);
|
|
begin
|
|
FDbgProcess := nil;
|
|
FExternalDebugger := AExternalDebugger;
|
|
SetLength(FTmpBuffer, DBG_STREAM_READ_LEN);
|
|
inherited Create;
|
|
end;
|
|
|
|
destructor TDebugProcess.Destroy;
|
|
begin
|
|
if DebugProcessRunning then
|
|
StopDebugProcess; // calls FDbgProcess.Release;
|
|
|
|
try
|
|
FDbgProcess.Destroy;
|
|
except
|
|
on E: Exception do DebugLn(DBG_WARNINGS, 'Exception while freeing debugger: ', E.Message);
|
|
end;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TDebugProcess.CreateDebugProcess(const AOptions: String;
|
|
AnEnvironment: TStrings): Boolean;
|
|
begin
|
|
Result := False;
|
|
|
|
if FDbgProcess = nil
|
|
then begin
|
|
FDbgProcess := TDebugAsyncProcess.Create(nil);
|
|
try
|
|
FDbgProcess.Options:= [poUsePipes, poNoConsole, poStdErrToOutPut, poNewProcessGroup];
|
|
{$if defined(windows) and not defined(wince)}
|
|
// under win9x and winMe should be created with console,
|
|
// otherwise no break can be send.
|
|
if Win32MajorVersion <= 4 then
|
|
FDbgProcess.Options:= [poUsePipes, poNewConsole, poStdErrToOutPut, poNewProcessGroup];
|
|
{$endif windows}
|
|
FDbgProcess.ShowWindow := swoNone;
|
|
except
|
|
FreeAndNil(FDbgProcess);
|
|
end;
|
|
end;
|
|
if FDbgProcess = nil then exit;
|
|
|
|
FDbgProcess.ParseCmdLine(FExternalDebugger + ' ' + AOptions);
|
|
FDbgProcess.Environment:= AnEnvironment;
|
|
FDbgProcess.OnReadData := @DoReadData;
|
|
FDbgProcess.OnTerminate := @DoTerminate;
|
|
|
|
if not FDbgProcess.Running
|
|
then begin
|
|
try
|
|
FDbgProcess.Execute;
|
|
DebugLn(DBG_VERBOSE, '[TDebugProcess] Debug PID: ', IntToStr(FDbgProcess.Handle));
|
|
Result := FDbgProcess.Running;
|
|
except
|
|
on E: Exception do begin
|
|
FOutputBuf := E.Message;
|
|
DebugLn(DBG_WARNINGS, 'Exception while executing debugger: ', FOutputBuf);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
|
|
procedure TDebugProcess.StopDebugProcess;
|
|
begin
|
|
debugln(['TDebugProcess.StopDebugProcess FDbgProcess = nil ',FDbgProcess = nil]);
|
|
if FDbgProcess = nil then exit;
|
|
|
|
FDbgProcess.Terminate(0);
|
|
end;
|
|
|
|
procedure TDebugProcess.SendCmdLn(const ACommand: String);
|
|
const
|
|
LE: string = LineEnding;
|
|
begin
|
|
if (DBG_CMD_ECHO_FULL <> nil) and (DBG_CMD_ECHO_FULL^.Enabled)
|
|
then debugln(DBG_CMD_ECHO_FULL, '>> >> TDebugProcess.SendCmdLn "',ACommand,'"')
|
|
else debugln(DBG_CMD_ECHO, '>> >> TDebugProcess.SendCmdLn "',ACommand,'"');
|
|
|
|
if DebugProcessRunning
|
|
then begin
|
|
if FOnLineSent <> nil then
|
|
FOnLineSent(Self, ACommand);
|
|
|
|
if ACommand <> ''
|
|
then FDbgProcess.Input.Write(ACommand[1], Length(ACommand));
|
|
FDbgProcess.Input.Write(LE[1], Length(LE));
|
|
end
|
|
else begin
|
|
DebugLn(DBG_WARNINGS, '[TDebugProcess.SendCmdLn] Unable to send <', ACommand, '>. No process running.');
|
|
if FOnSendError <> nil then
|
|
FOnSendError(Self, ACommand);
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugProcess.SendCmdLn(const ACommand: String;
|
|
Values: array of const);
|
|
begin
|
|
SendCmdLn(Format(ACommand, Values));
|
|
end;
|
|
|
|
initialization
|
|
DBG_CMD_ECHO := DebugLogger.FindOrRegisterLogGroup('DBG_CMD_ECHO' {$IF defined(DBG_VERBOSE) or defined(DBG_CMD_ECHO)} , True {$ENDIF} );
|
|
DBG_CMD_ECHO_FULL := DebugLogger.FindOrRegisterLogGroup('DBG_CMD_ECHO_FULL' {$IF defined(DBG_VERBOSE_FULL_DATA) or defined(DBG_CMD_ECHO_FULL)} , True {$ENDIF} );
|
|
DBG_VERBOSE := DebugLogger.FindOrRegisterLogGroup('DBG_VERBOSE' {$IFDEF DBG_VERBOSE} , True {$ENDIF} );
|
|
DBG_WARNINGS := DebugLogger.FindOrRegisterLogGroup('DBG_WARNINGS' {$IFDEF DBG_WARNINGS} , True {$ENDIF} );
|
|
|
|
end.
|
|
|