MacOS 下的动态链接

本文主要讲解 MacOS 下的链接,是与 MacOS 和 iOS 开发相关的内容,其他平台可能也会有类似的操作。

本文大部分篇幅都是讲动态链接,静态链接也简单讲了一下,很多不错的参考文章列在文末,感兴趣可以看下。

1、ld manual

Libraries

A static library (aka static archive) is a collection of .o files with a table of contents that lists the global symbols in the .o files. ld will only pull .o files out of a static library if needed to resolve some symbol reference. Unlike traditional linkers, ld will continually search a static library while linking. There is no need to specify a static library multiple times on the command line.

A dynamic library (aka dylib or framework) is a final linked image. Putting a dynamic library on the command line causes two things: 1) The generated final linked image will have encoded that it depends on that dynamic library. 2) Exported symbols from the dynamic library are used to resolve references.

Both dynamic and static libraries are searched as they appear on the command line.

2、动态链接

2.1、动态库符号查找

链接器发现 program1.o 中引用的外部符号在一个动态库(动态库中保存由其到处的符号信息)中,此时链接器并不会对该符号进行重定位,而是标记其为动态链接符号,把真正的链接过程保留到装载时执行。

image.png

如果存在未定义符号,编译链接时一样是会报错的。

这时候,可以利用本文 2.4 介绍的方法,把符号查找推迟给 dyld 查找。

2.2、案例

通过例子来分析:

// add.h
extern int add(int num);

// add.c
#include "add.h"

int add(int num){
  return num + 8;
}

// main.c
#include"add.h"

int main(){
  int tmp = 10;
  tmp = add(tmp);
  return 0;
}
  • 生成动态链接库:

动态库需要为地址无关代码,这样不论它在内存哪里被加载,都可以正常使用,所以得加上 -fPIC 参数。

gcc -fPIC -shared add.c -o share

使用 MachOView 观摩下这个动态库:

image.png

解析一下这个 LC_ID_DYLIB 字段:

Any clients that link against the dylib record the path specified in the LC_ID_DYLIB load command as the path at which to find the dylib at runtime.

This is useful as the path where the dylib lives at build time is often not where it will be found at runtime.

简单来说就是:This is the path that will be copied into the binaries that link to it.

  • 链接动态库生成最终可执行文件:
gcc main.c share -o main

image.png

The LC_LOAD_DYLIB header in a Mach-O binary tells macOS and OS X which dynamic libraries (dylibs) to load during execution time.

2.3、汇编分析

对于以动态库链接的符号,是在程序加载进内存时或者运行时才进行链接,因此这些符号在 MachO 里被特殊对待。

image.png

这里的 dyld_stub_binder 不是 lazy symbol,启动时,dyld 就直接给绑定好了。

像上面 1.1 案例中,add 符号被当作延迟绑定的了,因为这个符号不会马上被用到:

image.png

通过汇编代码来看看整个过程是怎么处理的:

  • __TEXT,__text
                     _main:
0000000100003f60         push       rbp                         ; 保存栈帧指针,标识当前栈底
0000000100003f61         mov        rbp, rsp                    ; rbp 更新为当前栈顶 rsp
0000000100003f64         sub        rsp, 0x10                   ; 开辟 16 字节的栈空间
0000000100003f68         mov        dword [rbp-4], 0x0          ; double word,前 4 字节初始化为 0
0000000100003f6f         mov        dword [rbp-8], 0xa          ; 后 4 字节初始化为 10,对应源码:int tmp = 10;
0000000100003f76         mov        edi, dword [rbp-8]          ; 把这个变量交给 edi
0000000100003f79         call       imp___stubs__add            ; add 方法的延迟绑定处理
0000000100003f7e         xor        ecx, ecx                    ; 异或自身,即把 ecx 赋为 0
0000000100003f80         mov        dword [rbp-8], eax          ; eax 保存 add 方法的返回值,赋回给变量
0000000100003f83         mov        eax, ecx                    ; 把 eax 清理为 0
0000000100003f85         add        rsp, 0x10                   ; 归还栈空间
0000000100003f89         pop        rbp                         ; 取回栈帧指针
0000000100003f8a         ret                                    ; 返回
                        ; endp
  • __TEXT,__stubs
                     imp___stubs__add:        // add
0000000100003f8c         jmp        qword [_add_ptr]            ; 直接跳转到另一个地址 (void *)0x0000000100003fa4
                        ; endp
  • __TEXT,__stub_helper
0000000100003f94         lea        r11, qword [__dyld_private] ; Load Effective Address
0000000100003f9b         push       r11                         ; 0x0000000100008008  _dyld_private 在 data 段
0000000100003f9d         jmp        qword [dyld_stub_binder_100004000]
																; (void *)0x00007fff6d355578: dyld_stub_binder 我这标的是运行时地址
