Learn c language again (IV. how to link the program)

After so much foreshadowing, this time we finally came to how the program is linked. We should see the previous sections. We can guess how the link is linked. In fact, the program link is not so difficult. Next, let's analyze a wave.

Originally, I thought the program link was relatively simple. When I went to prepare, I found that I didn't understand some details very well, so I went to read the book "in-depth understanding of computer system". Finally, my doubts were solved, and then I wrote them according to my own thinking.

4.1 how is the program linked

After the previous analysis, we understand that the program is compiled first and then linked. Since the previous examples are all a. c file, it is not easy to reflect the link, so in this section, we will add another. c file and then call across files to better analyze the problem.

#include <stdio.h>

int f_a = 0;
int f_b = 84;

int func2(int i)
{
    static int s_a = 0;
    static int s_b = 84;

    printf("i = %d %d %d\n", i, s_a, s_b);
    return 0;
}
#include <stdio.h>

int g_a = 0;
int g_b = 84;

int func1(int i)
{
    printf("i = %d\n", i);
    return 0;
}

int main(int argc, char **argv)
{
    static int s_a = 0;
    static int s_b = 84;

    int a = 1;
    int b;
    func1(s_a+s_b+a+b);
    func2(s_a+s_b+a+b);
    printf("hello world %d %d %d\n", g_a, a, b);

    return 0;
}

Posted two programs and cheated hundreds of words. If you write a novel, you'll make money, ha ha ha.

When the code is written, it must be compiled. Don't talk about compilation. You can compile the previous sections and relocatable files. You can also see the previous sections. In this section, we focus on links. No more nonsense. Let's get to the point.

4.1.1 symbol analysis

What does symbol parsing do? To tell you the truth, a few days ago, I also made a circle for this thing, so I haven't written the reason.

Don't know what to do? I can't help looking for information and watching videos. Here's a video of station b. you can go and have a look. It's really good:

[essences] programmers' self-cultivation video tutorial

After several days of study, I finally understand what this symbol parsing is. My understanding of symbol parsing is: the linker finds the symbol reference in the whole program (where the symbol is used, such as using global variables and calling other file functions), and then finds the corresponding symbol definition (where the symbol is defined in the code) through this symbol reference.

Here someone will ask: we define two global variables with the same name in the same file, which is an error reported by the compiler.

Yes, the compiler will independently check the syntax of a. c file. There must be problems with two global variables with the same name. The compiler can find them. The compiler will also provide a local link symbol for static local variables. What the compiler cannot do is those referenced in this file but not defined. These parts cannot be done by the compiler, We can only give it to the linker. In the previous section, we also saw the symbol table of the compiled. o file. Here we review:

root@ubuntu:~/c_test/04# readelf -s fun2.o 

Symbol table '.symtab' contains 15 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS fun2.c
     6: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    3 s_b.2289
     7: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    4 s_a.2288
    11: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    4 f_a
    12: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 f_b
    13: 0000000000000000    50 FUNC    GLOBAL DEFAULT    1 func2
    14: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

This is the legacy left by the compiler to the linker. LOCAL represents the scope of this file, and the linker will ignore it. The linker is concerned about this GLOBAL, and the linker will try to find the definition of these symbols through these symbol references.

What happens when the linker can't find these symbols? The linker will report an error. We often see this error. Let's review it again:

root@ubuntu:~/c_test/04# gcc hello_world.c 
/tmp/ccsjP1Lu.o: In function `main':
hello_world.c:(.text+0x7b): undefined reference to `func2'
collect2: error: ld returned 1 exit status
root@ubuntu:~/c_test/04# 

This problem is generally due to the lack of library files or the lack of target files containing this function. You need to modify the linked parameters, which will be described later.

4.1.2 consolidation of similar sections

After the above symbol parsing, the next thing the linker needs to do is merge the similar segments.

