首页
社区
课程
招聘
32位elf格式中的10种重定位类型
发表于: 2018-8-20 02:21 20546

32位elf格式中的10种重定位类型

2018-8-20 02:21
20546

    首先需要知道的是,一个程序从源码到被执行,当中经历了3个过程:

      

    c程序中引用全局变量的语句,经过编译得到的机器码会包含一个地址值部分,机器码执行时,该值必须为变量在内存中的绝对地址,调用函数的语句,经过编译得到的机器码也包含一个地址值部分,机器码执行时,该值必须为内存中函数地址与下一条指令地址的偏移。但是在编译、静态链接,甚至动态链接之后,该地址值部分可能暂时无法满足最终要求,从而必须相应设置一个重定项,要求后续过程对该值进行修改,重定项一方面标记了地址值的位置,另一方面提供了计算正确地址值的方法和计算参数。

    对于局部变量的使用,由于程序执行时,esp寄存器保存的一定是栈顶的内存地址,那么从逻辑上讲,编译阶段就可以确定所有局部变量运行时的内存地址,所以不需要设置重定项。

    另外,elf格式中设计了10种不同的重定位类型,是由于不同场合,对地址值进行重新计算的方法和参数不同:

        

    具体来讲,以下表格包含了生成各种类型重定项的情况:

    

1.  R_386_32

    公式:S+A

    S:重定项中VALUE成员所指符号的内存地址

    A:被重定位处原值,表示"引用符号的内存地址"与S的偏移


    将g.c编译成g.o文件,观察包含的重定项信息:

    

    g1与g4/g5重定项区别:当前没有g1位置的任何线索,所以希望延迟到加载时,通过搜索动态符号表确定g1的内存地址,而g4/g5在g.o的.bss/.data节中,并且有static属性,不可能被外部引用,加载到内存必然还在g.o镜像的.bss/.data节中,所以编译器使用.bss/.data作为重定位计算参数,可以避免后续过程搜索动态符号表,提高重定位效率;

    g2/g3与g4/g5重定项区别:g2/g3虽然和g4/g5一样,也在g.o的.bss/.data节中,但g2/g3可以被外部引用,在一种特殊情况下,g2/g3会被安排到其它地方,如果仍然使用在g.o镜像中.bss/.data的地址进行重定位,就会导致进程运行的逻辑错误,稍后介绍R_386_COPY类型时,会详细说明。

    上图希望展示的是,在一个进程的创建过程中,a.out是最先映射到该进程的虚拟空间,然后才会映射所依赖的.so。换句话说,在a.out加载的时候,仍然不知道g的地址,而如果等加载libg.so时再处理重定项,虽然知道g的地址了,但a.out的.text段所在内存页,这时已经被设置为只读,也无法进行重定位。

    所以,针对这种情况,静态ld会将g转移到a.out的.bss段。由于a.out的加载地址,是在静态链接阶段就确定的(通过链接脚本设置,32位系统默认设置为0x8048000),从而静态ld也可以知道g的运行时地址,那么就不需要重定项了,但同时又带来2个新的问题:

    a. 毕竟libg.so中的g才是是原生的,怎么保证遵循libg.so中g的初始值?

        其实这就是设计R_386_COPY类型的用意,它表示让动态ld加载libg.so时知道g的初始值后,将值复制到内存中a.out的.bss段。

        但是如果再仔细想想,其实静态链接阶段,就有机会从libg.so中读取g的初始值,并且如果不将g安排在a.out的.bss段,而是安排在.data段,存储空间也具备了,按道理就不需要R_386_COPY类型了。个人猜测,可能是设计者本着.data只存储显式赋初值的变量的原则,而没有这样实现。

    b. g既然已经转移到新地地方了,怎么保证lig.so和a.out的.text段使用同一处的g?

        分析R_386_32类型时,已经看到g2/g3和g4/g5一样,分别在g.o的.bss/.data节,重定项中却仍然使用g2/g3作为计算参数,其实就是为了在这种情况下,放弃使用本身.bss/.data段中的g,而使用a.out中的g。


