C memory layout of a program

Viewed 133

I'm getting familiar with C and I was going over the memory layout of a program. I did manage to get some info about where variables go inside the memory, but I still got a few that remain unclear to me.

I ran this on lubuntu 18.04 running on virtualbox and windows 10 as a host

Say I have the following program:

int foo(); // (??)
void point(void* p); // (??)
int data1; // bss segment
int data2 = 3; // data segment
int main(){
  char str[3] = {'a','b','c'}; // text segment(??)
  char *str1 = "word"; // str1 - stack, *str1 = 'w' - text segment (??)

  point(&data1); // call stack

  return 0;
}

void point(void* p){

 long dist1 = (size_t)&data2 - (size_t)p; // call stack - inside point's AF: dist1, p
 printf("%ld\n", dist1); /* is a long integer generally enough to hold addresses (or difference of 
                           addresses). is it legal to calculate differences of addresses of different 
                           segments? */  

}

int foo(){
  return 0;
}

I'd like some help with the questions I've added in comments, and maybe some confirmation for the what I already did.

Thanks in advance.

2 Answers

Let's compile your program with gcc -O0 program.c -o program then disassemble it with objdump -D program. For convenience, I went ahead and did this (AT&T syntax, Intel syntax). You can see that foo is in the .text section, and it's been replaced by a stub that essentially does nothing and just returns out of the function. Since point was defined after the declaration, it's equivalent to having been defined right at the declaration, and is also in the .text section with an actual implementation. You can see that you are correct, and that data1 is in .bss and that data2 is in .data. As for your {'a', 'b', 'c'} array in main, you can see it's a bit weird.

 6c1:   c6 45 f5 61             mov    BYTE PTR [rbp-0xb],0x61
 6c5:   c6 45 f6 62             mov    BYTE PTR [rbp-0xa],0x62
 6c9:   c6 45 f7 63             mov    BYTE PTR [rbp-0x9],0x63

The values are actually being loaded one by one into the array, so I guess you could say that it is being stored in the .text section. You may notice that the "word" string is not actually in the disassembly. However, if you do readelf -x .rodata program, you'll find it sitting in the .rodata section.

Hex dump of section '.rodata':
  0x000007d0 01000200 776f7264 00256c64 0a00     ....word.%ld..

You can also see that while the variables aren't referenced by name, they are located on the function's stack frame, given by an offset of the base pointer rbp. For 64 bit binaries, an address is 8 bytes, and for 32 bit binaries, an address is 4 bytes.

That’s totally implementation-defined.

Regarding addresses, most systems provide intptr_t and uintptr_t, which are integer types suitable for holding an address. You can do anything with it as it is just an integer, but if you cast it back to pointer its validness is up to you (i.e. casting to intptr_t and back is OK, just like performing an equivalent of correct pointer arithmetic, but not much more).

There is also ptrdiff_t, which holds pointer difference; it is usually the same as intptr_t but is always available. Still you can do anything with it as it is an integer; the operation meaning is up to you. But subtracting pointers themselves is only allowed if they point to different elements of the same array. 1

Regarding sections, function bodies go into .text or equivalent; function headers don’t go anywhere per se, but if the function is exported (the default on Linux) the linker adds the necessary data into the program file, which the dynamic linker/loader uses to build real jump tables. Local variables go on the thread stack. Constants may find themselves in data, rodata, or even text (esp. on x64). bss is a non-stored section, i.e. in the file, only its description is present, but on program startup it is allocated and (usually) zero-filled.

Related