2

I have the following c programme:

void function(int a, int b, int c) {
  char buffer1[]="aaaaa";
  char buffer2[]="bbbbbbbbbb";
}
int main() {
  function(1,2,3);
  return 0;

}

When i print frame information when executing the function, I get the following gdb output:

(gdb) info frame
Stack level 0, frame at 0x7fffffffe1c0:
 rip = 0x40119b in function (ss1.c:4); saved rip = 0x4011ca
 called by frame at 0x7fffffffe1d0
 source language c.
 Arglist at 0x7fffffffe1b0, args: a=1, b=2, c=3
 Locals at 0x7fffffffe1b0, Previous frame's sp is 0x7fffffffe1c0
 Saved registers:
  rbp at 0x7fffffffe1b0, rip at 0x7fffffffe1b8
(gdb) 

When printing the addresses of the function arguments and local variables, I get:

(gdb) p/x &c
$65 = 0x7fffffffe184
(gdb) p/x &b
$66 = 0x7fffffffe188
(gdb) p/x &a
$67 = 0x7fffffffe18c
(gdb) p/x &buffer1
$68 = 0x7fffffffe197
(gdb) p/x &buffer2
$69 = 0x7fffffffe19d
  1. Why is there a gap of 11 bytes between the address of arg a and that of var buffer1 -and not just a gap of 4 bytes which is the size of a?

  2. Why is there a gap of 19 bytes between the address of buffer2 and the frame pointer (0x7fffffffe1b0) -and not just a gap of 11 bytes which is the size of buffer2?

Thanks

