FpDebug: refactor stack unwinding for AVR

This commit is contained in:
ccrause 2023-12-02 11:45:02 +02:00 committed by Maxim Ganetsky
parent 53474705c4
commit 3b833564d5
2 changed files with 182 additions and 130 deletions

View File

@ -58,6 +58,8 @@ type
FIsSteppingBreakPoint: boolean;
FDidResetInstructionPointer: Boolean;
FHasThreadState: boolean;
FUnwinder: TDbgStackUnwinder;
function ReadDebugReg(ind: byte; out AVal: TDbgPtr): boolean;
function WriteDebugReg(ind: byte; AVal: PtrUInt): boolean;
@ -73,6 +75,7 @@ type
function RequestInternalPause: Boolean;
function CheckSignalForPostponing(AWaitedStatus: integer): Boolean;
procedure ResetPauseStates;
function GetStackUnwinder: TDbgStackUnwinder; override;
public
constructor Create(const AProcess: TDbgProcess; const AID: Integer; const AHandle: THandle); override;
function ResetInstructionPointerAfterBreakpoint: boolean; override;
@ -85,8 +88,6 @@ type
function GetStackBasePointerRegisterValue: TDbgPtr; override;
procedure SetStackPointerRegisterValue(AValue: TDbgPtr); override;
function GetStackPointerRegisterValue: TDbgPtr; override;
procedure PrepareCallStackEntryList(AFrameRequired: Integer = -1); override;
end;
{ TDbgAvrProcess }
@ -161,6 +162,7 @@ type
property Count: integer read DataCount;
end;
implementation
uses
@ -363,6 +365,13 @@ begin
FDidResetInstructionPointer := False;
end;
function TDbgAvrThread.GetStackUnwinder: TDbgStackUnwinder;
begin
if FUnwinder = nil then
FUnwinder := TDbgStackUnwinderAVR.Create(Process);
Result := FUnwinder;
end;
constructor TDbgAvrThread.Create(const AProcess: TDbgProcess;
const AID: Integer; const AHandle: THandle);
begin
@ -526,128 +535,6 @@ begin
ReadDebugReg(SPindex, result);
end;
procedure TDbgAvrThread.PrepareCallStackEntryList(AFrameRequired: Integer);
const
MAX_FRAMES = 50000; // safety net
// To read RAM, add data space offset to address
DataOffset = $800000;
Size = 2;
var
Address, FrameBase, LastFrameBase: TDBGPtr;
CountNeeded, CodeReadErrCnt: integer;
AnEntry: TDbgCallstackEntry;
R: TDbgRegisterValue;
NextIdx: LongInt;
OutSideFrame: Boolean;
StackPtr: TDBGPtr;
startPC, endPC: TDBGPtr;
returnAddrStackOffset: word;
b: byte;
begin
// TODO: use AFrameRequired // check if already partly done
if FCallStackEntryList = nil then
FCallStackEntryList := TDbgCallstackEntryList.Create;
if AFrameRequired = -2 then
exit;
if (AFrameRequired >= 0) and (AFrameRequired < FCallStackEntryList.Count) then
exit;
FCallStackEntryList.FreeObjects:=true;
if FCallStackEntryList.Count > 0 then begin
AnEntry := FCallStackEntryList[FCallStackEntryList.Count - 1];
R := AnEntry.RegisterValueList.FindRegisterByDwarfIndex(PCindex);
if R = nil then exit;
Address := R.NumValue;
R := AnEntry.RegisterValueList.FindRegisterByDwarfIndex(28);
if R = nil then exit;
FrameBase := R.NumValue;
R := AnEntry.RegisterValueList.FindRegisterByDwarfIndex(29);
if R = nil then exit;
FrameBase := FrameBase + (byte(R.NumValue) shl 8);
R := AnEntry.RegisterValueList.FindRegisterByDwarfIndex(SPindex);
if R = nil then exit;
StackPtr := R.NumValue;
end
else begin
Address := GetInstructionPointerRegisterValue;
FrameBase := GetStackBasePointerRegisterValue;
StackPtr := GetStackPointerRegisterValue;
AnEntry := TDbgCallstackEntry.create(Self, 0, FrameBase, Address);
// Top level could be without entry in registerlist / same as GetRegisterValueList / but some code tries to find it here ....
AnEntry.RegisterValueList.DbgRegisterAutoCreate[nPC].SetValue(Address, IntToStr(Address),Size, PCindex);
AnEntry.RegisterValueList.DbgRegisterAutoCreate[nSP].SetValue(StackPtr, IntToStr(StackPtr),Size, SPindex);
// Y pointer register [r28:r29] is used as frame pointer for AVR
// Store these to enable stack based parameters to be read correctly
// as they are frame pointer relative and dwarf stores the frame pointer under r28
//AnEntry.RegisterValueList.DbgRegisterAutoCreate[nFP].SetValue(FrameBase, IntToStr(FrameBase),Size, FPindex);
b := byte(FrameBase);
AnEntry.RegisterValueList.DbgRegisterAutoCreate['r28'].SetValue(b, IntToStr(b),Size, 28);
b := (FrameBase and $FF00) shr 8;
AnEntry.RegisterValueList.DbgRegisterAutoCreate['r29'].SetValue(b, IntToStr(b),Size, 29);
FCallStackEntryList.Add(AnEntry);
end;
NextIdx := FCallStackEntryList.Count;
if AFrameRequired < 0 then
AFrameRequired := MaxInt;
CountNeeded := AFrameRequired - FCallStackEntryList.Count;
LastFrameBase := 0;
CodeReadErrCnt := 0;
while (CountNeeded > 0) and (FrameBase <> 0) and (FrameBase > LastFrameBase) do
begin
// Get start/end PC of proc from debug info
if not Self.Process.FindProcStartEndPC(Address, startPC, endPC) then
begin
// Give up for now, it is complicated to locate prologue/epilogue in general without proc limits
// ToDo: Perhaps interpret .debug_frame info if available,
// or scan forward from address until an epilogue is found.
break;
end;
if not TAvrAsmDecoder(Process.Disassembler).GetFunctionFrameReturnAddress(Address, startPC, endPC, returnAddrStackOffset, OutSideFrame) then
begin
OutSideFrame := False;
end;
LastFrameBase := FrameBase;
if OutSideFrame then begin
// Before adjustment of frame pointer, or after restoration of frame pointer,
// return PC should be located by offset from SP
if not Process.ReadData(DataOffset or (StackPtr + returnAddrStackOffset), Size, Address) or (Address = 0) then Break;
end
else begin
// Inside stack frame, return PC should be located by offset from FP
if not Process.ReadData(DataOffset or (FrameBase + returnAddrStackOffset), Size, Address) or (Address = 0) then Break;
end;
// Convert return address from BE to LE, shl 1 to get byte address
Address := BEtoN(word(Address)) shl 1;
{$PUSH}{$R-}{$Q-}
StackPtr := FrameBase + returnAddrStackOffset + Size - 1; // After popping return-addr from "StackPtr"
FrameBase := StackPtr; // Estimate of previous FP
LastFrameBase := LastFrameBase - 1; // Make the loop think that LastFrameBase was smaller
{$POP}
AnEntry := TDbgCallstackEntry.create(Self, NextIdx, FrameBase, Address);
AnEntry.RegisterValueList.DbgRegisterAutoCreate[nPC].SetValue(Address, IntToStr(Address),Size, PCindex);
AnEntry.RegisterValueList.DbgRegisterAutoCreate[nSP].SetValue(StackPtr, IntToStr(StackPtr),Size, SPindex);
AnEntry.RegisterValueList.DbgRegisterAutoCreate['r28'].SetValue(byte(FrameBase), IntToStr(b),Size, 28);
AnEntry.RegisterValueList.DbgRegisterAutoCreate['r29'].SetValue((FrameBase and $FF00) shr 8, IntToStr(b),Size, 29);
FCallStackEntryList.Add(AnEntry);
Dec(CountNeeded);
inc(NextIdx);
CodeReadErrCnt := 0;
if (NextIdx > MAX_FRAMES) then
break;
end;
if CountNeeded > 0 then // there was an error / not possible to read more frames
FCallStackEntryList.SetHasReadAllAvailableFrames;
end;
{ TDbgAvrProcess }
procedure TDbgAvrProcess.InitializeLoaders;

