diff --git a/components/fpdebug/fpdbgavrclasses.pas b/components/fpdebug/fpdbgavrclasses.pas index c3d5eebb32..5d3e52e859 100644 --- a/components/fpdebug/fpdbgavrclasses.pas +++ b/components/fpdebug/fpdbgavrclasses.pas @@ -1,9 +1,7 @@ unit FpDbgAvrClasses; -// Connects to gdbserver instance and communicate over gdb's remote serial protocol (RSP) -// in principle possible to connect over any serial text capabile interface such as -// tcp/ip, RS-232, pipes etc. -// Support only tcp/ip connection for now. +{ This unit supports AVR specific code for fpdebug. + It communicates with a remote target via remote serial protocol } {$mode objfpc}{$H+} {$packrecords c} @@ -18,34 +16,20 @@ uses FpDbgLoader, DbgIntfBaseTypes, DbgIntfDebuggerBase, {$ifdef FORCE_LAZLOGGER_DUMMY} LazLoggerDummy {$else} LazLoggerBase {$endif}, Maps, - FpDbgRsp, FpDbgCommon, FpdMemoryTools, + FpDbgRsp, FpDbgRspClasses, FpDbgCommon, FpdMemoryTools, FpErrorMessages; const - // RSP commands - Rsp_Status = '?'; // Request break reason - returns either S or T - lastCPURegIndex = 31; // After this are SREG, SP and PC // Use as dwarf register indexes SREGindex = 32; // 1 byte SPindex = 33; // 2 bytes PCindex = 34; // 4 bytes - RegArrayLength = 35; // Special register names - nSREG = 'SReg'; nSP = 'SP'; nPC = 'PC'; - - // Byte level register indexes - SPLindex = 33; - SPHindex = 34; - PC0 = 35; - PC1 = 36; - PC2 = 37; - PC3 = 38; - RegArrayByteLength = 39; + nSREG = 'SReg'; type - { TAvrMemManager } TAvrMemManager = class(TFpDbgMemManager) @@ -54,42 +38,24 @@ type { TDbgAvrThread } - TDbgAvrThread = class(TDbgThread) + TDbgAvrThread = class(TDbgRspThread) private - FRegs: TInitializedRegisters; - FRegsUpdated: boolean; // regs read from target - //FRegsChanged: boolean; // write regs to target - FExceptionSignal: integer; - FIsPaused, FInternalPauseRequested, FIsInInternalPause: boolean; - FIsSteppingBreakPoint: boolean; - FDidResetInstructionPointer: Boolean; - FHasThreadState: boolean; - FUnwinder: TDbgStackUnwinder; + const + lastCPURegIndex = 31; // After this are SREG, SP and PC - function ReadDebugReg(ind: byte; out AVal: TDbgPtr): boolean; - function WriteDebugReg(ind: byte; AVal: PtrUInt): boolean; - - // Cache registers if reported in event - // Only cache if all reqisters are reported - // if not, request registers from target - procedure UpdateStatusFromEvent(event: TStatusEvent); - procedure InvalidateRegisters; - procedure RefreshRegisterCache; + // Byte level register indexes + SPLindex = 33; + SPHindex = 34; + PC0 = 35; + PC1 = 36; + PC2 = 37; + PC3 = 38; + RegArrayByteLength = 39; protected - function ReadThreadState: boolean; - - function RequestInternalPause: Boolean; - function CheckSignalForPostponing(AWaitedStatus: integer): Boolean; - procedure ResetPauseStates; + procedure RefreshRegisterCache; override; function GetStackUnwinder: TDbgStackUnwinder; override; public - constructor Create(const AProcess: TDbgProcess; const AID: Integer; const AHandle: THandle); override; - function ResetInstructionPointerAfterBreakpoint: boolean; override; - procedure ApplyWatchPoints(AWatchPointData: TFpWatchPointData); override; - function DetectHardwareWatchpoint: Pointer; override; - procedure BeforeContinue; override; procedure LoadRegisterValues; override; - function GetInstructionPointerRegisterValue: TDbgPtr; override; function GetStackBasePointerRegisterValue: TDbgPtr; override; procedure SetStackPointerRegisterValue(AValue: TDbgPtr); override; @@ -98,77 +64,18 @@ type { TDbgAvrProcess } - TDbgAvrProcess = class(TDbgProcess) - private - FStatus: integer; - FProcessStarted: boolean; - FIsTerminating: boolean; - // RSP communication - FConnection: TRspConnection; - FRemoteConfig: TRemoteConfig; - - procedure OnForkEvent(Sender : TObject); + TDbgAvrProcess = class(TDbgRspProcess) + private const + FNumRegisters = 35; // r0..r31, SREG, SP, PC protected - procedure InitializeLoaders; override; function CreateThread(AthreadIdentifier: THandle; out IsMainThread: boolean): TDbgThread; override; - function AnalyseDebugEvent(AThread: TDbgThread): TFPDEvent; override; - function CreateWatchPointData: TFpWatchPointData; override; public class function isSupported(target: TTargetDescriptor): boolean; override; constructor Create(const AFileName: string; AnOsClasses: TOSDbgClasses; AMemManager: TFpDbgMemManager; AProcessConfig: TDbgProcessConfig); override; destructor Destroy; override; - function StartInstance(AParams, AnEnvironment: TStrings; - AWorkingDirectory, AConsoleTty: string; AFlags: TStartInstanceFlags; - out AnError: TFpError): boolean; override; - - // FOR AVR target AAddress could be program or data (SRAM) memory (or EEPROM) - // Gnu tools masks data memory with $800000 - function ReadData(const AAdress: TDbgPtr; const ASize: Cardinal; out AData): Boolean; override; - function WriteData(const AAdress: TDbgPtr; const ASize: Cardinal; const AData): Boolean; override; - - procedure TerminateProcess; override; - function Pause: boolean; override; - function Detach(AProcess: TDbgProcess; AThread: TDbgThread): boolean; override; - - function Continue(AProcess: TDbgProcess; AThread: TDbgThread; SingleStep: boolean): boolean; override; - // Wait for -S or -T response from target, or if connection to target is lost - function WaitForDebugEvent(out ProcessIdentifier, ThreadIdentifier: THandle): boolean; override; - - // Insert/Delete break points on target - // TODO: if target doesn't support break points or have limited break points - // then debugger needs to manage insertion/deletion of break points in target memory - function InsertBreakInstructionCode(const ALocation: TDBGPtr; out OrigValue: Byte; AMakeTempRemoved: Boolean): Boolean; override; - function RemoveBreakInstructionCode(const ALocation: TDBGPtr; const OrigValue: Byte): Boolean; override; - - property RspConfig: TRemoteConfig read FRemoteConfig; end; - // Lets stick with points 4 for now - - { TFpRspWatchPointData } - - TRspBreakWatchPoint = record - Owner: Pointer; - Address: TDBGPtr; - Size: Cardinal; - Kind: TDBGWatchPointKind; - end; - - TFpRspWatchPointData = class(TFpWatchPointData) - private - FData: array of TRspBreakWatchPoint; - function BreakWatchPoint(AnIndex: Integer): TRspBreakWatchPoint; - function DataCount: integer; - function FindOwner(AnAddr: TDBGPtr): Pointer; - public - function AddOwnedWatchpoint(AnOwner: Pointer; AnAddr: TDBGPtr; ASize: Cardinal; AReadWrite: TDBGWatchPointKind): boolean; override; - function RemoveOwnedWatchpoint(AnOwner: Pointer): boolean; override; - property Data[AnIndex: Integer]: TRspBreakWatchPoint read BreakWatchPoint; - property Count: integer read DataCount; - end; - - implementation uses @@ -201,200 +108,27 @@ begin end; end; -{ TFpRspWatchPointData } - -function TFpRspWatchPointData.BreakWatchPoint(AnIndex: Integer - ): TRspBreakWatchPoint; -begin - if AnIndex < length(FData) then - result := FData[AnIndex]; -end; - -function TFpRspWatchPointData.DataCount: integer; -begin - result := length(FData); -end; - -function TFpRspWatchPointData.FindOwner(AnAddr: TDBGPtr): Pointer; -var - i: integer; -begin - i := 0; - while (i < Count) and not ((AnAddr >= Data[i].Address) and (AnAddr < Data[i].Address + Data[i].Size)) do - begin - inc(i); - end; - if i < Count then - Result := Data[i].Owner - else - Result := nil; -end; - -function TFpRspWatchPointData.AddOwnedWatchpoint(AnOwner: Pointer; - AnAddr: TDBGPtr; ASize: Cardinal; AReadWrite: TDBGWatchPointKind): boolean; -var - idx: integer; -begin - Result := false; - idx := length(FData); - SetLength(FData, idx+1); - FData[idx].Address := AnAddr; - FData[idx].Size := ASize; - FData[idx].Kind := AReadWrite; - FData[idx].Owner := AnOwner; - Changed := true; - Result := true; -end; - -function TFpRspWatchPointData.RemoveOwnedWatchpoint(AnOwner: Pointer): boolean; -var - i, j: integer; -begin - Result := False; - i := 0; - while (i < length(FData)) and (FData[i].Owner <> AnOwner) do - inc(i); - - if i < length(FData) then begin - for j := i+1 to length(FData)-1 do begin - FData[j-1] := FData[j]; - Changed := True; - Result := True; - end; - - SetLength(FData, length(FData)-1); - Changed := True; - Result := True; - end; -end; - { TDbgAvrThread } -procedure TDbgAvrProcess.OnForkEvent(Sender: TObject); -begin -end; - -function TDbgAvrThread.ReadDebugReg(ind: byte; out AVal: TDbgPtr): boolean; -begin - Result := false; - if TDbgAvrProcess(Process).FIsTerminating or (TDbgAvrProcess(Process).FStatus = SIGHUP) then - DebugLn(DBG_WARNINGS, 'TDbgRspThread.GetDebugReg called while FIsTerminating is set.') - else - begin - DebugLn(DBG_VERBOSE, ['TDbgRspThread.GetDebugReg requesting register: ',ind]); - RefreshRegisterCache; - if ind < length(FRegs) then - begin - AVal := FRegs[ind].Value; - Result := true; - end; - end; -end; - -function TDbgAvrThread.WriteDebugReg(ind: byte; AVal: PtrUInt): boolean; -begin - if TDbgAvrProcess(Process).FIsTerminating or (TDbgAvrProcess(Process).FStatus = SIGHUP) then - begin - DebugLn(DBG_WARNINGS, 'TDbgRspThread.WriteDebugReg called while FIsTerminating is set.'); - Result := false; - end - else - result := TDbgAvrProcess(Process).FConnection.WriteDebugReg(ind, AVal); -end; - -procedure TDbgAvrThread.UpdateStatusFromEvent(event: TStatusEvent); -var - i: integer; -begin - for i := 0 to high(FRegs) do - begin - FRegs[i].Initialized := event.registers[i].Initialized; - if event.registers[i].Initialized then - FRegs[i].Value := event.registers[i].Value; - end; -end; - -procedure TDbgAvrThread.InvalidateRegisters; -var - i: integer; -begin - FRegsUpdated := false; - for i := 0 to high(FRegs) do - FRegs[i].Initialized := false; -end; - procedure TDbgAvrThread.RefreshRegisterCache; var regs: TBytes; i: integer; begin - if not FRegsUpdated then + if not FRegs.Initialized then begin SetLength(regs, RegArrayByteLength); - FRegsUpdated := TDbgAvrProcess(Process).FConnection.ReadRegisters(regs[0], length(regs)); + FRegs.Initialized := TDbgRspProcess(Process).RspConnection.ReadRegisters(regs[0], length(regs)); for i := 0 to lastCPURegIndex do - begin - FRegs[i].Initialized := true; - FRegs[i].Value := regs[i]; - end; + FRegs.regs[i] := regs[i]; + + FRegs.regs[SREGindex] := regs[SREGindex]; // repack according to target endianness - FRegs[SPindex].Value := regs[SPLindex] + (regs[SPHindex] shl 8); - FRegs[SPHindex].Initialized := true; - FRegs[PCindex].Value := regs[PC0] + (regs[PC1] shl 8) + (regs[PC2] shl 16) + (regs[PC3] shl 24); - FRegs[PCindex].Initialized := true; + FRegs.regs[SPindex] := regs[SPLindex] + (regs[SPHindex] shl 8); + FRegs.regs[PCindex] := regs[PC0] + (regs[PC1] shl 8) + (regs[PC2] shl 16) + (regs[PC3] shl 24); end; end; -function TDbgAvrThread.ReadThreadState: boolean; -begin -// assert(FIsPaused, 'TDbgRspThread.ReadThreadState: FIsPaused'); - result := true; - if FHasThreadState then - exit; - FRegisterValueListValid := false; -end; - -function TDbgAvrThread.RequestInternalPause: Boolean; -begin - if TDbgAvrProcess(Process).FIsTerminating then - DebugLn(DBG_WARNINGS, 'TDbgRspThread.RequestInternalPause called while FIsTerminating is set.'); - - Result := False; - if FInternalPauseRequested or FIsPaused or (TDbgAvrProcess(Process).FStatus = SIGHUP) then - exit; - - DebugLn(DBG_VERBOSE, 'TDbgRspThread.RequestInternalPause requesting Ctrl-C.'); - - FInternalPauseRequested := true; - // Send SIGSTOP/break - TDbgAvrProcess(Process).FConnection.Break(); -end; - -function TDbgAvrThread.CheckSignalForPostponing(AWaitedStatus: integer): Boolean; -begin - Assert(not FIsPaused, 'Got WaitStatus while already paused'); - assert(FExceptionSignal = 0, 'TDbgLinuxThread.CheckSignalForPostponing: FExceptionSignal = 0'); - Result := FIsPaused; - DebugLn(DBG_VERBOSE and (Result), ['Warning: Thread already paused', ID]); - - DebugLn(DBG_VERBOSE, ['TDbgRspThread.CheckSignalForPostponing called with ', AWaitedStatus]); - - if Result then - exit; - - FIsPaused := True; - FIsInInternalPause := False; -end; - -procedure TDbgAvrThread.ResetPauseStates; -begin - FIsInInternalPause := False; - FIsPaused := False; - FExceptionSignal := 0; - FHasThreadState := False; - FDidResetInstructionPointer := False; -end; - function TDbgAvrThread.GetStackUnwinder: TDbgStackUnwinder; begin if FUnwinder = nil then @@ -402,88 +136,11 @@ begin Result := FUnwinder; end; -constructor TDbgAvrThread.Create(const AProcess: TDbgProcess; - const AID: Integer; const AHandle: THandle); -begin - inherited; - SetLength(FRegs, RegArrayLength); -end; - -function TDbgAvrThread.ResetInstructionPointerAfterBreakpoint: boolean; -begin - if not ReadThreadState then - exit(False); - result := true; - if FDidResetInstructionPointer then - exit; - FDidResetInstructionPointer := True; - - // This is not required for gdbserver - // since remote stub should ensure PC points to break address - //Dec(FRegs.cpuRegs[PCindex]); - //FRegsChanged:=true; -end; - -procedure TDbgAvrThread.ApplyWatchPoints(AWatchPointData: TFpWatchPointData); -var - i: integer; - addr: PtrUInt; - watchData: TRspBreakWatchPoint; - tmpData: TBytes; -begin - for i := 0 to TFpRspWatchPointData(AWatchPointData).Count-1 do - begin - watchData := TFpRspWatchPointData(AWatchPointData).Data[i]; - addr := watchData.Address; - SetLength(tmpData, watchData.Size); - if Process.ReadData(addr, watchData.Size, tmpData[0]) then - begin - if not TDbgAvrProcess(Process).FConnection.SetBreakWatchPoint(addr, watchData.Kind) then - DebugLn(DBG_WARNINGS, 'Failed to set watch point.', []); - end - else - DebugLn(DBG_WARNINGS, 'Failed to read memory.', []); - end; -end; - -function TDbgAvrThread.DetectHardwareWatchpoint: Pointer; -begin - if TDbgAvrProcess(Process).FConnection.LastStatusEvent.stopReason in [srAnyWatchPoint, srReadWatchPoint, srWriteWatchPoint] then - begin - Result := TFpRspWatchPointData(TDbgAvrProcess(Process).WatchPointData).FindOwner(TDbgAvrProcess(Process).FConnection.LastStatusEvent.watchPointAddress); - TDbgAvrProcess(Process).FConnection.ResetStatusEvent; - end - else - result := nil; -end; - -procedure TDbgAvrThread.BeforeContinue; -//var -// regs: TBytes; -begin - if not FIsPaused then - exit; - - inherited; - InvalidateRegisters; - - // TODO: currently nothing changes registers locally? - - // Update registers if changed locally - //if FRegsChanged then - //begin - // SetLength(regs, RegArrayByteLength); - // for i := 0 to lastCPURegIndex do - // regs[i] := - // FRegsChanged:=false; - //end; -end; - procedure TDbgAvrThread.LoadRegisterValues; var i: integer; begin - if TDbgAvrProcess(Process).FIsTerminating or (TDbgAvrProcess(Process).FStatus = SIGHUP) then + if TDbgRspProcess(Process).IsTerminating or (TDbgRspProcess(Process).Status = SIGHUP) then begin DebugLn(DBG_WARNINGS, 'TDbgRspThread.LoadRegisterValues called while FIsTerminating is set.'); exit; @@ -494,14 +151,14 @@ begin RefreshRegisterCache; - if FRegsUpdated then + if FRegs.Initialized then begin for i := 0 to lastCPURegIndex do - FRegisterValueList.DbgRegisterAutoCreate['r'+IntToStr(i)].SetValue(FRegs[i].Value, IntToStr(FRegs[i].Value),1, i); // confirm dwarf index + FRegisterValueList.DbgRegisterAutoCreate['r'+IntToStr(i)].SetValue(FRegs.regs[i], IntToStr(FRegs.regs[i]),1, i); // confirm dwarf index - FRegisterValueList.DbgRegisterAutoCreate[nSREG].SetValue(FRegs[SREGindex].Value, IntToStr(FRegs[SREGindex].Value),1,SREGindex); - FRegisterValueList.DbgRegisterAutoCreate[nSP].SetValue(FRegs[SPindex].Value, IntToStr(FRegs[SPindex].Value),2,SPindex); - FRegisterValueList.DbgRegisterAutoCreate[nPC].SetValue(FRegs[PCindex].Value, IntToStr(FRegs[PCindex].Value),4,PCindex); + FRegisterValueList.DbgRegisterAutoCreate[nSREG].SetValue(FRegs.regs[SREGindex], IntToStr(FRegs.regs[SREGindex]),1,SREGindex); + FRegisterValueList.DbgRegisterAutoCreate[nSP].SetValue(FRegs.regs[SPindex], IntToStr(FRegs.regs[SPindex]),2,SPindex); + FRegisterValueList.DbgRegisterAutoCreate[nPC].SetValue(FRegs.regs[PCindex], IntToStr(FRegs.regs[PCindex]),4,PCindex); FRegisterValueListValid := true; end else @@ -511,7 +168,7 @@ end; function TDbgAvrThread.GetInstructionPointerRegisterValue: TDbgPtr; begin Result := 0; - if TDbgAvrProcess(Process).FIsTerminating then + if TDbgRspProcess(Process).IsTerminating then begin DebugLn(DBG_WARNINGS, 'TDbgRspThread.GetInstructionPointerRegisterValue called while FIsTerminating is set.'); exit; @@ -529,7 +186,7 @@ var lval, hval: QWord; begin Result := 0; - if TDbgAvrProcess(Process).FIsTerminating then + if TDbgRspProcess(Process).IsTerminating then begin DebugLn(DBG_WARNINGS, 'TDbgAvrThread.GetStackBasePointerRegisterValue called while FIsTerminating is set.'); exit; @@ -552,7 +209,7 @@ end; function TDbgAvrThread.GetStackPointerRegisterValue: TDbgPtr; begin Result := 0; - if TDbgAvrProcess(Process).FIsTerminating then + if TDbgRspProcess(Process).IsTerminating then begin DebugLn(DBG_WARNINGS, 'TDbgRspThread.GetStackPointerRegisterValue called while FIsTerminating is set.'); exit; @@ -567,12 +224,6 @@ end; { TDbgAvrProcess } -procedure TDbgAvrProcess.InitializeLoaders; -begin - if LoaderList.Count = 0 then - TDbgImageLoader.Create(Name).AddToLoaderList(LoaderList); -end; - function TDbgAvrProcess.CreateThread(AthreadIdentifier: THandle; out IsMainThread: boolean): TDbgThread; begin IsMainThread:=False; @@ -585,449 +236,24 @@ begin result := nil; end; -function TDbgAvrProcess.CreateWatchPointData: TFpWatchPointData; -begin - DebugLn(DBG_VERBOSE, 'TDbgRspProcess.CreateWatchPointData called.'); - Result := TFpRspWatchPointData.Create; -end; - constructor TDbgAvrProcess.Create(const AFileName: string; AnOsClasses: TOSDbgClasses; AMemManager: TFpDbgMemManager; AProcessConfig: TDbgProcessConfig); begin - if Assigned(AProcessConfig) and (AProcessConfig is TRemoteConfig) then - begin - FRemoteConfig := TRemoteConfig.Create; - FRemoteConfig.Assign(AProcessConfig); - end; - + FRegArrayLength := FNumRegisters; inherited Create(AFileName, AnOsClasses, AMemManager, AProcessConfig); end; destructor TDbgAvrProcess.Destroy; begin - if Assigned(FConnection) then - FreeAndNil(FConnection); - if Assigned(FRemoteConfig) then - FreeAndNil(FRemoteConfig); inherited Destroy; end; -function TDbgAvrProcess.StartInstance(AParams, AnEnvironment: TStrings; - AWorkingDirectory, AConsoleTty: string; AFlags: TStartInstanceFlags; out - AnError: TFpError): boolean; -var - AnExecutabeFilename: string; -begin - Result := false; - AnExecutabeFilename:=ExcludeTrailingPathDelimiter(Name); - if DirectoryExists(AnExecutabeFilename) then - begin - DebugLn(DBG_WARNINGS, 'Can not debug %s, because it''s a directory',[AnExecutabeFilename]); - Exit; - end; - - if not FileExists(Name) then - begin - DebugLn(DBG_WARNINGS, 'Can not find %s.',[AnExecutabeFilename]); - Exit; - end; - - if not Assigned(FRemoteConfig) then - begin - DebugLn(DBG_WARNINGS, 'TDbgAvrProcess only supports remote debugging and requires a valid TRemoteConfig class'); - Exit; - end; - - try - FConnection := TRspConnection.Create(Name, self, self.FRemoteConfig); - FConnection.Connect; - try - FConnection.RegisterCacheSize := RegArrayLength; - FStatus := FConnection.Init; - Result := true; - except - on E: Exception do - begin - DebugLn(DBG_WARNINGS, Format('Failed to init remote connection. Errormessage: "%s".', [E.Message])); - end; - end; - except - on E: Exception do - begin - DebugLn(DBG_WARNINGS, Format('Failed to start remote connection. Errormessage: "%s".', [E.Message])); - end; - end; -end; - class function TDbgAvrProcess.isSupported(target: TTargetDescriptor): boolean; begin result := (target.OS = osEmbedded) and (target.machineType = mtAVR8); end; -function TDbgAvrProcess.ReadData(const AAdress: TDbgPtr; - const ASize: Cardinal; out AData): Boolean; -begin - if FIsTerminating or (TDbgAvrProcess(Process).FStatus = SIGHUP) then - begin - DebugLn(DBG_WARNINGS, 'TDbgRspProcess.ReadData called while FIsTerminating is set.'); - Result := false; - exit; - end; - - result := FConnection.ReadData(AAdress, ASize, AData); - if Result then - MaskBreakpointsInReadData(AAdress, ASize, AData); -end; - -function TDbgAvrProcess.WriteData(const AAdress: TDbgPtr; - const ASize: Cardinal; const AData): Boolean; -begin - if FIsTerminating or (TDbgAvrProcess(Process).FStatus = SIGHUP) then - begin - DebugLn(DBG_WARNINGS, 'TDbgRspProcess.WriteData called while FIsTerminating is set.'); - Result := false; - exit; - end; - - result := FConnection.WriteData(AAdress,AAdress, AData); -end; - -procedure TDbgAvrProcess.TerminateProcess; -begin - // Try to prevent access to the RSP socket after it has been closed - if not (FIsTerminating or (TDbgAvrProcess(Process).FStatus = SIGHUP)) then - begin - DebugLn(DBG_VERBOSE, 'Removing all break points'); - RemoveAllBreakPoints; - DebugLn(DBG_VERBOSE, 'Sending kill command from TDbgRspProcess.TerminateProcess'); - FConnection.Kill(); - FIsTerminating:=true; - end; -end; - -function TDbgAvrProcess.Pause: boolean; -begin - if FIsTerminating or (TDbgAvrProcess(Process).FStatus = SIGHUP) then - begin - DebugLn(DBG_WARNINGS, 'TDbgRspProcess.Pause called while FIsTerminating is set.'); - Result := false; - exit; - end; - - // Target should automatically respond with T or S reply after processing the break - result := true; - if not PauseRequested then - begin - FConnection.Break(); - PauseRequested := true; - DebugLn(DBG_VERBOSE, 'TDbgRspProcess.Pause called.'); - end - else - begin - result := true; - DebugLn(DBG_WARNINGS, 'TDbgRspProcess.Pause called while PauseRequested is set.'); - end; -end; - -function TDbgAvrProcess.Detach(AProcess: TDbgProcess; AThread: TDbgThread): boolean; -begin - RemoveAllBreakPoints; - DebugLn(DBG_VERBOSE, 'Sending detach command from TDbgRspProcess.Detach'); - Result := FConnection.Detach(); -end; - -function TDbgAvrProcess.Continue(AProcess: TDbgProcess; AThread: TDbgThread; SingleStep: boolean): boolean; -var - ThreadToContinue: TDbgAvrThread; - PC: word; - s: string; - tempState: integer; - initRegs: TInitializedRegisters; -begin - // Terminating process and all threads - if FIsTerminating or (FStatus = SIGHUP) then - begin - AThread.BeforeContinue; - TDbgAvrThread(AThread).InvalidateRegisters; - DebugLn(DBG_VERBOSE, 'TDbgRspProcess.Continue called while terminating.'); - - // The kill command should have been issued earlier (if using fpd), calling SendKill again will lead to an exception since the connection should be terminated already. - // FConnection.Kill(); - - TDbgAvrThread(AThread).ResetPauseStates; - if not FThreadMap.HasId(AThread.ID) then - AThread.Free; - exit; - end; - - if TDbgAvrThread(AThread).FIsPaused then // in case of deInternal, it may not be paused and can be ignored - AThread.NextIsSingleStep:=SingleStep; - - // check other threads if they need a singlestep - for TDbgThread(ThreadToContinue) in FThreadMap do - if (ThreadToContinue <> AThread) and ThreadToContinue.FIsPaused then - begin - PC := ThreadToContinue.GetInstructionPointerRegisterValue; - if HasInsertedBreakInstructionAtLocation(PC) then - begin - TempRemoveBreakInstructionCode(PC); - ThreadToContinue.BeforeContinue; - - while (ThreadToContinue.GetInstructionPointerRegisterValue = PC) do - begin - result := FConnection.SingleStep(); - TDbgAvrThread(ThreadToContinue).ResetPauseStates; // So BeforeContinue will not run again - ThreadToContinue.FIsPaused := True; - if result then - begin - tempState := FConnection.WaitForSignal(s, initRegs); // TODO: Update registers cache for this thread - if (tempState = SIGTRAP) then - break; // if the command jumps back an itself.... - end - else - begin - DebugLn(DBG_WARNINGS, ['Error single stepping other thread ', ThreadToContinue.ID]); - break; - end; - end; - end; - end; - - if TDbgAvrThread(AThread).FIsPaused and SingleStep then // in case of deInternal, it may not be paused and can be ignored - if HasInsertedBreakInstructionAtLocation(AThread.GetInstructionPointerRegisterValue) then - begin - TempRemoveBreakInstructionCode(AThread.GetInstructionPointerRegisterValue); - TDbgAvrThread(AThread).FIsSteppingBreakPoint := True; - AThread.BeforeContinue; - result := FConnection.SingleStep(); // TODO: pass thread ID once it is supported in FConnection - also signals not yet passed through - TDbgAvrThread(AThread).ResetPauseStates; - FStatus := 0; // need to call WaitForSignal to read state after single step - exit; - end; - - RestoreTempBreakInstructionCodes; - - ThreadsBeforeContinue; - - // start all other threads - for TDbgThread(ThreadToContinue) in FThreadMap do - begin - if (ThreadToContinue <> AThread) and (ThreadToContinue.FIsPaused) then - begin - FConnection.Continue(); - ThreadToContinue.ResetPauseStates; - end; - end; - - if TDbgAvrThread(AThread).FIsPaused then // in case of deInternal, it may not be paused and can be ignored - if not FIsTerminating then - begin - AThread.BeforeContinue; - if SingleStep then - result := FConnection.SingleStep() - else - result := FConnection.Continue(); - TDbgAvrThread(AThread).ResetPauseStates; - FStatus := 0; // should update status by calling WaitForSignal - end; - - if not FThreadMap.HasId(AThread.ID) then - AThread.Free; -end; - -function TDbgAvrProcess.WaitForDebugEvent(out ProcessIdentifier, ThreadIdentifier: THandle): boolean; -var - s: string; - initRegs: TInitializedRegisters; -begin - debugln(DBG_VERBOSE, ['Entering WaitForDebugEvent, FStatus = ', FStatus]); - // Currently only single process/thread - // TODO: Query and handle process/thread states of target - ThreadIdentifier := self.ThreadID; - ProcessIdentifier := Self.ProcessID; - - if FIsTerminating then - begin - DebugLn(DBG_VERBOSE, 'TDbgRspProcess.WaitForDebugEvent called while FIsTerminating is set.'); - FStatus := SIGKILL; - end - else - // Wait for S or T response from target, or if connection to target is lost - if FStatus = 0 then - repeat - try - FStatus := FConnection.WaitForSignal(s, initRegs); // TODO: Update registers cache - sleep(1); - except - FStatus := 0; - end; - until FStatus <> 0; - - if FStatus in [SIGINT, SIGTRAP] then - RestoreTempBreakInstructionCodes; - - result := FStatus <> 0; -end; - -function TDbgAvrProcess.InsertBreakInstructionCode(const ALocation: TDBGPtr; - out OrigValue: Byte; AMakeTempRemoved: Boolean): Boolean; -begin - if FIsTerminating or (FStatus = SIGHUP) then - DebugLn(DBG_WARNINGS, 'TDbgRspProcess.InsertBreakInstruction called while FIsTerminating is set.'); - - result := ReadData(ALocation, SizeOf(OrigValue), OrigValue); - if result then - begin - // HW break... - result := FConnection.SetBreakWatchPoint(ALocation, wkpExec); - if not result then - DebugLn(DBG_WARNINGS, 'Failed to set break point.', []); - end - else - DebugLn(DBG_WARNINGS, 'Failed to read memory.', []); -end; - -function TDbgAvrProcess.RemoveBreakInstructionCode(const ALocation: TDBGPtr; - const OrigValue: Byte): Boolean; -begin - if FIsTerminating or (FStatus = SIGHUP) then - begin - DebugLn(DBG_WARNINGS, 'TDbgRspProcess.RemoveBreakInstructionCode called while FIsTerminating is set'); - result := false; - end - else - result := FConnection.DeleteBreakWatchPoint(ALocation, wkpExec); -end; - -function TDbgAvrProcess.AnalyseDebugEvent(AThread: TDbgThread): TFPDEvent; -var - ThreadToPause: TDbgAvrThread; -begin - debugln(DBG_VERBOSE, ['Entering TDbgRspProcess.AnalyseDebugEvent, FStatus = ', FStatus, ' PauseRequested = ', PauseRequested]); - if FIsTerminating then begin - result := deExitProcess; - exit; - end; - - if AThread = nil then begin // should not happen... / just assume the most likely safe failbacks - result := deInternalContinue; - exit; - end; - - TDbgAvrThread(AThread).FExceptionSignal:=0; - TDbgAvrThread(AThread).FIsPaused := True; - TDbgAvrThread(AThread).UpdateStatusFromEvent(FConnection.lastStatusEvent); - - if FStatus in [SIGHUP, SIGKILL] then // not sure which signals is relevant here - begin - if AThread.ID=ProcessID then - begin - // Main thread stop -> application exited - SetExitCode(FStatus); - result := deExitProcess - end - else - begin - // Thread stopped, just continue - RemoveThread(AThread.Id); - result := deInternalContinue; - end; - end - else if FStatus <> 0 then - begin - TDbgAvrThread(AThread).ReadThreadState; - - if (not FProcessStarted) and (FStatus <> SIGTRAP) then - begin - // attached, should be SigStop, but may be out of order - debugln(DBG_VERBOSE, ['Attached ', FStatus]); - result := deCreateProcess; - FProcessStarted:=true; - end - else - case FStatus of - SIGTRAP: - begin - if not FProcessStarted then - begin - result := deCreateProcess; - FProcessStarted:=true; - DebugLn(DBG_VERBOSE, ['Creating process - SIGTRAP received for thread: ', AThread.ID]); - end - else if TDbgAvrThread(AThread).FInternalPauseRequested then - begin - DebugLn(DBG_VERBOSE, ['???Received late SigTrap for thread ', AThread.ID]); - result := deBreakpoint; - end - else - begin - DebugLn(DBG_VERBOSE, ['Received SigTrap for thread ', AThread.ID, - ' PauseRequest=', PauseRequested]); - result := deBreakpoint; - - if not TDbgAvrThread(AThread).FIsSteppingBreakPoint then - AThread.CheckAndResetInstructionPointerAfterBreakpoint; - end; - end; - SIGINT: - begin - if PauseRequested then - result := deBreakpoint - else - begin - ExceptionClass:='SIGINT'; - TDbgAvrThread(AThread).FExceptionSignal:=SIGINT; - result := deException; - end; - end; - SIGKILL: - begin - if FIsTerminating then - result := deInternalContinue - else - begin - ExceptionClass:='SIGKILL'; - TDbgAvrThread(AThread).FExceptionSignal:=SIGKILL; - result := deException; - end; - end; - SIGSTOP: - begin - // New thread (stopped within the new thread) - result := deInternalContinue; - end - else - begin - ExceptionClass:='Unknown exception code ' + inttostr(FStatus); - TDbgAvrThread(AThread).FExceptionSignal := FStatus; - result := deException; - end; - end; {case} - if result=deException then - ExceptionClass:='External: '+ExceptionClass; - end; - - debugln(DBG_VERBOSE, ['Leaving AnalyseDebugEvent, result = ', result]); - - TDbgAvrThread(AThread).FIsSteppingBreakPoint := False; - - if Result in [deException, deBreakpoint, deFinishedStep] then // deFinishedStep will not be set here - begin - // Signal all other threads to pause - for TDbgThread(ThreadToPause) in FThreadMap do - begin - if (ThreadToPause <> AThread) then - begin - DebugLn(DBG_VERBOSE and (ThreadToPause.FInternalPauseRequested), ['Re-Request Internal pause for ', ThreadToPause.ID]); - ThreadToPause.FInternalPauseRequested:=false; - if not ThreadToPause.RequestInternalPause then // will fail, if already paused - break; - end; - end; - end; -end; - initialization DBG_VERBOSE := DebugLogger.FindOrRegisterLogGroup('DBG_VERBOSE' {$IFDEF DBG_VERBOSE} , True {$ENDIF} ); DBG_WARNINGS := DebugLogger.FindOrRegisterLogGroup('DBG_WARNINGS' {$IFDEF DBG_WARNINGS} , True {$ENDIF} ); diff --git a/components/fpdebug/fpdbgrsp.pas b/components/fpdebug/fpdbgrsp.pas index 8e7cf41391..3dffe97633 100644 --- a/components/fpdebug/fpdbgrsp.pas +++ b/components/fpdebug/fpdbgrsp.pas @@ -69,12 +69,6 @@ type property AfterUploadMonitorCmds: TStringList read FAfterUploadMonitorCmds write FAfterUploadMonitorCmds; end; - TInitializedRegister = record - Initialized: boolean; - Value: qword; // sized to handle largest register, should truncate as required to smaller registers - end; - TInitializedRegisters = array of TInitializedRegister; - TStopReason = (srNone, srSWBreakPoint, srHWBreakPoint, srWriteWatchPoint, srReadWatchPoint, srAnyWatchPoint); TStatusEvent = record @@ -84,7 +78,6 @@ type threadID: integer; stopReason: TStopReason; watchPointAddress: qword; // contains address which triggered watch point - registers: TInitializedRegisters; end; { TRspConnection } @@ -99,7 +92,6 @@ type // Catch exceptions and store as socket errors FSockErr: boolean; FConfig: TRemoteConfig; - procedure SetRegisterCacheSize(sz: cardinal); function WaitForData(timeout_ms: integer): integer; overload; // Wrappers to catch exceptions and set SockErr @@ -126,7 +118,7 @@ type constructor Create(AFileName: string; AOwner: TDbgProcess; AConfig: TRemoteConfig); Overload; destructor Destroy; override; // Wait for async signal - blocking - function WaitForSignal(out msg: string; out registers: TInitializedRegisters): integer; + function WaitForSignal(out msg: string): integer; procedure ResetStatusEvent; procedure Break(); @@ -154,7 +146,6 @@ type function Init: integer; property State: integer read FState; - property RegisterCacheSize: cardinal write SetRegisterCacheSize; property lastStatusEvent: TStatusEvent read FStatusEvent; property SockErr: boolean read FSockErr; end; @@ -208,11 +199,6 @@ begin end; end; -procedure TRspConnection.SetRegisterCacheSize(sz: cardinal); -begin - SetLength(FStatusEvent.registers, sz); -end; - procedure TRspConnection.ResetStatusEvent; var i: integer; @@ -225,11 +211,6 @@ begin threadID := 0; stopReason := srNone; watchPointAddress := 0; - for i := low(registers) to high(registers) do - begin - registers[i].Initialized := false; - registers[i].Value := 0; - end; end; end; @@ -589,8 +570,7 @@ begin DoneCriticalSection(fCS); end; -function TRspConnection.WaitForSignal(out msg: string; out - registers: TInitializedRegisters): integer; +function TRspConnection.WaitForSignal(out msg: string): integer; var res: boolean; startIndex, colonIndex, semicolonIndex, i: integer; @@ -599,7 +579,6 @@ var begin result := 0; res := false; - SetLength(registers, 0); EnterCriticalSection(fCS); try @@ -719,23 +698,6 @@ begin else DebugLn(DBG_WARNINGS, 'Stop reason "thread" with no thread data'); end; - else // catch valid hex numbers - will be register info - begin - // check if part1 is a number, this should then be a register index - if HexToIntLittleEndian(part1, tmp) and HexToIntLittleEndian(part2, tmp2) then - begin - if tmp < length(FStatusEvent.registers) then - begin - FStatusEvent.registers[tmp].Value := tmp2; - FStatusEvent.registers[tmp].Initialized := true; - end - else - DebugLn(DBG_WARNINGS, format('Register index exceeds total number of registers (%d > %d)', - [tmp, length(FStatusEvent.registers)])); - end - else - DebugLn(DBG_WARNINGS, format('Ignoring stop reply pair [%s:%s] ', [part1, part2])); - end; end; startIndex := semicolonIndex + 1; until (semicolonIndex = 0) or (semicolonIndex = length(msg)); @@ -1029,7 +991,6 @@ end; function TRspConnection.Init: integer; var reply: string; - intRegs: TInitializedRegisters; res: boolean; pSection: PDbgImageSection; dataStart: int64; @@ -1065,7 +1026,6 @@ begin repeat inc(i); pSection := FOwner.LoaderList[0].SectionByID[i]; - if (pSection <> nil) and (pSection^.Size > 0) and (pSection^.IsLoadable) then begin if Assigned(FConfig.SkipSectionsList) and @@ -1122,7 +1082,7 @@ begin if res then begin // Already wrapped in critical section - result := WaitForSignal(reply, intRegs); + result := WaitForSignal(reply); end; end; diff --git a/components/fpdebug/fpdbgrspclasses.pas b/components/fpdebug/fpdbgrspclasses.pas new file mode 100644 index 0000000000..ec1ca0e3b2 --- /dev/null +++ b/components/fpdebug/fpdbgrspclasses.pas @@ -0,0 +1,834 @@ +unit FpDbgRspClasses; + +{ Connects to gdbserver instance and communicate over gdb's remote serial protocol (RSP). + Can in principle possible to connect over any serial text capabile interface + such as tcp/ip, RS-232, pipes etc. Currently only tcp/ip connections are supported. } + +{$mode objfpc}{$H+} +{$packrecords c} +{$modeswitch advancedrecords} + +interface + +uses + Classes, + SysUtils, + FpDbgClasses, + FpDbgLoader, + DbgIntfBaseTypes, DbgIntfDebuggerBase, + {$ifdef FORCE_LAZLOGGER_DUMMY} LazLoggerDummy {$else} LazLoggerBase {$endif}, Maps, + FpDbgRsp, FpDbgCommon, FpdMemoryTools, + FpErrorMessages; + +type + TInitializedRegisters = record + Initialized: boolean; + regs: array of qword; // sized to handle largest register, should truncate as required to smaller registers + end; + + { TDbgRspThread } + + TDbgRspThread = class(TDbgThread) + private + protected + FRegs: TInitializedRegisters; + FExceptionSignal: integer; + FIsPaused, FInternalPauseRequested, FIsInInternalPause: boolean; + FIsSteppingBreakPoint: boolean; + FDidResetInstructionPointer: Boolean; + FHasThreadState: boolean; + FUnwinder: TDbgStackUnwinder; + + procedure RefreshRegisterCache; virtual; + procedure InvalidateRegisters; + function ReadDebugReg(ind: byte; out AVal: TDbgPtr): boolean; + function WriteDebugReg(ind: byte; AVal: PtrUInt): boolean; + function ReadThreadState: boolean; + function RequestInternalPause: Boolean; + function CheckSignalForPostponing(AWaitedStatus: integer): Boolean; + procedure ResetPauseStates; + public + constructor Create(const AProcess: TDbgProcess; const AID: Integer; const AHandle: THandle); override; + function ResetInstructionPointerAfterBreakpoint: boolean; override; + procedure ApplyWatchPoints(AWatchPointData: TFpWatchPointData); override; + function DetectHardwareWatchpoint: Pointer; override; + procedure BeforeContinue; override; + procedure SetRegisterValue(AName: string; AValue: QWord); override; + procedure StoreRegisters; override; + procedure RestoreRegisters; override; + end; + + { TDbgRspProcess } + + TDbgRspProcess = class(TDbgProcess) + private + protected + FStatus: integer; + FProcessStarted: boolean; + FIsTerminating: boolean; + FConnection: TRspConnection; + FRemoteConfig: TRemoteConfig; + // Initialize in target specific class + FRegArrayLength: integer; + + function AnalyseDebugEvent(AThread: TDbgThread): TFPDEvent; override; + function CreateWatchPointData: TFpWatchPointData; override; + procedure InitializeLoaders; override; + // Insert/Delete break points on target + // TODO: if target doesn't support break points or have limited break points + // then debugger needs to manage insertion/deletion of break points in target memory + function InsertBreakInstructionCode(const ALocation: TDBGPtr; out OrigValue: Byte; AMakeTempRemoved: Boolean): Boolean; override; + function RemoveBreakInstructionCode(const ALocation: TDBGPtr; const OrigValue: Byte): Boolean; override; + public + constructor Create(const AFileName: string; AnOsClasses: TOSDbgClasses; + AMemManager: TFpDbgMemManager; AProcessConfig: TDbgProcessConfig = nil); override; + destructor Destroy; override; + + function StartInstance(AParams, AnEnvironment: TStrings; AWorkingDirectory, AConsoleTty: string; + AFlags: TStartInstanceFlags; out AnError: TFpError): boolean; override; + function AttachToInstance(APid: Integer; out AnError: TFpError): boolean; override; + + procedure CreateRspConnection(AFileName: string); + + function ReadData(const AAdress: TDbgPtr; const ASize: Cardinal; out AData): Boolean; override; + function WriteData(const AAdress: TDbgPtr; const ASize: Cardinal; const AData): Boolean; override; + + procedure TerminateProcess; override; + function Pause: boolean; override; + function Detach(AProcess: TDbgProcess; AThread: TDbgThread): boolean; override; + + function Continue(AProcess: TDbgProcess; AThread: TDbgThread; SingleStep: boolean): boolean; override; + // Wait for -S or -T response from target, or if connection to target is lost + function WaitForDebugEvent(out ProcessIdentifier, ThreadIdentifier: THandle): boolean; override; + + property RspConnection: TRspConnection read FConnection; + // Target specific length of register array + property RegArrayLength: integer read FRegArrayLength; + property IsTerminating: boolean read FIsTerminating; + property Status: integer read FStatus; + end; + + { TFpRspWatchPointData } + + TRspBreakWatchPoint = record + Owner: Pointer; + Address: TDBGPtr; + Size: Cardinal; + Kind: TDBGWatchPointKind; + end; + + TFpRspWatchPointData = class(TFpWatchPointData) + private + FData: array of TRspBreakWatchPoint; + function BreakWatchPoint(AnIndex: Integer): TRspBreakWatchPoint; + function DataCount: integer; + public + function AddOwnedWatchpoint(AnOwner: Pointer; AnAddr: TDBGPtr; ASize: Cardinal; AReadWrite: TDBGWatchPointKind): boolean; override; + function RemoveOwnedWatchpoint(AnOwner: Pointer): boolean; override; + function FindOwner(AnAddr: TDBGPtr): Pointer; + property Data[AnIndex: Integer]: TRspBreakWatchPoint read BreakWatchPoint; + property Count: integer read DataCount; + end; + +implementation + +uses + FpDbgDwarfDataClasses; + +var + DBG_VERBOSE, DBG_WARNINGS: PLazLoggerLogGroup; + +{ TFpRspWatchPointData } + +function TFpRspWatchPointData.BreakWatchPoint(AnIndex: Integer + ): TRspBreakWatchPoint; +begin + if AnIndex < length(FData) then + result := FData[AnIndex]; +end; + +function TFpRspWatchPointData.DataCount: integer; +begin + result := length(FData); +end; + +function TFpRspWatchPointData.FindOwner(AnAddr: TDBGPtr): Pointer; +var + i: integer; +begin + i := 0; + while (i < Count) and not ((AnAddr >= Data[i].Address) and (AnAddr < Data[i].Address + Data[i].Size)) do + begin + inc(i); + end; + if i < Count then + Result := Data[i].Owner + else + Result := nil; +end; + +function TFpRspWatchPointData.AddOwnedWatchpoint(AnOwner: Pointer; + AnAddr: TDBGPtr; ASize: Cardinal; AReadWrite: TDBGWatchPointKind): boolean; +var + idx: integer; +begin + Result := false; + idx := length(FData); + SetLength(FData, idx+1); + FData[idx].Address := AnAddr; + FData[idx].Size := ASize; + FData[idx].Kind := AReadWrite; + FData[idx].Owner := AnOwner; + Changed := true; + Result := true; +end; + +function TFpRspWatchPointData.RemoveOwnedWatchpoint(AnOwner: Pointer): boolean; +var + i, j: integer; +begin + Result := False; + i := 0; + while (i < length(FData)) and (FData[i].Owner <> AnOwner) do + inc(i); + + if i < length(FData) then begin + for j := i+1 to length(FData)-1 do begin + FData[j-1] := FData[j]; + Changed := True; + Result := True; + end; + + SetLength(FData, length(FData)-1); + Changed := True; + Result := True; + end; +end; + +{ TDbgRspThread } + +function TDbgRspThread.ReadDebugReg(ind: byte; out AVal: TDbgPtr): boolean; +begin + Result := false; + if TDbgRspProcess(Process).FIsTerminating or (TDbgRspProcess(Process).FStatus = SIGHUP) then + DebugLn(DBG_WARNINGS, 'TDbgRspThread.GetDebugReg called while FIsTerminating is set.') + else + begin + DebugLn(DBG_VERBOSE, ['TDbgRspThread.GetDebugReg requesting register: ',ind]); + RefreshRegisterCache; + if ind < length(FRegs.regs) then + begin + AVal := FRegs.regs[ind]; + Result := true; + end; + end; +end; + +function TDbgRspThread.WriteDebugReg(ind: byte; AVal: PtrUInt): boolean; +begin + if TDbgRspProcess(Process).FIsTerminating or (TDbgRspProcess(Process).FStatus = SIGHUP) then + begin + DebugLn(DBG_WARNINGS, 'TDbgRspThread.WriteDebugReg called while FIsTerminating is set.'); + Result := false; + end + else + result := TDbgRspProcess(Process).RspConnection.WriteDebugReg(ind, AVal); +end; + +procedure TDbgRspThread.InvalidateRegisters; +var + i: integer; +begin + FRegs.Initialized := false; +end; + +procedure TDbgRspThread.RefreshRegisterCache; +begin + // Target specific +end; + +function TDbgRspThread.ReadThreadState: boolean; +begin +// assert(FIsPaused, 'TDbgRspThread.ReadThreadState: FIsPaused'); + result := true; + if FHasThreadState then + exit; + FRegisterValueListValid := false; +end; + +function TDbgRspThread.RequestInternalPause: Boolean; +begin + if TDbgRspProcess(Process).FIsTerminating then + DebugLn(DBG_WARNINGS, 'TDbgRspThread.RequestInternalPause called while FIsTerminating is set.'); + + Result := False; + if FInternalPauseRequested or FIsPaused or (TDbgRspProcess(Process).FStatus = SIGHUP) then + exit; + + DebugLn(DBG_VERBOSE, 'TDbgRspThread.RequestInternalPause requesting Ctrl-C.'); + + FInternalPauseRequested := true; + // Send SIGSTOP/break + TDbgRspProcess(Process).RspConnection.Break(); +end; + +function TDbgRspThread.CheckSignalForPostponing(AWaitedStatus: integer): Boolean; +begin + Assert(not FIsPaused, 'Got WaitStatus while already paused'); + assert(FExceptionSignal = 0, 'TDbgLinuxThread.CheckSignalForPostponing: FExceptionSignal = 0'); + Result := FIsPaused; + DebugLn(DBG_VERBOSE and (Result), ['Warning: Thread already paused', ID]); + + DebugLn(DBG_VERBOSE, ['TDbgRspThread.CheckSignalForPostponing called with ', AWaitedStatus]); + + if Result then + exit; + + FIsPaused := True; + FIsInInternalPause := False; +end; + +procedure TDbgRspThread.ResetPauseStates; +begin + FIsInInternalPause := False; + FIsPaused := False; + FExceptionSignal := 0; + FHasThreadState := False; + FDidResetInstructionPointer := False; +end; + +constructor TDbgRspThread.Create(const AProcess: TDbgProcess; + const AID: Integer; const AHandle: THandle); +begin + inherited; + FRegs.Initialized := false; + SetLength(FRegs.regs, TDbgRspProcess(AProcess).RegArrayLength); +end; + +function TDbgRspThread.ResetInstructionPointerAfterBreakpoint: boolean; +begin + if not ReadThreadState then + exit(False); + result := true; + if FDidResetInstructionPointer then + exit; + FDidResetInstructionPointer := True; +end; + +procedure TDbgRspThread.ApplyWatchPoints(AWatchPointData: TFpWatchPointData); +var + i: integer; + addr: PtrUInt; + watchData: TRspBreakWatchPoint; + tmpData: TBytes; +begin + for i := 0 to TFpRspWatchPointData(AWatchPointData).Count-1 do + begin + watchData := TFpRspWatchPointData(AWatchPointData).Data[i]; + addr := watchData.Address; + SetLength(tmpData, watchData.Size); + if Process.ReadData(addr, watchData.Size, tmpData[0]) then + begin + if not TDbgRspProcess(Process).RspConnection.SetBreakWatchPoint(addr, watchData.Kind) then + DebugLn(DBG_WARNINGS, 'Failed to set watch point.', []); + end + else + DebugLn(DBG_WARNINGS, 'Failed to read memory.', []); + end; +end; + +function TDbgRspThread.DetectHardwareWatchpoint: Pointer; +begin + if TDbgRspProcess(Process).RspConnection.LastStatusEvent.stopReason in [srAnyWatchPoint, srReadWatchPoint, srWriteWatchPoint] then + begin + Result := TFpRspWatchPointData(TDbgRspProcess(Process).WatchPointData).FindOwner(TDbgRspProcess(Process).RspConnection.LastStatusEvent.watchPointAddress); + TDbgRspProcess(Process).RspConnection.ResetStatusEvent; + end + else + result := nil; +end; + +procedure TDbgRspThread.BeforeContinue; +begin + if not FIsPaused then + exit; + + inherited; + InvalidateRegisters; +end; + +procedure TDbgRspThread.SetRegisterValue(AName: string; AValue: QWord); +begin + assert(true, 'Not implemented'); +end; + +procedure TDbgRspThread.StoreRegisters; +begin + assert(true, 'Not implemented'); +end; + +procedure TDbgRspThread.RestoreRegisters; +begin + assert(true, 'Not implemented'); +end; + +{ TDbgRspProcess } + +procedure TDbgRspProcess.InitializeLoaders; +begin + if LoaderList.Count = 0 then + TDbgImageLoader.Create(Name).AddToLoaderList(LoaderList); +end; + +procedure TDbgRspProcess.CreateRspConnection(AFileName: string); +begin + self.FConnection := TRspConnection.Create(AFileName, self, FRemoteConfig); +end; + +function TDbgRspProcess.CreateWatchPointData: TFpWatchPointData; +begin + DebugLn(DBG_VERBOSE, 'TDbgRspProcess.CreateWatchPointData called.'); + Result := TFpRspWatchPointData.Create; +end; + +constructor TDbgRspProcess.Create(const AFileName: string; + AnOsClasses: TOSDbgClasses; AMemManager: TFpDbgMemManager; + AProcessConfig: TDbgProcessConfig); +begin + if Assigned(AProcessConfig) and (AProcessConfig is TRemoteConfig) then + begin + FRemoteConfig := TRemoteConfig.Create; + FRemoteConfig.Assign(AProcessConfig); + end; + + inherited Create(AFileName, AnOsClasses, AMemManager); +end; + +destructor TDbgRspProcess.Destroy; +begin + if Assigned(FRemoteConfig) then + FreeAndNil(FRemoteConfig); + if Assigned(FConnection) then + FreeAndNil(FConnection); + inherited Destroy; +end; + +function TDbgRspProcess.AttachToInstance(APid: Integer; out AnError: TFpError + ): boolean; +begin + result := false; +end; + +function TDbgRspProcess.StartInstance(AParams, AnEnvironment: TStrings; + AWorkingDirectory, AConsoleTty: string; AFlags: TStartInstanceFlags; out + AnError: TFpError): boolean; +var + AnExecutabeFilename: string; +begin + Result := false; + AnExecutabeFilename:=ExcludeTrailingPathDelimiter(Name); + if DirectoryExists(AnExecutabeFilename) then + begin + DebugLn(DBG_WARNINGS, 'Can not debug %s, because it''s a directory',[AnExecutabeFilename]); + Exit; + end; + + if not FileExists(Name) then + begin + DebugLn(DBG_WARNINGS, 'Can not find %s.',[AnExecutabeFilename]); + Exit; + end; + + if not Assigned(FRemoteConfig) then + begin + DebugLn(DBG_WARNINGS, 'TDbgAvrProcess only supports remote debugging and requires a valid TRemoteConfig class'); + Exit; + end; + + try + FConnection := TRspConnection.Create(Name, self, self.FRemoteConfig); + FConnection.Connect; + try + FStatus := FConnection.Init; + Result := true; + except + on E: Exception do + begin + DebugLn(DBG_WARNINGS, Format('Failed to init remote connection. Errormessage: "%s".', [E.Message])); + end; + end; + except + on E: Exception do + begin + DebugLn(DBG_WARNINGS, Format('Failed to start remote connection. Errormessage: "%s".', [E.Message])); + end; + end; +end; + +function TDbgRspProcess.ReadData(const AAdress: TDbgPtr; + const ASize: Cardinal; out AData): Boolean; +begin + if FIsTerminating or (TDbgRspProcess(Process).FStatus = SIGHUP) then + begin + DebugLn(DBG_WARNINGS, 'TDbgRspProcess.ReadData called while FIsTerminating is set.'); + Result := false; + exit; + end; + + result := RspConnection.ReadData(AAdress, ASize, AData); + if Result then + MaskBreakpointsInReadData(AAdress, ASize, AData); +end; + +function TDbgRspProcess.WriteData(const AAdress: TDbgPtr; + const ASize: Cardinal; const AData): Boolean; +begin + if FIsTerminating or (TDbgRspProcess(Process).FStatus = SIGHUP) then + begin + DebugLn(DBG_WARNINGS, 'TDbgRspProcess.WriteData called while FIsTerminating is set.'); + Result := false; + exit; + end; + + result := RspConnection.WriteData(AAdress, ASize, AData); +end; + +procedure TDbgRspProcess.TerminateProcess; +begin + // Try to prevent access to the RSP socket after it has been closed + if not (FIsTerminating or (TDbgRspProcess(Process).FStatus = SIGHUP)) then + begin + DebugLn(DBG_VERBOSE, 'Removing all break points'); + RemoveAllBreakPoints; + DebugLn(DBG_VERBOSE, 'Sending kill command from TDbgRspProcess.TerminateProcess'); + RspConnection.Kill(); + FIsTerminating:=true; + end; +end; + +function TDbgRspProcess.Pause: boolean; +begin + if FIsTerminating or (TDbgRspProcess(Process).FStatus = SIGHUP) then + begin + DebugLn(DBG_WARNINGS, 'TDbgRspProcess.Pause called while FIsTerminating is set.'); + Result := false; + exit; + end; + + // Target should automatically respond with T or S reply after processing the break + result := true; + if not PauseRequested then + begin + RspConnection.Break(); + PauseRequested := true; + DebugLn(DBG_VERBOSE, 'TDbgRspProcess.Pause called.'); + end + else + begin + result := true; + DebugLn(DBG_WARNINGS, 'TDbgRspProcess.Pause called while PauseRequested is set.'); + end; +end; + +function TDbgRspProcess.Detach(AProcess: TDbgProcess; AThread: TDbgThread): boolean; +begin + RemoveAllBreakPoints; + DebugLn(DBG_VERBOSE, 'Sending detach command from TDbgRspProcess.Detach'); + Result := RspConnection.Detach(); +end; + +function TDbgRspProcess.Continue(AProcess: TDbgProcess; AThread: TDbgThread; SingleStep: boolean): boolean; +var + ThreadToContinue: TDbgRspThread; + PC: word; + s: string; + tempState: integer; + initRegs: TInitializedRegisters; +begin + // Terminating process and all threads + if FIsTerminating or (FStatus = SIGHUP) then + begin + AThread.BeforeContinue; + TDbgRspThread(AThread).InvalidateRegisters; + DebugLn(DBG_VERBOSE, 'TDbgRspProcess.Continue called while terminating.'); + + + + + TDbgRspThread(AThread).ResetPauseStates; + if not FThreadMap.HasId(AThread.ID) then + AThread.Free; + exit; + end; + + if TDbgRspThread(AThread).FIsPaused then // in case of deInternal, it may not be paused and can be ignored + AThread.NextIsSingleStep:=SingleStep; + + // check other threads if they need a singlestep + for TDbgThread(ThreadToContinue) in FThreadMap do + if (ThreadToContinue <> AThread) and ThreadToContinue.FIsPaused then + begin + PC := ThreadToContinue.GetInstructionPointerRegisterValue; + if HasInsertedBreakInstructionAtLocation(PC) then + begin + TempRemoveBreakInstructionCode(PC); + ThreadToContinue.BeforeContinue; + + while (ThreadToContinue.GetInstructionPointerRegisterValue = PC) do + begin + result := RspConnection.SingleStep(); + TDbgRspThread(ThreadToContinue).ResetPauseStates; // So BeforeContinue will not run again + ThreadToContinue.FIsPaused := True; + if result then + begin + tempState := RspConnection.WaitForSignal(s); // TODO: Update registers cache for this thread + if (tempState = SIGTRAP) then + break; // if the command jumps back an itself.... + end + else + begin + DebugLn(DBG_WARNINGS, ['Error single stepping other thread ', ThreadToContinue.ID]); + break; + end; + end; + end; + end; + + if TDbgRspThread(AThread).FIsPaused and SingleStep then // in case of deInternal, it may not be paused and can be ignored + if HasInsertedBreakInstructionAtLocation(AThread.GetInstructionPointerRegisterValue) then + begin + TempRemoveBreakInstructionCode(AThread.GetInstructionPointerRegisterValue); + TDbgRspThread(AThread).FIsSteppingBreakPoint := True; + AThread.BeforeContinue; + result := RspConnection.SingleStep(); // TODO: pass thread ID once it is supported in RspConnection - also signals not yet passed through + TDbgRspThread(AThread).ResetPauseStates; + FStatus := 0; // need to call WaitForSignal to read state after single step + exit; + end; + + RestoreTempBreakInstructionCodes; + + ThreadsBeforeContinue; + + // start all other threads + for TDbgThread(ThreadToContinue) in FThreadMap do + begin + if (ThreadToContinue <> AThread) and (ThreadToContinue.FIsPaused) then + begin + RspConnection.Continue(); + ThreadToContinue.ResetPauseStates; + end; + end; + + if TDbgRspThread(AThread).FIsPaused then // in case of deInternal, it may not be paused and can be ignored + if not FIsTerminating then + begin + AThread.BeforeContinue; + if SingleStep then + result := RspConnection.SingleStep() + else + result := RspConnection.Continue(); + TDbgRspThread(AThread).ResetPauseStates; + FStatus := 0; // should update status by calling WaitForSignal + end; + + if not FThreadMap.HasId(AThread.ID) then + AThread.Free; +end; + +function TDbgRspProcess.WaitForDebugEvent(out ProcessIdentifier, ThreadIdentifier: THandle): boolean; +var + s: string; + initRegs: TInitializedRegisters; +begin + debugln(DBG_VERBOSE, ['Entering WaitForDebugEvent, FStatus = ', FStatus]); + // Currently only single process/thread + // TODO: Query and handle process/thread states of target + ThreadIdentifier := self.ThreadID; + ProcessIdentifier := Self.ProcessID; + + if FIsTerminating then + begin + DebugLn(DBG_VERBOSE, 'TDbgRspProcess.WaitForDebugEvent called while FIsTerminating is set.'); + FStatus := SIGKILL; + end + else + // Wait for S or T response from target, or if connection to target is lost + if FStatus = 0 then + repeat + try + FStatus := RspConnection.WaitForSignal(s); // TODO: Update registers cache + sleep(1); + except + FStatus := 0; + end; + until FStatus <> 0; + + if FStatus in [SIGINT, SIGTRAP] then + RestoreTempBreakInstructionCodes; + + result := FStatus <> 0; +end; + +function TDbgRspProcess.InsertBreakInstructionCode(const ALocation: TDBGPtr; + out OrigValue: Byte; AMakeTempRemoved: Boolean): Boolean; +begin + if FIsTerminating or (FStatus = SIGHUP) then + DebugLn(DBG_WARNINGS, 'TDbgRspProcess.InsertBreakInstruction called while FIsTerminating is set.'); + + // Todo: This does not respect a break instruction larger than 1 byte. + // Fix: Use target specific break information in parent class. + result := ReadData(ALocation, SizeOf(OrigValue), OrigValue); + if AMakeTempRemoved then + exit; + + // Insert HW break... + result := RspConnection.SetBreakWatchPoint(ALocation, wkpExec); + if not result then + DebugLn(DBG_WARNINGS, 'Failed to set break point.', []); +end; + +function TDbgRspProcess.RemoveBreakInstructionCode(const ALocation: TDBGPtr; + const OrigValue: Byte): Boolean; +begin + if FIsTerminating or (FStatus = SIGHUP) then + begin + DebugLn(DBG_WARNINGS, 'TDbgRspProcess.RemoveBreakInstructionCode called while FIsTerminating is set'); + result := false; + end + else + result := RspConnection.DeleteBreakWatchPoint(ALocation, wkpExec); +end; + +function TDbgRspProcess.AnalyseDebugEvent(AThread: TDbgThread): TFPDEvent; +var + ThreadToPause: TDbgRspThread; +begin + debugln(DBG_VERBOSE, ['Entering TDbgRspProcess.AnalyseDebugEvent, FStatus = ', FStatus, ' PauseRequested = ', PauseRequested]); + if FIsTerminating then begin + result := deExitProcess; + exit; + end; + + if AThread = nil then begin // should not happen... / just assume the most likely safe failbacks + result := deInternalContinue; + exit; + end; + + TDbgRspThread(AThread).FExceptionSignal:=0; + TDbgRspThread(AThread).FIsPaused := True; + + if FStatus in [SIGHUP, SIGKILL] then // not sure which signals is relevant here + begin + if AThread.ID=ProcessID then + begin + // Main thread stop -> application exited + SetExitCode(FStatus); + result := deExitProcess + end + else + begin + // Thread stopped, just continue + RemoveThread(AThread.Id); + result := deInternalContinue; + end; + end + else if FStatus <> 0 then + begin + TDbgRspThread(AThread).ReadThreadState; + + if (not FProcessStarted) and (FStatus <> SIGTRAP) then + begin + // attached, should be SigStop, but may be out of order + debugln(DBG_VERBOSE, ['Attached ', FStatus]); + result := deCreateProcess; + FProcessStarted:=true; + end + else + case FStatus of + SIGTRAP: + begin + if not FProcessStarted then + begin + result := deCreateProcess; + FProcessStarted:=true; + DebugLn(DBG_VERBOSE, ['Creating process - SIGTRAP received for thread: ', AThread.ID]); + end + else if TDbgRspThread(AThread).FInternalPauseRequested then + begin + DebugLn(DBG_VERBOSE, ['???Received late SigTrap for thread ', AThread.ID]); + result := deBreakpoint; + end + else + begin + DebugLn(DBG_VERBOSE, ['Received SigTrap for thread ', AThread.ID, + ' PauseRequest=', PauseRequested]); + result := deBreakpoint; + + if not TDbgRspThread(AThread).FIsSteppingBreakPoint then + AThread.CheckAndResetInstructionPointerAfterBreakpoint; + end; + end; + SIGINT: + begin + if PauseRequested then + result := deBreakpoint + else + begin + ExceptionClass:='SIGINT'; + TDbgRspThread(AThread).FExceptionSignal:=SIGINT; + result := deException; + end; + end; + SIGKILL: + begin + if FIsTerminating then + result := deInternalContinue + else + begin + ExceptionClass:='SIGKILL'; + TDbgRspThread(AThread).FExceptionSignal:=SIGKILL; + result := deException; + end; + end; + SIGSTOP: + begin + // New thread (stopped within the new thread) + result := deInternalContinue; + end + else + begin + ExceptionClass:='Unknown exception code ' + inttostr(FStatus); + TDbgRspThread(AThread).FExceptionSignal := FStatus; + result := deException; + end; + end; {case} + if result=deException then + ExceptionClass:='External: '+ExceptionClass; + end; + + debugln(DBG_VERBOSE, ['Leaving AnalyseDebugEvent, result = ', result]); + + TDbgRspThread(AThread).FIsSteppingBreakPoint := False; + + if Result in [deException, deBreakpoint, deFinishedStep] then // deFinishedStep will not be set here + begin + // Signal all other threads to pause + for TDbgThread(ThreadToPause) in FThreadMap do + begin + if (ThreadToPause <> AThread) then + begin + DebugLn(DBG_VERBOSE and (ThreadToPause.FInternalPauseRequested), ['Re-Request Internal pause for ', ThreadToPause.ID]); + ThreadToPause.FInternalPauseRequested:=false; + if not ThreadToPause.RequestInternalPause then // will fail, if already paused + break; + end; + end; + end; +end; + +initialization + DBG_VERBOSE := DebugLogger.FindOrRegisterLogGroup('DBG_VERBOSE' {$IFDEF DBG_VERBOSE} , True {$ENDIF} ); + DBG_WARNINGS := DebugLogger.FindOrRegisterLogGroup('DBG_WARNINGS' {$IFDEF DBG_WARNINGS} , True {$ENDIF} ); + +end.