{ ******************************************************************************** SPackGui (common files) Copyright (C) 2009-2013 Geoffray Levasseur . Copyright (C) http://www.geoffray-levasseur.org/ http://0.tuxfamilly.org/ This software is governed by the CeCILL license under French law and abiding by the rules of distribution of free software. You can use, modify and/ or redistribute the software under the terms of the CeCILL license as circulated by CEA, CNRS and INRIA at the following URL "http://www.cecill.info". As a counterpart to the access to the source code and rights to copy, modify and redistribute granted by the license, users are provided only with a limited warranty and the software's author, the holder of the economic rights, and the successive licensors have only limited liability. In this respect, the user's attention is drawn to the risks associated with loading, using, modifying and/or developing or reproducing the software by the user in light of its specific status of free software, that may mean that it is complicated to manipulate, and that also therefore means that it is reserved for developers and experienced professionals having in-depth computer knowledge. Users are therefore encouraged to load and test the software's suitability as regards their requirements in conditions enabling the security of their systems and/or data to be ensured and, more generally, to use and operate it in the same conditions as regards security. The fact that you are presently reading this means that you have had knowledge of the CeCILL license and that you accept its terms. ******************************************************************************** Description: Console debuging facilities ******************************************************************************** } unit uDebug; {$include defines.inc} interface uses Classes, syncobjs, SysUtils, CustApp; type TVerboseLevel = (vlFull, vlHigh, vlLow, vlNone); THandleException = class private FHandleExceptionLock: TCriticalSection; FHandleExceptionMessage: String; FHandleExceptionBackTrace: TStringList; FOldExceptionHandler: TExceptionEvent; procedure ShowException; public constructor Create; reintroduce; destructor Destroy; override; procedure HandleException(Sender: TObject; E: Exception); procedure ThreadException(E: Exception; AThread: TThread); end; var iDebugLevel: Integer; vlVerboseLevel: TVerboseLevel; bLogError: Boolean; sLogFileName: string; tLogFile: Text; heHandleException: THandleException; ExceptionProc: TExceptionEvent; DebugLock: TCriticalSection; // init logging facilities function InitDbg(const FileName: string): Boolean; // close logging file procedure TerminateDbg; // print and log messages procedure PrintDbg(Mes: string; Verbosity: TVerboseLevel); procedure PrintDbg(Mes: string); procedure PrintLnDbg(Mes: string; Verbosity: TVerboseLevel); procedure PrintLnDbg(Mes: TStringList; Verbosity: TVerboseLevel); procedure PrintLnDbg(Verbosity: TVerboseLevel); procedure PrintLnDbg(Mes: string); procedure PrintDbgStat(Stat: Boolean; Verbosity: TVerboseLevel); procedure PrintDbgResult(Res: Boolean; Verbosity: TVerboseLevel); procedure PrintDbgResult(Res: string; Verbosity: TVerboseLevel); // handle manual exceptions when we want procedure HandleManualException(E: Exception); // show the call stack procedure DumpCallStack; // put full informations about heap inside a string function GetHeapStatus(aHS: THeapStatus): string; implementation uses Crt, Dialogs, Forms, uStrings; procedure DumpCallStack; var I: Longint; prevbp: Pointer; CallerFrame, CallerAddress, bp: Pointer; Report: TStringList; const MaxDepth = 30; begin Report := TStringList.Create; bp := get_frame; // This trick skip SendCallstack item // bp:= get_caller_frame(get_frame); try prevbp := bp - 1; I := 0; while bp > prevbp do begin CallerAddress := get_caller_addr(bp); CallerFrame := get_caller_frame(bp); if CallerAddress = nil then Break; Report.Add(BackTraceStrFunc(CallerAddress)); Inc(I); if (I >= MaxDepth) or (CallerFrame = nil) then Break; prevbp := bp; bp := CallerFrame; end; except { prevent endless dump if an exception occured } end; PrintLnDbg(Report, vlNone); Report.Free; end; procedure DumpExceptionCallStack(E: Exception); var I: Integer; Frames: PPointer; Report: TStringList; begin Report := TStringList.Create; Report.Add(rsErrorUnhandledException); if E <> nil then begin Report.Add(rsExceptionClass + E.ClassName); Report.Add(rsMessage + E.Message); end; Report.Add(rsStackTrace); Report.Add(BackTraceStrFunc(ExceptAddr)); Frames := ExceptFrames; for I := 0 to ExceptFrameCount - 1 do Report.Add(BackTraceStrFunc(Frames[I])); PrintLnDbg(Report, vlNone); Report.Free; {$IFNDEF Debug} Halt; // End of program execution (if not debugging) {$ENDIF} end; procedure HandleManualException(E: Exception); begin heHandleException.HandleException(nil, E); end; constructor THandleException.Create; begin FHandleExceptionLock := TCriticalSection.Create; FOldExceptionHandler := ExceptionProc; ExceptionProc := @HandleException; end; destructor THandleException.Destroy; begin inherited; ExceptionProc := FOldExceptionHandler; FreeAndNil(FHandleExceptionLock); end; procedure THandleException.HandleException(Sender: TObject; E: Exception); begin if MainThreadID = GetCurrentThreadId then begin DumpExceptionCallStack(E); MessageDlg(E.Message, mtError, [MBOK], 0); //create something better end else begin FHandleExceptionLock.Acquire; try FHandleExceptionMessage := E.Message; DumpExceptionCallStack(E); DumpCallStack; MessageDlg(FHandleExceptionMessage, mtError, [MBOK], 0); //same finally FHandleExceptionLock.Release; end; end; end; procedure THandleException.ThreadException(E: Exception; AThread: TThread); var Frames: PPointer; I: Integer; begin PrintLnDbg(Format(rsErrorInThread, [AThread.ClassName])); if MainThreadID = GetCurrentThreadId then begin DumpExceptionCallStack(E); MessageDlg(E.Message, mtError, [MBOK], 0); //create something better end else begin FHandleExceptionLock.Acquire; try FHandleExceptionBackTrace := TStringList.Create; FHandleExceptionMessage := E.Message; FHandleExceptionBackTrace.Add(rsErrorUnhandledException); if E <> nil then begin FHandleExceptionBackTrace.Add(rsExceptionClass + E.ClassName); FHandleExceptionBackTrace.Add(rsMessage + E.Message); end; FHandleExceptionBackTrace.Add(rsStackTrace); FHandleExceptionBackTrace.Add(BackTraceStrFunc(ExceptAddr)); Frames := ExceptFrames; for I := 0 to ExceptFrameCount - 1 do FHandleExceptionBackTrace.Add(BackTraceStrFunc(Frames[I])); TThread.Synchronize(AThread, @ShowException); finally FHandleExceptionLock.Release; end; end; end; procedure THandleException.ShowException; begin PrintLnDbg(FHandleExceptionBackTrace, vlNone); MessageDlg(FHandleExceptionMessage, mtError, [MBOK], 0); //create something better end; procedure LoggingError(Msg: string); begin bLogError := True; PrintLnDbg(Msg, vlLow); PrintLnDbg(rsWarningNoLogfile, vlLow); end; //create logfile and rotate old ones (10 max) function InitDbg(const FileName: string): Boolean; var Count: Byte; I: Integer; F: File; Orig, Dest: string; begin {$I-} //init some vars Count := 1; Result := True; //first step: do the logrotate (10 logfile max fixed) if FileExists(Trim(FileName)) then while FileExists(Trim(FileName) + '.' + IntToStr(Count)) do Inc(Count) else Count := 0; if Count > 0 then for I := Count downto 1 do begin Assign(F, Trim(FileName) + '.' + IntToStr(I - 1)); if I = 1 then Orig := Trim(FileName) else Orig := Trim(FileName) + '.' + IntToStr(I - 1); Dest := Trim(FileName) + '.' + IntToStr(I); if I >= 10 then if not FileIsReadOnly(Orig) then Erase(F) else begin LoggingError(Format(rsErrorLogrotate, [Trim(FileName)])); Result := False; Exit; end else if (not FileIsReadOnly(Orig)) and (not FileExists(Dest)) then RenameFile(Orig, Dest) else begin LoggingError(Format(rsErrorLogrotate, [Trim(FileName)])); Result := False; Exit; end; end; {$I+} //second step: check if file can be safely created and create it if FileExists(Trim(FileName)) then begin LoggingError(Format(rsErrorButLogrotate, [Trim(FileName)])); Result := False; Exit; end; Assign(tLogFile, Trim(FileName)); {$I-} Rewrite(tLogFile); {$I+} if IOResult <> 0 then begin LoggingError(Format(rsErrorCreatingLogfile, [Trim(FileName)])); Result := False; Exit; end; //third step: write header and assign vars bLogError := False; sLogFileName := Trim(FileName); PrintLnDbg(Format(rsInfoLoggingStarted, [sLogFileName]), vlLow); end; procedure TerminateDbg; begin PrintLnDbg(Format(rsInfoLoggingTerminated, [sLogFileName]), vlLow); if (not bLogError) and (Trim(sLogFileName) <> '') then CloseFile(tLogFile); end; function VerbosityToInt(V: TVerboseLevel): integer; begin Result := 4; //default is all the messages to be shown case V of vlFull: Result := 4; vlHigh: Result := 2; vlLow: Result := 1; vlNone: Result := 0; //nothing is displayed end; end; procedure PrintDbg(Mes: string; Verbosity: TVerboseLevel); var I: integer; Err: Boolean; C: Char; LineBegining: Boolean; begin //showing debugging message only if Debug is defined {$IFNDEF Debug} if LineBegining and (Length(Mes) > 2) and (Mes[2] = ' ') and (Mes[1] = 'D') then Exit; {$ENDIF} //if it's not meant to be displayed get out of here if VerbosityToInt(Verbosity) > VerbosityToInt(vlVerboseLevel) then Exit; //init error state {$I-} Err := False; DebugLock.Acquire; //display time only if we are on a new line try LineBegining := WhereX <= 1; if LineBegining or ((Length(Mes) > Length(sTimeJump)) and (Copy(Mes, 1, Length(sTimeJump)) = sTimeJump)) then begin Write('[' + TimeToStr(Time) + '] '); if (not bLogError) and (Trim(sLogFileName) <> '') then begin Write(tLogFile, '[' + TimeToStr(Time) + '] '); Err := (IOResult <> 0) or Err; end; //this is used in case where indentation is needed in log if iDebugLevel > 0 then for I := 0 to iDebugLevel do begin Write(' '); //this is 3 spaces if (not bLogError) and (Trim(sLogFileName) <> '') then begin Write(tLogFile, ' '); //this is 3 spaces Err := (IOResult <> 0) or Err; end; end; end; // we use the first char to know if it's error warning or info if LineBegining and (Length(Mes) > 2) and (Mes[2] = ' ') and (Mes[1] in ['E', 'W', 'i'{$IFDEF Debug}, 'D'{$ENDIF}]) then begin C := Mes[1]; Mes := Copy(Mes, 3, Length(Mes) - 2); case C of 'E': Mes := rsBaseError + Mes; 'W': Mes := rsBaseWarning + Mes; 'i': Mes := rsBaseInfo + Mes; {$IFDEF Debug} 'D': Mes := rsBaseDebug + Mes; {$ENDIF} end; end; //write the message Write(Mes); if (not bLogError) and (Trim(sLogFileName) <> '') then begin Write(tLogFile, Mes); Err := (IOResult <> 0) or Err; end; except Err := True; end; {$I+} //if error occurs somewhere, trigger an error if Err then LoggingError(Format(rsErrorWritingLogFile, [sLogFileName])); //force truely writing the file if (not bLogError) and (Trim(sLogFileName) <> '') then Flush(tLogFile); DebugLock.Release; end; procedure PrintDbg(Mes: string); begin PrintDbg(Mes, vlFull); end; procedure PrintLnDbg(Mes: TStringList; Verbosity: TVerboseLevel); var I: Integer; begin if VerbosityToInt(Verbosity) > VerbosityToInt(vlVerboseLevel) then Exit; for I := 0 to Mes.Count - 1 do if I = 0 then PrintLnDbg(Mes[I], Verbosity) else PrintDbg(sTimeJump + Mes[I] + sLineBreak, Verbosity); end; procedure PrintLnDbg(Mes: string); begin PrintLnDbg(Mes, vlFull); end; procedure PrintLnDbg(Mes: string; Verbosity: TVerboseLevel); begin if VerbosityToInt(Verbosity) > VerbosityToInt(vlVerboseLevel) then Exit; PrintDbg(Mes + sLineBreak, Verbosity); end; procedure PrintLnDbg(Verbosity: TVerboseLevel); begin if VerbosityToInt(Verbosity) > VerbosityToInt(vlVerboseLevel) then Exit; Writeln; if (not bLogError) and (Trim(sLogFileName) <> '') then Writeln(tLogFile); end; procedure PrintDbgStat(Stat: Boolean; Verbosity: TVerboseLevel); begin if VerbosityToInt(Verbosity) > VerbosityToInt(vlVerboseLevel) then Exit; if Stat then PrintDbg(dbgStatOk, Verbosity) else PrintDbg(dbgStatWarn, Verbosity); end; procedure PrintDbgResult(Res: Boolean; Verbosity: TVerboseLevel); begin if VerbosityToInt(Verbosity) > VerbosityToInt(vlVerboseLevel) then Exit; if Res then PrintLnDbg(dbgOk, Verbosity) else PrintLnDbg(dbgFail, Verbosity); end; procedure PrintDbgResult(Res: string; Verbosity: TVerboseLevel); begin if VerbosityToInt(Verbosity) > VerbosityToInt(vlVerboseLevel) then Exit; PrintLnDbg(Res, Verbosity); end; function GetHeapStatus(aHS: THeapStatus): string; begin Result := Format(rsHeapStatusDetails, [aHS.TotalAddrSpace, aHS.TotalUncommitted, aHS.TotalCommitted, aHS.TotalAllocated, aHS.TotalFree, aHS.FreeSmall, aHS.FreeBig, aHS.Unused, aHS.Overhead, aHS.HeapErrorCode]); end; initialization ExceptionProc := Application.OnException; {$IFDEF DEBUG} vlVerboseLevel := vlFull; {$ELSE} vlVerboseLevel := vlLow; {$ENDIF} iDebugLevel := 0; bLogError := True; //means that we can't write in the log file as it's not opened sLogFileName := ''; heHandleException := THandleException.Create; DebugLock := TCriticalSection.Create; finalization DebugLock.Free; FreeAndNil(heHandleException); end.