4.  R_386_PC32

    公式:S+A-P

    S:重定项中VALUE成员所指符号的内存地址

    A:被重定位处原值,表示"被重定位处"与"下一条指令"的偏移

    P:被重定位处的内存地址

    

    由于调用函数的指令中,要求的是相对地址,并且编译阶段就能确定f3()与fun()的偏移,即"f3加载地址(B+0x05)-下一条指令内存地址(B+1f)=0xe6ffffff",加载到内存也不会发生改变,所以0x1b处不需要被重定位。


    将上述f.o文件,链接为libf.so,静态ld无法对R_386_PC32重定项做进一步处理,这样,加载时动态ld会通过搜索动态符号表,确定libf.so镜像中0x53c/0x541处的地址值,保证运行时能调用到到f1()/f2()函数:

    


    

    将g.c编译成g.o文件,观察包含的重定项信息:

    


    


    我之前在另外一个论坛发过一篇这样的主题,但是当时还剩下一些疑问没有想清楚,最近利用业余时间再次学习了ellf格式,针对10种重定位类型重新做了总结,希望分享出来,可以带给初学者一点帮助。

    首先需要知道的是,一个程序从源码到被执行,当中经历了3个过程:

  • 编译:将.c文件编译成.o文件,不关心.o文件之间的联系
  • 静态链接:将所有.o文件合并成一个.so或a.out文件,处理所有.o文件节区在目标文件中的布局;
  • 动态链接:将.so或a.out文件加载到内存,处理加载文件在的内存中的布局。

      

    c程序中引用全局变量的语句,经过编译得到的机器码会包含一个地址值部分,机器码执行时,该值必须为变量在内存中的绝对地址,调用函数的语句,经过编译得到的机器码也包含一个地址值部分,机器码执行时,该值必须为内存中函数地址与下一条指令地址的偏移。但是在编译、静态链接,甚至动态链接之后,该地址值部分可能暂时无法满足最终要求,从而必须相应设置一个重定项,要求后续过程对该值进行修改,重定项一方面标记了地址值的位置,另一方面提供了计算正确地址值的方法和计算参数。

    对于局部变量的使用,由于程序执行时,esp寄存器保存的一定是栈顶的内存地址,那么从逻辑上讲,编译阶段就可以确定所有局部变量运行时的内存地址,所以不需要设置重定项。

    另外,elf格式中设计了10种不同的重定位类型,是由于不同场合,对地址值进行重新计算的方法和参数不同:

  • 引用变量的指令中,需要使用变量的绝对地址,而函数调用指令,需要使用函数与下一条指令地址的相对地址。
  • -fPIC编译选项,可以决定物理内存中的同一份.so镜像,是否可以被多个进程共享。
  • 静态ld是将.o文件按节区"撕开",将各个.o文件中相同类型的节,合并为.so或a.out文件中的一个段,而动态ld则是维持.so文件"原状",合并到进程的虚拟内存空间。

        

    具体来讲,以下表格包含了生成各种类型重定项的情况:

    

  • 全局变量,在不加-fPIC编译生成的.o文件中,每个引用处对应一个R_386_32重定位项,非static全局变量,在不加-fPIC编译生成的.so文件中,每个引用处对应一个R_386_32重定位项;
  • static全局变量,在不加-fPIC编译生成的.so文件中,每个引用处对应一个R_386_RELATIVE重定位项;
  • 非static全局变量,在加-fPIC编译生成的.o文件中,每个引用处对应一个R_386_GOT32重定位项;
  • static全局变量,在加-fPIC编译生成的.o文件中,每个引用处对应一个R_386_GOTOFF重定位项;
  • 非static全局变量,在加-fPIC编译生成的.so文件中,每个引用处对应一个R_386_GOLB_DAT重定位项;
  • a.out中利用extern引用.so中的变量,每个引用处对应一个R_386_COPY重定位项;
  • 非static函数,在不加-fPIC编译生成的.o和.so文件中,每个调用处对应一个R_386_PC32重定位项;
  • 非static函数,在加-fPIC编译生成的.o文件中,每个调用处对应一个R_386_PLT32重定位项;
  • 非static函数,在加-fPIC编译生成的.so文件中,每个调用处对应一个R_386_JMP_SLOT重定位项;
  • 全局变量,在加-fPIC编译生成的.o文件中,会额外生成R_386_PC32和R_386_GOTPC重定位项,非static函数,在加-fPIC编译生成的.o文件中,也会额外 生成R_386_PC32和R_386_GOTPC重定位项。

1.  R_386_32

    公式:S+A

    S:重定项中VALUE成员所指符号的内存地址

    A:被重定位处原值,表示"引用符号的内存地址"与S的偏移

// g.c
extern int g1;
int g2;
int g3 = 0x03030303;
static int g4;
static int g5 = 0x05050505;

