{ ---------------------------------------------- GDBMiServerDebugger.pp - Debugger class for gdbserver ---------------------------------------------- This unit contains the debugger class for the GDB/MI debugger through SSH. *************************************************************************** * * * 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 . You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. * * * *************************************************************************** } unit GDBMIServerDebugger; {$mode objfpc} {$H+} interface uses Classes, sysutils, UTF8Process, Process, LazFileUtils, MacroIntf, // DebuggerIntf DbgIntfDebuggerBase, // CmdLineDebuggerBase DebuggerPropertiesBase, LazDebuggerIntf, LazDebuggerIntfBaseTypes, // LazDebuggerGdbmi GDBMIDebugger, GDBMIMiscClasses, GdbmiStringConstants; type { TGDBMIServerDebugger } TGDBMIServerDebugger = class(TGDBMIDebuggerBase) private protected function CreateCommandInit: TGDBMIDebuggerCommandInitDebugger; override; function CreateCommandStartDebugging(AContinueCommand: TGDBMIDebuggerCommand): TGDBMIDebuggerCommandStartDebugging; override; procedure InterruptTarget; override; procedure ProcessLineWhileRunning(const ALine: String; AnInLogWarning: boolean; var AHandled, AForceStop: Boolean; var AStoppedParams: String; var AResult: TGDBMIExecResult); override; procedure StopInitProc; public InitProc: TProcessUTF8; destructor Destroy; override; function NeedReset: Boolean; override; class function CreateProperties: TDebuggerProperties; override; // Creates debuggerproperties class function Caption: String; override; class function RequiresLocalExecutable: Boolean; override; procedure Done; override; // Kills external debugger end; TInitExecMode = ( ieRun, // run and forget ieRunCloseOnStop // run, and keep the process until the debugger is stopped // when the debugger is stopped, terminate the process, if it's still running // todo: to be implemented! //ieRunWaitToExit // run and wait until the process finishes, before letting the debugger run "target remote" ); TDebugger_Target_Mode = ( dtTargetRemote, dtTargetExtendedRemote ); { TGDBMIServerGdbEventProperties } TGDBMIServerGdbEventProperties = class(TGDBMIDebuggerGdbEventPropertiesBase) private FAfterConnect: TXmlConfStringList; procedure SetAfterConnect(AValue: TXmlConfStringList); public constructor Create; override; destructor Destroy; override; procedure Assign(Source: TPersistent); override; published property AfterConnect: TXmlConfStringList read FAfterConnect write SetAfterConnect; property AfterInit; end; { TGDBMIServerDebuggerProperties } TGDBMIServerDebuggerProperties = class(TGDBMIDebuggerPropertiesBase) private FArchitecture: string; FDebugger_Remote_Hostname: string; FDebugger_Remote_Port: string; FDebugger_Remote_DownloadExe: boolean; FRemoteTimeout: integer; FSkipSettingLocalExeName: Boolean; FInitExec_RemoteTarget: string; FInitExec_Mode: TInitExecMode; FDebugger_Target_Mode : TDebugger_Target_Mode; function GetEventProperties: TGDBMIServerGdbEventProperties; procedure SetEventProperties(AValue: TGDBMIServerGdbEventProperties); protected procedure CreateEventProperties; override; public constructor Create; override; procedure Assign(Source: TPersistent); override; published property Debugger_Remote_Hostname: String read FDebugger_Remote_Hostname write FDebugger_Remote_Hostname; property Debugger_Remote_Port: String read FDebugger_Remote_Port write FDebugger_Remote_Port; property Debugger_Remote_DownloadExe: boolean read FDebugger_Remote_DownloadExe write FDebugger_Remote_DownloadExe; property Debugger_Target_Mode: TDebugger_Target_Mode read FDebugger_Target_Mode write FDebugger_Target_Mode default dtTargetRemote; property RemoteTimeout: integer read FRemoteTimeout write FRemoteTimeout default -1; property Architecture: string read FArchitecture write FArchitecture; property SkipSettingLocalExeName: Boolean read FSkipSettingLocalExeName write FSkipSettingLocalExeName default False; property InitExec_RemoteTarget: string read FInitExec_RemoteTarget write FInitExec_RemoteTarget; property InitExec_Mode: TInitExecMode read FInitExec_Mode write FInitExec_Mode default ieRun; published property Debugger_Startup_Options; {$IFDEF UNIX} property ConsoleTty; {$ENDIF} property MaxDisplayLengthForString; property MaxDisplayLengthForStaticArray; property MaxLocalsLengthForStaticArray; property TimeoutForEval; property WarnOnTimeOut; property WarnOnInternalError; property EncodeCurrentDirPath; property EncodeExeFileName; property EncodingForEnvironment; property EncodingForExeArgs; property EncodingForExeFileName; property EncodingForCurrentDirPath; property InternalStartBreak; property UseNoneMiRunCommands; property DisableLoadSymbolsForLibraries; property DisableForcedBreakpoint; //property WarnOnSetBreakpointError; property CaseSensitivity; property GdbValueMemLimit; property GdbLocalsValueMemLimit; property AssemblerStyle; property DisableStartupShell; property FixStackFrameForFpcAssert; property FixIncorrectStepOver; property InternalExceptionBreakPoints; property InternalExceptionBrkSetMethod; property EventProperties: TGDBMIServerGdbEventProperties read GetEventProperties write SetEventProperties; end; procedure Register; implementation type { TGDBMIServerDebuggerCommandInitDebugger } TGDBMIServerDebuggerCommandInitDebugger = class(TGDBMIDebuggerCommandInitDebugger) protected function DoExecute: Boolean; override; end; { TGDBMIServerDebuggerCommandStartDebugging } TGDBMIServerDebuggerCommandStartDebugging = class(TGDBMIDebuggerCommandStartDebugging) protected function GdbRunCommand: TGDBMIExecCommandType; override; procedure DetectTargetPid(InAttach: Boolean = False); override; function DoTargetDownload: boolean; override; function DoChangeFilename: Boolean; override; end; { TGDBMIServerGdbEventProperties } procedure TGDBMIServerGdbEventProperties.SetAfterConnect( AValue: TXmlConfStringList); begin FAfterConnect.Assign(AValue); end; procedure TGDBMIServerGdbEventProperties.Assign(Source: TPersistent); var aSource: TGDBMIServerGdbEventProperties; begin inherited Assign(Source); if Source is TGDBMIServerGdbEventProperties then begin aSource := TGDBMIServerGdbEventProperties(Source); FAfterConnect.Assign(aSource.FAfterConnect); end; end; constructor TGDBMIServerGdbEventProperties.Create; begin FAfterConnect := TXmlConfStringList.Create; inherited Create; end; destructor TGDBMIServerGdbEventProperties.Destroy; begin FAfterConnect.Free; inherited Destroy; end; { TGDBMIServerDebuggerCommandStartDebugging } function TGDBMIServerDebuggerCommandStartDebugging.GdbRunCommand: TGDBMIExecCommandType; begin Result := ectContinue; end; procedure TGDBMIServerDebuggerCommandStartDebugging.DetectTargetPid(InAttach: Boolean); begin // do nothing // prevent dsError in inherited end; function TGDBMIServerDebuggerCommandStartDebugging.DoTargetDownload: boolean; begin Result := True; if TGDBMIServerDebuggerProperties(DebuggerProperties).FDebugger_Remote_DownloadExe then begin // Called after -file-exec-and-symbols, so gdb knows what file to download // If call sequence is different, then supply binary file name below as parameter Result := ExecuteCommand('-target-download', [], [cfCheckError]); Result := Result and (DebuggerState <> dsError); end; end; function TGDBMIServerDebuggerCommandStartDebugging.DoChangeFilename: Boolean; begin Result := True; if not TGDBMIServerDebuggerProperties(DebuggerProperties).SkipSettingLocalExeName then Result := inherited DoChangeFilename; end; { TGDBMIServerDebuggerCommandInitDebugger } function TGDBMIServerDebuggerCommandInitDebugger.DoExecute: Boolean; var R: TGDBMIExecResult; t: Integer; s: String; ip : TProcessUTF8; ipsucc : Boolean; ipkeep : Boolean; iperr : string; srv : TGDBMIServerDebugger; begin Result := inherited DoExecute; if (not FSuccess) then exit; if not TGDBMIDebuggerBase(FTheDebugger).AsyncModeEnabled then begin SetDebuggerErrorState(GDBMiSNoAsyncMode); FSuccess := False; exit; end; s := Trim(TGDBMIServerDebuggerProperties(DebuggerProperties).InitExec_RemoteTarget); IDEMacros.SubstituteMacros(s); if s <> '' then begin iperr := ''; ip := TProcessUTF8.Create(nil); SplitCmdLineParams(s, ip.Parameters); ip.Executable := ip.Parameters[0]; ip.Parameters.Delete(0); ip.Options := [poNewConsole,poNewProcessGroup]; try ip.Execute; {if TGDBMIServerDebuggerProperties(DebuggerProperties).InitExec_Mode = ieRunWaitToExit then begin ip.WaitOnExit; iperr := Format(GDBMiSFailedInitProcWaitOnExit, [ip.ExitStatus, ip.ExitCode]); ipkeep := false; end else} ipkeep := TGDBMIServerDebuggerProperties(DebuggerProperties).InitExec_Mode = ieRunCloseOnStop; ipsucc := true; except on e: exception do begin iperr := e.Message; ipkeep := false; ipsucc := false; end; end; if not ipsucc then begin ip.Free; SetDebuggerErrorState(GDBMiSFailedInitProc, iperr); FSuccess := False; exit; end; if ipkeep then begin srv := TGDBMIServerDebugger(FTheDebugger); srv.StopInitProc; srv.InitProc := ip end else ip.Free; end; s := TGDBMIServerDebuggerProperties(DebuggerProperties).Architecture; if s <> '' then ExecuteCommand(Format('set architecture %s', [s]), R); t := TGDBMIServerDebuggerProperties(DebuggerProperties).RemoteTimeout; if t >= 0 then ExecuteCommand(Format('set remotetimeout %d', [t]), R); // TODO: Maybe should be done in CommandStart, But Filename, and Environment will be done before Start s := ''; if TGDBMIServerDebuggerProperties(DebuggerProperties).Debugger_Target_Mode = dtTargetExtendedRemote then s := 'extended-'; if TGDBMIServerDebuggerProperties(DebuggerProperties).Debugger_Remote_Port = '' then FSuccess := ExecuteCommand(Format('target %sremote %s', [s, TGDBMIServerDebuggerProperties(DebuggerProperties).FDebugger_Remote_Hostname ]), R) else FSuccess := ExecuteCommand(Format('target %sremote %s:%s', [s, TGDBMIServerDebuggerProperties(DebuggerProperties).FDebugger_Remote_Hostname, TGDBMIServerDebuggerProperties(DebuggerProperties).Debugger_Remote_Port ]), R); FSuccess := FSuccess and (r.State <> dsError); if (FSuccess = true) then ExecuteUserCommands(TGDBMIServerDebuggerProperties(DebuggerProperties).EventProperties.AfterConnect); end; { TGDBMIServerDebuggerProperties } function TGDBMIServerDebuggerProperties.GetEventProperties: TGDBMIServerGdbEventProperties; begin Result := TGDBMIServerGdbEventProperties(InternalEventProperties); end; procedure TGDBMIServerDebuggerProperties.SetEventProperties( AValue: TGDBMIServerGdbEventProperties); begin InternalEventProperties.Assign(AValue); end; procedure TGDBMIServerDebuggerProperties.CreateEventProperties; begin InternalEventProperties := TGDBMIServerGdbEventProperties.Create; end; constructor TGDBMIServerDebuggerProperties.Create; begin inherited Create; FDebugger_Remote_Hostname:= ''; FDebugger_Remote_Port:= '2345'; FDebugger_Remote_DownloadExe := False; FDebugger_Target_Mode := dtTargetRemote; FRemoteTimeout := -1; FArchitecture := ''; FSkipSettingLocalExeName := False; UseAsyncCommandMode := True; end; procedure TGDBMIServerDebuggerProperties.Assign(Source: TPersistent); begin inherited Assign(Source); if Source is TGDBMIServerDebuggerProperties then begin FDebugger_Remote_Hostname := TGDBMIServerDebuggerProperties(Source).FDebugger_Remote_Hostname; FDebugger_Remote_Port := TGDBMIServerDebuggerProperties(Source).FDebugger_Remote_Port; FDebugger_Remote_DownloadExe := TGDBMIServerDebuggerProperties(Source).FDebugger_Remote_DownloadExe; FDebugger_Target_Mode := TGDBMIServerDebuggerProperties(Source).FDebugger_Target_Mode; FRemoteTimeout := TGDBMIServerDebuggerProperties(Source).FRemoteTimeout; FArchitecture := TGDBMIServerDebuggerProperties(Source).FArchitecture; FSkipSettingLocalExeName := TGDBMIServerDebuggerProperties(Source).FSkipSettingLocalExeName; UseAsyncCommandMode := True; FInitExec_RemoteTarget := TGDBMIServerDebuggerProperties(Source).FInitExec_RemoteTarget; FInitExec_Mode := TGDBMIServerDebuggerProperties(Source).FInitExec_Mode; end; end; { TGDBMIServerDebugger } class function TGDBMIServerDebugger.Caption: String; begin Result := 'GNU remote debugger (gdbserver)'; end; function TGDBMIServerDebugger.CreateCommandInit: TGDBMIDebuggerCommandInitDebugger; begin Result := TGDBMIServerDebuggerCommandInitDebugger.Create(Self); end; function TGDBMIServerDebugger.CreateCommandStartDebugging( AContinueCommand: TGDBMIDebuggerCommand): TGDBMIDebuggerCommandStartDebugging; begin Result:= TGDBMIServerDebuggerCommandStartDebugging.Create(Self, AContinueCommand); end; procedure TGDBMIServerDebugger.InterruptTarget; begin if not( CurrentCmdIsAsync and (CurrentCommand <> nil) ) then begin exit; end; inherited InterruptTarget; end; procedure TGDBMIServerDebugger.ProcessLineWhileRunning(const ALine: String; AnInLogWarning: boolean; var AHandled, AForceStop: Boolean; var AStoppedParams: String; var AResult: TGDBMIExecResult); const LogDisconnect = 'remote connection closed'; var i: Integer; begin inherited ProcessLineWhileRunning(ALine, AnInLogWarning, AHandled, AForceStop, AStoppedParams, AResult); // If remote connection terminated then this debugging session is over i := 1; if (ALine[1] = '&') and (ALine[2] = '"') then i := 3; if (not AnInLogWarning) and (LowerCase(Copy(ALine, i, Length(LogDisconnect))) = LogDisconnect) then begin AHandled := True; AForceStop := True; AStoppedParams := ''; SetState(dsStop); end; end; procedure TGDBMIServerDebugger.StopInitProc; begin if not Assigned(InitProc) then Exit; if InitProc.Active then InitProc.Terminate(0); InitProc.Free; InitProc:=nil; end; destructor TGDBMIServerDebugger.Destroy; begin StopInitProc; inherited Destroy; end; function TGDBMIServerDebugger.NeedReset: Boolean; begin Result := True; end; class function TGDBMIServerDebugger.CreateProperties: TDebuggerProperties; begin Result := TGDBMIServerDebuggerProperties.Create; end; class function TGDBMIServerDebugger.RequiresLocalExecutable: Boolean; begin Result := False; end; procedure TGDBMIServerDebugger.Done; begin inherited Done; StopInitProc; end; procedure Register; begin //RegisterDebugger(TGDBMIServerDebugger); end; initialization RegisterDebugger(TGDBMIServerDebugger); end.