深刻浅出计算机组成原理学习笔记:第八讲

1、为何会Permission denied

一、测试用例

不知道你注意到没,在过去的几节,咱们经过gcc生成的文件和objdump获取到的汇编指令都有些小小的问题,咱们先把前面的函数示例,拆分红两个文件add_lib.c 和 link_example.c。linux

一、建立示例文件

一、add_lib.csass

[root@luoahong c]# cat add_lib.c
int add(int a, int b)
{
    return a+b;
}

 二、link_example.cbash

[root@luoahong c]# cat link_example.c
#include <stdio.h>
int main()
{
    int a = 10;
    int b = 5;
    int c = add(a, b);
    printf("c = %d\n", c);
}

二、咱们经过 gcc 来编译这两个文件,而后经过objdump 命令看看它们的汇编代码。

[root@luoahong c]# gcc -g -c add_lib.c link_example.c

三、经过 objdump 命令看看它们的汇编代码。

objdump -d -M intel -S add_lib.o函数

[root@luoahong c]# objdump -d -M intel -S add_lib.o

add_lib.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <add>:
int add(int a, int b)
{
   0:	55                   	push   rbp
   1:	48 89 e5             	mov    rbp,rsp
   4:	89 7d fc             	mov    DWORD PTR [rbp-0x4],edi
   7:	89 75 f8             	mov    DWORD PTR [rbp-0x8],esi
    return a+b;
   a:	8b 45 f8             	mov    eax,DWORD PTR [rbp-0x8]
   d:	8b 55 fc             	mov    edx,DWORD PTR [rbp-0x4]
  10:	01 d0                	add    eax,edx
}
  12:	5d                   	pop    rbp
  13:	c3                   	ret

objdump -d -M intel -S link_example.o测试

[root@luoahong c]# objdump -d -M intel -S link_example.o

link_example.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
#include <stdio.h>
int main()
{
   0:	55                   	push   rbp
   1:	48 89 e5             	mov    rbp,rsp
   4:	48 83 ec 10          	sub    rsp,0x10
    int a = 10;
   8:	c7 45 fc 0a 00 00 00 	mov    DWORD PTR [rbp-0x4],0xa
    int b = 5;
   f:	c7 45 f8 05 00 00 00 	mov    DWORD PTR [rbp-0x8],0x5
    int c = add(a, b);
  16:	8b 55 f8             	mov    edx,DWORD PTR [rbp-0x8]
  19:	8b 45 fc             	mov    eax,DWORD PTR [rbp-0x4]
  1c:	89 d6                	mov    esi,edx
  1e:	89 c7                	mov    edi,eax
  20:	b8 00 00 00 00       	mov    eax,0x0
  25:	e8 00 00 00 00       	call   2a <main+0x2a>
  2a:	89 45 f4             	mov    DWORD PTR [rbp-0xc],eax
    printf("c = %d\n", c);
  2d:	8b 45 f4             	mov    eax,DWORD PTR [rbp-0xc]
  30:	89 c6                	mov    esi,eax
  32:	bf 00 00 00 00       	mov    edi,0x0
  37:	b8 00 00 00 00       	mov    eax,0x0
  3c:	e8 00 00 00 00       	call   41 <main+0x41>
}
  41:	c9                   	leave
  42:	c3                   	ret

二、文件没有执行权限

[root@luoahong c]# ./link_example.o
-bash: ./link_example.o: Permission denied

三、经过chmod命令赋予文件可执行的权限,故障依旧

[root@luoahong c]# chmod +x link_example.o
[root@luoahong c]# ll link_example.o
-rwxr-xr-x 1 root root 3408 May 16 17:06 link_example.o
[root@luoahong c]# ./link_example.o
-bash: ./link_example.o: cannot execute binary file

咱们再仔细看一下 objdump 出来的两个文件的代码,会发现两个程序的地址都是从 0 开始的。若是地址是同样的,程序若是须要经过 call 指令调用函数的话,它怎么知道应该跳转到哪个文件里呢?spa

这么说吧,不管是这里的运行报错,仍是 objdump 出来的汇编代码里面的重复地址,都是由于add_lib.o 以及 link_example.o 并不一个可执行文件(Executable Program),而是目标文操作系统

件(Object File)。只有经过连接器(Linker)把多个目标文件以及调用的各类函数库连接起来,咱们才能获得一个可执行文件。3d

四、经过gcc的-o参数完美解决Permission denied

 咱们经过 gcc 的 -o 参数,能够生成对应的可执行文件,对应执行以后,就能够获得这个简单的加法调用函数的结果。orm

