链接基本原理(Linking)

《深入理解计算机系统》又名《understanding the computer system》阅读笔记

略有删节,重要内容用下划线标注或高亮显示。

 

7 Linking 349

7.1 CompilerDrivers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350

7.2 StaticLinking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351

7.3 Object Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352

7.4 RelocatableObjectFiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353

7.5 Symbols andSymbolTables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354

7.6 SymbolResolution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357

7.6.1 How Linkers Resolve Multiply-Defined Global Symbols . . . . . . . . . . . . . . . 358

7.6.2 LinkingwithStaticLibraries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361

7.6.3 HowLinkersUseStaticLibraries toResolveReferences . . . . . . . . . . . . . . . 364

7.7 Relocation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365

7.7.1 Relocation Entries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366

7.7.2 Relocating SymbolReferences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367

7.8 ExecutableObjectFiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371

7.9 LoadingExecutableObject Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372

7.10 DynamicLinkingwithSharedLibraries . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374

7.11 Loading andLinking SharedLibraries fromApplications . . . . . . . . . . . . . . . . . . . 376

7.12 *Position-Independent Code (PIC) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377

7.13 Tools forManipulatingObjectFiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381

7.14 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382

 

链接按时间分类:compile time, load time, run time.

7.1. Compiler Drivers编译器驱动程序


Invoke the GCC driver by typing the following command to the shell:

unix> gcc -O2 -g -o p main.c swap.c

Compiller driver translates program from ASCII source file into an executable object file.

Run GCC with the –v option, see the steps by yourself.

Steps:

(1)C preprocessor (cpp) translates the C source file main.c into an ASCII intermediate file main.i:

       Cpp [other arguments] main.c /tmp/main.i

(2)C compiler(cc1) translates main.i into an ASCII assembly language file main.s.

       cc1 /tmp/main.i main.c –O2 [other arguments] –o /tmp/main.s

(3)Assembler(as) translates main.s into a relocatable object file main.o

       as [other arguments] –o /tmp/main.o /tmp/main.s

(4) The driver goes through the same process to generate swap.o.

(5) Run the linker program ld, which combines main.o and swap.o along with the necessary system object files, to create the executable object file p.

       ld –o p [system object files and args] /tmp/main.o /tmp/swap.o

 

Run the executable p.

       The shell invokes a function in the operating system called the loader, which copies the code and data in the executable file p into memory, and then transfer control to the beginning of the program.

 

7.1. Static Linking

Relocatable object files consists of:

(1) Instructions are in one section

(2) Initialized global variables are in another section

(3) Uninitialized variables are in yet another section.

 

7.3 Object Files

Object files come in three forms:

(1)Relocatable object file. Contains binary code and data in a form that can be combined with other

relocatable object files at compile time to create an executable object file.

(2)Executable object file. Contains binary code and data in a form that can be copied directly into

memory and executed.

(3)Shared object file. A special type of relocatable object file that can be loaded into memory and linked

dynamically, at either load time or run time.

 

7.4. Relocatable Object Files

Object file formats vary from system to system.

Modern Unix systems — such as Linux, later versions of System V Unix, BSD

Unix variants, and Sun Solaris — use the Unix Executable and Linkable Format (ELF).

.text: The machine code of the compiled program.

 

.rodata: Read-only data such as the format strings in printf statements, and jump tables for switch

statements (see Problem 7.14).

 

.data: Initialized global C variables. Local C variables are maintained at run time on the stack, and do

not appear in either the .data or .bss sections.

 

.bss: Uninitialized global C variables. This section occupies no actual space in the object file; it is merely a place holder. Object file formats distinguish between initialized and uninitialized variables for space efficiency: uninitialized variables do not have to occupy any actual disk space in the object file.

 

.symtab: A symbol table with information about functions and global variables that are defined and

referenced in the program. Some programmers mistakenly believe that a program must be compiled

with the -g option to get symbol table information. In fact, every relocatable object file has a symbol

table in .symtab. However, unlike the symbol table inside a compiler, the .symtab symbol table

does not contain entries for local variables.

 

.rel.text: A list of locations in the .text section that will need to be modified when the linker

