1. 程式人生 > >《深入理解計算機系統》筆記(三)連結知識【附圖】

《深入理解計算機系統》筆記(三)連結知識【附圖】

歡迎檢視《深入理解計算機系統》系列部落格

--------------------------------------------------------------------------------------------------------------------

概述

        該章節主要講解的是ELF檔案的結構。    

        ●靜態庫的概念

        ●動態庫(又叫共享庫)的概念,一般用於作業系統,普通應用程式作用不大。

        ●程式的載入過程。

        該書中對連結的解釋也不夠詳細。在章節最後,作者也承認:在計算機系統文獻中並沒有很好的記錄連結。因為連結是處在編譯器、計算機體系結構和作業系統的交叉點上,他要求理解程式碼生成、機器語言程式設計、程式例項化和虛擬儲存器。它恰好不落在某個通常的計算機系統領域中。

        該章節講述Linux的X86系統,使用標準的ELF目標檔案,無論是什麼樣的作業系統,細節可能不盡相同,但是概念是相同的。

        讀完這一章節後,對“符號”的概念很是模糊。

7.1編譯驅動程式

    這裡再說一下編譯系統。大多數編譯系統提供編譯驅動程式,它代表使用者在需要的時候呼叫語言預處理、編譯器、彙編器、和連結器。我自己畫了一個結構圖。


7.2靜態連結

7.3目標檔案

    目標檔案有三種:可重定位目標檔案、可執行目標檔案和共享目標檔案(即動態連結庫),個個系統上對目標檔案的叫法不一致,Unix叫a.out,Windows NT叫PE(Portable Executable)。現代Unix使用ELF格式(EXecutable and Linkable Format 即可執行和可連結格式)。     下面詳細介紹“可重定位目標檔案”,下圖最左邊的一個圖。

    上圖說明了,一個目標檔案生成可執行檔案,然後載入到記憶體後的對映等,三個步驟。     ELF頭描述了生成該檔案的系統的字的大小和位元組序。ELF和節頭部表之間每個部分都稱為一個節(section)     .text:已編譯程式的機器程式碼     .rodada:只讀資料,比如printf語句中的格式串。     .data:已經初始化的全域性C變數。區域性變數在執行時儲存在棧中。即不再data節也不在bss節     .bss:未初始化的全域性C變數。不佔據實際的空間,僅僅是一個佔位符。所以未初始化變數不需要佔據任何實際的磁碟空間。C++弱化BSS段。可能是沒有,也可能有。     .symtab:一個符號表,它存放“在程式中定義和引用的函式和全域性變數的資訊”。     .rel.text:一個.text節中位置的列表。(將來重定位使用的)     .rel.data:被模組引用或定義的任何全域性變數的重定位資訊。     .debug:除錯符號表,其內容是程式中定義的區域性變數和型別定義。     .line:原始C源程式的行號和.text節中機器指令之間的對映。     .strtab:一個字串表.

可定位目標檔案的結構:讓你深入瞭解程式段,資料段,bss段,符號表等等。

7.4可重定位目標檔案——參考7.3

7.5符號和符號表

符號表是一個數組,數組裡存放一個結構體。

typedef struct {
    int name;		/*String table offset*/
    int value;		/*Section offset, or VM address*/
    int size;		/*Object size in bytes*/
    char type:4,	/*Data, fund,section,or src file name (4 bits)*/
        binding:4;	/* Local of global(4bits)*/
    char reserved;	/*Unused*/
    char section;	/*Section header index ABS UNDEF*/
}Elf_Symbol;

7.6符號解析

原則是:編譯器只允許每個模組中每個本地符號只有一個定義。而且對全域性的符號的解析很棘手,因為多個目標檔案可能會定義相同的符號。C++和Java使用mangling手段來支援過載。

    多重定義的全域性符號,請看下面的程式:

/*foo.c*/                      /*bar.c*/
#include <stdio.h>             int x;
void f(void);                  void f()
int x =15213;                  {
int main()                         x = 15212;
{                               }
    f();
    printf("x=%d\n",x);
    return 0;
}

