美文网首页
iOS 内存布局

iOS 内存布局

作者: _涼城 | 来源:发表于2022-03-31 17:54 被阅读0次
    Apple-macho.png

        现代操作系统的内存分配以页为单位进行管理,而页通过段进行管理,组成了段页式内存管理。对于一个典型的进程来说,它的内存空间是由哪些部分组成的?每个部分又被安置在空间的什么位置?

    抽象内存布局

    代码段

        CPU 运行一个程序,实质就是在顺序执行该程序的机器码。一个程序的机器码会被组织到同一个地方,这个地方就是代码段

    • 可执行指令
    • 在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

    数据段

        另外,程序在运行过程中必然要操作数据。这其中,对于有初值的变量,它的初始值会存放在程序的二进制文件中,而且,这些数据部分也会被装载到内存中,即程序的数据段

    • 数据段存放的是程序中已经初始化且不为 0 的全局变量和静态变量

    .data 可读/写 .rodata 只读

    BSS段

        对于未初始化的全局变量和静态变量,因为编译器知道它们的初始值都是 0,因此便不需要再在程序的二进制映像中存放这么多 0 了,只需要记录他们的大小即可,这便是BSS 段

        数据段和 BSS段里存放的数据也只能是部分数据,主要是全局变量和静态变量,但程序在运行过程中,仍然需要记录大量的临时变量,以及运行时生成的变量,这里就需要新的内存区域了,即程序的堆空间栈空间。与代码段以及数据段不同的是,堆和栈并不是从磁盘中加载,它们都是由程序在运行的过程中申请,在程序运行结束后释放。

    堆空间

        堆是向⾼地址扩展的数据结构,是不连续的内存区域。由代码分配和释放,手动申请空间,如果不释放,程序结束时,可能会由操作系统回收 。

    • 优点:
      灵活⽅便,数据适应⾯⼴泛
    • 缺点:
      效率有⼀定降低,容易产生碎片。

    栈空间

        栈是向低地址扩展的数据结构,由编译器⾃动分配并释放,存放函数的参数值,局部变量等。栈是系统数据结构,对应线程/进程是唯⼀的。

    • 优点:
      快速⾼效,不会产生碎片
    • 缺点:
      有限制,数据不灵活。(先进后出)

        总的来说,一个程序想要运行起来所需要的几块基本内存区域:代码段、数据段、BSS 段、堆空间和栈空间。下面就是内存布局的示意图:

    基本内存区域.png

        这是程序运行起来所需要的最小功能集,除了上面所讲的基本内存区域外,现代应用程序中还会包含其他的一些内存区域,主要有以下几类:

    • 存放加载的共享库的内存空间
      如果一个进程依赖共享库,那对应的,该共享库的代码段、数据段、BSS 段也需要被加载到这个进程的地址空间中。
    • 共享内存段
      我们可以通过系统调用映射一块匿名区域作为共享内存,用来进行进程间通信。
    • 内存映射文件
      我们也可以将磁盘的文件映射到内存中,用来进行文件编辑或者是类似共享内存的方式进行进程通信。

        这样我们就初步了解了一个进程内存中需要哪些区域。

    Section 与 Segment

        对于磁盘的程序,每一个单元结构称为 Section。对于内存镜像,每一个单元结构称为 Segment。往往多个 Section 会对应一个 Segment,例如 .text.rodata、等一些只读的 Section,会被映射到内存的一个只读 / 执行的 Segment 里;而 .data.bss 等一些可读写的 Section,则会被映射到内存的一个具有读写权限的 Segment 里。
         总的来说,Section 主要是指在磁盘中的程序段,而 Segment 则用来指代内存中的程序段,Segment 是将具有相同权限属性的 Section 集合在一起,系统为它们分配的一块内存空间
    下图展示了部分 Mach-O 文件结构,也展示了在 iOS 中 Section 和 Segment 的关系

    Section 和 Segment 的关系

    ASLR:内存空间布局随机化

        Mach-O 文件利用两种空间描述,来表达自己在 Mach-O 文件和虚拟内存中不同空间的分配方式。每个 App 都有自己独立的虚拟内存,这个虚拟内存只存在与自己的 Mach-O 文件的 load commands 的描述中。当 App 执行时,会被系统映射到实际的物理内存中。程序一旦编译完成,函数就安静的放在了 __TEXT 段, 全局变量就安静的放在了 __DATA 段上。等用户点击后,才被加载到内存。
        在 iOS 逆向中,通过 Hopper 工具反汇编可以看到函数的虚拟内存地址,但是 Hopper 中展示的内存地址是没有 ASLR 的。想要得到函数的真正函数地址,还需要得到当前 Mach-O 文件在内存中的 ASLR 偏移值。当 App 被加载到内时,系统会自动进行 ASLR ,在 __PAGEZERO 段的上面随机多出一段空间作为偏移,使得 Mach-O 文件的整个虚拟内存向下整体(包括堆,栈,共享库映射等线性布局)偏移。从而可以让生成的函数内存地址不断变动。这样可以提高黑客的破解难道。

    那如何得到当前 Mach-O 文件的 ASLR 偏移值呢?

        通过 lldb 调试器的 Mach-O 文件列表查询命令,-o 查询所有使用的 Mach-O 文件包括 dyld 链接编辑器、App 的 Mach-O 文件、dylib 库,得到的第一个结果就是所需要的 ASLR 偏移值。

    image list -o -f
    

    APP 的进程内存布局

        一般来说,进程的内存布局是相似的。在iOS App 中是通过 Mach-O 文件从而在虚拟内存中逻辑结构和布局的。关于 Mach-O 可以查看了解 Mach-O 文件Mach-O 文件中不同的内容段 __TEXT__DATA 等在虚拟内存中的内存分布是用 VM AddressVM Size 来描述的;在 Mach-O 本地文件中的空间分布是用 File OffsetFile Size 来描述的

    通过代码输出变量及常量的地址

    int a = 10;                   //已初始化全局变量
    int b;                        //未初始化全局变量
    const int testConst =  20; //const 常量
    
    int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {
            static int c = 20;    //已初始化静态变量
             
             static int d;         //未初始化静态变量
             
             int e;                //未初始化局部变量
    
             int f = 20;           //已初始化局部变量
    
             NSString *str = @"123";//字符串常量
             
             NSObject *obj = [[NSObject alloc] init];//通过alloc动态分配(实例对象)
             
             NSLog(@"\n&a=%p\n&b=%p\n&c=%p\n&d=%p\n&e=%p\n&f=%p\nstr=%p\nobj=%p\n&testConst=%p",
                   &a, &b, &c, &d, &e, &f, str, obj,&testConst);
            // Setup code that might create autoreleased objects goes here.
            appDelegateClassName = NSStringFromClass([AppDelegate class]);
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    
    

    通过输出地址以及 image list -o -f 查看到的 dylibdyld 地址信息,得到下面 iOS 内存布局的示意图

    iOS 内存布局的示意图

    相关文章

      网友评论

          本文标题:iOS 内存布局

          本文链接:https://www.haomeiwen.com/subject/kpbgjrtx.html