Powered By Blogger

Tuesday, October 18, 2016

COMPILER, ASSEMBLER, LINKER AND LOADER: A BRIEF STORY

W.1  COMPILERS, ASSEMBLERS and LINKERS

  • Normally the C’s program building process involves four stages and utilizes different ‘tools’ such as a preprocessor, compiler, assembler, and linker.
  • At the end there should be a single executable file.  Below are the stages that happen in order regardless of the operating system/compiler and graphically illustrated in Figure w.1.
  1. Preprocessing is the first pass of any C compilation. It processes include-files, conditional compilation instructions and macros.
  2. Compilation is the second pass. It takes the output of the preprocessor, and the source code, and generates assembler source code.
  3. Assembly is the third stage of compilation. It takes the assembly source code and produces an assembly listing with offsets. The assembler output is stored in an object file.
  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 doesn’t 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
file_name.cpp
file_name.c++
file_name.C
C++ source code which must be preprocessed.  For file_name.cxx, the xx must both be literally character x and file_name.C, is capital c.
file_name.s
Assembler code.
file_name.S
Assembler code which must be preprocessed.
file_name.o
Object file by default, the object file name for a source file is made by replacing the 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.
Compiler assembler linker and loader

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.
Object File Format
Description
a.out
The a.out format is the original file format for Unix.  It consists of three sections: text, data, and bss, which are for program code, initialized data, and uninitialized data, respectively.  This format is so simple that it doesn't have any reserved place for debugging information.  The only debugging format for a.out is stabs, which is encoded as a set of normal symbols with distinctive attributes.
The COFF (Common Object File Format) format was introduced with System V Release 3 (SVR3) Unix. COFF 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 extension for this format.
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 sections, symbols, and line numbers are used, but debugging symbols are dbx-style stabs whose strings are located in 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 with additional headers.  The extension normally .exe.
The ELF (Executable and Linking Format) format came with System V Release 4 (SVR4) Unix.  ELF is similar to COFF in being organized into a number of sections, but it removes many of COFF's limitations.  ELF used on most modern Unix systems, including GNU/Linux, Solaris and Irix. Also used on many embedded systems.
SOM/ESOM
SOM (System Object Module) and ESOM (Extended SOM) is HP's object file and debug format (not to be 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:
Section
Description
.text
This section contains the executable instruction codes and is shared among every process running the same binary. This section usually has READ and EXECUTE permissions only. This section is the one most affected by optimization.
.bss
BSS stands for ‘Block Started by Symbol’. It holds un-initialized global and static variables. Since the BSS only holds variables that don't have any values yet, it doesn't actually need to store the image of these variables. The size that BSS will require at runtime is recorded in the object file, but the BSS (unlike the data section) doesn't take up any actual space in the object file.
.data
Contains the initialized global and static variables and their values. It is usually the largest part of the executable. It usually has READ/WRITE permissions.
.rdata
Also known as .rodata (read-only data) section. This contains constants and string literals.
.reloc
Stores the information required for relocating the image while loading.
Symbol table
A symbol is basically a name and an address.  Symbol table holds information needed to locate and relocate a program’s symbolic definitions and references. A symbol table index is a subscript into this array. Index 0 both designates the first entry in the table and serves as the undefined symbol index.  The symbol table contains an array of symbol entries.
Relocation records
Relocation is the process of connecting symbolic references with symbolic definitions. For example, when a program calls a function, the associated call instruction must transfer control to the proper destination address at execution. Re-locatable files must have relocation entries’ which are necessary because they contain information that describes how to modify their section contents, thus allowing executable and shared object files to hold the right information for a process's program 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.  
/* 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)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

There are no program headers in this file.

Relocation section '.rel.text' at offset 0x52c contains 13 entries:
 Offset       Info                 Type   Sym.Value  Sym. Name
0000002d  00000501 R_386_32 00000000   .rodata
00000032  00000a02 R_386_PC32      00000000   printf
00000044  00000501 R_386_32 00000000   .rodata
00000049  00000a02 R_386_PC32      00000000   printf
0000005c  00000501 R_386_32 00000000   .rodata
00000061  00000a02 R_386_PC32      00000000   printf
0000008c  00000501 R_386_32 00000000   .rodata
0000009c  00000501 R_386_32 00000000   .rodata
000000a1  00000a02 R_386_PC32      00000000   printf
000000b3  00000501 R_386_32 00000000   .rodata
000000b8  00000a02 R_386_PC32      00000000   printf
000000cb  00000501 R_386_32 00000000   .rodata
000000d0  00000a02 R_386_PC32      00000000   printf

There are no unwind sections in this file.

Symbol table '.symtab' contains 11 entries:
   Num:    Value     Size Type    Bind        Vis             Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS testprog1.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    1
     3: 00000000     0 SECTION LOCAL  DEFAULT    3
     4: 00000000     0 SECTION LOCAL  DEFAULT    4
     5: 00000000     0 SECTION LOCAL  DEFAULT    5
     6: 00000080     94 FUNC   LOCAL  DEFAULT    1 display
     7: 00000000     0 SECTION LOCAL  DEFAULT    6
     8: 00000000     0 SECTION LOCAL  DEFAULT    7
     9: 00000000     128 FUNC  GLOBAL DEFAULT    1 main
    10: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

No version information found in this file.
  • When writing a program using the assembly language it should be compatible with the sections in the assembler directives (x86) and the partial list that is interested to us is listed below:

Section
Description
1
Text (.section .text)
Contain code (instructions).
Contain the _start label.
2
Read-Only Data (.section .rodata)
Contains pre-initialized constants.
3
Read-Write Data (.section .data)
Contains pre-initialized variables.
4
BSS (.section .bss)
Contains un-initialized data.

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:
;initializing data


.section
.data

x:
.byte
128
;one byte initialized to 128
y:
.long
1,1000,10000
;3 long words




;initializing ascii data


.ascii
"hello"
;ascii without null character

asciz
"hello"
;ascii with \0




;allocating memory in bss


.section
.bss


.equ
BUFFSIZE 1024
;define a constant

.comm
z, 4, 4
;allocate 4 bytes for x with
;4-byte alignment




;making symbols externally visible


.section
.data


.globl
w
;declare externally visible
;e.g: int w = 10

.text



.globl
fool
;e.g: fool(void) {…}
fool:




…



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.
Relocation record and symbol table

Figure w.2: The relocation record

W.5  LINKING

  • The linker actually enables separate compilation. As shown in Figure w.3, an executable can be made up of a number of source files which can be compiled and assembled into their object files respectively, independently.
Linking all together by linker

Figure w.3:  The object files linking process

W.5.1  SHARED OBJECTS

  • In a typical system, a number of programs will be running. Each program relies on a number of functions, some of which will be standard C library functions, like printf(), malloc(), strcpy(), etc. and some are non-standard or user defined functions.
  • If every program uses the standard C library, it means that each program would normally have a unique copy of this particular library present within it. Unfortunately, this result in wasted resources, degrade the efficiency and performance.
  • Since the C library is common, it is better to have each program reference the common, one instance of that library, instead of having each program contain a copy of the library.
  • This is implemented during the linking process where some of the objects are linked during the link time whereas some done during the run time (deferred/dynamic linking).

W.5.2  STATICALLY LINKED

  • The term ‘statically linked’ means that the program and the particular library that it’s linked against are combined together by the linker at link time.
  • This means that the binding between the program and the particular library is fixed and known at link time before the program run. It also means that we can't change this binding, unless we re-link the program with a new version of the library.
 
  • Programs that are linked statically are linked against archives of objects (libraries) that typically have the extension of .a.  An example of such a collection of objects is the standard C library, libc.a.
  • You might consider linking a program statically for example, in cases where you weren't sure whether the correct version of a library will be available at runtime, or if you were testing a new version of a library that you don't yet want to install as shared.
  • 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 module-naming 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 resident in memory at any time, and multiple processes, each can executes this shared code (read only).  This results in a considerable memory saving, although demands an efficient swapping policy.

W.6  HOW SHARED OBJECTS ARE USED

  • To understand how a program makes use of shared objects, let's first examine the format of an executable and the steps that occur when the program starts.

W.6.1  SOME ELF FORMAT DETAILS

  • Executable and Linking Format (ELF) is binary format, which is used in SVR4 Unix and Linux systems. 
  • It is a format for storing programs or fragments of programs on disk, created as a result of compiling and linking.
  • ELF not only simplifies the task of making shared libraries, but also enhances dynamic loading of modules at runtime. 

W.6.2  ELF SECTIONS

  • The Executable and Linking Format used by GNU/Linux and other operating systems, defines a number of ‘sections’ in an executable program.
  • These sections are used to provide instruction to the binary file and allowing inspection. Important function sections include the Global Offset Table (GOT), which stores addresses of system functions, the Procedure Linking Table (PLT), which stores indirect links to the GOT, .init/.fini, for internal initialization and shutdown, .ctors/.dtors, for constructors and destructors.
  • The data sections are .rodata, for read only data, .data for initialized data, and .bss for uninitialized data.
  • Partial list of the ELF sections are organized as follows (from low to high):
  1. .init           - Startup
  2. .text          - String
  3. .fini           - Shutdown
  4. .rodata     - Read Only
  5. .data         - Initialized Data
  6. .tdata        - Initialized Thread Data
  7. .tbss          - Uninitialized Thread Data
  8. .ctors         - Constructors
  9. .dtors         - Destructors
  10. .got            - Global Offset Table
  11. .bss           - Uninitialized Data
  • You can use the readelf or objdump program against the object or executable files in order to view the sections.
  • In the following Figure, two views of an ELF file are shown: the linking view and the execution view.

Linking and execution view
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 file’s 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.

W.7  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 file’s 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:
  1. Allocates primary memory for the program's execution.
  2. Copies address space from secondary to primary memory.
  3. Copies the .text and .data sections from the executable into primary memory.
  4. Copies program arguments (e.g., command line arguments) onto the stack.
  5. Initializes registers: sets the esp (stack pointer) to point to top of stack, clears the rest.
  6. Jumps to start routine, which: copies main()'s arguments off of the stack, and jumps to main().
  • 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.

Process memory layout
Figure w.4:  Process memory layout

W.8  RUNTIME DATA STRUCTURE – From Sections to Segments

  • A process is a running program.  This means that the operating system has loaded the executable file for the program into memory, has arranged it to have access to its command-line arguments and environment variables, and has started it running.
  • Typically a process has 5 different areas of memory allocated to it as listed in Table w.5 (refer to Figure w.4):
Segment
Description
Code - text segment
Often referred to as the text segment, this is the area in which the executable instructions reside.  For example, Linux/Unix arranges things so that multiple running instances of the same program share their code if possible.  Only one copy of the instructions for the same program resides in memory at any time.  The portion of the executable file containing the text segment is the text section.
Initialized data – data segment
Statically allocated and global data that are initialized with nonzero values live in the data segment.  Each process running the same program has its own data segment.  The portion of the executable file containing the data segment is the data section.
Uninitialized data – bss segment
BSS stands for ‘Block Started by Symbol’.  Global and statically allocated data that initialized to zero by default are kept in what is called the BSS area of the process.  Each process running the same program has its own BSS area.  When running, the BSS data are placed in the data segment.  In the executable file, they are stored in the BSS section.  For Linux/Unix the format of an executable, only variables that are initialized to a nonzero value occupy space in the executable’s disk file.
Heap
The heap is where dynamic memory (obtained by malloc(), calloc(), realloc() and new for C++) comes from.  Everything on a heap is anonymous, thus you can only access parts of it through a pointer. As memory is allocated on the heap, the process’s address space grows.  Although it is possible to give memory back to the system and shrink a process’s address space, this is almost never done because it will be allocated to other process again.   Freed memory (free() and delete) goes back to the heap, creating what is called holes.   It is typical for the heap to grow upward.  This means that successive items that are added to the heap are added at addresses that are numerically greater than previous items.  It is also typical for the heap to start immediately after the BSS area of the data segment.  The end of the heap is marked by a pointer known as the break. You cannot reference past the break. You can, however, move the break pointer (via brk() and sbrk() system calls) to a new position to increase the amount of heap memory available.
Stack
The stack segment is where local (automatic) variables are allocated.  In C program, local variables are all variables declared inside the opening left curly brace of a function body including the main() or other left curly brace that aren’t defined as static.  The data is popped up or pushed into the stack following the Last In First Out (LIFO) rule.  The stack holds local variables, temporary information, function parameters, return address and the like.  When a function is called, a stack frame (or a procedure activation record) is created and PUSHed onto the top of the stack. This stack frame contains information such as the address from which the function was called and where to jump back to when the function is finished (return address), parameters, local variables, and any other information needed by the invoked function. The order of the information may vary by system and compiler.  When a function returns, the stack frame is POPped from the stack.  Typically the stack grows downward, meaning that items deeper in the call chain are at numerically lower addresses and toward the heap.

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
(disk file)
Address space segment
Program memory segment
.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 C’s 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.
Another big picture of the C process memory layout
Figure w.5:  C’s process memory layout on an x86.


W.10  RUNTIME LINKER AND SHARED LIBRARY LOADING

  • The runtime linker is invoked when a program that was linked against a shared object is started or when a program requests that a shared object be dynamically loaded.
  • So the resolution of the symbols can be done at one of the following time:
  1. Load-time dynamic linking – the application program is read from the disk (disk file) into memory and unresolved references are located.  The load time loader finds all necessary external symbols and alters all references to each symbol (all previously zeroed) to memory references relative to the beginning of the program.
  2. Run-time dynamic linking – the application program is read from disk (disk file) into memory and unresolved references are left as invalid (typically zero).  The first access of an invalid, unresolved, reference results in a software trap.  The run-time dynamic linker determines why this trap occurred and seeks the necessary external symbol.  Only this symbol is loaded into memory and linked into the calling program.
  • 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).
  • In dynamic relocation the address changed dynamically during every reference.  Virtual address is generated by a process (also called logical address) and the physical address is the actual address in physical memory at the run-time.
  • The address translation normally done by Memory Management Unit (MMU) that incorporated in the processor itself.
  • Virtual addresses are relative to the process.  Each process believes that its virtual addresses start from 0.  The process does not even know where it is located in physical memory; the code executes entirely in terms of virtual addresses.
  • MMU can refuse to translate virtual addresses that are outside the range of memory for the process for example by generating the segmentation faults.  This provides the protection for each process.
  • During translation, one can even move parts of the address space of a process between disk and memory as needed (normally called swapping or paging).
  • This allows the virtual address space of the process to be much larger than the physical memory available to it.
  • Graphically, this dynamic relocation for a process is shown in Figure w.6.
Physical and virtual address: Address translation

Figure w.6: Physical and virtual address: Address translation

Thursday, August 4, 2016

Linux Addressing

On x86 32-bit architecture, maximum addressable memory is 4GB.  This
addressable space is known as "virtual address space" and those addresses are
called "virtual addresses". Now to access physical memory, or more
specifically, to access a physical address, a virtual address must go through
the segmentation then paging system, known as the "mapping" process. 

                         MMU
     virtual/   +----------------------+
        logical |  +----------------+  | logical is Intel terms
        ------->|  | Segmented Unit |  |
                |  +----------------+  |
                |          | linear    | 
                |  +----------------+  |
                |  | Paging Unit    |  |
                |  +----------------+  |
                |          |           |
                +----------|-----------+
                           | physical
                           |
                           v


In order to access *any* physical pages, that page *must* be in the process's
page table - every process has its own page table. That is the base.

Now, there are two "less obvious" details worth pointing out: first, even it
is often said a process is "given" a unique 4GB virtual address space, it
doesn't really mean that the process can do whatever it wants in that space:
to access a paricular area inside that virtual space, it must ask kernel for a
so-called "valid" memory area for it - the corresponding data structure
defined in Linux is known as "vm_area_struct" or VMAs. You can check all VMAs
associated with a process through "pmap" command on a process id.

A second point is that even in theory, a process got the "potential" of
accessing 4GB space, but say a user-space application makes use of libc, then
libc should be mapped to the virtual space; the user-space application may
also make use of syscalls, that means kernel will work on behalf of this
process, so kernel image should also be mapped to process's virtual address
space: and this mapping is better be permanent, given how often a process
needs to switch to kernel mode. A temporary mapping scheme seems possible, but
doesn't make much sense.

To summarize, a 4GB virtual address space for a process needs to be split
between kernel and user space program: therefore the well-known 3G/1G split.
User space takes the 0-3GB, and kernel takes the 3GB-4GB.

Thus, in the 3G/1G split, kernel has the virtual address space of 1GB.
Remember that to access a physical address, you need a virtual address to
start with, even for kernel. So if you don't do anything special, the 1GB
virtual address effectively limits the physical space a kernel can access to
1GB. Okay, maybe this is a third less obvious detail: kernel _needs_ to access
*every* physical memory to make full use of it.

In the early days, where a machine's physical space is much less than 1GB, it
is OK, the whole physical memory is mapped to this 1GB virtual address.

       process address space 

    4GB +---------------+
        |     512MB     |
        +---------------+ <------+     physical memory               
        |     512MB     |        | 
    3GB +---------------+ <--+   +---> +------------+ 
        |               |    |         |   512 MB   |
        |     /////     |    +-------> +------------+
        |               |     
    0GB +---------------+     

  Example: Physical address {x} is mapped in kernel address space, virtual
  address is {PAGE_OFFSET + x}, where PAGE_OFFSET is defined as 3GB in Linux
  kernel for the 3/1 split.

  Two general observations related to this:

  - When all physical memory can be directly mapped into virtual address
    space, those corresponding virtual addresses are also called "kernel
    logical address". These logical address can often be mapped to physical
    address through constant offset, 3GB for example.

  - The part of physical memory can be mapped into virtual space is also known
    as "Low Memory", conversely, those can't be mapped (say the portion over
    1GB) is known as High Memory. In above case, all 512MB is Low Memory.


Now you say, most machines got 2GB or more physical memory now. What happens
then? when you want to access physical memory above 1GB (or more precisely,
896MB), Linux kernel use 128 MB virtual address space to *temporarily* map
those virtual addresses to the physical address, thus to achieve the goal of
being able to access all physical pages. There are some details not clearly
spelled out here, for example, will be keep that 128 MB pre-allocated for this
temporary mapping etc. But I think at this point, we can safely skip those
over, and focus on the essential issue here: use temporal mapping to access
all physical memory. The following figure roughly illustrate the scheme:


                                           physical mem
       process address space    +------> +------------+
                                |        |  3200 M    |
                                |        |            |
    4GB +---------------+ <-----+        |  HIGH MEM  |
        |     128 MB    |                |            |
        +---------------+ <---------+    |            |
        +---------------+ <------+  |    |            | 
        |     896 MB    |        |  +--> +------------+         
    3GB +---------------+ <--+   +-----> +------------+ 
        |               |    |           |   896 MB   |
        |     /////     |    +---------> +------------+
        |               |     
    0GB +---------------+     


Putting this in Linux context: kmalloc() will return you a chunk of virtual
memory: yes, pointed by a virtual address, but more importantly, that is also
a kernel logical address, meaning it has direct mapping to *continuous"
physical pages.

vmalloc() is another kernel call that will return you a chunk of virtual
memory. However, this virtual memory is only continous on virtual space, it
may not be continous on physical space. Also, the actual mapped physical pages
can not only come from Low Memory, but also can come from High Memory,
especially when you are asking for a large chunk of it.


On 64-bit architecture
----------------------------------------------

On such architecture, the 3G/1G split doesn't apply anymore. Due to the huge
address space, you can easily pick a split scheme between user space and
kernel space, and still easily map the whole physical memory into kernel
address space.


C pointer address question
----------------------------------------------

A sorta interesting observation on C pointers: we can print a pointer address
in C. For a user-space application, if you print out the pointer address you
defined, it should be one of the virtual addresses out of the (0-3GB) range.

What about kernel? what if print a pointer address in kernel? is it always
from kernel address space? The answer is no ... since kernel *can* access user
space address, depending on the pointer, it can be from either.

How do you tell if it is from kernel or user space? Yes, if it fall into
0-3GB, then it is from user-space, otherwise, it is from kernel. The take-away
message here, either way, it is virtual address you are seeing.

Does it make sense?




In real world #1
----------------------------------------------

  $ cat /proc/iomem

  00100000-003205e3 : Kernel code
  003205e4-0041bdc3 : Kernel data
  0047d000-004f3aff : Kernel bss


  So the address shown here is physical, kernel code start out at 1G, and code
  portion of it is a bit over 2 MB.

  PAGE_OFFSET =  0xC8000000 = 3GB

  For a 512MB system, the virtual address for kernel will be from
  3GB ~ PAGE_OFFSET + 512MB

In real world #2
----------------------------------------------

From a user process point of view

    +----------------------+ <--- 0xBFFF FFFF (=3GB)
    | environment variable |
    |----------------------| <--- 0xBFFF FD0C 
    | stacks (down grow)   |
    |          |           |
    |          v           |
    |----------------------|
    |                      |
    |     // free memory   |
    |                      |
    |                      |
    |----------------------| <--------------------
    |  myprogram.o         |                    |
    |----------------------|                    |
    |  mylib.o             |                    |
    |----------------------|              executable image      
    |  myutil.o            |                    |     
    |----------------------|                    |
    |  library code (libc) |                    |
    |----------------------| <--------------------                
    |                      | 0x8000 0000 (=2GB) 
    |                      |
    |  other memory        |
    |                      |
    +----------------------+

Monday, July 25, 2016

Introduction to Linux Interrupts and CPU SMP Affinity

Interrupts are signal that are sent across IRQ (Interrupt Request Line) by a hardware or software.
Interrupts allow devices like keyboard, serial cards and parallel ports to indicate that it needs CPU attention.
Once the CPU receives the Interrupt Request, CPU will temporarily stop execution of running program and invoke a special program called Interrupt Handler or ISR (Interrupt Service Routine).

The Interrupt Service or Interrupt Handler Routine can be found in Interrupt Vector table that is located at fixed address in the memory. After the interrupt is handled CPU resumes the interrupted program.
At boot time, system identifies all devices, and appropriate interrupt handlers are loaded into the interrupt table.
The following are two ways of requesting CPU attention:
  1. Interrupt based
  2. Polling based
All Linux based OS are interrupt driven.
When we press a key on keyboard, keyboards says to CPU that a key has been pressed. But CPU can be busy processing some stuff from RAM, System Clock, NIC card, may be video or PCI bus. In that case Keyboard places a voltage on IRQ line assigned to that hardware, here in this case [Keyboard]. This change in voltage serves as request from device saying that device has a request that needs processing.

/proc/interrupts File

On a Linux machine, the file /proc/interrupts contains information about the interrupts in use and how many times processor has been interrupted
# cat /proc/interrupts
           CPU0   CPU1  CPU2  CPU3
  0: 3710374484      0     0     0  IO-APIC-edge  timer
  1:         20      0     0     0  IO-APIC-edge  i8042
  6:          5      0     0     0  IO-APIC-edge  floppy
  7:          0      0     0     0  IO-APIC-edge  parport0
  8:          0      0     0     0  IO-APIC-edge  rtc
  9:          0      0     0     0  IO-APIC-level  acpi
 12:        240      0     0     0  IO-APIC-edge  i8042
 14:   11200026      0     0     0  IO-APIC-edge  ide0
 51:   61281329      0     0     0  IO-APIC-level  ioc0
 59:          1      0     0     0  IO-APIC-level  vmci
 67:   19386473      0     0     0  IO-APIC-level  eth0
 75:   94595340      0     0     0  IO-APIC-level  eth1
NMI:          0      0     0     0
LOC: 3737150067 3737142382 3737145101 3737144204
ERR:          0
MIS:          0
In the above file:
  • The first Column is the IRQ number.
  • The Second column says how many times the CPU core has been interrupted. In the above example timer is interrupt name [System clock] and 3710374484 is the number of times CPU0 has been interrupted. I8042 is Keyboard controller that controls PS/2 keyboards and mouse in Pc’s.
  • For interrupt like rtc [Real time clock] CPU has not being interrupted. RTC are present in electronic devices to keep track of time.
  • NMI and LOC are drivers used on system that are not accessible/configured by user.
IRQ number determines the priority of the interrupt that needs to be handled by the CPU.
A small IRQ number value means higher priority.
For example if CPU receives interrupt from Keyboard and system clock simultaneously. CPU will serve System Clock first since it has IRQ number 0.
  • IRQ 0 — system timer (cannot be changed);
  • IRQ 1 — keyboard controller (cannot be changed)
  • IRQ 3 — serial port controller for serial port 2 (shared with serial port 4, if present);
  • IRQ 4 — serial port controller for serial port 1 (shared with serial port 3, if present);
  • IRQ 5 — parallel port 2 and 3 or sound card;
  • IRQ 6 — floppy disk controller;
  • IRQ 7 — parallel port 1. It is used for printers or for any parallel port if a printer is not present.
For devices like joystick CPU doesn’t wait for the device to send interrupt. Since Joystick used for gaming and the movement of joystick will be fast it will be ideal to use polling and check whether device needs attention. The disadvantage behind this method is CPU can get into busy wait, checking the device many times.
On a related note, it is also essential to handle the signals properly in Linux.

Hardware Interrupts

All of the above discussed scenarios are example of Hardware interrupts.
Hardware interrupts are further classified into two major categories:
  1. Non-maskable interrupts [NMI]: As the name suggests these types of interrupts cannot be ignored or suppressed by the CPU. MNI’s are send over separate interrupt line and it’s generally used for critical hardware errors like memory error, Hardware traps indicating Fan failure, Temperature Sensor failure etc.
  2. Maskable interrupts: These interrupts can be ignored or delayed by CPU. The Interrupt Mask Register masks the interrupts being triggered on external pins of cache controller. Setting a bit by writing a 0, disables the interrupt triggering on the pin

Software Interrupts

These interrupts are generated when the CPU executes an instruction which can cause an exception condition in the CPU [ALU unit] itself.
For example, divide a number by zero which is not possible, it will lead to divide-by-zero exception, causing the computer to abandon the calculation or display an error message.
The file /proc/stat is also a file part of the /proc filesystem, which has information about system kernel statistics, also holds some interrupt information.
# cat /proc/stat
cpu  17028082 5536753 5081493 1735530500 42592308 90006 479750 0
cpu0 5769176 1170683 1495750 403368354 39406374 90006 284864 0
cpu1 3714389 1451937 1186134 444082258 1084780 0 64876 0
cpu2 3791544 1471013 1211868 443988514 1056981 0 64764 0
cpu3 3752971 1443119 1187740 444091373 1044172 0 65244 0
intr 417756956 --- Output Truncated
The line intr shows the count of the interrupt serviced since boot time. The first column is total of all interrupts serviced. Each subsequent column is the total for a particular interrupt.

SMP_AFFINITY

Symmetric multiprocessing is the processing of programs by multiple processors.
smp_affinity file holds interrupt affinity value for a IRQ number. The smp_affinity file associated with each IRQ number is stored in /proc/irq/IRQ_NUMBER/smp_affinity file. The value in the file is stored in hexadecimal bit-mask representing all CPU cores in the system. smp_affinity works for device that has IO-APIC enabled device drivers.
For example, smp_affinity entry for Ethernet driver is shown below:
grep eth0 /proc/interrupts
67: 23834931 0 0 0 IO-APIC-level eth0
IRQ number for eth0 is 67 and corresponding smp_affinity file is located at:
cat /proc/irq/67/smp_affinity
00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
The decimal equivalent for value ‘000000001’ is ‘1’. ie All the interrupt related to Ethernet driver will be serviced by CPU0.
We can manually change the processor affinity by changing values in smp_affinity file for a particular controller or by using irqbalance.

Monday, March 21, 2016

Atheros chipset overview

Hardware Overview

The Atheros 802.11 NIC is, at it's core, a DMA engine with hardware timers implementing the 802.11 packet timing. The hardware handles doing the OFDM/CCK RF encoding and decoding.

General Overview

The NIC can be broken down into a few larger blocks:
  • The MAC - implements the 802.11 packet timing, encryption/decryption, packet scheduling, DMA and queuing
  • The PHY - implements the 802.11 packet encoding and decoding.
  • The radio (analog) - implements the 2.4GHz / 5GHz radio.
  • Power control / RTC - handling turning on and off power and clock generation to various parts of the device
  • Host interface - the glue between the internal AHB and whatever the host supports - PCI, PCIe, etc

MAC

The MAC side is further broken down into:
  • PCU - packet control unit. This handles handles packet reception.
  • QCU - queue control unit. This handles packet transmission DMA.
  • DCU - (?) control unit. This handles the 802.11 side of packet transmission - contention window management, QoS settings, etc.
  • FIFOs - there's a TX and an RX FIFO.
The MAC itself implements (not an exhaustive list!):
  • contention window and backoff timers
  • RTS generation, CTS-to-self generation, CTS response
  • ACK response
  • Block-ACK response on 802.11n hardware
  • Beacon generation
  • On later chips, it supports waking up the hardware when it sees various things - WoW packets, TIM bit set for itself, etc
  • Transmit and receive DMA
  • Interrupt generation
  • MIB counters - TX frame, RX frame, RX clear, cycle timers

PHY

The PHY implements the packet encoding and decoding. The MAC selects packets to send to the PHY for transmission; the PHY decodes packets from the air and sends them to the MAC for reception. (Yes, the Atheros NIC drivers do no actual packet encoding/decoding in software - the hardware does all of this. It's not a software defined radio.)
It is also responsible for watching the air and determining when the air is clear enough to transmit. The MAC has an input (RX_CLEAR) which it uses to determine whether it's able to transmit or not.
The PHY has one (or more, for later chips) ADCs and DACs which handle receiving and transmitting encoding frames to the analog section.

Analog

The Analog section links into the PHY via the ADCs and DACs. It's responsible for tuning to the relevant 2 or 5GHz frequency and converting things as needed.

Chipset specifics

AR5210

The MAC supports:
  • One RX queue
  • Two TX queues (data / beacon?)
  • 5GHz OFDM only transmit and receive
  • Open, WEP encryption
  • Station, AP, Ad-hoc configuration
The AR5210 requires an external analog chip (RF5110) which handles the 5GHz conversion.

AR5211

The AR5211 MAC supports:
  • One RX queue
  • Four(?) TX queues
  • OFDM _or_ CCK operation - it doesn't automatically switch between both
  • Open, WEP, WPA encryption schemes
  • Station, AP, Ad-hoc configuration
The AR5211 requires an external analog chip. There were two made: RF5111 for 5GHz operation and RF2111 for 2GHz operation. The driver would configure which analog chip was active.
Since the decoder can't automatically determine the difference between OFDM and CCK on received frames, the AR5211 doesn't support 802.11bg operation. It either supports 802.11b operation (CCK) or 802.11g operation (OFDM) or 802.11a operation (OFDM). This is why madwifi/net80211 supported the concept of "PUREG" - OFDM-only in 2GHz.
The AR5212 MAC supports:
  • One RX queue
  • 10 TX queues - 8 data, 1 beacon, one CAB (content-after-beacon)
  • OFDM, CCK operation - the PHY now supports "voting" on which decoder matched the received preamble and can choose between OFDM and CCK
  • Open, WEP, WPA encryption schemes
  • Station, AP, Ad-hoc configuration.
The AR5212 is the first to support automatic OFDM/CCK detection on received packets. This allows it to operate as an 802.11bg aware device (ie, OFDM and CCK on a 2GHz channel.)
The 10 TX queues allow for separate WMM parameters for each of the 8 WMM QoS levels.

AR5416 and later 802.11n NICs

(TODO)

AR7010

The AR7010 isn't a wireless device - it's a Tensilica core with a USB, Ethernet and PCIe to connect to various devices. The most popular core - AR7010 - has a USB target mode interface, a PCIe interface to connect to an Atheros wireless NIC, and onboard RAM/flash. Other options were available (Ethernet, PCIe to connect to the host.)

AR9271


The AR9271 can be viewed as an AR7010 style device with an AR9285 NIC on-die.

Thursday, November 26, 2015

TCP/IP Address Resolution For IP Multicast Addresses




Like most discussions of address resolution, the preceding sections all focus on unicast communication, where a datagram is sent from one source device to one destination device. Whether direct mapping or dynamic resolution is used for resolving a network layer address, it is a relatively simple matter to resolve addresses when there is only one intended recipient of the datagram. TCP/IP uses ARP for its dynamic resolution scheme, which is designed for unicast resolution only.

However, the Internet Protocol also supports multicasting of datagrams, as I explain in the topics on IP multicasting and IP multicast addressing. In this situation, the datagram must be sent to multiple recipients, which complicates matters considerably. We need to establish a relationship of some sort between the IP multicast group address and the addresses of the devices at the data link layer. We could do this by converting the IP multicast datagram to individual unicast transmissions at the data link layer, each using ARP for resolution, but this would be horribly inefficient.

Direct Mapping Technique for IEEE 802 Multicast MAC Addresses
When possible, IP makes use of the multicast addressing and delivery capabilities of the underlying network to deliver multicast datagrams on a physical network. Perhaps surprisingly, even though ARP employs dynamic resolution, multicast address resolution is done using a version of the direct mapping technique. By defining a mapping between IP multicast groups and data link layer multicast groups we enable physical devices to know when to pay attention to multicasted datagrams.

The most commonly used multicast-capable data link addressing scheme is the IEEE 802 addressing system best known for it use in Ethernet networks. These data link layer addresses have 48 bits, arranged into two blocks of 24. The upper 24 bits are arranged into a block called the organizationally unique identifier (OUI), with different values assigned to individual organizations; the lower 24 bits are then used for specific devices.

The Internet Assigned Number Authority (IANA) itself has an OUI that it uses for mapping multicast addresses to IEEE 802 addresses. This OUI is "01:00:5E". To form a mapping for Ethernet, 24 bits are used for this OUI and the 25th (of the 48) is always zero. This leaves 23 bits of the original 48 to encode the multicast address. To do the mapping, the lower-order 23 bits of the multicast address are used as the last 23 bits of the Ethernet address starting with "01:00:5E" for sending the multicast message. This process is illustrated in Figure 51.



Figure 51: Mapping of Multicast IP Addresses to IEEE 802 Multicast MAC Addresses
IP multicast addresses consist of the bit string “1110” followed by a 28-bit multicast group address. To create a 48-bit multicast IEEE 802 (Ethernet) address, the top 24 bits are filled in with the IANA’s multicast OUI, 01-00-5E, the 25th bit is zero, and the bottom 23 bits of the multicast group are put into the bottom 23 bits of the MAC address. This leaves 5 bits (shown in pink) that are not mapped to the MAC address, meaning that 32 different IP addresses may have the same mapped multicast MAC address.

Monday, November 2, 2015

Kmalloc: Which Flag to Use When

Which Flag to Use When
Situation                                                        Solution
Process context, can sleep                             Use GFP_KERNEL

Process context, cannot sleep                        Use GFP_ATOMIC, or perform your allocations                     
                                                                       with GFP_KERNEL at an earlier or later point
                                                                       when you can sleep.

Interrupt handler                                           Use GFP_ATOMIC

Softirq                                                           Use GFP_ATOMIC

Tasklet                                                          Use GFP_ATOMIC

Need DMA-able memory, can sleep            Use (GFP_DMA | GFP_KERNEL)

Need DMA-able memory, cannot sleep       Use (GFP_DMA | GFP_ATOMIC), or perform your 
                                                                      allocation at an earlier point when you can sleep