在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或者更高版本的系统才能编译出这个二进制文件。

http://blog.csdn.net/dongaxis/article/details/41114071

Mac-O文件加载的全过程(一)的更多相关文章

  1. 从输入URL到页面加载的全过程

    前面的话 本文将详细介绍从输入URL到页面加载的全过程 概述 从输入URL到页面加载的主干流程如下: 1.浏览器构建HTTP Request请求 2.网络传输 3.服务器构建HTTP Response ...

  2. 文件加载---理解一个project的第一步

    当我最开始写php的时候,总是担心这个问题:我在这儿new的一个class能加载到对应的类文件吗?毕竟一运行就报Fatal Error,什么**文件没找到,类无法实例化等等是一种很“低级”的错误,怕别 ...

  3. scrapy cookies:将cookies保存到文件以及从文件加载cookies

    我在使用scrapy模拟登录新浪微博时,想将登录成功后的cookies保存到本地,下次加载它实现直接登录,省去中间一系列的请求和POST等.关于如何从本次请求中获取并在下次请求中附带上cookies的 ...

  4. 前端设计中关于外部js文件加载的速度优化

    在一般情况下,许多人都是将<script>写在了<head>标签中,而许多浏览器都是使用单一的线程来加载js文件的,从上往下,从左往右. 若是加载过程出错,那么网页就会阻塞,就 ...

  5. php基础知识(3)(文件加载include)

    文件加载 综述: 有4个文件加载的语法形式(注意,不是函数): include,  include_once,  require, require_once; 他们的本质是一样的,都是用于加载/引入/ ...

  6. HTML5文件加载进度管理

    /** * 文件加载进度管理 */ DownloadUtils = function(options){ options = options || {}; this.init(options); }; ...

  7. js文件加载优化

    在js引擎部分,我们可以了解到,当渲染引擎解析到script标签时,会将控制权给JS引擎,如果script加载的是外部资源,则需要等待下载完后才能执行. 所以,在这里,我们可以对其进行很多优化工作. ...

  8. Java基础之Throwable,文件加载

    Java中的异常与错误都继承自Throwable,Exception又分为运行时异常(RuntimeException)和编译时异常. 运行时异常是程序的逻辑不够严谨或者特定条件下程序出现了错误,例如 ...

  9. 在IIS上新发布的网站,样式与js资源文件加载不到(资源文件和网页同一个域名下)

    在IIS上新发布的网站,网站能打开,但样式与js资源文件加载不到(资源文件和网页是同一个域名下,例如:网页www.xxx.com/index.aspx,图片www.xxx.com/pic.png). ...

随机推荐

  1. 脚本编写 nginx 启动

    #!bin/bash#功能:本脚本编写完成后,放置在/etc/init.d/目录下,就可以被 Linux 系统自动识别到该脚本.#如果本脚本命名为/etc/init.d/nginx,则 service ...

  2. switch 语句来选择要执行的多个代码块之一。

    switch(n) { case 1: 执行代码块 1 break; case 2: 执行代码块 2 break; default: n 与 case 1 和 case 2 不同时执行的代码 }

  3. UWP添加数字证书导出安装包本地安装

    先生成一个简单的HelloWorld应用程序 <Page x:Class="HelloWorld.MainPage" xmlns="http://schemas.m ...

  4. class一些内置方法

    一. __getattribute__ class Foo: def __init__(self,x): self.x=x def __getattr__(self, item): print('执行 ...

  5. css 垂直居中方法总结

    工作中遇到垂直居中问题,特此总结了一下几种方式与大家分享.本文讨论的垂直居中仅支持IE8+ 1.使用绝对定位垂直居中 HTML <div class="container"& ...

  6. struct 模块解决 TCP黏包问题

    首先来看一下产生黏包现象的一段代码: # server.py 服务端 import socket ​ sk = socket.socket() sk.bind(('127.0.0.1',9000)) ...

  7. BA-通讯总线-百通1419a和9841

    百通1419A线缆的简单介绍: Belden1419A- Belden电缆线1419A 多股导体—低容计算机电缆 FOR EIA RS-232/422 Belden 1419A是24 AWG(7*32 ...

  8. NEFU 84

    其实同POJ 1061 #include <iostream> #include <cstdio> #include <cstring> #include < ...

  9. 去哪网实习总结:JavaWeb中文传參乱码问题的解决(JavaWeb)

    本来是以做数据挖掘的目的进去哪网的.结构却成了系统开发... 只是还是比較认真的做了三个月.老师非常认同我的工作态度和成果... 实习立即就要结束了,总结一下几点之前没有注意过的变成习惯和问题,分享给 ...

  10. ubuntu14.04无法安装Curl

    ubuntu14.04无法安装Curl apt-get install curl 提示没有这个软件 源 更换软件源到163也不行,更新软件源也不行. 解决:參考http://www.linuxidc. ...