void fun(int a[5])
{
	a[0] = g1;
	a[1] = g2;
	a[2] = g3;
	a[3] = g4;
	a[4] = g5;
}


    将g.c编译成g.o文件,观察包含的重定项信息:

    

// g.c
extern int g1;
int g2;
int g3 = 0x03030303;
static int g4;
static int g5 = 0x05050505;

void fun(int a[5])
{
	a[0] = g1;
	a[1] = g2;
	a[2] = g3;
	a[3] = g4;
	a[4] = g5;
}


    将g.c编译成g.o文件,观察包含的重定项信息:

  • "00000005 R_386_32 g1":编译器连g1在哪个.o文件都不知道,当然更不知道g1运行时的地址,所以在g.o文件中设置一个重定项,要求后续过程根据"S(g1内存地址)+A(0)",修改g.o镜像中0x05偏移处的值;
  • "0000002f R_386_32 .bss":g4在g.o文件.bss节的0偏移处(由于加载时必然知道.bss的内容为全0,就是说elf文件只需要记录.bss的位置和大小,不需要安排空间记录.bss内容,而且就算文件中为.bss节安排了空间,也无法区分g4在.bss节的什么位置,所以g4在.bss节中的偏移,要通过查看.bss节起始位置和g4符号的位置来验证),要求后续过程根据"S(g.o镜像中.bss的内存地址)+A(0)",修改g.o镜像中0x2f偏移处的值;
  • "0000003c R_386_32 .data":g5在g.o文件.data节的0x04偏移处,要求后续过程根据"S(g.o镜像中.data的内存地址)+A(0x04)",修改g.o镜像中0x3c偏移处的值;
  • "00000015 R_386_32 g2":g2在g.o文件的.bss节,要求后续过程根据"S(g2内存地址)+A(0)",修改g.o镜像中0x15偏移处的值;
  • "00000022 R_386_32 g3":g3在g.o文件的.data节,要求后续过程根据"S(g3内存地址)+A(0)",修改g.o镜像中0x22偏移处的值。

    g1与g4/g5重定项区别:当前没有g1位置的任何线索,所以希望延迟到加载时,通过搜索动态符号表确定g1的内存地址,而g4/g5在g.o的.bss/.data节中,并且有static属性,不可能被外部引用,加载到内存必然还在g.o镜像的.bss/.data节中,所以编译器使用.bss/.data作为重定位计算参数,可以避免后续过程搜索动态符号表,提高重定位效率;

    g2/g3与g4/g5重定项区别:g2/g3虽然和g4/g5一样,也在g.o的.bss/.data节中,但g2/g3可以被外部引用,在一种特殊情况下,g2/g3会被安排到其它地方,如果仍然使用在g.o镜像中.bss/.data的地址进行重定位,就会导致进程运行的逻辑错误,稍后介绍R_386_COPY类型时,会详细说明。


2.  R_386_RELATIVE
    公式:B+A
    B:.so文件加载到内存中的基地址
    A:被重定位处原值,表示引用符号在.so文件中的偏移
    将上述g.o文件,链接成libg.so文件,重定位信息如下:
    
  • "00000560 R_386_32 g1":任然没有g1位置的任何线索,所以重定项保持原有的计算方法和参数;
  • "00000570 R_386_32 g2":不确定是否需要放弃.bss中的位置,所以仍然使用g2的内存地址进行重定位计算;
  • "0000057d R_386_32 g3":不确定是否需要放弃.data中的位置,所以仍然使用g3的内存地址进行重定位计算;
  • "0000058a R_386_RELATIVE *ABS*":.so文件.bss段的第一项用于保存.bss本身的位置,g.o的.bss节被安排在了libg.so的0x2024处,所以静态ld根据g.o中的R_386_32重定项,进一步精确了g4在libg.so的0x2024偏移处,但g4的内存地址,还需要加上libg.so的加载地址,所以重定位类型转换为R_386_RELATIVE;
  • "00000597 R_386_RELATIVE *ABS*":.so文件.data段的第一项用于保存.data本身的位置,g.o的.bss节被安排在了libg.so的0x2018处,所以静态ld根据g.o中的R_386_32重定项,进一步精确了g4在libg.so的0x201c偏移处,但g5的内存地址,还需要加上libg.so的加载地址,所以重定位类型转换为R_386_RELATIVE。

3.  R_386_COPY
    公式:无
