Recent

Author Topic: Getting Caller function or Procedure.  (Read 5852 times)

jamie

  • Hero Member
  • *****
  • Posts: 6090
Getting Caller function or Procedure.
« on: September 03, 2017, 04:00:21 pm »
When debugging you can use the stack track determine who called the current function..

I know how this is done in the background via ASM but is there a ready made function in
Fpc that returns the pointer to the procedure or function that it will return back to?

 Like for example, GetReturnCallAddress or something like that ? I would like to identify
what procedure or function called the current block, just like stack track does but only part of
your app..

 Like I said, I can do this with ASM inline but I would like to do this at a more human level, one
that is cross platform friendly.
The only true wisdom is knowing you know nothing

jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Getting Caller function or Procedure.
« Reply #1 on: September 03, 2017, 04:41:15 pm »
I think I found it.
Get_caller_Addr

and a couple of others that go with it.
The only true wisdom is knowing you know nothing

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1312
    • Lebeau Software
Re: Getting Caller function or Procedure.
« Reply #2 on: September 06, 2017, 12:06:27 am »
If you figure out how to use it, let me know.  Indy has an IndyRaiseOuterException() function that just throws an exception at the caller's return address (ie, "raise E at <address>") on systems where SysUtils.Exception.RaiseOuterException() is not available (like FreePascal).  On systems where Delphi's System.ReturnAddress intrinsic is not available (ie, "raise E at ReturnAddress"), I resort to inline assembly to get the return address manually.

It would be nice if FreePascal had something like Delphi's ReturnAddress intrinsic.  But it doesn't, and I wasn't sure what capabilities FreePascal actually had, so IndyRaiseOuterException() only uses "raise E" and not "raise E at <address>".

I just found this FreePascal documentation, The raise statement, which has this code example:

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}  
  2. uses sysutils;  
  3.  
  4. procedure error(Const msg : string);  
  5.  
  6. begin  
  7.   raise exception.create(Msg) at  
  8.     get_caller_addr(get_frame),  
  9.     get_caller_frame(get_frame);  
  10. end;  
  11.  
  12. procedure test2;  
  13.  
  14. begin  
  15.   error(’Error’);  
  16. end;  
  17.  
  18. begin  
  19.   test2;  
  20. end.

Does get_caller_addr() work in all modes, or only in objfpc mode?  Indy runs in Delphi mode.  Does get_caller_addr(get_frame) work even if stack frames are disabled?  And why is get_caller_frame() being passed as an argument to "raise ... at"?
« Last Edit: September 06, 2017, 12:11:08 am by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

Bi0T1N

  • Jr. Member
  • **
  • Posts: 85
Re: Getting Caller function or Procedure.
« Reply #3 on: August 08, 2020, 11:04:18 am »
Does get_caller_addr() work in all modes, or only in objfpc mode?
It's independent of the mode. :)
Running your example in Lazarus (Delphi mode) reports the exception at line 15 instead of line 7.

Does get_caller_addr(get_frame) work even if stack frames are disabled?
I've changed your example to fit the requirements to remove the stackframe but it still reports it at line 17.

Code: Pascal  [Select][+][-]
  1. program test;
  2.  
  3. {$mode delphi}
  4. {$STACKFRAMES OFF}
  5.  
  6. uses sysutils;
  7.  
  8. procedure error;
  9. begin
  10.   raise exception.create('err') at
  11.     get_caller_addr(get_frame),
  12.     get_caller_frame(get_frame);
  13. end;
  14.  
  15. procedure test;
  16. begin
  17.   error;
  18. end;
  19.  
  20. begin
  21.   test;
  22. end.

Maybe I did something wrong or is not correctly documented? :-\
« Last Edit: August 08, 2020, 11:25:10 am by Bi0T1N »

jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Getting Caller function or Procedure.
« Reply #4 on: August 08, 2020, 02:16:36 pm »
Looks like it's being used incorrectly ?

