美文网首页
7章: 动态链接

7章: 动态链接

作者: my_passion | 来源:发表于2022-07-17 10:34 被阅读0次

    1 引入

    (1) 静态链接 2个问题

    1) 2个进程 (prog1 / prog2) 共享 .o 时, 磁盘和内存中 要存 2 份 .o => 空间资源浪费

    2) 1个 (不依赖其他 .o 的) .o 变, 所有 .o 要重新链接 => 程序 更新、部署、发布 难

        |
        |   引入
        |/
    

    (2) 动态链接

    1) 思想: 把 链接/重定位 过程 (从 静态链接的 链接时) 延迟到 装载/运行时

        [1] 多进程 `共享的 .o` 在磁盘和内存中 `只存 1 份`
        
        [2] 1个 .o(不依赖其他 .o) 变 -> 只要 覆盖该 .o 
    

    [3] 可扩展性&兼容性 好, 插件(Plug-in)式: 运行时 可 动态(选择)装载 各模块

    2) 实现: 把程序 按模块拆分 成各 (相对) 独立 的 part, 程序 装载/运行时 再链接

        动态链接文件
                        
            Linux 
                `动态共享对象` DSO (Dynamic Shared Objects); 即 `elf 动态链接文件`
                    .so
                        C 运行库 glibc: /lib/libc.so
            Windows 
                动态链接库 DLL (Dynamical Linking Library)
                .dll
    

    Note: 理论上可直接用 .o 进行动态链接

    3) 动态链接符号 (运行时)地址空间: 装载时, 由 装载器 动态分配

    2 例子

        proj1.c  -> 编译 —— —— —— -> proj1.o 
                    \ 共                 | 
                     \                  Linker
                      \                     |
                        Lib.c -> 编译 -> Lib.so: 动态符号 foobar 
                     /            |
                    / 享      gcc -fPIC -shared -o Lib.so Lib.c
        proj1.c                         
        
        
        linker 发现 proj1.o 和 proj2.o 中引用的 符号 foobar 是 Lib.so 中的 动态符号 => `重定位 延迟到 装载时`
    

    3 地址无关代码

    (1) 共享对象 固定装载地址: 地址冲突

    (2) 装载时 重定位

    问题: 指令无法 在 多进程间 共享

        解决:
    

    (3) 地址无关 代码 PIC (Position-Independent Code)

    1) 思想: 将 指令中 需要重定位 的部分 分离, 与 数据 放一起

    2) 共享对象4 种寻址模式

    共享模块 中 地址引用是否跨模块 分为2类: 模块 内/外 引用

        ——————————————————————————————————————————————————————————
                    4种 地址引用
        ——————————————————————————————————————————————————————————      
               | 调用、指令跳转                |   数据访问
        ——————————————————————————————————————————————————————————  
        模块内 | (1) 相对 跳转、调用      | (2) `相对` 地址访问
        模块外 | (3) 间接 跳转、调用 (GOT)    | (4) `间接` 访问 (GOT)
        ——————————————————————————————————————————————————————————  
    

    [1] 模块内 地址引用: 相对 位置固定

        1] `callee 与 caller` 间: `相对 地址调用 ( call )`
    
        2] `指令` 与它要访问的 `数据` 间: 只需 相对于 PC 所指当前指令 加上 一 固定偏移
    
            转化为
            - - - > 相对于 `PC 所指 call <__i686.get_pc_thunk.cx> 指令
                    的 next 指令( 函数 ...get_pc_thunk... 返回地址: 放 %ecx) 加上一固定偏移
    

    [2] 模块外 地址引用: GOT (Global Offset Table, 全局偏移表)

        pointer array 
        
        各 表项 `指向` 放在 `进程虚拟内存 数据段(.data VMA) 中 地址相关的 func/var`
    

    3) -fPIC / -fPIE: 产生 地址无关代码

    4 延迟绑定(Lazy Binding)

    动态链接下, 一开始就 link 所有 func => 程序 启动速度 慢

    func 第1次 被用到时, 才绑定 (符号查找、重定位) => 加快 程序启动速度

    实现: PLT (Procedure Linkage Table): GOT + 增加1个 中间层 间接跳转

        1) PLT
            _dl_runtime_resolve(moduleReferingFunc, func)
                将 func/bar 最终映射在 `.data VMA 中的 地址` 填到  GOT 的 `bar@GOT 表项`
                                                       |    
        2) PLT 结构     bar@GOT             GOT 结构 |/
                表项 —— —— —— —— —— —— —— ——>          表项 —— —— ——> `bar()` 在 进程虚拟空间 `.data VMA` 中 的 `地址`
                     GOT 中 指向 bar的 表项
    

    5 动态链接 相关 结构 : 存在 ELF 可执行文件

    动态链接
        OS 装载 可执行文件
            |
            |   控制权 转交
            |/
        
        动态链接器  的 入口地址 
            |     \
            |      \ Linux 下, 是 
            |       \
            |        共享对象 ld.so -> 被 OS 装载进 进程地址空间
            |/
        自身初始化 + 动态链接 
            |
            |   控制权 转交
            |/
        可执行文件 的 入口地址
        
                        
    (1) .interp 段 // (interpreter 解释器)
        
        1) 作用
            `ELF 可执行文件` 中 `.interp  段 决定 `动态链接器 的 位置`
                                
        2) 内容                                           Linux 下
            字符串 = 可执行文件 所需 动态链接器的 路径 —— —— ——> /lib/ld-linux.so.2
    
    (2) .dynamic 段
        
        存 动态链接器 所需基本信息
            动态链接 符号表位置/重定位表位置
            依赖于哪些共享对象
            共享对象 初始化代码 的 地址
    
    (3) 动态符号表 .dynsym
    
               依赖于
        proj1  — —— —> Lib.so
               \        |
                \       | 定义 -> foobar 是 Lib.so 的 导出函数 (Export Function)
           引用  \        |
            |     \/    |/
            |     foobar() 函数
            |       
            |/  
        foobar 是 proj1 的 `导入函数 (Import Function)`
            
        
        动态符号表 .dynsym
            
    (4) 动态链接 重定位表
    
        可执行文件 / 共享对象, 一旦 `依赖于 other 共享对象` ( 即, `有 导入导出符号`)
            其 code 或 data 中就会 `引用 导入符号`                         
                导入符号地址 在 运行时 才确定
                    => 运行时 重定位
                    
        用 PIC 的 可执行文件/共享对象 也需要 重定位
                    |
                    |
                    |/
                代码段 中 绝对地址的引用 被 分离 -> 变成 GOT -> 放 数据段
                
                数据段 中 除了 GOT 之外, 还有 数据本身 绝对地址的引用
            
        ————————————————————————————————————————————————————————————    
        重定位表    |   修正 data 引用          |   修正 函数引用
        /修正的位置  |                           |
        ————————————————————————————————————————————————————————————
        静态链接    |   .rel.data               |   .rel.text
        ————————————————————————————————————————————————————————————
        动态链接    |   .rel.dynamic + 数据段  |   .rel.plt 
                    |    / .got(也在数据段)      |    / .got.plt
        ————————————————————————————————————————————————————————————
    

    6 动态链接 步骤 与 实现

        (1) 3 step
        
            1) 动态链接器 `自举(BootStrap)`
    
                ——————————————————————————————————————
                                  | 由谁完成 重定位 ?
                ——————————————————————————————————————                                  
                    普通共享对象  | 动态链接器
                ——————————————————————————————————————
                    动态链接器     | 自身 
                    (特殊共享对象)|
                ————————|——————————————————————————————     
                        |/
                `鸡生蛋, 蛋生鸡` 的 无限循环问题 
                        |
                        |   解决
                        |/
                    2个问题
                        动态链接器
                            1> 本身 不能依赖 other 共享对象
                                -> 编程时 人为控制
                        
                            2> 本身 所需 globalVar 和 staticVar 的 重定位 由自身完成
                                                          称
                                -> 启动时 用 精巧的代码 - - -> 自举
                
            2) 装载 共享对象
                
                [1] 从 可执行文件 开始 构建 `依赖(共享对象)关系图`
                    `.dynamic 段` 中, 入口为 `DT_NEEDED` 的 类型 -> 指出的是 `所依赖的共享对象`
                    
                [2] 图遍历: 广度优先 / 深度优先
                    打开 共享对象
                        读 ELFHeader 和 .dynamic 段 
                            相应 代码/数据段 映射到 进程空间
                            符号表 合并到 `全局符号表`
                    
            
            3) 重定位 与 初始化
        
        (2) Linux 动态链接器 实现
        
            OS 内核 装载完 `ELF 可执行文件` 
                |                           1) `ELFHeader 中 e_entry`
                |/                       /  
            返回 用户空间                 / 静态链接 (没 .interp 段)    
            + 控制权 转交给  程序入口     
                    |                   \ 动态链接 (有 .interp 段)
                    |                    \  
            据 ELF 文件是否有 .interp 段  \ 
                                           \
                                            2) 内核 分析 `动态链接器 地址(在 .interp 段)` 
                                                |
                                                |
                                                |/
                                            将 动态链接器 映射到 进程空间
                                                |
                                                |
                                                |/
                                            控制权 转交给 `动态链接器 的 e_entry`
                
                共享库 与 可执行文件 没本质区别
                    ELFHeader 的 标志位 和 扩展名       
        
        (3) 3个问题 `动态链接器` (ld.so)
            
            1)  本身 `是 动态/静态链接` ?
                静态
                    它 不能依赖 other 共享对象
            
            2) 本身 `必须是 PIC 吗` ?
                可以是(更简单), 也可以不是(代码段 无法共享 / 自举时 还要对 代码段 重定位)
            
            3) 可被当作 可执行文件 运行, 那它的 `装载地址` 是多少 ?
                0x00000000 -> 无效地址 -> 作为 `共享库`, OS 内核 装载 ld.so 时, 会为其 选择合适的装载地址 
    

    7 显式运行时 链接

    动态链接器 提供 3 个 API

            (1) dlopen()  打开动态库
            (2) dlsym()   查找符号
            (3) dlerror() 错误处理
            (4) dlclose() 关闭动态库
    
    静态链接: 磁盘/内存 中 2 份共享库 Lib.o.jpg 静态链接: 磁盘/内存 中 1 份共享库 Lib.so/o.jpg 动态链接过程.jpg 4种 寻址模式.jpg 相对偏移 调用指令.jpg 模块 内 数据访问.jpg 模块 间 数据访问.jpg 模块间 调用、跳转.jpg GOT 中 PLT.jpg Lib.so 中 .got.plt 结构.jpg

    相关文章

      网友评论

          本文标题:7章: 动态链接

          本文链接:https://www.haomeiwen.com/subject/qqgrirtx.html