// g.c
int g = 1;

// main.c
extern int g;

void fun(int *a)
{
	*a = g;
}

int main()
{
	return 0;
}

// g.c
int g = 1;

// main.c
extern int g;

void fun(int *a)
{
	*a = g;
}

int main()
{
	return 0;
}

    将g.c编译为libg.so,main.c编译为a.out,由于a.out引用了libg.so中的全局变量g,从而可以出现说明R_386_32类型时提到的特殊情况:
    
    a.out中使用了libg.so中的全局变量g,这样就必须等到执行阶段,确定了libg.so在进程空间的位置后,才能知道g的绝对地址,不细想的话,可能会认为通过设置一个R_386_32或R_386_RELATIVE重定项,就能解决问题了。
    但遗憾的是,a.out的.text段,不可以有重定项:
    

    上图希望展示的是,在一个进程的创建过程中,a.out是最先映射到该进程的虚拟空间,然后才会映射所依赖的.so。换句话说,在a.out加载的时候,仍然不知道g的地址,而如果等加载libg.so时再处理重定项,虽然知道g的地址了,但a.out的.text段所在内存页,这时已经被设置为只读,也无法进行重定位。

    所以,针对这种情况,静态ld会将g转移到a.out的.bss段。由于a.out的加载地址,是在静态链接阶段就确定的(通过链接脚本设置,32位系统默认设置为0x8048000),从而静态ld也可以知道g的运行时地址,那么就不需要重定项了,但同时又带来2个新的问题:

    a. 毕竟libg.so中的g才是是原生的,怎么保证遵循libg.so中g的初始值?

        其实这就是设计R_386_COPY类型的用意,它表示让动态ld加载libg.so时知道g的初始值后,将值复制到内存中a.out的.bss段。

        但是如果再仔细想想,其实静态链接阶段,就有机会从libg.so中读取g的初始值,并且如果不将g安排在a.out的.bss段,而是安排在.data段,存储空间也具备了,按道理就不需要R_386_COPY类型了。个人猜测,可能是设计者本着.data只存储显式赋初值的变量的原则,而没有这样实现。

    b. g既然已经转移到新地地方了,怎么保证lig.so和a.out的.text段使用同一处的g?

        分析R_386_32类型时,已经看到g2/g3和g4/g5一样,分别在g.o的.bss/.data节,重定项中却仍然使用g2/g3作为计算参数,其实就是为了在这种情况下,放弃使用本身.bss/.data段中的g,而使用a.out中的g。


4.  R_386_PC32

    公式:S+A-P

    S:重定项中VALUE成员所指符号的内存地址

    A:被重定位处原值,表示"被重定位处"与"下一条指令"的偏移

    P:被重定位处的内存地址

// f.c
extern void f1();
void f2() {}
static void f3() {}

void fun()
{
	f1();
	f2();
	f3();
}

    将f.c编译成f.o文件,观察包含的重定项信息:

    

  • "00000011 R_386_PC32 f1":编译器连f1指令块在哪个.o文件都不知道,当然更不知道f1运行时的地址,所以在g.o文件中设置一个重定项,要求后续过程根据"S(f1内存地址)+A(-4)-P(被重定位处内存地址)",即"S(f1内存地址)-(p+4)(下一条指令内存地址)",修改g.o文件中0x11偏移处的值;
  • "00000016 R_386_PC32 f2":f2类似分析R_386_32类型时的g2/g3,虽然在g.o文件,但有可能被外部调用,所以和f1一样,编译器在g.o文件中设置一个重定项,要求后续过程根据"S(f2内存地址)+A(-4)-P(被重定位处内存地址)",即"S(f2内存地址)-(p+4)(下一条指令内存地址)",修改g.o文件中的0x16偏移处的值。

    由于调用函数的指令中,要求的是相对地址,并且编译阶段就能确定f3()与fun()的偏移,即"f3加载地址(B+0x05)-下一条指令内存地址(B+1f)=0xe6ffffff",加载到内存也不会发生改变,所以0x1b处不需要被重定位。


    将上述f.o文件,链接为libf.so,静态ld无法对R_386_PC32重定项做进一步处理,这样,加载时动态ld会通过搜索动态符号表,确定libf.so镜像中0x53c/0x541处的地址值,保证运行时能调用到到f1()/f2()函数:

    


// f.c
extern void f1();
void f2() {}
static void f3() {}

