Logging exceptions/ru

From Free Pascal wiki

English (en)русский (ru)


Вступление

Stacktrace иногда называют backtrace или call stack. Это список снимков стека, помещенных в стек, содержащих адрес возврата и локальные переменные. Поэтому трассировка стека полезна для отслеживания пути выполнения вашей программы (после вызова процедур).

Чтобы получить трассировку стека, компилятор сначала должен обратиться к сгенерированной отладочной информации с использованием ключей:

  • -g - генерировать отладочную информацию (в выходном формате по умолчанию для соответствующей платформы, который является dwarf2 на многих платформах)
  • -gl - генерировать номера строк для отладочной информации
  • -gs - генерировать отладочную информацию для устаревшего режима stabs; не используйте одновременно оба ключа -gs и -gw
  • -gw - генерировать отладочную информацию dwarf2; не используйте одновременно оба ключа -gs и -gw
  • -Xg - использовать внешний файл символов отладки


Модуль SysUtils

Этот модуль содержит некоторые процедуры, полезные для отладки исключений.

{ Процедуры обработки исключений }
function ExceptObject: TObject;
function ExceptAddr: Pointer;
function ExceptFrameCount: Longint;
function ExceptFrames: PPointer;

Модуль System

Этот модуль содержит некоторые связанные со стеком процедуры:

function SysBackTraceStr(Addr:Pointer): ShortString; // Адрес по умолчанию для преобразования строки, назначенный BackTraceStrFunc
procedure Dump_Stack(var f : text;bp:pointer); // Дамп стека в текстовый файл
procedure DumpExceptionBackTrace(var f:text); // Дамп backtrace в текстовый файл

Процедурная переменная BackTraceStrFunc отвечает за преобразование адреса памяти в строковую отладочную информацию. Поведение по умолчанию реализовано с помощью SysBackTraceStr.

Выводимая информация

Если выбран вывод отладочной информации в виде строк (переключатель компилятора -gl), в программу автоматически включается модуль lineinfo. Этот модуль гарантирует, что отладчики/обработчики исключений смогут найти номера строк выполняющегося кода. Это может быть полезно, если вы не хотите развертывать получающийся исполняемый файл с полной отладочной информацией, но вам нужна полезная информация при возникновении ошибок.

Stabs

Если используется старый формат stabs (-gs), функция BackTraceStrFunc переназначается на StabBackTraceStr.

DWARF

Если выбран формат отладки dwarf (ключ компилятора -gw), в программу автоматически включается модуль lnfodwrf, а функция BackTraceStrFunc преобразуется в DwarfBacktraceStr.

Модуль LCLProc

Этот модуль Lazarus имеет некоторые функции, связанные с отладкой:

//Отладка
procedure RaiseGDBException(const Msg: string);
procedure RaiseAndCatchException;
procedure DumpExceptionBackTrace;
procedure DumpStack;
function GetStackTrace(UseCache: boolean): string;
procedure GetStackTracePointers(var AStack: TStackTracePointers);
function StackTraceAsString(const AStack: TStackTracePointers;
                            UseCache: boolean): string;
function GetLineInfo(Addr: Pointer; UseCache: boolean): string;

Сброс текущего стека вызовов

См. также справку FPC [для получения] подробностей по сбросу стека/исключений:

procedure DumpCallStack;
var
  I: Longint;
  prevbp: Pointer;
  CallerFrame,
  CallerAddress,
  bp: Pointer;
  Report: string;
const
  MaxDepth = 20;
begin
  Report := '';
  bp := get_frame;
  // Этот трюк пропустит элемент SendCallstack
  // 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 := Report + BackTraceStrFunc(CallerAddress) + LineEnding;
       Inc(I);
       if (I >= MaxDepth) or (CallerFrame = nil) then
         Break;
       prevbp := bp;
       bp := CallerFrame;
     end;
   except
     { предотвратить бесконечный сброс, если произошло исключение}
   end;
  ShowMessage(Report);
end;

Сброс исключений стека вызовов

Стек вызовов исключения может быть получен через функции модуля SysUtils ExceptAddr, ExceptFrames и ExceptFrameCount.

uses SysUtils;

procedure DumpExceptionCallStack(E: Exception);
var
  I: Integer;
  Frames: PPointer;
  Report: string;
