OS X 應用程式 格式講解
OS X 如何執行應用程式
譯者:51test2003 譯自http://0xfe.blogspot.com/2006/03 ... s-applications.html
作為長期的 UNIX 使用者, 我通常有一些排除系統故障的工具. 最近, 我正在開發軟體並新增了Apple's OS X 系統支援; 但是和其他傳統UNIX 變種不同, OS X 不支援許多與載入,連結和執行程式相關的工具.
例如, 當共用庫重定位出錯時, 我所做的首要事情就是對可執行檔運行ldd. ldd工具列出了可執行檔所依賴的共用庫(包括所在路徑)。但是在OS X , 試圖運行ldd將報錯.
evil:~ mohit$ ldd /bin/ls
-bash: ldd: command not found
沒找到? 但在所有的UNIX上基本上都有的啊. 我想知道objdump是否可用.
$ objdump -x /bin/ls
-bash: objdump: command not found
命令未找到. 怎麼回事?
問題在於與Linux, Solaris, HP-UX, 和其他許多UNIX 變種不同, OS X 不使用 ELF二進位檔案. 另外, OS X 不屬於GNU 項目的一部分。該項目包含想ldd和objdump這樣的工具.
為了在OS X獲得可執行檔所依賴的共用庫列表,需要使用 otool 工具.
evil:~ mohit$ otool /bin/ls
otool: one of -fahlLtdoOrTMRIHScis must be specified
Usage: otool [-fahlLDtdorSTMRIHvVcXm] object_file ...
-f print the fat headers
-a print the archive header
-h print the mach header
-l print the load commands
-L print shared libraries used
-D print shared library id name
-t print the text section (disassemble with -v)
-p start dissassemble from routine name
-s print contents of section
-d print the data section
-o print the Objective-C segment
-r print the relocation entries
-S print the table of contents of a library
-T print the table of contents of a dynamic shared library
-M print the module table of a dynamic shared library
-R print the reference table of a dynamic shared library
-I print the indirect symbol table
-H print the two-level hints table
-v print verbosely (symbolicly) when possible
-V print disassembled operands symbolicly
-c print argument strings of a core file
-X print no leading addresses or headers
-m don't use archive(member) syntax
evil:~ mohit$ otool -L /bin/ls
/bin/ls:
/usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 88.0.0)
好多了. 我們可以看見/bin/ls引用了兩個動態庫. 儘管, 副檔名我們根本不熟悉.
我相信許多UNIX / Linux 使用者使用OS X系統時有類似的經曆,所以我決定寫一點目前我所知道的關於 OS X 可執行檔的知識.
OS X 運行時架構運行時環境是OS X上代碼擴充的一個架構。它一組定義代碼如何被載入,被管理,被執行的集合組成。一旦應用程式運行, 合適的運行時環境就載入程式到記憶體, 解決外部庫的引用, 並為執行準備代碼.
OS X 支援三種運行時環境:
dyld 運行時環境:基於 dyld庫管理器的推薦環境.
CFM 運行時環境: OS 9遺留環境. 實際用來設計需要使用 OS X新特色, 但還沒完全移植到dyld的應用程式.
The Classic環境: OS 9 (9.1 or 9.2) 程式無需修改直接在OS X運行.
本文主要關注於Dyld 運行時環境.
Mach-O 可執行檔格式在 OS X, 幾乎所有的包含可執行代碼的檔案,如:應用程式、架構、庫、核心擴充……, 都是以Mach-O檔案實現. Mach-O 是一種檔案格式,也是一種描述可執行檔如何被核心載入並啟動並執行ABI (應用程式二進位介面). 專業一點講, 它告訴系統:
使用哪個動態庫載入器
載入哪個共用庫.
如何組織進程地址空間.
函數進入點地址,等.
Mach-O 不是新事物. 最初由開放軟體基金會 (OSF) 用於設計基於 Mach 微核心OSF/1 作業系統. 後來移植到 x86 系統OpenStep.
為了支援Dyld 運行時環境, 所有檔案應該編譯成Mach-O 可執行檔格式.
Mach-O 檔案的組織
Mach-O 檔案分為三個地區: 頭部、載入命令區Section和原始段資料. 頭部和載入命令區描述檔案功能、布局和其他特性;原始段資料包含由載入命令引用的位元組序列。為了研究和檢查 Mach-O 檔案的各部分, OS X 內建了一個很有用的程式otool,其位於/usr/bin目錄下.
接下來, 將使用 otool來瞭解 Mach-O 檔案如何組織的.
頭部查看檔案的 Mach-O頭部, 使用otool 命令的 -h參數
evil:~ mohit$ otool -h /bin/ls
/bin/ls:
Mach header
magic cputype cpusubtype filetype ncmds sizeofcmds flags
0xfeedface 18 0 2 11 1608 0x00000085
頭部首先指定的是魔數(magic number). 魔數標明檔案是32位還是64位的Mach-O 檔案. 也標明 CPU位元組順序. 魔數的解釋,參看/usr/include/mach-o/loader.h.
頭部也指定檔案的目標架構. 這樣就允許核心確保該代碼不會在不是為此處理器編寫的CPU上運行。例如, 在上面的輸出, cputype 設成18, 它代表CPU_TYPE_POWERPC, 在 /usr/include/mach/machine.h中定義.
從上兩項資訊,我們推斷出此二進位檔案用於32-位基於PowerPC 的系統.
有時二進位檔案可能包含不止一個體系的代碼。通常稱為Universal Binaries, 通常以 fat_header這額外的頭部開始。檢查 fat_header內容, 使用otool命令的 -f切換參數.
cpusubtype 屬性制定了CPU確切模型, 通常設成CPU_SUBTYPE_POWERPC_ALL 或 CPU_SUBTYPE_I386_ALL.
filetype 指出檔案如何對齊如何使用。實際上它告訴你檔案是庫、靜態可執行檔、core file等。上面的 filetype等於MH_EXECUTE, 指出demand paged executable file. 下面是從/usr/include/mach-o/loader.h截取的片段,列出了不同的檔案類型。
#define MH_OBJECT 0x1 /* relocatable object file */
#define MH_EXECUTE 0x2 /* demand paged executable file */
#define MH_FVMLIB 0x3 /* fixed VM shared library file */
#define MH_CORE 0x4 /* core file */
#define MH_PRELOAD 0x5 /* preloaded executable file */
#define MH_DYLIB 0x6 /* dynamically bound shared library */
#define MH_DYLINKER 0x7 /* dynamic link editor */
#define MH_BUNDLE 0x8 /* dynamically bound bundle file */
#define MH_DYLIB_STUB 0x9 /* shared library stub for static */
/* linking only, no section contents */
接下來的兩個屬性涉及到載入命令區段, 指定了命令的數目和大小.
最後, 獲得了狀態資訊, 這些可能在裝載和執行時被核心使用。
載入命令載入命令區段包含一個告知核心如何載入檔案中的各個原始段的命令列表。典型的描述如何對齊,保護每個段及各段在記憶體中的布局.
查看檔案中的載入命令列表, 使用otool 命令的 -l切換參數.
evil:~/Temp mohit$ otool -l /bin/ls
/bin/ls:
Load command 0
cmd LC_SEGMENT
cmdsize 56
segname __PAGEZERO
vmaddr 0x00000000
vmsize 0x00001000
fileoff 0
filesize 0
maxprot 0x00000000
initprot 0x00000000
nsects 0
flags 0x4
Load command 1
cmd LC_SEGMENT
cmdsize 600
segname __TEXT
vmaddr 0x00001000
vmsize 0x00006000
fileoff 0
filesize 24576
maxprot 0x00000007
initprot 0x00000005
nsects 8
flags 0x0
Section
sectname __text
segname __TEXT
addr 0x00001ac4
size 0x000046e8
offset 2756
align 2^2 (4)
reloff 0
nreloc 0
flags 0x80000400
reserved1 0
reserved2 0
[ ___SNIPPED FOR BREVITY___ ]
Load command 4
cmd LC_LOAD_DYLINKER
cmdsize 28
name /usr/lib/dyld (offset 12)
Load command 5
cmd LC_LOAD_DYLIB
cmdsize 56
name /usr/lib/libncurses.5.4.dylib (offset 24)
time stamp 1111407638 Mon Mar 21 07:20:38 2005
current version 5.4.0
compatibility version 5.4.0
Load command 6
cmd LC_LOAD_DYLIB
cmdsize 52
name /usr/lib/libSystem.B.dylib (offset 24)
time stamp 1111407267 Mon Mar 21 07:14:27 2005
current version 88.0.0
compatibility version 1.0.0
Load command 7
cmd LC_SYMTAB
cmdsize 24
symoff 28672
nsyms 101
stroff 31020
strsize 1440
Load command 8
cmd LC_DYSYMTAB
cmdsize 80
ilocalsym 0
nlocalsym 0
iextdefsym 0
nextdefsym 18
iundefsym 18
nundefsym 83
tocoff 0
ntoc 0
modtaboff 0
nmodtab 0
extrefsymoff 0
nextrefsyms 0
indirectsymoff 30216
nindirectsyms 201
extreloff 0
nextrel 0
locreloff 0
nlocrel 0
Load command 9
cmd LC_TWOLEVEL_HINTS
cmdsize 16
offset 29884
nhints 83
Load command 10
cmd LC_UNIXTHREAD
cmdsize 176 flavor PPC_THREAD_STATE
count PPC_THREAD_STATE_COUNT
r0 0x00000000 r1 0x00000000 r2 0x00000000 r3 0x00000000 r4 0x00000000
r5 0x00000000 r6 0x00000000 r7 0x00000000 r8 0x00000000 r9 0x00000000
r10 0x00000000 r11 0x00000000 r12 0x00000000 r13 0x00000000 r14 0x00000000
r15 0x00000000 r16 0x00000000 r17 0x00000000 r18 0x00000000 r19 0x00000000
r20 0x00000000 r21 0x00000000 r22 0x00000000 r23 0x00000000 r24 0x00000000
r25 0x00000000 r26 0x00000000 r27 0x00000000 r28 0x00000000 r29 0x00000000
r30 0x00000000 r31 0x00000000 cr 0x00000000 xer 0x00000000 lr 0x00000000
ctr 0x00000000 mq 0x00000000 vrsave 0x00000000 srr0 0x00001ac4 srr1 0x00000000
上面的檔案在頭部下有11 載入命令直接定位, 從 0 到 10.
前四個命令(LC_SEGMENT), 從 0 到 3, 定義了檔案中的段如何映射到記憶體中去。段定義了Mach-O binary 二進位檔案中的位元組序列, 可以包含零個或更多的 sections. 稍候我們談談段。
Load command 4 (LC_LOAD_DYLINKER) 指定使用哪個動態連結器. 幾乎總是設成OS X預設動態連結器 /usr/lib/dyld。
Commands 5 and 6 (LC_LOAD_DYLIB) 指定檔案需要連結的共用庫。它們由command 4規定的動態連結器載入。
Commands 7 and 8 (LC_SYMTAB, LC_DYNSYMTAB) 指定由檔案和動態連結器分別使用的符號表. Command 9 (LC_TWOLEVEL_HINTS) 包含兩級名稱空間的hint table。最後, command 10 (LC_UNIXTHREAD), 定義進程主線程的初始狀態. 該命令僅僅包含在可執行檔裡。
Segments and Sections
上面涉及到的大多數載入命令都引用了檔案中的段. 段是Mach-O檔案直接被核心和動態連結器映射到虛擬記憶體中的一系列字元序列. 頭部和載入命令地區認為是檔案的首段。一個典型的 OS X 可執行檔通常由下列五段::
__PAGEZERO : 定位於虛擬位址0,無任何保護權利。此段在檔案中不佔用空間,訪問NULL導致立即崩潰.
__TEXT : 包含唯讀資料和可執行代碼.
__DATA : 包含可寫資料. 這些 section通常由核心標誌為copy-on-write .
__OBJC : 包含Objective C 語言運行時環境使用的資料。
__LINKEDIT :包含動態連結器用的未經處理資料.
__TEXT和 __DATA段可能包含0或更多的section. 每個section由指定類型的資料, 如, 可執行代碼, 常量, C 字串等組成.
查看某section內容, 使用otool命令 -s選項.
evil:~/Temp mohit$ otool -sv __TEXT __cstring /bin/ls
/bin/ls:
Contents of (__TEXT,__cstring) section
00006320 00000000 5f5f6479 6c645f6d 6f645f74
00006330 65726d5f 66756e63 73000000 5f5f6479
00006340 6c645f6d 616b655f 64656c61 7965645f
00006350 6d6f6475 6c655f69 6e697469 616c697a
__SNIP__
反組譯碼__text section, 使用 the -tv 切換參數.
evil:~/Temp mohit$ otool -tv /bin/ls
/bin/ls:
(__TEXT,__text) section
00001ac4 or r26,r1,r1
00001ac8 addi r1,r1,0xfffc
00001acc rlwinm r1,r1,0,0,26
00001ad0 li r0,0x0
00001ad4 stw r0,0x0(r1)
00001ad8 stwu r1,0xffc0(r1)
00001adc lwz r3,0x0(r26)
00001ae0 addi r4,r26,0x4
__SNIP__
在 __TEXT段裡, 存在四個主要的 section:
__text : 編譯後的機器碼。
__const : 通用常量資料.
__cstring : 字面量字串常量.
__picsymbol_stub : 動態連結器使用的位置無關碼stub 路由.
這樣保持了可執行檔和不可執行檔代碼在段裡的明顯隔離.
運行應用程式既然知道了Mach-O 檔案的格式, 接下來看看OS X 如何載入並運行應用程式的。運行應用程式時, shell首先調用fork()系統調用. fork 建立調用進程(shell) 邏輯拷貝並準備好執行. 子進程然後調用execve()系統調用,當然需要提供要執行的程式路徑.
核心載入指定的檔案, 檢查其頭部驗證是否是合法的Mach-O 檔案. 然後開始解釋載入命令,將子進程地址空間替換成檔案中的各段。同時,核心也執行有二進位檔案指定的動態連結器, 著手載入、連結所有依賴庫。在綁定了運行所必備的各個符號後,調用entry-point 函數.
在build應用程式時entry-point 函數通常從/usr/lib/crt1.o靜態連結(標準函數). 此函數初始化核心環境,調用可執行檔的main()函數.
應用程式現在運行了.
動態連結器
OS X 動態連結器/usr/lib/dyld, 負責載入依賴的共用庫, 匯入變數符號和函數,與當前進程的綁定。進程首次運行時, 連結器所做的就是把共用庫匯入到進程地址空間。取決於程式的build方式, 實際綁定也足執行不同的方式。
載入後立即綁定—— load-time綁定.
當符號引用時—— just-in-time綁定.
預綁定
如未指定綁定類型, 使用 just-in-time綁定.
應用程式僅僅當所有需要的符號和段從不同的目標檔案解決是才能繼續運行。為了尋找庫和架構, 標準動態連結器/usr/bin/dyld, 將搜尋預定義的目錄集合. 要修改目錄, 或提供復原路徑, 可以設定DYLD_LIBRARY_PATH或DYLD_FALLBACK_LIBRARY_PATH環境變數