unit FpDbgXtensaClasses; {$mode objfpc}{$H+} {$packrecords c} {$modeswitch advancedrecords} interface uses Classes, SysUtils, FpDbgClasses, DbgIntfBaseTypes, DbgIntfDebuggerBase, {$ifdef FORCE_LAZLOGGER_DUMMY} LazLoggerDummy {$else} LazLoggerBase {$endif}, FpDbgRsp, FpDbgRspClasses, FpDbgCommon, FpdMemoryTools, FpErrorMessages; const SPindexDwarf = 1; // Also known as a1, Index refers to active window PCIndexDwarf = 100; // Dwarf index (assumed) nPC = 'PC'; nReturnPC = 'a0'; nSP = 'a1'; ReturnPCIndexDwarf = 0; // Also known as a0, Index refers to active window WindowBaseIndex = 65; WindowStartIndex = 66; PSIndex = 67; nWindowBase = 'windowbase'; nWindowStart = 'windowstart'; nPS = 'ps'; type { TDbgXtensaThread } TDbgXtensaThread = class(TDbgRspThread) private const lastCPURegIndex = 64; // PC + 64 registers // Offsets to load specific registers from register data // These are byte offsets, to be used when reading from the raw byte register data WindowBaseOffset = 276; // Dependent on Windowed option... WindowStartOffset = 280; // " PSOffset = 292; // " RegArrayByteLength = 420; // Depends on qemu options, but this seems to be the smallest size to handle. Only show basic registers, so rest can be ignored for now. // Ordered registers index // PC, ar0..ar63, wb, ws, ps PCindex = 0; // Offset into raw register array protected procedure LoadRegisterCache; override; procedure SaveRegisterCache; override; function GetReturnPC: TDbgPtr; function GetStackUnwinder: TDbgStackUnwinder; override; public destructor Destroy; override; procedure LoadRegisterValues; override; procedure SetRegisterValue(AName: string; AValue: QWord); override; function GetInstructionPointerRegisterValue: TDbgPtr; override; function GetStackBasePointerRegisterValue: TDbgPtr; override; // procedure SetInstructionPointerRegisterValue(AValue: TDbgPtr); override; function GetStackPointerRegisterValue: TDbgPtr; override; // procedure SetStackPointerRegisterValue(AValue: TDbgPtr); override; end; { TDbgXtensaProcess } TDbgXtensaProcess = class(TDbgRspProcess) private const FNumRegisters = 68; // pc, ar0..ar63, WindowBase, WindowStart, PS protected function CreateThread(AthreadIdentifier: THandle; out IsMainThread: boolean): TDbgThread; override; function CreateBreakPointTargetHandler: TFpBreakPointTargetHandler; override; public class function isSupported(target: TTargetDescriptor): boolean; override; constructor Create(const AFileName: string; AnOsClasses: TOSDbgClasses; AMemManager: TFpDbgMemManager; AMemModel: TFpDbgMemModel; AProcessConfig: TDbgProcessConfig = nil); override; destructor Destroy; override; end; TXtensaBreakInfo = object const _CODE: DWord = $F06D; // ILL.N -> this is a 16 bit narrow instruction end; TXtensaBreakPointTargetHandler = specialize TRspBreakPointTargetHandler; { TDbgStackUnwinderXtensa } TDbgStackUnwinderXtensa = class(TDbgStackUnwinder) private FThread: TDbgThread; FProcess: TDbgProcess; FAddressSize: Integer; FLastFrameBaseIncreased: Boolean; FCodeReadErrCnt: integer; FWindowBase: uint32; FWindowStart: uint32; protected property Process: TDbgProcess read FProcess; property Thread: TDbgThread read FThread; property AddressSize: Integer read FAddressSize; public constructor Create(AProcess: TDbgProcess); procedure InitForThread(AThread: TDbgThread); override; procedure InitForFrame(ACurrentFrame: TDbgCallstackEntry; out CodePointer, StackPointer, FrameBasePointer: TDBGPtr); override; procedure GetTopFrame(out CodePointer, StackPointer, FrameBasePointer: TDBGPtr; out ANewFrame: TDbgCallstackEntry); override; function Unwind(AFrameIndex: integer; var CodePointer, StackPointer, FrameBasePointer: TDBGPtr; ACurrentFrame: TDbgCallstackEntry; out ANewFrame: TDbgCallstackEntry): TTDbgStackUnwindResult; override; end; implementation uses FpDbgDisasXtensa, FpDbgDwarfDataClasses; var DBG_VERBOSE, DBG_WARNINGS: PLazLoggerLogGroup; { TDbgXtensaThread } procedure TDbgXtensaThread.LoadRegisterCache; var regs: TBytes; i: integer; begin if not FRegs.Initialized then begin SetLength(regs, RegArrayByteLength); FRegs.Initialized := TDbgXtensaProcess(Process).RspConnection.ReadRegisters(regs[0], length(regs)); // 32 bit LE registers for i := 0 to lastCPURegIndex do // PC, ar0..ar63 FRegs.regs[i] := regs[4*i] + (regs[4*i + 1] shl 8) + (regs[4*i + 2] shl 16) + (regs[4*i + 3] shl 24); i := WindowBaseOffset; FRegs.regs[WindowBaseIndex] := regs[i] + (regs[i + 1] shl 8) + (regs[i + 2] shl 16) + (regs[i + 3] shl 24); i := WindowStartOffset; FRegs.regs[WindowStartIndex] := regs[i] + (regs[i + 1] shl 8) + (regs[i + 2] shl 16) + (regs[i + 3] shl 24); i := PSOffset; FRegs.regs[PSIndex] := regs[i] + (regs[i + 1] shl 8) + (regs[i + 2] shl 16) + (regs[i + 3] shl 24); end; end; procedure TDbgXtensaThread.SaveRegisterCache; procedure CopyDWordToByteArray( const val: DWord; regs: PByte); begin regs[0] := val; regs[1] := val shr 8; regs[2] := val shr 16; regs[3] := val shr 24; end; var regs: TBytes; i: Integer; begin exit; // TODO: Need to determine which other registers will also change in case of a subroutine call on the debugger side. if FRegsChanged then begin SetLength(regs, RegArrayByteLength); for i := 0 to lastCPURegIndex do CopyDWordToByteArray(FRegs.regs[i], @regs[4*i]); CopyDWordToByteArray(FRegs.regs[WindowBaseIndex], @regs[WindowBaseOffset]); CopyDWordToByteArray(FRegs.regs[WindowStartIndex], @regs[WindowStartOffset]); CopyDWordToByteArray(FRegs.regs[PSIndex], @regs[PSOffset]); end; end; function TDbgXtensaThread.GetReturnPC: TDbgPtr; var wb: TDBGPtr; begin Result := 0; if TDbgXtensaProcess(Process).FIsTerminating then begin DebugLn(DBG_WARNINGS, 'TDbgXtensaProcess.GetStackPointerRegisterValue called while FIsTerminating is set.'); exit; end; if not ReadThreadState then exit; DebugLn(DBG_VERBOSE, 'TDbgXtensaProcess.GetStackPointerRegisterValue requesting stack registers.'); // Windowed ABI: retPC in a0 ReadDebugReg(WindowBaseIndex, wb); wb := wb * 4; ReadDebugReg(wb+1, result); end; function TDbgXtensaThread.GetStackUnwinder: TDbgStackUnwinder; begin if FUnwinder = nil then FUnwinder := TDbgStackUnwinderXtensa.Create(Process); Result := FUnwinder; end; destructor TDbgXtensaThread.Destroy; begin if Assigned(FUnwinder) then FreeAndNil(FUnwinder); inherited Destroy; end; procedure TDbgXtensaThread.LoadRegisterValues; var i, j, wb: integer; begin if TDbgXtensaProcess(Process).FIsTerminating or (TDbgXtensaProcess(Process).FStatus = SIGHUP) then begin DebugLn(DBG_WARNINGS, 'TDbgXtensaProcess.LoadRegisterValues called while FIsTerminating is set.'); exit; end; if not ReadThreadState then exit; LoadRegisterCache; if FRegs.Initialized then begin { FRegs.regs start with PC, then the 64 core registers, then a bunch of other system and optionally user registers. Only load currently visible registers, PC and WindowBaseOffset for now. The start of currently visible core registers is given by WindowBaseOffset } // WindowBaseOffset is a 4 bit value wb := 4 * (FRegs.regs[WindowBaseIndex] and $0F); // Returned registers start with PC, followed by core register file, so offset index by 1 for i := 0 to 15 do begin // Index should wrap around to start of register file j := ((wb+i) and 63) + 1; FRegisterValueList.DbgRegisterAutoCreate['a'+IntToStr(i)].SetValue(FRegs.regs[j], IntToStr(FRegs.regs[j]), 4, i); end; FRegisterValueList.DbgRegisterAutoCreate[nPC].SetValue(FRegs.regs[0], IntToStr(FRegs.regs[0]), 4, 0); FRegisterValueList.DbgRegisterAutoCreate[nWindowBase].SetValue(byte(FRegs.regs[WindowBaseIndex]), IntToStr(byte(FRegs.regs[WindowBaseIndex])),1 , WindowBaseIndex); FRegisterValueList.DbgRegisterAutoCreate[nWindowStart].SetValue(word(FRegs.regs[WindowStartIndex]), IntToStr(word(FRegs.regs[WindowStartIndex])),2 , WindowStartIndex); FRegisterValueList.DbgRegisterAutoCreate[nPS].SetValue(byte(FRegs.regs[PSIndex]), IntToStr(byte(FRegs.regs[PSIndex])),1 , 0); FRegisterValueList.DbgRegisterAutoCreate[nPS].SetValue(byte(FRegs.regs[PSIndex]), IntToStr(byte(FRegs.regs[PSIndex])),1 , 0); FRegisterValueListValid := true; end else DebugLn(DBG_WARNINGS, 'Warning: Could not update registers'); end; procedure TDbgXtensaThread.SetRegisterValue(AName: string; AValue: QWord); var i, err: integer; res: boolean; begin if AName[1] = 'a' then begin val(copy(AName, 2, length(Aname)), i, err); res := (err = 0) and (i <= 15); if res then res := TDbgRspProcess(Process).RspConnection.WriteDebugReg(i, byte(AValue)); end else if AName = nPC then res := TDbgRspProcess(Process).RspConnection.WriteDebugReg(PCindex, dword(AValue)) else res := False; if not res then DebugLn(DBG_WARNINGS, 'Error setting register %s to %u', [AName, AValue]); end; function TDbgXtensaThread.GetInstructionPointerRegisterValue: TDbgPtr; begin Result := 0; if TDbgXtensaProcess(Process).FIsTerminating then begin DebugLn(DBG_WARNINGS, 'TDbgXtensaProcess.GetInstructionPointerRegisterValue called while FIsTerminating is set.'); exit; end; if not ReadThreadState then exit; DebugLn(DBG_VERBOSE, 'TDbgXtensaProcess.GetInstructionPointerRegisterValue requesting PC.'); ReadDebugReg(PCindex, Result); end; function TDbgXtensaThread.GetStackBasePointerRegisterValue: TDbgPtr; begin Result := 0; if TDbgXtensaProcess(Process).FIsTerminating then begin DebugLn(DBG_WARNINGS, 'TDbgXtensaProcess.GetStackBasePointerRegisterValue called while FIsTerminating is set.'); exit; end; if not ReadThreadState then exit; DebugLn(DBG_VERBOSE, 'TDbgXtensaProcess.GetStackBasePointerRegisterValue requesting base registers.'); // Todo: check FPC implementation of stack frame for ESP32 Result := GetStackPointerRegisterValue; end; function TDbgXtensaThread.GetStackPointerRegisterValue: TDbgPtr; var wb: TDBGPtr; begin Result := 0; if TDbgXtensaProcess(Process).FIsTerminating then begin DebugLn(DBG_WARNINGS, 'TDbgXtensaProcess.GetStackPointerRegisterValue called while FIsTerminating is set.'); exit; end; if not ReadThreadState then exit; DebugLn(DBG_VERBOSE, 'TDbgXtensaProcess.GetStackPointerRegisterValue requesting stack registers.'); // Windowed ABI: SP in a1 if ReadDebugReg(WindowBaseIndex, wb) then begin wb := wb * 4; ReadDebugReg(wb+2, result); end else Result := 0; end; { TDbgXtensaProcess } function TDbgXtensaProcess.CreateThread(AthreadIdentifier: THandle; out IsMainThread: boolean): TDbgThread; begin IsMainThread:=False; if AthreadIdentifier<>feInvalidHandle then begin IsMainThread := AthreadIdentifier=ProcessID; result := TDbgXtensaThread.Create(Self, AthreadIdentifier, AthreadIdentifier); end else result := nil; end; function TDbgXtensaProcess.CreateBreakPointTargetHandler: TFpBreakPointTargetHandler; begin Result := TXtensaBreakPointTargetHandler.Create(Self); end; constructor TDbgXtensaProcess.Create(const AFileName: string; AnOsClasses: TOSDbgClasses; AMemManager: TFpDbgMemManager; AMemModel: TFpDbgMemModel; AProcessConfig: TDbgProcessConfig); begin FRegArrayLength := FNumRegisters; inherited Create(AFileName, AnOsClasses, AMemManager, AMemModel, AProcessConfig); end; destructor TDbgXtensaProcess.Destroy; begin inherited Destroy; end; class function TDbgXtensaProcess.isSupported(target: TTargetDescriptor): boolean; begin result := (target.OS = osEmbedded) and (target.machineType = mtXTENSA); end; { TDbgStackUnwinderXtensa } constructor TDbgStackUnwinderXtensa.Create(AProcess: TDbgProcess); begin FProcess := AProcess; FAddressSize := 4; FCodeReadErrCnt := 0; end; procedure TDbgStackUnwinderXtensa.InitForThread(AThread: TDbgThread); begin FThread := AThread; end; procedure TDbgStackUnwinderXtensa.InitForFrame( ACurrentFrame: TDbgCallstackEntry; out CodePointer, StackPointer, FrameBasePointer: TDBGPtr); var R: TDbgRegisterValue; begin CodePointer := ACurrentFrame.AnAddress; // Frame pointer is a7 (if used) FrameBasePointer := ACurrentFrame.FrameAdress; StackPointer := 0; R := ACurrentFrame.RegisterValueList.FindRegisterByDwarfIndex(SPindexDwarf); if R = nil then exit; StackPointer := R.NumValue; end; procedure TDbgStackUnwinderXtensa.GetTopFrame(out CodePointer, StackPointer, FrameBasePointer: TDBGPtr; out ANewFrame: TDbgCallstackEntry); var i: Integer; R: TDbgRegisterValue; begin FLastFrameBaseIncreased := True; CodePointer := Thread.GetInstructionPointerRegisterValue; StackPointer := Thread.GetStackPointerRegisterValue; FrameBasePointer := Thread.GetStackBasePointerRegisterValue; ANewFrame := TDbgCallstackEntry.create(Thread, 0, FrameBasePointer, CodePointer); // Frame pointer may not have been updated yet if FrameBasePointer > StackPointer then FrameBasePointer := StackPointer; i := Thread.RegisterValueList.Count; while i > 0 do begin dec(i); R := Thread.RegisterValueList[i]; ANewFrame.RegisterValueList.DbgRegisterAutoCreate[R.Name].SetValue(R.NumValue, R.StrValue, R.Size, R.DwarfIdx); end; FWindowBase := ANewFrame.RegisterValueList.FindRegisterByDwarfIndex(WindowBaseIndex).NumValue and $F; FWindowStart := ANewFrame.RegisterValueList.FindRegisterByDwarfIndex(WindowStartIndex).NumValue and $FFFF; end; function TDbgStackUnwinderXtensa.Unwind(AFrameIndex: integer; var CodePointer, StackPointer, FrameBasePointer: TDBGPtr; ACurrentFrame: TDbgCallstackEntry; out ANewFrame: TDbgCallstackEntry): TTDbgStackUnwindResult; const MAX_FRAMES = 500; // safety net Size = 4; var Address, returnAddress: TDBGPtr; callSize: byte; j, k, stackRegCount, numRegsRead: integer; spilled: boolean; stackRegs: array of TDBGPtr; tmpReg: uint32; LastFrameBase: TDBGPtr; regName: string; begin ANewFrame := nil; Result := suFailed; if (StackPointer > $40000000) or (StackPointer < $3F000000) or (CodePointer < $40000001) or not FLastFrameBaseIncreased then exit; LastFrameBase := FrameBasePointer; ReturnAddress := ACurrentFrame.RegisterValueList.FindRegisterByName(nReturnPC).NumValue; spilled := FWindowStart and (1 shl FWindowBase) = 0; FCodeReadErrCnt := 0; callSize := uint32(returnAddress) shr 30; if callsize = 0 then exit; Address := (returnAddress and $3FFFFFFF) or $40000000; // Check if start of this window frame is live or spilled if not spilled then begin // Move window base to previous window frame // Wraparound subtraction FWindowBase := (FWindowBase + 16 - callsize) and $F; spilled := FWindowStart and (1 shl FWindowBase) = 0; end; stackRegCount := 4*callSize; SetLength(stackRegs, stackRegCount); if not spilled then begin // Live registers, read from register file j := ((4*FWindowBase) and 63); numRegsRead := stackRegCount; for k := 0 to stackRegCount-1 do stackRegs[k] := TDbgRspThread(Thread).InternalRegs.regs[((j+k) and 63)+1]; // wraparound indexing end else begin // Registers spilled to stack, read from memory numRegsRead := 0; for j := 0 to callSize-1 do begin for k := 0 to 3 do begin if j = 0 then begin if not Process.ReadData(StackPointer-16+4*k, 4, tmpReg) then Break; end else begin if (stackRegs[1] < $3F000000) or not Process.ReadData(stackRegs[1]-16+4*k, 4, tmpReg) then Break; end; stackRegs[4*j + k] := NtoLE(tmpReg); inc(numRegsRead); end; end; end; returnAddress := stackRegs[0]; StackPointer := stackRegs[1]; FrameBasePointer := StackPointer; ANewFrame:= TDbgCallstackEntry.create(Thread, AFrameIndex, FrameBasePointer, Address); ANewFrame.RegisterValueList.DbgRegisterAutoCreate[nPC].SetValue(Address, IntToStr(Address), Size, PCIndexDwarf); for j := 0 to numRegsRead-1 do begin regName := 'a' + IntToStr(j); ANewFrame.RegisterValueList.DbgRegisterAutoCreate[regName].SetValue(stackRegs[j], IntToStr(stackRegs[j]), Size, j); end; FLastFrameBaseIncreased := (FrameBasePointer <> 0) and (FrameBasePointer > LastFrameBase); Result := suSuccess; end; initialization DBG_VERBOSE := DebugLogger.FindOrRegisterLogGroup('DBG_VERBOSE' {$IFDEF DBG_VERBOSE} , True {$ENDIF} ); DBG_WARNINGS := DebugLogger.FindOrRegisterLogGroup('DBG_WARNINGS' {$IFDEF DBG_WARNINGS} , True {$ENDIF} ); RegisterDbgOsClasses(TOSDbgClasses.Create( TDbgXtensaProcess, TDbgXtensaThread, TXtensaAsmDecoder)); end.