void myFunc(char dummy) {
char *addrFirstArg = &dummy;
}
int main() {
char dummy = 42;
myFunc(dummy);
return 0;
}
I run the above under gdb and add a breakpoint at myFunc. I step once to compute the addrFirstArg value and examine it.
I also do
info frame
to spit out information about the frame myFunc. As far as my understanding of the C stack implementation goes, I expect that addrFirstArg should be 8 bytes above the base pointer for the frame myFunc.
This is the output that I see:
(gdb) p &dummy
$1 = 0xffffd094 "*\202\f\b\032\004"
(gdb) info frame
Stack level 0, frame at 0xffffd0b0:
eip = 0x8048330 in findStackBottom (reporter.c:64); saved eip 0x8048478
called by frame at 0xffffd170
source language c.
Arglist at 0xffffd0a8, args: dummy=42 '*'
Locals at 0xffffd0a8, Previous frame's sp is 0xffffd0b0
Saved registers:
ebp at 0xffffd0a8, eip at 0xffffd0ac
(gdb) x/1c 0xffffd0b0
0xffffd0b0: 42 'a'
Thus, inside the frame myFunc, ebp points to the location 0xffffd0a8, where as the address of dummy is 0xffffd094, which is 0x14 bytes below ebp, instead of being 0x8 bytes above it.
This ‘discrepancy’ disappears if I declare my dummy to be an int and myFunc to take in an int argument.
I’m really intrigued by this behavior.
It was reproducible – I ran it a bunch of times.
You see the differences better if you use
gcc -S; in the char case we haveWhen the function is entered, the stack is (top on top):
This is because the single char is “pushed” on the stack this way
and the x86 is little endian.
After the “preamble” the situation is like this
The “char” (stored as 32 bit integer) is then taken from ebp+8 (the original value “pushed” by the main, but as “32 bit”) to eax and then the lower less significant byte is put in a local storage.
The int case is simpler since we don’t need alignments and we can take “directly” the address of whatever was on the stack.
So, in the first case (the char case), esp is decremented by 4 more bytes to hold the single char: there’s an extra local storage.
Why this?
As you have seen, the single char is pushed on stack as a 32bit “integer” (eax), and it is taken back in eax in the same way. This opeartion has no endianness problem.
But, what if it would give back the address of ebp+8 for the char and the machine is no little endian? In that case, ebp+8 points to
00 00 00 2Aand deferencing with*dummywould give 0, not 42.So, once the “fake int” is taken (operation that the CPU handles coherently whatever the endianness is) into a register, the LSByte must be put in a local storage so that its address is guaranteed to point to that char (lower byte) when deferenced. This is the reason for the extra code and the fact that the ebp+8 is not used: endianness altogether with the requirements of the address being aligned (e.g. the 2A in
00 00 00 2Ain the big endian case would have an odd address.