korppu73
  • 237
  • 1
  • 2
  • 5
  • This is very compiler and option dependent. Please provide compile (I assume gcc) and options used. – chux - Reinstate Monica Oct 01 '18 at 21:00
  • 1
    Padding for alignment and stack canaries are possibilities. Have you checked [this thread](https://stackoverflow.com/questions/1061818/stack-allocation-padding-and-alignment)? – Reticulated Spline Oct 01 '18 at 21:00
  • Yes, I'm using gcc with no particular option except -g for debugging. Thanks for link to other post. According to that post, the addresses would need to be aligned to multiples of 8 addresses?, which is not the case with buffer1 and buffer2. – korppu73 Oct 01 '18 at 21:20
  • @korppu73 `char` arrays don't need to be aligned. – Barmar Oct 01 '18 at 21:29
  • @Barmar gcc alligns the char arrays to the allignment set in the linker script. If there is no explicit linker script given - the native word length – 0___________ Oct 01 '18 at 21:45

3 Answers3

0

just run the simple program:

#include <stdio.h>

void function(int a, int b, int c) 
{
  char buffer1[]="aaaaa";
  char buffer2[]="bbbbbbbbbb";

  printf("%p = &a\n", &a);
  printf("%p = &b\n", &b);
  printf("%p = &c\n", &c);
  printf("%p = &buffer1 sizeof(buffer1) = %zu\n", buffer1, sizeof(buffer1));
  printf("%p = &buffer2 sizeof(buffer2) = %zu\n", buffer2, sizeof(buffer2));
  printf("%zu = &buffer - &a\n", (char *)buffer1 - (char *)&a);
}

int main() 
{
  function(1,2,3);
  return 0;
}

and result is exactly as expected.

0x7fff9d9d830c = &a                                                                                                                                                                                                                                           
0x7fff9d9d8308 = &b                                                                                                                                                                                                                                           
0x7fff9d9d8304 = &c                                                                                                                                                                                                                                           
0x7fff9d9d8310 = &buffer1 sizeof(buffer1) = 6                                                                                                                                                                                                                 
0x7fff9d9d8320 = &buffer2 sizeof(buffer2) = 11                                                                                                                                                                                                                
4 = &buffer - &a 

Try to run it on your system.

0___________
  • 60,014
  • 4
  • 34
  • 74
  • The ouput of running your code is: `0x7ffe210676cc = &a 0x7ffe210676c8 = &b 0x7ffe210676c4 = &c 0x7ffe210676d7 = &buffer1 sizeof(buffer1) = 6 0x7ffe210676dd = &buffer2 sizeof(buffer2) = 11 11 = &buffer - &a` I compiled with: gcc -o test -g test.c – korppu73 Oct 02 '18 at 07:08
  • @korppu73 Where do you see the gaps – 0___________ Oct 02 '18 at 07:57
  • I don't get the same ouput if I just run the program on the command line or if I run it from gdb. When I run it from gdb, I get different addresses for the buffer variables and I get a 19 byte gap between the address of buffer2 and the frame pointer – korppu73 Oct 02 '18 at 09:07
  • There is something wrong with your gdb. Or your compiler adds some code (guardians etc). BTW what you see with no gdb is important. – 0___________ Oct 02 '18 at 09:21
0

This should put you on the right path, but doesn't answer the actual gap:

  • The stack grows downwards and you are trying to read it upwards
  • What you are seeing as &a, &b and &c are not the passed parameters, but local storage that can be used if (e.g.) you say a=1 within function(). I believe that these get optimised out if you don't do -O0
  • a, b and c are passed through registers to the function instead of the stack, so you won't find them in there twice. I.e. the caller doesn't push them to the stack.
  • buffer1 and buffer2 are not aligned in the stack and are packed together as strings.

E.g. Just after (before) buffer2, you can find the saved RBP value and then the return address. For me:

(gdb) p &buffer1
$102 = (char (*)[6]) 0x7fffffffde82
(gdb) p &buffer2
$103 = (char (*)[11]) 0x7fffffffde77

(buffer1 ends at 0x7fffffffde87)

And then the saved RBP:

(gdb) p/x (char [8]) *0x7fffffffde88
$104 = {0xb0, 0xde, 0xff, 0xff, 0xff, 0x7f, 0x0, 0x0}

And then the return address:

(gdb) p/x (char [8]) *0x7fffffffde90
$105 = {0x80, 0x51, 0x55, 0x55, 0x55, 0x55, 0x0, 0x0}

Which you can also see from gdb:

(gdb) info frame
Stack level 0, frame at 0x7fffffffde98:
 rip = 0x55555555513f in function (c.c:3); saved rip = 0x555555555180
                                                       ^^^^^^^^^^^^^^
 called by frame at 0x7fffffffdec0
 source language c.
 Arglist at 0x7fffffffde88, args: a=1, b=2, c=3
 Locals at 0x7fffffffde88, Previous frame's sp is 0x7fffffffde98
 Saved registers:
  rbp at 0x7fffffffde88, rip at 0x7fffffffde90
  ^^^^^^^^^^^^^^^^^^^^^^

You can also see this by looking at the assembly code:

gcc -S c.c -o c.s

or if you prefer intel:

gcc -masm=intel -S c.c -o c.s

I don't know why gcc leaves that gap though:

    mov     DWORD PTR -36[rbp], edi
    mov     DWORD PTR -40[rbp], esi
    mov     DWORD PTR -44[rbp], edx
    mov     DWORD PTR -6[rbp], 1633771873  <-- aaaa
    mov     WORD PTR -2[rbp], 97           <-- a\0
    movabs  rax, 7089336938131513954       <-- bbbbbbbb
    mov     QWORD PTR -17[rbp], rax
    mov     WORD PTR -9[rbp], 25186        <-- bb
    mov     BYTE PTR -7[rbp], 0            <-- \0
V13
  • 853
  • 8
  • 14
0

Compiler normally respects the efficiency defined ABI specification in order to optimize the parameter passing through registers, alignment, and space for possible deep nested expressions inside the code. For example, intel ABI spec says the stack pointer be expanded on function call with parameters in multiples of 16 bytes, so all kind of alignments are ok. So, it's quite normal to see that on entry, space for local variables is reserved with only one SP subtract, and then begin to execute code until we need another chunk of space. The ABI specification says which registers are used to pass parameters, which ones you must respect in the call and which you can destroy, which are used to link the stack frames (normally EBP in intel) etc. This allows for compiler interdependency (interface between languages) and at the same time implement optimised code and ensure program efficiency. This is the reason you see some apparent stack memory loss in the entry/exit procedure calls.

Luis Colorado
  • 10,974
  • 1
  • 16
  • 31