Delphi-получить полную трассировку стека на OSX
у меня есть приложение, которое может регистрировать stacktrace, который позже может использоваться для отладки.
в Windows я получил, используя отличный блок JCLDebug, предоставленный проектом JEDI.
теперь, когда мое приложение работает на OSX, я попал в небольшую заминку - я не знаю, как получить правильный stacktrace при возникновении исключения.
у меня есть основы вниз -
1) я могу получить stacktrace, используя "backtrace" (найдено в libSystem.dylib нужна)
2) результирующий backtrace может быть преобразован в номера строк с помощью.файл карты, предоставленный компоновщиком Delphi
проблема, с которой я остаюсь, - я не знаю, откуда позвонить в backtrace. Я знаю, что Delphi использует исключения Mach (в отдельном потоке), и что я не могу использовать сигналы posix, но это все, что мне удалось разобраться.
Я могу получить backtrace в " try...кроме блока, но, к сожалению, к этому моменту стек уже закончили.
как я могу установить правильный регистратор исключений, который будет работать сразу после возникновения исключения?
обновление:
согласно предложению 'Honza R, я взглянул на процедуру "GetExceptionStackInfoProc".
эта функция позволяет мне "внутри" процесса обработки исключений, но, к сожалению, оставляет меня с некоторыми из тех же проблем, которые я имел ранее.
прежде всего-на рабочем столе платформы, эта функция "GetExceptionStackInfoProc" - это просто указатель функции, который вы можете назначьте свой собственный обработчик информации об исключениях. Таким образом, из коробки Delphi не предоставляет никакого поставщика информации о стеке.
если я назначаю функцию "GetExceptionStackInfoProc" , а затем запускаю "backtrace" внутри нее, я получаю stacktrace, но эта трассировка относится к обработчику исключений, а не к потоку, который вызвал исключение.
'GetExceptionStackInfoProc' содержит указатель на "TExceptionRecord", но на это есть очень ограниченная документация.
Я могу выйти за пределы моей глубины, но как я могу получить stacktrace из правильной нити? Можно ли было бы ввести мою собственную функцию "backtrace" в обработчик исключений, а затем вернуться к стандартному обработчику исключений оттуда?
обновление 2
некоторые более подробности. Одно дело прояснить - этот вопрос касается исключений, которые обрабатываются сообщениями MACH, а не исключениями программного обеспечения, которые обрабатываются полностью в RTL.
Embarcadero выложил некоторые комментарии вместе с этими функциями -
System.Internal.MachExceptions.pas -> catch_exception_raise_state_identity
{
Now we set up the thread state for the faulting thread so that when we
return, control will be passed to the exception dispatcher on that thread,
and this POSIX thread will continue watching for Mach exception messages.
See the documentation at <code>DispatchMachException()</code> for more
detail on the parameters loaded in EAX, EDX, and ECX.
}
System.Internal.ExcUtils.pas -> SignalConverter
{
Here's the tricky part. We arrived here directly by virtue of our
signal handler tweaking the execution context with our address. That
means there's no return address on the stack. The unwinder needs to
have a return address so that it can unwind past this function when
we raise the Delphi exception. We will use the faulting instruction
pointer as a fake return address. Because of the fencepost conditions
in the Delphi unwinder, we need to have an address that is strictly
greater than the actual faulting instruction, so we increment that
address by one. This may be in the middle of an instruction, but we
don't care, because we will never be returning to that address.
Finally, the way that we get this address onto the stack is important.
The compiler will generate unwind information for SignalConverter that
will attempt to undo any stack modifications that are made by this
function when unwinding past it. In this particular case, we don't want
that to happen, so we use some assembly language tricks to get around
the compiler noticing the stack modification.
}
которые, похоже, ответственны за проблему, которую я имею.
когда я делаю stacktrace после того, как эта система исключений передала управление RTL, это выглядит так - (имея в виду, размотчик стека была заменена программой backtrace. Backtrace передаст управление на разматыватель, как только он будет завершен)
0: MyExceptionBacktracer
1: initunwinder in System.pas
2: RaiseSignalException in System.Internal.ExcUtils.pas
С RaiseSignalException
называется SignalConverter
, Я убежден, что backtrace
функция, предоставляемая libc, несовместима с изменениями, внесенными в стек. Таким образом, он не способен читать стек за пределами этой точки, но стек все еще присутствует под ним.
кто-нибудь знает, что с этим делать (или моя гипотеза правильно)?
обновление 3
мне наконец удалось получить правильные stacktraces на OSX. Огромное спасибо и Хонзе, и Себастьяну. Объединив обе их техники, я нашел то, что работает.
для всех, кто может извлечь выгоду из этого, вот основной источник. Имейте в виду, что я не совсем уверен, что это 100% правильно, если вы можете предложить улучшения, продолжайте. Этот метод зацепляется за исключение прямо перед раскручиванием Delphi стек в потоке ошибок и компенсирует любое повреждение фрейма стека, которое могло произойти заранее.
unit MyExceptionHandler;
interface
implementation
uses
SysUtils;
var
PrevRaiseException: function(Exc: Pointer): LongBool; cdecl;
function backtrace2(base : NativeUInt; buffer : PPointer; size : Integer) : Integer;
var SPMin : NativeUInt;
begin
SPMin:=base;
Result:=0;
while (size > 0) and (base >= SPMin) and (base <> 0) do begin
buffer^:=PPointer(base + 4)^;
base:=PNativeInt(base)^;
//uncomment to test stacktrace
//WriteLn(inttohex(NativeUInt(buffer^), 8));
Inc(Result);
Inc(buffer);
Dec(size);
end;
if (size > 0) then buffer^:=nil;
end;
procedure UnInstallExceptionHandler; forward;
var
InRaiseException: Boolean;
function RaiseException(Exc: Pointer): LongBool; cdecl;
var b : NativeUInt;
c : Integer;
buff : array[0..7] of Pointer;
begin
InRaiseException := True;
asm
mov b, ebp
end;
c:=backtrace2(b - {this is the compiler dependent value}, @buff, Length(buff));
//... do whatever you want to do with the stacktrace
Result := PrevRaiseException(Exc);
InRaiseException := False;
end;
procedure InstallExceptionHandler;
var
U: TUnwinder;
begin
GetUnwinder(U);
Assert(Assigned(U.RaiseException));
PrevRaiseException := U.RaiseException;
U.RaiseException := RaiseException;
SetUnwinder(U);
end;
procedure UnInstallExceptionHandler;
var
U: TUnwinder;
begin
GetUnwinder(U);
U.RaiseException := PrevRaiseException;
SetUnwinder(U);
end;
initialization
InstallExceptionHandler;
end.
2 ответов
можно использовать GetExceptionStackInfoProc
, CleanUpStackInfoProc
и GetStackInfoStringProc
на Exception
class вам нужно сохранить трассировку стека в GetExceptionStackInfoProc
а затем получить его с GetStackInfoStringProc
который будет вызван RTL, если вы используете StackTrace
свойства Exception
. Может быть, вы также могли бы взглянуть на https://bitbucket.org/shadow_cs/delphi-arm-backtrace что демонстрирует это на Android.
чтобы сделать это правильно на Mac OS X libc
backtrace
функция не может использоваться, потому что Delphi повредит стек рамка при вызове GetExceptionStackInfoProc
С Exception.RaisingException
. Необходимо использовать собственную реализацию, способную ходить по стеку с другого базового адреса, который можно исправить вручную.
код GetExceptionStackInfoProc
будет выглядеть так (я использовал XE5 для этого примера значение, добавленное к ebp ниже, может отличаться в зависимости от того, какой компилятор вы используете, и этот пример был протестирован только на Mac OS X, реализация Windows может отличаться или не отличаться):
var b : NativeUInt;
c : Integer;
buff : array[0..7] of Pointer;
begin
asm
mov b, ebp
end;
c:=backtrace2(b - {this is the compiler dependent value}, @buff, Length(buff));
//... do whatever you want to do with the stacktrace
end;
и backtrace2
функция будет выглядеть как это (обратите внимание, что условия остановки и другие проверки отсутствуют в реализации, чтобы гарантировать, что AVs не вызваны во время ходьбы стека):
function backtrace2(base : NativeUInt; buffer : PPointer; size : Integer) : Integer;
var SPMin : NativeUInt;
begin
SPMin:=base;
Result:=0;
while (size > 0) and (base >= SPMin) and (base <> 0) do begin
buffer^:=PPointer(base + 4)^;
base:=PNativeInt(base)^;
Inc(Result);
Inc(buffer);
Dec(size);
end;
if (size > 0) then buffer^:=nil;
end;
вы можете подключить себя к Разматывателю исключений. Затем вы можете вызвать backtrace, где происходит исключение. Вот пример. Блок SBMapFiles-это то, что я использую для чтения mapfiles. Не требуется получать стек вызовов исключений.
unit MyExceptionHandler;
interface
implementation
uses
Posix.Base, SysUtils, SBMapFiles;
function backtrace(result: PNativeUInt; size: Integer): Integer; cdecl; external libc name '_backtrace';
function _NSGetExecutablePath(buf: PAnsiChar; BufSize: PCardinal): Integer; cdecl; external libc name '__NSGetExecutablePath';
var
PrevRaiseException: function(Exc: Pointer): LongBool; cdecl;
MapFile: TSBMapFile;
const
MaxDepth = 20;
SkipFrames = 3;
procedure ShowCurrentStack;
var
StackLog: PNativeUInt; //array[0..10] of Pointer;
Cnt: Integer;
I: Integer;
begin
{$POINTERMATH ON}
GetMem(StackLog, SizeOf(Pointer) * MaxDepth);
try
Cnt := backtrace(StackLog, MaxDepth);
for I := SkipFrames to Cnt - 1 do
begin
if StackLog[I] = $BE00EF00 then
begin
WriteLn('---');
Break;
end;
WriteLn(IntToHex(StackLog[I], 8), ' ', MapFile.GetFunctionName(StackLog[I]));
end;
finally
FreeMem(StackLog);
end;
{$POINTERMATH OFF}
end;
procedure InstallExceptionHandler; forward;
procedure UnInstallExceptionHandler; forward;
var
InRaiseException: Boolean;
function RaiseException(Exc: Pointer): LongBool; cdecl;
begin
InRaiseException := True;
ShowCurrentStack;
Result := PrevRaiseException(Exc);
InRaiseException := False;
end;
procedure InstallExceptionHandler;
var
U: TUnwinder;
begin
GetUnwinder(U);
Assert(Assigned(U.RaiseException));
PrevRaiseException := U.RaiseException;
U.RaiseException := RaiseException;
SetUnwinder(U);
end;
procedure UnInstallExceptionHandler;
var
U: TUnwinder;
begin
GetUnwinder(U);
U.RaiseException := PrevRaiseException;
SetUnwinder(U);
end;
procedure LoadMapFile;
var
FileName: array[0..255] of AnsiChar;
Len: Integer;
begin
if MapFile = nil then
begin
MapFile := TSBMapFile.Create;
Len := Length(FileName);
_NSGetExecutablePath(@FileName[0], @Len);
if FileExists(ChangeFileExt(FileName, '.map')) then
MapFile.LoadFromFile(ChangeFileExt(FileName, '.map'));
end;
end;
initialization
LoadMapFile;
InstallExceptionHandler;
end.