void fun()
{
	f1();
	f2();
	f3();
}

    将f.c编译成f.o文件,观察包含的重定项信息:
5.  R_386_GOTPC
    公式:GOT+A-P
    GOT:运行时,.got段的结束地址
    A:被重定位处原值,表示"被重定位处"在机器码中的偏移
    P:被重定位处的内存地址
// g.c
extern int g1;

void fun(int a[1])
{
	a[0] = g1;
}

    由于程序执行时,eip寄存器保存的一定是当前指令的内存地址,虽然eip寄存器不直接提供给软件使用,但是有间接的方法可以获取,那么从逻辑上讲,所有跟代码区有固定偏移的内容,编译和静态链接阶段,就可以确定它们的内存地址。利用这个特点,可以将代码区中的被重定位处转移出去,加-fPIC选项将g.c编译为g.o,并链接为libg.so,可以验证这一点:
// g.c
extern int g1;

void fun(int a[1])
{
	a[0] = g1;
}

    
    g.o中包含3个重定项:
  • "00000004 R_386_PC32 __x86.get_pc_thunk.cx":R_386_PC32重定位类型已经介绍过了,这条重定项可以保证,运行时当前指令可以调用到编译器自动生成的__x86.get_pc_thunk.cx()函数,由于call指令会将下一条指令内存地址B+0x513压栈,这样从该函数经过一次再回到B+0x513处的指令时,当前指令的内存地址B+0x513就存到了ecx寄存器;
  • "0000000a R_386_GOTPC _GLOBAL_OFFSET_TABLE_":要求静态ld根据"GOT(B+.got结束位置在libg.so中的偏移)+A(2)-P(B+0x515)",即libg.so文件中.got结束位置相对0x515处机器码的偏移,修改g.o文件中0x0a偏移处的值,这样运行时加上ecx寄存器中当前指令的内存地址后,就是.got段结束位置的内存地址;
  • "00000010 R_386_GOT32 g1":要求静态ld在目标文件中生成.got表,并在.got表中安排4字节存储g1地址,这样代码区就可以从.got表中获取g1地址,而.got表运行时的结束地址,以及存储g1地址的位置在.got表中的偏移,静态链接阶段都是知道的,从而就不需要对代码区进行重定位。
    静态ld在libg.so中设置了.got段,并将代码区中的重定位处,转移到.got段中:
  • "00001fec R_386_GLOB_DAT g1":0x1fec处用于保存运行g1的内存地址,但当前没有g1位置的任何线索,所以留下重定项,要求后续过程进行修改。
    对于R_386_32、R_386_RELATIVE类型的重定项,由于被重定位处在代码区,而重定项计算参数的地址,在不同进程中是不同的,所以不同进程对.so代码区的修改要求就不同,这样就不能共享同一份物理内存中的.so镜像。
    R_386_GLOB_DAT的优势就在于,它将"散落"在代码区的被重定位处,集中转移到.got表中,从而大大减小了不可共享区域,如下图所示,进程B希望加载.so文件时,发现内存中已经存在该.so的镜像了,就直接映射到自己的虚拟空间,动态ld在处理重定项时,仅需要修改小小的.got段,并通过COW(写时复制)机制,创建了一个.got副本,从而也可以保证与其它进程互不干扰。

    