When analyzing relocatable files, we can see that each relocatable file is divided into several segments. We have also browsed the executable file a little before, and there are indeed many segments in it. This imagination can guess whether the similar segments of our multiple relocatable files have been merged. In fact, it is true.

Let's take the above two examples back to disassembly:

root@ubuntu:~/c_test/04# objdump -h fun2.o 

fun2.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000032  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  0000000000000000  0000000000000000  00000074  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000008  0000000000000000  0000000000000000  0000007c  2**2
                  ALLOC
  3 .rodata       0000000e  0000000000000000  0000000000000000  0000007c  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
root@ubuntu:~/c_test/04# objdump -h hello_world.o 

hello_world.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         000000a3  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  0000000000000000  0000000000000000  000000e4  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000008  0000000000000000  0000000000000000  000000ec  2**2
                  ALLOC
  3 .rodata       0000001e  0000000000000000  0000000000000000  000000ec  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
root@ubuntu:~/c_test/04# objdump -h hello_world 

hello_world:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
 13 .text         00000242  0000000000400430  0000000000400430  00000430  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 15 .rodata       00000030  0000000000400680  0000000000400680  00000680  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 24 .data         00000020  0000000000601028  0000000000601028  00001028  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 25 .bss          00000018  0000000000601048  0000000000601048  00001048  2**2
                  ALLOC

Each segment of the final executable is larger than the sum of their two segments, which is called alignment.

When you are free, you can use objdump -s to view their binaries, and you will find that the last executable does contain the above two relocatable files.

I don't need this order here.

4.1.3 space allocation

After merging the above segments, the linker finally knows the size of each segment. After knowing the size of each segment, what do you do?

Then share the candy!!

Of course, there is no candy in the program. There is only memory in the program. Therefore, after determining the size, the space allocation of each segment is also determined. The space allocation includes the location and offset of the executable file. This benefit is not great, so it can be ignored. In addition, the allocation of virtual address is more important. These things need to be loaded when the program is running.

Is it silly to listen to this? The picture above shows:

fun2.c does not link the previous segment information:

root@ubuntu:~/c_test/04# objdump -h fun2.o 

fun2.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000032  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  0000000000000000  0000000000000000  00000074  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000008  0000000000000000  0000000000000000  0000007c  2**2
                  ALLOC
  3 .rodata       0000000e  0000000000000000  0000000000000000  0000007c  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      00000036  0000000000000000  0000000000000000  0000008a  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  0000000000000000  0000000000000000  000000c0  2**0
                  CONTENTS, READONLY
  6 .eh_frame     00000038  0000000000000000  0000000000000000  000000c0  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

This is the linked hello_ Segment information of world:

root@ubuntu:~/c_test/04# objdump -h hello_world

hello_world:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .interp       0000001c  0000000000400238  0000000000400238  00000238  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 13 .text         00000242  0000000000400430  0000000000400430  00000430  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 14 .fini         00000009  0000000000400674  0000000000400674  00000674  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 15 .rodata       00000030  0000000000400680  0000000000400680  00000680  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 16 .eh_frame_hdr 00000044  00000000004006b0  00000000004006b0  000006b0  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 17 .eh_frame     00000134  00000000004006f8  00000000004006f8  000006f8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 24 .data         00000020  0000000000601028  0000000000601028  00001028  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 25 .bss          00000018  0000000000601048  0000000000601048  00001048  2**2
                  ALLOC
 26 .comment      00000035  0000000000000000  0000000000000000  00001048  2**0
                  CONTENTS, READONLY

Here, VMA represents the virtual address and LMA represents the load address. Under normal circumstances, these two values are the same, unless those embedded systems may be different. Here we analyze the ubuntu system, and the two values must be the same, so we can focus on VMA. Size is the size of this segment, and file off is the offset in the executable file, which we ignore.

The biggest difference between the two information is VMA. There is no virtual address assigned before the link, but only after the link.

