Reputation: 4754
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
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
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
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
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