4.2. Section and Object Layout

For this example we're going to build ./main composed of main.c and add.c:

/*
 * Copyright (C) 2006  Trevor Woerner
 */
#include <stdio.h>
int add (int, int);
int global_val;
int gval_init = 0;
int
main (void)
{
	int local_val = 25;
	global_val = 17;
	printf ("local_val: %d    global_val: %d    gval_init: %d\n",
			local_val, global_val, gval_init);
	printf ("%d + %d = %d\n", local_val, global_val,
			add (local_val, global_val));
	return 0;
}
       
By the way, the local_val, gval_init, and global_val were just added so we could see which sections they end up in.

/*
 * Copyright (C) 2006  Trevor Woerner
 */
int
add (int i, int j)
{
	return i+j;
}
       

Compiling is a simple process:

[trevor]$ gcc -c add.c
[trevor]$ gcc -c main.c
[trevor]$ gcc -o main main.o add.o
       

Here is a dump of the info from add.o:

[trevor]$ objdump -t add.o
add.o:     file format elf32-i386
SYMBOL TABLE:
00000000 l    df *ABS*  00000000 add.c
00000000 l    d  .text  00000000 .text
00000000 l    d  .data  00000000 .data
00000000 l    d  .bss   00000000 .bss
00000000 l    d  .note.GNU-stack        00000000 .note.GNU-stack
00000000 l    d  .comment       00000000 .comment
00000000 g     F .text  0000000b add
       

Here is a similar dump of main.o:

[trevor]$ objdump -t main.o
main.o:     file format elf32-i386
SYMBOL TABLE:
00000000 l    df *ABS*  00000000 main.c
00000000 l    d  .text  00000000 .text
00000000 l    d  .data  00000000 .data
00000000 l    d  .bss   00000000 .bss
00000000 l    d  .rodata        00000000 .rodata
00000000 l    d  .note.GNU-stack        00000000 .note.GNU-stack
00000000 l    d  .comment       00000000 .comment
00000000 g     O .bss   00000004 gval_init
00000000 g     F .text  0000007d main
00000004       O *COM*  00000004 global_val
00000000         *UND*  00000000 printf
00000000         *UND*  00000000 add
       

And here is the objdump -t of the final executable:

[trevor]$ objdump -t main
main:     file format elf32-i386
SYMBOL TABLE:
08048114 l    d  .interp        00000000              .interp
08048128 l    d  .note.ABI-tag  00000000              .note.ABI-tag
08048148 l    d  .hash  00000000              .hash
08048174 l    d  .dynsym        00000000              .dynsym
080481d4 l    d  .dynstr        00000000              .dynstr
08048234 l    d  .gnu.version   00000000              .gnu.version
08048240 l    d  .gnu.version_r 00000000              .gnu.version_r
08048260 l    d  .rel.dyn       00000000              .rel.dyn
08048268 l    d  .rel.plt       00000000              .rel.plt
08048280 l    d  .init  00000000              .init
08048298 l    d  .plt   00000000              .plt
080482d8 l    d  .text  00000000              .text
08048488 l    d  .fini  00000000              .fini
080484a4 l    d  .rodata        00000000              .rodata
080484ec l    d  .eh_frame      00000000              .eh_frame
080494f0 l    d  .ctors 00000000              .ctors
080494f8 l    d  .dtors 00000000              .dtors
08049500 l    d  .jcr   00000000              .jcr
08049504 l    d  .dynamic       00000000              .dynamic
080495cc l    d  .got   00000000              .got
080495d0 l    d  .got.plt       00000000              .got.plt
080495e8 l    d  .data  00000000              .data
080495f4 l    d  .bss   00000000              .bss
00000000 l    d  .comment       00000000              .comment
00000000 l    d  *ABS*  00000000              .shstrtab
00000000 l    d  *ABS*  00000000              .symtab
00000000 l    d  *ABS*  00000000              .strtab
080482fc l     F .text  00000000              call_gmon_start
00000000 l    df *ABS*  00000000              crtstuff.c
080494f0 l     O .ctors 00000000              __CTOR_LIST__
080494f8 l     O .dtors 00000000              __DTOR_LIST__
08049500 l     O .jcr   00000000              __JCR_LIST__
080495f4 l     O .bss   00000001              completed.4583
080495f0 l     O .data  00000000              p.4582
08048320 l     F .text  00000000              __do_global_dtors_aux
08048354 l     F .text  00000000              frame_dummy
00000000 l    df *ABS*  00000000              crtstuff.c
080494f4 l     O .ctors 00000000              __CTOR_END__
080494fc l     O .dtors 00000000              __DTOR_END__
080484ec l     O .eh_frame      00000000              __FRAME_END__
08049500 l     O .jcr   00000000              __JCR_END__
08048460 l     F .text  00000000              __do_global_ctors_aux
00000000 l    df *ABS*  00000000              main.c
00000000 l    df *ABS*  00000000              add.c
08049504 g     O .dynamic       00000000              _DYNAMIC
080495fc g     O .bss   00000004              global_val
080484a4 g     O .rodata        00000004              _fp_hw
080494f0 g       *ABS*  00000000              .hidden __fini_array_end
080495ec g     O .data  00000000              .hidden __dso_handle
08048458 g     F .text  00000005              __libc_csu_fini
08048280 g     F .init  00000000              _init
080483fc g     F .text  0000000b              add
080495f8 g     O .bss   00000004              gval_init
080482d8 g     F .text  00000000              _start
080494f0 g       *ABS*  00000000              .hidden __fini_array_start
08048408 g     F .text  0000004f              __libc_csu_init
080495f4 g       *ABS*  00000000              __bss_start
0804837c g     F .text  0000007d              main
00000000       F *UND*  00000187              __libc_start_main@@GLIBC_2.0
080494f0 g       *ABS*  00000000              .hidden __init_array_end
080495e8  w      .data  00000000              data_start
00000000       F *UND*  00000039              printf@@GLIBC_2.0
08048488 g     F .fini  00000000              _fini
080494f0 g       *ABS*  00000000              .hidden __preinit_array_end
080495f4 g       *ABS*  00000000              _edata
080495d0 g     O .got.plt       00000000              .hidden _GLOBAL_OFFSET_TABLE_
08049600 g       *ABS*  00000000              _end
080494f0 g       *ABS*  00000000              .hidden __init_array_start
080484a8 g     O .rodata        00000004              _IO_stdin_used
080495e8 g       .data  00000000              __data_start
00000000  w      *UND*  00000000              _Jv_RegisterClasses
080494f0 g       *ABS*  00000000              .hidden __preinit_array_start
00000000  w      *UND*  00000000              __gmon_start__
       

