重學電腦組成原理(七)- 程式無法同時在Linux和Windows下運行?

来源:https://www.cnblogs.com/JavaEdge/archive/2019/08/16/11361399.html
-Advertisement-
Play Games

既然程式最終都被變成了一條條機器碼去執行,那為什麼同一個程式,在同一臺電腦上,在Linux下可以運行,而在Windows下卻不行呢? 反過來,Windows上的程式在Linux上也是一樣不能執行的 可是我們的CPU並沒有換掉,它應該可以識別同樣的指令呀!!! 如果你和我有同樣的疑問,那這一節,我們 ...


既然程式最終都被變成了一條條機器碼去執行,那為什麼同一個程式,在同一臺電腦上,在Linux下可以運行,而在Windows下卻不行呢?

反過來,Windows上的程式在Linux上也是一樣不能執行的

可是我們的CPU並沒有換掉,它應該可以識別同樣的指令呀!!!

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

1 編譯、鏈接和裝載:拆解程式執行

寫好的C語言代碼,可以通過編譯器編譯成彙編代碼,然後彙編代碼再通過彙編器變成CPU可以理解的機器碼,於是CPU就可以執行這些機器碼了

你現在對這個過程應該不陌生了,但是這個描述把過程大大簡化了

下麵,我們一起具體來看,C語言程式是如何變成一個可執行程式的。

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

我們先把前面的add函數示例,拆分成兩個文件

  • add_lib.c
  • link_example.c

通過gcc來編譯這兩個文件,然後通過objdump命令看看它們的彙編代碼。

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

既然代碼已經被我們“編譯”成了指令

不妨嘗試運行一下 ./link_example.o

  • 不幸的是,文件沒有執行許可權,我們遇到一個Permission denied錯誤

即使通過chmod命令賦予link_example.o文件可執行的許可權,運行 ./link_example.o 仍然只會得到一條cannot execute binary file: Exec format error的錯誤。

仔細看一下objdump出來的兩個文件的代碼,會發現兩個程式的地址都是從0開始

如果地址一樣,程式如果需要通過call指令調用函數的話,怎麼知道應該跳到哪一個文件呢?

無論是這裡的運行報錯,還是objdump出來的彙編代碼裡面的重覆地址

都是因為 add_lib.o 以及 link_example.o 並不是一個可執行文件(Executable Program),而是目標文件(Object File)

只有通過鏈接器(Linker) 把多個目標文件以及調用的各種函數庫鏈接起來,我們才能得到一個可執行文件

  • gcc的-o參數,可以生成對應的可執行文件,對應執行之後,就可以得到這個簡單的加法調用函數的結果。

C語言代碼-彙編代碼-機器碼 過程,在我們的電腦上進行的時候是由兩部分組成:

  • 第一個部分由編譯(Compile)、彙編(Assemble)以及鏈接(Link)三個階段組成
    三階段後,就生成了一個可執行文件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:

...你會發現,可執行代碼dump出來內容,和之前的目標代碼長得差不多,但是長了很多

因為在Linux下,可執行文件和目標文件所使用的都是一種叫ELF(Execuatable and Linkable File Format)的文件格式,中文名字叫可執行與可鏈接文件格式

這裡面不僅存放了編譯成的彙編指令,還保留了很多別的數據。

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

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

這些名字和它們對應的地址,在ELF文件裡面,存儲在一個叫作符號表(Symbols Table)的位置里。符號表相當於一個地址簿,把名字和地址關聯了起來。

我們先只關註和我們的add以及main函數相關的部分

你會發現,這裡面,main函數里調用add的跳轉地址,不再是下一條指令的地址了,而是add函數的入口地址了,這就是EFL格式和鏈接器的功勞

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

  • 首先是.text Section,也叫作代碼段或者指令段(Code Section),用來保存程式的代碼和指令;
  • 接著是.data Section,也叫作數據段(Data Section),用來保存程式裡面設置好的初始化數據信息;
  • 然後就是.rel.text Secion,叫作重定位表(Relocation Table)。重定位表裡,保留的是當前的文件裡面,哪些跳轉地址其實是我們不知道的。比如上面的 link_example.o 裡面,我們在main函數裡面調用了 add 和 printf 這兩個函數,但是在鏈接發生之前,我們並不知道該跳轉到哪裡,這些信息就會存儲在重定位表裡;
  • 最後是.symtab Section,叫作符號表(Symbol Table)。符號表保留了我們所說的當前文件裡面定義的函數名稱和對應地址的地址簿。

