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