The interesting thing for me is how such a small amount of code generates such a large number of segments! Notice how both of the *.o files contain .text, .data, and .bss segments. When they are combined into the executable main, it still contains these segments (i.e. it makes no distinction about where the specific parts come from, they all get combined into one larger segment of the same name).

If we want to know the linker script that was used (to find out how ld lays out all the sections), all we have to do is pass the --verbose flag to ld via gcc (like this: gcc -Wl,--verbose ...) and we will get the linker script spat out on stderr. Here is the linker script that I get for this code:

/* Script for -z combreloc: combine and sort reloc sections */
OUTPUT_FORMAT("elf32-i386", "elf32-i386",
              "elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(_start)
SEARCH_DIR("/usr/i386-redhat-linux/lib"); SEARCH_DIR("/usr/local/lib"); SEARCH_DIR("/lib"); SEARCH_DIR("/usr/lib");
/* Do we need any of these for elf?
   __DYNAMIC = 0;    */
SECTIONS
{
    /* Read-only sections, merged into text segment: */
    PROVIDE (__executable_start = 0x08048000); . = 0x08048000 + SIZEOF_HEADERS;
    .interp         : { *(.interp) }
    .hash           : { *(.hash) }
    .dynsym         : { *(.dynsym) }
    .dynstr         : { *(.dynstr) }
    .gnu.version    : { *(.gnu.version) }
    .gnu.version_d  : { *(.gnu.version_d) }
    .gnu.version_r  : { *(.gnu.version_r) }
    .rel.dyn        :
    {
        *(.rel.init)
            *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*)
            *(.rel.fini)
            *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*)
            *(.rel.data.rel.ro*)
            *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*)
            *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*)
            *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*)
            *(.rel.ctors)
            *(.rel.dtors)
            *(.rel.got)
            *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*)
    }
    .rela.dyn       :
    {
        *(.rela.init)
            *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
            *(.rela.fini)
            *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
            *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
            *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
            *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
            *(.rela.ctors)
            *(.rela.dtors)
            *(.rela.got)
            *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
    }
    .rel.plt        : { *(.rel.plt) }
    .rela.plt       : { *(.rela.plt) }
    .init           :
    {
        KEEP (*(.init))
    } =0x90909090
    .plt            : { *(.plt) }
    .text           :
    {
        *(.text .stub .text.* .gnu.linkonce.t.*)
            KEEP (*(.text.*personality*))
            /* .gnu.warning sections are handled specially by elf32.em.  */
            *(.gnu.warning)
    } =0x90909090
    .fini           :
    {
        KEEP (*(.fini))
    } =0x90909090
    PROVIDE (__etext = .);
    PROVIDE (_etext = .);
    PROVIDE (etext = .);
    .rodata         : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
    .rodata1        : { *(.rodata1) }
    .eh_frame_hdr : { *(.eh_frame_hdr) }
    .eh_frame       : ONLY_IF_RO { KEEP (*(.eh_frame)) }
    .gcc_except_table   : ONLY_IF_RO { KEEP (*(.gcc_except_table)) *(.gcc_except_table.*) }
    /* Adjust the address for the data segment.  We want to adjust up to
       the same address within the page on the next page up.  */
    . = ALIGN (0x1000) - ((0x1000 - .) & (0x1000 - 1)); . = DATA_SEGMENT_ALIGN (0x1000, 0x1000);
    /* Exception handling  */
    .eh_frame       : ONLY_IF_RW { KEEP (*(.eh_frame)) }
    .gcc_except_table   : ONLY_IF_RW { KEEP (*(.gcc_except_table)) *(.gcc_except_table.*) }
    /* Thread Local Storage sections  */
    .tdata          : { *(.tdata .tdata.* .gnu.linkonce.td.*) }
    .tbss           : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
    /* Ensure the __preinit_array_start label is properly aligned.  We
       could instead move the label definition inside the section, but
       the linker would then create the section even if it turns out to
       be empty, which isn't pretty.  */
    . = ALIGN(32 / 8);
    PROVIDE (__preinit_array_start = .);
    .preinit_array     : { KEEP (*(.preinit_array)) }
    PROVIDE (__preinit_array_end = .);
    PROVIDE (__init_array_start = .);
    .init_array     : { KEEP (*(.init_array)) }
    PROVIDE (__init_array_end = .);
    PROVIDE (__fini_array_start = .);
    .fini_array     : { KEEP (*(.fini_array)) }
    PROVIDE (__fini_array_end = .);
    .ctors          :
    {
        /* gcc uses crtbegin.o to find the start of
           the constructors, so we make sure it is
           first.  Because this is a wildcard, it
           doesn't matter if the user does not
           actually link against crtbegin.o; the
           linker won't look for a file to match a
           wildcard.  The wildcard also means that it
           doesn't matter which directory crtbegin.o
           is in.  */
        KEEP (*crtbegin*.o(.ctors))
            /* We don't want to include the .ctor section from
               from the crtend.o file until after the sorted ctors.
               The .ctor section from the crtend file contains the
               end of ctors marker and it must be last */
            KEEP (*(EXCLUDE_FILE (*crtend*.o ) .ctors))
            KEEP (*(SORT(.ctors.*)))
            KEEP (*(.ctors))
    }
    .dtors          :
    {
        KEEP (*crtbegin*.o(.dtors))
            KEEP (*(EXCLUDE_FILE (*crtend*.o ) .dtors))
            KEEP (*(SORT(.dtors.*)))
            KEEP (*(.dtors))
    }
    .jcr            : { KEEP (*(.jcr)) }
    .data.rel.ro : { *(.data.rel.ro.local) *(.data.rel.ro*) }
    .dynamic        : { *(.dynamic) }
    .got            : { *(.got) }
    . = DATA_SEGMENT_RELRO_END (12, .);
    .got.plt        : { *(.got.plt) }
    .data           :
    {
        *(.data .data.* .gnu.linkonce.d.*)
            KEEP (*(.gnu.linkonce.d.*personality*))
            SORT(CONSTRUCTORS)
    }
    .data1          : { *(.data1) }
    _edata = .;
    PROVIDE (edata = .);
    __bss_start = .;
    .bss            :
    {
        *(.dynbss)
            *(.bss .bss.* .gnu.linkonce.b.*)
            *(COMMON)
            /* Align here to ensure that the .bss section occupies space up to
               _end.  Align after .bss to ensure correct alignment even if the
               .bss section disappears because there are no input sections.  */
            . = ALIGN(32 / 8);
    }
    . = ALIGN(32 / 8);
    _end = .;
    PROVIDE (end = .);
    . = DATA_SEGMENT_END (.);
    /* Stabs debugging sections.  */
    .stab          0 : { *(.stab) }
    .stabstr       0 : { *(.stabstr) }
    .stab.excl     0 : { *(.stab.excl) }
    .stab.exclstr  0 : { *(.stab.exclstr) }
    .stab.index    0 : { *(.stab.index) }
    .stab.indexstr 0 : { *(.stab.indexstr) }
    .comment       0 : { *(.comment) }
    /* DWARF debug sections.
       Symbols in the DWARF debugging sections are relative to the beginning
       of the section so we begin them at 0.  */
    /* DWARF 1 */
    .debug          0 : { *(.debug) }
    .line           0 : { *(.line) }
    /* GNU DWARF 1 extensions */
    .debug_srcinfo  0 : { *(.debug_srcinfo) }
    .debug_sfnames  0 : { *(.debug_sfnames) }
    /* DWARF 1.1 and DWARF 2 */
    .debug_aranges  0 : { *(.debug_aranges) }
    .debug_pubnames 0 : { *(.debug_pubnames) }
    /* DWARF 2 */
    .debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
    .debug_abbrev   0 : { *(.debug_abbrev) }
    .debug_line     0 : { *(.debug_line) }
    .debug_frame    0 : { *(.debug_frame) }
    .debug_str      0 : { *(.debug_str) }
    .debug_loc      0 : { *(.debug_loc) }
    .debug_macinfo  0 : { *(.debug_macinfo) }
    /* SGI/MIPS DWARF 2 extensions */
    .debug_weaknames 0 : { *(.debug_weaknames) }
    .debug_funcnames 0 : { *(.debug_funcnames) }
    .debug_typenames 0 : { *(.debug_typenames) }
    .debug_varnames  0 : { *(.debug_varnames) }
    /DISCARD/ : { *(.note.GNU-stack) }
}
       

