1. 程式人生 > >可執行檔案的裝載與程序的一點小總結 《程式設計師的自我修養》·筆記

可執行檔案的裝載與程序的一點小總結 《程式設計師的自我修養》·筆記

可執行檔案的裝載與程序小結

  • 程序的虛擬地址空間
      每個程式被執行起來之後都擁有自己獨立的虛擬地址空間,這個虛擬地址空間的大小是CPU的位數決定的。比如,32位的硬體平臺決定了虛擬地址空間的地址為(2^32-1),也就是我們常說的4GB虛擬記憶體的大小。
      需要注意的是,分配的4GB的虛擬空間並不是全部給程序的,比如,linux下1GB給作業系統,餘下的3GB中基本上都分配給程序,但是3GB中的其中小部分要分配給其他用途;win下面按照2GB、2GB進行類似的劃分。

  • 裝載的方式

    • 裝載的基本思想
        將程式最常用的部分駐留在記憶體。最常用的方法是頁對映,如下。
    • 頁對映
        要完成頁對映就要將記憶體和磁碟中的資料和指令按“頁”為單位劃分成若干頁,以後所有的裝載和操作的單位就是頁。下圖就是可執行檔案(虛擬空間)與實體記憶體的對映(不考慮程式執行的虛擬地址空間):


        關於頁的操作有很多種情況,比如“記憶體滿時的頁置換”、“頁錯誤”等等情況下采取的種種策略(FIFO、LUR)這裡不再贅述。
  • 從作業系統的角度看可執行檔案的裝載方法

    • 程序的建立
        從作業系統的角度看,一個程序最關鍵的特徵就是他有獨立的虛擬地址空間,這使得它有別於其他程序,上述的對映關係直接使用實體地址進行操作,那麼每次頁裝入的時候就要就行重定位,所以我們需要引入程序的虛擬執行地址空間。那麼,下面就說一下從作業系統角度看一個程式被執行的大致過程:
        1.首先是建立程式對應的虛擬地址空間。即進行虛擬地址空間與程式執行的實體記憶體的對映(方向是程序虛擬空間到程序實體記憶體)。我們知道一個虛擬空間由一組頁對映函式將虛擬空間各個頁對映至相應的物理空間。此處所謂的“建立”並不是建立空間,而是建立虛擬空間到實體記憶體空間的對映函式所需要的一系列的資料結構,對於Linux就是建立一個“頁目錄”結構即可,並不需要設定虛擬頁到物理頁的對映關係。linux下將虛擬空間的各個頁對映至相應的物理空間,實際上只是分配了一個頁目錄(Page Directory)就可以了,並且不用設定頁對映關係,這些對映關係到後面程式發生頁錯誤的時候再進行設定。
        2.讀取可執行檔案頭,建立程序虛擬地址空間和可執行檔案的對映關係
      。這一步將可執行檔案空間與虛擬空間關聯起來(方向是可執行檔案虛擬空間到程序虛擬空間),使得發生缺頁錯誤時,OS能夠知道到可執行檔案中的哪個位置去找到所需要載入到實體記憶體的內容;**這種對映關係只是儲存在作業系統內部的一個數據結構。**Linux中將程序虛擬空間中的一個段叫做虛擬記憶體區域(VMA,Virtual Memory Area);在Windows中將這個叫做虛擬段(Virtual Section)。
        【注意】由於可執行檔案在裝載的時候實際上是被對映的虛擬空間,所以可執行檔案很多時候被稱作對映檔案。程序虛擬地址空間和可執行檔案的對映關係如下:

        3.設定CPU的指令暫存器為可執行檔案的入口地址,啟動執行
      :OS將控制權交給了程序。從程序的角度看這一步可以簡單的認為作業系統執行了一條跳轉指令,直接跳轉到可執行檔案的入口(ELF檔案頭中儲存了入口地址項)。
    • 頁錯誤
        完成上述三個步驟之後,其實OS僅僅只是可執行檔案與程序虛存之間建立起了對映——即通常意義上所說的程式載入到了記憶體,實際上這裡說的是程式完全載入到了虛擬記憶體,但是程式碼和資料根本就沒有載入到實體記憶體中,程序虛存與實體記憶體空間的對映關係其實也沒有建立起來(上面也說了在“頁錯誤階段進行對映關係的設定”),這樣程式一旦開始執行,將會立即出現缺頁錯誤,即程式將要訪問的程序虛存地址並沒有對映到實體記憶體空間的某個page(虛擬頁),(頁錯誤的處理執行緒執行)此時OS會重新接管系統控制權,查詢剛才第二步儲存的可執行檔案到程序虛存對映關係的資料結構,找到所缺的虛擬頁對應於可執行檔案中的偏移,然後在程序實體記憶體分配一個物理頁,將可執行檔案中的內容從磁碟讀入到記憶體中,並將這個物理頁(程序實體記憶體)與該虛擬頁(程序虛存)建立起對映,然後OS將控制權重新交給程序,程式繼續執行。如下圖所示:
  • 程序虛存空間分佈

    • ELF檔案在對映到程序虛存的過程中是以系統的頁作為單位的,那麼每個段在對映時的長度都應該是系統頁長度的整數倍;如果不是那麼多餘部分也將佔用一頁。這樣的話記憶體浪費是大問題。
    • ELF檔案中, 段的許可權只有為數不多的幾種組合:
      1.以程式碼段為代表的許可權為可讀可執行的段
      2.以資料段和BSS段為代表的許可權為可讀可寫的段
      3.以只讀資料段為代表的許可權為只讀的段。
      對於相同許可權的段,把它們合併到一起當作一個段進行對映。如下圖,”.text”和”.init”段都是可讀可執行的,則進行合併,形成一個”segment”:
    • 堆和棧
      kernel使用VMA劃分來管理程序的虛擬地址空間。典型的程序包括程式碼:
      1.程式碼VMA(RE屬性,有映像檔案)
      2.資料VMA(RWE屬性,有映像檔案)
      3.堆VMA(RWE屬性,無映像檔案,向上擴充套件)
      4.棧VMA(RW屬性,無映像檔案,向下擴充套件)
      如下圖所示:

      【需要注意】其實DATA segment對應的就是DATA VMA;CODE segment對應的就是CODE VMA。幾乎在每一個程序的VMS檢視中都可以看見[heap]和[stack]這兩個VMA,但是這兩個VMA在可執行檔案中都沒有對應的segment存在,所以它們被稱之為匿名VMA。malloc()庫函式就是從堆VMA中分配空間。
  • Linux核心裝載ELF過程
      Linux環境下,fork系統呼叫將會建立一個與當前task完全一樣的新task,直到應用程式呼叫exec*系列的Glibc庫函式最終呼叫execve()系統呼叫之後,Linux核心才開始真正裝載ELF可執行檔案(映像檔案)。execve核心入口為sys_execve(),隨之呼叫do_execve()將查詢這個可執行檔案,如果找到則讀取ELF可執行檔案的前128個位元組,然後呼叫search_binary_handle()通過ELF檔案頭中的e_ident得到可執行檔案的Magic Number,判斷出這是一個什麼型別的可執行檔案,並呼叫不同可執行檔案的裝載處理程式,對於ELF可執行檔案而言,其裝載處理程式為load_elf_binary(),這個函式將會把execve系統呼叫的返回地址修改為ELF可執行檔案的入口點,對於靜態連結得到的ELF檔案即檔案頭中定義的e_entry,對於動態連結得到的ELF可執行檔案則是動態連結器。一步一步返回到sys_execve()之後,因為返回地址已經被修改為了ELF程式入口地址了,所以系統呼叫返回到使用者態之後,EIP指令暫存器將直接跳轉到ELF程式入口地址,程式開始執行,裝載完成。
    ELF檔案的裝載過程:

    fork -> execve() -> sys_execve() -> do_execve()

      do_execve() 讀取檔案的前128個位元組判斷檔案的格式(一般根據魔數來判斷,比如elf的頭四個位元組為:0x7F, e, l, f)。
      然後呼叫search_binary_handle()去搜索和匹配合適的可執行檔案裝載處理過程,對於elf則呼叫load_elf_binary():

    • 檢查ELF可執行檔案格式的有效性
    • 尋找動態連結的“.interp”段,設定動態聯結器路徑
    • 根據ELF可執行檔案的程式頭表的描述,對ELF檔案進行對映,比如程式碼、資料、只讀資料。
    • 根據ELF程序環境,比如程序啟動是EDX暫存器的地址應該是DT_FINI的地址。
    • 將系統呼叫的返回地址修改成ELF可執行檔案的入口點,這個入口點取決於程式的連結方式,靜態ELF可執行檔案為e_entry所指的地址,對於動態ELF入口點為動態聯結器。

      Load_elf_binary()執行完畢,返回至do_execve()再返回至sys_execve(),最後一步的系統呼叫返回地址改成了被裝在的ELF程式入口地址。當sys_execve()系統呼叫從核心態返回到使用者態時,EIP暫存器直接跳轉到了ELF程式的入口地址,新程式開始執行。

