推广

4、iOS强化 — 链接与符号(Symbol)

iseeyu3年前 (2024-02-21)推广182

image.png

  • 这里大家要注意一下:
    在生成目标文件的过程中
    1、链接器 (llvm-ld) 并没有被执行。
    2、目标文件不会包含Unix程序在被装载和执行时所必须的包含信息。
    那么上面这句话究竟是什么意思呢?
    接下来我们来探索一下:
    首先我们在Mach-O 文件这篇文章里面已经简单了解了Mach-o文件的格式,这里我们再来加深一下印象,这里我们将通过终端打印一下Mach-o的一些相关信息。
    1、首相我们建立一个命令行工程test,在工程中引入一个脚本脚本地址,配置如下:

    image.png

2、创建我们自己的xcconfig文件(这一步有不明白的同学可以阅读Xcode 多环境的配置这一批文章)
3、在xcconfig文件中输入脚本要用的一些参数(注意:1、这里不是shell指令,是Key-Value的形式,2、此时我们还没有写任何代码)

脚本中需要输入两个变量:
CMD : 指令
和
TTY:指定的终端(可以终端输入tty,会打印当前终端的信息)

同时我们还需要知道当前Mach-O的地址:

MACH_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/${PRODUCT_NAME}
  • MACH_PATH:我们自己定义的变量,用来存储Mach-O的地址
  • ${BUILD_DIR}:当前编译的路径
  • $(CONFIGURATION):构建产品的目录
  • $(EFFECTIVE_PLATFORM_NAME):Mach-O所在的目录
  • ${PRODUCT_NAME}:项目名称,也就是Mach-O的名称

① 首先我们打印一下Mach header

// 查看mach-header
CMD = objdump --macho -private-header ${MACH_PATH}

image.png

这里大家可以看到Mach header的一些原始的信息。
② 接下来我们查看一下Mach-o里面的__TEXT段(因为:main函数被编译之后会放到__TEXT段的)

image.png

可以看到显示的__TEXT段、__text section的机器指令(如:55),后面跟着的汇编代码是给开发者看的。
在底层会有一个类似与字典的东西,会将汇编指令和机器码一一对应起来
下面我们在main.m文件中添加一些代码,在来看一下__TEXT段(代码段):

/// 全局变量
int global_uninit_value;

int global_init_value = 10;
//__attribute__关键字主要是用来在函数或数据声明中设置其属性。给函数赋予属性的主要目的在于让编译器进行优化。
double defaule_x __attribute__((visibility("hidden")));
/// 静态变量 -》本地符号
static int static_uninit_value;

int main(int argc, const char * argv[]) {
    static_uninit_value = 10;
    NSLog(@"%d", static_uninit_value);
    return 0;
}

image.png

我们会发现__TEXT段多了很多东西。
可以看到NSLog直接就变成了callq 0x100003f90指令,一个有明确地址的指令。

  • 其实在编译生成.o文件的过程中是做了这样一件事情:
    1、能变成汇编的,尽量变成机器码
    2、符号归类(比如:数据放到数据段,等等);再比如NSLog,在生成目标文件的时候,我们并不知道它的地址,这个时候就要将它临时发到一个地方。将符号归类之后,放到重定位符号表里面。

  • 为什么要放到重定位符号表里面呢?
    1、因为在生成.o文件的时候,整个的地址并没有虚拟化(虚拟内存的地址)
    2、在生成.o的过程中,我们链接的符号,一些我们知道它的位置(如:global_init_value,因为在同一个Mach-o中,我们可以通过偏移地址直接取到符号);但是有一些导入符号我们是不知道的(如:NSLog)
    也就是说:重定位符号表里面放的就是.o文件里面用到的API,没有用到的就不在这里面。
    既然重定位符号表是这样的,那么我们也就可以通过检查.o文件里面的重定位符号表来查看文件里面对某种API的使用情况。

  • 接下来.o会进入链接过程,处理我们编译的情况;会把生成的多个.o文件合并成一个,这也就意味着,大家的符号表(包括重定位符号表)都会被合并到一张表中。

  • 最后生成可执行文件(exec)

  • 因此我们说的链接,就是在处理.o文件中符号的过程

全局符号与本地符号

结合上面的代码:

  • 全局符号:int global_uninit_value;
    全局符号对整个项目可见,对使用它的项目也是可见的
  • 本地符号:static int static_uninit_value;
    本地符号只对定义它的文件可见

接下来我们来查看一下符号表:

// 查看符号表
CMD = objdump --syms ${MACH_PATH}

image.png

上图中有一点不同,不知道大家注意到没有:
defaule_x这个符号我们定义的是一个全局符号,但是最终它是一个本地符号,这是为什么呢?
因为我们是这样写的:

double defaule_x __attribute__((visibility("hidden")));

这里我们就要引入visibility属性:

// visibility属性,控制文件导出符号,限制符号可见性
/**
    -fvisibility:clang参数
    default:用它定义的符号将被导出。
    hidden:用它定义的符号将不被导出。
 */
// 隐藏 -> 本地
int hidden_y __attribute__((visibility("hidden"))) = 99;
// 符号
double default_y __attribute__((visibility("default"))) = 100;

这里说明一下,我们在开发中经常会遇到被Apple遗弃的方法,会有一条黄线的警告,其实也是用了这个属性。

导入符号 & 导出符号

还是上面的代码,我们用到了NSLog(它存在于Foundation动态库中);那么对于我们自己的可执行文件NSLog就是导入符号;对于Foundation动态库NSLog就是导出符号。
这也就意味着导出符号是全局符号。
下面我们打印一下导出符号:

// 查看导出符号
CMD = objdump --macho --exports-trie ${MACH_PATH}

image.png

可以看到正好对应的是我们设置的全局变量。
在日常开发中我们要注意:当我们把变量设置成全局变量的时候,也就意味着会被默认设置成导出符号。

  • 这里我们补充一下,间接符号表里面保存着,我们当前可执行文件使用的其他的动态库里面的导出符号
    下面我们来打印一下间接符号表:
// 查看间接符号表
CMD = objdump --macho --indirect-symbols ${MACH_PATH}

image.png

  • 这里就有一个问题了,在我们在strip符号剥离的时候,间接符号表里面的面的符号是我们不能剥离的,那我们反过来向上推,也就意味着我们在写自己的OC的动态库的时候没办法剥离我们没有暴露的符号。这里跟大家讲一下,OC默认的符号都是全局符号,而全局符号又是导出符号,我们来验证一下:

    image.png

    image.png

    接着我们来打印一下导出符号

// 查看导出符号CMD = objdump --macho --exports-trie ${MACH_PATH}

image.png

  • 不导出符号
OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbol -Xlinker _OBJC_METACLASS_$_YSOneObject

image .png

我们成功的将符号_OBJC_METACLASS_$_YSOneObject设置成非导出符号
这样我们就可以对齐进行符号剥离,进一步减少动态库的体积;同时外界也就看不到我们的符号了。
️ 开发中我们也不必一个一个的去将符号设置成不导出,我们可以指定一个文件来设置符号的导出属性,使用-unexported_symbol_list这个指令

  • 我们还可以查看我们使用的所有的符号,并写入到相应的文件中:
OTHER_LDFLAGS=$(inherited) -Xlinker -S -Xlinker -map -Xlinker /Users/aaron/Desktop/test-02/Source.text

image.png

弱符号(Weak Symbol)

弱符号分为:

  • Weak Reference Symbol(弱引用符号):表示此未定义符号是弱引用。如果动态连接器找不到该符号的定义,则将其设置为0。链接器会将此符号设置弱链接标志。
  • Weak defintion Symbol(弱定义符号):表示此符号为弱定义符号。如果静态链接器或动态链接器为此符号找到另一个(非弱)定义,则弱定义将被忽略。只能将合并部分中的符号标记为弱定义。

那么怎么理解上面的两句话呢?
首先我们来看在代码中怎么写:

// 弱引用
void weak_import_function(void) __attribute__((weak_import));

// 弱定义
// weak def
void weak_function(void)  __attribute__((weak));
// weak 本地符号
void weak_hidden_function(void) __attribute__((weak, visibility("hidden")));

首先我们来看一下:

  • Weak defintion Symbol(弱定义符号)
    其中weak_function为全局弱定义符号;weak_hidden_function为本地弱定义符号。
    接着我们查看一下到处符号表:
CMD = objdump --macho --exports-trie ${MACH_PATH}

image.png

可以看到弱定义并不影响其作为导出符号。
接着我们在其他地方在定义一个名字相同的全军符号,如下:

image.png

我们会发现,工程并不会报错。运行起来也没有问题,这正式我们上面说的:如果静态链接器或动态链接器为此符号找到另一个(非弱)定义,则弱定义将被忽略。

接下来我们再看一下:

  • Weak Reference Symbol(弱引用符号)
    首先我们只声明,不实现:
void weak_import_function(void) __attribute__((weak_import));

同样的查看导出符号表:

image.png

我们会发现导出符号表里面并没有该符号。
接下来我们实现以下该函数:

void weak_import_function(void) {
    NSLog(@"weak_import_function");
}

然后再查看一下导出符号表:

image.png

这也就是当动态链接器找不到它的定义,则将其定义为0,也就不会出现在导出符号表里面了。

  • 既然是这样,那我们只声明,不定义该符号。通过判断的符号是否为0,去做一些事情呢?
if (weak_import_function) {
        weak_import_function();
        
        /**
         一些其他的业务
         */
    }

我们cmd + B会发现,工程报错:

image.png

也就是说说在链接的过程中,链接器找不到这个符号(不知道符号具体在哪个地方)。

