
Relocatable object files
In this section, we are going to talk about relocatable object files. As we explained in the previous chapter, these object files are the output of the assembly step in the C compilation pipeline. These files are considered to be temporary products of a C project, and they are the main ingredients to produce further and final products. For this reason, it would be useful to have a deeper look at them and see what we can find in a relocatable object file.
In a relocatable object file, we can find the following items regarding the compiled translation unit:
- The machine-level instructions produced for the functions found in the translation unit (code).
- The values of the initialized global variables declared in the translation unit (data).
- The symbol table containing all the defined and reference symbols found in the translation unit.
These are the key items that can be found in any relocatable object file. Of course, the way that they are put together depends on the object file format, but using proper tools, you should be able to extract these items from a relocatable object file. We are going to do this for an ELF relocatable object file shortly.
But before delving into the example, let's talk about the reason why relocatable object files are named like this. In other words, what does the relocatable mean after all? The reason comes from the process that a linker performs in order to put some relocatable object files together and form a bigger object file – an executable object file or a shared object file.
We discuss what can be found in an executable file in the next section, but for now, we should know that the items we find in an executable object file are the sum of all the items found in all the constituent relocatable object files. Let's just talk about machine-level instructions.
The machine-level instructions found in one relocatable object file should be put next to the machine-level instructions coming from another relocatable object file. This means that the instructions should be easily movable or relocatable. For this to happen, the instructions have no addresses in a relocatable object file, and they obtain their addresses only after the linking step. This is the main reason why we call these object files relocatable. To elaborate more on this, we need to show it in a real example.
Example 3.1 is about two source files, one containing the definitions of two functions, max
and max_3
, and the other source file containing the main
function using the declared functions max
and max_3
. Next, you can see the content of the first source file:
int max(int a, int b) {
return a > b ? a : b;
}
int max_3(int a, int b, int c) {
int temp = max(a, b);
return c > temp ? c : temp;
}
Code Box 3-1 [ExtremeC_examples_chapter3_1_funcs.c]: A source file containing two function definitions
And the second source file looks like the following code box:
int max(int, int);
int max_3(int, int, int);
int a = 5;
int b = 10;
int main(int argc, char** argv) {
int m1 = max(a, b);
int m2 = max_3(5, 8, -1);
return 0;
}
Code Box 3-2 [ExtremeC_examples_chapter3_1.c]: The main function using the already declared functions. Definitions are put in a separate source file.
Let's produce the relocatable object files for the preceding source files. This way, we can investigate the content and that which we explained before. Note that, since we are compiling these sources on a Linux machine, we expect to see ELF object files as the result:
$ gcc -c ExtremeC_examples_chapter3_1_funcs.c -o funcs.o
$ gcc -c ExtremeC_examples_chapter3_1.c -o main.o
$
Shell Box 3-1: Compiling source files to their corresponding relocatable object files
Both funcs.o
and main.o
are relocatable ELF object files. In an ELF object file, the items described to be in a relocatable object file are put into a number of sections. In order to see the present sections in the preceding relocatable object files, we can use the readelf
utility as follows:
$ readelf -hSl funcs.o
[7/7]
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
...
Number of section headers: 12
Section header string table index: 11
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000045 0000000000000000 AX 0 0 1
...
[ 3] .data PROGBITS 0000000000000000 00000085
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 00000085
0000000000000000 0000000000000000 WA 0 0 1
...
[ 9] .symtab SYMTAB 0000000000000000 00000110
00000000000000f0 0000000000000018 10 8 8
[10] .strtab STRTAB 0000000000000000 00000200
0000000000000030 0000000000000000 0 0 1
[11] .shstrtab STRTAB 0000000000000000 00000278
0000000000000059 0000000000000000 0 0 1
...
$
Shell Box 3-2: The ELF content of the funcs.o object file
As you can see in the preceding shell box, the relocatable object file has 11 sections. The sections in bold font are the sections that we have introduced as items existing in an object file. The .text
section contains all the machine-level instructions for the translation unit. The .data
and .bss
sections contain the values for initialized global variables, and the number of bytes required for uninitialized global variables respectively. The .symtab
section contains the symbol table.
Note that, the sections existing in both preceding object files are the same, but their content is different. Therefore, we don't show the sections for the other relocatable object file.
As we mentioned before, one of the sections in an ELF object file contains the symbol table. In the previous chapter, we had a thorough discussion about the symbol table and its entries. We described how it is being used by the linker to produce executable and shared object files. Here, we want to draw your attention to something about the symbol table that we didn't discuss in the previous chapter. This would be in accordance with our explanation on why relocatable object files are named in this manner.
Let's dump the symbol table for funcs.o
. In the previous chapter, we used objdump
but now, we are going to use readelf
to do so:
$ readelf -s funcs.o
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
...
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 5
8: 0000000000000000 22 FUNC GLOBAL DEFAULT 1 max
9: 0000000000000016 47 FUNC GLOBAL DEFAULT 1 max_3
$
Shell Box 3-3: The symbol table of the funcs.o object file
As you can see in the Value
column, the address assigned to max
is 0
and the address assigned to max_3
is 22
(hexadecimal 16
). This means that the instructions related to these symbols are adjacent and their addresses start from 0. These symbols, and their corresponding machine-level instructions, are ready to be relocated to other places in the final executable. Let's look at the symbol table of main.o
:
$ readelf -s main.o
Symbol table '.symtab' contains 14 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
...
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 a
9: 0000000000000004 4 OBJECT GLOBAL DEFAULT 3 b
10: 0000000000000000 69 FUNC GLOBAL DEFAULT 1 main
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND max
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND max_3
$
Shell Box 3-4: The symbol table of the main.o object file
As you can see, the symbols associated with global variables a
and b
, as well as the symbol for the main
function are put at addresses that don't seem be the final addresses that they should be placed at. This is a sign of being a relocatable object file. As we have said before, the symbols in a relocatable object files don't have any final and absolute addresses and their addresses will be determined as part of the linking step.
In the following section, we continue to produce an executable file from the preceding relocatable object files. You will see that the symbol table is different.