<ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>




      既然程序最終都被變成了一條條機(jī)器碼去執(zhí)行,那為什么同一個(gè)程序,在同一臺(tái)計(jì)算機(jī)上,在Linux下可以運(yùn)行,而在Windows下卻不行呢?

      反過來,Windows上的程序在Linux上也是一樣不能執(zhí)行的

      可是我們的CPU并沒有換掉,它應(yīng)該可以識(shí)別同樣的指令呀!!!

      如果你和我有同樣的疑問,那這一節(jié),我們就一起來解開。

      1 編譯、鏈接和裝載:拆解程序執(zhí)行

      寫好的C語言代碼,可以通過編譯器編譯成匯編代碼,然后匯編代碼再通過匯編器變成CPU可以理解的機(jī)器碼,于是CPU就可以執(zhí)行這些機(jī)器碼了

      你現(xiàn)在對這個(gè)過程應(yīng)該不陌生了,但是這個(gè)描述把過程大大簡化了

      下面,我們一起具體來看,C語言程序是如何變成一個(gè)可執(zhí)行程序的。

      過去幾節(jié),我們通過gcc生成的文件和objdump獲取到的匯編指令都有些小小的問題

      我們先把前面的add函數(shù)示例,拆分成兩個(gè)文件

      * add_lib.c

      * link_example.c
      通過gcc來編譯這兩個(gè)文件,然后通過objdump命令看看它們的匯編代碼。



      * objdump -d -M intel -S link_example.o

      既然代碼已經(jīng)被我們“編譯”成了指令

      不妨嘗試運(yùn)行一下 ./link_example.o

      * 不幸的是,文件沒有執(zhí)行權(quán)限,我們遇到一個(gè)Permission denied錯(cuò)誤
      即使通過chmod命令賦予link_example.o文件可執(zhí)行的權(quán)限,運(yùn)行 ./link_example.o 仍然只會(huì)得到一條cannot execute
      binary file: Exec format error的錯(cuò)誤。

      仔細(xì)看一下objdump出來的兩個(gè)文件的代碼,會(huì)發(fā)現(xiàn)兩個(gè)程序的地址都是從0開始

      如果地址一樣,程序如果需要通過call指令調(diào)用函數(shù)的話,怎么知道應(yīng)該跳到哪一個(gè)文件呢?

      無論是這里的運(yùn)行報(bào)錯(cuò),還是objdump出來的匯編代碼里面的重復(fù)地址

      都是因?yàn)?add_lib.o 以及 link_example.o 并不是一個(gè)可執(zhí)行文件(Executable Program),而是目標(biāo)文件(Object
      File)

      只有通過鏈接器(Linker) 把多個(gè)目標(biāo)文件以及調(diào)用的各種函數(shù)庫鏈接起來,我們才能得到一個(gè)可執(zhí)行文件

      * gcc的-o參數(shù),可以生成對應(yīng)的可執(zhí)行文件,對應(yīng)執(zhí)行之后,就可以得到這個(gè)簡單的加法調(diào)用函數(shù)的結(jié)果。

      C語言代碼-匯編代碼-機(jī)器碼 過程,在我們的計(jì)算機(jī)上進(jìn)行的時(shí)候是由兩部分組成:

      * 第一個(gè)部分由編譯(Compile)、匯編(Assemble)以及鏈接(Link)三個(gè)階段組成
      三階段后,就生成了一個(gè)可執(zhí)行文件link_example: file format elf64-x86-64 Disassembly of section
      .init: ... Disassembly of section .plt: ... Disassembly of section .plt.got:
      ... Disassembly of section .text: ... 6b0: 55 push rbp 6b1: 48 89 e5 mov
      rbp,rsp 6b4: 89 7d fc mov DWORD PTR [rbp-0x4],edi 6b7: 89 75 f8 mov DWORD PTR
      [rbp-0x8],esi 6ba: 8b 55 fc mov edx,DWORD PTR [rbp-0x4] 6bd: 8b 45 f8 mov
      eax,DWORD PTR [rbp-0x8] 6c0: 01 d0 add eax,edx 6c2: 5d pop rbp 6c3: c3 ret
      00000000000006c4 <main>: 6c4: 55 push rbp 6c5: 48 89 e5 mov rbp,rsp 6c8: 48 83
      ec 10 sub rsp,0x10 6cc: c7 45 fc 0a 00 00 00 mov DWORD PTR [rbp-0x4],0xa 6d3:
      c7 45 f8 05 00 00 00 mov DWORD PTR [rbp-0x8],0x5 6da: 8b 55 f8 mov edx,DWORD
      PTR [rbp-0x8] 6dd: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 6e0: 89 d6 mov esi,edx
      6e2: 89 c7 mov edi,eax 6e4: b8 00 00 00 00 mov eax,0x0 6e9: e8 c2 ff ff ff call
      6b0 <add> 6ee: 89 45 f4 mov DWORD PTR [rbp-0xc],eax 6f1: 8b 45 f4 mov eax,DWORD
      PTR [rbp-0xc] 6f4: 89 c6 mov esi,eax 6f6: 48 8d 3d 97 00 00 00 lea
      rdi,[rip+0x97] # 794 <\_IO\_stdin\_used+0x4> 6fd: b8 00 00 00 00 mov eax,0x0
      702: e8 59 fe ff ff call 560 <printf@plt> 707: b8 00 00 00 00 mov eax,0x0 70c:
      c9 leave 70d: c3 ret 70e: 66 90 xchg ax,ax ... Disassembly of section .fini:
      ...你會(huì)發(fā)現(xiàn),可執(zhí)行代碼dump出來內(nèi)容,和之前的目標(biāo)代碼長得差不多,但是長了很多

      因?yàn)樵贚inux下,可執(zhí)行文件和目標(biāo)文件所使用的都是一種叫ELF(Execuatable and Linkable File Format)
      的文件格式,中文名字叫可執(zhí)行與可鏈接文件格式

      這里面不僅存放了編譯成的匯編指令,還保留了很多別的數(shù)據(jù)。

      * 第二部分,我們通過裝載器(Loader)把可執(zhí)行文件裝載(Load)到內(nèi)存中
      CPU從內(nèi)存中讀取指令和數(shù)據(jù),來開始真正執(zhí)行程序
      2 ELF格式和鏈接:理解鏈接過程程序最終是通過裝載器變成指令和數(shù)據(jù)的,所以其實(shí)生成的可執(zhí)行代碼也并不僅僅是一條條的指令
      我們還是通過objdump指令,把可執(zhí)行文件的內(nèi)容拿出來看看。

      比如我們過去所有objdump出來的代碼里,你都可以看到對應(yīng)的函數(shù)名稱,像add、main等等,乃至你自己定義的全局可以訪問的變量名稱,都存放在這個(gè)ELF格式文件里

      這些名字和它們對應(yīng)的地址,在ELF文件里面,存儲(chǔ)在一個(gè)叫作符號(hào)表(Symbols Table)的位置里。符號(hào)表相當(dāng)于一個(gè)地址簿,把名字和地址關(guān)聯(lián)了起來。

      我們先只關(guān)注和我們的add以及main函數(shù)相關(guān)的部分

      你會(huì)發(fā)現(xiàn),這里面,main函數(shù)里調(diào)用add的跳轉(zhuǎn)地址,不再是下一條指令的地址了,而是add函數(shù)的入口地址了,這就是EFL格式和鏈接器的功勞



      ELF文件格式把各種信息,分成一個(gè)一個(gè)的Section保存起來。ELF有一個(gè)基本的文件頭(File
      Header),用來表示這個(gè)文件的基本屬性,比如是否是可執(zhí)行文件,對應(yīng)的CPU、操作系統(tǒng)等等。除了這些基本屬性之外,大部分程序還有這么一些Section:

      * 首先是.text Section,也叫作代碼段或者指令段(Code Section),用來保存程序的代碼和指令;
      * 接著是.data Section,也叫作數(shù)據(jù)段(Data Section),用來保存程序里面設(shè)置好的初始化數(shù)據(jù)信息;
      * 然后就是.rel.text Secion,叫作重定位表(Relocation
      Table)。重定位表里,保留的是當(dāng)前的文件里面,哪些跳轉(zhuǎn)地址其實(shí)是我們不知道的。比如上面的 link_example.o 里面,我們在main函數(shù)里面調(diào)用了
      add 和 printf 這兩個(gè)函數(shù),但是在鏈接發(fā)生之前,我們并不知道該跳轉(zhuǎn)到哪里,這些信息就會(huì)存儲(chǔ)在重定位表里;
      * 最后是.symtab Section,叫作符號(hào)表(Symbol Table)。符號(hào)表保留了我們所說的當(dāng)前文件里面定義的函數(shù)名稱和對應(yīng)地址的地址簿。

      鏈接器會(huì)掃描所有輸入的目標(biāo)文件,然后把所有符號(hào)表里的信息收集起來,構(gòu)成一個(gè)全局的符號(hào)表。然后再根據(jù)重定位表,把所有不確定要跳轉(zhuǎn)地址的代碼,根據(jù)符號(hào)表里面存儲(chǔ)的地址,進(jìn)行一次修正。最后,把所有的目標(biāo)文件的對應(yīng)段進(jìn)行一次合并,變成了最終的可執(zhí)行代碼。這也是為什么,可執(zhí)行文件里面的函數(shù)調(diào)用的地址都是正確的



      在鏈接器把程序變成可執(zhí)行文件之后,要裝載器去執(zhí)行程序就容易多了。裝載器不再需要考慮地址跳轉(zhuǎn)的問題,只需要解析 ELF
      文件,把對應(yīng)的指令和數(shù)據(jù),加載到內(nèi)存里面供CPU執(zhí)行就可以了。

      3 總結(jié)


      講到這里,相信你已經(jīng)猜到,為什么同樣一個(gè)程序,在Linux下可以執(zhí)行而在Windows下不能執(zhí)行了。其中一個(gè)非常重要的原因就是,兩個(gè)操作系統(tǒng)下可執(zhí)行文件的格式不一樣。

      我們今天講的是Linux下的ELF文件格式,而Windows的可執(zhí)行文件格式是一種叫作PE(Portable Executable
      Format)的文件格式。Linux下的裝載器只能解析ELF格式而不能解析PE格式。

      如果我們有一個(gè)可以能夠解析PE格式的裝載器,我們就有可能在Linux下運(yùn)行Windows程序了。這樣的程序真的存在嗎?

      沒錯(cuò),Linux下著名的開源項(xiàng)目Wine,就是通過兼容PE格式的裝載器,使得我們能直接在Linux下運(yùn)行Windows程序的。

      而現(xiàn)在微軟的Windows里面也提供了WSL,也就是Windows Subsystem for Linux,可以解析和加載ELF格式的文件。


      我們?nèi)懣梢杂玫某绦?,也不僅僅是把所有代碼放在一個(gè)文件里來編譯執(zhí)行,而是可以拆分成不同的函數(shù)庫,最后通過一個(gè)靜態(tài)鏈接的機(jī)制,使得不同的文件之間既有分工,又能通過靜態(tài)鏈接來“合作”,變成一個(gè)可執(zhí)行的程序。

      對于ELF格式的文件,為了能夠?qū)崿F(xiàn)這樣一個(gè)靜態(tài)鏈接的機(jī)制,里面不只是簡單羅列了程序所需要執(zhí)行的指令,還會(huì)包括鏈接所需要的重定位表和符號(hào)表。

      4 推薦閱讀

      更深入了解程序的鏈接過程和ELF格式,推薦閱讀《程序員的自我修養(yǎng)——鏈接、裝載和庫》的1~4章。這是一本難得的講解程序的鏈接、裝載和運(yùn)行的好書。

      友情鏈接
      ioDraw流程圖
      API參考文檔
      OK工具箱
      云服務(wù)器優(yōu)惠
      阿里云優(yōu)惠券
      騰訊云優(yōu)惠券
      京東云優(yōu)惠券
      站點(diǎn)信息
      問題反饋
      郵箱:[email protected]
      QQ群:637538335
      關(guān)注微信

        <ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>
          国模自拍一区 | 日屄视频在线 | 电影无码av | 青娱乐免费视频观看 | 一级片毛 | 亚洲三级片网站 | 抽插.com | 簧片免费视频 | 午夜福利一区二区三区免费 | 免费看无码成人A片 |