델파이기 위해 전체 스택 추적 OSX
문제
내가 있는 응용 프로그램 로그인할 수 있습니 stacktrace,할 수 있는 나중에 사용하여 디버깅할 수 있습니다.
윈도우에서,나는 받을 사용하여 우수한 JCLDebug 단위에 의해 제공됩이 프로젝트입니다.
이제 내가 응용 프로그램이 실행되 OSX 에서,나 비치-나가지 않을 얻는 방법을 알고 올바른 stacktrace 경우 예외가 발생합니다.
나는 기초를 가지고 다운
1)나는 얻을 수 있습 stacktrace 를 사용하여'프로그램'(에서 발견 libSystem.dylib)
2)결과:프로그램으로 변환될 수 있는 라인은 숫자를 사용하는.map 파일에 의해 제공됩 델파이의 링커
문제 내가 왼쪽으로는-I don't know where to 화 프로그램에서.내가 알고 있는 델파이 사용하는 마치 예외(별도의 스레드에서),나는 사용할 수 없습니다 posix 신호를 그러나 그 모든 것을 관리합니다.
를 얻을 수 있습 역추적에서 시도한다는'블록,그러나 불행하게도,그 시점에 스택은 이미를 마무리 지었다.
할 수 있는 방법을 설치하는 적절한 예외로거는 것이 바로 실행한 후에 예외가 발생?
업데이트:
따라'Honza R 의 제안,나는 보'GetExceptionStackInfoProc'절차입니다.
이 기능은 나를 얻는'내부'예외 처리 프로세스,그러나 불행하게도 나의 일부와 같은 문제는 되었습니다.
첫째 모든 데스크톱 플랫폼에서,이 기능'GetExceptionStackInfoProc'은 함수 포인터,할 수 있는 지정 당신의 자신의 예외 info handler.그래서 밖의 상자,델파이 제공되지 않 스택 정보 제공자입니다.
는 경우가 지정하는 기능'GetExceptionStackInfoProc'한 후 실행하는'프로그램은'그것의 내부가 나타납 stacktrace,그러나는 추적이 상대적 예외 처리기 스레드가 아닌 발생하는 예외입니다.
'GetExceptionStackInfoProc'이 포함되지 않은 포인터'TExceptionRecord'지만,거기에 아주 제한된 문서를 사용할 수 있습니다.
나 갈 수 있습을 넘어서 깊이 있지만,어떻게 받을 수 있 stacktrace 에서는 올바른 스레드가?이 가능한 것 나를 주입하는 내 자신의'프로그램은'기능으로 예외 처리기와 그 반환 기준에는 예외 처리기에 있는가?
업데이트 2
좀 더 상세정보.한 가지를 명확한 이 질문에는 예외에 대한 의해 처리됩 MACH 메시지지 않은 소프트웨어 처리되는 예외에서 RTL.
엠바카데로가 뻗어있는 일부 의견과 함께 이러한 기능-
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,그것은 다음과 같습니다-(마음에 베어링,스 unwinder 에 의해 대체되었습 역추적니다.이 프로그램은 것입니다 손으로 통제하 unwinder 이 완료되면)
0: MyExceptionBacktracer
1: initunwinder in System.pas
2: RaiseSignalException in System.Internal.ExcUtils.pas
이 RaiseSignalException
의 SignalConverter
, 로,나도는 믿 backtrace
기능에 의해 제공되었는지와 호환되는 수정 사항이 적다는 것이다.그래서,그것은 불가능을 읽고 스택을 넘어 그 시점,그러나 스택은 여전히 존재가 아래에 있습니다.
는 사람은 무엇을 해야할지에 대한(또는지 여부를 나 가설은 정확한)?
업데이트 3
나는 마지막으로 관리하는 적절한 stacktraces on OSX.거대한 덕분에 모두 Honza 및 세바스챤.을 결합하여 모두의 자신의 기술을 발견했다.
다른 사람을 위해로부터 혜택을 받을 수 있던 이 여기에서의 기본 소스입니다.베어는 마음에서 나는 아주 확실하지 않다면 그것은 100%정확한할 수 있는 경우,개선을 제안,앞으로 이동합니다.이 기술은 후크에는 예외 전 델파이 풀어 스택에 오류가 있는 쓰레드,그리고 보상에 대한 어떤 스택 구조 손상이 있는 장소를 촬영했습니다.
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.
해결책
당신이 사용할 수 있는 GetExceptionStackInfoProc
, CleanUpStackInfoProc
고 GetStackInfoStringProc
에 Exception
클래스 저장해야에 스택 추적 GetExceptionStackInfoProc
다이 검색 GetStackInfoStringProc
을 얻을 것이다라는 의 RTL 사용하는 경우 StackTrace
성 Exception
.어쩌면 당신은 수도에서 보 https://bitbucket.org/shadow_cs/delphi-arm-backtrace 는 안드로이드에서 이 방법을 보여 줍니다.
이렇게 하려면 제대로 Mac OS X libc
backtrace
기능하기 때문에 사용할 수 없습니다 델파이 손상됩니다 스택 구조를 호출할 때 GetExceptionStackInfoProc
서 Exception.RaisingException
.자신의 구현을 사용해야 할 수있는 걸어 스택에서 다른 기본 주소는 수정할 수 있습니다.
귀하의 GetExceptionStackInfoProc
다음 다음과 같이(내가 사용하는 xe5 를 갖습 이 예제에서 부가가치를 EBP 벨에 따라 달라질 수 있습는 컴파일러를 사용하고 이 예에서 테스트 되었습니다만 맥 OS X,윈도우 구현지 않을 수도 있습 다르다):
var b : NativeUInt;
c : Integer;
buff : array[0..7] of Pointer;
begin
asm
mov b, ebp
end;
c:=backtrace2(b - $14 {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;
다른 팁
할 수 있습 훅으로 자신을 제외 언.다음 호출할 수 있는 프로그램은 예외가 발생합니다.예제는 다음과 같습니다.단위 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.