You need to see the layout of this executable file. You can see Chapter 3, which briefly introduces the executable file.

There are so many sections of executable files. It will be a long way to introduce them later.

Some sharp eyed students see that the virtual address of. text starts from 0x0000000000400430, not from 0. This is also talked about later. Curious students, collect curiosity, and we will continue to talk about links.

Here is the key information. In the previous section, we check whether there is an entry address in the ELF header. This address is actually the entry address of. text:

root@ubuntu:~/c_test/04# readelf -h hello_world
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:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400430
  Start of program headers:          64 (bytes into file)
  Start of section headers:          6976 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         31
  Section header string table index: 28

Do you feel that knowledge is related and that understanding is the most comfortable.

4.1.4 determination of symbol address

Since the address of each segment is confirmed, the symbol address in the segment can also be confirmed.

4.1.4.1. Text symbol address determination

We use Hello this time_ Take world. O as an example, the Hello obtained by our disassembly_ world.o:

root@ubuntu:~/c_test/04# objdump -d hello_world.o 

hello_world.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <func1>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp


0000000000000026 <main>:
  26:	55                   	push   %rbp
  27:	48 89 e5             	mov    %rsp,%rbp

From the previous section, we know that the starting position of the. text segment is 0x0000000000400430, and the address of func1 function is 0x0000000000400430+X (x is the offset).

Then many students said confidently that func1 is in hello_ The offset in world. O is 0, so it should be 0x0000000000400430+0;

In fact, this is not the case. We seem to have forgotten the merging of similar segments in the front. We also have a func2.o, so we should add the size of func2.o. someone asked why func2.o is in hello_ Before world. O, this was inevitable, hello_ There is a main function in world. O. you must be ready before calling the main function.

The size of the. text segment of func2.o can be obtained by disassembly, so the offset of our func1 function is equal to: 0x000000000040430 + 0x32 = 0x000000000040462?

In fact, the virtual address is not this, because the linker is secretly linking a lot of things. We disassemble hello_world view:

Disassembly of section .text:

0000000000400430 <_start>:
  400430:	31 ed                	xor    %ebp,%ebp
 ...
  40045a:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)

0000000000400460 <deregister_tm_clones>:
  400460:	b8 4f 10 60 00       	mov    $0x60104f,%eax
 ...
  40049d:	00 00 00 

00000000004004a0 <register_tm_clones>:
  4004a0:	be 48 10 60 00       	mov    $0x601048,%esi
 ...
  4004da:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)

00000000004004e0 <__do_global_dtors_aux>:
  4004e0:	80 3d 61 0b 20 00 00 	cmpb   $0x0,0x200b61(%rip)        # 601048 <__TMC_END__>
  ...
  4004fc:	0f 1f 40 00          	nopl   0x0(%rax)

0000000000400500 <frame_dummy>:
  400500:	bf 20 0e 60 00       	mov    $0x600e20,%edi
  ...
  400521:	e9 7a ff ff ff       	jmpq   4004a0 <register_tm_clones>

0000000000400526 <func2>:
  400526:	55                   	push   %rbp
  ...
  400557:	c3                   	retq   

0000000000400558 <func1>:
  400558:	55                   	push   %rbp
  ...
  40057d:	c3                   	retq   

000000000040057e <main>:
  40057e:	55                   	push   %rbp
 ...
  4005fb:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)

0000000000400600 <__libc_csu_init>:
  400600:	41 57                	push   %r15
 ...
  40066d:	00 00 00 

0000000000400670 <__libc_csu_fini>:
  400670:	f3 c3                	repz retq 

Through disassembly, it is found that there are so many codes in front, and the linker is really the king of stealth. In fact, the compiler does a lot of work. We will talk about these functions in the head later. As long as this department of code is the code just executed by the program and is responsible for calling the mian function, we will ignore it first.

We can start with func2: 0x0000000000400526. The virtual address of func1 is 0x0000000000400526+0x32=0x0000000000400558.