大家能猜到輸出的結果是15212;這是因為:bar.c中的x全域性變數沒有初始化,導致函式f中使用的是foo檔案中的x變數。

根據Unix聯結器使用下面的規則來處理多重定義的符號:

    ●規則1:不允許有多個強符號。

    ●規則2:如果有一個強符號和多個弱符號,那麼選擇強符號(這就是上面這道題的答案,初始化的int x=15213是強符號,而int x;是弱符號)

    ●規則3:如果有多個弱符號,那麼從這些弱符號中任意選擇一個(多麼可怕啊)

靜態庫

    事先寫好的一些可重定位的目標檔案打包成一個單獨的檔案,它可以用作聯結器的輸入。當聯結器構造一個輸出的可執行檔案時,它只拷貝靜態庫裡被應用程式引用的目標模組。(稍後講解動態連結庫,也稱之為共享庫)。

    在Unix系統中,靜態庫以一種成為存檔(archive)的特殊檔案格式存放在磁碟中。存檔檔案是一組連線起來的可重定位目標檔案的集合。有一個頭部用來描述每個成員目標檔案的大小和位置。存檔檔案的字尾是.a標識。是否可以這麼理解.a檔案的結構呢?(自己畫圖)


    下面用展示一個靜態庫連線的過程:


7.7重定位

7.8可執行檔案

    參考7.3節圖中央部分。可執行檔案跟可重定位目標檔案非常相似。只是可執行檔案多了“init”和“段頭部表"少了,”.rel.text“和”.rel.data“兩個節。

7.9載入可執行檔案。

    從7.3節圖中可以發現,右部是載入後的程式結構。ELF目標檔案被設計的非常容易載入到儲存器。需要注意的是Unix中,程式總的程式碼段總是從0x0804800處開始(這就是虛擬儲存器的作用)。資料段是在接下來的下一個4KB對齊的地址處。執行時堆在"讀/寫段"之後接下來的第一個4KB對齊的地址處,並通過malloc庫往上增長。而棧總是往下生長。

7.10動態庫(共享庫)

    動態庫是為了解決靜態庫的兩個弊端而出現的,靜態庫的兩個弊端:1)靜態庫更新後,程式要獲得該靜態庫然後再編譯。2)不同程式可能使用相同的靜態庫,導致很多靜態庫中的程式碼重複被載入到儲存器中。

    共享庫是致力於解決靜態庫的缺陷而出現的現代創新型產物。共享庫是一塊目標模組,在執行時,可以載入到任意的儲存器地址,並和一個在儲存其中的程式連結起來。這個過程稱之為”動態連結“,是由一個叫做”動態連結器“的程式來完成的。

    共享庫是以樑總方式來共享的:1)所有引用該庫的程式都共享一個.so檔案中的程式碼和資料,而不是靜態庫一樣拷貝一份。2)在儲存器中,一個共享庫的.text節的一個副本可以被不同正在執行的進城共享,從而節約寶貴的儲存器資源。(Unix中動態庫以.so字尾表示。)

    理解動態庫=共享庫的概念非常重要。動態庫一般是大型軟體或者作業系統的最愛,因為對於普通應用來說,沒有那麼多庫給別人使用,絕大多數都是自己用,所以靜態庫就夠了。

7.11從應用程式中載入和連結共享庫

    應用程式還可能從應用程式中載入和連結任意共享庫,而無需編譯時連結那些庫到應用中(這個牛逼大了)!

    Windows中的更新大部分是這個技術。另外還有構建高效能web伺服器。

    Linux為動態連結器提供了一系列簡單的介面:

    #include <dlfcn.h>
    void *dlopen(const char *filename, int flag);//載入共享庫
    void *dlsym(void *handle, char *symbol);    //指向一個共享庫的控制代碼和一個符號名字。
    int dlclose(void *handle);  //下載共享庫
    const char *dlerror(void);  //容錯

    Java定義了一個標準的呼叫規則,叫做Java本地介面(Java NativeInterface,JNI),它允許Java程式呼叫本地的C和C++函式。JNI的基本思想是將本地的C函式,如foo,編譯到共享庫中,如foo.so .當一個正在執行的Java程式試圖呼叫函式foo時,Java解析程式利用dlopen介面(或者類似的介面)動態連結和載入foo.so,然後呼叫foo。