combines this object file with others. In general, any instruction that calls an external function or

references a global variable will need to be modified. On the other hand, instructions that call local

functions do not need to be modified. Note that relocation information is not needed in executable

object files, and is usually omitted unless the user explicitly instructs the linker to include it.

 

.rel.data: Relocation information for any global variables that are referenced or defined by the module.In general, any initialized global variable whose initial value is the address of a global variable

or externally defined function will need to be modified.

 

.debug: A debugging symbol table with entries for local variables and typedefs defined in the program,

global variables defined and referenced in the program, and the original C source file. It is only

present if the compiler driver is invoked with the -g option.

 

.line: A mapping between line numbers in the original C source program and machine code instructions in the .text section. It is only present if the compiler driver is invoked with the -g option.

 

.strtab: A string table for the symbol tables in the .symtab and .debug sections, and for the section names in the section headers. A string table is a sequence of null-terminated character strings.

 

Aside: Why is uninitialized data called .bss?

The use of the term .bss to denote uninitialized data is universal. It was originally an acronym for the “Block

Storage Start” instruction from the IBM 704 assembly language (circa 1957) and the acronym has stuck. A simple

way to remember the difference between the .data and .bss sections is to think of “bss” as an abbreviation for

“Better Save Space!”. End Aside

 

7.5 Symbols and Symbol Tables

Each relocatable object module, m, has a symbol table that contains information about the symbols that are defined and referenced by m. In the context of a linker, there are three different kinds of symbols:

 

(1)Global symbols that are defined by module m  and that can be referenced by other modules. Global

linker symbols correspond to nonstatic C functions and global variables that are defined without the

C static attribute

 

(2) Global symbols that are referenced by module m but defined by some other module. Such symbols are called externals and correspond to C functions and variables that are defined in other modules.

 

(3) Local symbols that are defined and referenced exclusively by module m. Some local linker symbols

correspond to C functions and global variables that are defined with the static attribute. These

symbols are visible anywhere within module m, but cannot be referenced by other modules. The

sections in an object file and the name of the source file that corresponds module m also get local

symbols.

 

It is important to realize that local linker symbols are not the same as local program variables. The symbol

table in .symtab does not contain any symbols that correspond to local nonstatic program variables. These are managed at run time on the stack and are not of interest to the linker.

 

Local procedure variables that are defined with the C static attribute are not managed on

the stack. Instead, the compiler allocates space in .data or .bss for each definition and creates a local

linker symbol in the symbol table with a unique name.( The compiler allocates space in .data if this varible has been initialized, otherwise in .bss.)

 

For example, suppose a pair of functions in the same module define a static local variable x:

1 int f()

2 {

3 static int x = 0;

4 return x;

5 }

6

7 int g()

8 {

9 static int x = 1;

10 return x;

11 }

In this case, the compiler allocates space for two integers in .bss and exports a pair of unique local linker symbols to the assembler. For example, it might use x.1 for the definition in function f and x.2 for the definition in function g.

 

 

Symbol tables are built by assemblers, using symbols exported by the compiler into the assembly language.s file. An ELF symbol table is contained in the .symtab section. It contains an array of entries. Figure7.4 shows the format of each entry.

The name is a byte offset into the string table that points to the null-terminated string name of the symbol.

The value is the symbol’s address. For relocatable modules, the value is an offset from the beginning of the section where the object is defined. For executable object files, the value is an absolute run-time address.

The size is the size (in bytes) of the object.

The type is usually either data or function. The symbol table can also contain entries for the individual sections and for the path name of the original source file. So there are distinct types for these objects as well.

The binding field indicates whether the symbol is local or global.

 

Each symbol is associated with some section of the object file, denoted by the section field, which

is an index into the section header table.

There are three special pseudo-sections that don’t have entriesin the section header table: ABS is for symbols that should not be relocated. UNDEF is for undefined symbols, that is, symbols that are referenced in this object module but defined elsewhere. COMMON is for uninitialized data objects that are not yet allocated. For COMMON symbols, the value field gives the alignment requirement, and size gives the minimum size.

For example, here are the last three entries in the symbol table for main.o, as displayed by the GNU

