C 语言编程 — 程序的装载与运行
目录
文章目录
《C 语言编程 — GCC 工具链》
《C 语言编程 — 程序的编译流程》
《C 语言编程 — 静态库、动态库和共享库》
《C 语言编程 — 程序的装载与运行》
《计算机组成原理 — 指令系统》
《C 语言编程 — 结构化程序流的汇编代码与 CPU 指令集》
C 程序在操作系统中的装载与运行
一个程序在操作系统上运行需要经历以下阶段:
第一阶段:得到可执行文件
- 编译(Compile)
- 汇编(Assemble)
- 链接(Link)
第二阶段:装载运行
- 装载器(Loader)将可执行文件载入到内存
- CPU 从内存中可执行文件的程序入口开始读取指令和数据,开始真正执行程序。
编译和汇编的过程在上文中已经提到了,下面再继续介绍链接的过程。
- 子程序
// add_lib.c
int add(int a, int b)
{ return a+b;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 主函数
// link_example.c
#include <stdio.h>
int main()
{ int a = 10; int b = 5; int c = add(a, b); printf("c = %d\n", c);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 编译 C 程序得到 Object 文件
$ gcc -g -c add_lib.c link_example.c
- 1
- 链接上述两个 Object 文件得到一个可执行文件
$ gcc -o link-example add_lib.o link_example.o
$ ./link-example
c = 15
- 1
- 2
- 3
- 4
区别于 Object 文件,真正的可执行文件的内容如下:
$ objdump -d -M intel -S link-example
link-example: file format elf64-x86-64
Disassembly of section .init:
00000000004003c8 <_init>:
4003c8: 48 83 ec 08 sub rsp,0x8
4003cc: 48 8b 05 25 0c 20 00 mov rax,QWORD PTR [rip+0x200c25] # 600ff8 <__gmon_start__>
4003d3: 48 85 c0 test rax,rax
4003d6: 74 05 je 4003dd <_init+0x15>
4003d8: e8 43 00 00 00 call 400420 <.plt.got>
4003dd: 48 83 c4 08 add rsp,0x8
4003e1: c3 ret
Disassembly of section .plt:
00000000004003f0 <.plt>:
4003f0: ff 35 12 0c 20 00 push QWORD PTR [rip+0x200c12] # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
4003f6: ff 25 14 0c 20 00 jmp QWORD PTR [rip+0x200c14] # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
4003fc: 0f 1f 40 00 nop DWORD PTR [rax+0x0]
0000000000400400 <printf@plt>:
400400: ff 25 12 0c 20 00 jmp QWORD PTR [rip+0x200c12] # 601018 <printf@GLIBC_2.2.5>
400406: 68 00 00 00 00 push 0x0
40040b: e9 e0 ff ff ff jmp 4003f0 <.plt>
0000000000400410 <__libc_start_main@plt>:
400410: ff 25 0a 0c 20 00 jmp QWORD PTR [rip+0x200c0a] # 601020 <__libc_start_main@GLIBC_2.2.5>
400416: 68 01 00 00 00 push 0x1
40041b: e9 d0 ff ff ff jmp 4003f0 <.plt>
Disassembly of section .plt.got:
0000000000400420 <.plt.got>:
400420: ff 25 d2 0b 20 00 jmp QWORD PTR [rip+0x200bd2] # 600ff8 <__gmon_start__>
400426: 66 90 xchg ax,ax
Disassembly of section .text:
0000000000400430 <_start>:
400430: 31 ed xor ebp,ebp
400432: 49 89 d1 mov r9,rdx
400435: 5e pop rsi
400436: 48 89 e2 mov rdx,rsp
400439: 48 83 e4 f0 and rsp,0xfffffffffffffff0
40043d: 50 push rax
40043e: 54 push rsp
40043f: 49 c7 c0 f0 05 40 00 mov r8,0x4005f0
400446: 48 c7 c1 80 05 40 00 mov rcx,0x400580
40044d: 48 c7 c7 31 05 40 00 mov rdi,0x400531
400454: e8 b7 ff ff ff call 400410 <__libc_start_main@plt>
400459: f4 hlt
40045a: 66 0f 1f 44 00 00 nop WORD PTR [rax+rax*1+0x0]
0000000000400460 <deregister_tm_clones>:
400460: b8 37 10 60 00 mov eax,0x601037
400465: 55 push rbp
400466: 48 2d 30 10 60 00 sub rax,0x601030
40046c: 48 83 f8 0e cmp rax,0xe
400470: 48 89 e5 mov rbp,rsp
400473: 77 02 ja 400477 <deregister_tm_clones+0x17>
400475: 5d pop rbp
400476: c3 ret
400477: b8 00 00 00 00 mov eax,0x0
40047c: 48 85 c0 test rax,rax
40047f: 74 f4 je 400475 <deregister_tm_clones+0x15>
400481: 5d pop rbp
400482: bf 30 10 60 00 mov edi,0x601030
400487: ff e0 jmp rax
400489: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0]
0000000000400490 <register_tm_clones>:
400490: b8 30 10 60 00 mov eax,0x601030
400495: 55 push rbp
400496: 48 2d 30 10 60 00 sub rax,0x601030
40049c: 48 c1 f8 03 sar rax,0x3
4004a0: 48 89 e5 mov rbp,rsp
4004a3: 48 89 c2 mov rdx,rax
4004a6: 48 c1 ea 3f shr rdx,0x3f
4004aa: 48 01 d0 add rax,rdx
4004ad: 48 d1 f8 sar rax,1
4004b0: 75 02 jne 4004b4 <register_tm_clones+0x24>
4004b2: 5d pop rbp
4004b3: c3 ret
4004b4: ba 00 00 00 00 mov edx,0x0
4004b9: 48 85 d2 test rdx,rdx
4004bc: 74 f4 je 4004b2 <register_tm_clones+0x22>
4004be: 5d pop rbp
4004bf: 48 89 c6 mov rsi,rax
4004c2: bf 30 10 60 00 mov edi,0x601030
4004c7: ff e2 jmp rdx
4004c9: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0]
00000000004004d0 <__do_global_dtors_aux>:
4004d0: 80 3d 55 0b 20 00 00 cmp BYTE PTR [rip+0x200b55],0x0 # 60102c <_edata>
4004d7: 75 11 jne 4004ea <__do_global_dtors_aux+0x1a>
4004d9: 55 push rbp
4004da: 48 89 e5 mov rbp,rsp
4004dd: e8 7e ff ff ff call 400460 <deregister_tm_clones>
4004e2: 5d pop rbp
4004e3: c6 05 42 0b 20 00 01 mov BYTE PTR [rip+0x200b42],0x1 # 60102c <_edata>
4004ea: f3 c3 repz ret
4004ec: 0f 1f 40 00 nop DWORD PTR [rax+0x0]
00000000004004f0 <frame_dummy>:
4004f0: 48 83 3d 28 09 20 00 cmp QWORD PTR [rip+0x200928],0x0 # 600e20 <__JCR_END__>
4004f7: 00
4004f8: 74 1e je 400518 <frame_dummy+0x28>
4004fa: b8 00 00 00 00 mov eax,0x0
4004ff: 48 85 c0 test rax,rax
400502: 74 14 je 400518 <frame_dummy+0x28>
400504: 55 push rbp
400505: bf 20 0e 60 00 mov edi,0x600e20
40050a: 48 89 e5 mov rbp,rsp
40050d: ff d0 call rax
40050f: 5d pop rbp
400510: e9 7b ff ff ff jmp 400490 <register_tm_clones>
400515: 0f 1f 00 nop DWORD PTR [rax]
400518: e9 73 ff ff ff jmp 400490 <register_tm_clones>
000000000040051d <add>:
// add_lib.c
int add(int a, int b)
{
40051d: 55 push rbp
40051e: 48 89 e5 mov rbp,rsp
400521: 89 7d fc mov DWORD PTR [rbp-0x4],edi
400524: 89 75 f8 mov DWORD PTR [rbp-0x8],esi return a+b;
400527: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
40052a: 8b 55 fc mov edx,DWORD PTR [rbp-0x4]
40052d: 01 d0 add eax,edx
}
40052f: 5d pop rbp
400530: c3 ret
0000000000400531 <main>:
// link_example.c
#include <stdio.h>
int main()
{
400531: 55 push rbp
400532: 48 89 e5 mov rbp,rsp
400535: 48 83 ec 10 sub rsp,0x10 int a = 10;
400539: c7 45 fc 0a 00 00 00 mov DWORD PTR [rbp-0x4],0xa int b = 5;
400540: c7 45 f8 05 00 00 00 mov DWORD PTR [rbp-0x8],0x5 int c = add(a, b);
400547: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8]
40054a: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
40054d: 89 d6 mov esi,edx
40054f: 89 c7 mov edi,eax
400551: b8 00 00 00 00 mov eax,0x0
400556: e8 c2 ff ff ff call 40051d <add>
40055b: 89 45 f4 mov DWORD PTR [rbp-0xc],eax printf("c = %d\n", c);
40055e: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc]
400561: 89 c6 mov esi,eax
400563: bf 10 06 40 00 mov edi,0x400610
400568: b8 00 00 00 00 mov eax,0x0
40056d: e8 8e fe ff ff call 400400 <printf@plt>
}
400572: c9 leave
400573: c3 ret
400574: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
40057b: 00 00 00
40057e: 66 90 xchg ax,ax
0000000000400580 <__libc_csu_init>:
400580: 41 57 push r15
400582: 41 89 ff mov r15d,edi
400585: 41 56 push r14
400587: 49 89 f6 mov r14,rsi
40058a: 41 55 push r13
40058c: 49 89 d5 mov r13,rdx
40058f: 41 54 push r12
400591: 4c 8d 25 78 08 20 00 lea r12,[rip+0x200878] # 600e10 <__frame_dummy_init_array_entry>
400598: 55 push rbp
400599: 48 8d 2d 78 08 20 00 lea rbp,[rip+0x200878] # 600e18 <__init_array_end>
4005a0: 53 push rbx
4005a1: 4c 29 e5 sub rbp,r12
4005a4: 31 db xor ebx,ebx
4005a6: 48 c1 fd 03 sar rbp,0x3
4005aa: 48 83 ec 08 sub rsp,0x8
4005ae: e8 15 fe ff ff call 4003c8 <_init>
4005b3: 48 85 ed test rbp,rbp
4005b6: 74 1e je 4005d6 <__libc_csu_init+0x56>
4005b8: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0]
4005bf: 00
4005c0: 4c 89 ea mov rdx,r13
4005c3: 4c 89 f6 mov rsi,r14
4005c6: 44 89 ff mov edi,r15d
4005c9: 41 ff 14 dc call QWORD PTR [r12+rbx*8]
4005cd: 48 83 c3 01 add rbx,0x1
4005d1: 48 39 eb cmp rbx,rbp
4005d4: 75 ea jne 4005c0 <__libc_csu_init+0x40>
4005d6: 48 83 c4 08 add rsp,0x8
4005da: 5b pop rbx
4005db: 5d pop rbp
4005dc: 41 5c pop r12
4005de: 41 5d pop r13
4005e0: 41 5e pop r14
4005e2: 41 5f pop r15
4005e4: c3 ret
4005e5: 90 nop
4005e6: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
4005ed: 00 00 00
00000000004005f0 <__libc_csu_fini>:
4005f0: f3 c3 repz ret
Disassembly of section .fini:
00000000004005f4 <_fini>:
4005f4: 48 83 ec 08 sub rsp,0x8
4005f8: 48 83 c4 08 add rsp,0x8
4005fc: c3 ret
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
可见,链接(Link) 不仅仅是单纯的将多个 Object 文件拼凑起来而已,而是将程序真正的转换为一个可以在操作系统上执行的文件格式,且这个文件中还包含了整个程序所有 Object 文件的内容。在 Linux 上,这个文件格式就是 ELF(Execuatable and Linkable File Format,可执行与可链接文件格式)。
ELF 文件
ELF 文件格式:是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。ELF 文件由 4 部分组成,分别是 ELF header、程序头表(Program Header Table)、节(Section)和节头表(Section Header Table)。
位于 ELF Header 和 Section Header Table 之间的都是段(Section)。一个典型的 ELF 文件包含下面几个段:
- .text:已编译程序的指令代码段。
- .rodata:即只读数据(譬如常数 const)。
- .data:已初始化的C程序全局变量和静态局部变量。
- .bss:未初始化的C程序全局变量和静态局部变量。
- .debug:调试符号表,调试器用此段的信息帮助调试。
可以使用 readelf -S 查看其各个 section 的信息如下:
$ readelf -S hello
There are 31 section headers, starting at offset 0x19d8:
Section Headers:
[Nr] Name Type Address Offset Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0
……
[11] .init PROGBITS 00000000004003c8 000003c8 000000000000001a 0000000000000000 AX 0 0 4
……
[14] .text PROGBITS 0000000000400430 00000430 0000000000000182 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 00000000004005b4 000005b4
……
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
在链接器把程序转换为 ELF 格式的可执行文件之后,装载器再去处理就会容易得多。因为装载器不再需要考虑地址跳转的问题,只需要解析 ELF 文件,把对应的指令和数据加载到内存里面供 CPU 执行就可以了。
同样,Windows 也有自己的可执行文件格式 PE(Portable Executable Format)。因为 Linux 和 Windows 的可执行文件格式不同,所以也就不能够 “一次编译,跨平台执行” 了。那么换句话说:是不是只要在 Linux 上运行可以解析 PE 文件的装载器就可以解决这个问题呢?答案是肯定的,Linux 著名的开源软件 Wine 正是此类装载器,国内很多 Linux Desktop 发行版都是基于 Wine 实现了 Windows 常用软件的移植。
反汇编 ELF 文件
由于 ELF 文件无法被当做普通文本文件打开,如果希望直接查看一个 ELF 文件包含的指令和数据,需要使用反汇编的方法。
使用 objdump -D 对其进行反汇编如下:
$ objdump -D hello
……
0000000000400526 <main>: // main标签的PC地址
//PC地址:指令编码 指令的汇编格式
400526: 55 push %rbp
400527: 48 89 e5 mov %rsp,%rbp
40052a: bf c4 05 40 00 mov $0x4005c4,%edi
40052f: e8 cc fe ff ff callq 400400 <puts@plt>
400534: b8 00 00 00 00 mov $0x0,%eax
400539: 5d pop %rbp
40053a: c3 retq 40053b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
……
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
使用 objdump -S 将其反汇编并且将其 C 语言源代码混合显示出来:
# 要加上 -g 选项
$ gcc -o hello -g hello.c
$ objdump -S hello
……
0000000000400526 <main>:
#include <stdio.h>
int
main(void)
{
400526: 55 push %rbp
400527: 48 89 e5 mov %rsp,%rbp
printf("Hello World!" "\n");
40052a: bf c4 05 40 00 mov $0x4005c4,%edi
40052f: e8 cc fe ff ff callq 400400 <puts@plt>
return 0;
400534: b8 00 00 00 00 mov $0x0,%eax
}
400539: 5d pop %rbp
40053a: c3 retq 40053b:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
文章来源: is-cloud.blog.csdn.net,作者:范桂飓,版权归原作者所有,如需转载,请联系作者。
原文链接:is-cloud.blog.csdn.net/article/details/106912598
- 点赞
- 收藏
- 关注作者
评论(0)