Jump to content
Wagner Landgraf

Getting Exception stack trace in 2021

Recommended Posts

I wonder what is a good way to get the stack trace when an exception happens? I know about madExcept and EurekaLog but I was willing to have something lighter and simpler.

After a long Google-Fu session, it looks like using JclDebug is still the way to go? I also read here and there that madExcept is better at such job than JclDebug, and since DebugEngine is provided by the madExcept author, I wonder if it (DebugEngine) is a better alternative than JclDebug?

Or is there even a more direct, straightforward way to get the stack trace these days?

 

 

Share this post


Link to post

I gave DebugEngine a try and it is very good. Sometimes it provides stack traces strange (method where exception happened, two inner methods of DebugEngine and then the real stack trace) but it's really easy to install and use. It is also unaffected by the two issues I have with MadExcept (TThread.FatalException and Exception.InnerException is rendered useless).

 

It comes with a utility to attach the map (or it's own compressed smap) format to the .EXE itself, however the final executable is larger than with MadExcept (guess the .map compression is not that advanced in DebugEngine than in MadExcept).

 

I don't have much experience with JclDebug... I tried it once, and I disliked every bit of it. Only to be able to see stack traces I had to include ~30 extra files in my project and the stack traces were not accurate.; so I gave up on it.

If you can fit into the free usage conditions of MadExcept I'd say go with that. Otherwise, DebugEngine (or a license for MadExcept) would be my choice.

 

P.s.: keep in mind that also MadExcept and DebugEngine is known to have stack issues if the .exe is packed with UPX.

Edited by aehimself
  • Like 1
  • Thanks 1

Share this post


Link to post
25 minutes ago, aehimself said:

I gave DebugEngine a try and it is very good. Sometimes it provides stack traces strange (method where exception happened, two inner methods of DebugEngine and then the real stack trace) but it's really easy to install and use. It is also unaffected by the two issues I have with MadExcept (TThread.FatalException and Exception.InnerException is rendered useless).

 

Thank you very much for the feedback. Since you mentioned you just "gave it a try", you are not using it anymore, and/or didn't use it in production?

I'm also interested in knowing how it is (and the other mentioned tools) when it comes to performance and stability, since I intend to use it in server applications.

Share this post


Link to post
1 hour ago, Wagner Landgraf said:

Thank you very much for the feedback. Since you mentioned you just "gave it a try", you are not using it anymore, and/or didn't use it in production?

I'm also interested in knowing how it is (and the other mentioned tools) when it comes to performance and stability, since I intend to use it in server applications.

I had ~2 releases with DebugEngine, then I switched back to MadExcept. It's stack traces are less messed up when packed with UPX. I'd personally prefer DebugEngine as it is not installing hooks (thus - not having the issues I mentioned) getting stack traces is it's main purpose.

 

Performance wise - you won't feel a difference tbh. None of these tools will make your application less stable or significantly slower.

  • Thanks 1

Share this post


Link to post
2 hours ago, Wagner Landgraf said:

and since DebugEngine is provided by the madExcept author

What makes you think that? madExcept is from Matthias Rauen (aka madshi) and DebugEngine is from Mahdi Safsafi.

Share this post


Link to post
2 minutes ago, Uwe Raabe said:

What makes you think that? madExcept is from Matthias Rauen (aka madshi) and DebugEngine is from Mahdi Safsafi.

Potato, Phothatho :classic_smile:

  • Haha 2

Share this post


Link to post
11 minutes ago, Uwe Raabe said:

What makes you think that? madExcept is from Matthias Rauen (aka madshi) and DebugEngine is from Mahdi Safsafi.

Indeed, I google'd a lot and for some reason I set a flag they were both the same person. I thought I read it somewhere, but it looks I was mistaken.

Share this post


Link to post
6 hours ago, Wagner Landgraf said:

Spot on! Thanks for letting me know I'm not completely insane yet. 😅

Well, well, well, I had that misconception too!

While I was puzzled why madshi provides the open source DebugEngine while he already has the madExcept commercial package, but I didn't think too much since Jeroen said so...

Maybe people confused by the name 'Mahdi' with 'madshi' the nickname :)

 

I shouldn't have that misconception - because IIRC I asked Mahdi Safsafi and he's a doctor from the Middle-east, while IIRC madshi is in Germany...

Edited by Edwin Yip
  • Like 1

Share this post


Link to post
2 hours ago, Edwin Yip said:

while IIRC madshi is in Germany

Indeed! Perhaps my advantage is that I had communicated with Matthias personally in German, so I never stumbled upon the name similarity.

Share this post


Link to post
On 5/14/2021 at 8:04 PM, Wagner Landgraf said:

but I was willing to have something lighter and simpler

That's is what we usually do. Initially, we had the good old JCL. It was the only thing we used it for.

 

