From 121b9389ee6515aca026b6de93de7a4c0a168b94 Mon Sep 17 00:00:00 2001 From: martin Date: Thu, 12 Mar 2020 14:21:12 +0000 Subject: [PATCH] Naive implementation of reverse disassembling Patch/Contributed by ccrause git-svn-id: trunk@62754 - --- components/fpdebug/fpdbgclasses.pp | 41 +++++- components/fpdebug/fpdbgdisasavr.pp | 34 ++--- components/fpdebug/fpdbgdisasx86.pp | 24 ++-- .../lazdebuggerfp/fpdebugdebugger.pas | 120 ++++++++++++++++-- 4 files changed, 180 insertions(+), 39 deletions(-) diff --git a/components/fpdebug/fpdbgclasses.pp b/components/fpdebug/fpdbgclasses.pp index a1d062107b..9f9c3286ec 100644 --- a/components/fpdebug/fpdbgclasses.pp +++ b/components/fpdebug/fpdbgclasses.pp @@ -413,18 +413,22 @@ type TDbgDisassembler = class protected function GetLastErrorWasMemReadErr: Boolean; virtual; - function FMaxInstrSz: integer; virtual; abstract; - function FMinInstrSz: integer; virtual; abstract; + function GetMaxInstrSize: integer; virtual; abstract; + function GetMinInstrSize: integer; virtual; abstract; + function GetCanReverseDisassemble: boolean; virtual; public constructor Create(AProcess: TDbgProcess); virtual; abstract; procedure Disassemble(var AAddress: Pointer; out ACodeBytes: String; out ACode: String); virtual; abstract; + procedure ReverseDisassemble(var AAddress: Pointer; out ACodeBytes: String; out ACode: String); virtual; + function GetInstructionInfo(AnAddress: TDBGPtr): TDbgDisassemblerInstruction; virtual; abstract; function GetFunctionFrameInfo(AnAddress: TDBGPtr; out AnIsOutsideFrame: Boolean): Boolean; virtual; property LastErrorWasMemReadErr: Boolean read GetLastErrorWasMemReadErr; - property MaxInstructionSize: integer read FMaxInstrSz; // abstract - property MinInstructionSize: integer read FMinInstrSz; // abstract + property MaxInstructionSize: integer read GetMaxInstrSize; // abstract + property MinInstructionSize: integer read GetMinInstrSize; // abstract + property CanReverseDisassemble: boolean read GetCanReverseDisassemble; end; TDbgDisassemblerClass = class of TDbgDisassembler; @@ -1302,6 +1306,35 @@ begin Result := False; end; +function TDbgDisassembler.GetCanReverseDisassemble: boolean; +begin + Result := false; +end; + +// Naive backwards scanner, decode MaxInstructionSize +// if pointer to next instruction matches, done! +// If not decrease instruction size and try again. +// Many pitfalls with X86 instruction encoding... +// Avr may give 130/65535 = 0.2% errors per instruction reverse decoded +procedure TDbgDisassembler.ReverseDisassemble(var AAddress: Pointer; out + ACodeBytes: String; out ACode: String); +var + i, instrLen: integer; + tmpAddress: PtrUint; +begin + // Decode max instruction length backwards, + instrLen := MaxInstructionSize + MinInstructionSize; + repeat + dec(instrLen, MinInstructionSize); + tmpAddress := PtrUInt(AAddress) - instrLen; + Disassemble(pointer(tmpAddress), ACodeBytes, ACode); + until (tmpAddress >= PtrUInt(AAddress)) or (instrLen = MinInstructionSize); + + // After disassemble tmpAddress points to the starting address of next instruction + // Decrement with the instruction length to point to the start of this instruction + AAddress := AAddress - instrLen; +end; + function TDbgDisassembler.GetFunctionFrameInfo(AnAddress: TDBGPtr; out AnIsOutsideFrame: Boolean): Boolean; begin diff --git a/components/fpdebug/fpdbgdisasavr.pp b/components/fpdebug/fpdbgdisasavr.pp index 21e9ff6c41..3b927d421b 100644 --- a/components/fpdebug/fpdbgdisasavr.pp +++ b/components/fpdebug/fpdbgdisasavr.pp @@ -73,17 +73,16 @@ type { TAvrDisassembler } TAvrDisassembler = class(TDbgDisassembler) - private const - FMaxInstructionSize = 4; - FMinInstructionSize = 2; private FProcess: TDbgProcess; FLastErrWasMem: Boolean; FLastInstr: TAvrDisassemblerInstruction; - function FMaxInstrSz: integer; override; - function FMinInstrSz: integer; override; protected function GetLastErrorWasMemReadErr: Boolean; override; + function GetMaxInstrSize: integer; override; + function GetMinInstrSize: integer; override; + function GetCanReverseDisassemble: boolean; override; + procedure Disassemble(var AAddress: Pointer; out ACodeBytes: String; out ACode: String); override; function GetInstructionInfo(AnAddress: TDBGPtr): TDbgDisassemblerInstruction; override; @@ -214,21 +213,26 @@ begin Result := 4; end; -function TAvrDisassembler.FMaxInstrSz: integer; -begin - result := FMaxInstructionSize; -end; - -function TAvrDisassembler.FMinInstrSz: integer; -begin - -end; - function TAvrDisassembler.GetLastErrorWasMemReadErr: Boolean; begin Result := FLastErrWasMem; end; +function TAvrDisassembler.GetMaxInstrSize: integer; +begin + Result := 4; +end; + +function TAvrDisassembler.GetMinInstrSize: integer; +begin + Result := 2; +end; + +function TAvrDisassembler.GetCanReverseDisassemble: boolean; +begin + Result := true; +end; + procedure TAvrDisassembler.Disassemble(var AAddress: Pointer; out ACodeBytes: String; out ACode: String); var diff --git a/components/fpdebug/fpdbgdisasx86.pp b/components/fpdebug/fpdbgdisasx86.pp index 404da12ec5..c5a9600c53 100644 --- a/components/fpdebug/fpdbgdisasx86.pp +++ b/components/fpdebug/fpdbgdisasx86.pp @@ -241,10 +241,10 @@ type FLastErrWasMem: Boolean; FCodeBin: array[0..MAX_CODEBIN_LEN-1] of byte; FLastInstr: TX86DisassemblerInstruction; - function FMaxInstrSz: integer; override; - function FMinInstrSz: integer; override; protected function GetLastErrorWasMemReadErr: Boolean; override; + function GetMaxInstrSize: integer; override; + function GetMinInstrSize: integer; override; function ReadCodeAt(AnAddress: TDBGPtr; var ALen: Cardinal): Boolean; inline; procedure Disassemble(var AAddress: Pointer; out AnInstruction: TInstruction); public @@ -3522,21 +3522,21 @@ end; { TX86Disassembler } -function TX86Disassembler.FMaxInstrSz: integer; -begin - result := FMaxInstructionSize; -end; - -function TX86Disassembler.FMinInstrSz: integer; -begin - result := FMinInstructionSize; -end; - function TX86Disassembler.GetLastErrorWasMemReadErr: Boolean; begin Result := FLastErrWasMem; end; +function TX86Disassembler.GetMaxInstrSize: integer; +begin + Result := 16; +end; + +function TX86Disassembler.GetMinInstrSize: integer; +begin + Result := 1; +end; + function TX86Disassembler.ReadCodeAt(AnAddress: TDBGPtr; var ALen: Cardinal ): Boolean; begin diff --git a/components/lazdebuggers/lazdebuggerfp/fpdebugdebugger.pas b/components/lazdebuggers/lazdebuggerfp/fpdebugdebugger.pas index c23072ae2b..47166766d3 100644 --- a/components/lazdebuggers/lazdebuggerfp/fpdebugdebugger.pas +++ b/components/lazdebuggers/lazdebuggerfp/fpdebugdebugger.pas @@ -1296,7 +1296,7 @@ end; function TFPDBGDisassembler.PrepareEntries(AnAddr: TDbgPtr; ALinesBefore, ALinesAfter: Integer): boolean; var - ARange: TDBGDisassemblerEntryRange; + ARange, AReversedRange: TDBGDisassemblerEntryRange; AnEntry: TDisassemblerEntry; CodeBin: TBytes; p: pointer; @@ -1304,17 +1304,19 @@ var AStatement, ASrcFileName: string; ASrcFileLine: integer; - i,j, sz, prevInstructionSize, bytesDisassembled: Integer; + i,j, sz, bytesDisassembled, bufOffset: Integer; Sym: TFpSymbol; StatIndex: integer; FirstIndex: integer; - ALastAddr: TDBGPtr; + ALastAddr, tmpAddr, tmpPointer, prevInstructionSize: TDBGPtr; + ADisassembler: FpDbgClasses.TDbgDisassembler; begin Result := False; if (Debugger = nil) or not(Debugger.State = dsPause) then exit; + ADisassembler := TFpDebugDebugger(Debugger).FDbgController.CurrentProcess.Disassembler; Sym:=nil; ASrcFileLine:=0; ASrcFileName:=''; @@ -1324,10 +1326,110 @@ begin ARange.RangeStartAddr:=AnAddr; ALastAddr:=0; - Assert(ALinesBefore<>0,'TFPDBGDisassembler.PrepareEntries LinesBefore not supported'); + if (ALinesBefore > 0) and + ADisassembler.CanReverseDisassemble then + begin + AReversedRange := TDBGDisassemblerEntryRange.Create; + tmpAddr := AnAddr; // do not modify AnAddr in this loop + // Large enough block of memory for whole loop + sz := ADisassembler.MaxInstructionSize * ALinesBefore; + SetLength(CodeBin, sz); - if ALinesAfter < 1 then exit; - sz := ALinesAfter * TFpDebugDebugger(Debugger).FDbgController.CurrentProcess.Disassembler.MaxInstructionSize; + // TODO: Check if AnAddr is at lower address than length(CodeBin) + // and ensure ReadData size doesn't exceed available target memory. + // Fill out of bounds memory in buffer with "safe" value e.g. 0 + if sz + ADisassembler.MaxInstructionSize > AnAddr then + begin + FillByte(CodeBin[0], sz, 0); + // offset into buffer where active memory should start + bufOffset := sz - AnAddr; + // size of active memory to read + sz := integer(AnAddr); + end + else + begin + bufOffset := 0; + end; + + // Everything now counts back from starting address... + bytesDisassembled := 0; + // Only read up to byte before this address + if not TFpDebugDebugger(Debugger).ReadData(tmpAddr-sz, sz, CodeBin[bufOffset]) then + DebugLn(Format('Reverse disassemble: Failed to read memory at %s.', [FormatAddress(tmpAddr)])) + else + for i := 0 to ALinesBefore-1 do + begin + if bytesDisassembled >= sz then + break; + + tmpPointer := TDBGPtr(@CodeBin[bufOffset]) + TDBGPtr(sz) - TDBGPtr(bytesDisassembled); + p := pointer(tmpPointer); + ADisassembler.ReverseDisassemble(p, ADump, AStatement); // give statement before pointer p, pointer p points to decoded instruction on return + prevInstructionSize := tmpPointer - PtrUInt(p); + bytesDisassembled := bytesDisassembled + prevInstructionSize; + DebugLn(DBG_VERBOSE, format('Disassembled: [%.8X: %s] %s',[tmpAddr, ADump, Astatement])); + + Dec(tmpAddr, prevInstructionSize); + Sym := TFpDebugDebugger(Debugger).FDbgController.CurrentProcess.FindProcSymbol(tmpAddr); + // If this is the last statement for this source-code-line, fill the + // SrcStatementCount from the prior statements. + if (assigned(sym) and ((ASrcFileName<>sym.FileName) or (ASrcFileLine<>sym.Line))) or + (not assigned(sym) and ((ASrcFileLine<>0) or (ASrcFileName<>''))) then + begin + for j := 0 to StatIndex-1 do + begin + with AReversedRange.EntriesPtr[FirstIndex+j]^ do + SrcStatementCount := StatIndex; + end; + StatIndex := 0; + FirstIndex := i; + end; + + if assigned(sym) then + begin + ASrcFileName:=sym.FileName; + ASrcFileLine:=sym.Line; + sym.ReleaseReference; + end + else + begin + ASrcFileName:=''; + ASrcFileLine:=0; + end; + AnEntry.Addr := tmpAddr; + AnEntry.Dump := ADump; + AnEntry.Statement := AStatement; + AnEntry.SrcFileLine:=ASrcFileLine; + AnEntry.SrcFileName:=ASrcFileName; + AnEntry.SrcStatementIndex:=StatIndex; // should be inverted for reverse parsing + AReversedRange.Append(@AnEntry); + inc(StatIndex); + end; + + if AReversedRange.Count>0 then + begin + // Update start of range + ARange.RangeStartAddr := tmpAddr; + // Copy range in revese order of entries + for i := 0 to AReversedRange.Count-1 do + begin + // Reverse order of statements + with AReversedRange.Entries[AReversedRange.Count-1 - i] do + begin + for j := 0 to SrcStatementCount-1 do + SrcStatementIndex := SrcStatementCount - 1 - j; + end; + + ARange.Append(AReversedRange.EntriesPtr[AReversedRange.Count-1 - i]); + end; + end; + // Entries are all pointers, don't free entries + FreeAndNil(AReversedRange); + end; + + if ALinesAfter > 0 then + begin + sz := ALinesAfter * ADisassembler.MaxInstructionSize; SetLength(CodeBin, sz); bytesDisassembled := 0; if not TFpDebugDebugger(Debugger).ReadData(AnAddr, sz, CodeBin[0]) then @@ -1339,8 +1441,7 @@ begin for i := 0 to ALinesAfter-1 do begin p := @CodeBin[bytesDisassembled]; - TFpDebugDebugger(Debugger).FDbgController.CurrentProcess - .Disassembler.Disassemble(p, ADump, AStatement); + ADisassembler.Disassemble(p, ADump, AStatement); prevInstructionSize := p - @CodeBin[bytesDisassembled]; bytesDisassembled := bytesDisassembled + prevInstructionSize; @@ -1378,6 +1479,9 @@ begin inc(StatIndex); Inc(AnAddr, prevInstructionSize); end; + end + else + ALastAddr := AnAddr; if ARange.Count>0 then begin