begin
  Report := 'Program exception! ' + LineEnding +
    'Stacktrace:' + LineEnding + LineEnding;
  if E <> nil then begin
    Report := Report + 'Exception class: ' + E.ClassName + LineEnding +
    'Message: ' + E.Message + LineEnding;
  end;
  Report := Report + BackTraceStrFunc(ExceptAddr);
  Frames := ExceptFrames;
  for I := 0 to ExceptFrameCount - 1 do
    Report := Report + LineEnding + BackTraceStrFunc(Frames[I]);
  ShowMessage(Report);
  Halt; // Конец выполнения программы
end;

Обработка исключений

Ручная обработка исключений

Ручное выполнение обработчика исключений может быть вставлено во многие места в коде.

try
  // ... какая-то операция, которая должна вызвать исключение...
  raise Exception.Create('Test error');
except
  on E: Exception do 
    DumpExceptionCallStack(E);
end;

ExceptProc из модуля System

Если происходит необработанное исключение, выполняется переменная процедуры ExceptProc. Поведение по умолчанию инициализируется процедурой InitExceptions из модуля System. Если вы хотите, эту процедуру можно переназначить на собственный обработчик.

Пример:

procedure CatchUnhandledException(Obj: TObject; Addr: Pointer; FrameCount: Longint; Frames: PPointer);
var
  Message: string;
  i: LongInt;
  hstdout: ^Text;
begin
  hstdout := @stdout;
  Writeln(hstdout^, 'An unhandled exception occurred at $', HexStr(PtrUInt(Addr), SizeOf(PtrUInt) * 2), ' :');
  if Obj is exception then
   begin
     Message := Exception(Obj).ClassName + ' : ' + Exception(Obj).Message;
     Writeln(hstdout^, Message);
   end
  else
    Writeln(hstdout^, 'Exception object ', Obj.ClassName, ' is not of class Exception.');
  Writeln(hstdout^, BackTraceStrFunc(Addr));
  if (FrameCount > 0) then
    begin
      for i := 0 to FrameCount - 1 do
        Writeln(hstdout^, BackTraceStrFunc(Frames[i]));
    end;
  Writeln(hstdout^,'');
end;

Событие TApplication.OnException

Это событие можно использовать для перезаписи глобального обработчика исключений приложения по умолчанию. Пользовательский механизм ведения журнала может обеспечивать показ настраиваемого диалога, запись в файл, консоль, отправку отчета на почту, ведение журнала на HTTP-сервер, например,

procedure TMainForm.CustomExceptionHandler(Sender: TObject; E: Exception);
begin
  DumpExceptionCallStack;
  Halt; // Конец выполнения программы
end;   

procedure TMainForm.FormCreate(Sender: TObject);
begin
  Application.OnException := @CustomExceptionHandler;
end;

procedure TMainForm.ButtonClick(Sender: TObject);
begin
  raise Exception.Create('Test');
end;

Обработка исключений доп.потока

Обработка исключений, возникающих в потоках, должна выполняться вручную. Метод основного потока TThread Execute вызывается из функции ThreadProc, расположенной в модуле Classes.

function ThreadProc(ThreadObjPtr: Pointer): PtrInt;
begin
  ...
  try
    Thread.Execute;
  except
    Thread.FFatalException := TObject(AcquireExceptionObject);
  end; 
  ...
end;

В этой функции метод Execute заключен в блок try-except, и все исключения обрабатываются присвоением объекта исключения свойству FatalException объекта TThread как объекта последнего возникшего исключения. Таким образом, исключения не отображаются для пользователя вообще.

В каждом потоке приложения должен быть вставлен отдельный блок try-except, чтобы перехватить все необработанные исключения с помощью специального обработчика исключений.

procedure TMyThread.Execute;
begin
  try
    // какой-нибудь ошибочный код
  except
    on E: Exception do 
      CustomExceptionThreadHandler(Self, E);
  end;
end;

Затем CustomExceptionThreadHandler может получить стек вызовов исключений и управлять отображением сообщений об ошибках или протоколированием отчетов журнала в файл. Поскольку обработчик выполняется из потока, показ диалога сообщений должен быть безопасным для потока с использованием метода Synchronize.

procedure TMainForm.CustomExceptionHandler(Thread: TThread; E: Exception);
begin
  Thread.Synchronize(DumpExceptionCallStack);
end;

Использование map-файла

Используйте ключ компилятора -Xm для генерации map-файла.

Исключения в DLL

todo

См.также

Внешние ссылки

  • esprinter - инструмент для получения трассировки стека из запущенной программы FreePascal, заданной идентификатором процесса и идентификатором потока (для Win32)
  • PascalBugReports - проект, имитирующий EurekaLog / MadExcept, но долгое время не разрабатывался