Mach-O 了解一下

Mach-O为Mach object文件格式的缩写,它是一种用于可执行文件、目标代码、动态库的文件格式,由多个源文件组成。作为a.out格式的替代,Mach-O提供了更强的扩展性。
常见文件.a.dylibFramework,可执行文件,dyld,.dSYM(release)

这里值得注意的是nib虽然用file命令查看是data文件,但是仍被构建成动态链接共享库的。其本质就是伪nib,是一个.dylib文件。

image.png

C文件的编译链接实践

1、创建C文件,并编写相关代码,然后进行编译,生成test.o的目标文件。


查看目标文件,得到一个为Mach-Oobject文件。

file test.o

2、链接一下,生成a.out可执行文件。

链接器或链接编辑器是计算机实用程序,它负责接收由编译器生成的一个或多个目标文件,并将它们组合成单个可执行文件,库文件或者另一个"对象"文件。

Clang test.o


生成为test2的可执行文件:

clang -o test2 test.o 

通过执行可执行文件可得到打印结:

./a.out
//%只是一个结束符。
test===test===test===test===test%

探寻系统的dylib

通过命令:find /usr/lib -name "*.dylib"查看系统的动态库,下面列出常见的有:

#libobjc:objc和runtime
/usr/lib/libobjc.A.dylib
/usr/lib/libSystem.dylib

libSystem中常见的库有:

  • libdispatch ( GCD )
  • libsystem_c ( C语言库 )
  • libsystem_blocks ( Block )
  • libcommonCrypto ( 加密库,比如常用的 md5 函数 )

通过命令file /usr/lib/libobjc.dylib查看动态库信息:


通过cd /usr/lib进入dyld所在文件夹,file dyld查看其具体信息。可知它是Mach-O文件,但不是可执行文件,属于dynamic linker,Mach-O中的独立类型。

探寻dSYM文件。

将工程置于release模式下,Build


显示dSYM包内容,通过命令可知它也是属于Mach-O文件

浅谈可执行文件架构

通用二进制文件(UNIVERSAL BINARY):

通用二进制代码有两种基本类型。一种类型就是简单提供两种独立的二进制代码,一个用来对应x86架构,一个用来对应PowerPC架构。但是对于不熟悉代码的普通软件使用者来说,在购买和使用的时候,可能搞不清二者区别。另外一种类型就是只编写一个架构的代码,当另外一种处理环境时让系统自动调用模拟器运行。这会导致运行速度下降,一般是作为“通用二进制”或者“特别连编二进制”出现之前暂时使用的折衷办法。(参见Rosetta)
因为需要储存多种代码,通用二进制应用程序通常比单一平台二进制的程序要大,但是由于两种架构有共通的非执行资源,所以并不会达到单一版本的两倍之多。而且由于执行中只调用一部分代码,运行起来也不需要额外的内存。
目前,苹果公司的Xcode是唯一一个可以编译通用二进制代码的GUI工具

$(ARCHS_STANDARD):代表armv7arm64两种架构。

Xcode有关编译架构的设置

lipo命令=>拆分可执行文件


lipo命令=>合并可执行文件
image.png

使用lipo -info 可以查看MachO文件包含的架构
$lipo -info MachO文件
使用lipo –thin 拆分某种架构
$lipo MachO文件 –thin 架构 –output 输出文件路径
使用lipo -create  合并多种架构
$lipo -create MachO1   MachO2  -output 输出文件路径

分析Mach-O的可执行文件

分三大模块:

  • Header

header包含该二进制文件的一般信息
字节顺序、架构类型、加载指令的数量等。由系统内核负责读取。
使得可以快速确认一些信息,比如当前文件用于32位还是64位,对应的处理器是什么、文件类型是什么

  • Load Commands

一张包含了很多内容的表,内容包括区域的位置、符号表、动态符号表等。

  • Data

通常是对象文件中最大的部分,包含了Segement的具体数据

通过otool查看可执行文件的一些信息,根据提示命令获取所需要的信息。

    -f print the fat headers
    -a print the archive header
    -h print the mach header
    -l print the load commands
    -L print shared libraries used
    -D print shared library id name
    -t print the text section (disassemble with -v)
    -p   start dissassemble from routine name
    -s   print contents of se

查看Mach-O的源码文件

  • 定义的Mach-O文件:
#define    MH_OBJECT    0x1        /* relocatable object file */
#define    MH_EXECUTE    0x2        /* demand paged executable file */
#define    MH_FVMLIB    0x3        /* fixed VM shared library file */
#define    MH_CORE        0x4        /* core file */
#define    MH_PRELOAD    0x5        /* preloaded executable file */
#define    MH_DYLIB    0x6        /* dynamically bound shared library */
#define    MH_DYLINKER    0x7        /* dynamic link editor */
#define    MH_BUNDLE    0x8        /* dynamically bound bundle file */
#define    MH_DYLIB_STUB    0x9        /* shared library stub for static */
/*  linking only, no section contents */
#define    MH_DSYM        0xa        /* companion file with only debug */
/*  sections */
#define    MH_KEXT_BUNDLE    0xb        /* x86_64 kexts */
  • 定义的Header
