mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-03 21:23:52 +02:00

- Added port and autoport options to specify which (range of) tcp/ip-ports to bind to - Send a ListenerMessage with the actual port the tcp-ip connection is listening to - Added TFpDebugThreadCommand.PreExecute to execute command outside of the debug-loop - Changed default tcp/ip port to 9159 git-svn-id: trunk@49060 -
459 lines
14 KiB
ObjectPascal
459 lines
14 KiB
ObjectPascal
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,
|
|
ntListenerMessage,
|
|
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 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: TDbgController; 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: TDbgController; 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;
|
|
|
|
{ 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',
|
|
'ListenerMessage',
|
|
'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;
|
|
|
|
function TFpDebugThreadCommand.PreExecute(AController: TDbgController; 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.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);
|
|
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.Add(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.
|
|
|