Quickly go back to the answer and find that it is right. This step also proves the merger of similar paragraphs. In similar sections, I am lazy to disassemble and view. ha-ha.

4.1.4.2. Data symbol address determination

The above describes the address determination of the function symbol in the. text section. Let's take a look at the symbol determination in the. data section.

Functions can be seen when the. data section is not followed by the. text section. As mentioned earlier, the. data section can only be disassembled to view the stored values:

# data section in func2.o
Contents of section .data:
 0000 54000000 54000000                    T...T...
 #  hello_ data section in world. O
 Contents of section .data:
 0000 54000000 54000000                    T...T...
 # Although the two are the same, they represent different variables. I was just thinking that the compiler and linker should store values and symbols according to certain regulations before they can be read normally
 # hello_ data segment in world
 Contents of section .data:
 601028 00000000 00000000 00000000 00000000  ................
 601038 54000000 54000000 54000000 54000000  T...T...T...T...

From the above, we can see the virtual address 0x0000000000601028 from the. data segment.

hello_ The. data segment in the world is a combination of two relocatable files. Why are so many zeros added in front? I'm also a little confused. The alignment behind the bright side means that 2 * * 3 = 8-byte alignment. Obviously, 16 bytes are also 8-byte alignment. Keep this later, or who knows. Tell me in the comment area. Thank you.

Without tangled alignment problems, we can see that starting from the address 0x601038 is the merger of our two relocatable files, so the addresses of these four variables are 0x601038, 0x60103C, 0x601040 and 0x601044 respectively.

However, according to the regulations of the compiler, it should also be stored in order. It is also emphasized here that the compiler will specify the symbolic definition of static local variables. We can take a look at two static local variables:

# Symbol table for func2.o
YMBOL TABLE:
0000000000000004 l     O .data	0000000000000004 s_b.2289
0000000000000004 l     O .bss	0000000000000004 s_a.2288
0000000000000000 g     O .bss	0000000000000004 f_a
0000000000000000 g     O .data	0000000000000004 f_b
# hello_ Symbol table of world. O
SYMBOL TABLE:
0000000000000004 l     O .bss	0000000000000004 s_a.2292
0000000000000004 l     O .data	0000000000000004 s_b.2294
0000000000000000 g     O .bss	0000000000000004 g_a
0000000000000000 g     O .data	0000000000000004 g_b

So press the initialized address, f_b will be parsed by the compiler first, so f_b's address is 0x601038

Then: s_b.2289: 0x60103C

g_b: 0x601040

s_b.2294: 0x601044

Are there other students wondering if there are several variables?

In fact, the remaining variables are in the bss segment, and the analysis method is the same as that in the. data segment. We won't analyze them here.

I feel like disassembly to see if our results are correct:

000000000060103c l     O .data	0000000000000004              s_b.2289
0000000000601050 l     O .bss	0000000000000004              s_a.2288
0000000000601058 l     O .bss	0000000000000004              s_a.2292
0000000000601044 l     O .data	0000000000000004              s_b.2294
0000000000601038 g     O .data	0000000000000004              f_b
0000000000601040 g     O .data	0000000000000004              g_b
000000000060104c g     O .bss	0000000000000004              f_a
0000000000601054 g     O .bss	0000000000000004              g_a

After comparison, it is exactly the same, and the symbolic address of. data is confirmed in this way.

4.1.4.3 review symbol table

Here's another question:

We will store the symbol names in. strtab, which contains function names and variable names, and then how these symbols are mapped with the positions of other segments. We will mention the symbol table, which describes this information. Maybe we didn't use the symbol table in the previous section, so we didn't look carefully.

st_name is the subscript of this symbol in the string table (. strtab), st_value is the offset of the segment in the relocatable file and the virtual address in the executable file, st_size is the symbol size, st_other represents the information of this symbol, such as global, local and st_shndx is the segment where this symbol is located.

