热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Unix编程静态链接重定位

静态链接重定位静态链接过程会将汇编器生成的可重定位文件(*.o)合并到一个可执行文件中。当链接器完成符号解析后,就会执行重定位过程。重定位过程分为两步

静态链接·重定位

静态链接过程会将汇编器生成的可重定位文件(*.o)合并到一个可执行文件中。当链接器完成符号解析后,就会执行重定位过程。重定位过程分为两步:


  • 重定位节及符号定义。相同类型的节会被聚合到可执行文件的同一类型的节中,如下图的步骤一。符号解析过程已经确定了符号被定义的模块及节位置,所以重定位节后,每个符号的运行时地址也已经确定了。
  • 重定位节中的符号引用。如下图的步骤二,在编译main.o时,引用的符号func是由其它模块定义的,编译器无法确定该符号的运时地址,此时编译器所做的是为每个引用的位置记录一个重定位条目信息。链接器根据此信息进行符号引用重定位。


程序代码


main.c

#include const static char *s_str = "a static string";
extern int g_num;int func();static int func_st()
{return --g_num;
}int main()
{int ret = func();int ret2 = func_st();printf("str: %s, ret: %d, ret2: %d\n", s_str, ret, ret2); return 0;
}

func.c

static int s_num = 200;
int g_num = 200;int func()
{s_num++;return s_num;
}

链接示意

       main.o中引用了三个外部符号,其中g_num、func由func.o定义,使用静态的链接方式。printf是标准库定义的函数,如果其它模块都没定义,链接器会默认从标准库libc.so中查找。可以使用-static选项表明要生成一个完全的可执行文件,这样链接器会选择从libc.a库静态链接相应模块到可执行文件里。


 


汇编代码及重定位条目


main.o 汇编代码

[guest@linux ~]$ objdump -D main.omain.o: file format elf64-x86-64Disassembly of section .text:0000000000000000 :0: 55 push %rbp1: 48 89 e5 mov %rsp,%rbp4: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # a a: 83 e8 01 sub $0x1,%eaxd: 89 05 00 00 00 00 mov %eax,0x0(%rip) # 13 13: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 19 19: c9 leaveq 1a: c3 retq 000000000000001b

:1b: 55 push %rbp1c: 48 89 e5 mov %rsp,%rbp1f: 53 push %rbx20: 48 83 ec 18 sub $0x18,%rsp24: b8 00 00 00 00 mov $0x0,%eax29: e8 00 00 00 00 callq 2e 2e: 89 45 e8 mov %eax,-0x18(%rbp)31: b8 00 00 00 00 mov $0x0,%eax36: e8 c5 ff ff ff callq 0 3b: 89 45 ec mov %eax,-0x14(%rbp)3e: 48 8b 1d 00 00 00 00 mov 0x0(%rip),%rbx # 45 45: b8 00 00 00 00 mov $0x0,%eax4a: 8b 4d ec mov -0x14(%rbp),%ecx4d: 8b 55 e8 mov -0x18(%rbp),%edx50: 48 89 de mov %rbx,%rsi53: 48 89 c7 mov %rax,%rdi56: b8 00 00 00 00 mov $0x0,%eax5b: e8 00 00 00 00 callq 60 60: b8 00 00 00 00 mov $0x0,%eax65: 48 83 c4 18 add $0x18,%rsp69: 5b pop %rbx6a: c9 leaveq 6b: c3 retq Disassembly of section .data:0000000000000000 :00: 0000000000000000 Disassembly of section .rodata: &#xff08;objdump -s main.o)0000000000000000 <.rodata>:00: 61207374 61746963 20737472 696e6700 a static string.10: 7374723a 2025732c 20726574 3a202564 str: %s, ret: %d20: 2c207265 74323a20 25640a00 , ret2: %d..

        可以看出main.o不是一个可执行的文件如&#xff1a;.text节偏移0x29处的指令为" e8 00 00 00 00 callq "&#xff0c; "e8 "表示这是一条函数调用指令&#xff0c;但是并没有给出最终的函数位置&#xff1b;在.data节保存的s_str变量的值为0x00&#xff0c;但在原始代码里s_str为已初始化的指针&#xff0c;初始值为一个常量字符串起始地址。

 


main.o 重定向位目信息