0000000100003fa3         nop
0000000100003fa4         push       0x0                         ; add 绑定先跳到了这里,入栈 0x0
0000000100003fa9         jmp        0x100003f94                 ; 直接跳转到 0x100003f94,即这里的第一条指令
  • __DATA_CONST,__got

dyld_stub_binder 这个符号来自于 libdyld.dylib,由于已经绑定好了,所以上面跳转到其运行时地址 0x00007fff6d355578

                     dyld_stub_binder_100004000:        // dyld_stub_binder
0000000100004000         extern     dyld_stub_binder                            ; DATA XREF=0x100003f9d

这个函数非常长,这里就不展开了。

这个函数结束后,add 函数在内存中就已经有了,而且 dyld_stub_binder 最后一条指令会通过寄存器跳转到 add 函数。

image.png

再回去看 imp___stubs__add,看看现在的指令跳转是否被修正了:

image.png

之后就会直接跳转到 add 函数了。

实际上当程序启动时,相关的库或者函数已经被加载到内存里了,dyld_stub_binder 只是一个查找并绑定的操作。

image.png

2.4、-undefined dynamic_lookup

如果依赖了其他地方的符号,可能会报 undefined 错误,静态库的话可以直接用 -c 参数处理,即不链接。

gcc -c add.c -o add.o
ar rcs libadd.a add.o
gcc main.c libadd.a -o main

如果是动态库用了 -c 就没法编成动态库了:

image.png

image.png

可以采用标题的 -undefined dynamic_lookup 参数,把符号查找推迟到运行时:

(对静态库的 undefined 符号同样有用)

image.png

image.png

这种符号在动态库中会被变成 indirect symbol,由 dyld 代为查找。

但如果 dyld 在装载时或者运行时还找不到符号,则会导致 crash,所以最好还是把问题提前到编译链接期暴露,慎用!

image.png

Note that -undefined dynamic_lookup is OSX-specific. Linux shared libraries behave as if -undefined dynamic_lookup was enabled all the time.

I don’t recommend to enable global dynamic lookup:

-undefined dynamic_lookup which will mark all undefined symbols as having to be looked up at runtime.

Much more safe way to resolve it for specific symbols:

-Wl,-U,symbol_name, which only does so for the given symbol (note: you have to prepend an underscore to the symbol name)

You could also use weak dynamic linking:

extern int SayHello() __attribute__((weak));

链接器发现同时存在弱符号和强符号,有限选择强符号,如果发现不存在强符号,只存在弱符号,则选择弱符号。如果都不存在:静态链接,恭喜,编译时报错,动态链接:对不起,系统无法启动。

// strong.c  // 生成libstrong.so
#include <stdio.h>
void real_func()
{
    printf("int real func\n");
}
 
// weak.c // 生成libweak.so
#include <stdio.h>
void real_func() __attribute__((weak));
void real_func()
{
    printf("fake func\n");
}
 
// main.c //
#include <stdio.h>
extern void real_func();
void main()
{
        real_func();
}

2.5、无用代码

由于动态库无法判断哪些符号会被外部引用,即其所有 .o 都会被保留,那么就会占用内存空间和包大小。

image.png

如上图,我写了一个没用到的函数,它也被加载到内存空间了。

3、静态链接

根据苹果文档的描述,其并不支持静态链接:

  • A static library is a library of code that can be linked into a binary that will, eventually, be dynamically linked to the system libraries and frameworks.

  • A statically linked binary is one that does not import system libraries and frameworks dynamically, but instead makes direct system calls into the kernel.

Apple fully supports static libraries; if you want to create one, just start with the appropriate Xcode project or target template.

Apple does not support statically linked binaries on Mac OS X. A statically linked binary assumes binary compatibility at the kernel system call interface, and we do not make any guarantees on that front. Rather, we strive to ensure binary compatibility in each dynamically linked system library and framework.

这里需要区分静态库和静态链接。

-static     Produces a mach-o file that does not use the dyld.  Only used building the kernel.

所以我们在 MacOS 上对于静态库只是链接操作,不能称之为静态链接。

image.png

硬是想弄出来也行,参考文章:https://oao.moe/archives/937/

参考&推荐阅读

LC_ID_DYLIB:https://stackoverflow.com/questions/42210767/what-is-the-significance-of-a-macos-mach-o-dylib-lc-id-dylib-name-or-install-na

Statically linked binaries on Mac OS X:_https://developer.apple.com/library/archive/qa/qa1118/index.html

动态链接:https://www.jianshu.com/p/5deed1bf82b4

Clang and undefined symbols when building a library:https://stackoverflow.com/questions/25421479/clang-and-undefined-symbols-when-building-a-library

attribute((weak)) 用法:https://blog.csdn.net/sea_snow/article/details/83650519

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值