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環境變量
新聞熱點
疑難解答