[guest&#64;linux ~]$ readelf -r main.o Relocation section &#39;.rela.text&#39; at offset 0x6e0 contains 7 entries:Offset Info Type Sym. Value Sym. Name &#43; Addend
000000000006 000b00000002 R_X86_64_PC32 0000000000000000 g_num - 4
00000000000f 000b00000002 R_X86_64_PC32 0000000000000000 g_num - 4
000000000015 000b00000002 R_X86_64_PC32 0000000000000000 g_num - 4
00000000002a 000d00000002 R_X86_64_PC32 0000000000000000 func - 4
000000000041 000300000002 R_X86_64_PC32 0000000000000000 .data - 4
000000000046 00050000000a R_X86_64_32 0000000000000000 .rodata &#43; 10
00000000005c 000e00000002 R_X86_64_PC32 0000000000000000 printf - 4Relocation section &#39;.rela.data&#39; at offset 0x788 contains 1 entries:Offset Info Type Sym. Value Sym. Name &#43; Addend
000000000000 000500000001 R_X86_64_64 0000000000000000 .rodata &#43; 0

        .rela.text及.rela.data指示了需要被重定向的位置及重定向方式&#xff0c;关注以下字段&#xff1a;


    • Offset&#xff1a;重定向项在节中的偏移。
    • Type&#xff1a; 重定向类型。
    • Sym.Name 、 Addend&#xff1a;重定向的符号及偏移修正。

 


重定向符号引用

        假设链接过程中&#xff0c;已经确定了每个重定向模块的节及其它全局符号在加载时的地址。以s代表当前处理的节&#xff0c;t为重定向的项&#xff0c;Addr(s/sym)为节或符号的地址&#xff0c;那么重定向条目指示的修正值计算方式如下。

 

foreach sforeach tRelAddr &#61; Addr(s) &#43; t.Offset //重定向的项的地址... if t.Type &#61;&#61; R_X86_64_PC32 then*RelAddr &#61; Addr(t.Sym.Name) - RelAddr &#43; t.Addend //Addr(t.Sym.Name) - PC&#39;endif t.Type &#61;&#61; R_X86_64_32 then*RelAddr &#61; Addr(t.Sym.Name) &#43; t.Addendendend
end

        先看.rela.text节中类型为R_X86_64_PC32及R_X86_64_32中的两项。

 

Relocation section &#39;.rela.text&#39; at offset 0x6e0 contains 7 entries:Offset Info Type Sym. Value Sym. Name &#43; Addend
...
00000000002a 000d00000002 R_X86_64_PC32 0000000000000000 func - 0x4
...
000000000046 00050000000a R_X86_64_32 0000000000000000 .rodata &#43; 0x10
...

        对应的main.o中的指令位置。

 

Disassembly of section .text:0000000000000000 :
...000000000000001b

:
...29: e8 00 00 00 00 callq 2e 2e: 89 45 e8 mov %eax,-0x18(%rbp)
...45: b8 00 00 00 00 mov $0x0,%eax
...

        假设链接过程中已经知道&#xff1a;main.o中的.text节的起始地址为0x0400534&#xff0c;.rodata 节的起始地址0x4006ac&#xff0c;且符号func的地址为0x04005a0。那么以上的重定向值计算过程如下所示&#xff1a;

R_X86_64_PC32:

 

RelAddr &#61; Addr(.text) &#43; t.Offset &#61; 0x0400534 &#43; 0x02a &#61; 0x40055e;
*RelAddr &#61; Addr(func) - RelAddr &#43; t.Addend &#61; 0x04005a0 - 0x40055e &#43; (-4) &#61; 0x3e

R_X86_64_32:

 

RelAddr &#61; Addr(.text) &#43; t.Offset &#61; 0x0400534 &#43; 0x046 &#61; 0x40057a;
*RelAddr &#61; Addr(.rodata) &#43; t.Addend &#61; 0x4006ac &#43; 0x10 &#61; 0x4006bc;

重定向后的结果:

 

000000000040054f

:
...40055d: e8 3e 00 00 00 callq 4005a0 400562: 89 45 e8 mov %eax,-0x18(%rbp)
...400579: b8 bc 06 40 00 mov $0x4006bc,%eax
...

        R_X86_64_32类型的重定向&#xff0c;填入的修正值是重定向符号的绝对地址。对于static变量、static函数及字符串常量等&#xff0c;其实是不需要分配全局符号的&#xff0c;使用定义了该本地符号&#xff08;或数据&#xff09;的节&#43;节内偏移&#xff08;Addend&#xff09;的方式即可标识&#xff08;上面就是用了".rodata &#43; 0x10"标识了代码中的字符串常量&#xff09;。

        R_X86_64_PC32类型的重定向&#xff0c;填入的修正值是一个相对地址。这是因为修改的地方所对应的指令其实是采用了PC相对寻址的方式取值。在开始执行一条指令的时候&#xff0c;PC计数器总是保存下一条顺序执行的指令地址&#xff0c;如下图所示。利用这一点&#xff0c;就可以用重定向位置(RelAddr)及该位置与PC值的偏差(Addend)得到PC的值&#xff0c;最后计算出的修正值其实就是: *RelAddr &#61; 符号绝对地址- 指令执行时的PC值。

                           

 

 

 

 

 

 

 


推荐阅读
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 本文介绍了C函数ispunct()的用法及示例代码。ispunct()函数用于检查传递的字符是否是标点符号,如果是标点符号则返回非零值,否则返回零。示例代码演示了如何使用ispunct()函数来判断字符是否为标点符号。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
  • C++中的三角函数计算及其应用
    本文介绍了C++中的三角函数的计算方法和应用,包括计算余弦、正弦、正切值以及反三角函数求对应的弧度制角度的示例代码。代码中使用了C++的数学库和命名空间,通过赋值和输出语句实现了三角函数的计算和结果显示。通过学习本文,读者可以了解到C++中三角函数的基本用法和应用场景。 ... [详细]
author-avatar
手机用户2702936061
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有