Runtime function name?

Started by
7 comments, last by TechnoCore 16 years, 2 months ago
Is there any way I can get hold of the name of a function in C++? I need this for debug purposes. The task system in my game has a lot of callback-functions, and would really like to be able to print out in which callback I'm inside at the moment. I know i can use the __FUNCTION__ macro from inside a function. But that does not work if I'm outside it. /TC
//TechnoCore
Advertisement
Not directly. Even the __FUNCTION__ macro is not supported on all platforms (as it was defined only starting C99).

Depending on the platform and whether symbol information has been stored (which is enabled in most debug builds), you might be able to do a call stack walk, but it is far from trivial. (If you're developing for Windows -- look into the MSDN documentation for things related to DbgHelp: try this).
Kippesoep
A call stack walk is really the "proper" way of doing it.

However, a quick-and-dirty method which pretty much works on any platform is to simply obtain the CPU's instruction pointer, and reference this within the .map file generated by your linker.

I've used this on both PS2 and GameCube before to provide a very useful stack trace whenever our application did something bad. It came in rather handy when there wasn't a debugger attached. :)
Quote:Original post by TechnoCore
Is there any way I can get hold of the name of a function in C++?

I need this for debug purposes. The task system in my game has a lot of callback-functions, and would really like to be able to print out in which callback I'm inside at the moment.

I know i can use the __FUNCTION__ macro from inside a function. But that does not work if I'm outside it.

/TC


You could have the callback-registration accept an extra parameter to hold the function name, and optionally make a macro to add __FUNCTION__ to the parameters when you do the registration. Of course, it will be slower, but this is a debug build after all :) (You may want to set up extra macros so that you can easily get rid of the debug code.)
Quote:Original post by bpoint
However, a quick-and-dirty method which pretty much works on any platform is to simply obtain the CPU's instruction pointer, and reference this within the .map file generated by your linker.

I'd like to hear more about this :)
Would you mind sharing a few more details? I'm not too familliar with that kind of low level hackery (so to speak) [smile]

It's obviously platform dependent, but the theory is approximately the same on all platforms. Basically, the CPU tracks what's currently executing for a thread with a single register. This can usually called the Instruction Pointer or Program Counter. On the x86, you'd grab it from the EIP register, however, most of the time, the EIP is protected from being directly read by the application, so you need to resort to some trickery to grab it. On XP and later you can use RtlCaptureContext() to fill a CONTEXT structure that contains the instruction pointer. Some platforms allow inline assembly such as:
    __asm    call x    __asm x: pop eax    __asm    mov ctx.Eip, eax

However, some Vista boxes don't like this for reasons I have yet to fully understand.

In any case, once you have an instruction pointer, you can match that address to your application's functions. This process depends on your tool chain and sometimes the platform itself supports methods to do so at runtime. For example, on Windows machines, you can use SymFromAddr() to translate the instruction pointer to a function symbol, provided that the symbol information is available to the application. (Ex: a .PDB)
The CALL/POP approach is problematic - it breaks the 'return address stack' microarchitecture optimization in Athlon CPUs. Because CALL is not paired with RET, the stack will rapidly overflow and decrease function call performance because the return address cannot be determined as quickly as before.
It is preferable to grab EIP from *_AddressOfReturnAddress (MSVC-specific; GCC has something similar).
E8 17 00 42 CE DC D2 DC E4 EA C4 40 CA DA C2 D8 CC 40 CA D0 E8 40E0 CA CA 96 5B B0 16 50 D7 D4 02 B2 02 86 E2 CD 21 58 48 79 F2 C3
Quote:Original post by janta
Would you mind sharing a few more details? I'm not too familliar with that kind of low level hackery (so to speak) [smile]

Obtaining the CPU's instruction pointer is platform-specific, so it really depends on what hardware you're working with.

If you're working under Win32, SiCrane's assembly code will work just fine. You can take it a step further and work around the Athlon optimization by doing this instead (I'm assuming you're using Microsoft's compiler):
#define GET_EIP(_var)	__asm		push  eax	__asm		call  __l_x	__asm		jmp   __l_y	__asm __l_x:	pop   eax	__asm		push  eax	__asm		mov   _var, eax	__asm		ret	__asm __l_y:    pop   eax

You will need to append backslashes after each line, so the #define macro parses properly. I originally had them there, but the forum post decided to eat them. :(

Anyway, you can then use this like so:
void test(void){	unsigned long _eip;	GET_EIP(_eip);}

...and the variable _eip will have the instruction pointer stored into it. In my particular setup (yours will most likely be different), _eip has the value of 0x00425cc3.

I then open up the .map file, and look for a function that has a range where the address is located. Here's a small extract of the .map file that was generated with this test:
 0002:00002bd0       ??2@YAPAXI@Z               00425bd0 f i test.obj 0002:00002c40       ??3@YAXPAX@Z               00425c40 f i test.obj 0002:00002ca0       ?test@@YAXXZ               00425ca0 f   test.obj 0002:00002d00       _Sleep@4                   00425d00 f   kernel32:KERNEL32.dll 0002:00002d06       _WriteConsoleA@20          00425d06 f   kernel32:KERNEL32.dll

Look in the third column -- those addresses are where the executable image has been mapped into memory. Since _eip = 0x00425cc3, it lies between the range of 00425ca0 and 00425d00, so it was called within the test() function.

You can easily have your application load and parse the .map file to find a particular address. GCC and other compilers will output a slightly different format of .map file, however. And there's also the issue with C++ name mangling. But for the most part, it gets the job done. :)
Thanks all for the in-depth answers!

I did it in an easy way for now, using callback-registration to accept an extra parameter to hold the function name, like Zahlman said. It works for the moment :)

I will try grabbing the instruction pointer and using the .map later, it sounds really interesting :D


Thank you
-TC
//TechnoCore

This topic is closed to new replies.

Advertisement