相關推薦

Linux 執行檔案結構程序結構

一、Linux可執行檔案結構 在 Linux 下,程式是一個普通的可執行檔案,以下列出一個二進位制可執行檔案的基本情況: 可以看出,此可執行檔案在儲存時(沒有調入到內容)分為程式碼區(text)、資料區(data)和未初始化資料區(bss)3 個部分。各段基本內

Linux C的執行檔案結構以及程序結構

(公共部分):程式碼區,BSS區,資料區. 1.程式碼區:存放可執行的指令.順便規劃局部變數的相關資訊(??).   獨有性:一份指令在記憶體(不管虛擬記憶體還是實際)中只要有一份就可以的   只讀性:彙編指令包含 操作碼+運算元;一般操作碼是不可變的,但是運算元可

關於《程式設計師自我修養》中提到的裝載方式

w6r559詬竿仲凡市閹《http://baobao.baidu.com/question/84d5ae2b2045cb2e1d6905e443eceb01?nV》 4740fb鞘倜猛俜春付《http://baobao.baidu.com/question/9e58cdb8a

執行檔案裝載程序一點總結程式設計師自我修養》·筆記

可執行檔案的裝載與程序小結 程序的虛擬地址空間   每個程式被執行起來之後都擁有自己獨立的虛擬地址空間,這個虛擬地址空間的大小是CPU的位數決定的。比如,32位的硬體平臺決定了虛擬地址空間的地址為