READELF tool. The first eight entries, which are not shown, are local symbols that the linker uses internally.

In this example, we see an entry for the definition of global symbol buf, an 8-byte object located at an

offset (i.e., value) of zero in the .data section. This is followed by the definition of the global symbol

main, a 17-byte function located at an offset of zero in the .text section. The last entry comes from

the reference for the external symbol swap. READELF identifies each section by an integer index. Ndx=1

denotes the .text section, and Ndx=3 denotes the .data section.

Similarly, here are the symbol table entries for swap.o:

First, we see an entry for the definition of the global symbol bufp0, which is a 4-byte initialized object

starting at offset 0 in .data. The next symbol comes from the reference to the external buf symbol in the initialization code for bufp0. This is followed by the global symbol swap, a 39-byte function at an offset of 0 in .text. The last entry is the global symbol bufp1, a 4-byte uninitialized data object (with a 4-byte alignment requirement) that will eventually be allocated as a .bss object when this module is linked.

Practice Problem 7.1:

This problem concerns the swap.o module from Figure 7.1(b). For each symbol that is defined or

referenced in swap.o, indicate whether or not it will have a symbol table entry in the .symtab section

in module swap.o. If so, indicate the module that defines the symbol (swap.o or main.o), the

symbol type (local, global, or extern) and the section (.text, .data, or .bss) it occupies in that

module.

Symbol

swap.o .symtabentry?

Symbol type

Module where defined

Section

buf

Y

Extern

Main.o

.data

bufp0

Y

Global

Swap.o

.data

bufp1

Y

Global

Swap.o

.bss

swap

Y

Global

Swap.o

.data

temp

N

 

 

 

7.6 Symbol Resolution

The linker resolves symbol references by associating each reference with exactly one symbol definition from the symbol tables of its input relocatable object files. Symbol resolution is straightforward for references to local symbols that are defined in the same module as the reference. The compiler allows only one definition of each local symbol per module. The compiler also ensures that static local variables, which get local linker symbols, have unique names. However, resolving references to global symbols is trickier. When the compiler encounters a symbol (either a variable or function name) that is not defined in the current module, it assumes that it is defined in some other module, generates a linker symbol table entry, and leaves it for the linker to handle. If the linker is unable to find a definition for the referenced symbol in any of its input modules, it prints an (often cryptic) error message and terminates. For example, if we try to compile and link the following source file on a

Linux machine,

1 void foo(void);

2

3 int main() {

4 foo();

5 return 0;

6 }

then the compiler runs without a hitch, but the linker terminates when it cannot resolve the reference to foo:

unix> gcc -Wall -O2 -o linkerror linkerror.c

/tmp/ccSz5uti.o: In function ‘main’:

/tmp/ccSz5uti.o(.text+0x7): undefined reference to ‘foo’

collect2: ld returned 1 exit status

Symbol resolution for global symbols is also tricky because the same symbol might be defined by multiple object files. In this case, the linker must either flag an error, or somehow chose one of the definitions and discard the rest. The approach adopted by Unix systems involves cooperation between the compiler, assembler, and linker, and can introduce some baffling bugs to the unwary programmer.

 

7.6.1 How Linkers Resolve Multiply-Defined Global Symbols

At compile time, the compiler exports each global symbol to the assembler as either strong or weak, and the assembler encodes this information implicitly in the symbol table of the relocatable object file. Functions and initialized global variables get strong symbols. Uninitialized global variables get weak symbols. For the example program in Figure 7.1, buf, bufp0, main, and swap are strong symbols; bufp1 is a weak symbol.

Given this notion of strong and weak symbols, Unix linkers use the following rules for dealing with multiply defined symbols:

_ Rule 1: Multiple strong symbols are not allowed.

_ Rule 2: Given a strong symbol and multiple weak symbols, choose the strong symbol.

_ Rule 3: Given multiple weak symbols, choose any of the weak symbols.

For example, suppose we attempt to compile and link the following two C modules:

1 /* foo1.c */

2 int main()

3 {

4 return 0;

5 }

1 /* bar1.c */

2 int main()

3 {

4 return 0;

5 }