Later, we figured out we don't really need it at all, it was sufficient to hook into a System.ExceptObjProc (and one or two other procedure variables) to point to our own exception handler. There, you can easily acquire the stack trace with with FastMM_Fulldebugmode.dll. It exports three handy methods to acquire the "frame based" and "raw" stack trace and, of course, to just get a string for the textual representation.

 

Internally, the binary also uses the JCL, but can be recompiled to use Eureka or madExcept. At least, it saves us from installing the JCL at all, and the DLL is just a few kilobytes. Pretty happy with that, since we also have full control of how the stack trace is formatted and what happens with it, once an exception gets raised...

Edited by Der schöne Günther

Share this post


Link to post
1 hour ago, Der schöne Günther said:

That's is what we usually do. Initially, we had the good old JCL. It was the only thing we used it for.

 

Later, we figured out we don't really need it at all, it was sufficient to hook into a System.ExceptObjProc (and one or two other procedure variables) to point to our own exception handler. There, you can easily acquire the stack trace with with FastMM_Fulldebugmode.dll. It exports three handy methods to acquire the "frame based" and "raw" stack trace and, of course, to just get a string for the textual representation.

 

Found the detailed steps: https://stackoverflow.com/a/1130506/133516

Edit 1: Would love to see an example of using `System.ExceptObjProc` and the other related procedure variables ;)

 

Edited by Edwin Yip

Share this post


Link to post

FWIW, there's a poor man's OS-based solution that gets addresses only

const
  DBG_STACK_LENGTH = 32;
type
  TDbgInfoStack = array[0..DBG_STACK_LENGTH - 1] of Pointer;
  PDbgInfoStack = ^TDbgInfoStack;

{$IFDEF MSWINDOWS}
function RtlCaptureStackBackTrace(FramesToSkip: ULONG; FramesToCapture: ULONG; BackTrace: Pointer;
  BackTraceHash: PULONG): USHORT; stdcall; external 'kernel32.dll';
{$ENDIF}

{$IFDEF MSWINDOWS}
procedure GetCallStackOS(var Stack: TDbgInfoStack; FramesToSkip: Integer);
begin
  ZeroMemory(@Stack, SizeOf(Stack));

  RtlCaptureStackBackTrace(FramesToSkip, Length(Stack), @Stack, nil);
end;
{$ENDIF}

function CallStackToStr(const Stack: TDbgInfoStack): string;
var Ptr: Pointer;
begin
  Result := '';
  for Ptr in Stack do
    if Ptr <> nil then
      Result := Result + Format('$%p', [Ptr]) + sLineBreak
    else
      Break;
end;

function GetExceptionStackInfo(P: PExceptionRecord): Pointer;
begin
  Result := AllocMem(SizeOf(TDbgInfoStack));
  GetCallStackOS(PDbgInfoStack(Result)^, 1); // исключаем саму функцию GetCallStackOS
end;

function GetStackInfoStringProc(Info: Pointer): string;
begin
  Result := CallStackToStr(PDbgInfoStack(Info)^);
end;

procedure CleanUpStackInfoProc(Info: Pointer);
begin
  Dispose(PDbgInfoStack(Info));
end;

procedure InstallExceptionCallStack;
begin
  SysUtils.Exception.GetExceptionStackInfoProc := GetExceptionStackInfo;
  SysUtils.Exception.GetStackInfoStringProc := GetStackInfoStringProc;
  SysUtils.Exception.CleanUpStackInfoProc := CleanUpStackInfoProc;
end;

procedure UninstallExceptionCallStack;
begin
  SysUtils.Exception.GetExceptionStackInfoProc := nil;
  SysUtils.Exception.GetStackInfoStringProc := nil;
  SysUtils.Exception.CleanUpStackInfoProc := nil;
end;

 

Edited by Fr0sT.Brutal
  • Like 1

Share this post


Link to post

That does the job of acquiring the stacktrace, yes.

 

This often goes hand in hand with the question "How do I act whenever an exception happens so I can, for example, log them". To intercept both operating system and Delphi exceptions, it should be enough to point System.ExceptObjProc and System.RaiseExceptObjProc to an own handler.

Share this post


Link to post
8 hours ago, Der schöne Günther said:

Internally, the binary also uses the JCL, but can be recompiled to use Eureka or madExcept.

Thank you for presenting another approach. But that still needs JCL, so in the end, it's using the "JCL way". From what I saw DebugEngine is also very lightweight and even easier to use than this approach. It also looks like it did extra job to gather a "more correct" call stack. It all boils down to reliability, as JCL is widely used, and DebugEngine doesn't seem to be so. I guess I will have to just try it for a while and see it myself.

Share this post


Link to post
59 minutes ago, Wagner Landgraf said:

But that still needs JCL

No, it doesn't. It's just a DLL with no further dependencies.

 

Internally, it might leverage something from the JCL, but it's just a binary 🤷‍♂️

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×