Now do you understand it all? It turned out to be so.

4.1.5 relocation

After the symbol address is determined, does it mean the completion of the link.

In fact, it's not. We just determined the symbol address, but the symbol address referenced in our code is still the original, so this step is to fix the symbol reference in the code.

To fix this step, the linker depends on the relocation bit table, which is also generated by the compiler when compiling. Give out the information that needs relocation, so that the linker can accurately find the part that needs relocation.

4.1.5.1 relocation table

As the old rule, let's take a look at the relocation table:

root@ubuntu:~/c_test/04# readelf -r fun2.o

Relocation section '.rela.text' at offset 0x290 contains 4 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
00000000000d  000300000002 R_X86_64_PC32     0000000000000000 .data + 0
000000000013  000400000002 R_X86_64_PC32     0000000000000000 .bss + 0
00000000001d  00050000000a R_X86_64_32       0000000000000000 .rodata + 0
000000000027  000e00000002 R_X86_64_PC32     0000000000000000 printf - 4

root@ubuntu:~/c_test/04# readelf -r hello_world.o 

Relocation section '.rela.text' at offset 0x3a0 contains 12 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000011  00050000000a R_X86_64_32       0000000000000000 .rodata + 0
00000000001b  000e00000002 R_X86_64_PC32     0000000000000000 printf - 4
00000000003e  000400000002 R_X86_64_PC32     0000000000000000 .bss + 0
000000000044  000300000002 R_X86_64_PC32     0000000000000000 .data + 0
000000000057  000d00000002 R_X86_64_PC32     0000000000000000 func1 - 4
00000000005d  000400000002 R_X86_64_PC32     0000000000000000 .bss + 0
000000000063  000300000002 R_X86_64_PC32     0000000000000000 .data + 0
00000000007b  001000000002 R_X86_64_PC32     0000000000000000 func2 - 4
000000000081  001100000002 R_X86_64_PC32     0000000000000000 f_a - 8
00000000008b  000b00000002 R_X86_64_PC32     0000000000000000 g_a - 4
000000000098  00050000000a R_X86_64_32       0000000000000000 .rodata + 8
0000000000a2  000e00000002 R_X86_64_PC32     0000000000000000 printf - 4

Let's see how to describe the relocation table:

typedef struct {
	long offset;	// The section offset of the reference that needs to be modified
	long type : 32;		// Relocation type
		symbol : 32;	// Identifies the symbol that the modified reference should point to
	long addend;		// Use it to offset the value referenced by the modification
}Elf64_Rela;

The type is relatively simple:

There are also many types of type, but we only care about two types.

R_X86_64_PC32: relocates a reference that uses a 32-bit PC relative address.

R_X86_64_32: relocates a reference that uses a 32-bit absolute address.

4.1.5.2 relocation symbol reference

Let's first look at how the relocatable file is saved before relocating. This symbol reference.

