Compiler and Assembler
Compiler and Assembler
LOADER:
A BRIEF STORY
4.
Linking is the final stage of compilation. It takes one or more object files
or libraries as input and combines them to produce a single (usually
executable) file. In doing so, it resolves references to external symbols,
assigns final addresses to procedures/functions and variables, and
revises code and data to reflect new addresses (a process called
relocation).
Bear in mind that if you use the IDE type compilers, these processes quite
transparent.
Now we are going to examine more details about the process that happen
before and after the linking stage. For any given input file, the file name suffix
(file extension) determines what kind of compilation is done and the example for
GCC is listed in Table w.1.
In UNIX/Linux, the executable or binary file doesnt have extension whereas in
Windows the executables for example may have .exe, .com and .dll.
File extension
Description
file_name.c
C source code which must be preprocessed.
file_name.i
C source code which should not be preprocessed.
file_name.ii
C++ source code which should not be preprocessed.
file_name.h
C header file (not to be compiled or linked).
file_name.cc
file_name.cp
file_name.cxx
C++ source code which must be preprocessed. For file_name.cxx, the xx must both be
file_name.cpp literally character x and file_name.C, is capital c.
file_name.c++
file_name.C
file_name.s
Assembler code.
file_name.S
Assembler code which must be preprocessed.
Object file by default, the object file name for a source file is made by replacing the
file_name.o
extension .c, .i, .s etc with .o
Table w.1
The following Figure shows the steps involved in the process of building the C
program starting from the compilation until the loading of the executable image
into the memory for program running.
Figure w.1: Compile, link and execute stages for running program (a process)
W.2 OBJECT FILES and EXECUTABLE
After the source code has been assembled, it will produce an Object files
(e.g. .o, .obj) and then linked, producing an executable files.
An object and executable come in several formats such as ELF (Executable and
Linking Format) and COFF (Common Object-File Format). For example, ELF is
used on Linux systems, while COFF is used on Windows systems.
Other object file formats are listed in the following Table.
ct File
mat
Description
The a.out format is the original file format for Unix. It consists of three sections: text, data, and bss, which are
program code, initialized data, and uninitialized data, respectively. This format is so simple that it doesn't ha
any reserved place for debugging information. The only debugging format for a.out is stabs, which is encode
a set of normal symbols with distinctive attributes.
The COFF (Common Object File Format) format was introduced with System V Release 3 (SVR3) Unix. CO
files may have multiple sections, each prefixed by a header. The number of sections is limited. The COFF
specification includes support for debugging but the debugging information was limited. There is no file exte
for this format.
FF
A variant of COFF. ECOFF is an Extended COFF originally introduced for Mips and Alpha workstations.
The IBM RS/6000 running AIX uses an object file format called XCOFF (eXtended COFF). The COFF sectio
FF
symbols, and line numbers are used, but debugging symbols are dbx-style stabs whose strings are located i
the.debug section (rather than the string table). The default name for an XCOFF executable file is a.out.
Windows 9x and NT use the PE (Portable Executable) format for their executables. PE is basically COFF w
additional headers. The extension normally .exe.
The ELF (Executable and Linking Format) format came with System V Release 4 (SVR4) Unix. ELF is simi
COFF in being organized into a number of sections, but it removes many of COFF's limitations. ELF used o
most modern Unix systems, including GNU/Linux, Solaris and Irix. Also used on many embedded systems.
SOM (System Object Module) and ESOM (Extended SOM) is HP's object file and debug format (not to be
ESOM
confused with IBM's SOM, which is a cross-language Application Binary Interface - ABI).
Table w.2
When we examine the content of these object files there are areas called
sections. Sections can hold executable code, data, dynamic linking information,
debugging data, symbol tables, relocation information, comments, string tables,
and notes.
Some sections are loaded into the process image and some provide information
needed in the building of a process image while still others are used only in
linking object files.
There are several sections that are common to all executable formats (may be
named differently, depending on the compiler/linker) as listed below:
Description
This section contains the executable instruction codes and is shared among every process running the same
section usually has READ and EXECUTE permissions only. This section is the one most affected by optimization
BSS stands for Block Started by Symbol. It holds un-initialized global and static variables. Since the BSS only ho
that don't have any values yet, it doesn't actually need to store the image of these variables. The size that BSS
runtime is recorded in the object file, but the BSS (unlike the data section) doesn't take up any actual space in the
Contains the initialized global and static variables and their values. It is usually the largest part of the executable.
READ/WRITE permissions.
Also known as .rodata (read-only data) section. This contains constants and string literals.
Stores the information required for relocating the image while loading.
A symbol is basically a name and an address. Symbol table holds information needed to locate and relocate a p
symbolic definitions and references. A symbol table index is a subscript into this array. Index 0 both designates th
in the table and serves as the undefined symbol index. The symbol table contains an array of symbol entries.
Relocation is the process of connecting symbolic references with symbolic definitions. For example, when a prog
function, the associated call instruction must transfer control to the proper destination address at execution. Re-lo
must have relocation entries which are necessary because they contain information that describes how to modify
section contents, thus allowing executable and shared object files to hold the right information for a process's pro
image. Simply said relocation records are information used by the linker to adjust section contents.
Table w.3: Segments in executable file
The following is an example of the object file content dumping using readelf
program. Other utility can be used is objdump. These utilities presented in Linux
gcc, g++, gdb and gas 1 and Linux gcc, g++, gdb and gas 2.
For Windows dumpbin utility (coming with Visual C++ compiler) or more powerful
one is a free PEBrowse program that can be used for the same purpose.
/* testprog1.c */
#include <stdio.h>
static void display(int i, int *ptr);
int main(void)
{
int x = 5;
int *xptr = &x;
printf("In main() program:\n");
printf("x value is %d and is stored at address %p.\n", x, &x);
printf("xptr pointer points to address %p which holds a value of %d.\n", xptr,
*xptr);
display(x, xptr);
return 0;
}
void display(int y, int *yptr)
{
char var[7] = "ABCDEF";
printf("In display() function:\n");
printf("y value is %d and is stored at address %p.\n", y, &y);
printf("yptr pointer points to address %p which holds a value of %d.\n", yptr,
*yptr);
}
[bodo@bakawali test]$ gcc -c testprog1.c
[bodo@bakawali test]$ readelf -a testprog1.o
ELF Header:
Magic:
7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class:
ELF32
Data:
2's complement,
little endian
Version:
1 (current)
OS/ABI:
UNIX - System V
ABI Version:
0
Type:
REL (Relocatable file)
Machine:
Intel 80386
Version:
0x1
Entry point address:
0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 672 (bytes into file)
Flags:
0x0
Size of this header:
52 (bytes)
Size of program headers: 0 (bytes)
Number of program headers:
0
Size of section headers:
40 (bytes)
Number of section headers:
11
Section header string table index:
8
Section Headers:
[Nr]
Name
Type
Addr
Off
Size
ES
Flg Lk Inf Al
[ 0]
NULL
00000000 000000 000000
00
0
0 0
[ 1] .text
PROGBITS
00000000 000034 0000de
00 AX
0
0 4
[ 2] .rel.text
REL
00000000 00052c 000068
08
9
1 4
[ 3] .data
PROGBIT
00000000 000114 000000
00
WA 0
0 4
[ 4] .bss
NOBIT
00000000 000114 000000
00 WA 0
0 4
[ 5] .rodata
PROGBITS
00000000 000114
00010a 00
A 0
0 4
[ 6] .note.GNU-stack
PROGBITS
00000000 00021e
000000 00
0
0 1
[ 7] .comment
PROGBITS
00000000 00021e 000031
00
0
0 1
[ 8] .shstrtab
STRTAB 00000000 00024f 000051
00
0
0 1
[ 9] .symtab
SYMTAB 00000000 000458 0000b0
10
10 9 4
[10] .strtab
STRTAB 00000000 000508 000021
00
0
0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
Sym.Value
Sym.
R_386_32 00000000
.rodata
R_386_PC32
00000000
printf
R_386_32 00000000
.rodata
R_386_PC32
00000000
printf
R_386_32 00000000
.rodata
R_386_PC32
00000000
printf
R_386_32 00000000
.rodata
R_386_32 00000000
.rodata
R_386_PC32
00000000
printf
R_386_32 00000000
.rodata
R_386_PC32
00000000
printf
R_386_32 00000000
.rodata
R_386_PC32
00000000
printf
UND
ABS
1
3
4
5
1 display
6
7
1 main
UND printf
2
3
4
Table w.4
The assembler directives in assembly programming can be used to identify code
and data sections, allocate/initialize memory and making symbols externally
visible or invisible.
An example of the assembly code with some of the assembler directives (Intel) is
shown below:
tializing data
.section
.byte
.long
.data
128
1,1000,10000
"hello"
"hello"
.bss
BUFFSIZE 1024
z, 4, 4
fool
;define a constant
;allocate 4 bytes for x with
;4-byte alignment
leave
return
W.3 RELOCATION RECORDS
Because the various object files will include references to each others code
and/or data, so various locations, these shall need to be combined during the link
time.
For example in Figure w.2, the object file that has main() includes calls to
functions funct() and printf().
After linking all of the object files together, the linker uses the relocation records
to find all of the addresses that need to be filled in.
W.4 SYMBOL TABLE
Since assembling to machine code removes all traces of labels from the code,
the object file format has to keep these around in different places.
It is accomplished by the symbol table that contains a list of names and their
corresponding offsets in the text and data segments.
A disassembler provides support for translating back from an object file or
executable.
For gcc, the static option can be used during the compilation/linking of the
program.
gcc static filename.c o filename
The drawback of this technique is that the executable is quite big in size, all the
needed information need to be brought together.
W.5.3 DYNAMICALLY LINKED
The term dynamically linked means that the program and the particular library it
references are not combined together by the linker at link time.
Instead, the linker places information into the executable that tells the loader
which shared object module the code is in and which runtime linker should be
used to find and bind the references.
This means that the binding between the program and the shared object is done
at runtime that is before the program starts, the appropriate shared objects are
found and bound.
This type of program is called a partially bound executable, because it isn't fully
resolved. The linker, at link time, didn't cause all the referenced symbols in the
program to be associated with specific code from the library.
Instead, the linker simply said something like: This program calls some functions
within a particular shared object, so I'll just make a note of which shared object
these functions are in, and continue on.
Symbols for the shared objects are only verified for their validity to ensure that
they do exist somewhere and are not yet combined into the program.
The linker stores in the executable program, the locations of the external libraries
where it found the missing symbols. Effectively, this defers the binding until
runtime.
Programs that are linked dynamically are linked against shared objects that have
the extension .so. An example of such an object is the shared object version of
the standard C library, libc.so.
The advantageous to defer some of the objects/modules during the static linking
step until they are finally needed (during the run time) includes:
1. Program files (on disk) become much smaller because they need not hold
all necessary text and data segments information. It is very useful for
portability.
2. Standard libraries may be upgraded or patched without every one
program need to be re-linked. This clearly requires some agreed modulenaming convention that enables the dynamic linker to find the newest,
installed module such as some version specification. Furthermore the
distribution of the libraries is in binary form (no source), including
dynamically linked libraries (DLLs) and when you change your program
you only have to recompile the file that was changed.
3. Software vendors need only provide the related libraries module
required. Additional runtime linking functions allow such programs to
programmatically-link the required modules only.
4. In combination with virtual memory, dynamic linking permits two or more
processes to share read-only executable modules such as standard C
libraries. Using this technique, only one copy of a module needs be
W.7
Figure w.4: Simplified object file format: linking view and execution view.
Keep in mind that the full format of the ELF contains many more items. As
explained previously, the linking view, which is used when the program or library
is linked, deals with sections within an object file.
Sections contain the bulk of the object file information: data, instructions,
relocation information, symbols, debugging information, etc.
The execution view, which is used when the program runs, deals with
segments. Segments are a way of grouping related sections.
For example, the text segment groups executable code, the data segment
groups the program data, and the dynamic segment groups information relevant
to dynamic loading.
Each segment consists of one or more sections. A process image is created by
loading and interpreting segments.
The operating system logically copies a files segment to a virtual memory
segment according to the information provided in the program header table. The
OS can also use segments to create a shared memory resource.
At link time, the program or library is built by merging together sections with
similar attributes into segments.
Typically, all the executable and read-only data sections are combined into a
single text segment, while the data and BSS are combined into the data
segment.
These segments are normally called load segments, because they need to be
loaded in memory at process creation. Other sections such as symbol
information and debugging sections are merged into other, non-load segments.
PROCESS LOADING
In Linux processes loaded from a file system (using either
the execve() or spawn() system calls) are in ELF format.
If the file system is on a block-oriented device, the code and data are loaded into
main memory.
If the file system is memory mapped (e.g. ROM/Flash image), the code needn't
be loaded into RAM, but may be executed in place.
This approach makes all RAM available for data and stack, leaving the code in
ROM or Flash. In all cases, if the same process is loaded more than once, its
code will be shared.
Before we can run an executable, firstly we have to load it into memory.
This is done by the loader, which is generally part of the operating system. The
loader does the following things (from other things):
1. Memory and access validation - Firstly, the OS system kernel reads in the
program files header information and does the validation for type, access
permissions, memory requirement and its ability to run its instructions. It
confirms that file is an executable image and calculates memory
requirements.
2. Process setup includes:
i.
ii.
iii.
iv.
v.
vi.
Address space is memory space that contains program code, stack, and data
segments or in other word, all data the program uses as it runs.
The memory layout, consists of three segments (text, data, and stack), in
simplified form is shown in Figure w.5.
The dynamic data segment is also referred to as the heap, the place dynamically
allocated memory (such as from malloc() and new) comes from. Dynamically
allocated memory is memory allocated at run time instead of compile/link time.
This organization enables any division of the dynamically allocated memory
between the heap (explicitly) and the stack (implicitly). This explains why the
stack grows downward and heap grows upward.
Table w.5
When a program is running, the initialized data, BSS and heap areas are usually
placed into a single contiguous area called a data segment.
The stack segment and code segment are separate from the data segment and
from each other as illustrated in Figure w.4.
Although it is theoretically possible for the stack and heap to grow into each
other, the operating system prevents that event.
The relationship among the different sections/segments is summarized in Table
w.6, executable program segments and their locations.
Executable file section
Address space segment
Program memory segment
(disk file)
.text
Text
Code
.data
Data
Initialized data
.bss
Data
BSS
Data
Heap
Stack
Stack
Table w.6
W.9 THE PROCESS (IMAGE)
The diagram below shows the memory layout of a typical Cs process. The
process load segments (corresponding to "text" and "data" in the diagram) at the
process's base address.
The main stack is located just below and grows downwards. Any additional
threads or function calls that are created will have their own stacks, located
below the main stack.
Each of the stack frames is separated by a guard page to detect stack overflows
among stacks frame. The heap is located above the process and grows upwards.
In the middle of the process's address space, there is a region is reserved for
shared objects. When a new process is created, the process manager first maps
the two segments from the executable into memory.
It then decodes the program's ELF header. If the program header indicates that
the executable was linked against a shared library, the process manager will
extract the name of the dynamic interpreter from the program header.
The dynamic interpreter points to a shared library that contains the runtime linker
code. The process manager will load this shared library in memory and will then
pass control to the runtime linker code in this library.
The runtime linker is contained within the C runtime library. The runtime linker
performs several tasks when loading a shared library (.so file).
The dynamic section provides information to the linker about other libraries that
this library was linked against.
It also gives information about the relocations that need to be applied and the
external symbols that need to be resolved. The runtime linker will first load any
other required shared libraries (which may themselves reference other shared
libraries).
It will then process the relocations for each library. Some of these relocations
are local to the library, while others require the runtime linker to resolve a global
symbol.
In the latter case, the runtime linker will search through the list of libraries for this
symbol. In ELF files, hash tables are used for the symbol lookup, so they're very
fast.
Once all relocations have been applied, any initialization functions that have
been registered in the shared library's init section are called. This is used in
some implementations of C++ to call global constructors.
W.11 SYMBOL NAME RESOLUTION
When the runtime linker loads a shared library, the symbols within that library
have to be resolved. Here, the order and the scope of the symbol resolution are
important.
If a shared library calls a function that happens to exist by the same name in
several libraries that the program has loaded, the order in which these libraries
are searched for this symbol is critical. This is why the OS defines several
options that can be used when loading libraries.
All the objects (executables and libraries) that have global scope are stored on
an internal list (the global list).
Any global-scope object, by default, makes available all of its symbols to any
shared library that gets loaded.
The global list initially contains the executable and any libraries that are loaded at
the program's startup.
W.12 DYNAMIC ADDRESS TRANSLATION
In the view of the memory management, modern OS with multitasking, normally
implement dynamic relocation instead of static.
All the program layout in the address space is virtually same. This dynamic
relocation (in processor term it is called dynamic address translation) provides
the illusion that:
1. Each process can use addresses starting at 0, even if other processes are
running, or even if the same program is running more than one time.
2. Address spaces are protected.
3. Can fool process further into thinking it has memory that's much larger
than available physical memory (virtual memory).
3.
4.
5.
6.
Check the best selling C / C++, Linux and Open Source books at Amazon.com.
To view Windows the executable file content, you can use dumpbin tool that
comes with Microsoft Visual Studio or more powerful one is a
free PEBrowse utility.
For Linux/Unix/Fedora you can use readelf or other tools that can be found Linux
gnu gcc, g++, gdb and gas 1 or Linux gnu gcc, g++, gdb and gas 2.
Windows implementation of processes, threads and synchronization using C can
be found Win32 processes and threads tutorials.
Windows Dynamic-Link Library, DLL story and program examples can be
found Win32 DLL tutorials.
Windows Services story can be found Windows Services tutorials.