iOS XNU - 进程及 Mach-O 格式

iOS XNU - 进程及 Mach-O 格式

本来小编想先把内存这块梳理下,但是联系到内存在 iOS 平台中也是 app 编译后的 Mach-O 可执行文件,所以就先把 OS 编译启动过程来讲述。

进程生命周期

因为小编是一位 iOSer 这里讲解进程可以大概理解为:进程 == App,下面给出进程的生命周期。进程的生命周期可以分为:创建(SIDL)、运行(SRUN)、睡眠(SSLEEP)、停止(SSTOP)、退出ing(SZONE)和终止(Dead)

process-lifecycle.png

创建(SIDL)

SIDL 这个状态是被父进程刚刚 Fork 创建生成一个唯一的 PID,处于一个临时空闲状态。此时让然被称为:“正在初始化”,不会响应任何的信号和操作。这个初始化的过程是在单线程中来进行内存布局设置和加载所需要的引来模块。
实现处理完成只有进程可以执行,同时不会返回 SIDL 状态。相当与用户在手机屏幕点击 ICON 按钮后执行。

运行(SRUN)

SRUN 如果在进一步来进行细分可以分为两种:

(1)可运行状态:当一个进程被添加到运行队列在等待时期,但是因为 CPU 忙于运行其他的 App 此时还没有加载改进程的寄存器时 既短暂状态
(2)运行状态:当 CPU 从运行队列中开始执行进程的寄存器,改 App 也就处于运行转台。

App(进程) 在执行过程中时间片用完或者是被更高优先级的进程占用时,此时会由 运行转态加入到可以运行队列中变为可运行状态。

iOSApp 启动的标志如下 == SRUN

//下面执行是在系统加载完 `Mach-O` 和 `dyld` 链接库之后执行 
class AppDelegate: UIResponder, UIApplicationDelegate {

      func application(_ applicatxion: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { }
      
      //每次 `App` 第一次加载或者从后台 --> 前台均会执行
      func applicationDidBecomeActive(_ application: UIApplication) { }

}

睡眠(SSLEEP)

SSLEEPApp 执行过程中可能出现等待某一个资源或者是进行后台工作,这时进程就不要使用 CPU 甚至不用进入 可运行队列 而是进行睡眠。当再次获取改资源或者进行前台 通常会重新加入到可运行队列,并且当在当前运行队列之后。

这里睡眠状态也可以在结束信号时间来处理:

pid_suspendpid_resume 可以实现 App 进行休眠和唤醒之间的切换。这个睡眠我们可以成为深度睡眠,这个操作是在我们说的 Mach 层面来实现的,可以玩限次来进行切换操作。
在手机中我们采用按 HOME 键就是基础此来实现,App 睡眠状态。

在应用程序从 SRUN 状态到我们所说的 SSLEEP 休眠状态过程中在 iOS 会执行如下操作:

可以文件 xxxx.plist 设置 UIApplicationExitsOnSuspend || Application does not run in background 设置对应的属性 true || false 来设置进入后台设置,在 App 中按下 Home 键 == SSLEEP

class AppDelegate: UIResponder, UIApplicationDelegate {
      //下面是在运行期间点击 `Home` 键,`App` 进行后台
      func applicationWillResignActive(_ application: UIApplication) {  }
      
      func applicationDidEnterBackground(_ application: UIApplication) { 
      //此位置如果有需求可以实现延迟进入后台
       }
      
      //从后台进入运行状态 
      func applicationWillEnterForeground(_ application: UIApplication) {  }
      
