lazarus/components/fpdebug/app/fpdserver/debugthread.pas
joost 48e5ae3ac0 FpDebugServer:
- 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 -
2015-05-17 11:09:50 +00:00

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.