[root@luoahong c]# gcc -o link-example add_lib.o link_example.o
[root@luoahong c]# ll
total 44
-rw-r--r-- 1 root root   43 May 16 17:04 add_lib.c
-rw-r--r-- 1 root root 2576 May 16 17:38 add_lib.o
-rw-r--r-- 1 root root  140 May 16 14:21 function_example.c
-rw-r--r-- 1 root root 3288 May 16 14:22 function_example.o
-rwxr-xr-x 1 root root 9912 May 16 17:39 link-example
-rw-r--r-- 1 root root  115 May 16 17:05 link_example.c
-rw-r--r-- 1 root root 3408 May 16 17:38 link_example.o
-rw-r--r-- 1 root root   98 May 14 17:42 test.c
-rw-r--r-- 1 root root 2632 May 14 17:42 test.o
[root@luoahong c]# ./link-example
c = 15

 

2、C 语言程序是如何变成一个可执行程序的。

实际上,“C 语言代码 - 汇编代码 - 机器码” 这个过,在咱们的计算机上进行的时候是由两部分组成的。blog

第一个部分由编译(Compile)、汇编(Assemble)以及连接(Link)三个阶段组成。在这三个阶段完成以后,咱们就生成了一个可执行文件。

第二部分,咱们经过装载器(Loader)把可执行文件装载(Load)到内存中。CPU 从内存中读取指令和数据,来开始真正执行程序。

3、ELF 格式和连接:理解连接过程

一、用objdump查看执行文件内容

程序最终是经过装载器变成指令和数据的,因此其实咱们生成的可执行代码也并不只仅是一条条的指令。咱们仍是经过objdump指令,把可执行文件内容拿出来看看

[root@luoahong c]# 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>:
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>:
#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
[root@luoahong c]#

二、执行文件和目标代码的区别

一、和以前的目标代码长的差很少,可是长了不少,由于在linux下,可执行文件和目标文件所使用的都是一种叫ELF的文件格式,中文名字叫可执行与可连接文件格式,

二、这里面不只存放了编译成汇编指令,还保留了不少别的数据

  好比咱们过去全部 objdump 出来的代码里,你均可以看到对应的函数名称,像 add、main 等等,乃至你本身定义的全局能够访问的变量名称,都存放在这个 ELF 格式文件里。

  这些名字和它们对应的地址,在 ELF 文件里面,存储在一个叫做符号表的位置里。符号表至关于一个地址簿,把名字和地址关联了起来。

三、main 函数里调用add 的跳转地址,再也不是下一条指令的地址了,而是 add 函数的入口地址了

 

咱们先只关注和咱们的 add 以及 main 函数相关的部分。你会发现,这里面,main 函数里调用add 的跳转地址,再也不是下一条指令的地址了,

而是 add 函数的入口地址了,这就是 EFL 格式和连接器的功劳。

4、EFL文件格式

 

ELF 文件格式把各类信息,分红一个一个的 Section 保存起来。ELF 有一个基本的文件头(File Header),用来表示这个文件的基本属性,好比是不是可执行文件,对应的CPU、操做系统等等,除了这些基本属性以外,大部分程序还有这么一些Section:

连接器会扫描全部输入的目标文件,而后把全部的符号表里的信息收集起来,构成一个全局的符号表,而后再根据重定位表把全部不肯定要跳转地址的代码,

根据符号表里面的存储地址,进行一次修正,最后,把全部的目标文件的对应进行一次合并,变成了最终的可执行代码,这也是为何,可执行文件里面的函数调用的地址都是正确的

在连接器把程序变成可执行文件以后,要装载器去执行程序就容易多了。装载器再也不须要考虑地址跳转的问题,只须要解析 ELF 文件,把对应的指令和数据,加载到内存里面供 CPU 执行就能够了。

5、小结与延伸

讲到这里,相信你已经猜到,为何一样一个程序,在Linux下能够执行而Windows下不能执行了?

一、其中一个很是重要的缘由就是,两个操做系统下可执行文件的格式不同

二、咱们今天讲的事Linux下的ELF文件格式,而Windows的可执行文件格式是一种叫作PE的文件格式Linux下的装饰器只能解析ELF格式而不能解析PE格式而不能解析PE格式

三、若是咱们有一个能够可以解析PE格式的装载器,咱们就有可能在下Linux运行Windows里面也提供了WSL也就是Windows Subsystem for Linux,能够.解析和加载ELF格式的文件

咱们去写可用的程序,也不只仅是把全部代码放在一个文件里来编译执行,而是能够拆分红不一样的函数库,最后经过一个静态连接的机制,

使得不一样的文件之间既有分工有能经过静态连接来“合做“,变成了一个可执行的程序

四、对于ELF格式的文件,为了可以实现这样一个静态连接的机制,里面不仅是简单罗列了程序所须要执行的指令,还会包括连接所须要的重定位表和符号表

相关文章
相关标签/搜索