{ $Id$ } { ---------------------------------------- Debugger.pp - Debugger base classes ---------------------------------------- @created(Wed Feb 25st WET 2001) @lastmod($Date$) @author(Marc Weustink ) This unit contains the base class definitions of the debugger. These classes are only definitions. Implemented debuggers should be derived from these. /*************************************************************************** * * * This program 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. * * * ***************************************************************************/ } unit Debugger; {$mode objfpc}{$H+} interface uses Classes; type TDBGLocationRec = record Adress: Pointer; FuncName: String; SrcFile: String; SrcLine: Integer; end; TDBGCommand = (dcRun, dcPause, dcStop, dcStepOver, dcStepInto, dcRunTo, dcJumpto, dcBreak, dcWatch); TDBGCommands = set of TDBGCommand; TDBGState = (dsNone, dsIdle, dsStop, dsPause, dsRun, dsError); (* Debugger states -------------------------------------------------------------------------- dsNone: The debug object is created, but no instance of an external debugger exists. Initial state, leave with Init, enter with Done dsIdle: The external debugger is started, but no filename (or no other params requred to start) were given. dsStop: (Optional) The execution of the target is stopped The external debugger is loaded and ready to (re)start the execution of the target. Breakpoints, wathes etc can be defined dsPause: De debugger has paused the target. Targer variables canbe examined dsRun: The target is running. dsError: Something unforseen has happened. A shutdown of the debugger is in most cases needed. -------------------------------------------------------------------------- *) TDBGBreakPointAction = (bpaStop, bpaEnableGroup, bpaDisableGroup); TDBGBreakPointActions =set of TDBGBreakPointAction; TDebugger = class; TDBGBreakPointGroup = class; TDBGBreakPointClass = class of TDBGBreakPoint; TDBGBreakPoint = class(TCollectionItem) private FDebugger: TDebugger; // reference to our debugger FGroup: TDBGBreakPointGroup; FValid: Boolean; FEnabled: Boolean; FHitCount: Integer; FExpression: String; FSource: String; FLine: Integer; FFirstRun: Boolean; FActions: TDBGBreakPointActions; procedure SetActions(const AValue: TDBGBreakPointActions); procedure SetEnabled(const AValue: Boolean); procedure SetExpression(const AValue: String); procedure SetGroup(const AValue: TDBGBreakPointGroup); protected procedure AssignTo(Dest: TPersistent); override; procedure DoActionChange; virtual; procedure DoEnableChange; virtual; procedure DoExpressionChange; virtual; procedure DoStateChange; virtual; procedure SetHitCount(const AValue: Integer); procedure SetLocation(const ASource: String; const ALine: Integer); virtual; procedure SetValid(const AValue: Boolean); property Debugger: TDebugger read FDebugger; public procedure AddDisableGroup(const AGroup: TDBGBreakPointGroup); procedure AddEnableGroup(const AGroup: TDBGBreakPointGroup); constructor Create(ACollection: TCollection); override; procedure RemoveDisableGroup(const AGroup: TDBGBreakPointGroup); procedure RemoveEnableGroup(const AGroup: TDBGBreakPointGroup); property Actions: TDBGBreakPointActions read FActions write SetActions; property Enabled: Boolean read FEnabled write SetEnabled; property Group: TDBGBreakPointGroup read FGroup write SetGroup; property HitCount: Integer read FHitCount; property Expression: String read FExpression write SetExpression; property Source: String read FSource; property Line: Integer read FLine; property Valid: Boolean read FValid; end; TDBGBreakPoints = class(TCollection) private FDebugger: TDebugger; // reference to our debugger function GetItem(const AnIndex: Integer): TDBGBreakPoint; procedure SetItem(const AnIndex: Integer; const AValue: TDBGBreakPoint); protected procedure DoStateChange; public constructor Create(const ADebugger: TDebugger; const ABreakPointClass: TDBGBreakPointClass); function Add(const ASource: String; const ALine: Integer): TDBGBreakPoint; function Find(const ASource: String; const ALine: Integer): TDBGBreakPoint; property Items[const AnIndex: Integer]: TDBGBreakPoint read GetItem write SetItem; default; end; TDBGBreakPointGroup = class(TCollectionItem) private FEnabled: Boolean; FName: String; FBreakpoints: TList; function GetBreakpoint(const AIndex: Integer): TDBGBreakPoint; procedure SetEnabled(const AValue: Boolean); procedure SetName(const AValue: String); protected public function Add(const ABreakPoint: TDBGBreakPoint): Integer; function Count: Integer; constructor Create(ACollection: TCollection); override; procedure Delete(const AIndex: Integer); destructor Destroy; override; function Remove(const ABreakPoint: TDBGBreakPoint): Integer; property Breakpoints[const AIndex: Integer]: TDBGBreakPoint read GetBreakpoint; property Enabled: Boolean read FEnabled write SetEnabled; property Name: String read FName write SetName; end; TDBGBreakPointGroups = class(TCollection) private function GetItem(const AnIndex: Integer): TDBGBreakPointGroup; procedure SetItem(const AnIndex: Integer; const AValue: TDBGBreakPointGroup); protected public constructor Create; property Items[const AnIndex: Integer]: TDBGBreakPointGroup read GetItem write SetItem; default; end; TDBGWatchClass = class of TDBGWatch; TDBGWatch = class(TCollectionItem) private FDebugger: TDebugger; // reference to our debugger FEnabled: Boolean; FExpression: String; //FValue: String; FOnChange: TNotifyEvent; procedure SetEnabled(const AValue: Boolean); protected procedure DoEnableChange; virtual; procedure DoStateChange; virtual; function GetValue: String; virtual; function GetValid: Boolean; virtual; procedure SetExpression(const AValue: String); virtual; procedure SetValue(const AValue: String); virtual; property Debugger: TDebugger read FDebugger; public constructor Create(ACollection: TCollection); override; property Enabled: Boolean read FEnabled write SetEnabled; property Expression: String read FExpression write SetExpression; property Valid: Boolean read GetValid; property Value: String read GetValue write SetValue; property OnChange: TNotifyEvent read FOnChange write FOnChange; end; TDBGWatches = class(TCollection) private FDebugger: TDebugger; // reference to our debugger function GetItem(const AnIndex: Integer): TDBGWatch; procedure SetItem(const AnIndex: Integer; const AValue: TDBGWatch); protected procedure DoStateChange; public constructor Create(const ADebugger: TDebugger; const AWatchClass: TDBGWatchClass); property Items[const AnIndex: Integer]: TDBGWatch read GetItem write SetItem; default; end; TDBGOutputEvent = procedure(Sender: TObject; const AText: String) of object; TDBGCurrentLineEvent = procedure(Sender: TObject; const ALocation: TDBGLocationRec) of object; TDBGExceptionEvent = procedure(Sender: TObject; const AExceptionID: Integer; const AExceptionText: String) of object; TDebugger = class(TObject) private FArguments: String; FBreakPoints: TDBGBreakPoints; FBreakPointGroups: TDBGBreakPointGroups; FFileName: String; FState: TDBGState; FWatches: TDBGWatches; FOnCurrent: TDBGCurrentLineEvent; FOnException: TDBGExceptionEvent; FOnOutput: TDBGOutputEvent; FOnDbgOutput: TDBGOutputEvent; FOnState: TNotifyEvent; function GetState: TDBGState; function ReqCmd(const ACommand: TDBGCommand; const AParams: array of const): Boolean; protected function CreateBreakPoints: TDBGBreakPoints; virtual; function CreateWatches: TDBGWatches; virtual; procedure DoCurrent(const ALocation: TDBGLocationRec); procedure DoDbgOutput(const AText: String); procedure DoException(const AExceptionID: Integer; const AExceptionText: String); procedure DoOutput(const AText: String); procedure DoState; function GetCommands: TDBGCommands; function GetSupportedCommands: TDBGCommands; virtual; function RequestCommand(const ACommand: TDBGCommand; const AParams: array of const): Boolean; virtual; abstract; // True if succesful procedure SetFileName(const AValue: String); virtual; procedure SetState(const AValue: TDBGState); public constructor Create; {virtual; Virtual constructor makes no sense} //MWE: there will be a day that they do make sense :-) destructor Destroy; override; procedure Init; virtual; // Initializes the debugger procedure Done; virtual; // Kills the debugger procedure Run; // Starts / continues debugging procedure Pause; // Stops running procedure Stop; // quit debugging procedure StepOver; procedure StepInto; procedure RunTo(const ASource: String; const ALine: Integer); virtual; // Executes til a certain point procedure JumpTo(const ASource: String; const ALine: Integer); virtual; // No execute, only set exec point property SupportedCommands: TDBGCommands read GetSupportedCommands; // All available commands of the debugger property Arguments: String read FArguments write FArguments; // Arguments feed to the program property BreakPoints: TDBGBreakPoints read FBreakPoints; // list of all breakpoints property BreakPointGroups: TDBGBreakPointGroups read FBreakPointGroups; // list of all breakpointgroups property Commands: TDBGCommands read GetCommands; // All current available commands of the debugger property FileName: String read FFileName write SetFileName; // The name of the exe to be debugged property State: TDBGState read FState; // The current state of the debugger property Watches: TDBGWatches read FWatches; // list of all watches localvars etc property OnCurrent: TDBGCurrentLineEvent read FOnCurrent write FOnCurrent; // Passes info about the current line being debugged property OnException: TDBGExceptionEvent read FOnException write FOnException; // Fires when the debugger received an exeption property OnState: TNotifyEvent read FOnState write FOnState; // Fires when the current state of the debugger changes property OnOutput: TDBGOutputEvent read FOnOutput write FOnOutput; // Passes all output of the debugged target property OnDbgOutput: TDBGOutputEvent read FOnDbgOutput write FOnDbgOutput; // Passes all debuggeroutput end; implementation uses SysUtils; const COMMANDMAP: array[TDBGState] of TDBGCommands = ( {dsNone } [], {dsIdle } [], {dsStop } [dcRun, dcStepOver, dcStepInto, dcRunTo, dcJumpto, dcBreak, dcWatch], {dsPause} [dcRun, dcStop, dcStepOver, dcStepInto, dcRunTo, dcJumpto, dcBreak, dcWatch], {dsRun } [dcPause, dcStop, dcBreak, dcWatch], {dsError} [] ); { =========================================================================== } { TDebugger } { =========================================================================== } constructor TDebugger.Create; begin inherited Create; FOnState := nil; FOnCurrent := nil; FOnOutput := nil; FOnDbgOutput := nil; FState := dsNone; FArguments := ''; FFilename := ''; FBreakPoints := CreateBreakPoints; FWatches := CreateWatches; FBreakPointGroups := TDBGBreakPointGroups.Create; end; function TDebugger.CreateBreakPoints: TDBGBreakPoints; begin Result := TDBGBreakPoints.Create(Self, TDBGBreakPoint); end; function TDebugger.CreateWatches: TDBGWatches; begin Result := TDBGWatches.Create(Self, TDBGWatch); end; destructor TDebugger.Destroy; begin // don't call events FOnState := nil; FOnCurrent := nil; FOnOutput := nil; FOnDbgOutput := nil; if FState <> dsNone then Done; FBreakPointGroups.Free; FWatches.Free; inherited; end; procedure TDebugger.Done; begin SetState(dsNone); end; procedure TDebugger.DoCurrent(const ALocation: TDBGLocationRec); begin if Assigned(FOnCurrent) then FOnCurrent(Self, ALocation); end; procedure TDebugger.DoDbgOutput(const AText: String); begin if Assigned(FOnDbgOutput) then FOnDbgOutput(Self, AText); end; procedure TDebugger.DoException(const AExceptionID: Integer; const AExceptionText: String); begin if Assigned(FOnException) then FOnException(Self, AExceptionID, AExceptionText); end; procedure TDebugger.DoOutput(const AText: String); begin if Assigned(FOnOutput) then FOnOutput(Self, AText); end; procedure TDebugger.DoState; begin if Assigned(FOnState) then FOnState(Self); end; function TDebugger.GetCommands: TDBGCommands; begin Result := COMMANDMAP[State] * GetSupportedCommands; end; function TDebugger.GetState: TDBGState; begin Result := FState; end; function TDebugger.GetSupportedCommands: TDBGCommands; begin Result := []; end; procedure TDebugger.Init; begin SetState(dsIdle); end; procedure TDebugger.JumpTo(const ASource: String; const ALine: Integer); begin ReqCmd(dcJumpTo, [ASource, ALine]); end; procedure TDebugger.Pause; begin ReqCmd(dcPause, []); end; function TDebugger.ReqCmd(const ACommand: TDBGCommand; const AParams: array of const): Boolean; begin if FState = dsNone then Init; if ACommand in Commands then Result := RequestCommand(ACommand, AParams) else Result := False; end; procedure TDebugger.Run; begin ReqCmd(dcRun, []); end; procedure TDebugger.RunTo(const ASource: String; const ALine: Integer); begin ReqCmd(dcRunTo, [ASource, ALine]); end; procedure TDebugger.SetFileName(const AValue: String); begin if FFileName <> AValue then begin if FState in [dsRun, dsPause] then Stop; // Reset state FFileName := ''; SetState(dsIdle); FFileName := AValue; if FFilename <> '' then SetState(dsStop); end; end; procedure TDebugger.SetState(const AValue: TDBGState); begin if AValue <> FState then begin FState := AValue; FBreakpoints.DoStateChange; FWatches.DoStateChange; DoState; end; end; procedure TDebugger.StepInto; begin ReqCmd(dcStepInto, []); end; procedure TDebugger.StepOver; begin ReqCmd(dcStepOver, []); end; procedure TDebugger.Stop; begin ReqCmd(dcStop, []); end; { =========================================================================== } { TDBGBreakPoint } { =========================================================================== } procedure TDBGBreakPoint.AddDisableGroup(const AGroup: TDBGBreakPointGroup); begin end; procedure TDBGBreakPoint.AddEnableGroup(const AGroup: TDBGBreakPointGroup); begin end; procedure TDBGBreakPoint.AssignTo(Dest: TPersistent); begin if Dest is TDBGBreakPoint then begin TDBGBreakPoint(Dest).SetLocation(FSource, FLine); TDBGBreakPoint(Dest).SetExpression(FExpression); TDBGBreakPoint(Dest).SetActions(FActions); TDBGBreakPoint(Dest).SetEnabled(FEnabled); end else inherited; end; constructor TDBGBreakPoint.Create(ACollection: TCollection); begin inherited Create(ACollection); FSource := ''; FLine := -1; FValid := False; FEnabled := False; FHitCount := 0; FExpression := ''; FGroup := nil; FFirstRun := True; FActions := [bpaStop]; FDebugger := TDBGBreakPoints(ACollection).FDebugger; end; procedure TDBGBreakPoint.DoActionChange; begin end; procedure TDBGBreakPoint.DoEnableChange; begin end; procedure TDBGBreakPoint.DoExpressionChange; begin end; procedure TDBGBreakPoint.DoStateChange; begin case Debugger.State of dsStop, dsIdle: begin FFirstRun := True; end; dsRun: begin if FFirstRun then begin FHitCount := 0; FFirstRun := False; end; end; end; end; procedure TDBGBreakPoint.RemoveDisableGroup(const AGroup: TDBGBreakPointGroup); begin end; procedure TDBGBreakPoint.RemoveEnableGroup(const AGroup: TDBGBreakPointGroup); begin end; procedure TDBGBreakPoint.SetActions(const AValue: TDBGBreakPointActions); begin if FActions <> AValue then begin FActions := AValue; DoActionChange; end; end; procedure TDBGBreakPoint.SetEnabled(const AValue: Boolean); begin if FEnabled <> AValue then begin FEnabled := AValue; DoEnableChange; end; end; procedure TDBGBreakPoint.SetExpression(const AValue: String); begin if FExpression <> AValue then begin FExpression := AValue; DoExpressionChange; end; end; procedure TDBGBreakPoint.SetGroup(const AValue: TDBGBreakPointGroup); var Grp: TDBGBreakPointGroup; begin if FGroup <> AValue then begin if FGroup <> nil then begin Grp := FGroup; FGroup := nil; // avoid second entrance Grp.Remove(Self); end; FGroup := AValue; if FGroup <> nil then begin FGroup.Add(Self); end; end; end; procedure TDBGBreakPoint.SetHitCount(const AValue: Integer); begin FHitCount := AValue; end; procedure TDBGBreakPoint.SetLocation(const ASource: String; const ALine: Integer); begin FSource := ASource; FLine := ALine; end; procedure TDBGBreakPoint.SetValid(const AValue: Boolean); begin FValid := AValue; end; { =========================================================================== } { TDBGBreakPoints } { =========================================================================== } function TDBGBreakPoints.Add(const ASource: String; const ALine: Integer): TDBGBreakPoint; begin Result := TDBGBreakPoint(inherited Add); Result.SetLocation(ASource, ALine); end; constructor TDBGBreakPoints.Create(const ADebugger: TDebugger; const ABreakPointClass: TDBGBreakPointClass); begin inherited Create(ABreakPointClass); FDebugger := ADebugger; end; procedure TDBGBreakPoints.DoStateChange; var n: Integer; begin for n := 0 to Count - 1 do GetItem(n).DoStateChange; end; function TDBGBreakPoints.Find(const ASource: String; const ALine: Integer): TDBGBreakPoint; var n: Integer; begin for n := 0 to Count - 1 do begin Result := GetItem(n); if (Result.Line = ALine) and (Result.Source = ASource) then Exit; end; Result := nil; end; function TDBGBreakPoints.GetItem(const AnIndex: Integer): TDBGBreakPoint; begin Result := TDBGBreakPoint(inherited GetItem(AnIndex)); end; procedure TDBGBreakPoints.SetItem(const AnIndex: Integer; const AValue: TDBGBreakPoint); begin SetItem(AnIndex, AValue); end; { =========================================================================== } { TDBGBreakPointGroup } { =========================================================================== } function TDBGBreakPointGroup.Add(const ABreakPoint: TDBGBreakPoint): Integer; begin Result := FBreakpoints.IndexOf(ABreakPoint); //avoid dups if Result = -1 then begin Result := FBreakpoints.Add(ABreakPoint); ABreakpoint.Group := Self; end; end; function TDBGBreakPointGroup.Count: Integer; begin Result := FBreakpoints.Count; end; constructor TDBGBreakPointGroup.Create(ACollection: TCollection); begin inherited Create(ACollection); FBreakpoints := TList.Create; FEnabled := True; end; procedure TDBGBreakPointGroup.Delete(const AIndex: Integer); begin Remove(TDBGBreakPoint(FBreakPoints[AIndex])); end; destructor TDBGBreakPointGroup.Destroy; begin FBreakpoints.Free; inherited Destroy; end; function TDBGBreakPointGroup.GetBreakpoint(const AIndex: Integer): TDBGBreakPoint; begin Result := TDBGBreakPoint(FBreakPoints[AIndex]); end; function TDBGBreakPointGroup.Remove(const ABreakPoint: TDBGBreakPoint): Integer; begin Result := FBreakpoints.Remove(ABreakPoint); if ABreakpoint.Group = Self then ABreakpoint.Group := nil; end; procedure TDBGBreakPointGroup.SetEnabled(const AValue: Boolean); var n: Integer; begin if FEnabled <> AValue then begin FEnabled := AValue; for n := 0 to FBreakPoints.Count - 1 do TDBGBreakpoint(FBreakPoints[n]).Enabled := FEnabled; end; end; procedure TDBGBreakPointGroup.SetName(const AValue: String); begin FName := AValue; end; { =========================================================================== } { TDBGBreakPointGroups } { =========================================================================== } constructor TDBGBreakPointGroups.Create; begin inherited Create(TDBGBreakPointGroup); end; function TDBGBreakPointGroups.GetItem(const AnIndex: Integer): TDBGBreakPointGroup; begin Result := TDBGBreakPointGroup(inherited GetItem(AnIndex)); end; procedure TDBGBreakPointGroups.SetItem(const AnIndex: Integer; const AValue: TDBGBreakPointGroup); begin inherited SetItem(AnIndex, AValue); end; { =========================================================================== } { TDBGWatch } { =========================================================================== } constructor TDBGWatch.Create(ACollection: TCollection); begin inherited Create(ACollection); FEnabled := False; FDebugger := TDBGWatches(ACollection).FDebugger; end; procedure TDBGWatch.DoEnableChange; begin end; procedure TDBGWatch.DoStateChange; begin end; function TDBGWatch.GetValid: Boolean; begin Result := False; end; function TDBGWatch.GetValue: String; begin if Valid then Result := '' else Result := ''; end; procedure TDBGWatch.SetEnabled(const AValue: Boolean); begin if FEnabled <> AValue then begin FEnabled := AValue; DoEnableChange; end; end; procedure TDBGWatch.SetExpression(const AValue: String); begin end; procedure TDBGWatch.SetValue(const AValue: String); begin end; { =========================================================================== } { TDBGWatches } { =========================================================================== } constructor TDBGWatches.Create(const ADebugger: TDebugger; const AWatchClass: TDBGWatchClass); begin FDebugger := ADebugger; inherited Create(AWatchClass); end; procedure TDBGWatches.DoStateChange; var n: Integer; begin for n := 0 to Count - 1 do GetItem(n).DoStateChange; end; function TDBGWatches.GetItem(const AnIndex: Integer): TDBGWatch; begin Result := TDBGWatch(inherited GetItem(AnIndex)); end; procedure TDBGWatches.SetItem(const AnIndex: Integer; const AValue: TDBGWatch); begin inherited SetItem(AnIndex, AValue); end; end. { ============================================================================= $Log$ Revision 1.8 2002/02/20 23:33:24 lazarus MWE: + Published OnClick for TMenuItem + Published PopupMenu property for TEdit and TMemo (Doesn't work yet) * Fixed debugger running twice + Added Debugger output form * Enabled breakpoints Revision 1.7 2002/02/06 08:58:29 lazarus MG: fixed compiler warnings and asking to create non existing files Revision 1.6 2002/02/05 23:16:48 lazarus MWE: * Updated tebugger + Added debugger to IDE Revision 1.5 2001/11/12 19:28:23 lazarus MG: fixed create, virtual constructors makes no sense Revision 1.4 2001/11/06 23:59:13 lazarus MWE: + Initial breakpoint support + Added exeption handling on process.free Revision 1.3 2001/11/05 00:12:51 lazarus MWE: First steps of a debugger. Revision 1.2 2001/10/18 13:01:31 lazarus MG: fixed speedbuttons numglyphs>1 and started IDE debugging Revision 1.1 2001/02/28 22:09:15 lazarus MWE: * Renamed DBGDebugger to Debugger Revision 1.2 2001/02/25 16:44:57 lazarus MWE: + Added header and footer }