In this case the linker will generate an error message because the strong symbol main is defined multiple

times (Rule 1):

unix> gcc foo1.c bar1.c

/tmp/cca015022.o: In function ‘main’:

/tmp/cca015022.o(.text+0x0): multiple definition of ‘main’

/tmp/cca015021.o(.text+0x0): first defined here

Similarly, the linker will generate an error message for the following modules because the strong symbol x is defined twice (Rule 1):

1 /* foo2.c */

2 int x = 15213;

3

4 int main()

5 {

6 return 0;

7 }

1 /* bar2.c */

2 int x = 15213;

3

4 void f()

5 {

6 }

However, if x is uninitialized in one module, then the linker will quietly choose the strong symbol defined in the other (Rule 2):

1 /* foo3.c */

2 #include <stdio.h>

3 void f(void);

4

5 int x = 15213;

6

7 int main()

8 {

9 f();

10 printf("x = %d/n", x);

11 return 0;

12 }

1 /* bar3.c */

2 int x;

3

4 void f()

5 {

6 x = 15212;

7 }

At run time, function f changes the value of x from 15213 to 15212, which might come as a unwelcome

surprise to the author of function main! Notice that the linker normally gives no indication that it has

detected multiple definitions of x:

unix> gcc -o foobar3 foo3.c bar3.c

unix> ./foobar3

x = 15212

The same thing can happen if there are two weak definitions of x (Rule 3):

1 /* foo4.c */

2 #include <stdio.h>

3 void f(void);

4

5 int x;

6

7 int main()

8 {

9 x = 15213;

10 f();

11 printf("x = %d/n", x);

12 return 0;

13 }

1 /* bar4.c */

2 int x;

3

4 void f()

5 {

6 x = 15212;

7 }

The application of Rules 2 and 3 can introduce some insidious run-time bugs that are incomprehensible to

the unwary programmer, especially if the duplicate symbol definitions have different types. Consider the

following example, where x is defined as an int in one module and a double in another:

1 /* foo5.c */

2 #include <stdio.h>

3 void f(void);

4

5 int x = 15213;

6 int y = 15212;

7

8 int main()

9 {

10 f();

11 printf("x = 0x%x y = 0x%x /n",

12 x, y);

13 return 0;

14 }

 

1 /* bar5.c */

2 double x;

3

4 void f()

5 {

6 x = -0.0;

7 }

 

On an IA32/Linux machine, doubles are 8 bytes and ints are 4 bytes. Thus, the assignment x = -0.0 in line 5 of bar5.c will overwrite the memory locations for x and y (lines 5 and 6 in foo5.c) with the double-precision floating-point representation of negative one!

linux> gcc -o foobar5 foo5.c bar5.c

linux> ./foobar5

x = 0x0 y = 0x80000000

This is a subtle and nasty bug, especially because it occurs silently, with no warning from the compilation

system, and because it typically manifests itself much later in the execution of the program, far away from

where the error occurred. In a large system with hundreds of modules, a bug of this kind is extremely hard

to fix, especially because many programmers are not aware of how linkers work. When in doubt, invoke

the linker with a flag such as the GCC -warn-common flag(我使用的gcc不识别此flag), which instructs it to print a warning message

when it resolves multiply-defined global symbol definitions.

Practice Problem 7.2:

In this problem, let REF(x.i) --> DEF(x.k) denote that the linker will associate an arbitrary

reference to symbol x in module i to the definition of x in module k. For each example below, use

this notation to indicate how the linker would resolve references to the multiply-defined symbol in each

module. If there is a link-time error (Rule 1), write “ERROR”. If the linker arbitrarily chooses one of

the definitions (Rule 3), write “UNKNOWN”.

A. /* Module 1 */

int main()

{

}

/* Module 2 */

int main;

int p2()

{

}

(a) REF(main.1) --> DEF(__ERROR___.___)

(b) REF(main.2) --> DEF(_____.___)

B. /* Module 1 */

void main()

{

}

/* Module 2 */

int main=1;

int p2()

{

}

(a) REF(main.1) --> DEF(__ERROR___.___)

(b) REF(main.2) --> DEF(_____.___)