0000000000000026 <main>:
  26:	55                   	push   %rbp
  27:	48 89 e5             	mov    %rsp,%rbp
  2a:	48 83 ec 20          	sub    $0x20,%rsp
  2e:	89 7d ec             	mov    %edi,-0x14(%rbp)
  31:	48 89 75 e0          	mov    %rsi,-0x20(%rbp)
  35:	c7 45 f8 01 00 00 00 	movl   $0x1,-0x8(%rbp)
  3c:	8b 15 00 00 00 00    	mov    0x0(%rip),%edx        # 42 <main+0x1c>    s_a.2293 = %edi
  42:	8b 05 00 00 00 00    	mov    0x0(%rip),%eax        # 48 <main+0x22>		s_b.2294 = %edx
  48:	01 c2                	add    %eax,%edx
  4a:	8b 45 f8             	mov    -0x8(%rbp),%eax
  4d:	01 c2                	add    %eax,%edx
  4f:	8b 45 fc             	mov    -0x4(%rbp),%eax
  52:	01 d0                	add    %edx,%eax
  54:	89 c7                	mov    %eax,%edi
  56:	e8 00 00 00 00       	callq  5b <main+0x35>			# func1
  5b:	8b 15 00 00 00 00    	mov    0x0(%rip),%edx        # 61 <main+0x3b>	s_a.2293
  61:	8b 05 00 00 00 00    	mov    0x0(%rip),%eax        # 67 <main+0x41>	s_b.2294
  67:	01 c2                	add    %eax,%edx
  69:	8b 45 f8             	mov    -0x8(%rbp),%eax
  6c:	01 c2                	add    %eax,%edx
  6e:	8b 45 fc             	mov    -0x4(%rbp),%eax
  71:	01 d0                	add    %edx,%eax
  73:	89 c7                	mov    %eax,%edi
  75:	b8 00 00 00 00       	mov    $0x0,%eax
  7a:	e8 00 00 00 00       	callq  7f <main+0x59>			# func2
  7f:	c7 05 00 00 00 00 64 	movl   $0x64,0x0(%rip)        # 89 <main+0x63>  f_a
  86:	00 00 00 
  89:	8b 05 00 00 00 00    	mov    0x0(%rip),%eax        # 8f <main+0x69>  g_a
  8f:	8b 4d fc             	mov    -0x4(%rbp),%ecx
  92:	8b 55 f8             	mov    -0x8(%rbp),%edx
  95:	89 c6                	mov    %eax,%esi
  97:	bf 00 00 00 00       	mov    $0x0,%edi
  9c:	b8 00 00 00 00       	mov    $0x0,%eax
  a1:	e8 00 00 00 00       	callq  a6 <main+0x80>		# printf
  a6:	b8 00 00 00 00       	mov    $0x0,%eax
  ab:	c9                   	leaveq 
  ac:	c3                   	retq

