Files
0linux/common/udebug.pas
2023-10-06 15:59:41 +02:00

518 lines
14 KiB
ObjectPascal

{
********************************************************************************
SPackGui (common files)
Copyright (C) 2009-2013 Geoffray Levasseur <geoffray.levasseurbrandin@numericable.fr>.
Copyright (C) <date> <add your name and mail address here>
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.