C. /* Module 1 */

int x;

void main()

{

}

/* Module 2 */

double x=1.0;

int p2()

{

}

(a) REF(x.1) --> DEF(__x___._2__)

(b) REF(x.2) --> DEF(__x___._2__)

 

7.6.2 Linking with Static Libraries

So far we have assumed that the linker reads a collection of relocatable object files and links them together into an output executable file. In practice, all compilation systems provide a mechanism for packaging related object modules into a single file called a static library, which can then be supplied as input to the linker. When it builds the output executable, the linker copies only the object modules in the library that are referenced by the application program.

Why do systems support the notion of libraries? Consider ANSI C, which defines an extensive collection of standard I/O, string manipulation, and integer math functions such as atoi, printf, scanf, strcpy, and random. They are available to every C program in the libc.a library. ANSI C also defines an extensive collection of floating point math functions such as sin, cos, and sqrt in the libm.a library.

Consider the different approaches that compiler developers might use to provide these functions to users without the benefit of static libraries.

One approach would be to have the compiler recognize calls to the standard functions and to generate the appropriate code directly. Pascal, which provides a small set of standard functions, takes this approach, but it is not feasible for C because of the large number of standard functions defined by the C standard. It would add significant complexity to the compiler and would require a new compiler version each time a function was added, deleted, or modified. To application programmers,however, this approach would be quite convenient because the standard functions would always be available.

Another approach would be to put all of the standard C functions in a single relocatable object module, say libc.o, that application programmers could link into their executables:

unix> gcc main.c /usr/lib/libc.o

This approach has the advantage that it would decouple the implementation of the standard functions from the implementation of the compiler, and would still be reasonably convenient for programmers. However, a big disadvantage is that every executable file in a system would now contain a complete copy of the collection of standard functions, which would be extremely wasteful of disk space. (On a typical system, libc.a is about 8 MB and libm.a is about 1 MB.)Worse, each running program would now contain its own copy of these functions in memory, which would be extremely wasteful of memory. Another big disadvantage is that any change to any standard function, no matter how small, would require the library developer to recompile the entire source file, a time-consuming operation that would complicate the development and maintenance of the standard functions.

We could address some of these problems by creating a separate relocatable file for each standard function and storing them in a well-known directory. However this approach would require application programmers to explicitly link the appropriate object modules into their executables, a process that would be error prone and time-consuming:

unix> gcc main.c /usr/lib/printf.o /usr/lib/scanf.o ...

The notion of a static library was developed to resolve the disadvantages of these various approaches. Related functions can be compiled into separate object modules and then packaged in a single static library file. Application programs can then use any of the functions defined in the library by specifying a single file name on the command line. For example, a program that uses functions from the standard C library and the math library could be compiled and linked with a command of the form:

unix> gcc main.c /usr/lib/libm.a /usr/lib/libc.a

At link time, the linker will only copy the object modules that are referenced by the program, which reduces the size of the executable on disk and in memory. On the other hand, the application programmer only needs to include the names of a few library files. (In fact, C compiler drivers always pass libc.a to the linker, so the reference to libc.a above is unnecessary.)

On Unix systems, static libraries are stored on disk in a particular file format known as an archive. An

archive is a collection of concatenated relocatable object files, with a header that describes the size and

location of each member object file. Archive filenames are denoted with the .a suffix. To make our

discussion of libraries concrete, suppose that we want to provide the vector routines in Figure 7.5 in a static library called libvector.a.

To create the library, we would use the AR tool:

unix> gcc -c addvec.c multvec.c

unix> ar rcs libvector.a addvec.o multvec.o

To use the library, we might write an application such as main2.c in Figure 7.6, which invokes the addvec library routine. (The include file vector.h defines the function prototypes for the routines inlibvector.a.)

To build the executable, we would compile and link the input files main.o and libvector.a:

unix> gcc -O2 -c main2.c

unix> gcc -static -o p2 main2.o ./libvector.a