連結裝載庫 第6章 執行檔案裝載程序

可執行檔案的裝載與程序 在第一章中講到,程式直接使用實體記憶體地址有以下缺點: 地址空間不隔離。惡意程式可以很容易的改寫其他程式的資料。 記憶體使用效率低。一個程式需要執行時,需要將整個程式裝入記憶體之中。 程式執行地址的不確定。因為無法保證每次都將程式載入到相同

將Maven工程匯出war包(匯出執行檔案) war包的安裝部署(以兩個Tomcat為例,詳細)請到:

1  開啟war工程的pom.xml,將如下內容複製到配置Tomcat的程式碼中                 &l

Linux逆向---執行檔案程式碼靜態注入實驗

分析完節頭之後,我最大的收穫就是,這麼多的01,並不是所有的都是用來執行我寫的那段輸出helloworld的程式的,而且程式碼段中有很大一段空閒空間,這就給我們一個向可執行檔案中注入自己程式碼,然後通過修改程式邏輯達到讓它去執行我們自己寫的的部分的程式碼的邏輯的機會。 這裡我們的原始碼是

關於vs2017新增mvc模型報“未找到命令dotnet-aspnet-codegenerator匹配的執行檔案”的錯誤處理

  vs2017新建.net core專案,建好模型(book),遷移資料庫之後,在Startup執行“dotnet aspnet-codegenerator razorpage -m Book -dc BookContext -udl -outDir Pages\Books --referenceScrip

徹底理解連結器:庫執行檔案

庫與可執行檔案 在連結器可操作的元素這一節中我們提到,連結器可以操作的最小單元為目標檔案,也就是說我們見到的無論是靜態庫、動態庫、可執行檔案,都是基於目標檔案構建出來的。目標檔案就好比樂高積木中最小的零部件。 給定目標檔案以及連結選項,連結器可以生成兩種庫,分別是靜

【軟體開發底層知識修煉】九 連結器-重定位檔案執行檔案

