码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 链接、加载


    链接

    话不多说,先看例子

    test.c

    #include 
    
    int a = 100;
    
    void main()
    {
        printf("main = %p\n", main);
        printf("&a = %p\n", &a);
        printf("a = %d\n", a);
        *(int *)0x4000010 = 20;
        printf("a = %d\n", a);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    test.lds

    SECTIONS
    {
        .text 0x30000000:
        {
            *(.text)
        }
        
        .data 0x4000000:
        {
            *(.data)
        }
        
        .bss :
        {
            *(.bss)
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    $ gcc test.c test.lds -o test -no-pie
    $ ./test 
    main = 0x300000e6
    &a = 0x4000010
    a = 100
    a = 20
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    位置无关码 & 位置有关码

    PIE(position-independent executable,位置无关码),no-pie 就是位置有关码。

    PIE 还有个孪生兄弟 PIC(position-independent code)。其作用和 PIE 相同,都是使被编译后的程序能够随机的加载到某个内存地址。区别在于 PIC 是在生成动态链接库时使用(Linux 中的 so),PIE 是在生成可执行文件时使用。

    链接地址 ≠ 运行地址:位置无关码

    链接地址 = 运行地址:位置有关码

    示例解释

    上述示例,编译时使用了 -no-pie 选项,故编译出来的代码是位置有关码,即,链接地址 = 运行地址。我们来检查下是否符合预期。

    通过 readelf -h test 看到,程序的入口地址为 0x30000000,和我们链接脚本中的 .text 0x30000000 一致。

    $ readelf -h test
    ELF 头:
      Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
      类别:                              ELF64
      数据:                              2 补码,小端序 (little endian)
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI 版本:                          0
      类型:                              EXEC (可执行文件)
      系统架构:                          Advanced Micro Devices X86-64
      版本:                              0x1
      入口点地址:               0x30000000
      程序头起点:          64 (bytes into file)
      Start of section headers:          22848 (bytes into file)
      标志:             0x0
      Size of this header:               64 (bytes)
      Size of program headers:           56 (bytes)
      Number of program headers:         15
      Size of section headers:           64 (bytes)
      Number of section headers:         31
      Section header string table index: 30
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    从下面的反汇编可以看到,main 函数的地址为 0x300000e6,和打印一致,全局变量 a 的地址为 0x4000010,和打印一致。

    $ objdump -d test
    
    test:     文件格式 elf64-x86-64
    
    ...
    
    Disassembly of section .text:
    
    0000000030000000 <_start>:
        30000000:   f3 0f 1e fa             endbr64 
        30000004:   31 ed                   xor    %ebp,%ebp
        30000006:   49 89 d1                mov    %rdx,%r9
        30000009:   5e                      pop    %rsi
        3000000a:   48 89 e2                mov    %rsp,%rdx
        3000000d:   48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
        30000011:   50                      push   %rax
        30000012:   54                      push   %rsp
        30000013:   49 c7 c0 d0 01 00 30    mov    $0x300001d0,%r8
        3000001a:   48 c7 c1 60 01 00 30    mov    $0x30000160,%rcx
        30000021:   48 c7 c7 e6 00 00 30    mov    $0x300000e6,%rdi
        30000028:   ff 15 c2 2f 00 00       callq  *0x2fc2(%rip)        # 30002ff0 <__libc_start_main@GLIBC_2.2.5>
        3000002e:   f4                      hlt    
        3000002f:   90                      nop
    
    ...
    
    00000000300000e6 
    : 300000e6: f3 0f 1e fa endbr64 300000ea: 55 push %rbp 300000eb: 48 89 e5 mov %rsp,%rbp 300000ee: 48 8d 35 f1 ff ff ff lea -0xf(%rip),%rsi # 300000e6
    300000f5: 48 8d 3d 08 0f 00 00 lea 0xf08(%rip),%rdi # 30001004 <_IO_stdin_used+0x4> 300000fc: b8 00 00 00 00 mov $0x0,%eax 30000101: e8 3a 0f 40 d0 callq 401040 30000106: 48 8d 35 03 ff ff d3 lea -0x2c0000fd(%rip),%rsi # 4000010 3000010d: 48 8d 3d fb 0e 00 00 lea 0xefb(%rip),%rdi # 3000100f <_IO_stdin_used+0xf> 30000114: b8 00 00 00 00 mov $0x0,%eax 30000119: e8 22 0f 40 d0 callq 401040 3000011e: 8b 05 ec fe ff d3 mov -0x2c000114(%rip),%eax # 4000010 30000124: 89 c6 mov %eax,%esi 30000126: 48 8d 3d eb 0e 00 00 lea 0xeeb(%rip),%rdi # 30001018 <_IO_stdin_used+0x18> 3000012d: b8 00 00 00 00 mov $0x0,%eax 30000132: e8 09 0f 40 d0 callq 401040 30000137: b8 10 00 00 04 mov $0x4000010,%eax 3000013c: c7 00 14 00 00 00 movl $0x14,(%rax) 30000142: 8b 05 c8 fe ff d3 mov -0x2c000138(%rip),%eax # 4000010 30000148: 89 c6 mov %eax,%esi 3000014a: 48 8d 3d c7 0e 00 00 lea 0xec7(%rip),%rdi # 30001018 <_IO_stdin_used+0x18> 30000151: b8 00 00 00 00 mov $0x0,%eax 30000156: e8 e5 0e 40 d0 callq 401040 3000015b: 90 nop 3000015c: 5d pop %rbp 3000015d: c3 retq 3000015e: 66 90 xchg %ax,%ax ...
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    由于是位置有关码,运行地址 = 链接地址,我们能够很清楚地知道在运行时代码所处的地址,全局变量所处的地址。在示例代码中,我们将 data 段的地址设置为了 0x4000000,则全局变量 a 就落在这个区间,为 0x4000010。这样,甚至,我们可以在代码中直接操作这个地址,将该地址储存的值 100 改为 20,也就是示例中的效果。

    位置无关码

    test.c

    #include 
    
    int a = 100;
    
    void main()
    {
        printf("main = %p\n", main);
        printf("&a = %p\n", &a);
        // printf("a = %d\n", a);
        // *(int *)0x4000010 = 20;
        // printf("a = %d\n", a);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    test.lds 和示例一保持不变

    去除位置有关码编译选项,编译得到位置无关码,运行

    $ gcc test.c test.lds -o test
    liyongjun@Box:~/project/c/ld/4$ ./test 
    main = 0x564ec6e1e0e9
    &a = 0x564e9ae1e010
    liyongjun@Box:~/project/c/ld/4$ ./test 
    main = 0x55a23683f0e9
    &a = 0x55a20a83f010
    liyongjun@Box:~/project/c/ld/4$ ./test 
    main = 0x558bf273a0e9
    &a = 0x558bc673a010
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    可以看到,每次运行,运行地址都不一样,都是加载器随机选择内存的。

    位置无关码介绍

    因为可执行程序的代码段只有读和执行属性没有写属性,而数据段具有读写属性。要实现地址无关代码,就要将代码段中需要改变的值分离到数据段中,而程序加载时可以保存代码段不变,通过改变数据段中的内容,实现地址无关代码。

    Non-PIE

    执行程序会在固定的地址开始加载。系统的动态链接器库 ld.so 会首先加载,接着 ld.so 会通过 .dynamic 段中类型为 DT_NEED 的字段查找其他需要加载的共享库,并依次将它们加载到内存。

    注意:因为是 Non-PIE 模式,这些动态链接库每次加载的顺序和位置都一样。

    PIE

    而对于通过 PIE 方式生成的执行程序,因为没有绝对地址引用,所以每次加载的地址都不尽相同。

    不仅动态链接库的加载地址不固定,就连执行程序每次加载的地址也不一样。

    这就要求 ld.so 首先被加载,然后它不仅要负责重定为其它共享库,还要对可执行文件重定位。

    共享动态库原理

    多个进程间共享动态链接库的原理

    多个进程都链接同一个so动态库,代码段共享,数据段不共享

  • 相关阅读:
    go-10-字符串操作
    Spring Cloud微服务核心架构分析
    2022年最新青海水利水电施工安全员模拟试题题库及答案
    Aip接口自动化测试框架pytest+allure+request+jsonpath+excle
    Tableau概述
    EF Core 数据过滤
    pycharm的常用快捷键
    mysql子查询
    HugeGraph1.0.0部署,吐槽一下Hubble的数据导入 Bug
    linux-day01
  • 原文地址:https://blog.csdn.net/lyndon_li/article/details/126483567
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号