This might look like it's difficult to read, but it's not. Text within /* and */, as is the same with C code, indicates comments which are ignored. A period by itself ., as is the same in assembly notation, indicates the current value of the output location counter.

At the top of the file is a bunch of housekeeping stuff. Then it gives the SECTIONS command which indicates the start of the script that defines how the sections of the output ELF file are going to be laid out. As an example, let's look at the following lines of code which lay out the .text part of the image (i.e. the part where the executable code is placed):

.text           :
{
    *(.text .stub .text.* .gnu.linkonce.t.*)
        KEEP (*(.text.*personality*))
        /* .gnu.warning sections are handled specially by elf32.em.  */
        *(.gnu.warning)
} =0x90909090
       
This snippet says:

  1. Now I am going to lay out a .text section in the output file, at this point in the output.

  2. This section is going to be composed of all the .text, .stub, .text.*, and .gnu.linkonce.t.* sections I encounter (in that order) from all input files I am given (the * before the parenthesized list indicates which input files to consider).

  3. This is followed by all .gnu.warning sections I encounter from all input files.

  4. The =0x90909090 written at the end of the section's description tells me the fill pattern to use if there is any space between sections (mostly due to alignment constraints).

Just playing around and experimenting some more, here is the objdump -t of the executable again, with most of the cruft removed, and sorted by address:

[trevor]$ objdump -t main
080482d8 g     F .text  00000000              _start
080482d8 l    d  .text  00000000              .text
080482fc l     F .text  00000000              call_gmon_start
08048320 l     F .text  00000000              __do_global_dtors_aux
08048354 l     F .text  00000000              frame_dummy
0804837c g     F .text  0000007d              main
080483fc g     F .text  0000000b              add
08048408 g     F .text  0000004f              __libc_csu_init
08048458 g     F .text  00000005              __libc_csu_fini
08048460 l     F .text  00000000              __do_global_ctors_aux
       
If I change the compile line to be:
[trevor]$ gcc -o main add.o main.o
       
watch how the positions of the functions main() and add() change place in the executable image:
[trevor]$ objdump -t main
080482d8 g     F .text  00000000              _start
080482d8 l    d  .text  00000000              .text
080482fc l     F .text  00000000              call_gmon_start
08048320 l     F .text  00000000              __do_global_dtors_aux
08048354 l     F .text  00000000              frame_dummy
0804837c g     F .text  0000000b              add
08048388 g     F .text  0000007d              main
08048408 g     F .text  0000004f              __libc_csu_init
08048458 g     F .text  00000005              __libc_csu_fini
08048460 l     F .text  00000000              __do_global_ctors_aux
       
This happens because the linker script, when creating the .text section, does a wildcard match on all .text sections and joins them together into one single .text section in the order in which they are encountered. During the first compile we specified the order as main.o followed by add.o; therefore the symbols were placed in the executable starting with the symbols from main.o followed by the symbols from add.o. In the second case we specified the object files in the reverse order, therefore the symbols were stored in the executable in the reverse order too.