You need to first use the "Get_Caller_StackInfo" which defines two pointers to code return and stack frame data.

 But if there is no stack then those pointers should be NIL in which you don't call any following functions..

 Its been a while since I've looked at the code when I implemented the use of these functions so I would need to look again but I do know I had/have working code using these functions and the original intent was to determine who was making the call.
The only true wisdom is knowing you know nothing

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: Getting Caller function or Procedure.
« Reply #5 on: August 08, 2020, 06:55:29 pm »
Does get_caller_addr() work in all modes, or only in objfpc mode?  Indy runs in Delphi mode.  Does get_caller_addr(get_frame) work even if stack frames are disabled?  And why is get_caller_frame() being passed as an argument to "raise ... at"?

As Bi0T1N wrote the functions are independant of the mode, if a frame happens to be missing due to optimizations it will be skipped (in the worst case your exception will appear to be at the wrong location in the stack trace) and it is usually passed, because the stack walking mechanism uses it. If it's not given FPC will simply pass the current frame pointer value. I think the use of it is the cross platform nature of FPC.

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1312
    • Lebeau Software
Re: Getting Caller function or Procedure.
« Reply #6 on: August 11, 2020, 01:46:45 am »
So, just to be clear:

Below is what Indy currently does in Delphi, depending on compiler version ({$IFDEF}'s and {$I}'s omitted for brevity):

Code: Pascal  [Select][+][-]
  1. procedure IndyRaiseOuterException(AOuterException: Exception);
  2. begin
  3.   Exception.RaiseOuterException(AOuterException);
  4. end;

Or:

Code: Pascal  [Select][+][-]
  1. procedure IndyRaiseOuterException(AOuterException: Exception);
  2. begin
  3.   raise AOuterException at ReturnAddress;
  4. end;

Or:

Code: Pascal  [Select][+][-]
  1. // disable stack frames to reduce instructions
  2. {$W-}
  3. procedure IndyRaiseOuterException(AOuterException: Exception);
  4.   procedure RaiseE(E: Exception; ReturnAddr: Pointer);
  5.   begin
  6.     raise E at ReturnAddr;
  7.   end;
  8. asm
  9.   // AOuterException is already in EAX...
  10.   // MOV EAX, AOuterException
  11.   MOV EDX, [ESP]
  12.   JMP RaiseE
  13. end;
  14. {$W+}

Whereas on FreePascal, Indy does the following instead:

Code: Pascal  [Select][+][-]
  1. procedure IndyRaiseOuterException(AOuterException: Exception);
  2. begin
  3.   raise AOuterException;
  4. end;

For FreePascal, will the following work, in all versions and modes of FPC?  Is it affected by, or can be optimized by, any compiler switches?  And how far back is this actually supported?

Code: Pascal  [Select][+][-]
  1. procedure IndyRaiseOuterException(AOuterException: Exception);
  2. begin
  3.   raise AOuterException at get_caller_addr(get_frame), get_caller_frame(get_frame);
  4. end;
« Last Edit: August 11, 2020, 09:01:59 pm by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: Getting Caller function or Procedure.
« Reply #7 on: August 11, 2020, 09:54:57 am »
For FreePascal, will the following work, in all versions and modes of FPC?

Yes.

Is it affected by, or can be optimized by, any compiler switches?

