FpDebug: fix for some missing stackframes

(cherry picked from commit 8da9f2d084)
This commit is contained in:
Martin 2023-10-05 16:16:59 +02:00
parent 6ecf0f00f4
commit 70e8e1e211
3 changed files with 398 additions and 3 deletions

View File

@ -655,6 +655,8 @@ type
function GetInstructionInfo(AnAddress: TDBGPtr): TDbgAsmInstruction; virtual; abstract;
function GetFunctionFrameInfo(AnAddress: TDBGPtr; out AnIsOutsideFrame: Boolean): Boolean; virtual;
function IsAfterCallInstruction(AnAddress: TDBGPtr): boolean; virtual;
function UnwindFrame(var AnAddress, AStackPtr, AFramePtr: TDBGPtr; AQuick: boolean): boolean; virtual;
property LastErrorWasMemReadErr: Boolean read GetLastErrorWasMemReadErr;
property MaxInstructionSize: integer read GetMaxInstrSize; // abstract
@ -1875,6 +1877,17 @@ begin
Result := False;
end;
function TDbgAsmDecoder.IsAfterCallInstruction(AnAddress: TDBGPtr): boolean;
begin
Result := True; // if we don't know, then assume yes
end;
function TDbgAsmDecoder.UnwindFrame(var AnAddress, AStackPtr,
AFramePtr: TDBGPtr; AQuick: boolean): boolean;
begin
Result := False;
end;
{ TDbgInstance }
@ -3299,6 +3312,7 @@ const
MAX_FRAMES = 150000; // safety net
var
Address, FrameBase, LastFrameBase, Dummy: QWord;
PrevAddress, PrevFrameBase, PrevStackPtr: QWord;
Size, CountNeeded, IP, BP, CodeReadErrCnt, SP, i,
PrevStmtAddressOffs: integer;
AnEntry, NewEntry: TDbgCallstackEntry;
@ -3310,6 +3324,37 @@ var
Row: TDwarfCallFrameInformationRow;
CIE: TDwarfCIE;
CU: TDwarfCompilationUnit;
procedure CheckFrame(var NextEntry: TDbgCallstackEntry; AForce: boolean = False);
begin
if (not AForce) and (NextEntry <> nil) and Process.Disassembler.IsAfterCallInstruction(Address) then
exit;
if not Process.Disassembler.UnwindFrame(PrevAddress, PrevStackPtr, PrevFrameBase, False) then
exit;
if (NextEntry <> nil) and
(Address = PrevAddress) and (FrameBase = PrevFrameBase) and (StackPtr = PrevStackPtr)
then
exit;
if not Process.Disassembler.IsAfterCallInstruction(PrevAddress) then
exit;
Address := PrevAddress;
FrameBase := PrevFrameBase;
StackPtr := PrevStackPtr;
NextEntry.Free;
NextEntry := TDbgCallstackEntry.create(Self, NextIdx, FrameBase, Address);
NextEntry.RegisterValueList.DbgRegisterAutoCreate[nIP].SetValue(Address, IntToStr(Address),Size, IP);
NextEntry.RegisterValueList.DbgRegisterAutoCreate[nBP].SetValue(FrameBase, IntToStr(FrameBase),Size, BP);
NextEntry.RegisterValueList.DbgRegisterAutoCreate[nSP].SetValue(StackPtr, IntToStr(StackPtr),Size, SP);
LastFrameBase := FrameBase;
{$PUSH}{$R-}{$Q-}
if LastFrameBase > 0 then
LastFrameBase := LastFrameBase - 1;
{$POP}
end;
begin
// TODO: use AFrameRequired // check if already partly done
if FCallStackEntryList = nil then
@ -3385,8 +3430,13 @@ begin
CodeReadErrCnt := 0;
while (CountNeeded > 0) do
begin
PrevAddress := Address;
PrevFrameBase := FrameBase;
PrevStackPtr := StackPtr;
{$PUSH}{$R-}{$Q-}
if (Process.DbgInfo as TFpDwarfInfo).FindCallFrameInfo(Address - PrevStmtAddressOffs, CIE, Row) and
TDwarfCallFrameInformation.TryObtainNextCallFrame(AnEntry, CIE, Size, NextIdx, Self, Row, Process, NewEntry)
{$POP}
then begin
PrevStmtAddressOffs := 1;
if not Assigned(NewEntry) then begin
@ -3396,7 +3446,6 @@ begin
Break;
end
else begin
FCallStackEntryList.Add(NewEntry);
Address := NewEntry.AnAddress;
StackReg := NewEntry.RegisterValueList.FindRegisterByDwarfIndex(SP);
FrameReg := NewEntry.RegisterValueList.FindRegisterByDwarfIndex(BP);
@ -3406,6 +3455,8 @@ begin
FrameBase := FrameReg.FNumValue;
end;
AnEntry := NewEntry;
CheckFrame(NewEntry);
FCallStackEntryList.Add(NewEntry);
Dec(CountNeeded);
inc(NextIdx);
If (NextIdx > MAX_FRAMES) then
@ -3416,6 +3467,33 @@ begin
end;
PrevStmtAddressOffs := 1;
if Process.Disassembler.UnwindFrame(PrevAddress, PrevStackPtr, PrevFrameBase, True) and
Process.Disassembler.IsAfterCallInstruction(PrevAddress)
then begin
Address := PrevAddress;
FrameBase := PrevFrameBase;
StackPtr := PrevStackPtr;
AnEntry := TDbgCallstackEntry.create(Self, NextIdx, FrameBase, Address);
AnEntry.RegisterValueList.DbgRegisterAutoCreate[nIP].SetValue(Address, IntToStr(Address),Size, IP);
AnEntry.RegisterValueList.DbgRegisterAutoCreate[nBP].SetValue(FrameBase, IntToStr(FrameBase),Size, BP);
AnEntry.RegisterValueList.DbgRegisterAutoCreate[nSP].SetValue(StackPtr, IntToStr(StackPtr),Size, SP);
FCallStackEntryList.Add(AnEntry);
Dec(CountNeeded);
inc(NextIdx);
CodeReadErrCnt := 0;
If (NextIdx > MAX_FRAMES) then
break;
LastFrameBase := FrameBase;
{$PUSH}{$R-}{$Q-}
if LastFrameBase > 0 then
LastFrameBase := LastFrameBase - 1;
{$POP}
continue;
end;
Address := PrevAddress;
FrameBase := PrevFrameBase;
StackPtr := PrevStackPtr;
if (FrameBase <> 0) and (FrameBase > LastFrameBase)
then begin
if StackPtr = 0 then
@ -3446,7 +3524,8 @@ begin
else begin
{$PUSH}{$R-}{$Q-}
StackPtr := StackPtr + 1 * Size; // After popping return-addr from "StackPtr"
LastFrameBase := LastFrameBase - 1; // Make the loop think thas LastFrameBase was smaller
if LastFrameBase > 0 then
LastFrameBase := LastFrameBase - 1; // Make the loop think thas LastFrameBase was smaller
{$POP}
// last stack has no frame
//AnEntry.RegisterValueList.DbgRegisterAutoCreate[nBP].SetValue(0, '0',Size, BP);
@ -3463,6 +3542,7 @@ begin
AnEntry.RegisterValueList.DbgRegisterAutoCreate[nIP].SetValue(Address, IntToStr(Address),Size, IP);
AnEntry.RegisterValueList.DbgRegisterAutoCreate[nBP].SetValue(FrameBase, IntToStr(FrameBase),Size, BP);
AnEntry.RegisterValueList.DbgRegisterAutoCreate[nSP].SetValue(StackPtr, IntToStr(StackPtr),Size, SP);
CheckFrame(AnEntry, FrameBase < StackPtr);
FCallStackEntryList.Add(AnEntry);
Dec(CountNeeded);
inc(NextIdx);
@ -3471,7 +3551,14 @@ begin
break;
end
else
Break;
begin
AnEntry := nil;
CheckFrame(AnEntry);
if AnEntry <> nil then
FCallStackEntryList.Add(AnEntry)
else
Break;
end;
end;
if CountNeeded > 0 then // there was an error / not possible to read more frames
FCallStackEntryList.SetHasReadAllAvailableFrames;

