4

Suppose we have a simple code :

int* q = new int(13);

int main() {
    return 0;
}

Clearly, variable q is global and initialized. From this answer, we expect q variable to be stored in initialized data segment (.data) within program file but it is a pointer, so it's value (which is an address in heap segment) is determined at run time. So what's the value stored in data segment within program file ?

My try:
In my thinking, compiler allocates some space for variable q (typically 8 bytes for 64 bit address) in data segment with no meaningful value. Then, puts some initialization code in text segment before main function code to initialize q variable at run time. Something like this in assembly :

     ....
     mov  edi, 4
     call operator new(unsigned long)
     mov  DWORD PTR [rax], 13  // rax: 64 bit address (pointer value)

     // offset : q variable offset in data segment, calculated by compiler
     mov  QWORD PTR [ds+offset], rax // store address in data segment
     ....
main:
     ....

Any idea?

trincot
  • 317,000
  • 35
  • 244
  • 286
Mehran Torki
  • 977
  • 1
  • 9
  • 37
  • 1
    When linking with `gcc`, the default start point is `_start`. Which is where this initialization will have the code, and then it will call `main`. So people doing Assembly programming without clib, but linking with `gcc`, have to put at start of their code the `_start:` label, while people linking against default clib start at `main:` (in their source, the binary starts at `_start:` from lib). :) – Ped7g Dec 18 '16 at 19:11
  • 1
    To elaborate on that, `_start` isn't a function, so you can't write `_start` in C or C++. In assembly, you don't have to write functions, you can write arbitrary code, so you can write `_start` yourself. – Dietrich Epp Dec 18 '16 at 19:12
  • Got it, Thanks Ped7g & Dietrich Epp. – Mehran Torki Dec 18 '16 at 19:17
  • 1
    `int *q` will go in the `.bss`, not the `.data` section, since it's only initialized at run-time by a constructor. There's no need to have 8 bytes in the executable's data segment for it. – Peter Cordes Dec 18 '16 at 19:42

2 Answers2

3

Yes, that is essentially how it works.

Note that in ELF .data, .bss, and .text are actually sections, not segments. You can look at the assembly yourself by running your compiler:

c++ -S -O2 test.cpp

You will typically see a main function, and some kind of initialization code outside that function. The program entry point (part of your C++ runtime) will call the initialization code and then call main. The initialization code is also responsible for running things like constructors.

Dietrich Epp
  • 205,541
  • 37
  • 345
  • 415
  • Thanks, can you shortly mention the difference between segment and section? – Mehran Torki Dec 18 '16 at 19:13
  • 2
    Sections are used at link-time to produce a binary from your object files. Segments are used at runtime to load your binary into memory. Segments don't have names. – Dietrich Epp Dec 18 '16 at 19:19
  • @DietrichEpp: It's pretty standard to talk about the text segment (where the linker puts `.text`, `.rodata`, and various other stuff), or the data segment (`.data`), or the BSS. I guess this isn't an official part of ELF, though, since `readelf -a` output only shows numbered segments in the section-to-segment mapping. – Peter Cordes Dec 18 '16 at 19:39
  • 1
    @MehranTorki: In case you were wondering, ELF segments in Linux have nothing to do with x86 segment registers. They both come from a similar historical concept, though. – Peter Cordes Dec 18 '16 at 19:40
2

int *q will go in the .bss, not the .data section, since it's only initialized at run-time by a non-constant initializer (so this is only legal in C++, not in C). There's no need to have 8 bytes in the executable's data segment for it.

The compiler arranges for the initializer function to be run by putting its address into an array of initializers that the CRT (C Run-Time) startup code calls before calling main.

On the Godbolt compiler explorer, you can see the init function's asm without all the noise of directives. Notice that the addressing mode is just a simple RIP-relative access to q. The linker fills in the right offset from RIP at this point, since that's a link-time constant even though the .text and .bss sections end up in separate segments.