It might be affected by auto inlining, though it might be that FPC does not inline functions that have raise statements with an at-clause (I'd have to check that).

And how far back is this actually supported?

Is April 24th 2000 old enough?

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1312
    • Lebeau Software
Re: Getting Caller function or Procedure.
« Reply #8 on: August 11, 2020, 09:26:42 pm »
For FreePascal, will the following work, in all versions and modes of FPC?

Yes.

And, if stack frames are turned on/off?  I guess according to this documentation, there will always be a stack frame in my example, since there is a parameter present, right?

And how far back is this actually supported?

Is April 24th 2000 old enough?

Well, I meant actual compiler version number.  Based on this, I assume 2.0.  OK.  Good enough.
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: Getting Caller function or Procedure.
« Reply #9 on: August 12, 2020, 09:50:06 am »
For FreePascal, will the following work, in all versions and modes of FPC?

Yes.

And, if stack frames are turned on/off?  I guess according to this documentation, there will always be a stack frame in my example, since there is a parameter present, right?

Looking at the code for the stackframe optimization it could happen on certain platforms/targets. I would use the code nevertheless, because that is something that should be fixed in the compiler if the at-clause is used.
Sadly one can't check whether an optimization is enabled or not and they aren't affected by $PUSH / $POP either otherwise I'd suggest you to disable the stackframe optimization for that function, though you could do that if it's in a different unit or is the last function of a unit. Or, if you don't intend to do other changes to optimizations in that unit you could do {$OPTIMIZATION NOSTACKFRAME} { function } {$OPTIMIZATION DEFAULT} (Note: the name of the optimization is STACKFRAME, using NOSTACKFRAME disables it if it's enabled) To be on the safe side you could also add {$OPTIMIZATION NOAUTOINLINE} or {$OPTIMIZATION NOSTACKFRAME, NOAUTOINLINE}.

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1312
    • Lebeau Software
Re: Getting Caller function or Procedure.
« Reply #10 on: August 12, 2020, 06:27:16 pm »
Looking at the code for the stackframe optimization it could happen on certain platforms/targets. I would use the code nevertheless, because that is something that should be fixed in the compiler if the at-clause is used.

OK.

Sadly one can't check whether an optimization is enabled or not

Isn't that what {$IFOPT} is meant for?  At least for optimizations with short-letter directives, like {$W} for {$STACKFRAMES}.

and they aren't affected by $PUSH / $POP either otherwise I'd suggest you to disable the stackframe optimization for that function, though you could do that if it's in a different unit or is the last function of a unit.

Neither of those are true, it is right smack in the middle of the unit.

Or, if you don't intend to do other changes to optimizations in that unit you could do {$OPTIMIZATION NOSTACKFRAME} { function } {$OPTIMIZATION DEFAULT} (Note: the name of the optimization is STACKFRAME, using NOSTACKFRAME disables it if it's enabled)

I wasn't aware of {$OPTIMIZATION} before, thanks.

To be on the safe side you could also add {$OPTIMIZATION NOAUTOINLINE} or {$OPTIMIZATION NOSTACKFRAME, NOAUTOINLINE}.

AUTOINLINE is not listed on 1.2.58 $OPTIMIZATION (see Undocumented optimization compiler directives), but it is mentioned in passing on Optimization.
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: Getting Caller function or Procedure.
« Reply #11 on: August 12, 2020, 09:58:22 pm »
Sadly one can't check whether an optimization is enabled or not

Isn't that what {$IFOPT} is meant for?  At least for optimizations with short-letter directives, like {$W} for {$STACKFRAMES}.

These are two different things. The option controls whether stackframes are enforced while the optimization controls whether they are stripped away.

To illustrate:
- if the option is set, but the optimization is not, then stackframes are generated for all routines
- if the option is set and the optimization is as well, then stackframes are generated for all routines
- if the option is not set, but the optimization is, then stackframes will be omitted when possible
- if the option is not set and the optimization is not either, then stackframes will be generated for all routines except assembly routines in mode Delphi with no local variables

Note: Both the option and the optimization are local, thus they (in this case) apply to the routine level, not a unit level.

The default is that $W is not set and the stackframe optimization is set together with -O2.

But you mentioned a good point: by protecting the routine with {$push}{$W+} { function } {$pop} you can enforce the generation of a stackframe for that function.

To be on the safe side you could also add {$OPTIMIZATION NOAUTOINLINE} or {$OPTIMIZATION NOSTACKFRAME, NOAUTOINLINE}.

AUTOINLINE is not listed on 1.2.58 $OPTIMIZATION (see Undocumented optimization compiler directives), but it is mentioned in passing on Optimization.

Best would be to file a documentation bug. Please note that AUTOINLINE is (for whatever reason) not part of the supported optimization list for any CPU target and thus it won't be listed in fpc -io either.

 

TinyPortal © 2005-2018