View File

@ -568,6 +568,9 @@ type
function GetFunctionFrameInfo(AnAddress: TDBGPtr; out
AnIsOutsideFrame: Boolean): Boolean; override;
function IsAfterCallInstruction(AnAddress: TDBGPtr): boolean; override;
// Only Modify the values if result is true
function UnwindFrame(var AnAddress, AStackPtr, AFramePtr: TDBGPtr; AQuick: boolean): boolean; override;
end;
implementation
@ -5172,6 +5175,288 @@ begin
end;
end;
function TX86AsmDecoder.IsAfterCallInstruction(AnAddress: TDBGPtr): boolean;
const
MAX_RET_LEN = 10;
var
BytesRead: Cardinal;
i: Integer;
instr: TDbgAsmInstruction;
begin
BytesRead := MAX_RET_LEN;
Result := (AnAddress > MAX_RET_LEN) and ReadCodeAt(AnAddress - MAX_RET_LEN, BytesRead) and
(BytesRead = MAX_RET_LEN);
if not Result then begin
BytesRead := 5;
Result := (AnAddress > MAX_RET_LEN) and ReadCodeAt(AnAddress - MAX_RET_LEN, BytesRead) and
(BytesRead = MAX_RET_LEN);
end;
if not Result then
exit;
// can be a false positive
Result := (FCodeBin[MAX_RET_LEN-3] in [$9A, $E8]) or (FCodeBin[MAX_RET_LEN-5] in [$9A, $E8]);
if Result then
exit;
for i := 2 to 6 do
if (FCodeBin[MAX_RET_LEN - i] = $FF) and
(FCodeBin[MAX_RET_LEN + 1 - i] and $38 in [$10, $18])
then begin
instr := GetInstructionInfo(AnAddress - i);
Result := instr.IsCallInstruction;
exit;
end;
if (FCodeBin[MAX_RET_LEN-10] = $FF) and
(FCodeBin[MAX_RET_LEN + 1 - 10] and $38 in [$10, $18])
then begin
instr := GetInstructionInfo(AnAddress-10);
Result := instr.IsCallInstruction;
exit;
end;
end;
function TX86AsmDecoder.UnwindFrame(var AnAddress, AStackPtr,
AFramePtr: TDBGPtr; AQuick: boolean): boolean;
function IsRegister(Val, Reg: String): boolean;
begin
Result := (Length(val) >= 2) and (val[1] in ['r', 'e']) and (strlcomp(@Val[2], PChar(Reg), Length(Reg)) = 0);
end;
function RegisterSize(Reg: String): Cardinal;
begin
Result := 4;
if (Reg <> '') and (Reg[1] = 'r') then
Result := 8;
end;
const
MAX_SEARCH_ADDR = 1000;
MAX_SEARCH_CNT = 80;
var
NewAddr, NewStack, NewFrame, MaxAddr, StartStack: TDBGPtr;
Cnt: Integer;
instr: TX86AsmInstruction;
RSize: Cardinal;
Val: Int64;
CurAddr: PByte;
begin
Result := False;
NewAddr := AnAddress;
NewStack := AStackPtr;
StartStack := AStackPtr;
NewFrame := AFramePtr;
{$PUSH}{$R-}{$Q-}
MaxAddr := AnAddress + MAX_SEARCH_ADDR;
{$POP}
Cnt := MAX_SEARCH_CNT;
if AQuick then Cnt := 10;
while (NewAddr < MaxAddr) and (Cnt > 0) do begin
dec(Cnt);
instr := TX86AsmInstruction(GetInstructionInfo(NewAddr));
if instr.InstructionLength <= 0 then
exit;
NewAddr := NewAddr + instr.InstructionLength;
CurAddr := @instr.FCodeBin[0];
case instr.X86OpCode of
OPret:
begin
if instr.X86Instruction.OperCnt > 1 then
exit;
Val := 0;
if instr.X86Instruction.OperCnt = 1 then
Val := ValueFromMem(CurAddr[Instr.X86Instruction.Operand[1].CodeIndex], Instr.X86Instruction.Operand[1].ByteCount, Instr.X86Instruction.Operand[1].FormatFlags);
NewAddr := 0;
if FProcess.Mode = dm32 then begin
if not FProcess.ReadData(NewStack, 4, NewAddr, RSize) then
exit;
inc(NewStack, 4 + Val);
end
else begin
if not FProcess.ReadData(NewStack, 8, NewAddr, RSize) then
exit;
inc(NewStack, 8 + Val);
end;
Result := True;
AnAddress := NewAddr;
AStackPtr := NewStack;
AFramePtr := NewFrame;
exit;
end;
OPpush:
begin
if AQuick then
exit;
if (instr.X86Instruction.OperCnt <> 1) or
IsRegister(instr.X86Instruction.Operand[1].Value, 'bp') or
IsRegister(instr.X86Instruction.Operand[1].Value, 'sp')
then begin
exit; // false
end;
{$PUSH}{$R-}{$Q-}
NewStack := NewStack - RegisterSize(instr.X86Instruction.Operand[1].Value);
{$POP}
end;
OPpusha, OPpushf:
exit; // false
OPpopa, OPpopad:
exit; // false
OPpop:
begin
if instr.X86Instruction.OperCnt <> 1 then
exit;
if not(instr.X86Instruction.Operand[1].Value[1] in ['e', 'r'])
then
exit; // false
if IsRegister(instr.X86Instruction.Operand[1].Value, 'bp')
then begin
if NewStack < StartStack then
exit;
NewFrame := 0;
RSize := RegisterSize(instr.X86Instruction.Operand[1].Value);
if not FProcess.ReadData(NewStack, RSize, NewFrame, RSize) then
exit;
end;
{$PUSH}{$R-}{$Q-}
NewStack := NewStack + RegisterSize(instr.X86Instruction.Operand[1].Value);
{$POP}
end;
OPleave:
begin
NewStack := NewFrame;
NewFrame := 0;
if FProcess.Mode = dm32 then begin
if not FProcess.ReadData(NewStack, 4, NewFrame, RSize) then
exit;
inc(NewStack, 4);
end
else begin
if not FProcess.ReadData(NewStack, 8, NewFrame, RSize) then
exit;
inc(NewStack, 8);
end;
end;
OPmov:
begin
if instr.X86Instruction.OperCnt <> 2 then
exit;
if IsRegister(instr.X86Instruction.Operand[1].Value, 'sp')
then begin
if (not IsRegister(instr.X86Instruction.Operand[2].Value, 'bp')) or
(Instr.X86Instruction.Operand[2].ByteCount <> 0) or
(Instr.X86Instruction.Operand[2].ByteCount2 <> 0) or
(ofMemory in Instr.X86Instruction.Operand[2].Flags)
then
exit;
NewStack := NewFrame;
end;
end;
OPlea:
begin
if instr.X86Instruction.OperCnt <> 2 then
exit;
if IsRegister(instr.X86Instruction.Operand[1].Value, 'bp')
then begin
if (not IsRegister(instr.X86Instruction.Operand[2].Value, 'bp%s')) or
(Instr.X86Instruction.Operand[2].ByteCount = 0) or
(Instr.X86Instruction.Operand[2].ByteCount2 <> 0) or
not(ofMemory in Instr.X86Instruction.Operand[2].Flags)
then
exit;
Val := ValueFromMem(CurAddr[Instr.X86Instruction.Operand[2].CodeIndex], Instr.X86Instruction.Operand[2].ByteCount, Instr.X86Instruction.Operand[2].FormatFlags);
{$PUSH}{$R-}{$Q-}
NewFrame := NewFrame + Val;
{$POP}
end;
if IsRegister(instr.X86Instruction.Operand[1].Value, 'sp')
then begin
if (Instr.X86Instruction.Operand[2].ByteCount = 0) or
(Instr.X86Instruction.Operand[2].ByteCount2 <> 0) or
not(ofMemory in Instr.X86Instruction.Operand[2].Flags)
then
exit;
if (IsRegister(instr.X86Instruction.Operand[2].Value, 'sp%s')) then begin
Val := ValueFromMem(CurAddr[Instr.X86Instruction.Operand[2].CodeIndex], Instr.X86Instruction.Operand[2].ByteCount, Instr.X86Instruction.Operand[2].FormatFlags);
{$PUSH}{$R-}{$Q-}
NewStack := NewStack + Val;
{$POP}
end
else
if (IsRegister(instr.X86Instruction.Operand[2].Value, 'bp%s')) then begin
Val := ValueFromMem(CurAddr[Instr.X86Instruction.Operand[2].CodeIndex], Instr.X86Instruction.Operand[2].ByteCount, Instr.X86Instruction.Operand[2].FormatFlags);
{$PUSH}{$R-}{$Q-}
NewStack := NewFrame + Val;
{$POP}
end
else
exit;
end;
end;
OPadd:
begin
if instr.X86Instruction.OperCnt <> 2 then
exit;
if IsRegister(instr.X86Instruction.Operand[1].Value, 'sp')
then begin
if (instr.X86Instruction.Operand[2].Value <> '%s') or
(Instr.X86Instruction.Operand[2].ByteCount = 0) or
(Instr.X86Instruction.Operand[2].ByteCount2 <> 0) or
(ofMemory in Instr.X86Instruction.Operand[2].Flags)
then
exit;
Val := ValueFromMem(CurAddr[Instr.X86Instruction.Operand[2].CodeIndex], Instr.X86Instruction.Operand[2].ByteCount, Instr.X86Instruction.Operand[2].FormatFlags);
{$PUSH}{$R-}{$Q-}
NewStack := NewStack + Val;
{$POP}
end;
end;
OPjmp:
begin
if AQuick then
exit;
if instr.X86Instruction.OperCnt <> 1 then
exit;
if (instr.X86Instruction.Operand[1].Value <> '%s') then
exit; // false
if (Instr.X86Instruction.Operand[1].ByteCount = 0) or
(Instr.X86Instruction.Operand[1].ByteCount > 2) or
(Instr.X86Instruction.Operand[1].ByteCount2 <> 0)
then
exit;
Val := ValueFromMem(CurAddr[Instr.X86Instruction.Operand[1].CodeIndex], Instr.X86Instruction.Operand[1].ByteCount, Instr.X86Instruction.Operand[1].FormatFlags);
if Val <= 0 then
exit;
{$PUSH}{$R-}{$Q-}
NewAddr := NewAddr + Val
{$POP}
end;
OPjmpe, OPint, OPint1, OPint3:
exit; // false
else
begin
if (instr.X86Instruction.OperCnt >= 1) and
(not(ofMemory in Instr.X86Instruction.Operand[1].Flags)) and
( IsRegister(instr.X86Instruction.Operand[1].Value, 'bp') or
IsRegister(instr.X86Instruction.Operand[1].Value, 'sp')
)
then begin
exit; // false
end;
end;
end;
end;
end;
initialization
DBG_WARNINGS := DebugLogger.FindOrRegisterLogGroup('DBG_WARNINGS' {$IFDEF DBG_WARNINGS} , True {$ENDIF} );

View File

@ -206,6 +206,7 @@ function QuickUtf8UpperCase(const AText: String): String;
function QuickUtf8LowerCase(const AText: String): String;
function AlignPtr(Src: Pointer; Alignment: Byte): Pointer;
function ValueFromMem(const AValue; ASize: Byte; AFlags: THexValueFormatFlags): Int64;
function HexValue(const AValue; ASize: Byte; AFlags: THexValueFormatFlags): String;
function FormatAddress(const AAddress): String;
@ -436,6 +437,28 @@ begin
Result := HexValue(AAddress, DBGPTRSIZE[GMode], [hvfIncludeHexchar]);
end;
function ValueFromMem(const AValue; ASize: Byte; AFlags: THexValueFormatFlags
): Int64;
var
p: PByte;
begin
Result := 0;
if ASize > 8 then
Exit;
if ASize = 0 then
Exit;
p := @AValue;
if p[ASize - 1] < $80
then Exclude(AFlags, hvfSigned);
if hvfSigned in AFlags
then Result := -1
else Result := 0;
Move(AValue, Result, ASize);
end;
function HexValue(const AValue; ASize: Byte; AFlags: THexValueFormatFlags): String;
var
i: Int64;