这个时候我们可以告诉链接器,不要管这个符号,即使是未定义的也不要管,我这个符号是动态链接的,到时候会自己找到这个符号。那么我们可以通过下面的指令来达到这个目的:

OTHER_LDFLAGS=$(inherited) -Xlinker -U -Xlinker _weak_import_function

重新导出符号

举一个例子:
我们上面的代码在mian.m文件中用到的NSLog这个函数。然而NSLog对于当前工程来说是一个未定义符号

image.png

那么此时,我们如果想要使用我们这个库的工程,也能够使用NSLog,此时我们就需要将NSLog以别名的形式从新导出。
️ 这里要注意:只能给间接符号表中的符号起别名。

OTHER_LDFLAGS=$(inherited) -Xlinker -alias -Xlinker _NSLog -Xlinker YS_NSLog

我们再来查看一下导出符号表:

image.png

可以看得到YS_NSLog作为导出符号,并且被标记为re-export

Swift 符号

首先我们在swift文件中定义一些结构体、方法。

struct YSSwiftStructSymbol {
    func testSwiftStructSymbol(o: Int) {}
}

private protocol YSSwiftProtocolSymbol: class {
    func testSwiftProtocolSymbol()
}

private class YSSwiftClassSymbol {
    func testSwiftSymbol() {}
}

接着我们查看一下现在的符号表:

// 查看符号表
CMD = objdump --syms ${MACH_PATH}

image.png

会发现这里面多了很多的符号。
那么我们来定向查找swift产生的符号:

CMD = objdump --syms ${MACH_PATH} | grep "SwiftClass"

image.png

这样就少了很多,并且全部都是本地符号。下面我们修改一下swift中方法的权限:

public class YSSwiftClassSymbol {
    func testSwiftSymbol() {}
}

再来查看一下swift符号:

image.png

可以看到有一些符号已经变成了全局符号

总结:Swift是一门静态语言,跟OC不一样。Swift在编译的时候就能确定符号的类别。

扫描二维码推送至手机访问。

版权声明:本文由西安泽虎代运营发布,如需转载请注明出处。

转载请注明出处https://www.0291.com.cn/post/57649.html

相关文章

西安企业做百度推广,一个月花三千和花三万到底差在哪?

4|最近跟几个西安的老板聊天,发现一个挺有意思的现象:同样做百度推广,有人一个月花三千块,询盘不断;有人一个月砸三万,水花都没见着。问他们区别在哪,大多数人说不清楚,只知道'钱花了,没效果'。 5| 6|百度推广这东西,说复杂也复杂,说简单也简单。核心就一件事:让你的潜在客户搜某个词的时候,你的信息...

我想当策划,大学应该选什么专业?

我想当策划,大学应该选什么专业?

学,广告,传播,公关,这些专业其实都可以。另外也可以学像汉语言文学,以及一些跟文学相关的一些专业,会对你找工作有帮助。但是,你想从事这个这份职业,除了专业的学习之外,更多的是,你要在上学的时候积极参与一些更营销策划相关的社会实践。比如说你可以做一些兼职,比如说帮别人去写稿,...

服装市场营销策略分析,“定位不慎满盘皆输”

服装市场营销策略分析,“定位不慎满盘皆输”

不同的区域需要不同的策略,而不同的营销策略需要不同的销售思路来配合。因此,在研究女装产品销售思路之前,最好先对于市场与营销策略做一次深入地了解。1.市场区域化(market segmentation):目标营销的第一步,是将整个市场拆分成若干个同性质的小市场。2.目标市场的...

晚间悦读丨你的思维方式,决定你的人生高度!

晚间悦读丨你的思维方式,决定你的人生高度!

越长大,越发现,能够决定你人生高度的,往往是你的认知层次。 你的认知在哪个层级,你的人生就处在什么状态。你认知的高度,决定了你人生的高度。 那如何才能升级方式、提升认知能力呢?可以从这三个方面做出改变。 NO. 1 改变态度 有人...

编写项目营销策划方案六大要点

编写项目营销策划方案六大要点

现在商业发展非常迅速,的需求非常的大,但是要做好营销策划是有一定难度的,我把自己做营销策划的一些体会在这里分享给朋友们。营销策划方案六大要点我们先说项目营销策划方案这个话题。大部分的营销策划人都是在企业里工作,大部分项目之初都是来源于一个想法,创始人、老板或者创始团队的一个...

【商场营销策划】如何进行商场营销策划?

【商场营销策划】如何进行商场营销策划?

商场是策划者们关注的永恒话题,那么究竟该如何作好商场营销策划呢?掌握以下这几点营销策略设计原则,至关重要!1.义利兼顾的原则。商场在获取利润的同时要充分考虑到消费者的合法利益和商场的长远利益。在作商场活动营销策略方案的时候应该将这一点作为首要的原则。对商场有利而对顾客不利的...

现在,非常期待与您的又一次邂逅

我们努力让每一部企业宣传片和抖音短视频成为商业大片