mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-05 15:37:50 +02:00
3560 lines
116 KiB
ObjectPascal
3560 lines
116 KiB
ObjectPascal
{ $Id$ }
|
|
{
|
|
/***************************************************************************
|
|
debugmanager.pp
|
|
---------------
|
|
TDebugManager controls all debugging related stuff in the IDE.
|
|
|
|
|
|
***************************************************************************/
|
|
|
|
***************************************************************************
|
|
* *
|
|
* 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 DebugManager;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
{$I ide.inc}
|
|
{off $define VerboseDebugger}
|
|
|
|
uses
|
|
{$IFDEF IDE_MEM_CHECK}
|
|
MemCheck,
|
|
{$ENDIF}
|
|
Classes, SysUtils, math,
|
|
// LCL
|
|
LCLType, LCLIntf, Forms, Controls, Dialogs, ExtCtrls,
|
|
// LazUtils
|
|
LazFileUtils, LazFileCache, LazLoggerBase, Laz2_XMLCfg, LazUTF8, LazTracer,
|
|
LazMethodList,
|
|
// Codetools
|
|
CodeCache, CodeToolManager, PascalParserTool, CodeTree,
|
|
// BuildIntf
|
|
ProjectIntf, CompOptsIntf,
|
|
// IDEIntf
|
|
IDEWindowIntf, SrcEditorIntf, MenuIntf, IDECommands, LazIDEIntf,
|
|
IdeIntfStrConsts, IDEDialogs, ToolBarIntf, InputHistory,
|
|
// DebuggerIntf
|
|
DbgIntfBaseTypes, DbgIntfDebuggerBase, DbgIntfMiscClasses, DbgIntfPseudoTerminal,
|
|
// LazDebuggerIntf
|
|
LazDebuggerIntf, LazDebuggerIntfBaseTypes,
|
|
// IDEDebugger
|
|
IdeDebuggerStringConstants, DebuggerDlg, WatchesDlg, BreakPointsdlg, BreakPropertyDlg,
|
|
LocalsDlg, WatchPropertyDlg, CallStackDlg, EvaluateDlg, RegistersDlg, AssemblerDlg,
|
|
DebugOutputForm, ExceptionDlg, InspectDlg, PseudoTerminalDlg, FeedbackDlg, ThreadDlg,
|
|
HistoryDlg, ProcessDebugger, IdeDebuggerBase, IdeDebuggerOpts, EnvDebuggerOptions,
|
|
IdeDebuggerBackendValueConv, Debugger, BaseDebugManager,
|
|
// IdeConfig
|
|
LazConf,
|
|
// IDE
|
|
CompilerOptions, SourceEditor, ProjectDefs, Project,
|
|
LazarusIDEStrConsts, MainBar, MainIntf, MainBase, BaseBuildManager, SourceMarks,
|
|
DebugEventsForm, EnvGuiOptions;
|
|
|
|
type
|
|
|
|
{ TDebugEventLogManager }
|
|
|
|
TDebugEventLogManager = class(TObject, TDebuggerEventLogInterface)
|
|
private
|
|
FEventDialog: TDbgEventsForm;
|
|
FHiddenDebugEventsLog: TStringList;
|
|
FTargetWidth: Integer;
|
|
procedure SetEventDialog(AValue: TDbgEventsForm);
|
|
function FormatBreakPointAddress(const ABreakpoint: TDBGBreakPoint;
|
|
const ALocation: TDBGLocationRec): String;
|
|
protected
|
|
procedure DebuggerEvent(Sender: TObject; const ACategory: TDBGEventCategory; const AEventType: TDBGEventType; const AText: String);
|
|
public
|
|
procedure LogCustomEvent(const ACategory: TDBGEventCategory;
|
|
const AEventType: TDBGEventType; const AText: String);
|
|
procedure LogEventBreakPointHit(const ABreakpoint: TDBGBreakPoint; const ALocation: TDBGLocationRec);
|
|
procedure LogEventWatchPointTriggered(const ABreakpoint: TDBGBreakPoint;
|
|
const ALocation: TDBGLocationRec; const AOldWatchedVal, ANewWatchedVal: String);
|
|
procedure LogEventWatchPointScope(const ABreakpoint: TDBGBreakPoint;
|
|
const ALocation: TDBGLocationRec);
|
|
public
|
|
destructor Destroy; override;
|
|
procedure ClearDebugEventsLog;
|
|
property EventDialog: TDbgEventsForm read FEventDialog write SetEventDialog;
|
|
property TargetWidth: Integer read FTargetWidth write FTargetWidth;
|
|
end;
|
|
|
|
{ TDebugManager }
|
|
|
|
TDebugManager = class(TBaseDebugManager)
|
|
procedure DebuggerIdle(Sender: TObject);
|
|
function DoProjectClose(Sender: TObject; AProject: TLazProject): TModalResult;
|
|
procedure DoProjectModified(Sender: TObject);
|
|
private
|
|
FAsmWindowShouldAutoClose: Boolean;
|
|
procedure BreakAutoContinueTimer(Sender: TObject);
|
|
procedure OnRunTimer(Sender: TObject);
|
|
// Menu events
|
|
procedure mnuViewDebugDialogClick(Sender: TObject);
|
|
procedure mnuResetDebuggerClicked(Sender: TObject);
|
|
procedure mnuAddWatchClicked(Sender: TObject);
|
|
procedure mnuAddBpAddress(Sender: TObject);
|
|
procedure mnuAddBpSource(Sender: TObject);
|
|
procedure mnuAddBpData(Sender: TObject);
|
|
procedure mnuAddBpDataAtCursor(Sender: TObject);
|
|
|
|
// Debugger events
|
|
procedure DebuggerBreakPointHit({%H-}ADebugger: TDebuggerIntf; ABreakPoint: TBaseBreakPoint; var {%H-}ACanContinue: Boolean);
|
|
procedure DebuggerBeforeChangeState(ADebugger: TDebuggerIntf; AOldState: TDBGState);
|
|
procedure DebuggerChangeState(ADebugger: TDebuggerIntf; OldState: TDBGState);
|
|
procedure DebuggerCurrentLine(Sender: TObject; const ALocation: TDBGLocationRec);
|
|
procedure DoDebuggerCurrentLine(Sender: TObject);
|
|
procedure DebuggerOutput(Sender: TObject; const AText: String);
|
|
procedure DebuggerConsoleOutput(Sender: TObject; const AText: String);
|
|
function DebuggerFeedback(Sender: TObject; const AText, AInfo: String;
|
|
AType: TDBGFeedbackType; AButtons: TDBGFeedbackResults): TDBGFeedbackResult;
|
|
procedure DebuggerException(Sender: TObject;
|
|
const AExceptionType: TDBGExceptionType;
|
|
const AExceptionClass: String;
|
|
const AExceptionLocation: TDBGLocationRec;
|
|
const AExceptionText: String;
|
|
out AContinue: Boolean);
|
|
|
|
// Dialog events
|
|
procedure DebugDialogDestroy(Sender: TObject);
|
|
private
|
|
FDebugger: TDebuggerIntf;
|
|
FEventLogManager: TDebugEventLogManager;
|
|
FUnitInfoProvider: TDebuggerUnitInfoProvider;
|
|
FDialogs: array[TDebugDialogType] of TDebuggerDlg;
|
|
FInStateChange: Boolean;
|
|
FPrevShownWindow: HWND;
|
|
FStepping, FAsmStepping: Boolean;
|
|
// keep track of the last reported location
|
|
FCurrentLocation: TDBGLocationRec;
|
|
FCallStackNotification: TCallStackNotification;
|
|
// last hit breakpoint
|
|
FCurrentBreakpoint: TIDEBreakpoint;
|
|
FAutoContinueTimer: TTimer;
|
|
FIsInitializingDebugger: Boolean;
|
|
FStateNotificationList, FWatchesInvalidatedNotificationList: TMethodList;
|
|
|
|
// When a source file is not found, the user can choose one
|
|
// here are all choices stored
|
|
FUserSourceFiles: TStringList;
|
|
|
|
// when the debug output log is not open, store the debug log internally
|
|
FHiddenDebugOutputLog: TStringList;
|
|
|
|
FRunTimer: TTimer;
|
|
FAttachToID: String;
|
|
|
|
procedure SetDebugger(const ADebugger: TDebuggerIntf);
|
|
|
|
// Breakpoint routines
|
|
procedure CreateSourceMarkForBreakPoint(const ABreakpoint: TIDEBreakPoint;
|
|
ASrcEdit: TSourceEditor);
|
|
procedure GetSourceEditorForBreakPoint(const ABreakpoint: TIDEBreakPoint;
|
|
var ASrcEdit: TSourceEditor);
|
|
|
|
// Dialog routines
|
|
procedure DestroyDebugDialog(const ADialogType: TDebugDialogType);
|
|
procedure InitDebugOutputDlg;
|
|
procedure InitDebugEventsDlg;
|
|
procedure InitBreakPointDlg;
|
|
procedure InitWatchesDlg;
|
|
procedure InitThreadsDlg;
|
|
procedure InitPseudoTerminal;
|
|
procedure InitLocalsDlg;
|
|
procedure InitCallStackDlg;
|
|
procedure InitEvaluateDlg;
|
|
procedure InitRegistersDlg;
|
|
procedure InitAssemblerDlg;
|
|
procedure InitInspectDlg;
|
|
procedure InitHistoryDlg;
|
|
|
|
procedure FreeDebugger;
|
|
procedure ResetDebugger;
|
|
|
|
function GetLaunchPathAndExe(out LaunchingCmdLine, LaunchingApplication,
|
|
LaunchingParams: String; PromptOnError: Boolean = True): Boolean;
|
|
protected
|
|
function GetState: TDBGState; override;
|
|
function GetCommands: TDBGCommands; override;
|
|
function GetPseudoTerminal: TPseudoTerminal; override;
|
|
function GetDebuggerClass: TDebuggerClass;
|
|
{$IFDEF DBG_WITH_DEBUGGER_DEBUG}
|
|
function GetDebugger: TDebuggerIntf; override;
|
|
{$ENDIF}
|
|
function GetCurrentDebuggerClass: TDebuggerClass; override; (* TODO: workaround for http://bugs.freepascal.org/view.php?id=21834 *)
|
|
function AttachDebugger: TModalResult;
|
|
procedure CallWatchesInvalidatedHandlers(Sender: TObject);
|
|
function GetAvailableCommands: TDBGCommands;
|
|
function CanRunDebugger: Boolean;
|
|
public
|
|
constructor Create(TheOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure Reset; override;
|
|
|
|
procedure ConnectMainBarEvents; override;
|
|
procedure ConnectSourceNotebookEvents; override;
|
|
procedure SetupMainBarShortCuts; override;
|
|
procedure SetupSourceMenuShortCuts; override;
|
|
procedure UpdateButtonsAndMenuItems; override;
|
|
procedure UpdateToolStatus; override;
|
|
procedure EnvironmentOptsChanged; override;
|
|
|
|
procedure LoadProjectSpecificInfo(XMLConfig: TXMLConfig;
|
|
Merge: boolean);
|
|
procedure SaveProjectSpecificInfo(XMLConfig: TXMLConfig;
|
|
Flags: TProjectWriteFlags);
|
|
procedure DoRestoreDebuggerMarks(AnUnitInfo: TUnitInfo);
|
|
procedure ClearDebugOutputLog;
|
|
procedure ClearDebugEventsLog;
|
|
procedure DoBackendConverterChanged; override;
|
|
|
|
function RequiredCompilerOpts(ATargetCPU, ATargetOS: String
|
|
): TDebugCompilerRequirements; override;
|
|
function InitDebugger(AFlags: TDbgInitFlags = []): Boolean; override;
|
|
function DoSetBreakkPointWarnIfNoDebugger: boolean;
|
|
|
|
function DoPauseProject: TModalResult; override;
|
|
function DoShowExecutionPoint: TModalResult; override;
|
|
function DoStepIntoProject: TModalResult; override;
|
|
function DoStepOverProject: TModalResult; override;
|
|
function DoStepIntoInstrProject: TModalResult; override;
|
|
function DoStepOverInstrProject: TModalResult; override;
|
|
function DoStepOutProject: TModalResult; override;
|
|
function DoStepToCursor: TModalResult; override;
|
|
function DoRunToCursor: TModalResult; override;
|
|
function DoStopProject: TModalResult; override;
|
|
procedure DoToggleCallStack; override;
|
|
procedure DoSendConsoleInput(AText: String); override;
|
|
procedure ProcessCommand(Command: word; var Handled: boolean); override;
|
|
|
|
//Some debuugers may do things like ProcessMessages while processing commands
|
|
//and that can cause side-effects
|
|
//The debugger may run it's queue either during UnLockCommandProcessing or later
|
|
procedure LockCommandProcessing; override;
|
|
procedure UnLockCommandProcessing; override;
|
|
|
|
function StartDebugging: TModalResult; override; // returns immediately
|
|
function RunDebugger: TModalResult; override; // waits till program ends
|
|
procedure EndDebugging; override;
|
|
|
|
procedure Attach(AProcessID: String); override;
|
|
function FillProcessList(AList: TRunningProcessInfoList): boolean; override;
|
|
procedure Detach; override;
|
|
|
|
function Evaluate(const AExpression: String; ACallback: TDBGEvaluateResultCallback;
|
|
EvalFlags: TWatcheEvaluateFlags = []): Boolean; override;
|
|
function Modify(const AExpression, ANewValue: String): Boolean; override;
|
|
|
|
procedure EvaluateModify(const AExpression: String; AWatch: TWatch = nil); override;
|
|
procedure Inspect(const AExpression: String; AWatch: TWatch = nil); override;
|
|
|
|
function GetFullFilename(const AUnitinfo: TDebuggerUnitInfo; out Filename: string;
|
|
AskUserIfNotFound: Boolean): Boolean; override;
|
|
function GetFullFilename(var Filename: string; AskUserIfNotFound: Boolean): Boolean; override;
|
|
procedure JumpToUnitSource(AFileName: String; ALine: Integer; AMapLineFromDebug: Boolean = True); override;
|
|
procedure JumpToUnitSource(AnUnitInfo: TDebuggerUnitInfo; ALine: Integer; AMapLineFromDebug: Boolean = True); override;
|
|
|
|
function DoCreateBreakPoint(const AFilename: string; ALine: integer;
|
|
WarnIfNoDebugger: boolean): TModalResult; override;
|
|
function DoCreateBreakPoint(const AFilename: string; ALine: integer;
|
|
WarnIfNoDebugger: boolean;
|
|
out ABrkPoint: TIDEBreakPoint;
|
|
AnUpdating: Boolean = False): TModalResult; override;
|
|
function DoCreateBreakPoint(const AnAddr: TDBGPtr;
|
|
WarnIfNoDebugger: boolean;
|
|
out ABrkPoint: TIDEBreakPoint;
|
|
AnUpdating: Boolean = False): TModalResult; override;
|
|
|
|
function DoDeleteBreakPoint(const AFilename: string;
|
|
ALine: integer): TModalResult; override;
|
|
function DoDeleteBreakPointAtMark(const ASourceMarkObj: TObject): TModalResult; override;
|
|
|
|
function ShowBreakPointProperties(const ABreakpoint: TIDEBreakPoint): TModalresult; override;
|
|
function ShowWatchProperties(const AWatch: TCurrentWatch; AWatchExpression: String = ''): TModalresult; override;
|
|
|
|
// Dialog routines
|
|
procedure CreateDebugDialog(Sender: TObject; aFormName: string;
|
|
var AForm: TCustomForm; DoDisableAutoSizing: boolean); override;
|
|
procedure ViewDebugDialog(const ADialogType: TDebugDialogType;
|
|
BringToFront: Boolean = true;
|
|
Show: Boolean = true;
|
|
DoDisableAutoSizing: boolean = false;
|
|
InitFromSourceEdit: boolean = True
|
|
); override;
|
|
procedure ViewDisassembler(AnAddr: TDBGPtr;
|
|
BringToFront: Boolean = True; Show: Boolean = true;
|
|
DoDisableAutoSizing: boolean = false); override;
|
|
|
|
procedure RegisterStateChangeHandler(AHandler: TDebuggerStateChangeNotification); override;
|
|
procedure UnregisterStateChangeHandler(AHandler: TDebuggerStateChangeNotification); override;
|
|
procedure RegisterWatchesInvalidatedHandler(AHandler: TNotifyEvent); override;
|
|
procedure UnregisterWatchesInvalidatedHandler(AHandler: TNotifyEvent); override;
|
|
end;
|
|
|
|
function GetDebugManager: TDebugManager;
|
|
|
|
property DebugBossMgr: TDebugManager read GetDebugManager;
|
|
|
|
function DBGDateTimeFormatter(const aValue: string): string;
|
|
function ResolveLocationForLaunchApplication(ALaunchApp: String): String;
|
|
|
|
implementation
|
|
|
|
var
|
|
DBG_LOCATION_INFO: PLazLoggerLogGroup;
|
|
|
|
function GetDebugManager: TDebugManager;
|
|
begin
|
|
Result := TDebugManager(DebugBoss);
|
|
end;
|
|
|
|
function DBGDateTimeFormatter(const aValue: string): string;
|
|
var
|
|
FS: TFormatSettings;
|
|
MyDate: Extended;
|
|
begin
|
|
FillChar(FS{%H-}, SizeOf(TFormatSettings), 0);
|
|
FS.DecimalSeparator := '.';
|
|
if TryStrToFloat(aValue, MyDate, FS) then
|
|
begin
|
|
// it is important to know datetime for all TDate/TTime/TDateTime
|
|
if SameValue(Frac(MyDate), 0) then
|
|
Result := DateToStr(MyDate)
|
|
else
|
|
if SameValue(Int(MyDate), 0) then
|
|
Result := TimeToStr(MyDate)
|
|
else
|
|
Result := DateTimeToStr(MyDate);
|
|
end else
|
|
Result := aValue;
|
|
end;
|
|
|
|
function ResolveLocationForLaunchApplication(ALaunchApp: String): String;
|
|
var
|
|
tmp: String;
|
|
begin
|
|
Result := ALaunchApp;
|
|
if not FilenameIsAbsolute(ALaunchApp) then begin
|
|
tmp := CreateAbsolutePath(ALaunchApp, Project1.Directory);
|
|
if FileIsExecutable(tmp) then begin
|
|
Result := tmp;
|
|
end
|
|
else
|
|
if ExtractFilePath(ALaunchApp) = '' then begin
|
|
tmp := FindDefaultExecutablePath(ALaunchApp);
|
|
if tmp <> '' then
|
|
Result := tmp;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
type
|
|
|
|
{ TManagedBreakPoint }
|
|
|
|
TManagedBreakPoint = class(TIDEBreakPoint)
|
|
private
|
|
FSourceMark: TSourceMark;
|
|
FCurrentDebugExeLine: Integer;
|
|
procedure OnSourceMarkBeforeFree(Sender: TObject);
|
|
procedure OnSourceMarkCreatePopupMenu(SenderMark: TSourceMark;
|
|
const AddMenuItem: TAddMenuItemProc);
|
|
procedure OnSourceMarkGetHint(SenderMark: TSourceMark; var Hint: string);
|
|
procedure OnSourceMarkPositionChanged(Sender: TObject);
|
|
procedure OnToggleEnableMenuItemClick(Sender: TObject);
|
|
procedure OnDeleteMenuItemClick(Sender: TObject);
|
|
procedure OnViewPropertiesMenuItemClick(Sender: TObject);
|
|
protected
|
|
procedure DoChanged; override;
|
|
|
|
procedure SetSourceMark(const AValue: TSourceMark);
|
|
procedure UpdateSourceMark;
|
|
procedure UpdateSourceMarkImage;
|
|
procedure UpdateSourceMarkLineColor;
|
|
function DebugExeLine: Integer; override; // If known, the line in the compiled exe
|
|
public
|
|
procedure CopySourcePositionToBreakPoint;
|
|
procedure SetLocation(const ASource: String; const ALine: Integer); override;
|
|
property SourceMark: TSourceMark read FSourceMark write SetSourceMark;
|
|
end;
|
|
|
|
{ TManagedBreakPoints }
|
|
|
|
TManagedBreakPoints = class(TIDEBreakPoints)
|
|
private
|
|
FManager: TDebugManager;
|
|
protected
|
|
procedure NotifyAdd(const ABreakPoint: TIDEBreakPoint); override;
|
|
procedure NotifyRemove(const ABreakPoint: TIDEBreakPoint); override;
|
|
procedure Update(Item: TCollectionItem); override;
|
|
public
|
|
constructor Create(const AManager: TDebugManager);
|
|
end;
|
|
|
|
{ TProjectExceptions }
|
|
|
|
TProjectExceptions = class(TIDEExceptions)
|
|
protected
|
|
procedure SetIgnoreAll(const AValue: Boolean); override;
|
|
procedure Notify(Item: TCollectionItem; Action: TCollectionNotification); override;
|
|
procedure Update(Item: TCollectionItem); override;
|
|
end;
|
|
|
|
{ TDebugEventLogManager }
|
|
|
|
procedure TDebugEventLogManager.SetEventDialog(AValue: TDbgEventsForm);
|
|
begin
|
|
if FEventDialog = AValue then Exit;
|
|
|
|
If AValue = nil then begin
|
|
if FHiddenDebugEventsLog=nil then
|
|
FHiddenDebugEventsLog:=TStringList.Create;
|
|
FEventDialog.GetEvents(FHiddenDebugEventsLog);
|
|
end
|
|
else
|
|
if FHiddenDebugEventsLog <> nil then begin
|
|
AValue.SetEvents(FHiddenDebugEventsLog);
|
|
FreeAndNil(FHiddenDebugEventsLog);
|
|
end;
|
|
|
|
FEventDialog := AValue;
|
|
end;
|
|
|
|
function TDebugEventLogManager.FormatBreakPointAddress(
|
|
const ABreakpoint: TDBGBreakPoint; const ALocation: TDBGLocationRec): String;
|
|
var
|
|
SrcName: String;
|
|
begin
|
|
SrcName := ALocation.SrcFullName;
|
|
if SrcName = '' then
|
|
SrcName := ALocation.SrcFile;
|
|
|
|
if SrcName <> '' then
|
|
Result := Format(dbgEventBreakAtAddressSourceLine,
|
|
[IntToHex(ALocation.Address, FTargetWidth), SrcName, ALocation.SrcLine])
|
|
else
|
|
if (ABreakpoint <> nil) and (ABreakPoint.Kind = bpkSource) then
|
|
Result := Format(dbgEventBreakAtAddressOriginSourceOriginLine,
|
|
[IntToHex(ALocation.Address, FTargetWidth), ABreakpoint.Source, ABreakpoint.Line])
|
|
else
|
|
Result := Format(dbgEventBreakAtAddress, [IntToHex(ALocation.Address,
|
|
FTargetWidth)]);
|
|
end;
|
|
|
|
procedure TDebugEventLogManager.DebuggerEvent(Sender: TObject;
|
|
const ACategory: TDBGEventCategory; const AEventType: TDBGEventType;
|
|
const AText: String);
|
|
var
|
|
Rec: TDBGEventRec;
|
|
begin
|
|
if EventDialog <> nil
|
|
then begin
|
|
EventDialog.AddEvent(ACategory, AEventType, AText)
|
|
end
|
|
else begin
|
|
// store it internally, and copy it to the dialog, when the user opens it
|
|
if FHiddenDebugEventsLog=nil
|
|
then FHiddenDebugEventsLog := TStringList.Create;
|
|
if EnvironmentDebugOpts.DebuggerEventLogCheckLineLimit
|
|
then begin
|
|
while FHiddenDebugEventsLog.Count >= EnvironmentDebugOpts.DebuggerEventLogLineLimit do
|
|
FHiddenDebugEventsLog.Delete(0);
|
|
end;
|
|
Rec.Category := Ord(ACategory);
|
|
Rec.EventType := Ord(AEventType);
|
|
FHiddenDebugEventsLog.AddObject(AText, TObject(Rec.Ptr));
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugEventLogManager.LogCustomEvent(
|
|
const ACategory: TDBGEventCategory; const AEventType: TDBGEventType;
|
|
const AText: String);
|
|
begin
|
|
DebuggerEvent(nil, ACategory, AEventType, AText);
|
|
end;
|
|
|
|
procedure TDebugEventLogManager.LogEventBreakPointHit(
|
|
const ABreakpoint: TDBGBreakPoint; const ALocation: TDBGLocationRec);
|
|
var
|
|
Msg: String;
|
|
begin
|
|
if ABreakpoint = nil then
|
|
Msg := dbgEventBreakUnknownBreakPoint
|
|
else
|
|
case ABreakPoint.Kind of
|
|
bpkSource: Msg := dbgEventBreakSourceBreakPoint;
|
|
bpkAddress: Msg := dbgEventBreakAddressBreakPoint;
|
|
bpkData: Msg := dbgEventBreakWatchPoint; // should not be here, use LogEventWatchPointTriggered();
|
|
end;
|
|
|
|
LogCustomEvent(ecBreakpoint, etBreakpointHit,
|
|
Format(Msg, [FormatBreakPointAddress(ABreakpoint, ALocation)]));
|
|
end;
|
|
|
|
procedure TDebugEventLogManager.LogEventWatchPointTriggered(
|
|
const ABreakpoint: TDBGBreakPoint; const ALocation: TDBGLocationRec;
|
|
const AOldWatchedVal, ANewWatchedVal: String);
|
|
var
|
|
Msg, Loc: String;
|
|
begin
|
|
Loc := FormatBreakPointAddress(ABreakpoint, ALocation);
|
|
if ABreakpoint = nil then
|
|
Msg := Format(dbgEventUnknownWatchPointTriggered, [Loc, AOldWatchedVal, ANewWatchedVal])
|
|
else
|
|
case ABreakPoint.Kind of
|
|
bpkSource: Msg := Format(dbgEventBreakSourceBreakPoint , [Loc]); // should not be here
|
|
bpkAddress: Msg := Format(dbgEventBreakAddressBreakPoint, [Loc]); // should not be here
|
|
bpkData: Msg := Format(dbgEventWatchTriggered, [ABreakpoint.WatchData, Loc,
|
|
AOldWatchedVal, ANewWatchedVal]);
|
|
end;
|
|
|
|
LogCustomEvent(ecBreakpoint, etBreakpointHit, Msg );
|
|
end;
|
|
|
|
procedure TDebugEventLogManager.LogEventWatchPointScope(
|
|
const ABreakpoint: TDBGBreakPoint; const ALocation: TDBGLocationRec);
|
|
var
|
|
Msg, Loc: String;
|
|
begin
|
|
Loc := FormatBreakPointAddress(ABreakpoint, ALocation);
|
|
if ABreakpoint = nil then
|
|
Msg := Format(dbgEventUnknownWatchPointScopeEnded, [Loc])
|
|
else
|
|
case ABreakPoint.Kind of
|
|
bpkSource: Msg := Format(dbgEventBreakSourceBreakPoint , [Loc]); // should not be here
|
|
bpkAddress: Msg := Format(dbgEventBreakAddressBreakPoint, [Loc]); // should not be here
|
|
bpkData: Format(dbgEventWatchScopeEnded, [ABreakpoint.WatchData, Loc])
|
|
end;
|
|
|
|
LogCustomEvent(ecBreakpoint, etBreakpointHit, Msg );
|
|
end;
|
|
|
|
destructor TDebugEventLogManager.Destroy;
|
|
begin
|
|
FreeAndNil(FHiddenDebugEventsLog);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TDebugEventLogManager.ClearDebugEventsLog;
|
|
begin
|
|
if EventDialog <> nil then
|
|
EventDialog.Clear;
|
|
FreeAndNil(FHiddenDebugEventsLog);
|
|
end;
|
|
|
|
{ TProjectExceptions }
|
|
|
|
procedure TProjectExceptions.SetIgnoreAll(const AValue: Boolean);
|
|
begin
|
|
// Todo: move to Changed or Update, but they are called too often...
|
|
if (IgnoreAll <> AValue) and (Project1 <> nil) then
|
|
Project1.Modified := True;
|
|
inherited SetIgnoreAll(AValue);
|
|
end;
|
|
|
|
procedure TProjectExceptions.Notify(Item: TCollectionItem; Action: TCollectionNotification);
|
|
begin
|
|
inherited Notify(Item, Action);
|
|
if Project1 <> nil then
|
|
Project1.Modified := True;
|
|
end;
|
|
|
|
procedure TProjectExceptions.Update(Item: TCollectionItem);
|
|
begin
|
|
inherited Update(Item);
|
|
if Project1 <> nil then
|
|
Project1.Modified := True;
|
|
end;
|
|
|
|
{ TManagedBreakPoints }
|
|
|
|
constructor TManagedBreakPoints.Create(const AManager: TDebugManager);
|
|
begin
|
|
FManager := AManager;
|
|
inherited Create(TManagedBreakPoint);
|
|
end;
|
|
|
|
procedure TManagedBreakPoints.NotifyAdd(const ABreakPoint: TIDEBreakPoint);
|
|
begin
|
|
{$ifdef VerboseDebugger}
|
|
debugln('TManagedBreakPoints.NotifyAdd A ',ABreakpoint.Source,' ',IntToStr(ABreakpoint.Line));
|
|
{$endif}
|
|
inherited;
|
|
|
|
FManager.CreateSourceMarkForBreakPoint(ABreakpoint,nil);
|
|
Project1.Modified := True;
|
|
end;
|
|
|
|
procedure TManagedBreakPoints.NotifyRemove(const ABreakPoint: TIDEBreakPoint);
|
|
begin
|
|
{$ifdef VerboseDebugger}
|
|
debugln(['TManagedBreakPoints.NotifyRemove A ',ABreakpoint.Source,' ',ABreakpoint.Line,' ',TManagedBreakPoint(ABreakpoint).SourceMark <> nil]);
|
|
{$endif}
|
|
|
|
inherited;
|
|
if FManager.FCurrentBreakpoint = ABreakPoint
|
|
then FManager.FCurrentBreakpoint := nil;
|
|
|
|
TManagedBreakPoint(ABreakpoint).SourceMark.Free;
|
|
|
|
if Project1 <> nil
|
|
then Project1.Modified := True;
|
|
end;
|
|
|
|
procedure TManagedBreakPoints.Update(Item: TCollectionItem);
|
|
begin
|
|
inherited Update(Item);
|
|
if (Project1 <> nil) and (Item is TIDEBreakPoint) and (TIDEBreakPoint(Item).UserModified)
|
|
then begin
|
|
Project1.Modified := True;
|
|
TIDEBreakPoint(Item).UserModified := False;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TManagedBreakPoint }
|
|
|
|
procedure TManagedBreakPoint.SetSourceMark(const AValue: TSourceMark);
|
|
begin
|
|
if FSourceMark=AValue then exit;
|
|
if FSourceMark<>nil then begin
|
|
FSourceMark.RemoveAllHandlersForObject(Self);
|
|
FSourceMark.Data:=nil;
|
|
end;
|
|
FSourceMark:=AValue;
|
|
if FSourceMark<>nil then begin
|
|
FSourceMark.IncChangeLock;
|
|
FSourceMark.AddPositionChangedHandler(@OnSourceMarkPositionChanged);
|
|
FSourceMark.AddBeforeFreeHandler(@OnSourceMarkBeforeFree);
|
|
FSourceMark.Data:=Self;
|
|
FSourceMark.IsBreakPoint:=true;
|
|
FSourceMark.Line:=Line;
|
|
FSourceMark.Visible:=true;
|
|
FSourceMark.AddGetHintHandler(@OnSourceMarkGetHint);
|
|
FSourceMark.AddCreatePopupMenuHandler(@OnSourceMarkCreatePopupMenu);
|
|
UpdateSourceMark;
|
|
FSourceMark.DecChangeLock;
|
|
end;
|
|
end;
|
|
|
|
procedure TManagedBreakPoint.OnSourceMarkPositionChanged(Sender: TObject);
|
|
begin
|
|
CopySourcePositionToBreakPoint;
|
|
end;
|
|
|
|
procedure TManagedBreakPoint.OnToggleEnableMenuItemClick(Sender: TObject);
|
|
begin
|
|
Enabled:=not Enabled;
|
|
end;
|
|
|
|
procedure TManagedBreakPoint.OnDeleteMenuItemClick(Sender: TObject);
|
|
begin
|
|
ReleaseReference;
|
|
end;
|
|
|
|
procedure TManagedBreakPoint.OnViewPropertiesMenuItemClick(Sender: TObject);
|
|
begin
|
|
DebugBoss.ShowBreakPointProperties(Self);
|
|
end;
|
|
|
|
procedure TManagedBreakPoint.OnSourceMarkBeforeFree(Sender: TObject);
|
|
begin
|
|
SourceMark:=nil;
|
|
end;
|
|
|
|
procedure TManagedBreakPoint.OnSourceMarkGetHint(SenderMark: TSourceMark;
|
|
var Hint: string);
|
|
begin
|
|
Hint := GetBreakPointStateDescription(Self) + LineEnding +
|
|
Format('%s: %d' + LineEnding + '%s %s' + LineEnding + '%s: %s',
|
|
[lisHitCount, Hitcount,
|
|
lisAction, GetBreakPointActionsDescription(Self),
|
|
lisCondition, Expression]);
|
|
if SenderMark<>nil then ;
|
|
end;
|
|
|
|
procedure TManagedBreakPoint.OnSourceMarkCreatePopupMenu(
|
|
SenderMark: TSourceMark; const AddMenuItem: TAddMenuItemProc);
|
|
begin
|
|
if Enabled then
|
|
AddMenuItem(lisDisableBreakPoint, True, @OnToggleEnableMenuItemClick)
|
|
else
|
|
AddMenuItem(lisEnableBreakPoint, True, @OnToggleEnableMenuItemClick);
|
|
AddMenuItem(lisDeleteBreakPoint, True, @OnDeleteMenuItemClick);
|
|
AddMenuItem(lisViewBreakPointProperties, True, @OnViewPropertiesMenuItemClick);
|
|
if SenderMark<>nil then ;
|
|
end;
|
|
|
|
procedure TManagedBreakPoint.DoChanged;
|
|
begin
|
|
inherited DoChanged;
|
|
UpdateSourceMark;
|
|
end;
|
|
|
|
procedure TManagedBreakPoint.CopySourcePositionToBreakPoint;
|
|
begin
|
|
if FSourceMark=nil then exit;
|
|
SetLocation(Source,FSourceMark.Line);
|
|
end;
|
|
|
|
procedure TManagedBreakPoint.SetLocation(const ASource: String;
|
|
const ALine: Integer);
|
|
var
|
|
NewDebugExeLine: Integer;
|
|
begin
|
|
NewDebugExeLine := DebugExeLine;
|
|
if (Source = ASource) and (Line = ALine) and (FCurrentDebugExeLine = NewDebugExeLine)
|
|
then exit;
|
|
inherited SetLocation(ASource, ALine);
|
|
FCurrentDebugExeLine := NewDebugExeLine;
|
|
if Project1 <> nil
|
|
then Project1.Modified := True;
|
|
end;
|
|
|
|
procedure TManagedBreakPoint.UpdateSourceMarkImage;
|
|
var
|
|
Img: Integer;
|
|
begin
|
|
if SourceMark = nil then Exit;
|
|
case Valid of
|
|
vsValid:
|
|
if Enabled then
|
|
Img := SourceEditorMarks.ActiveBreakPointImg
|
|
else
|
|
Img := SourceEditorMarks.InactiveBreakPointImg;
|
|
vsInvalid:
|
|
if Enabled then
|
|
Img := SourceEditorMarks.InvalidBreakPointImg
|
|
else
|
|
Img := SourceEditorMarks.InvalidDisabledBreakPointImg;
|
|
vsPending:
|
|
if Enabled then
|
|
Img := SourceEditorMarks.PendingBreakPointImg
|
|
else
|
|
Img := SourceEditorMarks.InactiveBreakPointImg;
|
|
else
|
|
if Enabled then
|
|
Img := SourceEditorMarks.UnknownBreakPointImg
|
|
else
|
|
Img := SourceEditorMarks.UnknownDisabledBreakPointImg;
|
|
end;
|
|
SourceMark.ImageIndex := Img;
|
|
end;
|
|
|
|
procedure TManagedBreakPoint.UpdateSourceMarkLineColor;
|
|
var
|
|
aha: TAdditionalHilightAttribute;
|
|
begin
|
|
if SourceMark = nil then Exit;
|
|
aha := ahaNone;
|
|
case Valid of
|
|
vsValid:
|
|
if Enabled then
|
|
aha := ahaEnabledBreakpoint
|
|
else
|
|
aha := ahaDisabledBreakpoint;
|
|
vsInvalid:
|
|
if Enabled then
|
|
aha := ahaInvalidBreakpoint
|
|
else
|
|
aha := ahaDisabledBreakpoint;
|
|
else
|
|
if Enabled then
|
|
aha := ahaUnknownBreakpoint
|
|
else
|
|
aha := ahaDisabledBreakpoint;
|
|
end;
|
|
SourceMark.LineColorAttrib := aha;
|
|
end;
|
|
|
|
function TManagedBreakPoint.DebugExeLine: Integer;
|
|
var
|
|
se: TSourceEditor;
|
|
begin
|
|
Result := Line;
|
|
if (FSourceMark <> nil) and (FSourceMark.SourceEditor <> nil) then
|
|
Result := TSourceEditor(FSourceMark.SourceEditor).SourceToDebugLine(Line)
|
|
else begin
|
|
se := SourceEditorManager.SourceEditorIntfWithFilename(Source);
|
|
if se <> nil
|
|
then Result := se.SourceToDebugLine(Line);
|
|
end;
|
|
end;
|
|
|
|
procedure TManagedBreakPoint.UpdateSourceMark;
|
|
begin
|
|
if SourceMark = nil then Exit;
|
|
SourceMark.IncChangeLock;
|
|
SourceMark.Line := Line;
|
|
UpdateSourceMarkImage;
|
|
UpdateSourceMarkLineColor;
|
|
SourceMark.DecChangeLock;
|
|
end;
|
|
|
|
|
|
// Helper function for TDebugManager.GetFullFilename.
|
|
function FindFullFilenameSrc(const AUnitinfo: TDebuggerUnitInfo): boolean;
|
|
var
|
|
SrcUnitName: String;
|
|
SrcInFilename: String;
|
|
SrcFilename: String;
|
|
Code: TCodeBuffer;
|
|
ProcDef: String;
|
|
CurCodeTool: TCodeTool;
|
|
CurCodeNode: TCodeTreeNode;
|
|
CodePos: TCodeXYPosition;
|
|
begin
|
|
Result:=false;
|
|
// search unit in project unit path
|
|
SrcUnitName := AUnitinfo.UnitName;
|
|
SrcInFilename := '';
|
|
with CodeToolBoss.DirectoryCachePool do
|
|
SrcFilename := FindUnitSourceInCompletePath('', SrcUnitName, SrcInFilename);
|
|
if SrcFilename='' then exit;
|
|
// load unit
|
|
Code := CodeToolBoss.LoadFile(SrcFilename,true,false);
|
|
if Code=nil then exit; // read error
|
|
// procedure declaration: classname.functionname
|
|
ProcDef := '';
|
|
if AUnitinfo.SrcClassName<>'' then
|
|
ProcDef := AUnitinfo.SrcClassName+'.';
|
|
ProcDef := ProcDef+AUnitinfo.FunctionName;
|
|
// search proc in unit
|
|
if not CodeToolBoss.FindProcDeclaration(Code,ProcDef,CurCodeTool,CurCodeNode,
|
|
[phpWithoutParamList,phpWithoutBrackets,phpWithoutClassKeyword,phpWithoutSemicolon])
|
|
then
|
|
exit;
|
|
// get file, line, column
|
|
if CurCodeNode.Desc=ctnProcedure then
|
|
CurCodeNode := CurCodeNode.FirstChild; // jump to Name instead of keyword 'procedure'
|
|
if not CurCodeTool.CleanPosToCaret(CurCodeNode.StartPos,CodePos) then
|
|
exit;
|
|
AUnitinfo.LocationFullFile := CodePos.Code.Filename;
|
|
AUnitinfo.SrcLine := CodePos.Y;
|
|
//DumpStack;
|
|
Result:=true;
|
|
end;
|
|
|
|
function TDebugManager.GetFullFilename(const AUnitinfo: TDebuggerUnitInfo;
|
|
out Filename: string; AskUserIfNotFound: Boolean): Boolean;
|
|
|
|
function ResolveFromDbg: Boolean;
|
|
begin
|
|
Filename := AUnitinfo.FileName;
|
|
Result := (Filename<>'') and GetFullFilename(Filename, False) and FileExistsUTF8(Filename);
|
|
if Result then Exit;
|
|
Filename := AUnitinfo.DbgFullName;
|
|
if Filename='' then
|
|
Exit(False);
|
|
Result := FileExistsUTF8(Filename);
|
|
if not Result then
|
|
Result := GetFullFilename(Filename, AskUserIfNotFound);
|
|
end;
|
|
|
|
begin
|
|
Result := False;
|
|
if Destroying or (AUnitinfo = nil) then exit;
|
|
Filename := AUnitinfo.LocationFullFile;
|
|
Result := Filename <> '';
|
|
|
|
if (dlfSearchByFunctionName in AUnitinfo.Flags) and (AUnitinfo.FunctionName<>'')
|
|
and FindFullFilenameSrc(AUnitinfo) then
|
|
exit;
|
|
|
|
case AUnitinfo.LocationType of
|
|
dltUnknown: Result := ResolveFromDbg;
|
|
dltUnresolvable: Result := False;
|
|
dltProject:
|
|
begin
|
|
Filename := TrimFilename(AUnitinfo.LocationName);
|
|
Filename := MainIDE.FindSourceFile(Filename, Project1.Directory,
|
|
[fsfSearchForProject, fsfUseIncludePaths, fsfUseDebugPath,
|
|
{fsfMapTempToVirtualFiles,} fsfSkipPackages]);
|
|
Result := Filename <> '';
|
|
if not Result then
|
|
Result := ResolveFromDbg;
|
|
end;
|
|
dltPackage: Result := ResolveFromDbg;
|
|
end;
|
|
|
|
if Result then
|
|
AUnitinfo.LocationFullFile := Filename
|
|
else begin
|
|
Filename := AUnitinfo.FileName;
|
|
if AskUserIfNotFound then
|
|
AUnitinfo.LocationType := dltUnresolvable;
|
|
end;
|
|
end;
|
|
|
|
function TDebugManager.GetFullFilename(var Filename: string; AskUserIfNotFound: Boolean): Boolean;
|
|
var
|
|
SrcFile, SrcFN, UserFilename: String;
|
|
n: Integer;
|
|
OpenDialog: TIDEOpenDialog;
|
|
AnUnitInfo: TLazProjectFile;
|
|
begin
|
|
Result := False;
|
|
if Destroying or (Filename = '') then exit;
|
|
(* The below currently does not work for unsaved projects *)
|
|
//Result := FilenameIsAbsolute(Filename);
|
|
//if Result then exit;
|
|
|
|
// TODO, check for virtual file, and flag it
|
|
// Project1.IsVirtual
|
|
// Left(Filename,1, xxx) = LazarusIDE.GetTestBuildDirectory
|
|
|
|
// some debuggers (e.g. gdb) sometimes returns linux path delims under windows
|
|
// => fix that
|
|
Filename := TrimFilename(Filename);
|
|
SrcFile := MainIDE.FindSourceFile(Filename, Project1.Directory,
|
|
[fsfSearchForProject, fsfUseIncludePaths, fsfUseDebugPath{,
|
|
fsfMapTempToVirtualFiles}]);
|
|
if (SrcFile <> '') and (not FilenameIsAbsolute(SrcFile)) and
|
|
(Project1.IsVirtual) and
|
|
FileExistsUTF8(AppendPathDelim(LazarusIDE.GetTestBuildDirectory)+SrcFile)
|
|
then
|
|
SrcFile := AppendPathDelim(LazarusIDE.GetTestBuildDirectory)+SrcFile;
|
|
|
|
if SrcFile = '' then
|
|
SrcFile := Filename;
|
|
SrcFN := ExtractFilenameOnly(SrcFile);
|
|
if not FilenameIsAbsolute(SrcFile) then
|
|
begin
|
|
// first attempt to get a longer name
|
|
// short file, look in the user list
|
|
for n := 0 to FUserSourceFiles.Count - 1 do
|
|
begin
|
|
UserFilename := FUserSourceFiles[n];
|
|
if (CompareFileNames(SrcFN, ExtractFilenameOnly(UserFilename)) = 0)
|
|
and FileExistsUTF8(UserFilename) then
|
|
begin
|
|
FUserSourceFiles.Move(n, 0); // move most recent first
|
|
SrcFile := UserFilename;
|
|
Break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if not FilenameIsAbsolute(SrcFile) then
|
|
begin
|
|
AnUnitInfo := Project1.FindFile(SrcFile, [pfsfOnlyEditorFiles]);
|
|
if AnUnitInfo <> nil then
|
|
begin
|
|
// the file is an unsaved file -> can not be extended
|
|
Result := True;
|
|
Filename := SrcFile;
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
if ((not FilenameIsAbsolute(SrcFile)) or (not FileExistsUTF8(SrcFile)))
|
|
and AskUserIfNotFound then
|
|
begin
|
|
|
|
if IDEMessageDialog(lisFileNotFound,
|
|
Format(lisTheFileWasNotFoundDoYouWantToLocateItYourself, [SrcFile, LineEnding]),
|
|
mtConfirmation, [mbYes, mbNo]) <> mrYes
|
|
then Exit;
|
|
|
|
repeat
|
|
OpenDialog:=IDEOpenDialogClass.Create(nil);
|
|
try
|
|
InputHistories.ApplyFileDialogSettings(OpenDialog);
|
|
OpenDialog.Title:=lisOpenFile+' '+SrcFile;
|
|
OpenDialog.Options:=OpenDialog.Options+[ofFileMustExist];
|
|
OpenDialog.FileName := SrcFile;
|
|
if not OpenDialog.Execute then
|
|
exit;
|
|
SrcFile:=CleanAndExpandFilename(OpenDialog.FileName);
|
|
InputHistories.StoreFileDialogSettings(OpenDialog);
|
|
finally
|
|
OpenDialog.Free;
|
|
end;
|
|
until FilenameIsAbsolute(SrcFile) and FileExistsUTF8(SrcFile);
|
|
|
|
FUserSourceFiles.Insert(0, SrcFile);
|
|
end;
|
|
|
|
if (SrcFile<>'')
|
|
and ( (not FilenameIsAbsolute(SrcFile)) or FileExistsUTF8(SrcFile) )
|
|
then begin
|
|
Filename:=SrcFile;
|
|
Result:=True;
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.JumpToUnitSource(AFileName: String; ALine: Integer;
|
|
AMapLineFromDebug: Boolean);
|
|
var
|
|
ok: Boolean;
|
|
JmpFlags: TJumpToCodePosFlags;
|
|
begin
|
|
debugln(DBG_LOCATION_INFO, ['JumpToUnitSource Filename =', AFileName ]);
|
|
// avoid any process-messages, so this proc can not be re-entered (avoid opening one files many times)
|
|
LockCommandProcessing;
|
|
try
|
|
ok := false;
|
|
JmpFlags := [jfAddJumpPoint, jfFocusEditor, jfMarkLine, jfSearchVirtualFullPath];
|
|
if AMapLineFromDebug then
|
|
JmpFlags := JmpFlags + [jfMapLineFromDebug];
|
|
if FilenameIsAbsolute(AFilename) then
|
|
ok := MainIDEInterface.DoJumpToSourcePosition(AFilename, 0, ALine, 0, JmpFlags) = mrOK;
|
|
if not ok then
|
|
MainIDEInterface.DoJumpToSourcePosition(AFilename, 0, ALine, 0, JmpFlags+[jfDoNotExpandFilename]);
|
|
finally
|
|
UnLockCommandProcessing;
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.JumpToUnitSource(AnUnitInfo: TDebuggerUnitInfo;
|
|
ALine: Integer; AMapLineFromDebug: Boolean);
|
|
var
|
|
Filename: String;
|
|
ok: Boolean;
|
|
JmpFlags: TJumpToCodePosFlags;
|
|
begin
|
|
if AnUnitInfo = nil then exit;
|
|
debugln(DBG_LOCATION_INFO, ['JumpToUnitSource AnUnitInfo=', AnUnitInfo.DebugText ]);
|
|
// avoid any process-messages, so this proc can not be re-entered (avoid opening one files many times)
|
|
LockCommandProcessing;
|
|
try
|
|
(* Maybe trim the filename here and use jfDoNotExpandFilename
|
|
ExpandFilename works with the current IDE path, and may be wrong
|
|
*)
|
|
// TODO: better detection of unsaved project files
|
|
if GetFullFilename(AnUnitInfo, Filename, False) then
|
|
begin
|
|
ok := false;
|
|
if ALine <= 0 then
|
|
ALine := AnUnitInfo.SrcLine;
|
|
JmpFlags := [jfAddJumpPoint, jfFocusEditor, jfMarkLine, jfSearchVirtualFullPath];
|
|
if AMapLineFromDebug then
|
|
JmpFlags := JmpFlags + [jfMapLineFromDebug];
|
|
if FilenameIsAbsolute(Filename) then
|
|
ok := MainIDEInterface.DoJumpToSourcePosition(Filename, 0, ALine, 0, JmpFlags) = mrOK;
|
|
if not ok then
|
|
MainIDEInterface.DoJumpToSourcePosition(Filename, 0, ALine, 0, JmpFlags+[jfDoNotExpandFilename]);
|
|
end;
|
|
finally
|
|
UnLockCommandProcessing;
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.DebuggerConsoleOutput(Sender: TObject;
|
|
const AText: String);
|
|
begin
|
|
if not HasConsoleSupport then exit;;
|
|
if FDialogs[ddtPseudoTerminal] = nil
|
|
then ViewDebugDialog(ddtPseudoTerminal, False, False);
|
|
TPseudoConsoleDlg(FDialogs[ddtPseudoTerminal]).AddOutput(AText);
|
|
end;
|
|
|
|
function TDebugManager.DebuggerFeedback(Sender: TObject; const AText, AInfo: String;
|
|
AType: TDBGFeedbackType; AButtons: TDBGFeedbackResults): TDBGFeedbackResult;
|
|
begin
|
|
Result := ExecuteFeedbackDialog(AText, AInfo, AType, AButtons);
|
|
end;
|
|
|
|
procedure TDebugManager.DebuggerIdle(Sender: TObject);
|
|
begin
|
|
FSnapshots.DoDebuggerIdle;
|
|
end;
|
|
|
|
function TDebugManager.DoProjectClose(Sender: TObject; AProject: TLazProject): TModalResult;
|
|
begin
|
|
if AProject<>Project1 then exit(mrCancel);
|
|
ResetDebugger;
|
|
Result := mrOK;
|
|
end;
|
|
|
|
procedure TDebugManager.DoProjectModified(Sender: TObject);
|
|
begin
|
|
if Project1 <> nil then
|
|
Project1.Modified := True;
|
|
end;
|
|
|
|
procedure TDebugManager.mnuAddBpAddress(Sender: TObject);
|
|
var
|
|
NewBreakpoint: TIDEBreakPoint;
|
|
begin
|
|
NewBreakpoint := BreakPoints.Add(0, True);
|
|
if ShowBreakPointProperties(NewBreakpoint) <> mrOk then
|
|
ReleaseRefAndNil(NewBreakpoint)
|
|
else
|
|
NewBreakpoint.EndUpdate;
|
|
end;
|
|
|
|
procedure TDebugManager.mnuAddBpSource(Sender: TObject);
|
|
var
|
|
NewBreakpoint: TIDEBreakPoint;
|
|
SrcEdit: TSourceEditor;
|
|
begin
|
|
SrcEdit := SourceEditorManager.GetActiveSE;
|
|
if SrcEdit <> nil then
|
|
NewBreakpoint := BreakPoints.Add(SrcEdit.FileName, SrcEdit.CurrentCursorYLine, True)
|
|
else
|
|
NewBreakpoint := BreakPoints.Add('', 0, True);
|
|
if DebugBoss.ShowBreakPointProperties(NewBreakpoint) <> mrOk then
|
|
ReleaseRefAndNil(NewBreakpoint)
|
|
else
|
|
NewBreakpoint.EndUpdate;
|
|
end;
|
|
|
|
procedure TDebugManager.mnuAddBpData(Sender: TObject);
|
|
var
|
|
NewBreakpoint: TIDEBreakPoint;
|
|
begin
|
|
NewBreakpoint := BreakPoints.Add('', wpsGlobal, wpkWrite, True);
|
|
if ShowBreakPointProperties(NewBreakpoint) = mrOk then begin
|
|
NewBreakpoint.EndUpdate;
|
|
ViewDebugDialog(ddtBreakpoints, False);
|
|
end
|
|
else
|
|
ReleaseRefAndNil(NewBreakpoint);
|
|
end;
|
|
|
|
procedure TDebugManager.mnuAddBpDataAtCursor(Sender: TObject);
|
|
var
|
|
SE: TSourceEditor;
|
|
WatchVar: String;
|
|
NewBreakpoint: TIDEBreakPoint;
|
|
begin
|
|
SE := SourceEditorManager.GetActiveSE;
|
|
|
|
if Assigned(SE) then
|
|
begin
|
|
if SE.SelectionAvailable then
|
|
WatchVar := SE.Selection
|
|
else
|
|
WatchVar := SE.GetOperandAtCurrentCaret;
|
|
|
|
if (WatchVar <> '') and SE.EditorComponent.Focused then
|
|
begin
|
|
// TODO: find existing?
|
|
NewBreakpoint := BreakPoints.Add(WatchVar, wpsGlobal, wpkWrite, True);
|
|
if ShowBreakPointProperties(NewBreakpoint) = mrOk then begin
|
|
NewBreakpoint.EndUpdate;
|
|
ViewDebugDialog(ddtBreakpoints, False);
|
|
end
|
|
else
|
|
NewBreakpoint.ReleaseReference;
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
// watch was not added automatically => show a dialog
|
|
mnuAddBpData(nil);
|
|
end;
|
|
|
|
procedure TDebugManager.BreakAutoContinueTimer(Sender: TObject);
|
|
begin
|
|
FAutoContinueTimer.Enabled := False;
|
|
FDebugger.Run;
|
|
end;
|
|
|
|
procedure TDebugManager.OnRunTimer(Sender: TObject);
|
|
begin
|
|
FRunTimer.Enabled:=false;
|
|
if dmsWaitForRun in FManagerStates then
|
|
RunDebugger
|
|
else
|
|
if dmsWaitForAttach in FManagerStates then
|
|
AttachDebugger;
|
|
end;
|
|
|
|
procedure TDebugManager.DebuggerBreakPointHit(ADebugger: TDebuggerIntf;
|
|
ABreakPoint: TBaseBreakPoint; var ACanContinue: Boolean);
|
|
begin
|
|
FCurrentBreakPoint := nil;
|
|
if FBreakPoints = nil then Exit;
|
|
if ABreakpoint = nil then Exit;
|
|
|
|
FCurrentBreakpoint := nil;
|
|
if (ABreakPoint is TDBGBreakPoint) and (TDBGBreakPoint(ABreakPoint).Slave is TIDEBreakPoint) then
|
|
FCurrentBreakpoint := TIDEBreakPoint(TDBGBreakPoint(ABreakPoint).Slave)
|
|
else
|
|
DebugLn('ERROR: Breakpoint does not have correct class, or IDE slave breakpoint');
|
|
// TODO: remove / fallback to old behaviour
|
|
if FCurrentBreakpoint = nil then
|
|
FCurrentBreakPoint := FBreakPoints.Find(ABreakPoint.Source, ABreakPoint.Line);
|
|
end;
|
|
|
|
procedure TDebugManager.mnuViewDebugDialogClick(Sender: TObject);
|
|
var
|
|
xCommand: Integer;
|
|
begin
|
|
if (Sender is TIDESpecialCommand) and (TIDESpecialCommand(Sender).Command<>nil) then
|
|
xCommand := TIDESpecialCommand(Sender).Command.Command
|
|
else
|
|
if Sender is TIDECommand then
|
|
xCommand := TIDECommand(Sender).Command
|
|
else
|
|
xCommand := -1;
|
|
|
|
case xCommand of
|
|
ecToggleWatches : ViewDebugDialog(ddtWatches);
|
|
ecToggleBreakPoints : ViewDebugDialog(ddtBreakpoints);
|
|
ecToggleDebuggerOut : ViewDebugDialog(ddtOutput);
|
|
ecToggleLocals : ViewDebugDialog(ddtLocals);
|
|
ecToggleCallStack : ViewDebugDialog(ddtCallStack);
|
|
ecToggleRegisters : ViewDebugDialog(ddtRegisters);
|
|
ecToggleAssembler : ViewDebugDialog(ddtAssembler);
|
|
ecToggleDebugEvents : ViewDebugDialog(ddtEvents);
|
|
ecEvaluate : ViewDebugDialog(ddtEvaluate);
|
|
ecInspect : ViewDebugDialog(ddtInspect);
|
|
ecViewPseudoTerminal: ViewDebugDialog(ddtPseudoTerminal);
|
|
ecViewThreads : ViewDebugDialog(ddtThreads);
|
|
ecViewHistory : ViewDebugDialog(ddtHistory);
|
|
else
|
|
raise Exception.CreateFmt('IDE Internal error: TDebugManager.mnuViewDebugDialogClick, wrong command parameter %d.', [xCommand]);
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.mnuResetDebuggerClicked(Sender: TObject);
|
|
begin
|
|
ResetDebugger;
|
|
end;
|
|
|
|
procedure TDebugManager.mnuAddWatchClicked(Sender: TObject);
|
|
var
|
|
SE: TSourceEditor;
|
|
WatchVar: String;
|
|
w: TCurrentWatch;
|
|
begin
|
|
SE := SourceEditorManager.GetActiveSE;
|
|
|
|
if Assigned(SE) then
|
|
begin
|
|
if SE.SelectionAvailable then
|
|
WatchVar := SE.Selection
|
|
else
|
|
WatchVar := SE.GetOperandAtCurrentCaret;
|
|
if (WatchVar <> '') and (SE.SourceNotebook.Active or SE.EditorComponent.Focused) then
|
|
begin
|
|
Watches.CurrentWatches.BeginUpdate;
|
|
try
|
|
w := Watches.CurrentWatches.Find(WatchVar);
|
|
if w = nil
|
|
then w := Watches.CurrentWatches.Add(WatchVar);
|
|
if (w <> nil)
|
|
then begin
|
|
w.Enabled := True;
|
|
if EnvironmentDebugOpts.DebuggerAutoSetInstanceFromClass then
|
|
w.EvaluateFlags := w.EvaluateFlags + [defClassAutoCast];
|
|
ViewDebugDialog(ddtWatches, False);
|
|
Exit;
|
|
end;
|
|
finally
|
|
Watches.CurrentWatches.EndUpdate;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// watch was not added automatically => show a dialog
|
|
if ShowWatchProperties(nil, '') = mrOK then
|
|
ViewDebugDialog(ddtWatches, False);
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Debugger events
|
|
//-----------------------------------------------------------------------------
|
|
|
|
procedure TDebugManager.DebuggerException(Sender: TObject;
|
|
const AExceptionType: TDBGExceptionType;
|
|
const AExceptionClass: String;
|
|
const AExceptionLocation: TDBGLocationRec;
|
|
const AExceptionText: String;
|
|
out AContinue: Boolean);
|
|
|
|
function GetTitle: String;
|
|
begin
|
|
Result := Project1.GetTitle;
|
|
if Result = '' then
|
|
Result := ExtractFileName(FDebugger.FileName);
|
|
end;
|
|
|
|
const
|
|
MAX_CLASSNAME_LEN = 256; // shortstring
|
|
MAX_MSG_DISPLAY_LEN = 2048; // just sanity
|
|
var
|
|
ExpClassName, ExceptMsg: string;
|
|
msg, SrcText: String;
|
|
Ignore: Boolean;
|
|
Editor: TSourceEditor;
|
|
i: Integer;
|
|
begin
|
|
if Destroying then
|
|
begin
|
|
AContinue := True;
|
|
Exit;
|
|
end
|
|
else
|
|
AContinue := False;
|
|
|
|
ExpClassName := AExceptionClass;
|
|
if Length(ExpClassName) > MAX_CLASSNAME_LEN then
|
|
ExpClassName := copy(ExpClassName, 1, MAX_CLASSNAME_LEN) + '...';
|
|
|
|
if AExceptionText = ''
|
|
then
|
|
msg := Format(lisProjectSRaisedExceptionClassS,
|
|
[GetTitle, ExpClassName])
|
|
else begin
|
|
ExceptMsg := AExceptionText;
|
|
if Length(ExceptMsg) > MAX_MSG_DISPLAY_LEN then
|
|
ExceptMsg := copy(ExceptMsg, 1, MAX_MSG_DISPLAY_LEN) + '...';
|
|
// if AExceptionText is not a valid UTF8 string,
|
|
// then assume it has the ansi encoding and convert it
|
|
if FindInvalidUTF8Codepoint(pchar(ExceptMsg),length(ExceptMsg)) > 0 then
|
|
ExceptMsg := AnsiToUtf8(ExceptMsg);
|
|
msg := Format(lisProjectSRaisedExceptionClassSWithMessageSS,
|
|
[GetTitle, ExpClassName, LineEnding, ExceptMsg]);
|
|
end;
|
|
|
|
if AExceptionLocation.SrcFile <> '' then begin
|
|
if AExceptionLocation.SrcLine <> 0 then begin
|
|
SrcText := '';
|
|
if (AExceptionLocation.SrcFullName <> '') then begin
|
|
Editor := SourceEditorManager.SourceEditorIntfWithFilename(AExceptionLocation.SrcFullName);
|
|
if Editor <> nil then begin
|
|
try
|
|
i := Editor.DebugToSourceLine(AExceptionLocation.SrcLine);
|
|
if i > 0
|
|
then SrcText := Trim(Editor.Lines[i-1]);
|
|
except
|
|
end;
|
|
end;
|
|
end;
|
|
if SrcText <> '' then
|
|
msg := msg + Format(lisProjectSRaisedExceptionInFileLineSrc,
|
|
[LineEnding, AExceptionLocation.SrcFile, AExceptionLocation.SrcLine, SrcText])
|
|
else
|
|
msg := msg + Format(lisProjectSRaisedExceptionInFileLine,
|
|
[LineEnding, AExceptionLocation.SrcFile, AExceptionLocation.SrcLine]);
|
|
end
|
|
else
|
|
msg := msg + Format(lisProjectSRaisedExceptionInFileAddress,
|
|
[LineEnding, AExceptionLocation.SrcFile, AExceptionLocation.Address]);
|
|
end
|
|
else if AExceptionLocation.Address <> 0 then begin
|
|
msg := msg + Format(lisProjectSRaisedExceptionAtAddress,
|
|
[LineEnding, AExceptionLocation.Address]);
|
|
end;
|
|
|
|
if (AExceptionType in [deInternal, deRunError]) then begin
|
|
AContinue := ExecuteExceptionDialog(msg, Ignore, AExceptionType in [deInternal, deRunError]) = mrCancel;
|
|
if Ignore then begin
|
|
Exceptions.AddIfNeeded(ExpClassName);
|
|
Exceptions.Find(ExpClassName).Enabled := True;
|
|
end;
|
|
end
|
|
else begin
|
|
IDEMessageDialog(lisCCOErrorCaption, msg, mtError, [mbOk]);
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.DebuggerOutput(Sender: TObject; const AText: String);
|
|
begin
|
|
if Destroying then exit;
|
|
if FDialogs[ddtOutput] <> nil then
|
|
TDbgOutputForm(FDialogs[ddtOutput]).AddText(AText)
|
|
else begin
|
|
// store it internally, and copy it to the dialog, when the user opens it
|
|
if fHiddenDebugOutputLog=nil then
|
|
fHiddenDebugOutputLog:=TStringList.Create;
|
|
fHiddenDebugOutputLog.Add(AText);
|
|
while fHiddenDebugOutputLog.Count>100 do
|
|
fHiddenDebugOutputLog.Delete(0);
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.DebuggerBeforeChangeState(ADebugger: TDebuggerIntf;
|
|
AOldState: TDBGState);
|
|
var
|
|
DialogType: TDebugDialogType;
|
|
begin
|
|
if Destroying or (MainIDE=nil) or (MainIDE.ToolStatus=itExiting)
|
|
then exit;
|
|
if AOldState=dsNone then ;
|
|
assert((ADebugger=FDebugger) and (ADebugger<>nil), 'TDebugManager.OnDebuggerChangeState');
|
|
|
|
FInStateChange := True;
|
|
for DialogType := Low(TDebugDialogType) to High(TDebugDialogType) do
|
|
if FDialogs[DialogType] <> nil then
|
|
FDialogs[DialogType].BeginUpdate;
|
|
|
|
if FDebugger.State <> dsPause then
|
|
FCurrentWatches.Clear;
|
|
|
|
if FDebugger.State = dsInternalPause then exit; // set debug windows to ignore / no updating
|
|
end;
|
|
|
|
procedure TDebugManager.DebuggerChangeState(ADebugger: TDebuggerIntf; OldState: TDBGState);
|
|
|
|
procedure UnlockDialogs;
|
|
var
|
|
DialogType: TDebugDialogType;
|
|
begin
|
|
if not FInStateChange then exit;
|
|
FInStateChange := False;
|
|
for DialogType := Low(TDebugDialogType) to High(TDebugDialogType) do
|
|
if FDialogs[DialogType] <> nil then
|
|
FDialogs[DialogType].EndUpdate;
|
|
end;
|
|
|
|
//const
|
|
// dsNone, dsIdle, dsStop, dsPause, dsInit, dsRun, dsError
|
|
//STATENAME: array[TDBGState] of string = (
|
|
// 'dsNone', 'dsIdle', 'dsStop', 'dsPause', 'dsInit', 'dsRun', 'dsError'
|
|
//);
|
|
var
|
|
MsgResult: TModalResult;
|
|
i: Integer;
|
|
begin
|
|
if Destroying or (MainIDE=nil) or (MainIDE.ToolStatus=itExiting)
|
|
then begin
|
|
UnlockDialogs;
|
|
exit;
|
|
end;
|
|
assert((ADebugger=FDebugger) and (ADebugger<>nil), 'TDebugManager.OnDebuggerChangeState');
|
|
|
|
if (FDebugger.State in [dsRun])
|
|
then FCurrentBreakpoint := nil;
|
|
|
|
if not (FDebugger.State in [dsPause, dsInternalPause]) then
|
|
FCallStackNotification.OnChange := nil;
|
|
|
|
if not((OldState = dsInternalPause) and (State = dsPause)) then begin
|
|
// OldState=dsInternalPause means we already have a snapshot
|
|
// Notify FSnapshots of new state (while dialogs still in updating)
|
|
// TODO: Maybe move to TIDEBreakPoint.DoHit
|
|
if (FCurrentBreakpoint <> nil) and (bpaTakeSnapshot in FCurrentBreakpoint.Actions) and
|
|
(State in [dsPause, dsInternalPause])
|
|
then begin
|
|
FSnapshots.DoStateChange(OldState);
|
|
FSnapshots.Current.AddToSnapshots;
|
|
FSnapshots.DoDebuggerIdle(True);
|
|
end
|
|
else
|
|
if FDebugger.State <> dsInternalPause
|
|
then FSnapshots.DoStateChange(OldState);
|
|
end;
|
|
|
|
UnlockDialogs;
|
|
|
|
for i := 0 to FStateNotificationList.Count-1 do
|
|
TDebuggerStateChangeNotification(FStateNotificationList[i])(ADebugger, OldState);
|
|
|
|
if FDebugger.State = dsInternalPause
|
|
then exit;
|
|
|
|
if FDebugger.State=dsError
|
|
then begin
|
|
Include(FManagerStates,dmsDebuggerObjectBroken);
|
|
if dmsInitializingDebuggerObject in FManagerStates
|
|
then Include(FManagerStates,dmsInitializingDebuggerObjectFailed);
|
|
end;
|
|
|
|
//DebugLn('[TDebugManager.OnDebuggerChangeState] state: ', STATENAME[FDebugger.State]);
|
|
|
|
// All conmmands
|
|
// -------------------
|
|
// dcRun, dcPause, dcStop, dcStepOver, dcStepInto, dcStepOverInstrcution, dcStepIntoInstrcution,
|
|
// dcStepTo, dcJumpto, dcBreak, dcWatch
|
|
// -------------------
|
|
|
|
UpdateButtonsAndMenuItems;
|
|
// Next may call ResetDebugger, then FDebugger is gone
|
|
UpdateToolStatus;
|
|
|
|
FAutoContinueTimer.Enabled := false;
|
|
|
|
if FDebugger = nil then exit;
|
|
|
|
if (FDebugger.State in [dsRun])
|
|
then begin
|
|
// hide IDE during run
|
|
if EnvironmentGuiOpts.Desktop.HideIDEOnRun
|
|
and (MainIDE.ToolStatus=itDebugger) and not FStepping
|
|
then
|
|
MainIDE.HideIDE;
|
|
|
|
if (FPrevShownWindow <> 0) and not FStepping then
|
|
begin
|
|
SetForegroundWindow(FPrevShownWindow);
|
|
FPrevShownWindow := 0;
|
|
end;
|
|
end
|
|
else
|
|
if FDebugger.State <> dsInit then begin
|
|
if (FCurrentBreakPoint <> nil) and (FCurrentBreakPoint.AutoContinueTime > 0) then
|
|
begin
|
|
FAutoContinueTimer.Enabled := True;
|
|
FAutoContinueTimer.Interval := FCurrentBreakPoint.AutoContinueTime;
|
|
end
|
|
else if (OldState in [dsRun]) then
|
|
begin
|
|
if not FStepping then
|
|
begin
|
|
FPrevShownWindow := GetForegroundWindow;
|
|
if EnvironmentGuiOpts.Desktop.HideIDEOnRun then
|
|
MainIDE.UnhideIDE;
|
|
if not EnvironmentGuiOpts.Desktop.SingleTaskBarButton and
|
|
not EnvironmentGuiOpts.Desktop.HideIDEOnRun then
|
|
Application.BringToFront;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// unmark execution line
|
|
if (not (FDebugger.State in [dsInit, dsPause])) and (SourceEditorManager <> nil)
|
|
then
|
|
SourceEditorManager.ClearExecutionLines;
|
|
|
|
if (FDebugger.State in [dsPause, dsInit]) and (SourceEditorManager <> nil)
|
|
then
|
|
SourceEditorManager.FillExecutionMarks;
|
|
|
|
if not (FDebugger.State in [dsRun, dsPause, dsInit]) and (SourceEditorManager <> nil)
|
|
then begin
|
|
SourceEditorManager.ClearExecutionMarks;
|
|
// Refresh DebugExeLine
|
|
for i := 0 to FBreakPoints.Count - 1 do
|
|
FBreakPoints[i].SetLocation(FBreakPoints[i].Source, FBreakPoints[i].Line);
|
|
end;
|
|
|
|
case FDebugger.State of
|
|
dsError: begin
|
|
{$ifdef VerboseDebugger}
|
|
DebugLn('Ooops, the debugger entered the error state');
|
|
{$endif}
|
|
// shutting down lazarus may kill gdb, so we get an error
|
|
if not Application.Terminated
|
|
then FeedbackDlg.ExecuteFeedbackDialog
|
|
(Format(lisDebuggerErrorOoopsTheDebuggerEnteredTheErrorState, [LineEnding])
|
|
+ LineEnding + LineEnding + FDebugger.ErrorStateMessage,
|
|
FDebugger.ErrorStateInfo, ftError, [frStop]);
|
|
end;
|
|
dsStop: begin
|
|
// TODO: TDebugger.SetFileName sets dsStop during startup (leading to OldState=dsIdle)
|
|
FPrevShownWindow:=0;
|
|
if (OldState<>dsIdle)
|
|
then begin
|
|
MainIDE.DoCallRunFinishedHandler;
|
|
if not FDebugger.SkipStopMessage then begin
|
|
if (FDebugger.ExitCode <> 0) and EnvironmentDebugOpts.DebuggerShowExitCodeMessage then begin
|
|
i := 4;
|
|
if FDebugger.ExitCode > 65535 then
|
|
i := 8;
|
|
{$PUSH}{$R-}
|
|
MsgResult:=IDEQuestionDialog(lisExecutionStopped,
|
|
Format(lisExecutionStoppedExitCode, [LineEnding+'', FDebugger.ExitCode, IntToHex(FDebugger.ExitCode, i)]),
|
|
mtInformation, [mrOK, lisBtnOk,
|
|
mrYesToAll, lisDoNotShowThisMessageAgain], '');
|
|
{$POP}
|
|
if MsgResult=mrYesToAll then
|
|
EnvironmentDebugOpts.DebuggerShowExitCodeMessage:=false;
|
|
end
|
|
else
|
|
if EnvironmentDebugOpts.DebuggerShowStopMessage
|
|
then begin
|
|
MsgResult:=IDEQuestionDialog(lisExecutionStopped, lisExecutionStopped,
|
|
mtInformation, [mrOK, lisBtnOk,
|
|
mrYesToAll, lisDoNotShowThisMessageAgain], '');
|
|
if MsgResult=mrYesToAll then
|
|
EnvironmentDebugOpts.DebuggerShowStopMessage:=false;
|
|
end;
|
|
end;
|
|
|
|
if EnvironmentDebugOpts.DebuggerResetAfterRun or FDebugger.NeedReset then
|
|
ResetDebugger
|
|
else
|
|
FDebugger.FileName := ''; // SetState(dsIdle) via ResetStateToIdle
|
|
|
|
if FDialogs[ddtAssembler] <> nil
|
|
then begin
|
|
TAssemblerDlg(FDialogs[ddtAssembler]).SetLocation(nil, 0);
|
|
if FAsmWindowShouldAutoClose then
|
|
TAssemblerDlg(FDialogs[ddtAssembler]).Close;
|
|
end;
|
|
end;
|
|
end;
|
|
dsInit: begin
|
|
if FDialogs[ddtPseudoTerminal] <> nil then
|
|
TPseudoConsoleDlg(FDialogs[ddtPseudoTerminal]).Clear;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.DebuggerCurrentLine(Sender: TObject; const ALocation: TDBGLocationRec);
|
|
var
|
|
SrcLine, TId: Integer;
|
|
begin
|
|
FCallStackNotification.OnChange := nil;
|
|
if (Sender<>FDebugger) or (Sender=nil) then exit;
|
|
if FDebugger.State = dsInternalPause then exit;
|
|
if Destroying then exit;
|
|
|
|
FCurrentLocation := ALocation;
|
|
|
|
SrcLine := FCurrentLocation.SrcLine;
|
|
if (SrcLine < 1) and (SrcLine <> -2) // TODO: this should move to the debugger
|
|
// SrcLine will be -2 after stepping (gdbmi)
|
|
and not FAsmStepping
|
|
then begin
|
|
TId := Threads.CurrentThreads.CurrentThreadId;
|
|
if CallStack.CurrentCallStackList.EntriesForThreads[TId].HasAtLeastCount(30) = nbUnknown then begin
|
|
FCallStackNotification.OnChange := @DoDebuggerCurrentLine;
|
|
|
|
if FDialogs[ddtAssembler] <> nil
|
|
then TAssemblerDlg(FDialogs[ddtAssembler]).SetLocation(FDebugger, FCurrentLocation.Address);
|
|
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
DoDebuggerCurrentLine(nil);
|
|
end;
|
|
|
|
procedure TDebugManager.DoDebuggerCurrentLine(Sender: TObject);
|
|
// debugger paused program due to pause or error
|
|
// -> show the current execution line in editor
|
|
// if SrcLine < 1 then no source is available
|
|
|
|
function FileLocationToId(ALoc: TDBGLocationRec): string;
|
|
begin
|
|
Result := IntToStr(length(ALoc.SrcFile)) + ':' + ALoc.SrcFile + ':'
|
|
+ IntToStr(length(ALoc.SrcFullName)) + ':' + ALoc.SrcFullName;
|
|
end;
|
|
|
|
var
|
|
SrcFullName: String;
|
|
NewSource: TCodeBuffer;
|
|
Editor: TSourceEditor;
|
|
SrcLine: Integer;
|
|
c, i, TId: Integer;
|
|
StackEntry: TIdeCallStackEntry;
|
|
Flags: TJumpToCodePosFlags;
|
|
CurrentSourceUnitInfo: TDebuggerUnitInfo;
|
|
a: Boolean;
|
|
begin
|
|
FCallStackNotification.OnChange := nil;
|
|
if FDebugger.State = dsInternalPause then exit;
|
|
if Destroying then exit;
|
|
|
|
SrcLine := FCurrentLocation.SrcLine;
|
|
CurrentSourceUnitInfo := nil;
|
|
|
|
if (SrcLine < 1) and (SrcLine <> -2) // TODO: this should move to the debugger
|
|
// SrcLine will be -2 after stepping (gdbmi)
|
|
and not FAsmStepping
|
|
then begin
|
|
// jump to the deepest stack frame with debugging info
|
|
// TODO: Only below the frame supplied by debugger
|
|
i:=0;
|
|
TId := Threads.CurrentThreads.CurrentThreadId;
|
|
if CallStack.CurrentCallStackList.EntriesForThreads[TId].HasAtLeastCount(30) = nbUnknown then begin
|
|
FCallStackNotification.OnChange := @DoDebuggerCurrentLine;
|
|
exit;
|
|
end;
|
|
|
|
c := CallStack.CurrentCallStackList.EntriesForThreads[TId].CountLimited(30);
|
|
while (i < c) do
|
|
begin
|
|
StackEntry := CallStack.CurrentCallStackList.EntriesForThreads[TId].Entries[i];
|
|
if StackEntry.Validity = ddsRequested then begin// not yet available
|
|
FCallStackNotification.OnChange := @DoDebuggerCurrentLine;
|
|
exit;
|
|
end;
|
|
if StackEntry.Line > 0
|
|
then begin
|
|
CurrentSourceUnitInfo := StackEntry.UnitInfo;
|
|
CurrentSourceUnitInfo.AddReference;
|
|
SrcLine := StackEntry.Line;
|
|
StackEntry.MakeCurrent;
|
|
Break;
|
|
end;
|
|
Inc(i);
|
|
end;
|
|
end
|
|
else begin
|
|
CurrentSourceUnitInfo := FUnitInfoProvider.GetUnitInfoFor(FCurrentLocation.SrcFile, FCurrentLocation.SrcFullName);
|
|
CurrentSourceUnitInfo.AddReference;
|
|
end;
|
|
|
|
// TODO: do in DebuggerChangeState / Only currently State change locks execution of gdb
|
|
// Must be after stack frame selection (for inspect)
|
|
if FDialogs[ddtAssembler] <> nil
|
|
then TAssemblerDlg(FDialogs[ddtAssembler]).SetLocation(FDebugger, FCurrentLocation.Address);
|
|
|
|
if (SrcLine > 0) and (CurrentSourceUnitInfo <> nil) and
|
|
GetFullFilename(CurrentSourceUnitInfo, SrcFullName, True)
|
|
then begin
|
|
// Load the file
|
|
NewSource := CodeToolBoss.LoadFile(SrcFullName, true, false);
|
|
if NewSource = nil
|
|
then begin
|
|
if not (dlfLoadError in CurrentSourceUnitInfo.Flags) then begin
|
|
IDEMessageDialog(lisDebugUnableToLoadFile,
|
|
Format(lisDebugUnableToLoadFile2, [SrcFullName]),
|
|
mtError,[mbCancel]);
|
|
CurrentSourceUnitInfo.Flags := CurrentSourceUnitInfo.Flags + [dlfLoadError];
|
|
end;
|
|
SrcLine := -1;
|
|
end;
|
|
end
|
|
else begin
|
|
NewSource := Nil;
|
|
SrcLine := -1;
|
|
end;
|
|
|
|
ReleaseRefAndNil(CurrentSourceUnitInfo);
|
|
|
|
// clear old error and execution lines
|
|
if SourceEditorManager <> nil
|
|
then begin
|
|
SourceEditorManager.ClearExecutionLines;
|
|
SourceEditorManager.ClearErrorLines;
|
|
end;
|
|
|
|
if SrcLine < 1
|
|
then begin
|
|
a := FAsmWindowShouldAutoClose or (FDialogs[ddtAssembler] = nil) or (not FDialogs[ddtAssembler].Visible);
|
|
ViewDebugDialog(ddtAssembler);
|
|
FAsmWindowShouldAutoClose := a and EnvironmentDebugOpts.DebuggerAutoCloseAsm;
|
|
FAsmStepping := False;
|
|
exit;
|
|
end;
|
|
if (FDialogs[ddtAssembler] <> nil) and FAsmWindowShouldAutoClose then
|
|
TAssemblerDlg(FDialogs[ddtAssembler]).Close;
|
|
|
|
Editor := nil;
|
|
if SourceEditorManager <> nil
|
|
then Editor := SourceEditorManager.SourceEditorIntfWithFilename(NewSource.Filename);
|
|
|
|
// jump editor to execution line
|
|
Flags := [jfAddJumpPoint, jfSearchVirtualFullPath];
|
|
if (FCurrentBreakPoint = nil) or (FCurrentBreakPoint.AutoContinueTime = 0)
|
|
then include(Flags, jfFocusEditor);
|
|
i := SrcLine;
|
|
if (Editor <> nil) then
|
|
i := Editor.DebugToSourceLine(i);
|
|
if not (FAsmStepping and (FDialogs[ddtAssembler] <> nil) and
|
|
FDialogs[ddtAssembler].IsVisible and FDialogs[ddtAssembler].Active )
|
|
then
|
|
if MainIDE.DoJumpToCodePosition(nil,nil,NewSource,1,i,-1,-1,-1,Flags)<>mrOk
|
|
then exit;
|
|
FAsmStepping := False;
|
|
|
|
// mark execution line
|
|
if (Editor = nil) and (SourceEditorManager <> nil) then
|
|
Editor := SourceEditorManager.ActiveEditor;
|
|
if Editor <> nil
|
|
then begin
|
|
if not Editor.HasExecutionMarks then
|
|
Editor.FillExecutionMarks;
|
|
Editor.ExecutionLine := i;
|
|
end;
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Debugger dialog routines
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Common handler
|
|
// The tag of the destroyed form contains the form variable pointing to it
|
|
procedure TDebugManager.DebugDialogDestroy(Sender: TObject);
|
|
var
|
|
DlgType: TDebugDialogType;
|
|
begin
|
|
for DlgType:=Low(TDebugDialogType) to High(TDebugDialogType) do begin
|
|
if FDialogs[DlgType]<>Sender then continue;
|
|
case DlgType of
|
|
ddtOutput:
|
|
begin
|
|
if fHiddenDebugOutputLog=nil then
|
|
fHiddenDebugOutputLog:=TStringList.Create;
|
|
TDbgOutputForm(FDialogs[ddtOutput]).GetLogText(fHiddenDebugOutputLog);
|
|
end;
|
|
ddtEvents:
|
|
begin
|
|
FEventLogManager.EventDialog := nil;
|
|
end;
|
|
end;
|
|
FDialogs[DlgType]:=nil;
|
|
exit;
|
|
end;
|
|
RaiseGDBException('Invalid debug window '+Sender.ClassName);
|
|
end;
|
|
|
|
procedure TDebugManager.ViewDebugDialog(const ADialogType: TDebugDialogType;
|
|
BringToFront: Boolean; Show: Boolean; DoDisableAutoSizing: boolean;
|
|
InitFromSourceEdit: boolean);
|
|
const
|
|
DEBUGDIALOGCLASS: array[TDebugDialogType] of TDebuggerDlgClass = (
|
|
TDbgOutputForm, TDbgEventsForm, TBreakPointsDlg, TWatchesDlg, TLocalsDlg,
|
|
TCallStackDlg, TEvaluateDlg, TRegistersDlg, TAssemblerDlg, TIDEInspectDlg,
|
|
TPseudoConsoleDlg, TThreadsDlg, THistoryDialog
|
|
);
|
|
var
|
|
CurDialog: TDebuggerDlg;
|
|
begin
|
|
if Destroying then exit;
|
|
if (ADialogType = ddtPseudoTerminal) and not HasConsoleSupport
|
|
then exit;
|
|
if ADialogType = ddtAssembler then
|
|
FAsmWindowShouldAutoClose := False;
|
|
if FDialogs[ADialogType] = nil
|
|
then begin
|
|
CurDialog := TDebuggerDlg(DEBUGDIALOGCLASS[ADialogType].NewInstance);
|
|
if FInStateChange then CurDialog.BeginUpdate;
|
|
CurDialog.DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TDebugManager.ViewDebugDialog'){$ENDIF};
|
|
CurDialog.Create(Self);
|
|
FDialogs[ADialogType]:=CurDialog;
|
|
CurDialog.Name:= DebugDialogNames[ADialogType];
|
|
CurDialog.Tag := Integer(ADialogType);
|
|
CurDialog.OnDestroy := @DebugDialogDestroy;
|
|
case ADialogType of
|
|
ddtOutput: InitDebugOutputDlg;
|
|
ddtEvents: InitDebugEventsDlg;
|
|
ddtBreakpoints: InitBreakPointDlg;
|
|
ddtWatches: InitWatchesDlg;
|
|
ddtLocals: InitLocalsDlg;
|
|
ddtRegisters: InitRegistersDlg;
|
|
ddtCallStack: InitCallStackDlg;
|
|
ddtEvaluate: InitEvaluateDlg;
|
|
ddtAssembler: InitAssemblerDlg;
|
|
ddtInspect: InitInspectDlg;
|
|
ddtPseudoTerminal: InitPseudoTerminal;
|
|
ddtThreads: InitThreadsDlg;
|
|
ddtHistory: InitHistoryDlg;
|
|
end;
|
|
end
|
|
else begin
|
|
CurDialog:=FDialogs[ADialogType];
|
|
CurDialog.DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TDebugManager.ViewDebugDialog'){$ENDIF};
|
|
if (CurDialog is TBreakPointsDlg)
|
|
then begin
|
|
if (Project1<>nil) then
|
|
TBreakPointsDlg(CurDialog).BaseDirectory:=Project1.Directory;
|
|
end;
|
|
if (CurDialog is TAssemblerDlg)
|
|
then begin
|
|
TAssemblerDlg(CurDialog).SetLocation(FDebugger, FCurrentLocation.Address);
|
|
end;
|
|
if InitFromSourceEdit then begin
|
|
if (CurDialog is TIDEInspectDlg) and (SourceEditorManager.GetActiveSE <> nil)
|
|
then begin
|
|
if SourceEditorManager.GetActiveSE.SelectionAvailable then
|
|
TIDEInspectDlg(CurDialog).Execute(SourceEditorManager.GetActiveSE.Selection)
|
|
else
|
|
TIDEInspectDlg(CurDialog).Execute(SourceEditorManager.GetActiveSE.GetOperandAtCurrentCaret);
|
|
end;
|
|
if (CurDialog is TEvaluateDlg) and (SourceEditorManager.GetActiveSE <> nil)
|
|
then begin
|
|
if SourceEditorManager.GetActiveSE.SelectionAvailable then
|
|
TEvaluateDlg(CurDialog).Execute(SourceEditorManager.GetActiveSE.Selection)
|
|
else
|
|
TEvaluateDlg(CurDialog).Execute(SourceEditorManager.GetActiveSE.GetOperandAtCurrentCaret);
|
|
end;
|
|
end;
|
|
end;
|
|
if not DoDisableAutoSizing then
|
|
CurDialog.EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TDebugManager.ViewDebugDialog'){$ENDIF};
|
|
if Show then
|
|
begin
|
|
CurDialog.BeginUpdate;
|
|
IDEWindowCreators.ShowForm(CurDialog,BringToFront,vmOnlyMoveOffScreenToVisible);
|
|
CurDialog.EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.ViewDisassembler(AnAddr: TDBGPtr; BringToFront: Boolean;
|
|
Show: Boolean; DoDisableAutoSizing: boolean);
|
|
begin
|
|
ViewDebugDialog(ddtAssembler, BringToFront, Show, DoDisableAutoSizing);
|
|
if FDialogs[ddtAssembler] <> nil
|
|
then TAssemblerDlg(FDialogs[ddtAssembler]).SetLocation(FDebugger, FCurrentLocation.Address, AnAddr);
|
|
end;
|
|
|
|
procedure TDebugManager.RegisterStateChangeHandler(
|
|
AHandler: TDebuggerStateChangeNotification);
|
|
begin
|
|
FStateNotificationList.Add(TMethod(AHandler));
|
|
end;
|
|
|
|
procedure TDebugManager.UnregisterStateChangeHandler(
|
|
AHandler: TDebuggerStateChangeNotification);
|
|
begin
|
|
FStateNotificationList.Remove(TMethod(AHandler));
|
|
end;
|
|
|
|
procedure TDebugManager.RegisterWatchesInvalidatedHandler(AHandler: TNotifyEvent
|
|
);
|
|
begin
|
|
FWatchesInvalidatedNotificationList.Add(TMethod(AHandler));
|
|
end;
|
|
|
|
procedure TDebugManager.UnregisterWatchesInvalidatedHandler(
|
|
AHandler: TNotifyEvent);
|
|
begin
|
|
FWatchesInvalidatedNotificationList.Remove(TMethod(AHandler));
|
|
end;
|
|
|
|
procedure TDebugManager.DestroyDebugDialog(const ADialogType: TDebugDialogType);
|
|
begin
|
|
if FDialogs[ADialogType] = nil then Exit;
|
|
FDialogs[ADialogType].OnDestroy := nil;
|
|
FDialogs[ADialogType].Free;
|
|
FDialogs[ADialogType] := nil;
|
|
end;
|
|
|
|
procedure TDebugManager.InitDebugOutputDlg;
|
|
var
|
|
TheDialog: TDbgOutputForm;
|
|
begin
|
|
TheDialog := TDbgOutputForm(FDialogs[ddtOutput]);
|
|
if FHiddenDebugOutputLog <> nil
|
|
then begin
|
|
TheDialog.SetLogText(FHiddenDebugOutputLog);
|
|
FreeAndNil(FHiddenDebugOutputLog);
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.InitDebugEventsDlg;
|
|
var
|
|
TheDialog: TDbgEventsForm;
|
|
begin
|
|
TheDialog := TDbgEventsForm(FDialogs[ddtEvents]);
|
|
FEventLogManager.EventDialog := TheDialog;
|
|
end;
|
|
|
|
procedure TDebugManager.InitBreakPointDlg;
|
|
var
|
|
TheDialog: TBreakPointsDlg;
|
|
begin
|
|
TheDialog:=TBreakPointsDlg(FDialogs[ddtBreakpoints]);
|
|
if Project1 <> nil
|
|
then TheDialog.BaseDirectory := Project1.Directory;
|
|
TheDialog.BreakPoints := FBreakPoints;
|
|
end;
|
|
|
|
procedure TDebugManager.InitWatchesDlg;
|
|
var
|
|
TheDialog: TWatchesDlg;
|
|
begin
|
|
TheDialog := TWatchesDlg(FDialogs[ddtWatches]);
|
|
TheDialog.WatchesMonitor := FWatches;
|
|
TheDialog.ThreadsMonitor := FThreads;
|
|
TheDialog.CallStackMonitor := FCallStack;
|
|
TheDialog.BreakPoints := FBreakPoints;
|
|
TheDialog.SnapshotManager := FSnapshots;
|
|
end;
|
|
|
|
procedure TDebugManager.InitThreadsDlg;
|
|
var
|
|
TheDialog: TThreadsDlg;
|
|
begin
|
|
TheDialog := TThreadsDlg(FDialogs[ddtThreads]);
|
|
TheDialog.ThreadsMonitor := FThreads;
|
|
TheDialog.SnapshotManager := FSnapshots;
|
|
end;
|
|
|
|
procedure TDebugManager.InitPseudoTerminal;
|
|
//var
|
|
// TheDialog: TPseudoConsoleDlg;
|
|
begin
|
|
if not HasConsoleSupport then exit;
|
|
//TheDialog := TPseudoConsoleDlg(FDialogs[ddtPseudoTerminal]);
|
|
end;
|
|
|
|
procedure TDebugManager.InitLocalsDlg;
|
|
var
|
|
TheDialog: TLocalsDlg;
|
|
begin
|
|
TheDialog := TLocalsDlg(FDialogs[ddtLocals]);
|
|
TheDialog.LocalsMonitor := FLocals;
|
|
TheDialog.ThreadsMonitor := FThreads;
|
|
TheDialog.CallStackMonitor := FCallStack;
|
|
TheDialog.SnapshotManager := FSnapshots;
|
|
end;
|
|
|
|
procedure TDebugManager.InitRegistersDlg;
|
|
var
|
|
TheDialog: TRegistersDlg;
|
|
begin
|
|
TheDialog := TRegistersDlg(FDialogs[ddtRegisters]);
|
|
TheDialog.ThreadsMonitor := FThreads;
|
|
TheDialog.CallStackMonitor := FCallStack;
|
|
TheDialog.RegistersMonitor := FRegisters;
|
|
end;
|
|
|
|
procedure TDebugManager.InitAssemblerDlg;
|
|
var
|
|
TheDialog: TAssemblerDlg;
|
|
begin
|
|
TheDialog := TAssemblerDlg(FDialogs[ddtAssembler]);
|
|
TheDialog.BreakPoints := FBreakPoints;
|
|
TheDialog.Disassembler := FDisassembler;
|
|
TheDialog.DebugManager := Self;
|
|
TheDialog.SetLocation(FDebugger, FCurrentLocation.Address);
|
|
end;
|
|
|
|
procedure TDebugManager.InitInspectDlg;
|
|
var
|
|
TheDialog: TIDEInspectDlg;
|
|
begin
|
|
TheDialog := TIDEInspectDlg(FDialogs[ddtInspect]);
|
|
if (SourceEditorManager.GetActiveSE = nil) then
|
|
exit;
|
|
if SourceEditorManager.GetActiveSE.SelectionAvailable then
|
|
TheDialog.Execute(SourceEditorManager.GetActiveSE.Selection)
|
|
else
|
|
TheDialog.Execute(SourceEditorManager.GetActiveSE.GetOperandAtCurrentCaret);
|
|
end;
|
|
|
|
procedure TDebugManager.InitHistoryDlg;
|
|
var
|
|
TheDialog: THistoryDialog;
|
|
begin
|
|
TheDialog := THistoryDialog(FDialogs[ddtHistory]);
|
|
TheDialog.SnapshotManager := FSnapshots;
|
|
end;
|
|
|
|
procedure TDebugManager.InitCallStackDlg;
|
|
var
|
|
TheDialog: TCallStackDlg;
|
|
begin
|
|
TheDialog := TCallStackDlg(FDialogs[ddtCallStack]);
|
|
TheDialog.CallStackMonitor := FCallStack;
|
|
TheDialog.BreakPoints := FBreakPoints;
|
|
TheDialog.ThreadsMonitor := FThreads;
|
|
TheDialog.SnapshotManager := FSnapshots;
|
|
end;
|
|
|
|
procedure TDebugManager.InitEvaluateDlg;
|
|
var
|
|
TheDialog: TEvaluateDlg;
|
|
begin
|
|
TheDialog := TEvaluateDlg(FDialogs[ddtEvaluate]);
|
|
if (SourceEditorManager.GetActiveSE = nil) then
|
|
exit;
|
|
if SourceEditorManager.GetActiveSE.SelectionAvailable
|
|
then
|
|
TheDialog.EvalExpression := SourceEditorManager.GetActiveSE.Selection
|
|
else
|
|
TheDialog.EvalExpression := SourceEditorManager.GetActiveSE.GetOperandAtCurrentCaret;
|
|
end;
|
|
|
|
constructor TDebugManager.Create(TheOwner: TComponent);
|
|
var
|
|
DialogType: TDebugDialogType;
|
|
begin
|
|
FInStateChange := False;
|
|
for DialogType := Low(TDebugDialogType) to High(TDebugDialogType) do
|
|
FDialogs[DialogType] := nil;
|
|
|
|
FDebugger := nil;
|
|
FUnitInfoProvider := TDebuggerUnitInfoProvider.Create;
|
|
FBreakPoints := TManagedBreakPoints.Create(Self);
|
|
FBreakPointGroups := TIDEBreakPointGroups.Create;
|
|
FWatches := TIdeWatchesMonitor.Create;
|
|
FWatches.OnWatchesInvalidated := @CallWatchesInvalidatedHandlers;
|
|
FThreads := TIdeThreadsMonitor.Create;
|
|
FExceptions := TProjectExceptions.Create;
|
|
FSignals := TIDESignals.Create;
|
|
FLocals := TIdeLocalsMonitor.Create;
|
|
FLineInfo := TIDELineInfo.Create;
|
|
FCallStack := TIdeCallStackMonitor.Create;
|
|
FDisassembler := TIDEDisassembler.Create;
|
|
FRegisters := TIdeRegistersMonitor.Create;
|
|
|
|
FCallStackNotification := TCallStackNotification.Create;
|
|
FCallStackNotification.AddReference;
|
|
FCallStack.AddNotification(FCallStackNotification);
|
|
|
|
FSnapshots := TSnapshotManager.Create;
|
|
FSnapshots.Threads := FThreads;
|
|
FSnapshots.CallStack := FCallStack;
|
|
FSnapshots.Watches := FWatches;
|
|
FSnapshots.Locals := FLocals;
|
|
FSnapshots.UnitInfoProvider := FUnitInfoProvider;
|
|
|
|
FStateNotificationList := TMethodList.Create;
|
|
FWatchesInvalidatedNotificationList := TMethodList.Create;
|
|
FUserSourceFiles := TStringList.Create;
|
|
|
|
FAutoContinueTimer := TTimer.Create(Self);
|
|
FAutoContinueTimer.Enabled := False;
|
|
FAutoContinueTimer.OnTimer := @BreakAutoContinueTimer;
|
|
FRunTimer := TTimer.Create(Self);
|
|
FRunTimer.Interval := 1;
|
|
FRunTimer.OnTimer := @OnRunTimer;
|
|
|
|
FWatches.OnModified := @DoProjectModified;
|
|
|
|
FCurrentWatches := TCurrentWatches.Create(FWatches);
|
|
|
|
FIsInitializingDebugger:= False;
|
|
|
|
inherited Create(TheOwner);
|
|
|
|
LazarusIDE.AddHandlerOnProjectClose(@DoProjectClose);
|
|
|
|
RegisterValueFormatter(skSimple, 'TDate', @DBGDateTimeFormatter);
|
|
RegisterValueFormatter(skFloat, 'TDate', @DBGDateTimeFormatter);
|
|
RegisterValueFormatter(skSimple, 'TTime', @DBGDateTimeFormatter);
|
|
RegisterValueFormatter(skFloat, 'TTime', @DBGDateTimeFormatter);
|
|
RegisterValueFormatter(skSimple, 'TDateTime', @DBGDateTimeFormatter);
|
|
RegisterValueFormatter(skFloat, 'TDateTime', @DBGDateTimeFormatter);
|
|
|
|
FEventLogManager := TDebugEventLogManager.Create;
|
|
end;
|
|
|
|
destructor TDebugManager.Destroy;
|
|
var
|
|
DialogType: TDebugDialogType;
|
|
begin
|
|
FDestroying := true;
|
|
|
|
LazarusIDE.RemoveHandlerOnProjectClose(@DoProjectClose);
|
|
FreeAndNil(FAutoContinueTimer);
|
|
|
|
for DialogType := Low(TDebugDialogType) to High(TDebugDialogType) do
|
|
DestroyDebugDialog(DialogType);
|
|
|
|
if FCallStackNotification <> nil then
|
|
FCallStackNotification.ReleaseReference;
|
|
|
|
SetDebugger(nil);
|
|
|
|
FreeAndNil(FCurrentWatches);
|
|
FreeAndNil(FEventLogManager);
|
|
FreeAndNil(FSnapshots);
|
|
FreeAndNil(FWatches);
|
|
FreeAndNil(FThreads);
|
|
FreeAndNil(FBreakPoints);
|
|
FreeAndNil(FBreakPointGroups);
|
|
FreeAndNil(FCallStack);
|
|
FreeAndNil(FDisassembler);
|
|
FreeAndNil(FExceptions);
|
|
FreeAndNil(FSignals);
|
|
FreeAndNil(FLocals);
|
|
FreeAndNil(FLineInfo);
|
|
FreeAndNil(FRegisters);
|
|
|
|
FreeAndNil(FUserSourceFiles);
|
|
FreeAndNil(FHiddenDebugOutputLog);
|
|
FreeAndNil(FUnitInfoProvider);
|
|
FreeAndNil(FStateNotificationList);
|
|
FreeAndNil(FWatchesInvalidatedNotificationList);
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TDebugManager.Reset;
|
|
begin
|
|
FBreakPoints.Clear;
|
|
FBreakPointGroups.Clear;
|
|
FWatches.Clear;
|
|
FThreads.Clear;
|
|
FExceptions.Reset;
|
|
FSignals.Reset;
|
|
FUserSourceFiles.Clear;
|
|
FUnitInfoProvider.Clear;
|
|
end;
|
|
|
|
procedure TDebugManager.ConnectMainBarEvents;
|
|
begin
|
|
with MainIDEBar do begin
|
|
itmViewWatches.OnClick := @mnuViewDebugDialogClick;
|
|
itmViewWatches.Tag := Ord(ddtWatches);
|
|
itmViewBreakPoints.OnClick := @mnuViewDebugDialogClick;
|
|
itmViewBreakPoints.Tag := Ord(ddtBreakPoints);
|
|
itmViewLocals.OnClick := @mnuViewDebugDialogClick;
|
|
itmViewLocals.Tag := Ord(ddtLocals);
|
|
itmViewRegisters.OnClick := @mnuViewDebugDialogClick;
|
|
itmViewRegisters.Tag := Ord(ddtRegisters);
|
|
itmViewCallStack.OnClick := @mnuViewDebugDialogClick;
|
|
itmViewCallStack.Tag := Ord(ddtCallStack);
|
|
itmViewThreads.OnClick := @mnuViewDebugDialogClick;
|
|
itmViewThreads.Tag := Ord(ddtThreads);
|
|
itmViewAssembler.OnClick := @mnuViewDebugDialogClick;
|
|
itmViewAssembler.Tag := Ord(ddtAssembler);
|
|
itmViewDebugOutput.OnClick := @mnuViewDebugDialogClick;
|
|
itmViewDebugOutput.Tag := Ord(ddtOutput);
|
|
itmViewDebugEvents.OnClick := @mnuViewDebugDialogClick;
|
|
itmViewDebugEvents.Tag := Ord(ddtEvents);
|
|
if itmViewPseudoTerminal <> nil then begin
|
|
itmViewPseudoTerminal.OnClick := @mnuViewDebugDialogClick;
|
|
itmViewPseudoTerminal.Tag := Ord(ddtPseudoTerminal);
|
|
end;
|
|
itmViewDbgHistory.OnClick := @mnuViewDebugDialogClick;
|
|
itmViewDbgHistory.Tag := Ord(ddtHistory);
|
|
|
|
itmRunMenuResetDebugger.OnClick := @mnuResetDebuggerClicked;
|
|
|
|
itmRunMenuInspect.OnClick := @mnuViewDebugDialogClick;
|
|
itmRunMenuInspect.Tag := Ord(ddtInspect);
|
|
itmRunMenuEvaluate.OnClick := @mnuViewDebugDialogClick;
|
|
itmRunMenuEvaluate.Tag := Ord(ddtEvaluate);
|
|
itmRunMenuAddWatch.OnClick := @mnuAddWatchClicked;
|
|
|
|
itmRunMenuAddBpSource.OnClick := @mnuAddBpSource;
|
|
itmRunMenuAddBpAddress.OnClick := @mnuAddBpAddress;
|
|
itmRunMenuAddBpWatchPoint.OnClick := @mnuAddBpData;
|
|
|
|
// TODO: add capacibilities to DebuggerClass
|
|
// and disable unsuported items
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.ConnectSourceNotebookEvents;
|
|
begin
|
|
SrcEditMenuAddWatchAtCursor.OnClick:=@mnuAddWatchClicked;
|
|
SrcEditMenuAddWatchPointAtCursor.OnClick:=@mnuAddBpDataAtCursor;
|
|
SrcEditMenuEvaluateModify.OnClick:=@mnuViewDebugDialogClick;
|
|
SrcEditMenuEvaluateModify.Tag := Ord(ddtEvaluate);
|
|
SrcEditMenuInspect.OnClick:=@mnuViewDebugDialogClick;
|
|
SrcEditMenuInspect.Tag := Ord(ddtInspect);
|
|
end;
|
|
|
|
function GetCommand(ACommand: word): TIDECommand;
|
|
begin
|
|
Result:=IDECommandList.FindIDECommand(ACommand);
|
|
if Result<>nil then
|
|
RegisterIDEButtonCommand(Result);
|
|
end;
|
|
|
|
procedure TDebugManager.SetupMainBarShortCuts;
|
|
begin
|
|
with MainIDEBar do
|
|
begin
|
|
itmViewWatches.Command:=GetCommand(ecToggleWatches);
|
|
itmViewBreakpoints.Command:=GetCommand(ecToggleBreakPoints);
|
|
itmViewDebugOutput.Command:=GetCommand(ecToggleDebuggerOut);
|
|
itmViewDebugEvents.Command:=GetCommand(ecToggleDebugEvents);
|
|
itmViewLocals.Command:=GetCommand(ecToggleLocals);
|
|
itmViewRegisters.Command:=GetCommand(ecToggleRegisters);
|
|
itmViewCallStack.Command:=GetCommand(ecToggleCallStack);
|
|
itmViewAssembler.Command:=GetCommand(ecToggleAssembler);
|
|
itmViewThreads.Command:=GetCommand(ecViewThreads);
|
|
if itmViewPseudoTerminal <> nil then
|
|
itmViewPseudoTerminal.Command:=GetCommand(ecViewPseudoTerminal);
|
|
itmViewDbgHistory.Command:=GetCommand(ecViewHistory);
|
|
|
|
itmRunMenuInspect.Command:=GetCommand(ecInspect);
|
|
itmRunMenuEvaluate.Command:=GetCommand(ecEvaluate);
|
|
itmRunMenuAddWatch.Command:=GetCommand(ecAddWatch);
|
|
itmRunMenuAddBpSource.Command:=GetCommand(ecAddBpSource);
|
|
itmRunMenuAddBpAddress.Command:=GetCommand(ecAddBpAddress);
|
|
itmRunMenuAddBpWatchPoint.Command:=GetCommand(ecAddBpDataWatch);
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.SetupSourceMenuShortCuts;
|
|
begin
|
|
SrcEditMenuToggleBreakpoint.Command:=GetCommand(ecToggleBreakPoint);
|
|
SrcEditMenuStepToCursor.Command:=GetCommand(ecStepToCursor);
|
|
SrcEditMenuRunToCursor.Command:=GetCommand(ecRunToCursor);
|
|
SrcEditMenuEvaluateModify.Command:=GetCommand(ecEvaluate);
|
|
SrcEditMenuAddWatchAtCursor.Command:=GetCommand(ecAddWatch);
|
|
SrcEditMenuAddWatchPointAtCursor.Command:=GetCommand(ecAddBpDataWatch);
|
|
SrcEditMenuInspect.Command:=GetCommand(ecInspect);
|
|
SrcEditMenuViewCallStack.Command:=GetCommand(ecToggleCallStack);
|
|
end;
|
|
|
|
procedure TDebugManager.UpdateButtonsAndMenuItems;
|
|
var
|
|
DebuggerIsValid: boolean;
|
|
CanRun: Boolean;
|
|
AvailCommands: TDBGCommands;
|
|
CurState: TDBGState;
|
|
begin
|
|
if (MainIDE=nil) or (MainIDE.ToolStatus = itExiting) then exit;
|
|
|
|
CurState := dsStop;
|
|
if FDebugger <> nil then
|
|
CurState := FDebugger.State;
|
|
AvailCommands := GetAvailableCommands;
|
|
DebuggerIsValid:=(MainIDE.ToolStatus in [itNone, itDebugger]);
|
|
CanRun := CanRunDebugger;
|
|
with MainIDEBar do begin
|
|
// For 'run' and 'step' bypass 'idle', so we can set the filename later
|
|
// Run
|
|
itmRunMenuRun.Enabled := (CanRun and (dcRun in AvailCommands)) or
|
|
((Project1<>nil) and Project1.CompilerOptions.RunWithoutDebug);
|
|
itmRunMenuRunWithDebugging.Enabled := CanRun and (dcRun in AvailCommands);
|
|
itmRunMenuRunWithDebugging.Visible := (Project1<>nil) and Project1.CompilerOptions.RunWithoutDebug;
|
|
itmRunMenuRunWithoutDebugging.Visible := (Project1<>nil) and (not Project1.CompilerOptions.RunWithoutDebug);
|
|
// Pause
|
|
itmRunMenuPause.Enabled := CanRun and ((dcPause in AvailCommands) or FAutoContinueTimer.Enabled);
|
|
// Show execution point
|
|
itmRunMenuShowExecutionPoint.Enabled := CanRun and (CurState = dsPause);
|
|
// Step into
|
|
itmRunMenuStepInto.Enabled := CanRun and (dcStepInto in AvailCommands);
|
|
// Step over
|
|
itmRunMenuStepOver.Enabled := CanRun and (dcStepOver in AvailCommands);
|
|
// Step out
|
|
itmRunMenuStepOut.Enabled := CanRun and (dcStepOut in AvailCommands) and (CurState = dsPause);
|
|
// Step to cursor
|
|
itmRunMenuStepToCursor.Enabled := CanRun and (dcStepTo in AvailCommands);
|
|
// Run to cursor
|
|
itmRunMenuRunToCursor.Enabled := CanRun and (dcRunTo in AvailCommands);
|
|
// Stop
|
|
itmRunMenuStop.Enabled := (CanRun and (MainIDE.ToolStatus = itDebugger) and
|
|
(CurState in [dsPause, dsInternalPause, dsInit, dsRun, dsError])) or
|
|
(MainIDE.ToolStatus = itBuilder);
|
|
|
|
//Attach / Detach
|
|
itmRunMenuAttach.Enabled := DebuggerIsValid and (dcAttach in AvailCommands);
|
|
itmRunMenuDetach.Enabled := DebuggerIsValid and (dcDetach in AvailCommands);
|
|
|
|
// Evaluate
|
|
itmRunMenuEvaluate.Enabled := CanRun and (dcEvaluate in AvailCommands);
|
|
// Evaluate / modify
|
|
SrcEditMenuEvaluateModify.Enabled := CanRun and (dcEvaluate in AvailCommands);
|
|
// Add watch
|
|
itmRunMenuAddWatch.Enabled := True; // always allow to add a watch
|
|
|
|
// Add Breakpoint
|
|
itmRunMenuAddBpSource.Enabled := True;
|
|
itmRunMenuAddBpAddress.Enabled := True;
|
|
itmRunMenuAddBpWatchPoint.Enabled := True;
|
|
|
|
// TODO: add capacibilities to DebuggerClass
|
|
// menu view
|
|
//itmViewRegisters.Enabled := DebuggerIsValid;
|
|
//itmViewAssembler.Enabled := DebuggerIsValid;
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.UpdateToolStatus;
|
|
const
|
|
TOOLSTATEMAP: array[TDBGState] of TIDEToolStatus = (
|
|
//dsNone, dsIdle, dsStop, dsPause, dsInternalPause, dsInit, dsRun, dsError, dsDestroying
|
|
itNone, itNone, itNone, itDebugger, itDebugger, itDebugger, itDebugger, itNone, itNone
|
|
);
|
|
begin
|
|
// Next may call ResetDebugger, then FDebugger is gone
|
|
if MainIDE.ToolStatus in [itNone,itDebugger]
|
|
then begin
|
|
if FDebugger = nil then
|
|
MainIDE.ToolStatus := itNone
|
|
else
|
|
MainIDE.ToolStatus := TOOLSTATEMAP[FDebugger.State];
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.EnvironmentOptsChanged;
|
|
begin
|
|
if FDebugger <> nil then begin
|
|
if EnvironmentDebugOpts.DebuggerAllowFunctionCalls then
|
|
FDebugger.EnabledFeatures := FDebugger.EnabledFeatures + [dfEvalFunctionCalls]
|
|
else
|
|
FDebugger.EnabledFeatures := FDebugger.EnabledFeatures - [dfEvalFunctionCalls];
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TDebugManager.LoadProjectSpecificInfo(XMLConfig: TXMLConfig;
|
|
Merge: boolean);
|
|
|
|
Called when the main project is loaded from the XMLConfig.
|
|
------------------------------------------------------------------------------}
|
|
procedure TDebugManager.LoadProjectSpecificInfo(XMLConfig: TXMLConfig;
|
|
Merge: boolean);
|
|
begin
|
|
if not Merge then
|
|
begin
|
|
FExceptions.LoadFromXMLConfig(XMLConfig,'Debugging/'+XMLExceptionsNode+'/');
|
|
end;
|
|
// keep it simple: just load from the session and don't merge
|
|
FBreakPointGroups.LoadFromXMLConfig(XMLConfig,
|
|
'Debugging/'+XMLBreakPointGroupsNode+'/');
|
|
FBreakPoints.LoadFromXMLConfig(XMLConfig,'Debugging/'+XMLBreakPointsNode+'/',
|
|
@Project1.ConvertFromLPIFilename,
|
|
@FBreakPointGroups.GetGroupByName);
|
|
FWatches.LoadFromXMLConfig(XMLConfig,'Debugging/'+XMLWatchesNode+'/');
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TDebugManager.SaveProjectSpecificInfo(XMLConfig: TXMLConfig;
|
|
Flags: TProjectWriteFlags);
|
|
|
|
Called when the main project is saved to an XMLConfig.
|
|
------------------------------------------------------------------------------}
|
|
procedure TDebugManager.SaveProjectSpecificInfo(XMLConfig: TXMLConfig;
|
|
Flags: TProjectWriteFlags);
|
|
begin
|
|
if not (pwfSkipSeparateSessionInfo in Flags) then
|
|
begin
|
|
FBreakPointGroups.SaveToXMLConfig(XMLConfig,
|
|
'Debugging/'+XMLBreakPointGroupsNode+'/', pwfCompatibilityMode in Flags);
|
|
FBreakPoints.SaveToXMLConfig(XMLConfig,'Debugging/'+XMLBreakPointsNode+'/',
|
|
pwfCompatibilityMode in Flags, @Project1.ConvertToLPIFilename);
|
|
FWatches.SaveToXMLConfig(XMLConfig,'Debugging/'+XMLWatchesNode+'/', pwfCompatibilityMode in Flags);
|
|
end;
|
|
if not (pwfSkipProjectInfo in Flags) then
|
|
begin
|
|
// exceptions are not part of the project info (#0015256)
|
|
FExceptions.SaveToXMLConfig(XMLConfig,'Debugging/'+XMLExceptionsNode+'/', pwfCompatibilityMode in Flags);
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.DoRestoreDebuggerMarks(AnUnitInfo: TUnitInfo);
|
|
var
|
|
ASrcEdit: TSourceEditor;
|
|
i: Integer;
|
|
CurBreakPoint: TIDEBreakPoint;
|
|
SrcFilename: String;
|
|
begin
|
|
if (AnUnitInfo.OpenEditorInfoCount = 0) or Destroying then exit;
|
|
ASrcEdit := TSourceEditor(AnUnitInfo.OpenEditorInfo[0].EditorComponent);
|
|
// set breakpoints for this unit
|
|
SrcFilename:=AnUnitInfo.Filename;
|
|
for i := 0 to FBreakpoints.Count-1 do
|
|
begin
|
|
CurBreakPoint := FBreakpoints[i];
|
|
if CompareFileNames(CurBreakPoint.Source, SrcFilename) = 0 then
|
|
CreateSourceMarkForBreakPoint(CurBreakPoint, ASrcEdit);
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.CreateSourceMarkForBreakPoint(
|
|
const ABreakpoint: TIDEBreakPoint; ASrcEdit: TSourceEditor);
|
|
var
|
|
ManagedBreakPoint: TManagedBreakPoint;
|
|
NewSrcMark: TSourceMark;
|
|
begin
|
|
if not (ABreakpoint is TManagedBreakPoint) then
|
|
RaiseGDBException('TDebugManager.CreateSourceMarkForBreakPoint');
|
|
ManagedBreakPoint:=TManagedBreakPoint(ABreakpoint);
|
|
|
|
if (ManagedBreakPoint.SourceMark<>nil) or Destroying then exit;
|
|
if ASrcEdit=nil then
|
|
GetSourceEditorForBreakPoint(ManagedBreakPoint,ASrcEdit);
|
|
if ASrcEdit=nil then exit;
|
|
NewSrcMark:=TSourceMark.Create(ASrcEdit, nil);
|
|
ManagedBreakPoint.SourceMark:=NewSrcMark;
|
|
SourceEditorMarks.Add(NewSrcMark);
|
|
end;
|
|
|
|
procedure TDebugManager.GetSourceEditorForBreakPoint(
|
|
const ABreakpoint: TIDEBreakPoint; var ASrcEdit: TSourceEditor);
|
|
var
|
|
Filename: String;
|
|
begin
|
|
Filename:=ABreakpoint.Source;
|
|
if Filename<>'' then
|
|
ASrcEdit:=SourceEditorManager.SourceEditorIntfWithFilename(ABreakpoint.Source)
|
|
else
|
|
ASrcEdit:=nil;
|
|
end;
|
|
|
|
procedure TDebugManager.CreateDebugDialog(Sender: TObject; aFormName: string;
|
|
var AForm: TCustomForm; DoDisableAutoSizing: boolean);
|
|
|
|
function ItIs(Prefix: string): boolean;
|
|
begin
|
|
Result:=SysUtils.CompareText(copy(aFormName,1,length(Prefix)),Prefix)=0;
|
|
end;
|
|
|
|
var
|
|
DlgType: TDebugDialogType;
|
|
begin
|
|
for DlgType:=Low(TDebugDialogType) to High(TDebugDialogType) do
|
|
if ItIs(DebugDialogNames[DlgType]) then
|
|
begin
|
|
ViewDebugDialog(DlgType,false,false,DoDisableAutoSizing);
|
|
AForm:=FDialogs[DlgType];
|
|
exit;
|
|
end;
|
|
raise Exception.Create('TDebugManager.CreateDebugDialog invalid FormName "'+aFormName+'"');
|
|
end;
|
|
|
|
procedure TDebugManager.ClearDebugOutputLog;
|
|
begin
|
|
if FDialogs[ddtOutput] <> nil then
|
|
TDbgOutputForm(FDialogs[ddtOutput]).Clear
|
|
else if fHiddenDebugOutputLog<>nil then
|
|
fHiddenDebugOutputLog.Clear;
|
|
end;
|
|
|
|
procedure TDebugManager.ClearDebugEventsLog;
|
|
begin
|
|
FEventLogManager.ClearDebugEventsLog;
|
|
end;
|
|
|
|
procedure TDebugManager.DoBackendConverterChanged;
|
|
begin
|
|
ValueConverterSelectorList.Lock;
|
|
ProjectValueConverterSelectorList := nil;
|
|
|
|
try
|
|
ValueConverterSelectorList.Clear;
|
|
if (Project1 <> nil) and (Project1.UseBackendConverterFromProject) then
|
|
Project1.BackendConverterConfig.AssignEnabledTo(ValueConverterSelectorList, True);
|
|
if (Project1 = nil) or (Project1.UseBackendConverterFromIDE) then
|
|
DebuggerOptions.BackendConverterConfig.AssignEnabledTo(ValueConverterSelectorList, True);
|
|
|
|
if (Project1 <> nil) then
|
|
ProjectValueConverterSelectorList := Project1.BackendConverterConfig;
|
|
finally
|
|
ValueConverterSelectorList.Unlock;
|
|
end;
|
|
end;
|
|
|
|
function TDebugManager.RequiredCompilerOpts(ATargetCPU, ATargetOS: String
|
|
): TDebugCompilerRequirements;
|
|
begin
|
|
if DebuggerClass = nil then
|
|
exit([]);
|
|
Result := DebuggerClass.RequiredCompilerOpts(ATargetCPU, ATargetOS);
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Debugger routines
|
|
//-----------------------------------------------------------------------------
|
|
|
|
procedure TDebugManager.FreeDebugger;
|
|
var
|
|
dbg: TDebuggerIntf;
|
|
begin
|
|
dbg := FDebugger;
|
|
SetDebugger(nil);
|
|
dbg.Release;
|
|
FManagerStates := [];
|
|
FIsInitializingDebugger:= False;
|
|
|
|
if MainIDE.ToolStatus = itDebugger
|
|
then MainIDE.ToolStatus := itNone;
|
|
end;
|
|
|
|
procedure TDebugManager.ResetDebugger;
|
|
var
|
|
OldState: TDBGState;
|
|
begin
|
|
OldState := State;
|
|
if OldState = dsNone then Exit;
|
|
|
|
FDebugger.BeginReset;
|
|
EndDebugging;
|
|
// OnDebuggerChangeState(FDebugger, OldState);
|
|
// InitDebugger;
|
|
end;
|
|
|
|
function TDebugManager.GetLaunchPathAndExe(out LaunchingCmdLine,
|
|
LaunchingApplication, LaunchingParams: String; PromptOnError: Boolean
|
|
): Boolean;
|
|
|
|
procedure ClearPathAndExe;
|
|
begin
|
|
LaunchingApplication := '';
|
|
LaunchingParams := '';
|
|
LaunchingCmdLine := '';
|
|
end;
|
|
|
|
var
|
|
NewDebuggerClass: TDebuggerClass;
|
|
begin
|
|
Result := False;
|
|
NewDebuggerClass := GetDebuggerClass;
|
|
LaunchingCmdLine := BuildBoss.GetRunCommandLine;
|
|
SplitCmdLine(LaunchingCmdLine, LaunchingApplication, LaunchingParams);
|
|
|
|
if NewDebuggerClass.RequiresLocalExecutable then
|
|
LaunchingApplication := ResolveLocationForLaunchApplication(LaunchingApplication);
|
|
|
|
(* TODO: workaround for http://bugs.freepascal.org/view.php?id=21834
|
|
Se Debugger.RequiresLocalExecutable
|
|
*)
|
|
if NewDebuggerClass.RequiresLocalExecutable then begin
|
|
|
|
if BuildBoss.GetProjectUsesAppBundle then
|
|
begin
|
|
// it is Application Bundle (darwin only)
|
|
|
|
if not DirectoryExistsUTF8(LaunchingApplication) then
|
|
begin
|
|
if not PromptOnError then
|
|
ClearPathAndExe
|
|
else begin
|
|
BuildBoss.WriteDebug_RunCommandLine;
|
|
if IDEMessageDialog(lisLaunchingApplicationInvalid,
|
|
Format(lisTheLaunchingApplicationBundleDoesNotExists,
|
|
[LaunchingApplication, LineEnding, LineEnding, LineEnding+LineEnding]),
|
|
mtError, [mbYes, mbNo, mbCancel]) = mrYes then
|
|
begin
|
|
if not BuildBoss.CreateProjectApplicationBundle then Exit;
|
|
end
|
|
else
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
if (NewDebuggerClass = TProcessDebugger) and (LaunchingApplication <> '') then
|
|
begin // use executable path inside Application Bundle (darwin only)
|
|
LaunchingApplication := LaunchingApplication + '/Contents/MacOS/' +
|
|
ExtractFileNameOnly(LaunchingApplication);
|
|
end;
|
|
end
|
|
else
|
|
if not FileIsExecutable(LaunchingApplication)
|
|
then begin
|
|
BuildBoss.WriteDebug_RunCommandLine;
|
|
if not PromptOnError then
|
|
ClearPathAndExe
|
|
else begin
|
|
IDEMessageDialog(lisLaunchingApplicationInvalid,
|
|
Format(lisTheLaunchingApplicationDoesNotExistsOrIsNotExecuta,
|
|
[LaunchingApplication, LineEnding, LineEnding+LineEnding]),
|
|
mtError, [mbOK]);
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
// check if debugger needs an Exe and the exe is there
|
|
if (NewDebuggerClass.NeedsExePath)
|
|
and not FileIsExecutable(Project1.GetParsedDebuggerFilename)
|
|
then begin
|
|
if not PromptOnError then
|
|
ClearPathAndExe
|
|
else begin
|
|
debugln(['Info: (lazarus) [TDebugManager.GetLaunchPathAndExe] Project1.DebuggerFilename="',Project1.DebuggerFilename,'"']);
|
|
IDEMessageDialog(lisDebuggerInvalid,
|
|
Format(lisTheDebuggerDoesNotExistsOrIsNotExecutableSeeEnviro,
|
|
[Project1.DebuggerFilename, LineEnding, LineEnding+LineEnding]),
|
|
mtError,[mbOK]);
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
end; // if NewDebuggerClass.RequiresLocalExecutable then
|
|
Result := True;
|
|
end;
|
|
|
|
function TDebugManager.InitDebugger(AFlags: TDbgInitFlags): Boolean;
|
|
var
|
|
LaunchingCmdLine, LaunchingApplication, LaunchingParams: String;
|
|
NewWorkingDir: String;
|
|
NewDebuggerClass: TDebuggerClass;
|
|
DbgCfg: TDebuggerPropertiesConfig;
|
|
begin
|
|
{$ifdef VerboseDebugger}
|
|
DebugLn('[TDebugManager.DoInitDebugger] A');
|
|
{$endif}
|
|
|
|
Result := False;
|
|
if FIsInitializingDebugger then begin
|
|
DebugLn('[TDebugManager.DoInitDebugger] *** Re-Entered');
|
|
exit;
|
|
end;
|
|
|
|
if Destroying or (Project1 = nil) then Exit;
|
|
if not(difInitForAttach in AFlags) then begin
|
|
if (Project1.MainUnitID < 0) then Exit;
|
|
if not GetLaunchPathAndExe(LaunchingCmdLine, LaunchingApplication, LaunchingParams) then
|
|
exit;
|
|
end
|
|
else
|
|
GetLaunchPathAndExe(LaunchingCmdLine, LaunchingApplication, LaunchingParams, False);
|
|
|
|
FUnitInfoProvider.Clear;
|
|
FIsInitializingDebugger:= True;
|
|
try
|
|
NewDebuggerClass := GetDebuggerClass;
|
|
|
|
if (dmsDebuggerObjectBroken in FManagerStates)
|
|
then begin
|
|
FreeDebugger;
|
|
FIsInitializingDebugger:= True; // been reset by FreeDebuger
|
|
end;
|
|
|
|
// check if debugger is already created with the right type
|
|
if (FDebugger <> nil)
|
|
and (not (FDebugger.ClassType = NewDebuggerClass) // exact class match
|
|
or (FDebugger.ExternalDebugger <> Project1.GetParsedDebuggerFilename)
|
|
or (FDebugger.State in [dsError])
|
|
)
|
|
then begin
|
|
// the current debugger is the wrong type -> free it
|
|
FreeDebugger;
|
|
FIsInitializingDebugger:= True; // been reset by FreeDebuger
|
|
end;
|
|
|
|
// create debugger object
|
|
if FDebugger = nil
|
|
then SetDebugger(NewDebuggerClass.Create(Project1.GetParsedDebuggerFilename));
|
|
|
|
if FDebugger = nil
|
|
then begin
|
|
// something went wrong
|
|
Exit;
|
|
end;
|
|
|
|
DbgCfg := Project1.CurrentDebuggerPropertiesConfig;
|
|
|
|
if (DbgCfg <> nil) and (DbgCfg.DebuggerProperties <> nil) then
|
|
FDebugger.GetProperties.Assign(DbgCfg.DebuggerProperties);
|
|
|
|
ClearDebugOutputLog;
|
|
if EnvironmentDebugOpts.DebuggerEventLogClearOnRun then
|
|
ClearDebugEventsLog;
|
|
|
|
//ensure to unset all evemts in SetDebugger()
|
|
FDebugger.OnBreakPointHit := @DebuggerBreakPointHit;
|
|
FDebugger.OnBeforeState := @DebuggerBeforeChangeState;
|
|
FDebugger.OnState := @DebuggerChangeState;
|
|
FDebugger.OnCurrent := @DebuggerCurrentLine;
|
|
FDebugger.OnDbgOutput := @DebuggerOutput;
|
|
FDebugger.OnDbgEvent := @FEventLogManager.DebuggerEvent;
|
|
FDebugger.OnException := @DebuggerException;
|
|
FDebugger.OnConsoleOutput := @DebuggerConsoleOutput;
|
|
FDebugger.OnFeedback := @DebuggerFeedback;
|
|
FDebugger.OnIdle := @DebuggerIdle;
|
|
FDebugger.EventLogHandler := FEventLogManager;
|
|
|
|
FEventLogManager.TargetWidth := FDebugger.TargetWidth div 8;
|
|
|
|
if FDebugger.State = dsNone
|
|
then begin
|
|
Include(FManagerStates,dmsInitializingDebuggerObject);
|
|
Exclude(FManagerStates,dmsInitializingDebuggerObjectFailed);
|
|
// The following commands may call ProcessMessages, and FDebugger can be nil after each
|
|
FDebugger.Init;
|
|
Exclude(FManagerStates,dmsInitializingDebuggerObject);
|
|
if (FDebugger = nil) or (dmsInitializingDebuggerObjectFailed in FManagerStates)
|
|
then begin
|
|
FreeDebugger;
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
if not(difInitForAttach in AFlags) then begin
|
|
Project1.RunParameterOptions.AssignEnvironmentTo(FDebugger.Environment);
|
|
NewWorkingDir := BuildBoss.GetRunWorkingDir;
|
|
if NewDebuggerClass.RequiresLocalExecutable and (* TODO: workaround for http://bugs.freepascal.org/view.php?id=21834 *)
|
|
(NewWorkingDir<>'') and (not DirectoryExistsUTF8(NewWorkingDir))
|
|
then begin
|
|
IDEMessageDialog(lisUnableToRun,
|
|
Format(lisTheWorkingDirectoryDoesNotExistPleaseCheckTheWorki,
|
|
[NewWorkingDir, LineEnding]),
|
|
mtError,[mbCancel]);
|
|
exit;
|
|
end;
|
|
|
|
// The following commands may call ProcessMessages, and FDebugger can be nil after each
|
|
|
|
if (FDebugger <> nil) then begin
|
|
if NewDebuggerClass.RequiresLocalExecutable then
|
|
FDebugger.WorkingDir:=AppendPathDelim(NewWorkingDir)
|
|
else
|
|
if Project1.RunParameterOptions.GetActiveMode <> nil then
|
|
FDebugger.WorkingDir:=Project1.RunParameterOptions.GetActiveMode.WorkingDirectory;
|
|
end;
|
|
// set filename after workingdir
|
|
if FDebugger <> nil
|
|
then FDebugger.FileName := LaunchingApplication;
|
|
if FDebugger <> nil
|
|
then FDebugger.Arguments := LaunchingParams;
|
|
if FDebugger <> nil
|
|
then FDebugger.ShowConsole := not Project1.CompilerOptions.Win32GraphicApp;
|
|
end
|
|
else begin
|
|
// attach
|
|
if (FDebugger <> nil) and (LaunchingApplication <> '')
|
|
then FDebugger.FileName := LaunchingApplication;
|
|
end;
|
|
|
|
// check if debugging needs restart
|
|
// mwe: can this still happen ?
|
|
if (FDebugger = nil) or (dmsDebuggerObjectBroken in FManagerStates)
|
|
then begin
|
|
FreeDebugger;
|
|
Exit;
|
|
end;
|
|
|
|
Result := True;
|
|
finally
|
|
// Since ProcessMessages has been called, debugger may have been reseted, even during initialization...
|
|
if not FIsInitializingDebugger
|
|
then begin
|
|
Result := False;
|
|
ResetDebugger;
|
|
end;
|
|
FIsInitializingDebugger:= False;
|
|
end;
|
|
{$ifdef VerboseDebugger}
|
|
DebugLn('[TDebugManager.DoInitDebugger] END');
|
|
{$endif}
|
|
end;
|
|
|
|
function TDebugManager.DoSetBreakkPointWarnIfNoDebugger: boolean;
|
|
var
|
|
DbgClass: TDebuggerClass;
|
|
begin
|
|
DbgClass := Project1.CurrentDebuggerClass;
|
|
if (DbgClass=nil)
|
|
or (DbgClass.NeedsExePath
|
|
and (not FileIsExecutableCached(Project1.GetParsedDebuggerFilename)))
|
|
then begin
|
|
if IDEQuestionDialog(lisDbgMangNoDebuggerSpecified,
|
|
Format(lisDbgMangThereIsNoDebuggerSpecifiedSettingBreakpointsHaveNo,[LineEnding]),
|
|
mtWarning, [mrCancel, mrIgnore, lisDbgMangSetTheBreakpointAnyway]) <> mrIgnore
|
|
then
|
|
exit(false);
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
// still part of main, should go here when processdebugger is finished
|
|
//
|
|
//function TDebugManager.DoRunProject: TModalResult;
|
|
|
|
function TDebugManager.DoPauseProject: TModalResult;
|
|
begin
|
|
Result := mrCancel;
|
|
if (MainIDE.ToolStatus <> itDebugger)
|
|
or (FDebugger = nil) or Destroying
|
|
then Exit;
|
|
FAutoContinueTimer.Enabled := False;
|
|
FDebugger.Pause;
|
|
Result := mrOk;
|
|
end;
|
|
|
|
function TDebugManager.DoShowExecutionPoint: TModalResult;
|
|
begin
|
|
Result := mrCancel;
|
|
if (MainIDE.ToolStatus <> itDebugger)
|
|
or (FDebugger = nil) or Destroying
|
|
then Exit;
|
|
|
|
DebuggerCurrentLine(FDebugger, FCurrentLocation);
|
|
Result := mrOk;
|
|
end;
|
|
|
|
function TDebugManager.DoStepIntoProject: TModalResult;
|
|
begin
|
|
if (MainIDE.DoInitProjectRun <> mrOK)
|
|
or (MainIDE.ToolStatus <> itDebugger)
|
|
or (FDebugger = nil) or Destroying
|
|
then begin
|
|
Result := mrAbort;
|
|
Exit;
|
|
end;
|
|
|
|
FStepping:=True;
|
|
FAsmStepping := False;
|
|
FDebugger.StepInto;
|
|
Result := mrOk;
|
|
end;
|
|
|
|
function TDebugManager.DoStepOverProject: TModalResult;
|
|
begin
|
|
if (MainIDE.DoInitProjectRun <> mrOK)
|
|
or (MainIDE.ToolStatus <> itDebugger)
|
|
or (FDebugger = nil) or Destroying
|
|
then begin
|
|
Result := mrAbort;
|
|
Exit;
|
|
end;
|
|
|
|
FStepping:=True;
|
|
FAsmStepping := False;
|
|
FDebugger.StepOver;
|
|
Result := mrOk;
|
|
end;
|
|
|
|
function TDebugManager.DoStepIntoInstrProject: TModalResult;
|
|
begin
|
|
if (MainIDE.DoInitProjectRun <> mrOK)
|
|
or (MainIDE.ToolStatus <> itDebugger)
|
|
or (FDebugger = nil) or Destroying
|
|
then begin
|
|
Result := mrAbort;
|
|
Exit;
|
|
end;
|
|
|
|
FStepping:=True;
|
|
FAsmStepping := True;
|
|
FDebugger.StepIntoInstr;
|
|
Result := mrOk;
|
|
// Todo: move to DebuggerChangeState (requires the last run-command-type to be avail)
|
|
ViewDebugDialog(ddtAssembler);
|
|
end;
|
|
|
|
function TDebugManager.DoStepOverInstrProject: TModalResult;
|
|
begin
|
|
if (MainIDE.DoInitProjectRun <> mrOK)
|
|
or (MainIDE.ToolStatus <> itDebugger)
|
|
or (FDebugger = nil) or Destroying
|
|
then begin
|
|
Result := mrAbort;
|
|
Exit;
|
|
end;
|
|
|
|
FStepping:=True;
|
|
FAsmStepping := True;
|
|
FDebugger.StepOverInstr;
|
|
Result := mrOk;
|
|
// Todo: move to DebuggerChangeState (requires the last run-command-type to be avail)
|
|
ViewDebugDialog(ddtAssembler);
|
|
end;
|
|
|
|
function TDebugManager.DoStepOutProject: TModalResult;
|
|
begin
|
|
if (FDebugger = nil) or not(dcStepOut in FDebugger.Commands)
|
|
then begin
|
|
Result := mrAbort;
|
|
Exit;
|
|
end;
|
|
|
|
if (MainIDE.DoInitProjectRun <> mrOK)
|
|
or (MainIDE.ToolStatus <> itDebugger)
|
|
or (FDebugger = nil) or Destroying
|
|
then begin
|
|
Result := mrAbort;
|
|
Exit;
|
|
end;
|
|
|
|
FStepping:=True;
|
|
FAsmStepping := False;
|
|
FDebugger.StepOut;
|
|
Result := mrOk;
|
|
end;
|
|
|
|
function TDebugManager.DoStopProject: TModalResult;
|
|
begin
|
|
Result := mrCancel;
|
|
|
|
FRunTimer.Enabled:=false;
|
|
Exclude(FManagerStates,dmsWaitForRun);
|
|
Exclude(FManagerStates,dmsWaitForAttach);
|
|
FAsmStepping := False;
|
|
|
|
SourceEditorManager.ClearExecutionLines;
|
|
if (MainIDE.ToolStatus=itDebugger) and (FDebugger<>nil) and (not Destroying)
|
|
then begin
|
|
FDebugger.Stop;
|
|
end;
|
|
if (dmsDebuggerObjectBroken in FManagerStates) then begin
|
|
if (MainIDE.ToolStatus=itDebugger) then
|
|
MainIDE.ToolStatus:=itNone;
|
|
end;
|
|
|
|
FUnitInfoProvider.Clear; // Maybe keep locations? But clear "not found"/"not loadable" flags?
|
|
Result := mrOk;
|
|
end;
|
|
|
|
procedure TDebugManager.DoToggleCallStack;
|
|
begin
|
|
ViewDebugDialog(ddtCallStack);
|
|
end;
|
|
|
|
procedure TDebugManager.DoSendConsoleInput(AText: String);
|
|
begin
|
|
if FDebugger <> nil then
|
|
FDebugger.SendConsoleInput(AText);
|
|
end;
|
|
|
|
procedure TDebugManager.ProcessCommand(Command: word; var Handled: boolean);
|
|
var
|
|
AvailCommands: TDBGCommands;
|
|
CanRun: Boolean;
|
|
begin
|
|
//debugln('TDebugManager.ProcessCommand ',dbgs(Command));
|
|
Handled := True;
|
|
|
|
CanRun := CanRunDebugger;
|
|
AvailCommands := GetAvailableCommands;
|
|
case Command of
|
|
ecPause: DoPauseProject;
|
|
ecStepInto: if CanRun and (dcStepInto in AvailCommands) then DoStepIntoProject;
|
|
ecStepOver: if CanRun and (dcStepOver in AvailCommands) then DoStepOverProject;
|
|
ecStepIntoInstr: if CanRun and (dcStepIntoInstr in AvailCommands) then DoStepIntoInstrProject;
|
|
ecStepOverInstr: if CanRun and (dcStepOverInstr in AvailCommands) then DoStepOverInstrProject;
|
|
ecStepIntoContext: if CanRun then begin
|
|
if (FDialogs[ddtAssembler] <> nil) and FDialogs[ddtAssembler].Active
|
|
then begin
|
|
if dcStepIntoInstr in AvailCommands then DoStepIntoInstrProject;
|
|
end
|
|
else begin
|
|
if dcStepInto in AvailCommands then DoStepIntoProject;
|
|
end;
|
|
end;
|
|
ecStepOverContext: if CanRun then begin
|
|
if (FDialogs[ddtAssembler] <> nil) and FDialogs[ddtAssembler].Active
|
|
then begin
|
|
if dcStepOverInstr in AvailCommands then DoStepOverInstrProject;
|
|
end
|
|
else begin
|
|
if dcStepOver in AvailCommands then DoStepOverProject;
|
|
end;
|
|
end;
|
|
ecStepOut: if CanRun and (dcStepOut in AvailCommands) then DoStepOutProject;
|
|
ecStepToCursor: if CanRun and (dcStepTo in AvailCommands) then DoStepToCursor;
|
|
ecRunToCursor: if CanRun and (dcRunTo in AvailCommands) then DoRunToCursor;
|
|
ecStopProgram: DoStopProject;
|
|
ecResetDebugger: ResetDebugger;
|
|
ecToggleCallStack: DoToggleCallStack;
|
|
ecEvaluate: ViewDebugDialog(ddtEvaluate);
|
|
ecInspect: ViewDebugDialog(ddtInspect);
|
|
ecToggleWatches: ViewDebugDialog(ddtWatches);
|
|
ecToggleBreakPoints: ViewDebugDialog(ddtBreakpoints);
|
|
ecToggleDebuggerOut: ViewDebugDialog(ddtOutput);
|
|
ecToggleDebugEvents: ViewDebugDialog(ddtEvents);
|
|
ecToggleLocals: ViewDebugDialog(ddtLocals);
|
|
ecViewPseudoTerminal: ViewDebugDialog(ddtPseudoTerminal);
|
|
ecViewThreads: ViewDebugDialog(ddtThreads);
|
|
ecViewHistory: ViewDebugDialog(ddtHistory);
|
|
else
|
|
Handled := False;
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.LockCommandProcessing;
|
|
begin
|
|
if assigned(FDebugger)
|
|
then FDebugger.LockCommandProcessing;
|
|
end;
|
|
|
|
procedure TDebugManager.UnLockCommandProcessing;
|
|
begin
|
|
if assigned(FDebugger)
|
|
then FDebugger.UnLockCommandProcessing;
|
|
end;
|
|
|
|
function TDebugManager.StartDebugging: TModalResult;
|
|
begin
|
|
{$ifdef VerboseDebugger}
|
|
DebugLn('TDebugManager.StartDebugging A ',DbgS(FDebugger<>nil),' Destroying=',DbgS(Destroying));
|
|
{$endif}
|
|
Result:=mrCancel;
|
|
if Destroying then exit;
|
|
if FManagerStates*[dmsWaitForRun, dmsWaitForAttach] <> [] then exit;
|
|
if (FDebugger <> nil) then
|
|
begin
|
|
// dmsRunning + dsPause => evaluating stack+watches after run
|
|
if (dmsRunning in FManagerStates) then begin
|
|
if (FDebugger.State = dsPause) then
|
|
FDebugger.Run;
|
|
|
|
exit;
|
|
end;
|
|
|
|
{$ifdef VerboseDebugger}
|
|
DebugLn('TDebugManager.StartDebugging B ',FDebugger.ClassName);
|
|
{$endif}
|
|
// check if debugging needs restart
|
|
if (dmsDebuggerObjectBroken in FManagerStates)
|
|
and (MainIDE.ToolStatus=itDebugger) then begin
|
|
MainIDE.ToolStatus:=itNone;
|
|
Result:=mrCancel;
|
|
exit;
|
|
end;
|
|
Include(FManagerStates,dmsWaitForRun);
|
|
FRunTimer.Enabled:=true;
|
|
Result:=mrOk;
|
|
end;
|
|
end;
|
|
|
|
function TDebugManager.RunDebugger: TModalResult;
|
|
begin
|
|
{$ifdef VerboseDebugger}
|
|
DebugLn('TDebugManager.RunDebugger A ',DbgS(FDebugger<>nil),' Destroying=',DbgS(Destroying));
|
|
{$endif}
|
|
Result:=mrCancel;
|
|
if Destroying then exit;
|
|
Exclude(FManagerStates,dmsWaitForRun);
|
|
if dmsRunning in FManagerStates then exit;
|
|
if MainIDE.ToolStatus<>itDebugger then exit;
|
|
if (FDebugger <> nil) then
|
|
begin
|
|
{$ifdef VerboseDebugger}
|
|
DebugLn('TDebugManager.RunDebugger B ',FDebugger.ClassName);
|
|
{$endif}
|
|
// check if debugging needs restart
|
|
if (dmsDebuggerObjectBroken in FManagerStates)
|
|
and (MainIDE.ToolStatus=itDebugger) then begin
|
|
MainIDE.ToolStatus:=itNone;
|
|
Result:=mrCancel;
|
|
exit;
|
|
end;
|
|
Include(FManagerStates,dmsRunning);
|
|
FStepping:=False;
|
|
FAsmStepping := False;
|
|
try
|
|
FDebugger.Run;
|
|
finally
|
|
Exclude(FManagerStates,dmsRunning);
|
|
end;
|
|
Result:=mrOk;
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.EndDebugging;
|
|
begin
|
|
FRunTimer.Enabled:=false;
|
|
Exclude(FManagerStates,dmsWaitForRun);
|
|
Exclude(FManagerStates,dmsWaitForAttach);
|
|
if FDebugger <> nil then FDebugger.Done;
|
|
// if not already freed
|
|
FreeDebugger;
|
|
end;
|
|
|
|
procedure TDebugManager.Attach(AProcessID: String);
|
|
begin
|
|
if Destroying then exit;
|
|
if FManagerStates*[dmsWaitForRun, dmsWaitForAttach, dmsRunning] <> [] then exit;
|
|
if (FDebugger <> nil) then
|
|
begin
|
|
// check if debugging needs restart
|
|
if (dmsDebuggerObjectBroken in FManagerStates)
|
|
and (MainIDE.ToolStatus=itDebugger) then begin
|
|
MainIDE.ToolStatus:=itNone;
|
|
exit;
|
|
end;
|
|
FAttachToID := AProcessID;
|
|
Include(FManagerStates,dmsWaitForAttach);
|
|
FRunTimer.Enabled:=true;
|
|
end;
|
|
end;
|
|
|
|
function TDebugManager.FillProcessList(AList: TRunningProcessInfoList): boolean;
|
|
begin
|
|
Result := (not Destroying)
|
|
and (MainIDE.ToolStatus in [itDebugger, itNone])
|
|
and (FDebugger <> nil)
|
|
and FDebugger.GetProcessList(AList);
|
|
end;
|
|
|
|
procedure TDebugManager.Detach;
|
|
begin
|
|
FRunTimer.Enabled:=false; Exclude(FManagerStates,dmsWaitForRun);
|
|
Exclude(FManagerStates,dmsWaitForAttach);
|
|
|
|
SourceEditorManager.ClearExecutionLines;
|
|
if (MainIDE.ToolStatus=itDebugger) and (FDebugger<>nil) and (not Destroying)
|
|
then begin
|
|
FDebugger.Detach;
|
|
end;
|
|
if (dmsDebuggerObjectBroken in FManagerStates) then begin
|
|
if (MainIDE.ToolStatus=itDebugger) then
|
|
MainIDE.ToolStatus:=itNone;
|
|
end;
|
|
|
|
FUnitInfoProvider.Clear; // Maybe keep locations? But clear "not found"/"not loadable" flags?
|
|
end;
|
|
|
|
function TDebugManager.Evaluate(const AExpression: String;
|
|
ACallback: TDBGEvaluateResultCallback; EvalFlags: TWatcheEvaluateFlags): Boolean;
|
|
begin
|
|
Result := (not Destroying)
|
|
and (MainIDE.ToolStatus = itDebugger)
|
|
and (FDebugger <> nil)
|
|
and (dcEvaluate in FDebugger.Commands)
|
|
and FDebugger.Evaluate(AExpression, ACallback, EvalFlags);
|
|
end;
|
|
|
|
function TDebugManager.Modify(const AExpression, ANewValue: String): Boolean;
|
|
begin
|
|
Result := (not Destroying)
|
|
and (MainIDE.ToolStatus = itDebugger)
|
|
and (FDebugger <> nil)
|
|
and (dcModify in FDebugger.Commands)
|
|
and FDebugger.Modify(AExpression, ANewValue);
|
|
end;
|
|
|
|
procedure TDebugManager.EvaluateModify(const AExpression: String; AWatch: TWatch
|
|
);
|
|
begin
|
|
if Destroying then Exit;
|
|
ViewDebugDialog(ddtEvaluate, True, True, False, False);
|
|
if FDialogs[ddtEvaluate] <> nil then
|
|
TEvaluateDlg(FDialogs[ddtEvaluate]).Execute(AExpression, AWatch);
|
|
end;
|
|
|
|
procedure TDebugManager.Inspect(const AExpression: String; AWatch: TWatch);
|
|
begin
|
|
if Destroying then Exit;
|
|
ViewDebugDialog(ddtInspect, True, True, False, False);
|
|
if FDialogs[ddtInspect] <> nil then
|
|
begin
|
|
TIDEInspectDlg(FDialogs[ddtInspect]).Execute(AExpression, AWatch);
|
|
end;
|
|
end;
|
|
|
|
function TDebugManager.DoCreateBreakPoint(const AFilename: string;
|
|
ALine: integer; WarnIfNoDebugger: boolean): TModalResult;
|
|
var
|
|
ABrkPoint: TIDEBreakPoint;
|
|
begin
|
|
Result := DoCreateBreakPoint(AFilename, ALine, WarnIfNoDebugger, ABrkPoint);
|
|
end;
|
|
|
|
function TDebugManager.DoCreateBreakPoint(const AFilename: string;
|
|
ALine: integer; WarnIfNoDebugger: boolean; out ABrkPoint: TIDEBreakPoint;
|
|
AnUpdating: Boolean): TModalResult;
|
|
begin
|
|
ABrkPoint := nil;
|
|
if WarnIfNoDebugger and not DoSetBreakkPointWarnIfNoDebugger then
|
|
exit(mrCancel);
|
|
|
|
ABrkPoint := FBreakPoints.Add(AFilename, ALine, AnUpdating);
|
|
Result := mrOK;
|
|
end;
|
|
|
|
function TDebugManager.DoCreateBreakPoint(const AnAddr: TDBGPtr;
|
|
WarnIfNoDebugger: boolean; out ABrkPoint: TIDEBreakPoint; AnUpdating: Boolean
|
|
): TModalResult;
|
|
begin
|
|
ABrkPoint := nil;
|
|
if WarnIfNoDebugger and not DoSetBreakkPointWarnIfNoDebugger then
|
|
exit(mrCancel);
|
|
|
|
ABrkPoint := FBreakPoints.Add(AnAddr, AnUpdating);
|
|
Result := mrOK;
|
|
end;
|
|
|
|
function TDebugManager.DoDeleteBreakPoint(const AFilename: string;
|
|
ALine: integer): TModalResult;
|
|
var
|
|
OldBreakPoint: TIDEBreakPoint;
|
|
begin
|
|
LockCommandProcessing;
|
|
try
|
|
OldBreakPoint:=FBreakPoints.Find(AFilename,ALine);
|
|
if OldBreakPoint=nil then exit(mrOk);
|
|
ReleaseRefAndNil(OldBreakPoint);
|
|
Project1.Modified:=true;
|
|
Result := mrOK;
|
|
finally
|
|
UnLockCommandProcessing;
|
|
end;
|
|
end;
|
|
|
|
function TDebugManager.DoDeleteBreakPointAtMark(const ASourceMarkObj: TObject
|
|
): TModalResult;
|
|
var
|
|
OldBreakPoint: TIDEBreakPoint;
|
|
ASourceMark: TSourceMark absolute ASourceMarkObj;
|
|
begin
|
|
LockCommandProcessing;
|
|
try
|
|
// consistency check
|
|
if (ASourceMarkObj=nil) or (not (ASourceMarkObj is TSourceMark))
|
|
or (not ASourceMark.IsBreakPoint)
|
|
or (ASourceMark.Data=nil) or (not (ASourceMark.Data is TIDEBreakPoint)) then
|
|
RaiseGDBException('TDebugManager.DoDeleteBreakPointAtMark');
|
|
|
|
{$ifdef VerboseDebugger}
|
|
DebugLn('TDebugManager.DoDeleteBreakPointAtMark A ',ASourceMark.GetFilename,
|
|
' ',IntToStr(ASourceMark.Line));
|
|
{$endif}
|
|
OldBreakPoint:=TIDEBreakPoint(ASourceMark.Data);
|
|
{$ifdef VerboseDebugger}
|
|
DebugLn('TDebugManager.DoDeleteBreakPointAtMark B ',OldBreakPoint.ClassName,
|
|
' ',OldBreakPoint.Source,' ',IntToStr(OldBreakPoint.Line));
|
|
{$endif}
|
|
ReleaseRefAndNil(OldBreakPoint);
|
|
Project1.Modified:=true;
|
|
Result := mrOK;
|
|
finally
|
|
UnLockCommandProcessing;
|
|
end;
|
|
end;
|
|
|
|
function TDebugManager.DoStepToCursor: TModalResult;
|
|
var
|
|
ActiveSrcEdit: TSourceEditorInterface;
|
|
ActiveUnitInfo: TUnitInfo;
|
|
UnitFilename: string;
|
|
begin
|
|
{$ifdef VerboseDebugger}
|
|
DebugLn('TDebugManager.DoStepToCursor A');
|
|
{$endif}
|
|
if (FDebugger = nil) or not(dcStepTo in FDebugger.Commands)
|
|
then begin
|
|
Result := mrAbort;
|
|
Exit;
|
|
end;
|
|
|
|
if (MainIDE.DoInitProjectRun <> mrOK)
|
|
or (MainIDE.ToolStatus <> itDebugger)
|
|
or (FDebugger = nil) or Destroying
|
|
then begin
|
|
Result := mrAbort;
|
|
Exit;
|
|
end;
|
|
{$ifdef VerboseDebugger}
|
|
DebugLn('TDebugManager.DoStepToCursor B');
|
|
{$endif}
|
|
|
|
Result := mrCancel;
|
|
|
|
MainIDE.GetCurrentUnitInfo(ActiveSrcEdit,ActiveUnitInfo);
|
|
if (ActiveSrcEdit=nil) or (ActiveUnitInfo=nil)
|
|
then begin
|
|
IDEMessageDialog(lisRunToFailed, lisPleaseOpenAUnitBeforeRun, mtError,
|
|
[mbCancel]);
|
|
Result := mrCancel;
|
|
Exit;
|
|
end;
|
|
|
|
if not ActiveUnitInfo.Source.IsVirtual
|
|
then UnitFilename:=ActiveUnitInfo.Filename
|
|
else UnitFilename:=BuildBoss.GetTestUnitFilename(ActiveUnitInfo);
|
|
|
|
{$ifdef VerboseDebugger}
|
|
DebugLn('TDebugManager.DoStepToCursor C');
|
|
{$endif}
|
|
FDebugger.StepTo(ExtractFilename(UnitFilename),
|
|
TSourceEditor(ActiveSrcEdit).EditorComponent.CaretY);
|
|
|
|
{$ifdef VerboseDebugger}
|
|
DebugLn('TDebugManager.DoStepToCursor D');
|
|
{$endif}
|
|
Result := mrOK;
|
|
end;
|
|
|
|
function TDebugManager.DoRunToCursor: TModalResult;
|
|
var
|
|
ActiveSrcEdit: TSourceEditorInterface;
|
|
ActiveUnitInfo: TUnitInfo;
|
|
UnitFilename: string;
|
|
begin
|
|
if (MainIDE.DoInitProjectRun <> mrOK)
|
|
or (MainIDE.ToolStatus <> itDebugger)
|
|
or (FDebugger = nil) or Destroying
|
|
then begin
|
|
Result := mrAbort;
|
|
Exit;
|
|
end;
|
|
|
|
MainIDE.GetCurrentUnitInfo(ActiveSrcEdit,ActiveUnitInfo);
|
|
if (ActiveSrcEdit=nil) or (ActiveUnitInfo=nil)
|
|
then begin
|
|
IDEMessageDialog(lisRunToFailed, lisPleaseOpenAUnitBeforeRun, mtError,
|
|
[mbCancel]);
|
|
Result := mrCancel;
|
|
Exit;
|
|
end;
|
|
|
|
if not ActiveUnitInfo.Source.IsVirtual
|
|
then UnitFilename:=ActiveUnitInfo.Filename
|
|
else UnitFilename:=BuildBoss.GetTestUnitFilename(ActiveUnitInfo);
|
|
|
|
FStepping:=True;
|
|
FAsmStepping := False;
|
|
FDebugger.RunTo(ExtractFilename(UnitFilename),
|
|
TSourceEditor(ActiveSrcEdit).EditorComponent.CaretY);
|
|
|
|
Result := mrOK;
|
|
end;
|
|
|
|
function TDebugManager.GetState: TDBGState;
|
|
begin
|
|
if FDebugger = nil
|
|
then Result := dsNone
|
|
else Result := FDebugger.State;
|
|
end;
|
|
|
|
function TDebugManager.GetCommands: TDBGCommands;
|
|
begin
|
|
if FDebugger = nil
|
|
then Result := []
|
|
else Result := FDebugger.Commands;
|
|
end;
|
|
|
|
function TDebugManager.GetPseudoTerminal: TPseudoTerminal;
|
|
begin
|
|
if FDebugger = nil then
|
|
Result := nil
|
|
else
|
|
Result := FDebugger.PseudoTerminal;
|
|
end;
|
|
|
|
function TDebugManager.GetDebuggerClass: TDebuggerClass;
|
|
begin
|
|
Result := Project1.CurrentDebuggerClass;
|
|
if Result = nil then
|
|
Result := TProcessDebugger;
|
|
end;
|
|
|
|
{$IFDEF DBG_WITH_DEBUGGER_DEBUG}
|
|
function TDebugManager.GetDebugger: TDebuggerIntf;
|
|
begin
|
|
Result := FDebugger;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
function TDebugManager.GetCurrentDebuggerClass: TDebuggerClass;
|
|
begin
|
|
Result := GetDebuggerClass;
|
|
end;
|
|
|
|
function TDebugManager.AttachDebugger: TModalResult;
|
|
begin
|
|
Result:=mrCancel;
|
|
if Destroying then exit;
|
|
Exclude(FManagerStates,dmsWaitForAttach);
|
|
if dmsRunning in FManagerStates then exit;
|
|
if MainIDE.ToolStatus<>itDebugger then exit;
|
|
if (FDebugger <> nil) then
|
|
begin
|
|
// check if debugging needs restart
|
|
if (dmsDebuggerObjectBroken in FManagerStates)
|
|
and (MainIDE.ToolStatus=itDebugger) then begin
|
|
MainIDE.ToolStatus:=itNone;
|
|
Result:=mrCancel;
|
|
exit;
|
|
end;
|
|
Include(FManagerStates,dmsRunning);
|
|
FStepping:=False;
|
|
FAsmStepping := False;
|
|
try
|
|
FDebugger.Attach(FAttachToID);
|
|
finally
|
|
Exclude(FManagerStates,dmsRunning);
|
|
end;
|
|
Result:=mrOk;
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugManager.CallWatchesInvalidatedHandlers(Sender: TObject);
|
|
begin
|
|
FWatchesInvalidatedNotificationList.CallNotifyEvents(Self);
|
|
end;
|
|
|
|
function TDebugManager.GetAvailableCommands: TDBGCommands;
|
|
var
|
|
CurState: TDBGState;
|
|
begin
|
|
Result := [];
|
|
if FDebugger <> nil then begin
|
|
Result := FDebugger.Commands;
|
|
CurState := FDebugger.State;
|
|
if CurState = dsError then begin
|
|
CurState := dsStop;
|
|
Result := GetDebuggerClass.SupportedCommandsFor(dsStop);
|
|
end;
|
|
end
|
|
else begin
|
|
Result := GetDebuggerClass.SupportedCommandsFor(dsStop);
|
|
CurState := dsStop;
|
|
end;
|
|
end;
|
|
|
|
function TDebugManager.CanRunDebugger: Boolean;
|
|
var
|
|
DebuggerIsValid: Boolean;
|
|
SrcEdit: TSourceEditorInterface;
|
|
AnUnitInfo: TUnitInfo;
|
|
begin
|
|
DebuggerIsValid:=(MainIDE.ToolStatus in [itNone, itDebugger]);
|
|
// For 'run' and 'step' bypass 'idle', so we can set the filename later
|
|
Result:=false;
|
|
if (Project1<>nil) and DebuggerIsValid then begin
|
|
MainIDE.GetCurrentUnitInfo(SrcEdit,AnUnitInfo);
|
|
Result:=( (AnUnitInfo<>nil) and (AnUnitInfo.RunFileIfActive) ) or
|
|
( ((Project1.CompilerOptions.ExecutableType=cetProgram) or
|
|
((Project1.RunParameterOptions.GetActiveMode<>nil) and (Project1.RunParameterOptions.GetActiveMode.HostApplicationFilename<>'')))
|
|
and (pfRunnable in Project1.Flags)
|
|
);
|
|
end;
|
|
end;
|
|
|
|
function TDebugManager.ShowBreakPointProperties(const ABreakpoint: TIDEBreakPoint): TModalresult;
|
|
begin
|
|
Result := TBreakPropertyDlg.Create(Self, ABreakpoint).ShowModal;
|
|
end;
|
|
|
|
function TDebugManager.ShowWatchProperties(const AWatch: TCurrentWatch; AWatchExpression: String = ''): TModalresult;
|
|
begin
|
|
Result := TWatchPropertyDlg.Create(Self, AWatch, AWatchExpression).ShowModal;
|
|
end;
|
|
|
|
procedure TDebugManager.SetDebugger(const ADebugger: TDebuggerIntf);
|
|
begin
|
|
if FDebugger = ADebugger then Exit;
|
|
|
|
FRunTimer.Enabled:=false;
|
|
Exclude(FManagerStates,dmsWaitForRun);
|
|
Exclude(FManagerStates,dmsWaitForAttach);
|
|
|
|
if FDebugger <> nil then begin
|
|
FDebugger.OnBreakPointHit := nil;
|
|
FDebugger.OnBeforeState := nil;
|
|
FDebugger.OnState := nil;
|
|
FDebugger.OnCurrent := nil;
|
|
FDebugger.OnDbgOutput := nil;
|
|
FDebugger.OnDbgEvent := nil;
|
|
FDebugger.OnException := nil;
|
|
FDebugger.OnConsoleOutput := nil;
|
|
FDebugger.OnFeedback := nil;
|
|
FDebugger.OnIdle := nil;
|
|
FDebugger.Exceptions := nil;
|
|
FDebugger.EventLogHandler := nil;
|
|
end;
|
|
|
|
FDebugger := ADebugger;
|
|
if FDebugger = nil
|
|
then begin
|
|
TManagedBreakpoints(FBreakpoints).Master := nil;
|
|
FWatches.Supplier := nil;
|
|
FThreads.Supplier := nil;
|
|
FLocals.Supplier := nil;
|
|
FLineInfo.Master := nil;
|
|
FCallStack.Supplier := nil;
|
|
FDisassembler.Master := nil;
|
|
FSignals.Master := nil;
|
|
FRegisters.Supplier := nil;
|
|
FSnapshots.Debugger := nil;
|
|
end
|
|
else begin
|
|
TManagedBreakpoints(FBreakpoints).Master := FDebugger.BreakPoints;
|
|
FWatches.Supplier := FDebugger.WatchSupplier;
|
|
FThreads.Supplier := FDebugger.Threads;
|
|
FThreads.UnitInfoProvider := FUnitInfoProvider;
|
|
FLocals.Supplier := FDebugger.Locals;
|
|
FLineInfo.Master := FDebugger.LineInfo;
|
|
FCallStack.Supplier := FDebugger.CallStack;
|
|
FCallStack.UnitInfoProvider := FUnitInfoProvider;
|
|
FDisassembler.Master := FDebugger.Disassembler;
|
|
FSignals.Master := FDebugger.Signals;
|
|
FRegisters.Supplier := FDebugger.Registers;
|
|
FSnapshots.Debugger := FDebugger;
|
|
|
|
FDebugger.Exceptions := FExceptions;
|
|
end;
|
|
end;
|
|
|
|
initialization
|
|
DBG_LOCATION_INFO := DebugLogger.FindOrRegisterLogGroup('DBG_LOCATION_INFO' {$IFDEF DBG_LOCATION_INFO} , True {$ENDIF} );
|
|
if DBG_LOCATION_INFO=nil then ;
|
|
|
|
end.
|
|
|
|
|