鏈接器會掃描所有輸入的目標文件,然後把所有符號表裡的信息收集起來,構成一個全局的符號表。然後再根據重定位表,把所有不確定要跳轉地址的代碼,根據符號表裡面存儲的地址,進行一次修正。最後,把所有的目標文件的對應段進行一次合併,變成了最終的可執行代碼。這也是為什麼,可執行文件裡面的函數調用的地址都是正確的

在鏈接器把程式變成可執行文件之後,要裝載器去執行程式就容易多了。裝載器不再需要考慮地址跳轉的問題,只需要解析 ELF 文件,把對應的指令和數據,載入到記憶體裡面供CPU執行就可以了。

3 總結

講到這裡,相信你已經猜到,為什麼同樣一個程式,在Linux下可以執行而在Windows下不能執行了。其中一個非常重要的原因就是,兩個操作系統下可執行文件的格式不一樣。

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

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

沒錯,Linux下著名的開源項目Wine,就是通過相容PE格式的裝載器,使得我們能直接在Linux下運行Windows程式的。

而現在微軟的Windows裡面也提供了WSL,也就是Windows Subsystem for Linux,可以解析和載入ELF格式的文件。

我們去寫可以用的程式,也不僅僅是把所有代碼放在一個文件里來編譯執行,而是可以拆分成不同的函數庫,最後通過一個靜態鏈接的機制,使得不同的文件之間既有分工,又能通過靜態鏈接來“合作”,變成一個可執行的程式。

對於ELF格式的文件,為了能夠實現這樣一個靜態鏈接的機制,裡面不只是簡單羅列了程式所需要執行的指令,還會包括鏈接所需要的重定位表和符號表。

4 推薦閱讀

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • into用法: DECLARE a NUMBER; b number; c number;BEGIN SELECT MAX(SAL),MIN(SAL),AVG(SAL) INTO A,B,C FROM EMP; DBMS_OUTPUT.PUT_LINE('最高工資:'|| a); DBMS_OUTP ...
  • 有時候我們會通過mongo shell 運行一些腳本,去執行更新或運維需求。mongo shell 可執行的代碼可以實現比較複雜的功能,代碼也可以比較豐富。當執行報錯時,如果可以快速定位到錯誤點,對解決bug, 可以事半功倍。 我們先測試一下: Case 1 簡單的向集合中插入一筆數據 執行代碼: ...
  • 進去root許可權(su) 1.從https://mirrors.tuna.tsinghua.edu.cn/apache/hive/hive-1.2.2/apache-hive-1.2.2-bin.tar.gz獲取鏡像地址選擇版本下載(此處使用清華開源的Apache-hive1.2.2版本) wget ...
  • 一.首先解釋一下可能會查詢的基礎問題: 1.1db2 “with ur”是什麼意思: 在DB2中,共有四種隔離級:RS,RR,CS,UR.以下對四種隔離級進行一些描述,同時附上個人做試驗的結果。隔離級是影響加鎖策略的重要環節,它直接影響加鎖的範圍及鎖的持續時間。兩個應用程式即使執行的相同的操作,也可 ...
  • 請使用0.9以後的版本: 示例代碼 1、只需要配置kafka的server groupid autocommit 序列化 autooffsetreset(其中 bootstrap.server group.id key.deserializer value.deserializer 必須指定); 2 ...
  • https://www.cnblogs.com/wanglg/p/3740129.html 來自此文 僅做備忘 感謝提供信息讓我處理好此問題 sqlserver mdf向上相容附加資料庫(無法打開資料庫 'xxxxx' 版本 611。請將該資料庫升級為最新版本。) 最近工作中有一個sqlserver ...
  • mysql MySQL語法MySQL採用結構化查詢語言SQL (Structured Query Language)語言來操作資料庫SQL語句必須以 ; 結束SQL語句分類DDL(數據定義語言): create、drop、alter、truncateDQL(數據查詢語言): select、showD ...
  • 安裝參考 https://www.cnblogs.com/onezg/p/8768597.html 安裝參考 https://www.cnblogs.com/onezg/p/8768597.html 我當時安裝的是Oracle 12c Release 1(Version 12.1.0.1.0,64位 ...
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...