7.12與位置無關的程式碼(PIC)

7.13處理目標檔案的工具  

相關推薦

深入理解計算機系統筆記連結知識附圖

歡迎檢視《深入理解計算機系統》系列部落格 --------------------------------------------------------------------------------------------------------------

深入理解計算機系統

C只支援大小在編譯時就能知道的多維陣列(對於第一維可能有些例外)。在許多應用程式中,我們需要程式碼能夠動態分配的任意大小的陣列進行操作。,為此,我們必須顯示地寫出從多維陣列到一維陣列的對映。 異類的資料結構:C提供了兩種不同型別的物件結合到一起來建立資料型別的機制;結構,用

深入理解計算機系統學習1

原始檔:0, 1 組成每行以一個看不見的’\n’結尾, 執行一個原始檔分四個階段:預處理, 編譯,彙編,連線 .i 檔案:.c檔案預處理後生成 .ii檔案: .cpp檔案預處理後生成 預處理階段:前處理器(cpp)根據以#開頭的命

讀書筆記——《深入理解計算機系統》第章_程式的機器級表示

    前言:已經大四,沒有去找工作,選擇了保研,之所以這樣選擇,有三個原因,一、剛進校時,聽說保研都是牛人才能行的事,所以一心努力保研;二、2008年開始,經濟危機比較嚴重,工作不好找,雖然軟體專業要找一份工作還是比較容易,但好工作的機會少了很多,再多學習幾年,規避下風險;

深入理解計算機系統》第章學習筆記

