推广

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

iseeyu2年前 (2024-02-21)推广164

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://0291.com.cn/post/57649.html

相关文章

我来教你企业网站推广有哪些最有效方法(企业网站有哪些 比较有名)

我来教你企业网站推广有哪些最有效方法(企业网站有哪些 比较有名)

不知道从何时开始,成为了企业之间竞争的新的角逐点。今天云裂变的小编抛开类似百度的竞价付费推广,只单纯的讲一下不烧钱的企业网站推广有哪些方法吧。 当下企业网站推广除了付费推广外一般常用的就是论坛、博客推广,新闻媒体推广,网站链接推广,自媒体推广等。网站托管行业唯一上市公司云裂变是专业的企业网站推广服...

分享云裂变告诉你新的网站如何做网站推广。

分享云裂变告诉你新的网站如何做网站推广。

有关优化,网总编小编讲解一下新的网站如何做网站推广,例如有些人做100个站有100个排名,有些人做100个没有可能排名,这是相对技术的而不是招标。当然相对来讲,网站推广快的方式就是做排名了。 快方式的排名效果还是非常有效的。如果有钱的话,就把百度360.搜狗的竞价开户每个搜索引擎开一个户...

南充一大波事业单位、教师公开招聘,好多岗位大专就能报!

南充一大波事业单位、教师公开招聘,好多岗位大专就能报!

最近一段时间你是否正在为找工作奔忙?好机会来啦!开春第一批南充2018年上半年人事考试信息汇总来了!找工作的小伙伴们赶紧看过来!近日,南充各县区发布了一大波人事考试消息。涉及了事业单位考试、教师招考等。今天小编为大家总结了,话不多说,快来挑选属于你的岗位吧!1事业单位招聘报...

别让“囚徒思维”,拖垮你的人生

有人说,一个人拥有什么样的,就会拥有什么样的人生。每一次选择的走向,固然有时机和运气的加持,但更多的是思维抉择的结果。我们往往过于关注目光所及之处的得失,却忽略了退一步后全局的取舍。比起各种尝试后有概率的收获,太多人更加关注当下切实的所得。人生的路,也在一次次选择之后越来越...

小编分享云裂变教您写好软文——基本技巧。

小编分享云裂变教您写好软文——基本技巧。

关于网站内容的更新,好多人认为越多越好,云裂变,专业的网站托管专家告诉您:数量不足为参考依据,质量才是关键。好的软文更容易被百度收录,那么如何软文呢? 1.挖历史 对于企业写软文来说,挖掘历史很重要。在产品软文或者品牌软文中,历史就是价值,有悠久历史的品牌往往更受人们关注,更受欢...

高斯猜想被证明是错误的

高斯猜想被证明是错误的

Li(x)与π(x)取值对比   于是,高斯猜想:Li(x)-π(x)总是正的而且是递增的. 2、李特尔伍德的证明   1914年英国数学家李特尔伍德从理论上证明了事实正好相反(即存在Li(x)小于π(x))。高斯的猜想是错误的! 李特尔伍德证明了:Li(x)-π(x)从正到...

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

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