unit leakinfo; (* Testdata at aend of file *) {$mode objfpc}{$H+} interface uses Classes, SysUtils, fgl, // CodeTools CodeToolManager, CodeCache, // LazUtils FileUtil, LazFileUtils, LazStringUtils, LazClasses, LazLoggerBase, // IDEIntf TextTools, IDEDialogs, // LeakView {$IFDEF LEAK_RESOLVE_USE_FPC} DbgInfoReader, {$ENDIF} DbgIntfBaseTypes, FpDbgLoader, FpDbgDwarf, FpDbgInfo, FpdMemoryTools, Dialogs, LldbHelper; type { TStackLine } TStackLine = class(TRefCountedObject) LineNum : Integer; // -1 is line is unknown Column : Integer; // 0 is unknown FileName : string; // should be empty if file is unknown Addr : Int64; // -1 if address is unknown RawLineData: string; function Equals(ALine: TStackLine): boolean; reintroduce; procedure Assign(ALine: TStackLine); procedure AssignSourcePos(ALine: TStackLine); end; { TStackLines } TStackLines = class(TObject) private FLines: TRefCntObjList; function GetLine(Index: Integer): TStackLine; public constructor Create; destructor Destroy; override; function Count: Integer; procedure Clear; function Add(ALine: TStackLine): Integer; function IndexOfAddr(AnAddr: Int64): Integer; function FindAddr(AnAddr: Int64): TStackLine; procedure CopyLineInfoByAddr(AnOtherLines: TStackLines); property Lines[Index: Integer]: TStackLine read GetLine; end; { TStackTrace } TStackTrace = class(TStackLines) public BlockSize : integer; Addr : Int64; LeakCount : Integer; RawStackData: string; constructor Create; end; TLeakStatus = record TotalMem : Int64; // total mem used (-1) if unavailable LeakedMem : Int64; // leaked mem size (0) if none LeakCount : Int64; // number of unfreed pointers end; TStackTraceList = specialize TFPGObjectList; // abstract class { TLeakInfo } TLeakInfo = class(TObject) // returns True, if information has been successfully received, False otherwise // Fills LeakData record // if Traces is not nil, fill the list with TStackTrace object. User is responsible for freeing them function GetLeakInfo(out LeakData: TLeakStatus; var Traces: TStackTraceList): Boolean; virtual; abstract; function ResolveLeakInfo(AFileName: string; Traces: TStackTraceList): Boolean; virtual; abstract; end; // this file can be (should be?) hidden in the other unit, or to the implementation section // but it's hear for debugging purposes yet. // heap trc class { THeapTrcInfo } THeapTraceInfo = record ExeName : string; AllocSize : Int64; FreedSize : Int64; UnfreedSize : Int64; AllocBlocks : Int64; FreedBlocks : Int64; Unfreedblocks : Int64; HeapSize : Int64; HeapFreed : Int64; HeapShouldbe : Int64; StartupUsed : Int64; end; THeapTrcInfo = class(TLeakInfo) private protected fTRCFile : string; fTRCText : string; Trc : TStringList; TrcIndex : integer; fSummary : string; FKnownAddresses: TStackLines; fParsed : Boolean; function PosInTrc(const SubStr: string; CaseSensetive: Boolean = false): Boolean; function IsTraceLine(const Idx: Integer; CheckOnlyLineStart: Boolean = False): Boolean; function IsHeaderLine(Idx: integer; NoneStrict: Boolean = False): Boolean; function TrcNumberAfter(var Num: Int64; const AfterSub: string): Boolean; function TrcNumberAfter(var Num: Integer; const AfterSub: string): Boolean; function TrcNumFirstAndAfter(var FirstNum, AfterNum: Int64; const AfterSub: string): Boolean; procedure ParseTraceLine(s: string; var line: TStackLine); procedure ParseStackTrace(trace: TStackTrace); procedure DoParseTrc(traces: TStackTraceList); public TraceInfo : THeapTraceInfo; constructor Create(const ATRCFile: string); constructor CreateFromTxt(const AText: string); destructor Destroy; override; function GetLeakInfo(out LeakData: TLeakStatus; var Traces: TStackTraceList): Boolean; override; function ResolveLeakInfo(AFileName: string; Traces: TStackTraceList): Boolean; override; end; function AllocHeapTraceInfo(const TrcFile: string): TLeakInfo; function AllocHeapTraceInfoFromText(const TrcText: string): TLeakInfo; const CallTracePrefix = 'Call trace for block '; RawTracePrefix = 'Stack trace:'; rsStackTrace = 'Stack trace'; implementation type { TValgrindParser } TValgrindParser = class public type TValgrindLineType = (vltNone, vltEmpty, vltThread, vltHeader, vltTrace); public class function StripValgrindPrefix(var s: String): Boolean; class function ValgrindLineType(s: String): TValgrindLineType; end; function AllocHeapTraceInfo(const TrcFile: string): TLeakInfo; begin Result := THeapTrcInfo.Create(TrcFile); end; function AllocHeapTraceInfoFromText(const TrcText: string): TLeakInfo; begin Result := THeapTrcInfo.CreateFromTxt(TrcText); end; // heap trace parsing implementation procedure ClearTraceInfo(var TraceInfo: THeapTraceInfo); begin with TraceInfo do begin ExeName := ''; AllocSize := -1; FreedSize := -1; UnfreedSize := 0; AllocBlocks := -1; FreedBlocks := -1; Unfreedblocks := 0; HeapSize := -1; HeapFreed := -1; HeapShouldbe := -1; StartupUsed := -1; end; end; function ExtractNumberStr(const s: string; Offset: Integer): string; var i : integer; begin for i := Offset to length(s) do if not (s[i] in ['0'..'9']) then begin Result := Copy(s, Offset, i - Offset); Exit; end; Result := Copy(s, Offset, length(s)-Offset+1); end; function ExtractHexNumberStr(const s: string; Offset: Integer): string; var i : integer; begin Result := ''; if s[Offset] = '$' then i := Offset + 1 else if (Offset < length(s)) and (s[Offset] = '0') and (s[Offset+1] = 'x') then i := Offset + 2 else i := Offset; for i := i to length(s) do if not (s[i] in ['0'..'9','A'..'F', 'a'..'f']) then begin Result := Copy(s, Offset, i - Offset); Exit; end; Result := Copy(s, Offset, length(s)-Offset+1); end; function StrToInt(const s: string; var Num: int64): Boolean; var err : Integer; begin if s = '' then Result := false else begin Val(s, Num, err); Result := err = 0; end; end; function GetNumberAfter(const s: string; var Num: int64; const AfterStr: string): Boolean; overload; var i : integer; sub : string; begin i := Pos(AfterStr, s); Result := i > 0; if not Result then Exit; inc(i, length(AfterStr)); sub := ExtractNumberStr(s, i); Result := sub <> ''; if not Result then Exit; Result := StrToInt(sub, num); end; function GetNumberAfter(const s: string; var Num: integer; const AfterStr: string): Boolean; overload; var i64 : Int64; begin i64 := Num; Result := GetNumberAfter(s, i64, AfterStr); Num := i64; end; procedure GetNumFirstAndAfter(const s: string; var FirstNum, AfterNum: Int64; const AfterStr: string); begin StrToInt(ExtractNumberStr(s, 1), FirstNum); GetNumberAfter(s, AfterNum, AfterStr); end; { TValgrindParser } class function TValgrindParser.StripValgrindPrefix(var s: String): Boolean; var i: Integer; begin Result := False; if pos('==', s) <> 1 then exit; i := 3; while (i<=Length(s)) and (s[i] in ['0'..'9']) do inc(i); if (i+1 <= Length(s)) and (s[i] = '=') and (s[i+1] = '=') and ( (i+1 = Length(s)) or (s[i+2] in [' ', #10, #13]) ) then Result := true; if Result then begin i := i + 3; while (i<=Length(s)) and (s[i] in [#10, #13]) do inc(i); delete(s, 1, i-1); end; end; class function TValgrindParser.ValgrindLineType(s: String): TValgrindLineType; begin Result := vltNone; if not StripValgrindPrefix(s) then exit; if s = '' then Result := vltEmpty else if pos('Thread ', s) = 1 then Result := vltThread else if pos(' ', s) = 1 then Result := vltTrace // any sub line. Real traces begin with "by" ar "at" else Result := vltHeader; end; { TStackLine } function TStackLine.Equals(ALine: TStackLine): boolean; begin Result := (LineNum = ALine.LineNum) and (Column = ALine.Column) and (FileName = ALine.FileName) and (Addr = ALine.Addr) and (RawLineData = ALine.RawLineData); end; procedure TStackLine.Assign(ALine: TStackLine); begin LineNum := ALine.LineNum; Column := ALine.Column; FileName := ALine.FileName; Addr := ALine.Addr; RawLineData := ALine.RawLineData; end; procedure TStackLine.AssignSourcePos(ALine: TStackLine); begin LineNum := ALine.LineNum; Column := ALine.Column; FileName := ALine.FileName; end; { THeapTrcInfo } function THeapTrcInfo.PosInTrc(const SubStr: string; CaseSensetive: Boolean): Boolean; begin Result := TrcIndex0 else // slow? Result := PosI(SubStr, Trc[TrcIndex])>0; end; function THeapTrcInfo.IsTraceLine(const Idx: Integer; CheckOnlyLineStart: Boolean): Boolean; function IsGDBLine(s: string): boolean; begin Result:=false; if REMatches(s,'^#[0-9]+ +0x[0-9a-f]+ in ') then begin // gdb // #4 0x007489de in EXTTOOLEDITDLG_TEXTERNALTOOLMENUITEMS_$__LOAD$TCONFIGSTORAGE$$TMODALRESULT () Result:=true; end; if REMatches(s,'^#[0-9]+ .* (at|from) ') then begin // gdb // #0 DOHANDLEMOUSEACTION (this=0x14afae00, ANACTIONLIST=0x14a96af8,ANINFO=...) at synedit.pp:3000 Result:=true; end; end; var i, l: integer; s, SubStr: String; begin SubStr := Trc[Idx]; Result := False; s := Trim(SubStr); if s='' then exit; if (LeftStr(s,3) = '~"#') then begin // gdb Delete(s,1,2); if s[length(s)]='"' then Delete(s,length(s),1); Result:=CheckOnlyLineStart or IsGDBLine(s); end else if LeftStr(s,4) = '0000' then begin // leave 3 digits for pos // mantis mangled gdb // line format: 0000.*:0.* in .* (at|from) .* i := pos(':', s); Result := ( ((i > 1) and (i < Length(s)) and (s[i+1] in ['0'..'9'])) or (pos(' in ', s) > 1) ) and ( (pos(' at ', s) > 1) or (pos(' from ', s) > 1) ); Result := Result or CheckOnlyLineStart; end else if IsGDBLine(s) then begin Result := true; end else if pos('frame #', s) > 0 then begin Result := True; end else if TValgrindParser.ValgrindLineType(s) in [vltHeader] then begin Result := (Idx > 0) and (TValgrindParser.ValgrindLineType(Trc[Idx-1]) = vltTrace) and ( (Idx >= Trc.Count-1) or (TValgrindParser.ValgrindLineType(Trc[Idx+1]) in [vltTrace, vltEmpty, vltNone]) ); end else if TValgrindParser.ValgrindLineType(s) in [vltTrace] then begin Result := True; end else begin // heaptrc line? i := 1; l := length(SubStr); while (i <= l) and (SubStr[i] = ' ') do inc(i); if (i > l) or (SubStr[i] <> '$') then exit; inc(i); while (i <= l) and ((SubStr[i] in ['0'..'9']) or ((SubStr[i] in ['A'..'F'])) or ((SubStr[i] in ['a'..'f']))) do inc(i); if (i > l) and CheckOnlyLineStart then exit(True); if (i <= l) and (SubStr[i] <> ' ') then exit; while (i <= l) and (SubStr[i] = ' ') do inc(i); Result := (Pos('line', SubStr) > 0) or CheckOnlyLineStart or (i > l); end; end; function THeapTrcInfo.IsHeaderLine(Idx: integer; NoneStrict: Boolean): Boolean; var i: Integer; SubStr: String; begin SubStr := Trc[Idx]; if (TValgrindParser.ValgrindLineType(SubStr) = vltHeader) and ( (Idx < Trc.Count-1) and (TValgrindParser.ValgrindLineType(Trc[Idx+1]) = vltTrace) ) and ( (Idx = 0) or (TValgrindParser.ValgrindLineType(Trc[Idx-1]) in [vltNone, vltEmpty, vltThread]) ) then begin Result := True; exit; end; if NoneStrict and ( PosInTrc(RawTracePrefix) or PosInTrc(CallTracePrefix) ) then begin Result := True; exit; end; i := 1; while (i < length(SubStr)) and (SubStr[i] in [' ', #9, '#', '~', '"', '''']) do inc(i); Result := (PosI(CallTracePrefix, SubStr) = i) or (PosI(RawTracePrefix, SubStr) = i); end; function THeapTrcInfo.TrcNumberAfter(var Num: Int64; const AfterSub: string): Boolean; begin Result := TrcIndex= Trc.Count then Exit; TraceInfo.ExeName := Trc[TrcIndex]; while (TrcIndex < Trc.Count) and not( PosInTrc('Heap dump') or IsHeaderLine(TrcIndex, True) or IsTraceLine(TrcIndex) ) do inc(TrcIndex); if TrcIndex >= Trc.Count then Exit; if PosInTrc('Heap dump') then begin inc(TrcIndex); with TraceInfo do begin TrcNumFirstAndAfter(AllocBlocks, AllocSize, ': '); inc(TrcIndex); TrcNumFirstAndAfter(FreedBlocks, FreedSize, ': '); inc(TrcIndex); TrcNumFirstAndAfter(UnfreedBlocks, UnfreedSize, ': '); inc(TrcIndex); TrcNumberAfter(HeapSize, ': '); TrcNumberAfter(StartupUsed, '('); inc(TrcIndex); TrcNumberAfter(HeapFreed, ': '); inc(TrcIndex); if PosInTrc('Should be') then begin TrcNumberAfter(HeapShouldBe, ': '); inc(TrcIndex); end; end; end; if not Assigned(traces) then Exit; while TrcIndex < Trc.Count do begin if PosInTrc(CallTracePrefix) or ( (TrcIndex < Trc.Count-1) and IsHeaderLine(TrcIndex, True) and IsTraceLine(TrcIndex+1) ) or IsTraceLine(TrcIndex) then begin st := TStackTrace.Create; ParseStackTrace(st); // changes TrcIndex Traces.Add(st); end else inc(TrcIndex); end; end; constructor THeapTrcInfo.Create(const ATRCFile: string); begin FKnownAddresses := TStackLines.Create; fTrcFile := ATrcFile; fTRCText := ''; inherited Create; end; constructor THeapTrcInfo.CreateFromTxt(const AText: string); begin FKnownAddresses := TStackLines.Create; fTRCText := AText; end; destructor THeapTrcInfo.Destroy; begin FreeAndNil(FKnownAddresses); inherited Destroy; end; procedure THeapTrcInfo.ParseTraceLine(s: string; var line: TStackLine); procedure ReadValgrindLine; var i, SrcX, SrcY, SrcTopLine: Integer; fn, TheErrorMsg: String; Complete: boolean; SrcCode: TCodeBuffer; begin i := 3; while (i<=Length(s)) and (s[i] in ['0'..'9']) do inc(i); if (i+2 < Length(s)) and (s[i] = '=') and (s[i+1] = '=') and (s[i+2] = ' ') then i := i + 3 else exit; while (i<=Length(s)) and (s[i] = ' ') do inc(i); if (i+2 < Length(s)) and ( (copy(s, i, 3) = 'at ') or (copy(s, i, 3) = 'by ') ) then i := i + 3 else exit; Delete(s, 1, i-1); i := pos(' ', s); if i < 3 then exit; line.Addr := StrToInt64Def(copy(s,1,i-1), 0); Delete(s, 1, i); i := pos(' (', s); if i < 3 then exit; fn := copy(s,1,i-1); Delete(s, 1, i+1); i := pos(':', s); if i < 3 then exit; line.FileName := copy(s,1,i-1); Delete(s, 1, i); i := pos(')', s); if i < 3 then exit; line.LineNum := StrToIntDef(copy(s,1,i-1), 0); Delete(s, 1, i); if (fn <> '') and (line.FileName = '') then begin CodeToolBoss.FindFPCMangledIdentifier(fn ,Complete,TheErrorMsg,nil, SrcCode,SrcX,SrcY,SrcTopLine); if SrcCode<>nil then begin line.FileName:=SrcCode.Filename; line.LineNum:=SrcY; line.Column:=SrcX; end; end; end; procedure ReadLLDBLine; var AnId, SrcX, SrcY, SrcTopLine: Integer; AnIsCurrent, Complete: Boolean; AFuncName, AReminder, TheErrorMsg: String; AnArgs: TStringList; SrcCode: TCodeBuffer; begin ParseFrameLocation(s, AnId, AnIsCurrent, TDBGPtr(line.Addr), AFuncName, AnArgs, line.FileName, line.LineNum, AReminder); AnArgs.Free; if (line.FileName = '') and (AFuncName <> '') then begin CodeToolBoss.FindFPCMangledIdentifier(AFuncName,Complete,TheErrorMsg,nil, SrcCode,SrcX,SrcY,SrcTopLine); if SrcCode<>nil then begin line.FileName:=SrcCode.Filename; line.LineNum:=SrcY; line.Column:=SrcX; end; end; end; procedure ReadGDBLine; var p: PChar; StartP: PChar; hex: String; {%H-}err : Integer; i: SizeInt; GDBIdentifier: String; Complete: boolean; TheErrorMsg: string; SrcCode: TCodeBuffer; SrcX, SrcY, SrcTopLine: integer; begin p:=PChar(s); // read stack depth decimal number if not (p^ in ['0'..'9']) then exit; while p^ in ['0'..'9'] do inc(p); if p^<>' ' then exit; while p^=' ' do inc(p); // read address (hex number $000 or 0x000) if p^='$' then inc(p) else if (p^='0') and (p[1]='x') then inc(p,2); //else // exit; StartP:=p; while p^ in ['0'..'9','a'..'z'] do inc(p); hex:=copy(s,StartP-PChar(s)+1,p-StartP); Val('$'+hex, line.Addr, err); line.LineNum := -1; line.FileName := ''; Delete(s,1,p-PChar(s)); i := pos (' at ', s); if i > 0 then begin // ' at :' while i > 0 do begin // find last " at " delete(s,1,i); i := pos (' at ', s); end; i := pos (':', s); line.FileName := Copy(s, 4, i - 4); GetNumberAfter(s, line.LineNum, ':') end else if LeftStr(s,4)=' in ' then begin // for example: // #4 0x007489de in EXTTOOLEDITDLG_TEXTERNALTOOLMENUITEMS_$__LOAD$TCONFIGSTORAGE$$TMODALRESULT () Delete(s,1,4); i:=1; while (i<=length(s)) and (s[i] in ['a'..'z','A'..'Z','0'..'9','_','$','?']) do inc(i); GDBIdentifier:=copy(s,1,i-1); CodeToolBoss.FindFPCMangledIdentifier(GDBIdentifier,Complete,TheErrorMsg,nil, SrcCode,SrcX,SrcY,SrcTopLine); if SrcCode<>nil then begin line.FileName:=SrcCode.Filename; line.LineNum:=SrcY; line.Column:=SrcX; end; end; end; var i : integer; {%H-}err : Integer; hex : string; begin //DebugLn(['ParseTraceLine "',s,'"']); s := Trim(s); // gdb if (LeftStr(s,3) = '~"#') then begin // gdb Delete(s,1,3); if s[length(s)]='"' then Delete(s,length(s),1); ReadGDBLine; end else if LeftStr(s,4) = '0000' then begin // mantis mangled gdb ReadGDBLine; end else if REMatches(s,'^#[0-9]+ +0x[0-9a-f]+ in ') or REMatches(s,'^#[0-9]+ .* at ') then begin // gdb // #4 0x007489de in EXTTOOLEDITDLG_TEXTERNALTOOLMENUITEMS_$__LOAD$TCONFIGSTORAGE$$TMODALRESULT () Delete(s,1,1); ReadGDBLine; end else // lldb if pos('frame #', s) > 0 then begin if s[1] = '*' then s := ' ' + s // restore spaces else s := ' ' + s; // restore spaces ReadLLDBLine; end else // valgrind if TValgrindParser.ValgrindLineType(s) = vltTrace then begin ReadValgrindLine; end else begin i := Pos('$', s); if i > 0 then begin hex := ExtractHexNumberStr(s, i); Val(hex, line.Addr, err); if not GetNumberAfter(s, line.LineNum, 'line ') then begin line.LineNum := -1; line.FileName := ''; end else begin i := Pos(' of ', s); if i <= 0 then Exit; inc(i, 4); line.FileName := Copy(s, i, length(s) - i + 1); end; end; end; // gdb line? {i := Pos('#', s); if (i < 1) and (copy(s,1,4) = '0000') then i := 4; // mantis mangled line if (i > 0) and (i < 5) and (i < Length(s)) and (s[i+1] in ['0'..'9']) then begin inc(i); // read stack depth (a decimal number) while (i <= Length(s)) and (s[i] in ['0'..'9']) do inc(i); while (i <= Length(s)) and (s[i] in [' ', #9]) do inc(i); // read proc address (a hex number 0x00..00) if (s[i] = '0') and (i < Length(s)) and (s[i+1] = 'x') then begin hex := ExtractHexNumberStr(s, i); Val(hex, line.Addr, err); end; line.LineNum := -1; line.FileName := ''; i := pos (' at ', s); if i > 0 then begin // ' at :' while i > 0 do begin // find last " at " delete(s,1,i); i := pos (' at ', s); end; i := pos (':', s); line.FileName := Copy(s, 4, i - 4); GetNumberAfter(s, line.LineNum, ':') end else if LeftStr(s,4)=' in ' then begin // for example: // #4 0x007489de in EXTTOOLEDITDLG_TEXTERNALTOOLMENUITEMS_$__LOAD$TCONFIGSTORAGE$$TMODALRESULT () Delete(s,1,4); i:=1; while (i<=length(s)) and (s[i] in ['a'..'z','A'..'Z','0'..'9','_','$','?']) do inc(i); GDBIdentifier:=copy(s,1,i-1); CodeToolBoss.FindFPCMangledIdentifier(GDBIdentifier,Complete,TheErrorMsg,@OnFindCTSource, SrcCode,SrcX,SrcY,SrcTopLine); if SrcCode<>nil then begin line.FileName:=SrcCode.Filename; line.LineNum:=SrcY; line.Column:=SrcX; end; end; end;} end; procedure THeapTrcInfo.ParseStackTrace(trace: TStackTrace); var i : integer; {%H-}err : integer; hex : string; NewLine: TStackLine; s: String; ValHeader2: Boolean; begin i := Pos(RawTracePrefix, Trc[TrcIndex]); if (i <= 0) and (not IsTraceLine(TrcIndex)) and (TValgrindParser.ValgrindLineType(Trc[TrcIndex]) = vltNone) then begin i := Pos(CallTracePrefix, Trc[TrcIndex]); if i <= 0 then begin inc(TrcIndex); Exit; end; trace.RawStackData := Trc[TrcIndex]; // raw stack trace data inc(i, length(CallTracePrefix)); hex := ExtractHexNumberStr(Trc[TrcIndex], i); Val(hex, trace.Addr, err); GetNumberAfter(Trc[TrcIndex], trace.BlockSize, 'size '); end else begin trace.RawStackData := rsStackTrace; trace.Addr := 0; trace.BlockSize := 0; end; if (TValgrindParser.ValgrindLineType(Trc[TrcIndex]) <> vltNone) and IsHeaderLine(TrcIndex) // add valgrind headers then begin trace.RawStackData := Trc[TrcIndex]; // raw stack trace data inc(Trcindex); end else if (not IsTraceLine(TrcIndex)) then inc(TrcIndex); ValHeader2 := False; while (TrcIndex < Trc.Count) and (not IsHeaderLine(TrcIndex, True)) do begin s := Trc[Trcindex]; if (not ValHeader2) and (TValgrindParser.ValgrindLineType(s) = vltHeader) then begin ValHeader2 := True; TValgrindParser.StripValgrindPrefix(s); trace.RawStackData := trace.RawStackData + ' / ' + s; s := Trc[Trcindex]; end; NewLine := TStackLine.Create; // No reference trace.Add(NewLine); if (TrcIndex < Trc.Count-1) and (not IsTraceLine(Trcindex+1, True)) and (not IsHeaderLine(Trcindex+1)) and (TValgrindParser.ValgrindLineType(Trc[Trcindex+1]) <> vltEmpty) then begin // join next line, as there may be a linewrap while (length(s) > 0) and (s[length(s)] in [#10,#13]) do delete(s, length(s), 1); s := s + Trc[Trcindex+1]; end; ParseTraceLine(s, NewLine); NewLine.RawLineData := s; // raw stack line data inc(Trcindex); if (NewLine.FileName <> '') then begin i := FKnownAddresses.IndexOfAddr(NewLine.Addr); // Todo: compare addr, to detect inconsistencies if i < 0 then FKnownAddresses.Add(NewLine); end; end; end; function THeapTrcInfo.GetLeakInfo(out LeakData: TLeakStatus; var Traces: TStackTraceList): Boolean; var i: Integer; begin Result := false; FKnownAddresses.Clear; if (not FileExistsUTF8(fTRCFile)) and (fTRCText = '') then Exit; try Trc := TStringList.Create; try if fTRCText <> '' then Trc.Text := fTRCText else Trc.LoadFromFile(fTrcFile); TrcIndex := 0; DoParseTrc(Traces); LeakData.LeakCount := TraceInfo.UnfreedBlocks; LeakData.LeakedMem := TraceInfo.UnfreedSize; LeakData.TotalMem := TraceInfo.AllocSize; Result := true; finally Trc.Free; Trc := nil; end; for i := 0 to Traces.Count - 1 do TStackTrace(Traces[i]).CopyLineInfoByAddr(FKnownAddresses); except Result := false; end; end; function THeapTrcInfo.ResolveLeakInfo(AFileName: string; Traces: TStackTraceList ): Boolean; {$IFnDEF LEAK_RESOLVE_USE_FPC} var DwarfInfo: TFpDwarfInfo; MemModel: TFpDbgMemModel; ImageLoaderList: TDbgImageLoaderList; procedure UnLoadDwarf; begin FreeAndNil(DwarfInfo); FreeAndNil(MemModel); FreeAndNil(ImageLoaderList); end; procedure LoadDwarf; var ImageLoader: TDbgImageLoader; begin DwarfInfo := nil; ImageLoader := TDbgImageLoader.Create(AFileName); if not ImageLoader.IsValid then begin FreeAndNil(ImageLoader); exit; end; ImageLoaderList := TDbgImageLoaderList.Create(True); ImageLoader.AddToLoaderList(ImageLoaderList); MemModel := TFpDbgMemModel.Create; DwarfInfo := TFpDwarfInfo.Create(ImageLoaderList, nil, MemModel); DwarfInfo.LoadCompilationUnits; end; {$ENDIF} var trace: TStackTrace; i, j, k: Integer; CurLine: TStackLine; {$IFDEF LEAK_RESOLVE_USE_FPC} FuncName, SrcName: shortstring; SrcLine: longint; {$ENDIF} BadAddresses: TStackLines; sym: TFpSymbol; begin Result := False; {$IFDEF LEAK_RESOLVE_USE_FPC} if not OpenSymbolFile(AFileName) then exit; {$ELSE} LoadDwarf; if DwarfInfo = nil then begin IDEMessageDialog('Error','Unable to load info from file.', mtError,[mbOK]); exit; end; {$ENDIF} BadAddresses := TStackLines.Create; try for i := 0 to Traces.Count - 1 do begin trace := TStackTrace(Traces[i]); for j := 0 to trace.Count - 1 do begin CurLine := trace.Lines[j]; if (CurLine.FileName = '') then begin k := FKnownAddresses.IndexOfAddr(CurLine.Addr); if k >= 0 then CurLine.Assign(FKnownAddresses.Lines[k]) else if (BadAddresses.IndexOfAddr(CurLine.Addr) < 0) and (CurLine.Addr > 0) then begin {$IFDEF LEAK_RESOLVE_USE_FPC} if GetLineInfo(CurLine.Addr, FuncName, SrcName, SrcLine) then begin CurLine.FileName := SrcName; CurLine.LineNum := SrcLine; FKnownAddresses.Add(CurLine); end {$ELSE} if j > 0 then sym := DwarfInfo.FindProcSymbol(CurLine.Addr - 1 + ImageLoaderList.RelocationOffset) else sym := DwarfInfo.FindProcSymbol(CurLine.Addr + ImageLoaderList.RelocationOffset); if (sym <> nil) and (sym.Line > 0) then begin CurLine.FileName := sym.FileName; CurLine.LineNum := sym.Line; FKnownAddresses.Add(CurLine); end {$ENDIF} else begin BadAddresses.Add(CurLine); end; {$IFnDEF LEAK_RESOLVE_USE_FPC} ReleaseRefAndNil(sym); {$ENDIF} end; end; end; end; finally {$IFDEF LEAK_RESOLVE_USE_FPC} CloseSymbolFile; {$ELSE} UnLoadDwarf; {$ENDIF} FreeAndNil(BadAddresses); Result := True; end; end; { TStackLines } function TStackLines.GetLine(Index: Integer): TStackLine; begin Result := TStackLine(FLines[Index]); end; constructor TStackLines.Create; begin FLines := TRefCntObjList.Create; end; destructor TStackLines.Destroy; begin FLines.Clear; FreeAndNil(FLines); inherited Destroy; end; function TStackLines.Count: Integer; begin Result := FLines.Count; end; procedure TStackLines.Clear; begin FLines.Clear; end; function TStackLines.Add(ALine: TStackLine): Integer; begin Result := FLines.Add(ALine); end; function TStackLines.IndexOfAddr(AnAddr: Int64): Integer; begin Result := Count - 1; while (Result >= 0) and (Lines[Result].Addr <> AnAddr) do dec(Result); end; function TStackLines.FindAddr(AnAddr: Int64): TStackLine; var i: Integer; begin i := IndexOfAddr(AnAddr); if i < 0 then Result := nil else Result := Lines[i]; end; procedure TStackLines.CopyLineInfoByAddr(AnOtherLines: TStackLines); var i, j: Integer; CurLine: TStackLine; begin for i := 0 to Count - 1 do begin CurLine := Lines[i]; if (CurLine.FileName = '') and (CurLine.Addr <> 0) then begin j := AnOtherLines.IndexOfAddr(CurLine.Addr); if J >= 0 then CurLine.AssignSourcePos(AnOtherLines.Lines[j]); end; end; end; { TStackTrace } constructor TStackTrace.Create; begin LeakCount := 1; inherited Create; end; end. (* Testdata full leak trace fpc trace $00C86CF3 line 4015 of ../debugger/gdbmidebugger.pp $00CA4EC7 line 11321 of ../debugger/gdbmidebugger.pp $00C95663 line 6914 of ../debugger/gdbmidebugger.pp $00C95EAE line 7053 of ../debugger/gdbmidebugger.pp $00C96E43 line 7491 of ../debugger/gdbmidebugger.pp $00C97DCA line 7793 of ../debugger/gdbmidebugger.pp $0081FFBA line 6472 of ../debugger/debugger.pp $008208DF line 6626 of ../debugger/debugger.pp $0080FC76 line 2347 of debugmanager.pas $008101AE line 2418 of debugmanager.pas $004562D1 line 3290 of main.pp $00C73C00 line 411 of ../debugger/assemblerdlg.pp GDB -i mi (with line broken by output on shell) bt &"bt\n" ~"#0 DOHANDLEMOUSEACTION (this=0x14afae00, ANACTIONLIST=0x14a96af8, ANINFO=...) at synedit.pp:3000\n" ~"#1 0x00aea3e9 in FINDANDHANDLEMOUSEACTION (this=0x14afae00, ABUTTON=MBLEFT, ASHIFT=..., X=233, Y=241, ACCOUNT=CCSINGLE, ADIR=CDDOWN, ANAC TIONRESULT=..., AWHEELDELTA=0) at synedit.pp:3307\n" ~"#2 0x00aea914 in MOUSEDOWN (this=0x14afae00, BUTTON=MBLEFT, SHIFT=..., X=233, Y=241) at synedit.pp:3374\n" ~"#3 0x005e083b in DOMOUSEDOWN (this=0x14afae00, MESSAGE=..., BUTTON=MBLEFT, SHIFT=...) at include/control.inc:2135\n" ~"#4 0x005e0e8f in WMLBUTTONDOWN (this=0x14afae00, MESSAGE=...) at include/control.inc:2269\n" ~"#5 0x0040d096 in DISPATCH (this=0xeebf6d4, MESSAGE=0) at ../inc/objpas.inc:592\n" ~"#6 0x005e06e3 in WNDPROC (this=0x14afae00, THEMESSAGE=...) at include/control.inc:2099\n" ~"#7 0x005d1b88 in WNDPROC (this=0x14afae00, MESSAGE=...) at include/wincontrol.inc:5327\n" ~"#8 0x00af3b76 in WNDPROC (this=0x14afae00, MSG=...) at synedit.pp:5740\n" ~"#9 0x006666a0 in DELIVERMESSAGE (TARGET=0x14afae00, AMESSAGE=0) at lclmessageglue.pas:112\n" ~"#10 0x0057ad0e in WINDOWPROC (WINDOW=3934144, MSG=513, WPARAM=1, LPARAM=15794409) at win32/win32callback.inc:2478\n" ~"#11 0x7673fd72 in ?? () from C:\\Windows\\system32\\user32.dll\n" ~"#12 0x7673fe4a in ?? () from C:\\Windows\\system32\\user32.dll\n" ~"#13 0x7674018d in ?? () from C:\\Windows\\system32\\user32.dll\n" ~"#14 0x7674022b in ?? () from C:\\Windows\\system32\\user32.dll\n" ~"#15 0x0057e0b8 in APPPROCESSMESSAGES (this=0x183d58) at win32/win32object.inc:367\n" ~"#16 0x0043d9e1 in HANDLEMESSAGE (this=0x12bf68) at include/application.inc:1257\n" ~"#17 0x0043df56 in RUNLOOP (this=0x12bf68) at include/application.inc:1390\n" ~"#18 0x00490481 in APPRUN (this=0x183d58, ALOOP=...) at include/interfacebase.inc:54\n" ~"#19 0x0043defb in RUN (this=0x12bf68) at include/application.inc:1378\n" ~"#20 0x0040358f in main () at lazarus.pp:128\n" ^done GDB mangled by mantis 0000001 gdk_drawable_copy_to_image at :0 0000002 gdk_pixbuf_get_from_drawable at :0 0000003 PAINTWINDOW(0x7fffffffc2d0, 0xd0a480) at gtk2/gtk2wscontrols.pp:1021 0000004 PAINTWIDGET(0x7fffffffc2d0, 0xcf28f0) at gtk2/gtk2wscontrols.pp:1038 0000005 PAINTTO(0x7ffff7fb36f0, 0x7ffff7fb30b0, 140737353635824, 0, 0) at gtk2/gtk2wscontrols.pp:1045 0000006 PAINTTO(0x7ffff7fb30b0, 140737353635824, 0, 0) at include/wincontrol.inc:4968 0000007 PAINTTO(0x7ffff7fb30b0, 0x7ffff04de480, 0, 0) at include/wincontrol.inc:4973 0000008 PAINTBOX1PAINT(0x7ffff7fbd3b0, 0x7ffff7fbdbd0) at r26u01.pas:36 0000009 PAINT(0x7ffff7fbdbd0) at include/graphiccontrol.inc:90 0000010 PAINT(0x7ffff7fbdbd0) at include/paintbox.inc:45 0000011 WMPAINT(0x7ffff7fbdbd0, {MSG = 15, UNUSEDMSG = 1431655765, DC = 140737353635824, PAINTSTRUCT = 0x0, RESULT = 0}) at include/graphiccontrol.inc:58 0000012 SYSTEM_TOBJECT_$__DISPATCH$formal at :0 *)