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.
-
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.
-
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