安全防护

iOS 启动优化原理

2021-06-25  本文已影响0人  木扬音

1、虚拟内存 & ASLR

在早期计算机中数据是直接通过物理地址访问的,这就造成了下面两个问题

内存不够 --- > 虚拟内存

虚拟内存就是通过创建一张物理地址和虚拟地址的映射表来管理内存,提高了CPU利用率,使多个进程可以同时/按需加载

数据安全 --- > ASLR

因为虚拟内存的起始地址(0x000000)和大小(4G)是固定的,这就意味着我们的数据地址也是固定的,所以在iOS4.3引进了ASLR
ASLR:英文全称Address Space Layout Randomization,也叫地址空间配置随机加载,是一种针对缓冲区溢出安全保护技术。通过对堆、栈、共享库映射等线性区布局的随机化,增强了攻击者找到目标物理内存的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。
由于ASLR的存在,导致可执行文件和动态链接库在虚拟内存中的地址每次都是不固定的,所以需要在编译时来修复镜像中的资源指针,来指向正确的地址。即正确的内存地址 = ASLR地址 + 偏移值

2、Mach-O

Mach-O文件是Mach Object文件格式的缩写,它是用于可执行文件、动态库、目标代码的文件格式。作为a.out格式的替代,Mach-O格式提供了更强的扩展性,以及更快的符号表信息访问速度
我们可以通过工具MachOView来查看Mach-O文件具体信息

对于OS X 和iOS来说,Mach-O是其可执行文件的格式,主要包括以下几种文件类型

Mach-O文件格式

Mach-O镜像文件

一个完整的Mach-O文件主要分为三大部分:

Header

Header中包含了整个Mach-O文件中的关键信息,可以使CPU快速知道整个Mach-O的基本信息,决定了一些基础架构、系统类型、指令条数等信息。针对32位和64位架构的cpu,分别使用了mach_headermach_header_64结构体来描述Mach-O头部,mach_header_64结构体相比于mach_header只是多了一个reserved保留字段

/*
 - magic:0xfeedface(32位) 0xfeedfacf(64位),系统内核用来判断是否是mach-o格式
 - cputype:CPU类型,比如ARM
 - cpusubtype:CPU的具体类型,例如arm64、armv7
 - filetype:由于可执行文件、目标文件、静态库和动态库等都是mach-o格式,所以需要filetype来说明mach-o文件是属于哪种文件
 - ncmds:sizeofcmds:LoadCommands加载命令的条数(加载命令紧跟header之后)
 - sizeofcmds:LoadCommands加载命令的大小
 - flags:标志位标识二进制文件支持的功能,主要是和系统加载、链接有关
 - reserved:保留字段
 */
struct mach_header_64 {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
    uint32_t    reserved;   /* reserved */
};

filetype主要记录Mach-O的文件类型,常用的有以下几种

#define MH_OBJECT   0x1     /* 目标文件*/
#define MH_EXECUTE  0x2     /* 可执行文件*/
#define MH_DYLIB    0x6     /* 动态库*/
#define MH_DYLINKER 0x7     /* 动态链接器*/
#define MH_DSYM     0xa     /* 存储二进制文件符号信息,用于debug分析*/
MachOView中的Header
Load Commands

Load Commands主要用于加载指令,例如动态链接器的位置、程序的入口、依赖库的信息、代码的位置、符号表的位置等等。其大小和数目在Header中已确定,在Mach.h中的定义如下

/*
 load_command用于加载指令
 - cmd 加载命令的类型
 - cmdsize 加载命令的大小
 */
struct load_command {
    uint32_t cmd;       /* type of load command */
    uint32_t cmdsize;   /* total size of command in bytes */
};
Mach-O文件中Load Commands

其中LC_SEGMENT_64的类型segment_command_64定义如下

/*
 segment_command 段加载命令
 - cmd:表示加载命令类型,
 - cmdsize:表示加载命令大小(还包括了紧跟其后的nsects个section的大小)
 - segname:16个字节的段名字
 - vmaddr:段的虚拟内存起始地址
 - vmsize:段的虚拟内存大小
 - fileoff:段在文件中的偏移量
 - filesize:段在文件中的大小
 - maxprot:段页面所需要的最高内存保护(4 = r,2 = w,1 = x)
 - initprot:段页面初始的内存保护
 - nsects:段中section数量
 - flags:其他杂项标志位
 
 - 从fileoff(偏移)处,取filesize字节的二进制数据,放到内存的vmaddr处的vmsize字节。(fileoff处到filesize字节的二进制数据,就是“段”)
 - 每一个段的权限相同(或者说,编译时候,编译器把相同权限的数据放在一起,成为段),其权限根据initprot初始化。initprot指定了如何通过读/写/执行位初始化页面的保护级别
 - 段的保护设置可以动态改变,但是不能超过maxprot中指定的值(在iOS中,+x和+w是互斥的)
 */
struct segment_command_64 { /* for 64-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT_64 */
    uint32_t    cmdsize;    /* includes sizeof section_64 structs */
    char        segname[16];    /* segment name */
    uint64_t    vmaddr;     /* memory address of this segment */
    uint64_t    vmsize;     /* memory size of this segment */
    uint64_t    fileoff;    /* file offset of this segment */
    uint64_t    filesize;   /* amount to map from the file */
    vm_prot_t   maxprot;    /* maximum VM protection */
    vm_prot_t   initprot;   /* initial VM protection */
    uint32_t    nsects;     /* number of sections in segment */
    uint32_t    flags;      /* flags */
};
Data

Data主要用于存储具体的只读、可读写代码,例如方法、符号表、字符表、代码数据、重定向/符号绑定等。其中大多数的Mach-O文件均包含以下三个段:

Data区中,section占很大比例,Section在Mach.h中是以结构体section_64(在arm64架构下)表示,其定义如下

/*
 Section节在MachO中集中体现在TEXT和DATA两段里.
 - sectname:当前section的名称
 - segname:section所在的segment名称
 - addr:内存中起始位置
 - size:section大小
 - offset:section的文件偏移
 - align:字节大小对齐
 - reloff:重定位入口的文件偏移
 - nreloc:重定位入口数量
 - flags:标志,section的类型和属性
 - reserved1:保留(用于偏移量或索引)
 - reserved2:保留(用于count或sizeof)
 - reserved3:保留
 */

struct section_64 { /* for 64-bit architectures */
    char        sectname[16];   /* name of this section */
    char        segname[16];    /* segment this section goes in */
    uint64_t    addr;       /* memory address of this section */
    uint64_t    size;       /* size in bytes of this section */
    uint32_t    offset;     /* file offset of this section */
    uint32_t    align;      /* section alignment (power of 2) */
    uint32_t    reloff;     /* file offset of relocation entries */
    uint32_t    nreloc;     /* number of relocation entries */
    uint32_t    flags;      /* flags (section type and attributes)*/
    uint32_t    reserved1;  /* reserved (for offset or index) */
    uint32_t    reserved2;  /* reserved (for count or sizeof) */
    uint32_t    reserved3;  /* reserved */
};
MachOView中TEXT和DATA 常见section

3、优化建议

启动的过程一般是指点击App图标开始到AppDelegate 的didFinishLaunching

启动优化一般指在冷启动情况下主要分为两部分:

main函数之前

在之前的文章已经了解过dyld加载流程
我们可以通过Edit Scheme -> Run -> Arguments ->Environment Variables->点击+添加环境变量DYLD_PRINT_STATISTICS == 1

检测启动时间 pre-main启动时间
上图中pre-main阶段总共用时1.7s
优化

main函数开始后

上一篇 下一篇

猜你喜欢

热点阅读