[TOC]
# Windows和Linux下的文件格式
現在PC平臺上流行的可執行文件格式主要是 Windows 下的PE(Portable Executable)和 Linux 下的ELF(Executable Linkable Format),它們都是COFF(Common File Format)格式的變種。
COFF 是 Unix V3首先提出的規范,微軟在此基礎上制定了 PE 格式標準,并將它用于 Windows。后來 Unix V4 又在 COFF 的基礎上引入了 ELF 格式,被 Linux 廣泛使用。這也就是為什么 Windows 和 Linux 上的可執行文件如此相似的主要原因,因為它們都是源于同一種可執行文件格式 COFF。
從廣義上講,目標文件與可執行文件的存儲格式幾乎是一樣的,我們可以將它們看成是同一種類型的文件,在 Windows 下,將它們統稱為 PE 文件,在 Linux 下,將它們統稱為 ELF文件。
另外,動態鏈接庫(DLL,Dynamic Linking Library)(Windows 下的`.dll`和 Linux 下的`.so`)和靜態鏈接庫(Static Linking Library)(Windows 下的`.lib`和 Linux 下的`.a`)也是按照可執行文件的格式存儲的。
靜態鏈接庫稍有不同,它是把多個目標文件捆綁在一起形成一個文件,再加上一些索引,你可以簡單地把它理解為一個包含了很多目標文件的包。
其他不太常見的可執行文件格式還有 Intel/Microsoft 的 OMF(Object Module Format)、Unix a.out、MS-DOS .COM 等。
在 Linux 的 ELF 標準中,主要包含以下四類文件:

# 目標文件的組織形式
從整體上看,編譯生成的目標文件被劃分成了多個部分,每個部分叫做一個段(Section)。下圖是 Linux GCC 生成的目標文件的格式:

段名大都以`.`作為前綴,表示這些名字是系統保留的。下面是對各個部分的說明:

除了這些系統保留的段名,應用程序也可以使用其它名字定義自己的段,比如可以在 ELF 文件中插入一個叫做`music`的段來保存 MP3 音樂。應用程序自定義的的段不建議使用`.`作為前綴,否則容易和系統保留段發生沖突。
# 可執行文件的組織形式
可執行文件的組織形式和目標文件非常類似,也被劃分成多個部分,如下圖所示:

圖中左半部分是可執行文件的結構:帶陰影的是可執行文件增加的一些段,另外可執行文件刪除了可重定位段(`.rel.text`和`.rel.data`)以及段表(Section Table)。
總體來說,目標文件包含了10個左右的段,而可執行文件包含了將近30個左右的段,上面的兩張圖只列出了一些關鍵段,剩下的段都隱藏在“Other Data(其他數據)”。
圖中右半部分是進程的地址空間
不同顏色的箭頭表明了可執行文件應該被加載到地址空間的哪一個區域,可以發現,操作系統并不是為每個段都分配一個區域,而是將多個具有相同權限的段合并在一起,加載到同一個區域。
站在文件結構的角度,可執行文件包含了眾多的段(Section),每個段都有不同的作用;站在加載和執行的角度,所有的段都是數據,操作系統只關心數據的權限,只要把相同權限的數據加載到同一個內存區域,程序就能正確執行。
常見的數據權限無外乎三種:只讀(例如 .rodata 只讀數據段)、讀寫(例如 .data 數據段)、讀取和執行(例如 .text 代碼段),我們將一塊連續的、具有相同權限的數據稱為一個Segment,一個 Segment 由多個權限相同的 Section 構成。
不巧的是,“Segment”也被翻譯為“段”,但這里的段(Segment)是針對加載和執行的過程。
在 Linux 下,相信很多讀者都遇到過一種叫做`Segment fault(段錯誤)`的錯誤,這種錯誤發生在程序執行期間,在編譯和鏈接時無法檢測,一般都是代碼的權限不足導致的。例如:
~~~
#include <stdio.h>
char *str = "c.biancheng.net";
int main(){
str[1] = '@';
return 0;
}
~~~
程序執行到 6 行時就會出現“Segment fault(段錯誤)”,這是因為字符串 str 保存在地址空間的常量區,只能讀取,不能寫入,而修改字符串顯然是越權操作。
在目標文件中,`段表(Section Table)`用來描述各個 Section 的信息,包括它的名字、長度、在文件中的偏移、讀寫權限等,通過段表可以詳細地了解目標文件的結構。
而在可執行文件中,段表被刪除了,取代它的是`程序頭表(Program Header Table)`;程序頭表用來描述各個 Segment 的信息,包括它的類型、偏移、在進程虛擬地址空間中的起始地址、物理裝載地址、長度、權限等。操作系統就是根據程序頭表將可執行文件加載到內存,并為各個 Segment 分配內存空間、確定起止地址。
也就是說,可執行文件不再關注具體的文件結構,而是關注程序的加載和執行過程。
由于可執行文件在加載時實際上是被映射的虛擬地址空間,所以可執行文件很多時候又被叫做映像文件(Image)
# 段(Section)的合并
編譯器生成的是目標文件,而我們最終需要的是可執行文件,鏈接(Linking)的作用就是將多個目標文件合并成一個可執行文件。
在鏈接過程中,鏈接器會將多個目標文件中的代碼段、數據段、調試信息等合并成可執行文件中的一個段。段的合并僅僅是一個簡單的疊加過程,如下圖所示:

除了合并有用的段(例如代碼段、數據段等),鏈接器還會刪除多余的段(例如重定位段、段表等),增加其他段(例如程序頭表等)
- c語言
- 基礎知識
- 變量和常量
- 宏定義和預處理
- 隨機數
- register變量
- errno全局變量
- 靜態變量
- 類型
- 數組
- 類型轉換
- vs中c4996錯誤
- 數據類型和長度
- 二進制數,八進制數和十六進制數
- 位域
- typedef定義類型
- 函數和編譯
- 函數調用慣例
- 函數進棧和出棧
- 函數
- 編譯
- sizeof
- main函數接收參數
- 宏函數
- 目標文件和可執行文件有什么
- 強符號和弱符號
- 什么是鏈接
- 符號
- 強引用和弱引用
- 字符串處理函數
- sscanf
- 查找子字符串
- 字符串指針
- qt
- MFC
- 指針
- 簡介
- 指針詳解
- 案例
- 指針數組
- 偏移量
- 間接賦值
- 易錯點
- 二級指針
- 結構體指針
- 字節對齊
- 函數指針
- 指針例子
- main接收用戶輸入
- 內存布局
- 內存分區
- 空間開辟和釋放
- 堆空間操作字符串
- 內存處理函數
- 內存分頁
- 內存模型
- 棧
- 棧溢出攻擊
- 內存泄露
- 大小端存儲法
- 寄存器
- 結構體
- 共用體
- 枚舉
- 文件操作
- 文件到底是什么
- 文件打開和關閉
- 文件的順序讀寫
- 文件的隨機讀寫
- 文件復制
- FILE和緩沖區
- 文件大小
- 插入,刪除,更改文件內容
- typeid
- 內部鏈接和外部鏈接
- 動態庫
- 調試器
- 調試的概念
- vs調試
- 多文件編程
- extern關鍵字
- 頭文件規范
- 標準庫以及標準頭文件
- 頭文件只包含一次
- static
- 多線程
- 簡介
- 創建線程threads.h
- 創建線程pthread
- gdb
- 簡介
- mac使用gdb
- setjump和longjump
- 零拷貝
- gc
- 調試器原理
- c++
- c++簡介
- c++對c的擴展
- ::作用域運算符
- 名字控制
- cpp對c的增強
- const
- 變量定義數組
- 盡量以const替換#define
- 引用
- 內聯函數
- 函數默認參數
- 函數占位參數
- 函數重載
- extern "C"
- 類和對象
- 類封裝
- 構造和析構
- 深淺拷貝
- explicit關鍵字
- 動態對象創建
- 靜態成員
- 對象模型
- this
- 友元
- 單例
- 繼承
- 多態
- 運算符重載
- 賦值重載
- 指針運算符(*,->)重載
- 前置和后置++
- 左移<<運算符重載
- 函數調用符重載
- 總結
- bool重載
- 模板
- 簡介
- 普通函數和模板函數調用
- 模板的局限性
- 類模板
- 復數的模板類
- 類模板作為參數
- 類模板繼承
- 類模板類內和類外實現
- 類模板和友元函數
- 類模板實現數組
- 類型轉換
- 異常
- 異常基本語法
- 異常的接口聲明
- 異常的棧解旋
- 異常的多態
- 標準異常庫
- 自定義異常
- io
- 流的概念和類庫結構
- 標準io流
- 標準輸入流
- 標準輸出流
- 文件讀寫
- STL
- 簡介
- string容器
- vector容器
- deque容器
- stack容器
- queue容器
- list容器
- set/multiset容器
- map/multimap容器
- pair對組
- 深淺拷貝問題
- 使用時機
- 常用算法
- 函數對象
- 謂詞
- 內建函數對象
- 函數對象適配器
- 空間適配器
- 常用遍歷算法
- 查找算法
- 排序算法
- 拷貝和替換算法
- 算術生成算法
- 集合算法
- gcc
- GDB
- makefile
- visualstudio
- VisualAssistX
- 各種插件
- utf8編碼
- 制作安裝項目
- 編譯模式
- 內存對齊
- 快捷鍵
- 自動補全
- 查看c++類內存布局
- FFmpeg
- ffmpeg架構
- 命令的基本格式
- 分解與復用
- 處理原始數據
- 錄屏和音
- 濾鏡
- 水印
- 音視頻的拼接與裁剪
- 視頻圖片轉換
- 直播
- ffplay
- 常見問題
- 多媒體文件處理
- ffmpeg代碼結構
- 日志系統
- 處理流數據
- linux
- 系統調用
- 常用IO函數
- 文件操作函數
- 文件描述符復制
- 目錄相關操作
- 時間相關函數
- 進程
- valgrind
- 進程通信
- 信號
- 信號產生函數
- 信號集
- 信號捕捉
- SIGCHLD信號
- 不可重入函數和可重入函數
- 進程組
- 會話
- 守護進程
- 線程
- 線程屬性
- 互斥鎖
- 讀寫鎖
- 條件變量
- 信號量
- 網絡
- 分層模型
- 協議格式
- TCP協議
- socket
- socket概念
- 網絡字節序
- ip地址轉換函數
- sockaddr數據結構
- 網絡套接字函數
- socket模型創建流程圖
- socket函數
- bind函數
- listen函數
- accept函數
- connect函數
- C/S模型-TCP
- 出錯處理封裝函數
- 多進程并發服務器
- 多線程并發服務器
- 多路I/O復用服務器
- select
- poll
- epoll
- epoll事件
- epoll例子
- epoll反應堆思想
- udp
- socket IPC(本地套接字domain)
- 其他常用函數
- libevent
- libevent簡介