6.  R_386_GOT32

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2019-1-28 10:23 被admin编辑 ,原因:
收藏
免费 2
支持
分享
最新回复 (20)
雪    币: 47147
活跃值: (20415)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
感谢分享!
2018-8-20 09:23
0
雪    币: 2141
活跃值: (7221)
能力值: ( LV11,RANK:180 )
在线值:
发帖
回帖
粉丝
3
马克下,收藏看看,我以前也写了个粗略的arm版本的。
最后于 2018-8-20 10:05 被爱吃菠菜编辑 ,原因:
2018-8-20 10:03
0
雪    币: 2290
活跃值: (2180)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
4
学习了。
2018-8-20 10:41
0
雪    币: 3315
活跃值: (10957)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
5
感谢支持。
2018-8-20 13:02
0
雪    币: 283
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
感谢分享~
2018-8-20 13:20
0
雪    币: 1604
活跃值: (640)
能力值: ( LV13,RANK:460 )
在线值:
发帖
回帖
粉丝
7
相当棒的链接器研究资料,学习一下~
2018-8-20 16:24
0
雪    币: 6818
活跃值: (153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
2018-8-20 23:43
0
雪    币: 2685
活跃值: (3680)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
9
学习了,谢谢楼主
2018-8-21 14:39
0
雪    币: 221
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
好文支持! 顺便LZ第一行是不是把elf写成ellf了呀
2018-9-4 13:32
0
雪    币: 6112
活跃值: (1212)
能力值: (RANK:30 )
在线值:
发帖
回帖
粉丝
11
感谢分享!
2018-9-4 14:11
0
雪    币: 3315
活跃值: (10957)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
12
谢谢鼓励!@展扬 
2018-9-4 15:36
0
雪    币: 11716
活跃值: (133)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
#寻宝大战#祝看雪19岁快乐!
2019-1-11 20:09
0
雪    币: 219
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
你好关于R_386_RELATIVE ,我思考着在.so动态库中其代码引用变量的地方,与static变量在数据段的地址偏移是一样的,那为啥不直接使用其偏移量来获取数据?
我思考了下,不知道下面回答是否正确:
x86没有指令支持数据相对寻址(而x64则有相对应的指令),而在pic中,确实实现了这种方法(获取当前指令的eip再加上与.got.plt的偏移量,获取.got.plt的位置,再根据变量位于.bss/.data节中,根据.bss/.data节变量地址减去.got.plt的地址得到的值加入到引用的偏移处,由此不需要再重定位了,这其实就是R_386_GOTOFF),不知道我这样理解是否正确,还恳请楼主告知
2019-2-23 18:28
0
雪    币: 3315
活跃值: (10957)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
15
请叫我鲁班大师 你好关于R_386_RELATIVE ,我思考着在.so动态库中其代码引用变量的地方,与static变量在数据段的地址偏移是一样的,那为啥不直接使用其偏移量来获取数据? 我思考了下,不知道下面回答是 ...
嗯,访问变量要用绝对地址,函数跳转要用相对地址
2019-2-25 13:22
0
雪    币: 3315
活跃值: (10957)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
16
R_386_PC32一节中,f2()与被重定位处相对位移是确定的,为什么还会设置重定项:函数的定义和声明没有显式的加static,默认情况下是extern的。所以写程序时,如果确定函数只会在当前.c文件中调用,建议加上static,一方面保证不与其它.c中的同名函数冲突,另一方面也会减少一个没必要的重定位过程。
2019-6-14 17:14
0
雪    币: 3315
活跃值: (10957)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
17
纠错:
1. R_386_32:g2在common区,链接时才会有空间,不在g.o的.bss节区
8. R_386_GOTOFF:公式S+A-P
2019-6-18 14:52
0
雪    币: 3315
活跃值: (10957)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
18
5. R_386_GOTPC:对__x86.get_pc_thunk.cx的重定项并不多余,其它.o里面可能也有__x86.get_pc_thunk.cx,链接目标文件中只留一个,并且不会出现"重复定义"错误
2019-6-21 14:24
0
雪    币: 3315
活跃值: (10957)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
19
R_386_GOTPC:如果g1.o、g2.o都有__x86.get_pc_thunk.cx,链接成libg.so后,只保留一个,所以.o文件中要对__x86.get_pc_thunk.cx调用处添加重定向。
2019-10-17 09:48
0
雪    币: 368
活跃值: (431)
能力值: ( LV2,RANK:140 )
在线值:
发帖
回帖
粉丝
20
感谢分享,收藏下,下班去看看
2019-10-17 10:12
0
雪    币: 3315
活跃值: (10957)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
21
// fun.c
#include <stdio.h>

//void __attribute__((weak)) fun()
void fun()
{
        printf("weak\n");
}

void test()
{
        fun();  // 这里可能会调用别处的fun()
}

// main.c
#include <stdio.h>

extern void test();

void fun()
{
        printf("force\n");
}

int main()
{
        test();
        return 0;
}

// gcc -c fun.c main.c -g -Wall
// gcc -shared fun.o -o libfun.so
// gcc main.o -L. -lfun -g -Wall
// 将当前路径添加到/etc/ld.so.conf,并且执行ldconfig
// ./a.out
/*
 * 结果:"force"
 * main()
 *    |- test()       // 执行main.c中的fun(),而不是fun.c本身的fun()
 *        |- fun()    // 打印"force"
*/

“ 4.R_386_PC32 ”一节中,要为f2设置重定项的原因。
2020-1-3 14:22
0
游客
登录 | 注册 方可回帖
返回
//