深刻淺出計算機組成原理學習筆記:第八講

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格式的文件,爲了可以實現這樣一個靜態連接的機制,裏面不僅是簡單羅列了程序所須要執行的指令,還會包括連接所須要的重定位表和符號表

相關文章
相關標籤/搜索