後でデバッグに使用できるスタックトレースをログに記録できるアプリケーションがあります。
Windows では、JEDI プロジェクトが提供する優れた JCLDebug ユニットを使用して取得しました。
アプリケーションが OSX で実行されるようになったので、ちょっと問題が発生しました。例外が発生したときに正しいスタック トレースを取得する方法がわかりません。
私は基本を理解しています -
1) 「backtrace」を使用してスタックトレースを取得できます (libSystem.dylib にあります)
2) 結果のバックトレースは、Delphi のリンカーが提供する .map ファイルを使用して行番号に変換できます。
私が残した問題は、どこからバックトレースを呼び出すべきかわからないということです。Delphi が(別のスレッドで)Mach 例外を使用していること、および posix シグナルを使用できないことは知っていますが、これですべて解決できました。
「try...except」ブロックでバックトレースを取得できますが、残念ながら、その時点でスタックはすでに縮小されています。
例外が発生した直後に実行される適切な例外ロガーをインストールするにはどうすればよいですか?
アップデート:
「Honza R の提案に従って、「GetExceptionStackInfoProc」プロシージャを調べました。
この機能により、例外処理プロセスの「内部」に入ることができますが、残念ながら、以前と同じ問題がいくつか残っています。
まず第一に、デスクトップ プラットフォームでは、この関数 'GetExceptionStackInfoProc' は単なる関数ポインターであり、独自の例外情報ハンドラーで割り当てることができます。そのため、すぐに使用できる Delphi は、スタック情報プロバイダーを提供しません。
関数を「GetExceptionStackInfoProc」に割り当て、その中で「バックトレース」を実行すると、スタックトレースを受け取りますが、そのトレースは、例外の原因となったスレッドではなく、例外ハンドラに関連しています。
「GetExceptionStackInfoProc」には「TExceptionRecord」へのポインタが含まれていますが、これに関するドキュメントは非常に限られています。
私は自分の深さを超えているかもしれませんが、正しいスレッドからスタックトレースを取得するにはどうすればよいですか? 独自の「バックトレース」関数を例外ハンドラーに挿入し、そこから標準の例外ハンドラーに戻ることは可能でしょうか?
更新 2
いくつかの詳細。1 つ明確にしておくべきことがあります。この質問は、RTL 内で完全に処理されるソフトウェア例外ではなく、MACH メッセージによって処理される例外に関するものです。
エンバカデロは、これらの機能とともにいくつかのコメントを発表しました -
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.
}
私が抱えている問題の原因のようです。
この例外システムが制御を RTL に渡した後にスタックトレースを実行すると、次のようになります。完了)
0: MyExceptionBacktracer
1: initunwinder in System.pas
2: RaiseSignalException in System.Internal.ExcUtils.pas
RaiseSignalException
によって呼び出されるため、libc によって提供される関数は、スタックに加えられた変更と互換性がないSignalConverter
と考えるようになりました。backtrace
そのため、そのポイントを超えてスタックを読み取ることはできませんが、スタックはまだその下に存在しています。
それについて何をすべきか(または私の仮説が正しいかどうか)を知っている人はいますか?
アップデート 3
私はついにOSXで適切なスタックトレースを取得することができました. Honza と Sebastian の両方に感謝します。両方のテクニックを組み合わせることで、うまくいくものを見つけました。
これから恩恵を受ける可能性のある他の人のために、ここに基本的なソースがあります. 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 - $4 {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.