View File

@ -47,8 +47,6 @@ type
TAvrAsmDecoder = class;
{ TX86DisassemblerInstruction }
{ TAvrAsmInstruction }
TAvrAsmInstruction = class(TDbgAsmInstruction)
@ -86,6 +84,7 @@ type
returnAddressOffset: word; out AnIsOutsideFrame: Boolean): Boolean;
function FParseEpilogue(AnAddress, AStartPC, AEndPC: TDBGPtr; out
returnAddressOffset: word; out AnIsOutsideFrame: Boolean): Boolean;
protected
function GetLastErrorWasMemReadErr: Boolean; override;
function GetMaxInstrSize: integer; override;
@ -94,10 +93,11 @@ type
function ReadCodeAt(AnAddress: TDBGPtr; var ALen: Cardinal): Boolean; inline;
public
procedure Disassemble(var AAddress: Pointer; out ACodeBytes: String; out ACode: String); override;
procedure Disassemble(var AAddress: Pointer; out ACodeBytes: String; out ACode: String; out AnInfo: TDbgInstInfo); override; overload;
procedure Disassemble(var AAddress: Pointer; out ACodeBytes: String; out ACode: String); override; overload;
function GetInstructionInfo(AnAddress: TDBGPtr): TDbgAsmInstruction; override;
// Don't use, ot really suited to AVR ABI
// Don't use, not really suited to AVR ABI
function GetFunctionFrameInfo(AnAddress: TDBGPtr; out
AnIsOutsideFrame: Boolean): Boolean; override;
@ -113,10 +113,36 @@ type
end;
{ TDbgStackUnwinderAVR }
TDbgStackUnwinderAVR = class(TDbgStackUnwinder)
private
FThread: TDbgThread;
FProcess: TDbgProcess;
FAddressSize: Integer;
FLastFrameBaseIncreased: Boolean;
FCodeReadErrCnt: integer;
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
StrUtils, LazClasses, Math;
StrUtils, LazClasses, Math,
FpDbgAvrClasses;
var
DBG_WARNINGS: PLazLoggerLogGroup;
@ -556,7 +582,7 @@ begin
end;
procedure TAvrAsmDecoder.Disassemble(var AAddress: Pointer; out
ACodeBytes: String; out ACode: String);
ACodeBytes: String; out ACode: String; out AnInfo: TDbgInstInfo);
var
CodeIdx, r, d, k, q: byte;
a: SmallInt;
@ -565,6 +591,7 @@ var
s1: string;
_set: boolean;
begin
AnInfo := default(TDbgInstInfo);
pcode := AAddress;
CodeIdx := 0;
code := pcode[CodeIdx];
@ -1156,6 +1183,18 @@ begin
ACodeBytes := ACodeBytes + HexStr(pcode[k], 2);
Inc(AAddress, CodeIdx);
// Todo: Indicate whether currrent instruction has a destination code address
// call jump branch(?)
// Return information in AnInfo
end;
procedure TAvrAsmDecoder.Disassemble(var AAddress: Pointer; out
ACodeBytes: String; out ACode: String);
var
AnInfo: TDbgInstInfo;
begin
Disassemble(AAddress, ACodeBytes, ACode, AnInfo);
end;
function TAvrAsmDecoder.GetInstructionInfo(AnAddress: TDBGPtr
@ -1222,6 +1261,132 @@ begin
inherited Destroy;
end;
{ TDbgStackUnwinderAVR }
constructor TDbgStackUnwinderAVR.Create(AProcess: TDbgProcess);
begin
FProcess := AProcess;
FAddressSize := 2;
FLastFrameBaseIncreased := True;
FCodeReadErrCnt := 0;
end;
procedure TDbgStackUnwinderAVR.InitForThread(AThread: TDbgThread);
begin
FThread := AThread;
end;
procedure TDbgStackUnwinderAVR.InitForFrame(ACurrentFrame: TDbgCallstackEntry;
out CodePointer, StackPointer, FrameBasePointer: TDBGPtr);
var
R: TDbgRegisterValue;
begin
CodePointer := ACurrentFrame.AnAddress;
// Frame pointer is r29:r28 (if used)
FrameBasePointer := ACurrentFrame.FrameAdress;
//Could update using GetStackBasePointerRegisterValue
//R := ACurrentFrame.RegisterValueList.FindRegisterByDwarfIndex(FDwarfNumBP);
//if R <> nil then
// FrameBasePointer := R.NumValue;
StackPointer := 0;
R := ACurrentFrame.RegisterValueList.FindRegisterByDwarfIndex(SPindex);
if R = nil then exit;
StackPointer := R.NumValue;
end;
procedure TDbgStackUnwinderAVR.GetTopFrame(out CodePointer, StackPointer,
FrameBasePointer: TDBGPtr; out ANewFrame: TDbgCallstackEntry);
var
i: Integer;
R: TDbgRegisterValue;
begin
CodePointer := Thread.GetInstructionPointerRegisterValue;
StackPointer := Thread.GetStackPointerRegisterValue;
FrameBasePointer := Thread.GetStackBasePointerRegisterValue;
ANewFrame := TDbgCallstackEntry.create(Thread, 0, FrameBasePointer, CodePointer);
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;
end;
function TDbgStackUnwinderAVR.Unwind(AFrameIndex: integer; var CodePointer,
StackPointer, FrameBasePointer: TDBGPtr; ACurrentFrame: TDbgCallstackEntry;
out ANewFrame: TDbgCallstackEntry): TTDbgStackUnwindResult;
const
MAX_FRAMES = 50000; // safety net
// To read RAM, add data space offset to address
DataOffset = $800000;
Size = 2;
var
NextIdx: LongInt;
LastFrameBase: TDBGPtr;
OutSideFrame: Boolean;
StackPtr: TDBGPtr;
startPC, endPC: TDBGPtr;
returnAddrStackOffset: word;
b: byte;
begin
ANewFrame := nil;
Result := suFailed;
if StackPointer = 0 then
exit;
if not FLastFrameBaseIncreased then
exit;
OutSideFrame := False;
LastFrameBase := FrameBasePointer;
// Get start/end PC of proc from debug info
if not Self.Process.FindProcStartEndPC(CodePointer, startPC, endPC) then
begin
// Give up for now, it is complicated to locate prologue/epilogue in general without proc limits
// ToDo: Perhaps interpret .debug_frame info if available,
// or scan forward from address until an epilogue is found.
exit;
end;
if not TAvrAsmDecoder(Process.Disassembler).GetFunctionFrameReturnAddress(CodePointer, startPC, endPC, returnAddrStackOffset, OutSideFrame) then
begin
OutSideFrame := False;
end;
if OutSideFrame then begin
// Before adjustment of frame pointer, or after restoration of frame pointer,
// return PC should be located by offset from SP
if not Process.ReadData(DataOffset or (StackPtr + returnAddrStackOffset), Size, CodePointer) or (CodePointer = 0) then exit;
end
else begin
// Inside stack frame, return PC should be located by offset from FP
if not Process.ReadData(DataOffset or (FrameBasePointer + returnAddrStackOffset), Size, CodePointer) or (CodePointer = 0) then exit;
end;
// Convert return address from BE to LE, shl 1 to get byte address
CodePointer := BEtoN(word(CodePointer)) shl 1;
{$PUSH}{$R-}{$Q-}
StackPtr := FrameBasePointer + returnAddrStackOffset + Size - 1; // After popping return-addr from "StackPtr"
FrameBasePointer := StackPtr; // Estimate of previous FP
{$POP}
FLastFrameBaseIncreased := (FrameBasePointer <> 0) and (FrameBasePointer > LastFrameBase);
ANewFrame:= TDbgCallstackEntry.create(Thread, NextIdx, FrameBasePointer, CodePointer);
ANewFrame.RegisterValueList.DbgRegisterAutoCreate[nPC].SetValue(CodePointer, IntToStr(CodePointer),Size, PCindex);
ANewFrame.RegisterValueList.DbgRegisterAutoCreate[nSP].SetValue(StackPtr, IntToStr(StackPtr),Size, SPindex);
ANewFrame.RegisterValueList.DbgRegisterAutoCreate['r28'].SetValue(byte(FrameBasePointer), IntToStr(b),Size, 28);
ANewFrame.RegisterValueList.DbgRegisterAutoCreate['r29'].SetValue((FrameBasePointer and $FF00) shr 8, IntToStr(b),Size, 29);
FCodeReadErrCnt := 0;
Result := suSuccess;
end;
initialization
DebugLogger.FindOrRegisterLogGroup('DBG_WARNINGS' {$IFDEF DBG_WARNINGS} , True {$ENDIF} );