Mac-O文件加载的全过程(一)

在Mac的开发中, 有没有想过当我们点击可执行文件之后,Mac究竟做了什么事情才让我们的程序运行起来? 对于应用层开发人员或者普通的用户而言, 其实无需知道的这么详细;但是对于内核开发人员而言, 如果能了解这一系列的过程, 那么将增强我们的内核的开发功底。

那么下面我们开始分析我们的鼠标点击之后, Mac都做了什么事情。

1. Mac的历史

这一部分有更好的文章

2. 准备工作

(1). 你需要下载XNU内核源代码以及dyld源代码
(2). Xcode,vim或者其他什么浏览源代码的工具。

3. 分析

在分析加载过程的时候, 需要涉及到内核和应用层两个部分。因此这篇文章也将分为两部分分别阐述。


3.1 内核部分

Mac的内核经过多次的发展, 目前这部分被称作XNU。具体为什么叫这个名字, 大家可以去搜索一下。在最初开始设计XNU的时候, Apple是打算设计一个微内核, 也就是将最重要的事情放在内核里面并且内核只负责仲裁而非逻辑处理。 这个内核也就是在Mac OS 9上面使用的内核,但是这个产品是一个效率底下系统。因此当乔布斯回归以后对内核进行了大改, 在内核部分引入了FreeBSD的部分, 这个产品就是我们现在在使用的Mac OSX的内核XNU。 简单的历史说完了, 下面我们来点儿干货。

3.1.1 Mac-O文件格式的分析

既然是要分析Mac-O文件的加载过程, 那么必须涉及到Mach-O的格式问题。 对于这个格式, 我们可以参考下面的图:



从这这张图片中, 我们可以看到Mach-O文件在结构上可以分成三个部分:
(1) 文件头 
(2) 命令区域 
(3) 数据区域(包括数据, 代码等等)

这三个部分共同组成了Mach-O文件格式。下面我们将以此讨论这个三个部分, 在最后我讲给出一些需要注意的部分和部分代码。

3.1.1.1 Mach-O文件头

Mach-O文件头的定义如下:

struct mach_header(_64)

代码来自:${XNU_ROOT}/EXTERNAL_HEADER/mach-o/loader.h:

struct mach_header {
  uint32_t    magic;      
  cpu_type_t  cputype;    
  cpu_subtype_t   cpusubtype; 
  uint32_t    filetype;   
  uint32_t    ncmds;      
  uint32_t    sizeofcmds; 
  uint32_t    flags;      
};
struct mach_header_64 {
  uint32_t    magic;      
  cpu_type_t  cputype;    
  cpu_subtype_t   cpusubtype; 
  uint32_t    filetype;   
  uint32_t    ncmds;      
  uint32_t    sizeofcmds; 
  uint32_t    flags;      
  uint32_t    reserved;   
};

上面的头部定义包含了32bit和64bit的头部。字段的含义如下(下面是以64bit的头部说明的):

命令含义 说明
magic 魔数字 主要用来区分当前Mach-O所支持的CPU架构(当前只有32bit和64bit)。
cputype CPU类型 主要的CPU类型(32/64bit), 以及其他的属性。
cpusubtype CPU子类型 cpu具体的类型。
filetype 文件类型 文件类型比较多,比如MH_EXECUTE代表可执行文件。
ncmds 命令个数 也就是下一个segment中得segment的数量。
sizeofcmd 第三个部分的大小 None。
flags 当前Mach-O的属性 比较常见的属性包括MH_PIE(当前文件执行ASLR)等。

更多的值可以参考这里.

3.1.1.2 Mach-O命令区域

这一部分是Mach-O文件中最重要的部分, 我们从上面的Mach-O结构图中可以看到, 所谓的segment其实是数据区域的一个索引。每个segment都在数据区域对应了一段自己的区域。我们需要做的就是找到这些部分并执行他们。首先我们看一下Segment的结构:
Segment的结构定义如下:

struct load_command

代码来自:${XNU_ROOT}/EXTERNAL_HEADER

struct load_command {
  uint32_t cmd;       /* type of load command */
  uint32_t cmdsize;   /* total size of command in bytes */
};

这个结构对应的成员比较少, cmd代表当前段的类型。cmdsize当前段的大小。 我们主要看看cmd有哪些的类型, 这个至关重要。在这里我需要说明的是, 下面的命令并不代表全部,之所以要在这里列出的它们的原因是这些命令将在内核中被加载。 那么你可能会问, 那么其他的段命令呢?这个你和dyld去说好了。列表如下:

命令十六进制作用
LC_SEGMENT
LC_SEGMENT_64 
0x01/0x19 将这些段加载到对应的进程空间上去(区分32位和64位)
LC_LOAD_DYLINKER 0x0E 加载dyld, 值得注意的是,每个Mach-O文件只能有一个段
LC_UUID 0x1B 将UUID这个值保存到执行进程的上下文中,同样每个Mach-O文件只能有一个段
LC_THREAD 0x04 开启一个Mach线程, 但是不分配栈(这个不常见)
LC_UNIXTHREAD 0x05 开启一个UNIX线程,其实最主要的用途是告诉加载器当前主函数的位置.
这条命令在10.8之后被LC_MAIN取代。
LC_MAIN 0x80000028 在10.8之后代替LC_UNIXTHREAD, 告诉加载器当前主函数的位置
LC_CODE_SIGNATURE 0x1D 这个是数字签名段
LC_ENCRYPTION_INFO 0x21 加密二进制文件, 貌似在IOS下使用的比较频繁。
3.1.1.3 Mach-O数据区域

这一部分我们将在dyld的时候着重讲。现在先略过。

3.1.1.4 例子

我们查看一下Mac for QQ的二进制文件格式: 



首先, 我们查看一下QQ文件头, 我们发现这个文件是一个32位的Mach-O文件。文件类型是MH_EXEXUTE,也就是可执行文件。 这个执行文件一共有56个segment, 全部的segment的大小有6580.最后一个数据室flag数据, 可以看到这个可执行文件,在加载的时候必须使用ASLR的保护技术,除此之外还有一个标志位MH_NO_HEAP_EXECUTION,这个标志位是防止当前的可执行文件的数据部分有执行权限, 一旦有执行权限, 黑客可以进行所谓的“堆喷射攻击”。



在看第二张图片, 我们发现当前segment中LC_SEGMENTM可以存在多个;还有就像我们上面说的,对于LC_UUID, LC_MAIN, LC_LOAD_DYLINKER等段来说,有且仅有一个。
值得注意的是, 当前这个可执行文件中存在段LC_MAIN, 因此说明当前QQ的编译环境是Mac OSX 10.8+, 因为我们在表格中说过LC_MAIN是在10.8的时候引入的, 因此只有10.8或者更高版本的系统才能编译出这个二进制文件。



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值