-
Symbol Table
:⽤来保存符号。 -
String Table
:⽤来保存符号的名称。 -
Indirect Symbol Table
:间接符号表。保存使⽤的外部符号。更准确⼀点就是使⽤的外部动态库的符号。是Symbol Table
的子集。
符号表也是通过读取
Load Command
找到符号表的具体位置
通过两个Load Commands
,描述Symbol Table
的大小和位置,以及其他元数据
LC_SYMTAB
:当前Mach-O
中的符号表信息LC_DYSYMTAB
:描述动态链接器使用其他的Symbol Table
信息
LC_SYMTAB
用来描述该文件的符号表。不论是静态链接器还是动态链接器在链接此文件时,都要使用该
Load Command
。调试器也可以使用该Load Command
找到调试信息
symtab_command
定义
LC_SYMTAB
加载命令具体属性。在/usr/include/mach-o/loader.h
中定义:
struct symtab_command {
// 共有属性。指明当前描述的加载命令,当前被设置为LC_SYMTAB
uint32_t cmd ;
// 共有属性。指明加载命令的大小,当前被设置为sizeof(symtab_command)
uint32_t cmdsize;
// 表示从文件开始到symbol table所在位置的偏移量。symbol table用[nlist]来表示
uint32_t symoff;
// 符号表内符号的数量
uint32_t nsyms;
// 表示从文件开始到string table所在位置的偏移量。
uint32_t stroff;
// 表示string table大小(以byteカ单位)
uint32_t strsize;
};
搭建测试项目
符号可以使用终端命令进行查看,但每次手动操作过于繁琐。这里介绍一个更高效的方案:使用
Sellp
脚本,在项目编译后,自动将符号展示到终端创建
xcode_run_cmd.sh
文件,写入以下代码,将文件放到项目根目录该脚本的作用:执行命令并将结果展示到终端
#!/bin/sh
RunCommand() {
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
if [[ -n "$TTY" ]]; then
echo "♦ $@" 1>$TTY
else
echo "♦ $*"
fi
echo "------------------------------------------------------------------------------" 1>$TTY
fi
if [[ -n "$TTY" ]]; then
eval "$@" &>$TTY
else
"$@"
fi
return $?
}
EchoError() {
if [[ -n "$TTY" ]]; then
echo "$@" 1>&2>$TTY
else
echo "$@" 1>&2
fi
}
RunCMDToTTY() {
if [[ ! -e "$TTY" ]]; then
EchoError "=========================================="
EchoError "ERROR: Not Config tty to output."
exit -1
fi
if [[ -n "$CMD" ]]; then
RunCommand $CMD
else
EchoError "=========================================="
EchoError "ERROR:Failed to run CMD. THE CMD must not null"
fi
}
RunCMDToTTY
脚本内定义了两个变量:
CMD
:命令+参数TTY
:终端的标识打开终端,使用
tty
命令,可获取终端标识
打开项目,创建
xcconfig
文件,定义脚本中需要的两个变量MACH_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/${PRODUCT_NAME} CMD=nm -pa $MACHO_PATH TTY=/dev/ttys001
MACHO_PATH
:定义变量,存储Mach-O
文件的路径${BUILD_DIR}
:当前编译路径$(CONFIGURATION)
:构建产品目录$(EFFECTIVE_PLATFORM_NAME)
:Mach-O
所在目录${PRODUCT_NAME}
:项目名称,也是Mach-O
文件的名称nm
:是names
的简称,通过该命令可以列举文件中的符号-p
:符号不进行排序-a
:显示所有符号,包含调试符号
点击
Target
,选择Build Phases
,在Run Script
中输入:/bin/sh "$SRCROOT/xcode_run_cmd.sh"
- 通过
/bin/bash
命令运行xcode_run_cmd.sh
脚本$SRCROOT
代表的是项目根目录下
项目编译后,自动将
Mach-O
中的符号展示到终端,无需手动操作
查看项目在
Build
时,脚本的执行时机
- 在编译链接之后,签名之前
C语言符号
打开
main.m
文件,写入以下代码:
#import <Foundation/Foundation.h>
int global_uninit_value;
int global_init_value = 10;
double default_x __attribute__((visibility("hidden")));
static int static_init_value = 9;
static int static_uninit_value;
int main(int argc, char *argv[]) {
static_uninit_value = 10;
NSLog(@"%d", static_init_value);
return 0;
}
代码里包含了全局变量和
static
关键字修饰的静态变量,二者最大的差异在于作用域:
- 全局变量:对整个项目可见
- 静态变量:仅对当前文件可见
案例1:
使用
nm -pa ${MACH_PATH}
命令查看符号表
0000000100008008 d __dyld_private
0000000100008014 d _static_init_value
0000000100008018 b _static_uninit_value
0000000100008028 s _default_x
0000000000000000 - 00 0000 SO /Users/zang/Zang/Spark/MachOAndSymbol/
0000000000000000 - 00 0000 SO main.m
0000000060335e1d - 03 0001 OSO /Users/zang/Library/Developer/Xcode/DerivedData/MachOAndSymbol-bdzlylfoorwnhggerxdmwocpeyad/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/main.o
0000000100003f50 - 01 0000 BNSYM
0000000100003f50 - 01 0000 FUN _main
000000000000003f - 00 0000 FUN
000000000000003f - 01 0000 ENSYM
0000000000000000 - 00 0000 GSYM _global_init_value
0000000100008014 - 0a 0000 STSYM _static_init_value
0000000100008018 - 0b 0000 STSYM _static_uninit_value
0000000000000000 - 00 0000 GSYM _global_uninit_value
0000000000000000 - 00 0000 GSYM _default_x
0000000000000000 - 01 0000 SO
0000000100000000 T __mh_execute_header
0000000100008010 D _global_init_value
0000000100008020 S _global_uninit_value
0000000100003f50 T _main
U _NSLog
U ___CFConstantStringClassReference
U dyld_stub_binder
第二列:按照符号种类划分的标识,参见以下列表
按照符号种类划分:
Type 说明 U undefined
(未定义)A absolute
(绝对符号)T ①
text section symbol
(__TEXT.__text
)D ①
data section symbol
(__DATA.__data
)B ①
bss section symbol
(__DATA.__bss
)C common symbol
(只能出现在MH_OBJECT
类型的Mach-O
⽂件中)- debugger symbol table S ①
除了上⾯所述的,存放在其他 section
的内容,例如未初始化的全局变量存放在(__DATA,__common
)中I indirect symbol
(符号信息相同,代表同⼀符号)u 动态共享库中的⼩写 u
表示⼀个未定义引⽤对同⼀库中另⼀个模块中私有外部符号注:标记
①
的Type
,⼩写代表本地符号(local symbol
)
案例2:
使用
objdump --macho --syms ${MACH_PATH}
命令查看符号表
SYMBOL TABLE:
0000000100008008 l O __DATA,__data __dyld_private
0000000100008014 l O __DATA,__data _static_init_value
0000000100008018 l O __DATA,__bss _static_uninit_value
0000000100008028 l O __DATA,__common _default_x
0000000000000000 l d *UND* /Users/zang/Zang/Spark/study/iOS高级强化/20210118-iOS强化第二节课:符号与链接(下)/上课代码/MachOAndSymbol/
0000000000000000 l d *UND* main.m
0000000060335b53 l d *UND* /Users/zang/Library/Developer/Xcode/DerivedData/MachOAndSymbol-bdzlylfoorwnhggerxdmwocpeyad/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/main.o
0000000100003f50 l d *UND*
0000000100003f50 l d *UND* _main
000000000000003f l d *UND*
000000000000003f l d *UND*
0000000000000000 l d *UND* _global_init_value
0000000100008014 l d *UND* _static_init_value
0000000100008018 l d *UND* _static_uninit_value
0000000000000000 l d *UND* _global_uninit_value
0000000000000000 l d *UND* _default_x
0000000000000000 l d *UND*
0000000100000000 g F __TEXT,__text __mh_execute_header
0000000100008010 g O __DATA,__data _global_init_value
0000000100008020 g O __DATA,__common _global_uninit_value
0000000100003f50 g F __TEXT,__text _main
0000000000000000 *UND* _NSLog
0000000000000000 *UND* ___CFConstantStringClassReference
0000000000000000 *UND* dyld_stub_binder
- 第二列:
l
代表本地符号(local
),g
代表全局符号(global
)- 第三列:按照功能划分的标识,参见以下列表
按照功能划分:
Type 说明 f File F Function O Data d Debug ABS Absolute COM Common UND 未定义
剥离调试符号
调试符号:当文件编译成.o
文件时,它会生成DWARF
格式的调试信息,放在__DWARF段
。链接时,将__DWARF段
变成符号放到符号表中。
上面的打印信息中,包含大量调试符号。为了避免干扰,下面介绍如何剥离调试符号。
方式一:
使用
Build Settings
里Strip
配置项
Deployment Postprocessing
在Debug
和Release
下均默认为NO
,它相当于是Deployment
的总开关。将其改为YES
Strip Style
在Debug
和Release
下均默认All Symbols
,剥离除间接符号表以外的全部符号。将其改为Debugging Symbols
,剥离调试符号- 当
Deployment Postprocessing
为NO
,Strip Style
的配置是无法生效的编译项目,
Strip
操作已经执行。但它是在脚本执行后才触发,故此该方式在使用Sellp
脚本自动化的场景下不可行
方式二:
在
xcconfig
中使用链接器配置链接器的作用就是将多个目标文件合并到一起,此时可以对符号进行指定处理
使用
man ld
命令查看链接器参数:
使用
/-S
向下查找-S
关键字
-S
:不要将调试信息放置到输出文件中,本质上和Strip
的Debugging Symbols
配置项是一样的效果打开
xcconfig
文件,添加OTHER_LDFLAGS
配置项OTHER_LDFLAGS = -Xlinker -S
Xcode
使用Target
编译时,执行的是clang
命令。但此时-S
需要传递给ld
链接器,所以前面要加上-Xlinker
关键字
Build Settings
中的Other Linker Flags
配置已生效
来到终端,先
command+k
清空内容,再编译项目。此时打印出除调试符号以外的所有符号SYMBOL TABLE: 0000000100008008 l O __DATA,__data __dyld_private 0000000100008014 l O __DATA,__data _static_init_value 0000000100008018 l O __DATA,__bss _static_uninit_value 0000000100008028 l O __DATA,__common _default_x 0000000100000000 g F __TEXT,__text __mh_execute_header 0000000100008010 g O __DATA,__data _global_init_value 0000000100008020 g O __DATA,__common _global_uninit_value 0000000100003f50 g F __TEXT,__text _main 0000000000000000 *UND* _NSLog 0000000000000000 *UND* ___CFConstantStringClassReference 0000000000000000 *UND* dyld_stub_binder
global_uninit_value
和global_init_value
为全局符号static_init_value
和static_uninit_value
为本地符号default_x
并没有使用static
关键字修饰,本应是全局符号,但使用了__attribute__((visibility("hidden")))
,变为本地符号
__attribute__()
的作用:将编译器支持的参数传递给编译器。例如:对default_x
全局变量进行visibility("hidden")
设置,就是隐藏其可见性
符号可见性
全局符号和本地符号,它们本质上的区别就是可见性
一个符号的可见性有两种:
default
:默认值,定义的符号类型是全局即为全局,是本地即为本地hidden
:将全局符号隐藏,变为本地符号
故此隐藏全局符号的方式有两种:
- 使用
static
关键字修饰- 使用
__attribute__((visibility("hidden")))
案例1:
打开项目,里面包含两个
Project
来到
LGOneFramework
,打开LGOneObject.m
文件,实现global_object
函数
在
LGOneObject.h
文件中,并没有暴露global_object
函数
来到
LGApp
(另一个Project
),打开ViewController.m
文件,定义global_object
函数,但并不实现。在viewDidLoad
方法中调用global_object
函数
运行项目,
LGOneFramework
中实现的global_object
函数被正常调用
案例2:
如果将
LGOneFramework
中的global_object
函数,使用static
关键字修饰
在
LGApp
中,global_object
函数的调用代码不变
此时
global_object
函数变成本地符号,仅对当前文件可见。所以在LGApp
中调用该函数,编译报错,并提示未定义符号
通过上述两个案例,理解符号的可见性:
- 全局符号对整个项目可见
- 本地符号仅对当前文件可见
案例3:
在
LGOneFramework
中,实现global_object
函数,不使用static
关键字修饰
在
LGApp
的ViewController.m
中,也实现global_object
函数
运行项目,调用的是
LGApp
中的global_object
函数
案例3
中,为什么不会发生冲突?
在LGApp
和LGOneFramework
两个Project
中,都定义了global_object
函数,对于global_object
函数来说,它们其实存储在两个Mach-O
中。由于编译器有⼆级命名空间的概念,所以两个global_object
函数的符号其实是不一样的
two_levelnamespace & flat_namespace
:
⼆级命名空间与⼀级命名空间。链接器默认采⽤⼆级命名空间,也就是除了会记录符号名称,还会记录符号属于哪个Mach-O
的,⽐如会记录下来_NSLog
来⾃Foundation
案例4:
在
LGApp
的ViewController.m
中,实现global_object
函数
同样在
LGApp
中,打开AppDelegate.m
文件,也实现global_object
函数
同一个
Project
中,实现两个global_object
函数。此时编译报错,提示出现重复符号
- 这说明在同一作用域中,不能出现相同的全局符号
导入符号和导出符号
export symbol
:导出符号意味着,告诉别的模块,我有⼀个这样的符号,你可以将其导⼊(Import
)。以
NSLog
为例:NSLog(@"%d", static_init_value);
NSLog
存储在Foundation
库中
- 对于
Foundation
库来说,NSLog
属于供外部使用的导出符号- 对于当前程序来说,
NSLog
属于从Foundation
库中导入的符号
导出符号就是全局符号
项目中定义的全局变量生成为全局符号,默认就会被导出,这些符号可以被外界查看并使用
使用
objdump --macho --exports-trie ${MACH_PATH}
命令查看导出符号Exports trie: 0x100000000 __mh_execute_header 0x100003F50 _main 0x100008010 _global_init_value 0x100008020 _global_uninit_value
- 只有上述四个导出符号,对应符号表中的四个全局符号
动态库在运行时才会加载,在编译链接阶段只提供符号即可。
Mach-O
中使用的动态库符号保存在间接符号表里使用
objdump --macho --indirect-symbols ${MACH_PATH}
命令查看间接符号表Indirect symbols for (__TEXT,__stubs) 1 entries address index name 0x0000000100003f90 8 _NSLog Indirect symbols for (__DATA_CONST,__got) 1 entries address index name 0x0000000100004000 10 dyld_stub_binder Indirect symbols for (__DATA,__la_symbol_ptr) 1 entries address index name 0x0000000100008000 8 _NSLog
- 符号在
Mach-O
中占有一定体积,剥离符号时,间接符号表是不能被删除的Mach-O
所使用的动态库的全局符号都不能被删除- 动态库剥离符号,只能剥离非全局符号的所有符号
查看
OC
中的符号打开
LGOneObject.m
文件,写入以下代码:
#import "LGOneObject.h"
@interface LGOneObject : NSObject
- (void)testOneObject;
@end
@implementation LGOneObject
- (void)testOneObject {
NSLog(@"testOneObject");
}
@end
使用
objdump --macho --exports-trie ${MACH_PATH}
命令查看导出符号Exports trie: 0x100000000 __mh_execute_header 0x100003F20 _main 0x1000080B8 _OBJC_METACLASS_$_LGOneObject 0x1000080E0 _OBJC_CLASS_$_LGOneObject 0x100008110 _global_init_value 0x100008120 _global_uninit_value
OC
默认都是全局符号,同时也是导出符号。它们可以被外界查看并使用,会增加Mach-O
的体积
开发
OC
动态库时,想要减小Mach-O
的体积,就要将外部无需使用的符号剥离。此时可以借助链接器,将不想暴露的符号声明为不导出符号打开
xcconfig
文件,添加OTHER_LDFLAGS
配置项OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbol -Xlinker _OBJC_CLASS_$_LGOneObject OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbol -Xlinker _OBJC_METACLASS_$_LGOneObject
- 将
_OBJC_CLASS_$_LGOneObject
声明为不导出符号- 将
_OBJC_METACLASS_$_LGOneObject
声明为不导出符号编译项目,此时
OC
的两个导出符号已经被隐藏Exports trie: 0x100000000 __mh_execute_header 0x100003F20 _main 0x100008110 _global_init_value 0x100008120 _global_uninit_value
- 隐藏
OC
不想暴露的符号,需要借助链接器,将符号声明为不导出符号- 由于
OC
是运行时语言,不能直接使用visibility("hidden")
- 不导出符号,将全局符号变为本地符号,这些符号可以被剥离,从而减小
Mach-O
的体积- 隐藏不需要暴露的符号,从而避免被外界查看并使用,解决安全隐患
链接器提供的另一种方式:指定一个文件,将文件内的符号全部声明为不导出符号
创建
symbol.txt
文件,放到工程目录中,里面定义不想暴露的符号_OBJC_CLASS_$_LGOneObject _OBJC_METACLASS_$_LGOneObject _global_init_value _global_uninit_value
打开
xcconfig
文件,添加OTHER_LDFLAGS
配置项OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbols_list >$(PROJECT_DIR)/symbol.txt
编译项目,此时
symbol.txt
文件中定义的四个符号已经被隐藏Exports trie: 0x100000000 __mh_execute_header 0x100003F20 _main
Weak Symbol
弱定义符号
Weak Defintion Symbol
:表示此符号为弱定义符号。如果静态链接器或动态链接器为此符号找到另⼀个(⾮弱)定义,则弱定义将被忽略。只能将合并部分中的符号标记为弱定义打开
WeakSymbol.m
文件,写入以下代码:#import "WeakSymbol.h" #import <Foundation/Foundation.h> void weak_function(void) { NSLog(@"weak_function"); }
- 此时
weak_function
是一个全局符号,同样也是导出符号打开
WeakSymbol.h
文件,写入以下代码:void weak_function(void) __attribute__((weak));
- 使用
__attribute__((weak))
将weak_function
声明为弱定义符号使用
objdump --macho --exports-trie ${MACH_PATH}
命令查看导出符号Exports trie: 0x100000000 __mh_execute_header 0x100003EF0 _weak_function [weak_def] 0x100003F30 _main 0x100008010 _global_init_value 0x100008020 _global_uninit_value
weak_function
还是导出符号,这也证明它依然是全局符号,其后增加了[weak_def]
的标记
弱定义符号的作用
在
WeakSymbol.m
和main.m
中,都实现一个weak_function
函数void weak_function(void) { NSLog(@"weak_function"); }
同一个
Project
中,出现两个相同的全局符号,此时编译报错,提示出现重复符号
将其中一个
weak_function
函数声明为弱定义符号,此时编译成功void weak_function(void) __attribute__((weak));
- 弱定义符号的作用:可以解决同名符号的冲突;链接器按照符号上下顺序,找到一处符号的实现后,其他地方的同名符号将被忽略
如果同时使用
weak
和visibility("hidden")
,符号会变成一个弱定义的本地符号打开
WeakSymbol.m
文件,写入以下代码:void weak_hidden_function(void) { NSLog(@"weak_hidden_function"); }
打开
WeakSymbol.h
文件,将weak_hidden_function
函数同时使用weak
和visibility("hidden")
修饰void weak_hidden_function(void) __attribute__((weak, visibility("hidden")));
使用
objdump --macho --syms ${MACH_PATH}
命令查看符号表SYMBOL TABLE: 0000000100003f10 lw F __TEXT,__text _weak_hidden_function 0000000100008008 l O __DATA,__data __dyld_private 0000000100008014 l O __DATA,__data _static_init_value 0000000100008018 l O __DATA,__bss _static_uninit_value 0000000100008028 l O __DATA,__common _default_x 0000000100000000 g F __TEXT,__text __mh_execute_header 0000000100008010 g O __DATA,__data _global_init_value 0000000100008020 g O __DATA,__common _global_uninit_value 0000000100003f30 g F __TEXT,__text _main 0000000100003ef0 gw F __TEXT,__text _weak_function 0000000000000000 *UND* _NSLog 0000000000000000 *UND* ___CFConstantStringClassReference 0000000000000000 *UND* dyld_stub_binder
- 此时
_weak_hidden_function
被标记为lw
,变为弱定义本地符号
弱引用符号
Weak Reference Symbol
:表示此未定义符号是弱引⽤。如果动态链接器找不到该符号的定义,则将其设置为0
。链接器会将此符号设置弱链接标志打开
WeakImportSymbol.h
文件,写入以下代码:void weak_import_function(void) __attribute__((weak_import));
- 使用
__attribute__((weak_import))
将weak_import_function
声明为若引用符号- 此时项目中没有
weak_import_function
函数的实现打开
main.m
文件,写入以下代码:#import <Foundation/Foundation.h> #import "WeakImportSymbol.h" int main(int argc, char *argv[]) { if (weak_import_function) { weak_import_function(); } return 0; }
由于
weak_import_function
函数没有实现,但在main.m
中被使用,此时编译报错,提示未定义符号
- 当导入
.h
头文件并使用符号时,类似于API
的使用,只要找到符号的声明即可。即使函数没有被实现,也可以生成目标文件。但链接生成可执行文件时,需要知道符号的具体位置,如果函数没有被实现,会出现错误提示:未定义符号
解决弱引用符号的使用问题,可以通过链接器,将符号声明为动态链接
使用
man ld
命令查看链接器参数:
-U
:指明该符号未定义,需要运行时动态查找打开
xcconfig
文件,添加OTHER_LDFLAGS
配置项OTHER_LDFLAGS=$(inherited) -Xlinker -U -Xlinker _weak_import_function
- 此时项目可以正常编译成功
- 通过
-U
参数,告诉链接器此符号是动态链接的,所以在链接阶段,即使它是未定义符号,忽略,不用管它。因为在运行时,动态链接器会自动找到它运行项目,虽然
weak_import_function
函数没有被实现,但运行并不会报错
- 因为
main
函数中调用weak_import_function
函数之前有if (weak_import_function)
的判断- 当动态链接器找不到该符号的定义,则将其设置为
0
。所以weak_import_function
函数并不会被调用
弱引用符号的作用
- 将一个符号声明为弱引用符号,可以避免编译链接时报错。在调用之前增加条件判断,运行时也不会报错
- 使用动态库的时候,可以将整个动态库声明为弱引用,此时动态库即使没有被导入,也不会出现未找到动态库的错误
Common Symbol
在定义时,未初始化的全局符号
例如:
main.m
文件中,未初始化的global_uninit_value
全局变量,它就属于Common Symbol
int global_uninit_value;
打开
main.m
文件,定义两个同名的全局变量,一个初始化,另一个不进行初始化,这种操作并不会报错int global_init_value = 10; int global_init_value;
Common Symbol
的作用- 在编译和链接的过程中,如果找到定义的符号,会自动将未定义符号删掉
- 在链接过程中,链接器默认会把未定义符号变成强制定义的符号
链接器设置:
-d
:强制定义Common Symbol
-commons
:指定对待Common Symbol
如何响应
重新导出符号
以
NSLog
为例:
- 对于当前程序来说,
NSLog
属于存储在间接符号表中的未定义符号
NSLog
可以在当前程序使用,如果想让使用此程序的其他程序也能使用,就要将此符号重新导出。重新导出之后的符号会放在导出符号表中,此时才能被外界查看并使用使用
man ld
命令查看链接器参数:
-alias
:只能给间接符号表中的符号创建别名,别名符号具有全局可见性打开
xcconfig
文件,添加OTHER_LDFLAGS
配置项OTHER_LDFLAGS=$(inherited) -Xlinker -alias -Xlinker _NSLog -Xlinker Cat_NSLog
- 给
_NSLog
符号创建Cat_NSLog
别名使用
nm -m ${MACH_PATH} | grep "Cat_NSLog"
命令查看符号表,指定"Cat_NSLog"
关键字(indirect) external Cat_NSLog (for _NSLog)
- 此时
Cat_NSLog
是一个间接外部符号,是_NSLog
符号的别名使用
objdump --macho --exports-trie ${MACH_PATH}
命令查看导出符号Exports trie: 0x100000000 __mh_execute_header 0x100003F20 _main 0x100008018 _global_init_value 0x100008028 _global_uninit_value [re-export] Cat_NSLog (_NSLog from Foundation)
Cat_NSLog
为导出符号,并且标记为[re-export]
,代表重新导出符号
重新导出符号的作用
- 将一个间接符号表中的符号声明为重新导出符号,可以让使用此程序的其他程序也能使用
- 当程序链接
A动态库
,而A动态库
又链接B动态库
时,B动态库
对于程序来说是不可见的。此时可以使用重新导出的方式,让B动态库
对程序可见
图解
查看项目使用的三方库和符号等信息
通过链接器,可以查看当前项目中使用的三方库和符号等信息
使用
man ld
命令查看链接器参数:
-map
:将所有符号详细信息导出到指定文件打开
xcconfig
文件,添加OTHER_LDFLAGS
配置项OTHER_LDFLAGS=$(inherited) -Xlinker -map -Xlinker $(PROJECT_DIR)/export.txt
编译项目,此时项目目录下多出
export.txt
文件
打开
export.txt
文件
# Path: /Users/zang/Library/Developer/Xcode/DerivedData/MachOAndSymbol-bdzlylfoorwnhggerxdmwocpeyad/Build/Products/Debug/MachOAndSymbol
# Arch: x86_64
# Object files:
[ 0] linker synthesized
[ 1] /Users/zang/Library/Developer/Xcode/DerivedData/MachOAndSymbol-bdzlylfoorwnhggerxdmwocpeyad/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/LGOneObject.o
[ 2] /Users/zang/Library/Developer/Xcode/DerivedData/MachOAndSymbol-bdzlylfoorwnhggerxdmwocpeyad/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/main.o
[ 3] /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk/System/Library/Frameworks//Foundation.framework/Foundation.tbd
# Sections:
# Address Size Segment Section
0x100003EF0 0x0000006F __TEXT __text
0x100003F60 0x00000006 __TEXT __stubs
0x100003F68 0x0000001A __TEXT __stub_helper
0x100003F82 0x00000011 __TEXT __cstring
0x100003F93 0x0000000C __TEXT __objc_classname
0x100003F9F 0x0000000E __TEXT __objc_methname
0x100003FAD 0x00000008 __TEXT __objc_methtype
0x100003FB8 0x00000048 __TEXT __unwind_info
0x100004000 0x00000008 __DATA_CONST __got
0x100004008 0x00000040 __DATA_CONST __cfstring
0x100004048 0x00000008 __DATA_CONST __objc_classlist
0x100004050 0x00000008 __DATA_CONST __objc_imageinfo
0x100008000 0x00000008 __DATA __la_symbol_ptr
0x100008008 0x000000B0 __DATA __objc_const
0x1000080B8 0x00000050 __DATA __objc_data
0x100008108 0x00000010 __DATA __data
0x100008118 0x00000004 __DATA __bss
0x100008120 0x00000010 __DATA __common
# Symbols:
# Address Size File Name
0x100003EF0 0x00000027 [ 1] -[LGOneObject testOneObject]
0x100003F20 0x0000003F [ 2] _main
0x100003F60 0x00000006 [ 3] _NSLog
0x100003F68 0x00000010 [ 0] helper helper
0x100003F78 0x0000000A [ 3] _NSLog
0x100003F82 0x0000000E [ 1] literal string: testOneObject
0x100003F90 0x00000003 [ 2] literal string: %d
0x100003F93 0x0000000C [ 1] literal string: LGOneObject
0x100003F9F 0x0000000E [ 1] literal string: testOneObject
0x100003FAD 0x00000008 [ 1] literal string: v16@0:8
0x100003FB8 0x00000048 [ 0] compact unwind info
0x100004000 0x00000008 [ 0] non-lazy-pointer-to-local: dyld_stub_binder
0x100004008 0x00000020 [ 1] CFString
0x100004028 0x00000020 [ 2] CFString
0x100004048 0x00000008 [ 1] objc-cat-list
0x100004050 0x00000008 [ 0] objc image info
0x100008000 0x00000008 [ 3] _NSLog
0x100008008 0x00000048 [ 1] __OBJC_METACLASS_RO_$_LGOneObject
0x100008050 0x00000020 [ 1] __OBJC_$_INSTANCE_METHODS_LGOneObject
0x100008070 0x00000048 [ 1] __OBJC_CLASS_RO_$_LGOneObject
0x1000080B8 0x00000028 [ 1] _OBJC_METACLASS_$_LGOneObject
0x1000080E0 0x00000028 [ 1] _OBJC_CLASS_$_LGOneObject
0x100008108 0x00000008 [ 0] __dyld_private
0x100008110 0x00000004 [ 2] _global_init_value
0x100008114 0x00000004 [ 2] _static_init_value
0x100008118 0x00000004 [ 2] _static_uninit_value
0x100008120 0x00000008 [ 2] _global_uninit_value
0x100008128 0x00000008 [ 2] _default_x
- 文件内包含了编译链接时生成的目标文件,项目中使用的三方库,还包含项目中的
Sections
和Symbols
等信息
Section的名称与作用
名称 | 作用 |
---|---|
TEXT.text | 可执行的机器码 |
TEXT.cstring | 去重后的C 字符串 |
TEXT.const | 初始化过的常量 |
TEXT.stubs | 符号桩。lazybinding 的表对 应项指针指向的地址的代码 |
TEXT.stub_ helper | 辅助函数。当在lazybinding 的表中没有找到对应项的指针表示的真正的符号地址的时候,指向这 |
TEXT.unwind_info | 存储处理异常情况信息 |
TEXT.eh_frame | 调试辅助信息 |
DATA.data | 初始化过的可变的数据 |
DATA.nI_symbol_ptr | 非lazy-binding 的指针表,每个表中的指针指向一个在装载过程中,被动态链接器搜索完成的符号 |
DATA.Ia_symbol_ptr |
lazy-binding 的指针表,每个表中的指针一开始指向stub_helper
|
DATA.const | 没有初始化过的常量 |
DATA.mod_init_func | 初始化函数,在main 之前调用 |
DATA.mod_term_func | 终止函数,在main 返回之后调用 |
DATA.bss | 没有初始化的静态变量 |
DATA.common | 没有初始化过的符号声明(for example, int I; ) |
Swift符号表
打开
SwiftSymbol.swift
文件,写入以下代码:public class LGSwiftClassSymbol { func testSwiftSymbol() { } }
使用
objdump --macho --syms ${MACH_PATH} | grep "Swift"
命令查看符号表,指定Swift
关键字
0000000100003f8a lw O __TEXT,__swift5_typeref _symbolic _____ 14MachOAndSymbol012LGSwiftClassC0C
0000000100003f90 l O __TEXT,__swift5_fieldmd _$s14MachOAndSymbol012LGSwiftClassC0CMF
0000000100008020 l O __DATA,__objc_const __METACLASS_DATA__TtC14MachOAndSymbol18LGSwiftClassSymbol
0000000100008068 l O __DATA,__objc_const __DATA__TtC14MachOAndSymbol18LGSwiftClassSymbol
00000001000080e8 l O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC0CMf
0000000100003d90 g F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0C09testSwiftC0yyF
0000000100003f78 g O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC0C09testSwiftC0yyFTq
0000000100003e10 g F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0CACycfC
0000000100003f80 g O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC0CACycfCTq
0000000100003e40 g F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0CACycfc
0000000100003e60 g F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0CMa
00000001000080c0 g O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC0CMm
0000000100003f44 g O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC0CMn
00000001000080f8 g O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC0CN
0000000100003dd0 g F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0CfD
0000000100003db0 g F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0Cfd
0000000000000000 *UND* _OBJC_CLASS_$__TtCs12_SwiftObject
0000000000000000 *UND* _OBJC_METACLASS_$__TtCs12_SwiftObject
- 查找出所有包含
Swift
关键字的符号,其中有很多标记为g
的全局符号
打开
SwiftSymbol.swift
文件,修改LGSwiftClassSymbol
类的访问控制,改为private
修饰private class LGSwiftClassSymbol { func testSwiftSymbol() { } }
使用
objdump --macho --syms ${MACH_PATH} | grep "Swift"
命令查看符号表,指定Swift
关键字
0000000100003d10 l F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLC09testSwiftC0yyF
0000000100003d30 l F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCfd
0000000100003d50 l F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCfD
0000000100003d90 l F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMa
0000000100003db0 l F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCADycfC
0000000100003de0 l F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCADycfc
0000000100003f1c lw O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMXX
0000000100003f44 l O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMn
0000000100003f78 l O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLC09testSwiftC0yyFTq
0000000100003f80 l O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCADycfCTq
0000000100003f8a lw O __TEXT,__swift5_typeref _symbolic _____ 14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLC
0000000100003f90 l O __TEXT,__swift5_fieldmd _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMF
0000000100008020 l O __DATA,__objc_const __METACLASS_DATA__TtC14MachOAndSymbolP33_66093EBE10D00815F1A5CBD65FFF466118LGSwiftClassSymbol
0000000100008068 l O __DATA,__objc_const __DATA__TtC14MachOAndSymbolP33_66093EBE10D00815F1A5CBD65FFF466118LGSwiftClassSymbol
00000001000080c0 l O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMm
00000001000080e8 l O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMf
00000001000080f8 l O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCN
0000000000000000 *UND* _OBJC_CLASS_$__TtCs12_SwiftObject
0000000000000000 *UND* _OBJC_METACLASS_$__TtCs12_SwiftObject
- 之前那些标记为
g
的全局符号,全部变为本地符号
$(SRCROOT)
和$(PROJECT_DIR)
的区别
$(SRCROOT)
代表的是项目根目录下$(PROJECT_DIR)
代表的是整个项目往项目添加文件时,例如
.a
文件,要先Show in Finder
,复制到工程目录中,然后再拖到xcode
项目中而有时,
.a
不在工程目录中。例如在工程的父目录,可以写成:$(SRCROOT)/../Spark/libSDK
。其中/../
就是指向父目录
网友评论