Figure 7.7 summarizes the activity of the linker. The -static argument tells the compiler driver that the linker should build a fully-linked executable object file that can be loaded into memory and run without any further linking at load time. When the linker runs, it determines that the addvec symbol defined by addvec.o is referenced by main.o, so it copies addvec.o into the executable. Since the program doesn’t reference any symbols defined by multvec.o, the linker does not copy this module into the executable. The linker also copies the printf.o module from libc.a, along with a number of other modules from the C run-time system.

7.6.3 How Linkers Use Static Libraries to Resolve References

During the symbol resolution phase, the linker scans the relocatable object files and archives left to right in the same sequential order that they appear on the compiler driver’s command line. (The driver automatically translates any .c files on the command line into .o files.) During this scan, the linker maintains a set E of relocatable object files that will be merged to form the executable, a set U of unresolved symbols (i.e., symbols referred to but not yet defined), and a set D of symbols that have been defined in previous input files. Initially, E, U, and D are empty.

 

_ For each input file f on the command line, the linker determines if f is an object file or an archive.

If f is an object file, the linker adds f to E, updates U and D to reflect the symbol definitions and

references in f, and proceeds to the next input file.

 

_ If f is an archive, the linker attempts to match the unresolved symbols in U against the symbols

defined by the members of the archive. If some archive member, m, defines a symbol that resolves a

reference in U, then m is added to E, and the linker updates U and D to reflect the symbol definitions

and references inm. This process iterates over the member object files in the archive until a fixed point

is reached where U and D no longer change. At this point, any member object files not contained in

E are simply discarded and the linker proceeds to the next input file.

 

_ If U is nonempty when the linker finishes scanning the input files on the command line, it prints

an error and terminates. Otherwise it merges and relocates the object files in E to build the output

executable file.

Unfortunately, this algorithm can result in some baffling link-time errors because the ordering of libraries

and object files on the command line is significant. If the library that defines a symbol appears on the

command line before the object file that references that symbol, then the reference will not be resolved and linking will fail. For example:

unix> gcc -static ./libvector.a main2.c

/tmp/cc9XH6Rp.o: In function ‘main’:

/tmp/cc9XH6Rp.o(.text+0x18): undefined reference to ‘addvec’

Here is what happened: When libvector.a is processed, U is empty, so no member object files from

libvector.a are added to E. Thus the reference to addvec is never resolved, and the linker emits an

error message and terminates..

The general rule for libraries is to place them at the end of the command line. If the members of the

different libraries are independent, in that no member references a symbol defined by another member, then the libraries can be placed at the end of the command line in any order. On the other hand, if the libraries are not independent, then they must be ordered so that for each symbol s that is referenced externally by a member of an archive, at least one definition of s follows a reference to

s on the command line. For example, suppose foo.c calls functions in libx.a and libz.a that call

functions in liby.a. Then libx.a and libz.a must precede liby.a on the command line:

unix> gcc foo.c libx.a libz.a liby.a

Libraries can be repeated on the command line if necessary to satisfy the dependence requirements. For

example, suppose foo.c calls a function in libx.a that calls a function in liby.a that calls a function in libx.a. Then libx.a must be repeated on the command line:

unix> gcc foo.c libx.a liby.a libx.a

Alternatively, we could combine libx.a and liby.a into a single archive.

Practice Problem 7.3:

Let a and b denote object modules or static libraries in the current directory, and let a->b denote that

a depends on b, in the sense that b defines a symbol that is referenced by a. For each of the following

scenarios, show the minimal command line (i.e., one with the least number of file object file and library

arguments) that will allow the static linker to resolve all symbol references.

A. p.o->libx.a.

       gcc p.o libx.a

B. p.o->libx.a->liby.a.

       gcc p.o libx.a liby.a

C. p.o->libx.a->liby.a and liby.a->libx.a->p.o.

       gcc p.o libx.a liby.a libx.a p.o

7.7 Relocation

Once the linker has completed the symbol resolution step, it has associated each symbol reference in the

code with exactly one symbol definition (i.e., a symbol table entry in one of its input object modules). At

this point, the linker knows the exact sizes of the code and data sections in its input object modules. It is

now ready to begin the relocation step, where it merges the input modules and assigns run-time addresses

to each symbol. Relocation consists of two steps:

_ Relocating sections and symbol definitions. In this step, the linker merges all sections of the same

type into a new aggregate section of the same type. For example, the .data sections from the input

modules are all merged into one section that will become the .data section for the output executable

object file. The linker then assigns run-time memory addresses to the new aggregate sections, to each

section defined by the input modules, and to each symbol defined by the input modules. When this

step is complete, every instruction and global variable in the program has a unique run-time memory

address.

_ Relocating symbol references within sections. In this step, the linker modifies every symbol reference

in the bodies of the code and data sections so that they point to the correct run-time addresses. To

perform this step, the linker relies on data structures in the relocatable object modules known as

relocation entries, which we describe next.

7.7.1 Relocation Entries

When an assembler generates an object module, it does not know where the code and data will ultimately

be stored in memory. Nor does it know the locations of any externally defined functions or global variables that are referenced by the module. So whenever the assembler encounters a reference to an object whose ultimate location is unknown, it generates a relocation entry that tells the linker how to modify the reference

when it merges the object file into an executable. Relocation entries for code are placed in .relo.text.

Relocation entries for initialized data are placed in .relo.data.

Figure 7.8 shows the format of an ELF relocation entry.

The offset is the section offset of the referencethat will need to be modified.

The symbol identifies the symbol that the modified reference should pointto.

The type tells the linker how to the modify the new reference.

Figure 7.8: ELF relocation entry. Each entry identifies a reference that must be relocated.

ELF defines 11 different relocation types, some quite arcane. We are concerned with only the two most

basic relocation types:

l         R_386_PC32: Relocate a reference that uses a 32-bit PC-relative address. Recall from Section 3.6.3

that a PC-relative address is an offset from the current run-time value of the program counter (PC).

When the CPU executes an instruction using PC-relative addressing, it forms the effective address

(e.g., the target of the call instruction) by adding the 32-bit value encoded in the instruction to the

current run-time value of the PC, which is always the address of the next instruction in memory.

l         R_386_32: Relocate a reference that uses a 32-bit absolute address. With absolute addressing, the

CPU directly uses the 32-bit value encoded in the instruction as the effective address, without further

modifications.

7.7.2 Relocating Symbol References

(这一节需要读到最后才可以完全理解,中间有不理解的地方先略过)

Figure 7.9 shows the pseudo-code for the linker’s relocation algorithm.

1 foreach section s {           //text section, data section

2 foreach relocation entry r {

3 refptr = s + r.offset; /* ptr to reference to be relocated */

4

5 /* relocate a PC-relative reference */

6 if (r.type == R_386_PC32) {

7 refaddr = ADDR(s) + r.offset; /* ref’s runtime address */

8 *refptr = (unsigned) (ADDR(r.symbol) + *refptr - refaddr);

9 }

10

11 /* relocate an absolute reference */

12 if (r.type == R_386_32)

13 *refptr = (unsigned) (ADDR(r.symbol) + *refptr);

14 }

15 }

Figure 7.9: Relocation algorithm.

Lines 1 and 2 iterate over each section s and each relocation entry r associated with each section. For

concreteness, assume that each section s is an array of bytes and that each relocation entry r is a struct of type Elf32 Rel, as defined in Figure 7.8. Also, assume that when the algorithm runs, the linker has already chosen run-time addresses for each section (denoted ADDR(s)), and each symbol (denoted ADDR(r.symbol)). Line 3 computes the address in the s array of the 4-byte reference that needs to be relocated. If this reference uses PC-relative addressing, then it is relocated by lines 5–9. If the reference uses absolute addressing, then it is relocated by lines 11–13.

 

Relocating PC-Relative References

Recall from our running example in Figure 7.1(a) that the main routine in the .text section of main.o calls the swap routine, which is defined in swap.o. Here is the disassembled listing for the call instruction, as generated by the GNU OBJDUMP tool:

 

6: e8 fc ff ff ff call 7 <main+0x7> swap();

7: R_386_PC32 swap relocation entry

From this listing we see that the call instruction begins at section offset 0x6 and consists of the 1-byte

opcode 0xe8, followed by the 32-bit reference 0xfffffffc (

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值