上幾篇文章學習了Binutils輔助工具裡面的幾個實用的工具,那些工具對於以後的學習都是非常有幫助的,尤其是C語、C++語言的學習以及除錯是非常有幫助的。點選連結檢視上一篇文章:點選檢視 本篇文章開始一個新的知識的學習,連結器的學習。學習完連結器的系列文章,我們將全面瞭解連結器的工作

一個exe執行檔案的生死(執行原理)(轉載)

這篇文章講一個EXE檔案從載入執行到結束的整個流程,感覺寫的不錯, 記錄並共享下。下面列的只是程式執行時的大概流程,詳見附件。1、Shell(Explorer.exe)呼叫CreateProcess函式啟用exe程式 2、系統建立一個程序核心物件,引用計數置為1 3、系統為程序建立一個

python機器視覺(X)打包為exe執行檔案

利用pyinstaller將.py 程式打包成可執行檔案 1. TL;DR 安裝:pip install pyinstaller 使用pyinstaller mycode.py,在dist資料夾下就能看到.exe程式了。 2.簡介 PyInstalle

使用 pyinstaller 建立執行檔案時的一個麻煩

想使用pyinstaller 建立exe檔案,首先安裝 pyinstaller :pip install pyinstaller然後 對一個python 檔案進行操作:pyinstaller -F hello.py 會在dist目錄下產生 hello.exe 但是若你的原始檔

python筆記--如何將python腳步打包成執行檔案

如何將python腳步打包成可執行檔案 如果一個朋友說讓你幫他實現一個簡單的小的程式,這個時候python是再合適不過的了,但是如何將自己的python程式打包成一個可執行檔案,這樣朋友只需要執行這個可執行檔案就好,不用去安裝繁雜的程式設計環境,這是很有必要的

根據程序控制代碼 獲得執行檔案路徑 的幾種方法

通過程序控制代碼,獲得可執行檔案的路徑,主要有以下幾種方法: 第一種方法:也是最常用的方法,是通過GetModuleFileNameEx函式獲得可執行檔案的模組路徑,這個函式從Windows NT 4.0開始到現在的Vista系統都能使用,向後相容性比較好。 【函式

Windows VC++ 調整程序當前目錄為程式執行檔案所在目錄

    調整程序當前目錄為程式可執行檔案所在目錄是個非常實用的方法。為了更加的讓程式碼複用,本文將調整程序當前目錄為程式可執行檔案所在目錄這一功能封裝為三個實用函式——1.SplitPathFileName這個函式將檔案全名(帶路徑)分解成路徑名,檔名,字尾名。2.GetPr

基於Python3.6寫的自助翻譯軟體--使用google translate的介面,Python實現爬取google翻譯API結果,並打包成.exe的執行檔案

看文獻看的頭疼,為了解決小麻煩沒事就寫了這個來玩一玩。其實也沒有什麼就是用一個簡單的爬蟲和介面,所以啥也不多說,直接貼程式碼,歡迎嘗試# -*- coding: utf-8 -*- # filename:GoogleTranslation1.2.py import urll

如何通過程序pid獲取程序名、執行檔案的名稱

一、過程序pid獲取到程序名。 如下: void getNameByPid(pid_t pid, char *task_name) { char proc_pid_path[BUF_SIZE]; char buf[BUF_SIZE]; sprin

白學習Python之路---py檔案轉換成exe執行檔案

一、背景 今天閒著無事,寫了一個小小的Python指令碼程式,然後給同學炫耀的時候,發現每次都得拉著其他人過來看著自己的電腦螢幕,感覺不是很爽,然後我想著網上肯定有關於Python指令碼轉換成可執行檔案的操作,事不宜遲,我就開始了問度娘,各種尋找資料,發現網上的資料太多了,有一些比較老了,適合Python2

深入Jar包:Gradle構建執行jar包訪問jar包中資料夾檔案

## 前言 Java的跨平臺功能聽起來很誘人可口,號稱“Write Once,Run Everywhere”,實際上是“Run Once,Debug Everywhere”... 在實際開發過程中還是會遇到各種各樣的坑的,剛剛解決了一系列問題,特地寫個文章總結一下。 ## 使用Gradle構建Jar包 感謝