並發 錯誤 ia32 庫函數 容易 簡單 linux 嚴重 格式 通過本周的學習,總結出一下知識內容 機器級代碼 計算機系統使用了多種不同形式的抽象,利用更簡單的抽象模型來隱藏實現的細節。 對於機器級編程來說,其中兩種抽象尤為重要: 1、指令集體系結構(Instructio

深入理解計算機系統筆記

這一點之前自己也有這樣的疑惑,就是當一個由w位組成的資料型別,如果要移動k≥w位會得到什麼樣的結果呢?C語言標準規避了在這種情況,比如對於w=32時,k分別是32,36和40時,位移量是通過k mode w得到的。這時候位移運算分別是移動0, 4, 8位。不過,對於這種行為C

深入理解MyBatis的原理:配置文件上

dynamic 如何 turn ready conf 屬性。 支持 left bool 前言:前文提到一個入門的demo,從這裏開始,會了解深入 MyBatis 的配置,本文講解 MyBatis 的配置文件的用法。 目錄 1、properties 元素 2、設置(set

深入理解MyBatis的原理:配置文件用法

pac amt 單個 gis obb rri tab obj 用戶 前言:前文講解了 MyBatis 的配置文件一部分用法,本文將繼續講解 MyBatis 的配置文件的用法。 目錄 1、typeHandler 類型處理器 2、ObjectFactory 3、插件 4、e

深入理解線性迴歸演算法:淺談貝葉斯線性迴歸

前言 上文介紹了正則化項與貝葉斯的關係,正則化項對應於貝葉斯的先驗分佈,因此通過設定引數的先驗分佈來調節正則化項。本文首先介紹了貝葉斯線性迴歸的相關性質,和正則化引數λ的作用,然後簡單介紹了貝葉斯思想的模型比較,最後總結全文。   目錄 1、後驗引數分佈和預測變數分

深入理解計算機系統筆記之第二章(一)

資訊的表示和處理(一) 大多數計算機使用8位的塊(也就是一個位元組byte),由此可以看到32位(4個位元組)系統和64位(8個位元組)系統的區別。32位系統在於cpu可以同時處理4個位元組(32位)的資料,那麼64位系統cpu可以同時處理8個位元組(64位)的資料。 一個

2018-2019-1 20189215 《深入理解計算機系統》第章學習總結

《第3章 程式的機器級表示》 彙編程式碼是機器程式碼的文字表示,是與特定機器密切相關的。用高階語言編寫的程式可以在很多不同的機器上編譯和執行。 3.2 程式編碼 彙編程式碼表示非常接近於機器程式碼。與機器程式碼的二進位制格式相比,彙編程式碼的主要特點是它用可讀性更好的文字格式表示,能

深入理解JVM——配置引數;垃圾回收演算法

深入理解JVM(三)——配置引數 1、跟蹤引數 2、堆分配引數 3、棧分配引數 這三類引數分別用於跟蹤監控JVM狀態,分配堆記憶體、棧記憶體。 跟蹤引數 跟蹤監控JVM,用於JVM調優以及故障排查。 1、當發生GC時,列印GC簡要資訊 使

深入理解計算機系統筆記

   對於自動駕駛,特別是嵌入式開發板上移植程式來說,用某個確定大小的表示來編碼資料型別非常重要。例如,當編寫程式,使得機器能夠按照一個標準協議在因特網上通訊時,讓資料型別與協議指定的資料型別相容是非常重要的。不過現在64位系統已經比較普及,不像以前處於過渡階段。特別是long型

深入理解計算機系統筆記

   我看的是《深入理解計算機系統》原書第三版,這真的是一本相見恨晚的好書。看了幾天,有些內容已經在實際程式設計中獲益了。我重點關注的是優化程式效能。作為程式設計師,我們無須為了寫出高效程式碼而去了解一些編譯器的內部工作。但是,為了在C程式中作出好的編碼選擇,我們確實需要了解一些

深入理解java虛擬機器

前言  上篇已經介紹到記憶體結構劃分《深入理解java虛擬機器二》本篇主要講述JVM垃圾回收機制。下面直接進入正題。 正文 JVM垃圾回收機制收集的是死亡的物件,也是就是沒有任何引用的物件。那怎麼判斷物件是否死亡。 引數計數演算法 引數計數演算法會給每個物件新增一個

深入理解Java記憶體模型——順序一致性

資料競爭與順序一致性保證 當程式未正確同步時,就會存在資料競爭。java記憶體模型規範對資料競爭的定義如下: 在一個執行緒中寫一個變數, 在另一個執行緒讀同一個變數, 而且寫和讀沒有通過同步來排序。 當代碼中包含資料競爭時,程式的執行往往產生違反直覺的結果(前一章的示例正是如此)。如果一

深入理解java虛擬機器一個類載入器只初始化一次類物件,不同類載入器可以對同一類物件進行初始化

package com.ygl; class Final{public static final int x=6/3;//此處x在編譯時能計算出值,是編譯時的常量,則System.out.println(Final.x);直接輸出值,不再執行下面static(前提是fina

深入理解計算機系統》(第版)第二章部分知識點總結

資訊的表示和處理      本渣渣要進行CSAPP期中考了,但是上半學期啥也沒聽QWQ,只能臨陣磨槍了*學習要點*1、計算機如何表示數字2、其他形式資料的基本屬性*資訊儲存*位元組:最小的可定址的記憶體單位地址:記憶體中每個位元組都由一個唯一的數字來標識虛擬地址空間:所有可能

深入理解openstack網路架構

前文中,我們學習了openstack網路使用的幾個基本網路元件,並通過一些簡單的use case解釋網路如何連通的。本文中,我們會通過一個稍微複雜(其實仍然相當基本)的use case(兩個網路間路由)探索網路的設定。 路由使用的元件與連通內部網路相同,使用names

深入理解計算機系統--筆記

採用儲存程式方式,指令和資料不加區別混合儲存在同一個儲存器中。 不可程式設計的計算機器(計算器,學習機,非智慧手機),不提供API,僅內含固定用途的程式,只能讀資料 儲存器是按地址訪問的線性編址的一維結構,每個單元的位數是固定的。 指令由操作碼和地址組成。操作碼指明本指令的操作型別,地址碼指明運算元和地址。運