- Mach-O文件概述:
-
Mach-O(Mach Object)
是macOS
、iOS
、iPadOS
存储程序和库的文件格式。对应系统通过应用二进制接口(application binary interface)
,缩写为ABI来运行该格式的文件。 -
Mach-O
格式用来替代BSD(Berkeley Software Distribution:伯克利软件套件)Unix
衍生系统的a.out
格式。Mach-O
文件格式保存在编译过程和链接过程中产生的机器代码和数据,从而为静态链接和动态链接的代码提供了单一文件格式。 - 段之前始终是
4096
字节或4KB
的倍数,其中4096
字节是最小大小。现在段是16KB
的倍数,在macOS_x86_64
上是16k
,在iOS
上是32k。
Mach-O
文件格式:
- 一个
Mach-O
文件有两部分组成:header
和data
。 -
header
: 代表了文件的映射,描述了文件的内容以及文件所有内容所在的目录。 -
Mach header
属于header
的一部分,它包含了整个文件的信息和segment
信息。 -
data
:紧跟header
之后,由多个二进制组成,一个接着一个。
Load Commands
:
- 进制文件加载进内存要执行的一些指令。
- 这里的指令主要在负责我们
App
对应进程的创建和基本设置(分配虚拟内存,创建主线程,处理代码签名/加密的工作),然后对动态链接库(.dylib系统库和我们自己创建的动态库)进行库加载和符号解析工作。 -
Segemnts(segment commands)
:指定操作系统应该将Segment
加载到内存中的什么位置,以及为该Segments
分配的字节数。还指定文件中的哪些字节属于该Segments
,以及文件包含多少sections
。Mac
始终是4096kb
或4kb
的倍数,其中4096
是最小大小。iOS最小是8kb。Segments
名称的约定是使用全大写字母,后跟双下划线(例如 __TEXT). -
Section
: 所有sections
都在每个segment
之后一个接一个的描述。sections
里面定义其名称,在内存中的地址,大小,文件中section
数据的偏移量和segment
名称。Section
的名称约定是使用全小写字母,再加上双下划线(例如 __text)。 -
__TEXT
: 只读区域:包含可执行代码和常量数据。 -
__DATA
: 读/写:包含初始化、未初始化数据和一些动态链接专属数据。
- 可执行文件调用过程(官方描述)
- 调用‘fork’函数,创建一个‘process’
- 调用’execve’或其衍生函数,在该进程上加载,执行我们的‘Mach-O’文件。
- 当我们调用时‘execve’(程序加载器),内核实际上在执行以下操作。
- 将文件加载到内存。
- 开始分析‘Mach-O’中的‘mach_header’,以确认它是有效的‘Mach-O’文件。
要了解Mach-O文件、
- 我们先创建一个项目–>
- 然后写上一句输出日志代码–>
- 运行之后–>
- 在Products文件夹下–>
- Show In Finder–> 显示包内容–>
- 拿到Unix可执行文件(这里我生成的为MachOFile)。
- Mach-O文件 我们这样理解 == 文件配置 + 二进制代码
- 拿到的Mach-O文件、我们在终端这样操作: 拿到
Load Command
//查看mach-header
$ objdump --macho --private-headers MachOFile
MachOFile:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC_64 X86_64 ALL 0x00 EXECUTE 27 3160 NOUNDEFS DYLDLINK TWOLEVEL PIE
Load command 0
cmd LC_SEGMENT_64
cmdsize 72
segname __PAGEZERO
vmaddr 0x0000000000000000
vmsize 0x0000000100000000
.......
//查看 __TEXT
$ objdump --macho -d MachOFile
MachOFile:
(__TEXT,__text) section
-[ViewController viewDidLoad]:
100001e50: 55 pushq %rbp
100001e51: 48 89 e5 movq %rsp, %rbp
100001e54: 48 83 ec 30 subq $48, %rsp
100001e58: 48 89 7d f8 movq %rdi, -8(%rbp)
100001e5c: 48 89 75 f0 movq %rsi, -16(%rbp)
100001e60: 48 8b 45 f8 movq -8(%rbp), %rax
100001e64: 48 89 45 e0 movq %rax, -32(%rbp)
100001e68: 48 8b 05 49 75 00 00 movq 30025(%rip), %rax ## Objc class ref: ViewController
100001e6f: 48 89 45 e8 movq %rax, -24(%rbp)
100001e73: 48 8b 35 0e 75 00 00 movq 29966(%rip), %rsi ## Objc selector ref: viewDidLoad
100001e7a: 48 8d 7d e0 leaq -32(%rbp), %rdi
100001e7e: e8 9b 05 00 00 callq 0x10000241e ## Objc message: -[[%rdi super] viewDidLoad]
100001e83: 48 8b 05 16 75 00 00 movq 29974(%rip), %rax ## Objc class ref: _OBJC_CLASS_$_AFURLSessionManager
100001e8a: 48 89 c7 movq %rax, %rdi
100001e8d: e8 98 05 00 00 callq 0x10000242a ## symbol stub for: _objc_opt_new
100001e92: 48 8d 0d 7f 21 00 00 leaq 8575(%rip), %rcx ## Objc cfstring ref: @"Watch a Mach-O file %@"
.......
//还可以通过 otool -h MachOFile查看 mach-header 少量信息
$ otool -h MachOFile
MachOFile:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
0xfeedfacf 16777223 3 0x00 2 27 3160 0x00200085
- 由于内容过多、那么我们可以通过附加参数来获取我们想要的信息、
- 先执行下
ag --version
命令、查看下自己是否安装了ag
。如果没有的话brew install ag
即可。
$ ag --version
ag version 2.2.0
Features:
+jit +lzma +zlib
- 我们查看下主程序的
Load Command
信息:
$ objdump --macho --private-headers MachOFile | ag 'LC_MAIN'
cmd LC_MAIN
//附加搜索条件、-A 在当前之后输出3行即可获得其全部信息、可以搜索更多行、会返回其他Load Command信息 (在当前搜索结果之前搜索为 -B )
$ objdump --macho --private-headers MachOFile | ag 'LC_MAIN' -A 3
cmd LC_MAIN
cmdsize 24
entryoff 8416
stacksize 0
- 为了简化我们的输出及更深刻理解Mach-O文件、下面我们借助于一个machoinfo 来查看信息
- 编译运行、拿到可执行文件、此时我们就可以直接使用其中的命令了。
- 进入含有
MachOFile
可执行文件的文件夹中、执行命令
$ ./machoinfo MachOFile
x86_64 //架构类型
LC: 0 segname: __PAGEZERO vmaddr: 0 nsects: 0 //捕捉空指针引用。通过在存储器的开头保留一个大的内存部分,通过一个空指针的任何访问都会被捕获并申请中止。
LC: 1 segname: __TEXT vmaddr: 100000000 nsects: 9 //代码段
LC: 2 segname: __DATA_CONST vmaddr: 100004000 nsects: 5 //数据常量段
LC: 3 segname: __DATA vmaddr: 100008000 nsects: 8 //数据段
LC: 4 segname: __LINKEDIT vmaddr: 10000c000 nsects: 0 //SEG_LINKEDIT : 包含所有结构的段;由链接编辑器创建和维护。
LC: 8 LC_LOAD_DYLINKER dyld: /usr/lib/dyld //动态链接器
LC: 9 LC_UUID uuid 3D45E7A9-EE30-33C7-A6FE-7BFA717A58C9 //UUID
LC: 10 LC_BUILD_VERSION platform: iossimulator, sdk: 14.3, minos: 14.3, ntools: 1 //平台信息
LC: 11 LC_SOURCE_VERSION version: 0.0 //用于构建二进制文件的源代码版本
LC: 12 LC_MAIN entryoff: 8416 stacksize: 0 //主函数入口
LC: 13 LC_LOAD_DYLIB dylid: @rpath/AFNetworking.framework/AFNetworking //加载动态链接的共享库
LC: 14 LC_LOAD_DYLIB dylid: /System/Library/Frameworks/ImageIO.framework/ImageIO
LC: 15 LC_LOAD_DYLIB dylid: @rpath/SDWebImage.framework/SDWebImage
LC: 16 LC_LOAD_DYLIB dylid: /System/Library/Frameworks/Foundation.framework/Foundation
LC: 17 LC_LOAD_DYLIB dylid: /usr/lib/libobjc.A.dylib
LC: 18 LC_LOAD_DYLIB dylid: /usr/lib/libSystem.B.dylib
LC: 19 LC_LOAD_DYLIB dylid: /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
LC: 20 LC_LOAD_DYLIB dylid: /System/Library/Frameworks/UIKit.framework/UIKit
LC: 21 LC_RPATH path: @executable_path/Frameworks //添加运行路径
LC: 22 LC_RPATH path: @loader_path/Frameworks
LC: 23 LC_RPATH path: @executable_path/Frameworks
LC: 24 LC_FUNCTION_STARTS cmdsize: 16 dataoff: 50432 datasize: 24 //函数起始地址的压缩表
LC: 25 LC_DATA_IN_CODE cmdsize: 16 dataoff: 50456 datasize: 0 //__text中的非指令表
LC: 26 LC_CODE_SIGNATURE cmdsize: 16 dataoff: 60160 datasize: 19200 //本地代码签名
- 如此可见、这样输出的
Mach-O
文件信息比较简洁、比刚才用objdump
输出可视化的多,而且输出结果基本一致、Load Command
一共有26个、也输出了动态链接库路径。 - 还可以直接在
- machinfo项目中–>
- Edit Scheme -->
- Arguments -->
- Arguments Passed On Launch -->
- 将可执行文件路径放置其中,运行后控制台得到输出信息。
- 配置方式
- 运行后输出结果
- 下面我们来探索编译与链接过程
- 编译的过程就是在将我们的代码放到对应的
page
中:全局符号、本地符号、外部符号、本地符号,在编译过程中进行分 类。 - 链接的本质就是将多个目标文件组合成一个文件、多个目标文件的符号表合并到一起。
- 接下来我们来看一下符号表
- Symbol Table就是用来保存符号的表
- String Table 就是用来保存符号的名称
- Indirect Symbol Table:间接符号表。保存使用的外部符号。更准确一点就是使用的外部动态库的符号,比如NSLog符号。是Symbol Table 的子集。
- 下面我们通过案例来理解符号
- 定义一个 VisibilitySymbol.h文件来测试我们符号的可见性。
extern int hidden_y;
extern double default_y;
extern double protected_y;
-
VisibilitySymbol.m
文件中实现部分
#import "VisibilitySymbol.h"
// visibility属性,控制文件导出符号,限制符号可见性
/**
-fvisibility:clang参数
default:用它定义的符号将被导出。
hidden:用它定义的符号将不被导出。
*/
int hidden_y __attribute__((visibility("hidden"))) = 99;
double default_y __attribute__((visibility("default"))) = 100;
double protected_y __attribute__((visibility("protected"))) = 120;
- 定义一个
WeakImportSymbol.h
文件来声明一个弱导入的符号
void weak_import_function(void) __attribute__((weak_import));
-
WeakImportSymbol.m
中这样实现
#import "WeakImportSymbol.h"
#import <Foundation/Foundation.h>
void weak_import_function(void) {
NSLog(@"weak_import_function");
}
- 在
WeakSymbol.h
定义弱引用及弱定义符号
void weak_function(void) __attribute__((weak));
void weak_hidden_function(void) __attribute__((weak, visibility("hidden")));
- 在
WeakSybol.m
中这样实现
#import "WeakSymbol.h"
#import <Foundation/Foundation.h>
void weak_function(void) {
NSLog(@"weak_function");
}
void weak_hidden_function(void) {
NSLog(@"weak_hidden_function");
}
- 我们在main.m中设置如下变量
//未初始化全局变量
int global_uninit_value;
//初始化全局变量
int global_init_value = 10;
double default_x __attribute__((visibility("hidden")));
//局部静态初始化变量
static int static_init_value = 99;
//局部静态未初始化变量
static int static_uninit_value;
int main(int argc, const char * argv[]) {
static_uninit_value = 10;
NSLog(@"%d",static_init_value);
return 0;
}
- 为了结果可视化、我们将运行结果直接输出到终端上。
- 创建xcconfig文件,文件中我们配置一个变量
HOST_URL = 192.168.0.1
,并在PROJECT
中Debug
模式下配置该xcconfig
文件–> - 在终端上输入
tty
获取当前终端编号/dev/ttys000
--> - 在当前
Target -- Build Phases --> Run Script
输出当前定义的变量值 -->
echo "${HOST_URL}" > /dev/ttys000
- 运行后,当前终端可见输出日志:
192.168.0.1
- 因此我们可以衍生为通过
Run Script
输出要执行的命令,并且显示输出结果。 - 在
xcconfig
中我们这样设置、最终实现通过命令获取运行后的可执行文件的Mach-O
信息
//CMD 运行的命令
//CMD_FLAG 运行的命令参数
//TTY 终端
//$SRCROOT 项目根目录
//nm -pa /machinfo
//-p 不排序
//-a 显示所有符号,包含调试符号
MACH_PATH = ${BUILD_DIR}/${CONFIGURATION}/${EFFECTIVE_PLATFORM_NAME}/*
CMD = nm
CMD_FLAG = -pa ${MACH_PATH}
TTY = /dev/ttys000
- 将脚本放置进项目根目录、并添加上可执行权限。
Build Phases
中,添加Run Script
输入下方命令。
/bin/sh "$SRCROOT/xcode_run_cmd.sh"
- 运行后可获取当前可执行文件输出信息:部分如下
- 由于信息太多、我们可以进行输出优化、输出结果中不显示调试信息、在xcconfig文件中我们使用这样的标志符。
// -Xlinker 表示要传递给链接器ld的参数
OTHER_LDFLAGS = -Xlinker -S
- 此时此刻、输出信息为
- Symbol Table
通过两个Load Commands
-
LC_SYMTAB
:当前Mach-O
中的符号信息。 -
LC_DYSYMTAB
:描述动态链接器使用其他的Symbol Table
信息 - 用来描述
Symbol Table
的大小和位置,以及其他元数据。
LC_SYMTAB - 用来描述该文件的符号表。不论是静态链接器还是动态链接器在链接此文件时,都要使用
load command
。调试器也可以使用该load command
找到调试信息。symtab_command
- 定义
LC_SYMTAB
加载命令具体属性。在/usr/include/mach-o/loader.h
中定义:
#import <mach-o/loader.h>
struct symtab_command {
//共有属性。指明当前描述的加载命令,当前被设置为LC_SYMTAB
uint32_t cmd; /* LC_SYMTAB */
//共有属性。指明加载命令的大小,当前被设置为sizeof(symtab_command)
uint32_t cmdsize; /* sizeof(struct symtab_command) */
//表示从文件开始到symbol table所在位置的便宜量。symbol table用 [nlist]来表示
uint32_t symoff; /* symbol table offset */
//符号表内符号的数量
uint32_t nsyms; /* number of symbol table entries */
//表示从文件开始到string table所在位置的偏移量。
uint32_t stroff; /* string table offset */
//表示string table大小(以byte为单位)
uint32_t strsize; /* string table size in bytes */
};
- nlist 定义符号的具体表示含义
#import <mach-o/nlist.h>
struct nlist {
//表示该符号在string table的索引
union {
#ifndef __LP64__
//在Mach-O中不使用此字段
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 <mach-o/stab.h> */
uint32_t n_value; /* value of this symbol (or stab offset) */
};
- 用于64位体系结构的符号表条目结构如下:
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 <mach-o/stab.h> */
uint64_t n_value; /* value of this symbol (or stab offset) */
};
n_type
: 1字节,通过四位掩码保存数据:
-
N_STAB(0xe0)
:如果当前的n_type
包含这3位中的任何一位,则该符号为 调试符号表(stab)。在这中情况下,整个n_type
字段将被解释为stab value
。 -
N_PEXT(0x10)
:如果当前的n_type
包含此位。则将此符号标记位私有外部符号__private_extern__(visibility=hidden)
,只在程序内可引用和访问。当文件通过静态链接器链接时,不要将其转换成静态符号(可以通过ld
的-keep_private_externs
关闭静态链接器的这种行为)。 -
N_TYPE(0x0e)
: 如果当前的n_type
包含此位。则使用预先定义的符号类型。 -
N_EXT(0x01)
:如果当前n_type
包含此位。则此符号为外部符号,该符号在该文件外部定义或在该文件中定义,但可以在其他文件中使用。
- N_TYPE 字段值包括:
-
N_UNDF(0x0)
: 该符号未定义。未定义符号是在当前模块中引用,但是被定义在其他模块中的符号。n_sect字段设置为NO_SECT
。 -
N_ABS(0x2)
: 该符号是绝对符号。链接器不会更改绝对符号的值。n_sect
字段设置为NO_SECT.
-
N_SECT(0xe)
:该符号在n_sect
中指定的段号中定义。 -
N_PBUD(0xc)
:该符号定义为与另一个符号相同。n_value
字段是string table
中的索引,用于指定另一个符号的名称。链接该符号时,此符号和另一个符号都具有相同的定义类型和值。
-
stab value
包括:
#import <mach-o/stab.h>
#define N_GSYM 0x20 /*全局符号 global symbol: name,,NO_SECT,type,0 */
#define N_FNAME 0x22 /*程序名称 procedure name (f77 kludge): name,,NO_SECT,0,0 */
#define N_FUN 0x24 /*方法/函数名称 procedure: name,,n_sect,linenumber,address */
#define N_STSYM 0x26 /*静态符号 static symbol: name,,n_sect,type,address */
#define N_LCSYM 0x28 /*.lcomm符号 .lcomm symbol: name,,n_sect,type,address */
#define N_BNSYM 0x2e /*nsect符号开始 begin nsect sym: 0,,n_sect,0,address */
#define N_AST 0x32 /*语法树文件路径 AST file path: name,,NO_SECT,0,0 */
#define N_OPT 0x3c /*用gcc2_compiled和gcc源发出 emitted with gcc2_compiled and in gcc source */
#define N_RSYM 0x40 /*寄存器符号:register sym: name,,NO_SECT,type,register */
#define N_SLINE 0x44 /*代码行数: src line: 0,,n_sect,linenumber,address */
#define N_ENSYM 0x4e /*nsect符号结束: end nsect sym: 0,,n_sect,0,address */
#define N_SSYM 0x60 /*结构体符号 structure elt: name,,NO_SECT,type,struct_offset */
#define N_SO 0x64 /*源码名称: source file name: name,,n_sect,0,address */
#define N_OSO 0x66 /*目标代码名称: object file name: name,,(see below),0,st_mtime historically N_OSO set n_sect to 0.*/
/* The N_OSO n_sect may instead hold the low byte of the cpusubtype value from the Mach-O header. */
#define N_LSYM 0x80 /*本地符号: local sym: name,,NO_SECT,type,offset */
#define N_BINCL 0x82 /*include文件开始: include file beginning: name,,NO_SECT,0,sum */
#define N_SOL 0x84 /*include文件名称: #included file name: name,,n_sect,0,address */
#define N_PARAMS 0x86 /*编译器参数: compiler parameters: name,,NO_SECT,0,0 */
#define N_VERSION 0x88 /*编译器版本: compiler version: name,,NO_SECT,0,0 */
#define N_OLEVEL 0x8A /*编译器-O级别 compiler -O level: name,,NO_SECT,0,0 */
#define N_PSYM 0xa0 /*参数: parameter: name,,NO_SECT,type,offset */
#define N_EINCL 0xa2 /*include文件结束: include file end: name,,NO_SECT,0,0 */
#define N_ENTRY 0xa4 /*可选程序入口 alternate entry: name,,n_sect,linenumber,address */
#define N_LBRAC 0xc0 /*左括号 left bracket: 0,,NO_SECT,nesting level,address */
#define N_EXCL 0xc2 /*删除include文件 deleted include file: name,,NO_SECT,0,sum */
#define N_RBRAC 0xe0 /*右括号: right bracket: 0,,NO_SECT,nesting level,address */
#define N_BCOMM 0xe2 /*通用符号开始: begin common: name,,NO_SECT,0,0 */
#define N_ECOMM 0xe4 /*通用符号结束: end common: name,,n_sect,0,0 */
#define N_ECOML 0xe8 /*本地通用符号结束: end common (local name): 0,,n_sect,0,address */
#define N_LENG 0xfe /*带有长度信息的第二个stab 入口 second stab entry with length information */
/*
* for the berkeley pascal compiler, pc(1):
*/
#define N_PC 0x30 /*全局pascal符号 global pascal symbol: name,,NO_SECT,subtype,line */
n_sect
: 整数,用来指定编号的section
中找到此符号;如果在该image
的任何部分都找不到该符号,则为NO_SECT
.根据seciton
在LC_SEGMENT
加载命令中出现的顺序,这些section
从1开始连续编号。
n_desc: 16-bit值,用来描述非调试符号。低三位使用REFERENCE_TYPE:
-
REFERENCE_FLAG_UNDEFINED_NON_LAZY(0x0):
该符号是外部非延迟(数据)符号的引用。 -
REFERENCE_FLAG_UNDEFINED_LAZY(0x1)
:该符号是外部延迟性符号(即对函数调用)的引用。 -
REFERENCE_FLAG_DEFINED(0x2)
: 该符号在该模块中定义。 -
REFERENCE_FLAG_PRIVATE_DEFINED(0x3)
:该符号在该模块中定义,但是仅对该共享库中的模块可见。 -
REFERENCE_FLAG_PRIVATE_UNDEFINED_NON_LAZY(0x4)
:该符号在该文件的另一个模块中定义,是非延迟加载(数据)符号,并且仅对该共享库中的模块可见。 -
REFERENCE_FLAG_PRIVATE_UNDEFINED_LAZY(0x5)
:该符号在该文件的另一个模块中定义,是延迟加载(函数)符号,仅对该共享库中的模块可见。
- 另外还可以设置如下标识位:
REFERENCE_DYNAMICALLY(0x10)
:定义的符号必须是使用在动态库加载器中(例如dlsym
和NSLookupSymbolInImage
)。而不是普通的未定义符号引用。strip使用该位来避免删除那些必须存在的符号(如果符号设置了该位,则strip不会剥离它)。N_DESC_DISCARAED(0x20)
:在完全链接的image在运行时动态链接器有可能会使用此符号。不要在完全链接的image中设置此为。N_NO_DEAD_STRIP(0x20)
:定义在可重定位目标文件(类型为MH_OBJECT
)中的符号设置时,指示静态链接器不对该符号进行dead-strip
。(与N_DESC_DISCARDED(0x20)
用于两个不同的目的)。N_WEAK_REF(0x40)
: 表示此未定义符号是弱引用。如果动态链接器找不到该符号的定义,则将其符号地址设置为0。静态链接器会将此符号设置弱链接标志。N_WEAK_DEF(0x80)
: 表示此符号为弱定义符号。如果静态链接器或动态链接器为此符号找到另一个(非弱)定义,则弱定义将被忽略。- 如果该文件是两级命名
two-level namespace image
(即如果mach_header
中设置了MH_TWOLEVEL
标志),则n_desc
的高8位表示定义此为定义符号的库的编号。使用宏GET_LIBRARY_ORDINAL来获取此值。0指定当前image
。 1到253根据文件中LC_LOAD_DYLIB
命令的顺序表明库号。254用于需要动态查找的未定义符号(仅在OS X V10.3和更高版本中支持)。对于从可执行程序加载符号的插件。255用来指定可执行image
。对于flat_namespace_images
,高8位必须是0.
n_value
:符号值。对于symbol table
中的每一项,该值的表达的意思都不同(具体由n_type
字段说明)。对于N_SECT符号类型,n_value是符号的地址。有关其他可能值的信息,请参见n_type
字段的描述。
-
Common symobls
必须为N_UNDF
类型,并且必须设置N_EXT
位。Common Symbols
的n_value
是符号表示的数据的大小(以字节为单位)。在C语言中,Common symbol
是在该文件中声明但未初始化的变量。Common symbols
只能出现在MH_OBJECT
类型的Mach-O
文件中。
section
名称与作用nm
命令 : 打印nlist
结构的符号表( symbol table).
- 常用
nm
命令参数
$ nm -pa a.o
-a: 显示符号表的所有内容
-g: 显示全局符号
-p: 不排序。显示符号表本来的顺序
-r: 逆序顺序
-u: 显示未定义符号
-m: 显示N_SECT类型的符号(Mach-O符号)显示