struct mach_header_64 {
    uint32_t    magic;        //魔数:快速定位属于64位还是32位
    cpu_type_t    cputype;    //cpu 类型
    cpu_subtype_t    cpusubtype;    //cpu的具体类型
    uint32_t    filetype;    //文件类型,比如可执行文件。
    uint32_t    ncmds;       //load commands 的数量
    uint32_t    sizeofcmds;   //load command 的大小
    uint32_t    flags;        //表示二进制文件所支持的一些功能,和系统加载有关系。描述文件在编译、链接等过程中的信息,示例中的 MH_NOUNDEFS 表示文件中不存在未定义的符号,MH_DYLDLINK 表示文件要交由 DYLD 进一步处理,MH_TWOLEVEL 表示文件使用两级命名空间,MH_PIE 表示启用地址空间布局随机化。
    uint32_t    reserved;    //比32位多一个保留字段。
};

通过MachOView查看文件
1、映射:告诉对方二进制文件会映射到手机内存中会占用多大,下一块是谁。

segment组

_PAGEZERO:这个字段在文件中不存在的,在虚拟内存中是一块区域,不具备访问权限,专门来处理空指针。
VM Address:虚拟内存地址,4个G,根据CPU架构而来。
File Offset:文件偏移地址。

2、LC_DYLD_ INFO_ONLY:动态链接的相关信息。

它的 ONLY 后缀表明这是程序运行所必须的,如果链接器不支持,那么加载过程就会终止。

struct dyld_info_command {
    uint32_t   cmd;     /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */
    uint32_t   cmdsize;     /* sizeof(struct dyld_info_command) */
    uint32_t   rebase_off;  //重定向的偏移值。(ASLR)
    uint32_t   rebase_size; //重定向的大小。
    uint32_t   bind_off;    //绑定。可执行文件读到内存中会绑定一些数据。weak绑定,lazy绑定。
    uint32_t   bind_size;   /* size of binding info  */
    uint32_t   weak_bind_off; /* file offset to weak binding info   */
    uint32_t   weak_bind_size;  /* size of weak binding info  */
    uint32_t   lazy_bind_off; /* file offset to lazy binding info */
    uint32_t   lazy_bind_size;  /* size of lazy binding infs */
    uint32_t   export_off;  //对外开发的函数。
    uint32_t   export_size; /* size of lazy binding infs */
};

3、LC_SYMTABLC_DYSYMTAB,符号表地址和动态符号表地址。

记录了程序的符号表以及字符串表的偏移量及大小,符号表中记录了程序用到的函数以及全局变量的信息

/*
 * Format of a symbol table entry of a Mach-O file for 32-bit architectures.
 */
struct nlist {
    union {
#ifndef __LP64__
        char *n_name;   /* for use when in-core */
#endif
        uint32_t n_strx;    /* index into the string table */
    } n_un;
    uint8_t n_type;     /* type flag, see below */
    uint8_t n_sect;     /* section number or NO_SECT */
    int16_t n_desc;     /* see  */
    uint32_t n_value;   /* value of this symbol (or stab offset) */
};
/*
 * This is the symbol table entry structure for 64-bit architectures.
 */
struct nlist_64 {
    union {
        uint32_t  n_strx; /* index into the string table */
    } n_un;
    uint8_t n_type;        /* type flag, see below */
    uint8_t n_sect;        /* section number or NO_SECT */
    uint16_t n_desc;       /* see  */
    uint64_t n_value;      /* value of this symbol (or stab offset) */
};

数据结构中相关字段的含义都可以在 nlist.h 中找到,这里值得一说的是 n_un 字段,它用来记录符号的名字,但为什么是 uint32_t 类型呢?又为什么在注释中标明是 string table 的 索引呢?
这是因为在程序中,字符串的长度是不固定的,所以会将其放在 string table 中,然后存储它在 string table 中的偏移。如果其他部分想要引用某个字符串,那么他首先需要找到 string table 的起始地址,然后根据偏移量找到相应字符串的起始位置并向后读取字符,直到遇见 \0 才会停止读取过程,最后返回读到的字符串。
这也是 LC_SYMTAB 额外记录 string table 地址的原因,string table 通常用于记录 section 名、符号名等信息。

4、LC_LOAD_DYLINKER:动态连接器:dyld,在iPhone手机下的usr/lib/dyld来加载的。调用这个方法之前都是系统内核进行调用的。
5、LC_UUID:静态链接器为其生成的文件所提供的唯一标识符
6、LC_MAIN:指定 main 函数的地址
7、LC_LOAD_DYLIB:加载一些依赖库和三方库的地址。

补充:

  • DATA段有懒加载表和非懒加载表。
  • symbol:用于绑定的时候会用到这里的信息。
  • Mach-O文件被内核加载,被DYLD读取。

参考链接:

Mach-O文件格式参考
Mach-O文件格式分析

你可能感兴趣的:(Mach-O 了解一下)