2007年7月23日 星期一

OS X 如何執行應用程式

How OS X Executes Applications

http://0xfe.blogspot.com/2006/03/how-os-x-executes-applications.html

這篇文章解釋 Mac OS X 的執行檔格式。有別於其他的 UN*X,Mac OS X 並不是採用 ELF 格式的執行檔,它採用的格式是 Mach-O,同時 Mach-O 也是一個 ABI (Application Binary Interface),它解釋一個執行檔如何被 kernel 載入並且執行,其中包括:

* Which dynamic loader to use.
* Which shared libraries to load.
* How to organize the process address space.
* Where the function entry-point is, and more.

Mach-O 原來是為 NeXTstep OS 設計的,在 Motorola 68000 處理器上執行,後來被 OpenStep 採用,並於 x86 上執行。

Mach-O 檔案區分為三個區域,包括一個 header、一個載入命令(load commands)、以及一個 raw segment data。前二個描述程式的特色(features)、佈局(layout)以及檔案的其他特徵,而 raw segment data 區域則包含了一系列由 load Commands 所參考的位元組。

在 Mac OS X 上要察看執行檔的各個部份的相關資訊並非使用 ldd 或 objdump,Mac OS X 提供了更好用的工具 otool。

文章後面提到如何利用 otool 來察看執行檔是如何安排的。現簡介如下:


The Header

使用 otool -h 可以看見 header,第一個資訊是 magic number,除了用來判斷是 32-bit 或是 64-bit 的 Mach-O 之外,它也用來確認 CPU endianness(http://en.wikipedia.org/wiki/Endianness)的形態。相關的定義在: /usr/include/mach-o/loader.h。 cputype 是讓核心確認該執行檔是在正確的 CPU 上執行,相關的定義在:

/usr/include/mach/machine.h。另外,若是可以在 PPC, x86 上執行的 Universal Binaries 會多出一個 fat_header,可以透過 -f 來察看。cpusubtype 則是 CPU 額外的資訊。filetype 則確認檔案如何被安排與使用,可以透過它分辨出該檔案是 library, executable 或是 core file。前述的 filetype 與 MH_EXECUTE 相等。後面二個則提到 load commands 的數量與大小。flags 則代表其他不同的核心功能。


Load Commands

load Commands 包含一系列的命令告訴核心如何載入各種 raw segments 區段的位元組。基本上都描述每個 segment 如何在記憶體中安排、保護以及佈置。使用 otool -l 可以看見該區域。

LC_SEGMENT 0-3: 指出 segment 如何在記憶體當中映射,一個 segment 可以包含 0 個以上的 sections,詳見後面。

LC_LOAD_DYLINKER:
 指定使用哪種 dynamic linker,預設是 OS X 的 /usr/bin/dyld

LC_SYMTAB, LC_DYNSYMTAB:
 指定檔案與 dynamic linker 所使用的 symbol tables

LC_TWOLEVEL_HINTS:
 則包含二階名稱空間的的 hint table


Segments and Sections

一個 segment 是一連串的位元組,可以被核心與 dynamic linker 直接映射到虛擬記憶體當中。 header 與 load commands 被視為檔案中前二個 segment。 通常一個 OS X 的執行檔包含下列五種 segment:

__PAGEZERO :
   指出虛擬記憶體當中的 address 0 並且沒有保護
   權限。這個 segment 不佔檔案空間,而且會因為
   存取 NULL 導致立即 crash。

__TEXT : 包含 read-only data 與 executable code.

__DATA : 包含 writable data. 這些 sections 通常被
      核心標記為 copy-on-write。

__OBJC : 包含被 Objective C 語言所使用的 runtime。

__LINKEDIT : 包含被 dynamic linker 所使用的 raw data。

其中 __TEXT 與 __DATA segments 可以包含 0 或多個 section,每個 section 包含一種特定的資料。例如執行碼、字串或是內容。section 的內容可以用 otool -s 看見。要反組譯 __text section 使用 otool -tv。


如何執行應用程式

1. shell 呼叫 fork() system call

2. fork 建立 calling process(即 shell)的 calling process 邏輯拷貝並且安排它執行。這個子程序會呼叫 execve() system call ,提供被執行程式的路徑。

3. 核心載入特定的檔案,並檢查 header 是否為合法的 Mach-O 然後開始解譯 load commands 並且取代子程序的空間置換成 segments

4. 在此同時,核心也會執行 dynamic linker,動態連結相關的 libraries,如果執行檔的 symbols 都足夠的話,就會呼叫程式的 entry-point function

5. entry-point function 通常是一個與 /usr/lib/crt1.o 靜態連結的標準 function。它會初始化核心的環境,並且執行 main() 。


The Dynamic Linker

/usr/bin/dyld 是負責載入相依的共用 libraries,匯入相關的 symbols 與 functions,並且與目前的程序 "黏合"(binding)。當程序開始時,所有 linker 的程式碼都是載入共用 libraries 到程序的空間。然後看程式如何 build,真正的黏合會在執行時表現出不同的階段:

* Immediately after loading, as in load-time binding.
* When a symbol is referenced, as in just-in-time binding.
* Before the process is even executed, an optimization
technique known as pre-binding

如果沒有特別指定黏合動作,just-in-time binding 將是預設。

一個應用程式只有在所有的 symbols 與 segments 都被解析的情況下才能執行。/usr/bin/dyld 會透過預設路徑來尋找 libraries 或是 frameworks。 DYLD_LIBRARY_PATH 或DYLD_FALLBACK_LIBRARY_PATH 環境變數可以用來設定這些路徑。


完了

你可以知道要載入一個執行檔是多麼複雜的事情,這裡已經儘可能的包含所有的資訊,不過還有許多細節沒提到。作者建議大家參考下面這些文件或網站:

Mac OS X ABI Mach-O File Format Reference
Executing Mach-O Files
Overview of Dynamic Libraries
The otool man page
The dyld man page
/usr/include/mach/machines.h
/usr/include/mach-o/loader.h