mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-03 04:43:48 +02:00
680 lines
20 KiB
ObjectPascal
680 lines
20 KiB
ObjectPascal
unit debugthread;
|
|
|
|
{$mode objfpc}{$H+}
|
|
{$INTERFACES CORBA}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes,
|
|
SysUtils, fgl,
|
|
FPDbgController,
|
|
FpDbgDwarfDataClasses,
|
|
FpdMemoryTools,
|
|
DbgIntfBaseTypes,
|
|
DbgIntfDebuggerBase,
|
|
lazCollections,
|
|
syncobjs,
|
|
LazFglHash,
|
|
fpjson,
|
|
FpDbgClasses, LazDebuggerIntf;
|
|
|
|
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,
|
|
ntListenerMessage,
|
|
ntReceivedCommand,
|
|
ntExecutedCommand,
|
|
ntFailedCommand
|
|
);
|
|
|
|
TFpDebugEventCallStackEntry = record
|
|
AnAddress: TDBGPtr;
|
|
FrameAdress: TDBGPtr;
|
|
SourceFile: string;
|
|
FunctionName: string;
|
|
Line: integer;
|
|
end;
|
|
TFpDebugEventWatchEntry = record
|
|
TextValue: string;
|
|
Expression: string;
|
|
NumValue: TDBGPtr;
|
|
Size: byte;
|
|
end;
|
|
|
|
TFpDebugEventCallStackEntryArray = array of TFpDebugEventCallStackEntry;
|
|
TFpDebugEventDisassemblerEntryArray = array of TDisassemblerEntry;
|
|
TFpDebugEventWatchEntryArray = array of TFpDebugEventWatchEntry;
|
|
|
|
TFPDLogLevel = (dllDebug, dllInfo, dllError);
|
|
TOnLog = procedure(const AString: string; const ALogLevel: TFPDLogLevel) of object;
|
|
|
|
// 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;
|
|
BreakpointServerIdr: Integer;
|
|
LocationRec: TDBGLocationRec;
|
|
Validity: TDebuggerDataState;
|
|
Addr1: TDBGPtr;
|
|
Addr2: TDBGPtr;
|
|
Addr3: TDBGPtr;
|
|
StackEntryArray: TFpDebugEventCallStackEntryArray;
|
|
DisassemblerEntryArray: TFpDebugEventDisassemblerEntryArray;
|
|
WatchEntryArray: TFpDebugEventWatchEntryArray;
|
|
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;
|
|
|
|
{ TFpServerDbgController }
|
|
|
|
TFpServerDbgController = class(TDbgController)
|
|
private type
|
|
TBreakPointIdMap = specialize TFPGMap<Integer, TFpDbgBreakpoint>;
|
|
function DoBreakPointCompare(Key1, Key2: Pointer): Integer;
|
|
private
|
|
FBreakPointIdCnt: Integer;
|
|
FBreakPointIdMap: TBreakPointIdMap;
|
|
public
|
|
constructor Create(AMemManager: TFpDbgMemManager; AMemModel: TFpDbgMemModel); override;
|
|
destructor Destroy; override;
|
|
function AddInternalBreakPointToId(ABrkPoint: TFpDbgBreakpoint): Integer;
|
|
function GetInternalBreakPointFromId(AnId: Integer): TFpDbgBreakpoint;
|
|
function GetIdFromInternalBreakPoint(ABrkPoint: TFpDbgBreakpoint): Integer;
|
|
procedure RemoveInternalBreakPoint(AnId: Integer);
|
|
//procedure RemoveInternalBreakPoint(ABrkPoint: TFpDbgBreakpoint);
|
|
procedure ClearInternalBreakPoint;
|
|
end;
|
|
|
|
{ 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 controller's debug loop. (This means it is only executed when the debuggee is paused or stopped)
|
|
// Should return true 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: TFpServerDbgController; out DoProcessLoop: boolean): boolean; virtual; abstract;
|
|
// This method is called before the command is queued for execution in the controller's debug loop. This
|
|
// can happen in any thread. If DoQueueCommand is true, the result is ignored or else a success-event is
|
|
// send if the result is true, a failure if the result is false.
|
|
function PreExecute(AController: TFpServerDbgController; out DoQueueCommand: boolean): boolean; virtual;
|
|
// 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;
|
|
TFpDebugThreadCommandQueue = specialize TLazThreadedQueue<TFpDebugThreadCommand>;
|
|
|
|
{ TFpDebugThread }
|
|
|
|
TFpDebugThread = class(TThread)
|
|
private
|
|
FCommandQueue: TFpDebugThreadCommandQueue;
|
|
FController: TFpServerDbgController;
|
|
FListenerList: TThreadList;
|
|
FMemConverter: TFpDbgMemConvertorLittleEndian;
|
|
FMemReader: TDbgMemReader;
|
|
FMemModel: TFpDbgMemModel;
|
|
FMemManager: TFpDbgMemManager;
|
|
FConsoleOutputThread: TThread;
|
|
procedure FreeConsoleOutputThread;
|
|
protected
|
|
// Handlers for the FController-events
|
|
procedure FControllerHitBreakpointEvent(var continue: boolean; const Breakpoint: TFpDbgBreakpoint;
|
|
AnEventType: TFPDEvent; AMoreHitEventsPending: Boolean);
|
|
procedure FControllerProcessExitEvent(ExitCode: DWord);
|
|
procedure FControllerCreateProcessEvent(var continue: boolean);
|
|
procedure FControllerDebugInfoLoaded(Sender: TObject);
|
|
// 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',
|
|
'ListenerMessage',
|
|
'ReceivedCommand',
|
|
'ExecutedCommand',
|
|
'FailedCommand');
|
|
|
|
|
|
implementation
|
|
|
|
var
|
|
FFpDebugThread: TFpDebugThread;
|
|
|
|
type
|
|
|
|
{ TFpDbgMemReader }
|
|
|
|
TFpDbgMemReader = class(TDbgMemReader)
|
|
private
|
|
FDebugThread: TFpDebugThread;
|
|
protected
|
|
function GetDbgProcess: TDbgProcess; override;
|
|
public
|
|
constructor create(ADebugThread: TFpDebugThread);
|
|
end;
|
|
|
|
{ TFpWaitForConsoleOutputThread }
|
|
|
|
TFpWaitForConsoleOutputThread = class(TThread)
|
|
private
|
|
FDebugThread: TFpDebugThread;
|
|
public
|
|
constructor Create(ADebugThread: TFpDebugThread);
|
|
procedure Execute; override;
|
|
end;
|
|
|
|
{ TFpServerDbgController }
|
|
|
|
function TFpServerDbgController.DoBreakPointCompare(Key1, Key2: Pointer
|
|
): Integer;
|
|
begin
|
|
Result := PPointer(Key1)^ - PPointer(Key1)^;
|
|
end;
|
|
|
|
constructor TFpServerDbgController.Create(AMemManager: TFpDbgMemManager; AMemModel: TFpDbgMemModel);
|
|
begin
|
|
FBreakPointIdMap := TBreakPointIdMap.Create;
|
|
FBreakPointIdMap.OnDataPtrCompare := @DoBreakPointCompare;
|
|
inherited Create(AMemManager, MemModel);
|
|
end;
|
|
|
|
destructor TFpServerDbgController.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
FBreakPointIdMap.Free;
|
|
end;
|
|
|
|
function TFpServerDbgController.AddInternalBreakPointToId(
|
|
ABrkPoint: TFpDbgBreakpoint): Integer;
|
|
begin
|
|
inc(FBreakPointIdCnt);
|
|
Result := FBreakPointIdCnt;
|
|
FBreakPointIdMap.Add(Result, ABrkPoint);
|
|
end;
|
|
|
|
function TFpServerDbgController.GetInternalBreakPointFromId(AnId: Integer
|
|
): TFpDbgBreakpoint;
|
|
begin
|
|
if not FBreakPointIdMap.TryGetData(AnId, Result) then
|
|
Result := nil;
|
|
end;
|
|
|
|
function TFpServerDbgController.GetIdFromInternalBreakPoint(
|
|
ABrkPoint: TFpDbgBreakpoint): Integer;
|
|
begin
|
|
Result := FBreakPointIdMap.IndexOfData(ABrkPoint);
|
|
end;
|
|
|
|
procedure TFpServerDbgController.RemoveInternalBreakPoint(AnId: Integer);
|
|
begin
|
|
FBreakPointIdMap.Remove(AnId);
|
|
end;
|
|
|
|
procedure TFpServerDbgController.ClearInternalBreakPoint;
|
|
begin
|
|
FBreakPointIdMap.Clear;
|
|
end;
|
|
|
|
constructor TFpWaitForConsoleOutputThread.Create(ADebugThread: TFpDebugThread);
|
|
begin
|
|
Inherited create(false);
|
|
FDebugThread := ADebugThread;
|
|
end;
|
|
|
|
procedure TFpWaitForConsoleOutputThread.Execute;
|
|
var
|
|
res: integer;
|
|
AnEvent: TFpDebugEvent;
|
|
s: string;
|
|
begin
|
|
while not terminated do
|
|
begin
|
|
res := FDebugThread.FController.CurrentProcess.CheckForConsoleOutput(100);
|
|
if res<0 then
|
|
Terminate
|
|
else if res>0 then
|
|
begin
|
|
FDebugThread.ClearEvent(AnEvent);
|
|
AnEvent.EventType:=etEvent;
|
|
AnEvent.EventName:='ConsoleOutput';
|
|
s := FDebugThread.FController.CurrentProcess.GetConsoleOutput;
|
|
if s <> '' then
|
|
begin
|
|
AnEvent.Message:=s;
|
|
FDebugThread.SendEvent(AnEvent);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ TFpDbgMemReader }
|
|
|
|
function TFpDbgMemReader.GetDbgProcess: TDbgProcess;
|
|
begin
|
|
result := FDebugThread.FController.CurrentProcess;
|
|
end;
|
|
|
|
constructor TFpDbgMemReader.create(ADebugThread: TFpDebugThread);
|
|
begin
|
|
Inherited Create;
|
|
FDebugThread:=ADebugThread;
|
|
end;
|
|
|
|
{ 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;
|
|
|
|
function TFpDebugThreadCommand.PreExecute(AController: TFpServerDbgController; out DoQueueCommand: boolean): boolean;
|
|
begin
|
|
DoQueueCommand:=true;
|
|
result:=true;
|
|
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.BreakpointServerIdr:=0;
|
|
AnEvent.LocationRec.Address:=0;
|
|
AnEvent.Validity:=ddsUnknown;
|
|
SetLength(AnEvent.StackEntryArray,0);
|
|
SetLength(AnEvent.DisassemblerEntryArray,0);
|
|
SetLength(AnEvent.WatchEntryArray,0);
|
|
AnEvent.Addr1:=0;
|
|
AnEvent.Addr2:=0;
|
|
AnEvent.Addr3:=0;
|
|
end;
|
|
|
|
procedure TFpDebugThread.FControllerDebugInfoLoaded(Sender: TObject);
|
|
begin
|
|
//TFpDwarfInfo(FController.CurrentProcess.DbgInfo).MemManager := FMemManager;
|
|
end;
|
|
|
|
procedure TFpDebugThread.FreeConsoleOutputThread;
|
|
var
|
|
ADebugEvent: TFpDebugEvent;
|
|
AThread: TFpWaitForConsoleOutputThread;
|
|
begin
|
|
if assigned(FConsoleOutputThread) then
|
|
begin
|
|
AThread := TFpWaitForConsoleOutputThread(FConsoleOutputThread);
|
|
FConsoleOutputThread := nil;
|
|
AThread.Terminate;
|
|
AThread.WaitFor;
|
|
AThread.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TFpDebugThread.FControllerHitBreakpointEvent(var continue: boolean;
|
|
const Breakpoint: TFpDbgBreakpoint; AnEventType: TFPDEvent; AMoreHitEventsPending: Boolean);
|
|
var
|
|
ADebugEvent: TFpDebugEvent;
|
|
AnId: Integer;
|
|
begin
|
|
ClearEvent(ADebugEvent);
|
|
ADebugEvent.EventType:=etEvent;
|
|
ADebugEvent.EventName:='BreakPoint';
|
|
ADebugEvent.InstructionPointerRegValue:=FController.CurrentThread.GetInstructionPointerRegisterValue;
|
|
if assigned(Breakpoint) then begin
|
|
(* There may be several breakpoints at this address.
|
|
For now sending the IP address allows the IDE to find the same breakpoint(s) as the fpdserver app.
|
|
*)
|
|
AnId := FController.GetIdFromInternalBreakPoint(Breakpoint);
|
|
ADebugEvent.BreakpointServerIdr := AnId;
|
|
end;
|
|
|
|
|
|
SendEvent(ADebugEvent);
|
|
continue:=false;
|
|
end;
|
|
|
|
procedure TFpDebugThread.FControllerProcessExitEvent(ExitCode: DWord);
|
|
var
|
|
ADebugEvent: TFpDebugEvent;
|
|
begin
|
|
FreeConsoleOutputThread;
|
|
|
|
ClearEvent(ADebugEvent);
|
|
ADebugEvent.EventType:=etEvent;
|
|
ADebugEvent.EventName:='ExitProcess';
|
|
ADebugEvent.InstructionPointerRegValue:=FController.CurrentThread.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.CurrentThread.GetInstructionPointerRegisterValue;
|
|
|
|
SendEvent(ADebugEvent);
|
|
|
|
if FController.RedirectConsoleOutput then
|
|
FConsoleOutputThread := TFpWaitForConsoleOutputThread.Create(self);
|
|
continue:=false;
|
|
end;
|
|
|
|
procedure TFpDebugThread.Execute;
|
|
var
|
|
ACommand: TFpDebugThreadCommand;
|
|
ARunLoop: boolean;
|
|
AnEvent: TFpDebugEvent;
|
|
begin
|
|
FController := TFpServerDbgController.Create(FMemManager, FMemManager.MemModel);
|
|
FController.RedirectConsoleOutput:=true;
|
|
FController.OnCreateProcessEvent:=@FControllerCreateProcessEvent;
|
|
FController.OnProcessExitEvent:=@FControllerProcessExitEvent;
|
|
FController.OnHitBreakpointEvent:=@FControllerHitBreakpointEvent;
|
|
FController.OnDebugInfoLoaded:=@FControllerDebugInfoLoaded;
|
|
//TODO: DebugLogger.OnLog ....
|
|
//FController.OnLog:=@SendLogMessage;
|
|
|
|
try
|
|
repeat
|
|
try
|
|
if FCommandQueue.PopItem(ACommand)<>wrSignaled then
|
|
ACommand:=nil;
|
|
|
|
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;
|
|
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 := TFpDebugThreadCommandQueue.create(100, INFINITE, 100);
|
|
FListenerList:=TThreadList.Create;
|
|
FMemReader := TFpDbgMemReader.Create(self);
|
|
FMemModel := TFpDbgMemModel.Create;
|
|
FMemConverter := TFpDbgMemConvertorLittleEndian.Create;
|
|
FMemManager := TFpDbgMemManager.Create(FMemReader, FMemConverter, FMemModel);
|
|
end;
|
|
|
|
destructor TFpDebugThread.Destroy;
|
|
begin
|
|
FreeConsoleOutputThread;
|
|
FListenerList.Free;
|
|
FCommandQueue.Free;
|
|
inherited Destroy;
|
|
FMemManager.Free;
|
|
FMemConverter.Free;
|
|
FMemReader.Free;
|
|
FMemModel.Free;
|
|
end;
|
|
|
|
class function TFpDebugThread.Instance: TFpDebugThread;
|
|
begin
|
|
if not assigned(FFpDebugThread) then
|
|
FFpDebugThread:=TFpDebugThread.Create;
|
|
result := FFpDebugThread;
|
|
end;
|
|
|
|
procedure TFpDebugThread.QueueCommand(ACommand: TFpDebugThreadCommand);
|
|
var
|
|
DoQueueCommand: boolean;
|
|
Success: boolean;
|
|
AnEvent: TFpDebugEvent;
|
|
begin
|
|
try
|
|
Success := ACommand.PreExecute(FController, DoQueueCommand);
|
|
except
|
|
on E: Exception do
|
|
begin
|
|
SendLogMessage('Exception while executing command :'+e.Message, dllError);
|
|
DoQueueCommand:=false;
|
|
Success:=false;
|
|
end;
|
|
end;
|
|
if DoQueueCommand then
|
|
begin
|
|
FCommandQueue.PushItem(ACommand);
|
|
end
|
|
else
|
|
begin
|
|
try
|
|
if Success then
|
|
begin
|
|
ClearEvent(AnEvent);
|
|
ACommand.ComposeSuccessEvent(AnEvent);
|
|
SendEvent(AnEvent);
|
|
end
|
|
else
|
|
begin
|
|
ClearEvent(AnEvent);
|
|
ACommand.ComposeFailureEvent(AnEvent);
|
|
SendEvent(AnEvent);
|
|
end;
|
|
finally
|
|
ACommand.Free;
|
|
end;
|
|
end;
|
|
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.
|
|
|