Godbolt's compiler-noise filtering isn't ideal for us. Some of the directives are relevant, but many of them aren't. Below is a hand-chosen mix of gcc6.2 -O3 asm output with Godbolt's "filter directives" option unchecked, for just the int* q = new int(13); statement. (No need to compile a main at the same time, we're not linking an executable).

# gcc6.2 -O3 output
_GLOBAL__sub_I_q:      # presumably stands for subroutine
    sub     rsp, 8           # align the stack for calling another function
    mov     edi, 4           # 4 bytes
    call    operator new(unsigned long)   # this is the demangled name, like from objdump -dC
    mov     DWORD PTR [rax], 13
    mov     QWORD PTR q[rip], rax      # clang uses the equivalent `[rip + q]`
    add     rsp, 8
    ret

    .globl  q
    .bss
q:
    .zero   8      # reserve 8 bytes in the BSS

There's no reference to the base of the ELF data (or any other) segment.

Also definitely no segment-register overrides. ELF segments have nothing to do with x86 segments. (And the default segment register for this is DS anyway, so the compiler doesn't need to emit [ds:rip+q] or anything. Some disassemblers may be explicit and show DS even though there was no segment-override prefix on the instruction, though.)


This is how the compiler arranges for it to be called before main():

    # the "aw" sets options / flags for this section to tell the linker about it.
    .section        .init_array,"aw"
    .align 8
    .quad   _GLOBAL__sub_I_q       # this assembles to the absolute address of the function.

The CRT start code has a loop that knows the size of the .init_array section and uses a memory-indirect call instruction on each function-pointer in turn.

The .init_array section is marked writeable, so it goes into the data segment. I'm not sure what writes it. Maybe the CRT code marks it as already-done by zeroing the pointers after calling them?


There's a similar mechanism in Linux for running initializers in dynamic libraries, which is done by the ELF interpreter while doing dynamic linking. This is why you can call printf() or other glibc stdio functions from _start in a dynamically-linked binary created from hand-written asm, and why that fails in a statically linked binary if you don't call the right init functions. (See this Q&A for more about building static or dynamic binaries that define their own _start or just main(), with or without libc).

Community
  • 1
  • 1
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • I compiled the code in my question with & without pointer variable, and the result was a change in `data` section size. Checked it using `size` command. So it must be stored in `data` section. – Mehran Torki Dec 18 '16 at 21:06
  • @MehranTorki: With what compiler? 64 or 32-bit code? What OS (I was assuming Linux)? Since `q` is global, perhaps some extra `.data` size is used for the GOT or something? In the asm output I showed, the pointer itself is definitely stored in the `.bss`, just like I expected. It makes zero sense for any compiler to put it in `.data` unless it can evaluate the `new` at compile-time and avoid a run-time initializer. But that would mean that a `delete`-able pointer would be embedded in the executable, so the compiler would have to depend on the inner workings of libstdc++. – Peter Cordes Dec 18 '16 at 21:17
  • 1
    @MehranTorki: I just tried it myself. An empty .cpp compiles to a .o with 0/0/0 test/data/bss. gcc5.2 -O3 compiles `int* q = new int(13);` to a .o which `size` says has 80 bytes in the text segment, 8 in the data segment, and 8 in bss. The 8 bytes in the bss are for `q`, as I can see with `objdump -D` (which shows the symbol as part of the `.bss` section). `readelf -a` confirms this: `q` is in section number 3, which is `.bss`. I haven't figured out what *is* going into `.data`, but it's is definitely not the storage space for `q` itself. – Peter Cordes Dec 18 '16 at 21:26
  • 1
    @MehranTorki: Oh, I just figured it out: **the `.init_array` section goes into the data segment**. `readelf -a` shows that its flags are `WA` (writeable, allocated) which means its non-zero writeable data, so it needs to go in the data segment. Its size is 8 bytes. – Peter Cordes Dec 18 '16 at 21:30
  • That's was really challenging :) Thanks for your efforts. BTW, i have two questions : 1. why `rip` register is used to access for storing `rax` ? 2. I don't get you by "ELF segments have nothing to do with x86 segments", how variables in data section are accessed without `ds` register?. Can you provide me a link for further reading ? Sorry i'm almost new to this topic. Thanks – Mehran Torki Dec 18 '16 at 21:36
  • @MehranTorki: RIP-relative addressing is the best way to address static data in x86-64. Absolute addressing takes an extra byte. re: segments: [this google hit](http://stackoverflow.com/questions/10903816/segmentation-registers-use) for `elf segment x86 "segment register"` looks like it explains things. Basically, all the x86 segment registers have base=0 and limit=2^64-1 (except for FS used for thread-local data). Linux uses virtual memory (paging) for memory protection, not segments. – Peter Cordes Dec 18 '16 at 21:41
  • Thanks a lot, so you mean that `cs` register is not used for accessing static data and instead RIP-relative addressing is used, am i right ? – Mehran Torki Dec 18 '16 at 21:49
  • @MehranTorki: RIP-relative addressing uses the `ds` segment register by default. Like I said, all the segment registers have the same base and limit, giving a flat memory model. (except FS which has a different value for each thread). **Just forget about segment registers for now, you can and should totally ignore them** unless you have to read some 16-bit code or you want to learn about how thread-local storage is implemented. (Different CS selectors also put the CPU in 32 or 64-bit mode). But anyway, x86 Linux does not use a segmented memory model at all. – Peter Cordes Dec 18 '16 at 21:57