      func applicationDidBecomeActive(_ application: UIApplication) {  }
      
}

停止(SSTOP)

SSTOP 这个状态是通过一个特殊的信号TSTOP || TOSTOP来时正在进行的 App 停止运行,使程序来处在 深度睡眠状态。可以通过信号 CONT 来切为可运行状态来重新被调度。

退出ing(SZONE)

在程序执行完之后运行 exit() 来对程序进行退出。

iOS 中程序的关闭 == SZONE

//程序在关闭时 `iOS` 的函数调用 
class AppDelegate: UIResponder, UIApplicationDelegate {
      func applicationWillTerminate(_ application: UIApplication) {  }
}

终止(Dead)

在程序退出之后,也就是会终止该进程所有线程。在此之前会极端的时间出现一个僵尸状态。

僵尸状态: 进程的空壳,占用的资源全部释放,只是对应的 PID 还在占用。如果父类进行没预释放就交由父类线程,如果释放就交给其祖先进程(PID = 1) 可以理解为操作系统来进行收养。

可执行文件

特殊文件在内存中加载执行就是我们所见到的进程,执行过程中文件中会有一个签名来表明当前文件是否有效:“魔数”。
下面给出 OS X 中对应的魔数和可执行文件类型:

执行格式 魔数 用途
通用二进制格式 0xcafebabess(小尾顺序) 0xbebafeca(大尾顺序) 包含多种架构的二进制格式,目前只在 OS X 使用。 目前我们见过一些 Framework 的库可以在 Mac 的虚拟机上运行及支持 Inteli386等,同时也支持移动端的 ARM 的 32 位和 64 位高通处理器。包体偏大。
Mach-O 0xfeedface(32位) 0xfeedfacf(64位) OS X 原生二进制。

下面给出在 Fat(通用二进制格式) VS Mach 格式头文件:

fat-mach-header-info.png

胖二进制(Fat)

fat_headers

magic:固定值在上面给出 0xcafebabe,来表示通用二进制文件;
nfa_arch:当前通用二进制包含的架构数目。

fat_arch

cputype:定义的 CPU 类型;
cpusubtype:定义的机器标识符;
offset:架构的二进制代码在整个通用二进制文件中的偏移量;
magic:内层二进制代码大小;
magic:对其页边界(4k),表示 2 幂函数。

在通用二进制代码加载执行时,Mach 加载器会根据自己的架构加载适合架构代码。所以不相关的架构代码不用占用相关的内存,但是加载整个 IPA 包体会比较大。这里可以采用 lipo 来对二进制文件进行阉割,lipo阉割文件
通用二进制的镜像文件做了优化,对其页边界,内核只需加载第一页就可以读取文件。用这个文件当做目录加载合适的镜像。

Mach-O 二进制格式

mach_header

magic0xfeedface 表示 32 位二进制,0xfeedfacf 表示 64 位二进制文件;
cputype & cpusubtypeCPU 类型和子类型;
filetype:文件类型(可执行文件、库文件、核心转储文件、内核拓展等);
ncmds & sizeofncmds:用于加载器的 “加载命令” 的条目和大小;
flags:动态连接器的标志(dyld);
Reserved: 64 位预留。。。

我们在目前编译过程总采用 Bitcode 来进行编码实际就是生成 Mach-O 文件,然后在由 Apple Server 根据应用端的处理架构来在实现安装时生成对应架构的 Mach-O

Mach-O 加载进程

mach-o-loader.png

内核加载进程

(1) 非配虚拟内存 LC_SEGMENT || LC_SEGMENT_64

LC_SEGMENT || LC_SEGMENT_64 指导内核如何设置新运行进程的内存空间,__PAGEZERO段(空指针陷阱)、__TEXT段(程序代码)、__DATA段(程序数据) 和 __LINKEDIT段(连接器使用的字符和其他段) 直接从 Mach-O 二进制文件加载到内存中。

LC_SEGMENT || LC_SEGMENT_64 的参数:

参数 用途
segment load_segment
vmaddr 所描述端的虚拟物理地址
vmsize 为这个段分配的虚拟内存大小
fileoff 段在该文件的偏移量
filesize 表示段在文本中占用的字节数
maxport 段的页面所需的最高内存保护,采用 8 进制表示
initport 段的页面最初始内存保护
nsects 段中的区 section 数量
flags 杂项标志位

对于上面每一段,将文件中相应的内容加载到内存中:
从偏移量为 fileoff 处加载 filesize 字节到虚拟内存地址 vmaddr 处的 vmsize 字节。每段的页面都是根据 initport 进行初始化,initport 指定如何通过 读/写 执行初始化页面的保护级别。

段进一步的分解区:

用途

(2) 创建主线程 LC_MAIN

LC_MAIN:主要的作用就是设置程序的入口点地址和栈的大小,在设置的郭晨中除了程序的计数器之外所有寄存器都设置 0

(3) 代码签名 LC_CODE_SIGATURE

LC_CODE_SIGATURE: 包含了 Mach-O 二进制文件的签名,如果签名和代码本身不符合的话,内核就会立即给程序发送 SIGKILL 信号将程序杀掉。在后面苹果在 iOS 客户端采用 entitlement 机制后,代码签名就和沙盒机制绑定在一起,而且 entitlement 声明必须内嵌在 Mach-O 中并且通过签名盖章来设置执行安全敏感的操作时具有对应的权限。

(4) 加密 LC_ENCRYPTION_INFO

LC_ENCRYPTION_INFO加密二进制文件,在 iOS 中普遍使用。

(5) Dyld 加载 LC_LOAD_DYLINKER

LC_LOAD_DYLINKER 调用 Dyld(/user/lib/dyld) 来加载 dyld 如下。

Dyld 加载进程

在内核执行 LC_DYLINKER 来实现 Dyld 动态连接器来启动,然后就会把进程的控制权来交给 Dyld,因为内核会把进程的入口点设置为 Dyld 的入口点。
此时连接器 Dyld 就会在启动时因为进程采用动态链接在 Mach-O 镜像文件中 “空洞” 来进行填补。这个过程可以成为:符号绑定(binding

符号绑定具体实现过程

(1)二进制中使用外部定义的函数和符号,这时在文本段中会存在一个 __stubs(桩)的区,在这个桩区存放本地未定义的占位符。
(2)编译器生成相关代码时创建符号桩区调用,链接其在运行时会解决桩区的调用。
(3)连接器解决的方式是在被调用的地址处防止一条 JMP 指令,JMP 指令会将控制权交给真实的函数体,于此同时不会对栈有任何修改,在调用的过程就像直接调用真实的函数。
(4)在实际链接过程中 LC_LOAD_DYLB 告诉连接器从哪里找到对应的符号,连接器采用递归的方式加载每一个指定的库,并且搜索对应匹配的字符。链接的库有一个符号表,符号表将这些符号名称和地址关联起来。符号表可以通过 Mach-O 目标文件地址可以通过 LC_SYMTAB 加载命令指定的 symoff 找到

dyld 是一个用户态进程,是由苹果公司来进行维护。从内核的角度来看, dyld 是一个可插入的组件,也可以替换为第三方连接器。

Dyld 共享库

Dyld 的另一个机制就是共享库缓存,共享库缓存:一些库经过预先链接,然后保存在磁盘的一个文件中。采用共享库缓存的预加载模式,可以节省加载时间。在 iOS 3.0Apple 就开始把一些基础库移到次缓存,比如我们在开发中常用到的 UIKitFoundationQuartzAVFoundationStoreKit 等。

参考资料:
深入解析 Mac OS X & iOS 操作系统 第四章
iOS内存abort(Jetsam) 原理探究
iOS App 签名的原理

最后一次修改:

10月9日 02:09:45,广州天河🏠

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容