动态链接库中的延迟绑定

  • 润物细无声
  • 2024-04-30 15:18

    在 Linux 平台上,动态库加载到内存之后,通常来说是被所有程序进程共享的,即不同进程通过不同的地址映射共享同一份物理内存的动态库代码。那么某个程序调用动态库中的某个函数,是如何实现的呢?

    是通过延迟绑定的实现的。

    这里介绍一些名称:

    延迟绑定代码

    通过反汇编,如果程序调用动态库的函数 libfoo,在编译时,代码是这样安排的:

    .got.plt                        # 数据段,可修改
        ... ...
        .got.plt[0]: label_0        # libfoo@plt 存根数据
        .got.plt[1]: label_1        # <other_foo_1@plt> 存根数据
        .got.plt[2]: label_2        # <other_foo_2@plt> 存根数据
    
    ... ...
    ... ...
    ... ...
    
    .plt                            # 代码段,不可修改
        <default stub>:             # 默认存根
            push <指针标识符>        # 可以表示进程的指针,也是从 .got.plt 获取的
            jmp  <动态链接器>        # 跳转到动态链接器执行
    
        <libfoo@plt> :              # <libfoo@plt> 存根
            jmp .got.plt[0]         # 从存根数据中取出地址跳转去执行
        label_0:
            push 0x0                # 压入编号,这里编号是程序中的,不是动态库中的
            jmp <default stub>
    
        <other_foo_1@plt> :         # <other_foo_1@plt> 存根
            jmp .got.plt[1]         # 从存根数据中取出地址跳转去执行
        label_1:
            push 0x1                # 根据编译,顺序压入
            jmp <default stub>
    
        <other_foo_2@plt>:          # <other_foo_2@plt> 存根
            jmp .got.plt[2]         # 从存根数据中取出地址跳转去执行
        label_2:
            push 0x2                # 表示是程序中调用的第几个动态库函数
            jmp <default stub>
    
    ... ...
    ... ...
    ... ...
    
    .text                           # 代码段,不可修改
        <main>:
            ... ...
            # 这里正常准备参数
            call libfoo@plt         # main 函数调用动态库中的 libfoo 函数
            ... ...
    

    模拟一下调用过程

    1. main 函数调用 libfoo
    2. 准备好参数之后,call 指令进入了存根函数中:
        <libfoo@plt> :
            jmp .got.plt[0]         # .got.plt[0] 存放着 label_0 的地址
                                    # 所以这里跳转到下面执行,相当于平白无故绕了个弯
        label_0:
            push 0x0                # 压入编号 0
            jmp <default stub>      # 跳转到默认存根函数中
    
    1. 进入默认存根函数 default stub 中:
        <default stub>:
            push <指针标识符>        # 可以表示进程的指针,也是从 .got.plt 获取的
            jmp  <动态链接器>        # 跳转到动态链接器执行
    
    1. 进入动态链接器执行,它需要完成的工作:
      • 根据压入的指针标识符和序号,找到调用库函数 libfoo 真正的地址
      • 根据压入的序号,将 libfoo 地址写入 .got.plt[0]
      • 跳转到 libfoo 执行,完成调用工作
    2. 当第二次调用 libfoo 时,由于 .got.plt[0] 已经写入 libfoo 真正的地址,所以 call 指令之后执行的 jmp 将会直接跳转到 libfoo 函数执行。
      • .got.plt 是数据段,可以修改,更安全一些
      • 所以,不能直接修改代码段,导致 call 调用多执行一条 jmp 指令

    总结

    最开始 .got.plt 存放着 label 的地址,也就是 jmp 下一条指令地址,经过第一次访问之后,动态链接器将修改 .got.plt 表,达到绑定的动态链接,延迟绑定的目的。之后调用动态库中的函数就不用经过动态链接中转了。

    © 2022-2024 留校察看 liuxocakn 保留所有权利 All Rights Reserved
    蜀ICP证2022022862号-1 川公网安备51010702003077号

    自 2024-04-23 22:54 之后访问量: 66470