RandyTek
RandyTek

Reputation: 4754

How to know the count of arguments when implementing backtrace in C

I am implementing a backtrace function in C, which can output caller's info. like this

ebp:0x00007b28 eip:0x00100869 args:0x00000000 0x00640000 0x00007b58 0x00100082

But how can I know the count of arguments of the caller?

Thank you very much

Upvotes: 2

Views: 2988

Answers (4)

FrankH.
FrankH.

Reputation: 18227

You can deduce the numbers of arguments a function uses in 32bit x86 code under some circumstances.
If the code has been compiled to use framepointers, then a given function's stackframe extends between (highest address) EBP and (lowest address / stack top) ESP. Immediately above the stack end at EBP you find the return address, and again above that you'll have, if your code is using the C calling convention (cdecl), consecutively, arg[0...].

That means: arg[0] at [EBP + 4], arg[1] at [EBP + 8 ], and so on.

When you disassemble function, look for instructions referencing [EBP + ...] and you know they access function arguments. The highest offset value used tells you how many there are.

This is of course somewhat simplified; arguments with sizes different from 32bits, code that doesn't use cdecl but e.g. fastcall, code where the framepointer has been optimized makes the method trip, at least partially.

Another option, again for cdecl functions, is to look at the return address (location of the call into the func you're interested in), and disassemble around there; you will, in many cases, find a sequence push argN; push ...; push arg0; call yourFunc and you can deduce how many arguments were passed in this instance. That's in fact the only way (from the code alone) to test how many arguments were passed to functions like printf() in a particular instance. Again, not perfect - these days, compilers often preallocate stackspace and then use mov to write arguments instead of pushing them (on some CPUs, this is better since sequences of push instructions have dependencies on each other due to each modifying the stackpointers).

Since all these methods are heuristic this requires quite a bit of coding to automate. If compiler-generated debugging information is available, use that - it's faster.

Edit: There's another useful heuristic that can be done; Compiler-generated code for function calling often looks like this:

...
[ code that either does "push arg" or "mov [ESP ...], arg" ]
...
call function
add  ESP, ...

The add instruction is there to clean up stackspace used for arguments. From the size of the immediate operand, you know how much space the args this code gave to function has used, and by implication (assuming they're all 32bit, for example), you know how many there were.
This is particularly simple given you already have the address of said add instruction if you have working backtrace code - the instruction at the return address is this add. So you can often get away with simply trying to disassemble the (single) instruction at the return address, and see if it's an add ESP, ... (sometimes it's a sub ESP, -...) and if so, calculate the number of arguments passed from the immediate operand. The code for that is much simpler than having to pull in a full disassembly library.

Upvotes: 3

Coren
Coren

Reputation: 5637

Glibc implements a backtrace function. It unwind backtrace, arg by arg.

You can see how they've done it in sysdeps/$ARCH/backtrace.c. Beware that it's quite hard to read.

Upvotes: 0

BlackBear
BlackBear

Reputation: 22989

You can't. The number of arguments isn't saved anywhere, as you can see in this simple disassembly:

    f(5);
002B144E  push        5  
002B1450  call        f (2B11CCh)  
002B1455  add         esp,4  
    g(1, "foo");
002B1458  push        offset string "foo" (2B5740h)  
002B145D  push        1  
002B145F  call        g (2B11C7h)  
002B1464  add         esp,8  
    h("bar", 'd', 8);
002B1467  push        8  
002B1469  push        64h  
002B146B  push        offset string "bar" (2B573Ch)  
002B1470  call        h (2B11D1h)  
002B1475  add         esp,0Ch  

Basically, only the called function knows how many arguments it has.

Upvotes: 1

ugoren
ugoren

Reputation: 16441

As Yahia commented, there's no general way.

You'll probably need to parse the debug information placed by the debugger (assuming you have compiled with gcc -g).

Upvotes: 0

Related Questions