This is the disassembly code before the main function is not linked, and the annotated ones need to be relocated.

  1. Relocate PC relative references

    Let's analyze this first:

    56: e8 00 00 00 00 callq 5b <main+0x35> # func1

    000000000057 000d00000002 R_X86_64_PC32 0000000000000000 func1 - 4

    The offset of func1 in the code is 0x56+1. Why add 1, because e8 is the opcode of callq.

    So what we got

    r.offset = 0x57;
    r.type = R_X86_64_PC32;
    r.symbol = func1;
    r.addend = -4;
    

    After the above processing, we have known the virtual address of. text: 0x0000000000400430

    And the virtual address of func1: 0x0000000000400558

    You can calculate the address when the instruction runs: refaddr = 0x000000000040430 + R. offset + corrected address = 0x000000000040430 + 0x57 + 0x128 = 0x0000000000405af (the corrected address is the starting position from the. text segment to the. text segment of hello_world.o. don't forget that we need to merge)

    Then update the application: * refptr = 0x0000000000400558 - 4 - 0x00000000004005af = 0xffffffffffffffffa5. Because this is a 32-bit PC offset, the final value is 0xFFFFFFA5.

    This value can be filled into the original position. Of course, we can directly view the answer:

    000000000040057e <main>:
      40057e:	55                   	push   %rbp
      40057f:	48 89 e5             	mov    %rsp,%rbp
      400582:	48 83 ec 20          	sub    $0x20,%rsp
      400586:	89 7d ec             	mov    %edi,-0x14(%rbp)
      400589:	48 89 75 e0          	mov    %rsi,-0x20(%rbp)
      40058d:	c7 45 f8 01 00 00 00 	movl   $0x1,-0x8(%rbp)
      400594:	8b 15 be 0a 20 00    	mov    0x200abe(%rip),%edx        # 601058 <s_a.2293>
      40059a:	8b 05 a4 0a 20 00    	mov    0x200aa4(%rip),%eax        # 601044 <s_b.2294>
      4005a0:	01 c2                	add    %eax,%edx
      4005a2:	8b 45 f8             	mov    -0x8(%rbp),%eax
      4005a5:	01 c2                	add    %eax,%edx
      4005a7:	8b 45 fc             	mov    -0x4(%rbp),%eax
      4005aa:	01 d0                	add    %edx,%eax
      4005ac:	89 c7                	mov    %eax,%edi
      4005ae:	e8 a5 ff ff ff       	callq  400558 <func1>    #  The answer is here
      4005b3:	8b 15 9f 0a 20 00    	mov    0x200a9f(%rip),%edx        # 601058 <s_a.2293>
      4005b9:	8b 05 85 0a 20 00    	mov    0x200a85(%rip),%eax        # 601044 <s_b.2294>
    

    Is this the answer? Some people wonder if it will be such a large number. 0xffffa5 represents - 91, because when we link, the main function is behind and func1 is in front, so the pc pointer must move forward, and moving forward is - 91.

    The PC pointer refers to the address of the next instruction. When the program runs to 0x4005ae, the PC value is 0x4005b3

    0x4005b3 + 0xffffffa5 = 0x400558. This is exactly the address, so the offset of append seems to be the offset of this PC.

    Suddenly, it is found that most of the data offsets are also relative offsets. Let's then analyze the data.

    40059d: 8b 05 b1 0a 20 00 mov 0x200ab1(%rip),%eax # 601054 <g_a>

    00000000008b 000b00000002 R_X86_64_PC32 0000000000000000 g_a - 4

    Based on these two information:

    r.offset = 0x81;
    r.type = R_X86_64_PC32;
    r.symbol = f_a;
    r.addend = -8;
    

    Address of. data: 0x40059d + 0x02 (instruction offset 2 bytes)

    Variable G_ The address of a is 0x601054,

    Finally, calculate the offset: 0x601054 - 0x40059d - 4 = 0x20 0AB1.

    This will happen.

  2. Relocate absolute references

    Relocating absolute references should be a little simpler. Let's analyze it. It seems that there is no in the code, so let's directly say the formula.

    r.offset = ;
    r.type = R_X86_64_PC32;
    r.symbol = xxx;
    r.addend = ;
    

    You need to determine the virtual address of ADDR(r.symbol), and then add it directly.

    Formula: addr (r.symbol) + r.append = absolute address.

4.1.6 common block

Although it is seen in both books that uninitialized global variables are defined in the COMMON block, in practice, uninitialized global variables are also placed in the. bss section. I don't know whether the compiler has been updated. This problem will be left for later analysis when it is encountered again.

Of course, I also defined a weakly typed variable, but this variable seems to exist directly in the week section, which may be a section handle I set.

__attribute__((weak)) int f_a = 2;

11: 0000000000000000     4 OBJECT  WEAK   DEFAULT    3 f_a

[ 3] .data             PROGBITS         0000000000000000  000000b0
       000000000000000c  0000000000000000  WA       0     0     4

4.1.7 c + + related issues

c + + language is much more complex than c language, so the c + + compiler can do a lot of things.

4.1.7.1 de duplication

When c + + is compiled, it will produce a lot of duplicate code, such as templates, external inline functions and virtual function tables.

You can take the simplest template as an example. A template is only a template in a program. It can only be converted into real code when compiling. If different. cpp files use the same template, the compilation will cause a waste of space. Therefore, at present, it is mainly to set the same type of template instance as a separate segment, At that time, similar segments can be merged directly when linking.

For example: add() has int type and float type.

The compiled segments are. temp.add and. temp.add.

The same segments can be merged directly. It's really powerful.

It's not easy to write this article. I haven't written so many words for a long time. As long as the link process is really complicated, it's all right. It's good to explain it clearly.

Reference article:

Self cultivation of programmers - linking, loading and Library

Deep understanding of computer systems

Keywords: C

Added by Cheeky Chino on Sun, 05 Dec 2021 00:00:29 +0200