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

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

2018-8-20 02:21
19064
    我之前在另外一个论坛发过一篇这样的主题,但是当时还剩下一些疑问没有想清楚,最近利用业余时间再次学习了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文件,观察包含的重定项信息:

    

  • "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编译为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()函数:

    


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.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
    公式:G
    G:引用符号的地址指针,相对于GOT的偏移

// g.c
extern int g1;
int g2;
int g3 = 0x03030303;

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

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

    

  • "00000010 R_386_GOT32 g1":要求静态ld根据"G(g1地址指针相对GOT的偏移)",修改g.o镜像0x10偏移处的值;
  • "00000023 R_386_GOT32 g2":要求静态ld根据"G(g2地址指针相对GOT的偏移)",修改g.o镜像0x23偏移处的值;
  • "00000033 R_386_GOT32 g3":要求静态ld根据"G(g3地址指针相对GOT的偏移)",修改g.o镜像0x33偏移处的值;


7.  R_386_GOLB_DAT
    公式:S
    S:重定项中VALUE成员所指符号的内存地址
    将上述g.o链接为libg.so文件,发现被重定位处都被集中转移到.got段中:

    


8.  R_386_GOTOFF
    公式:S-GOT
    S:重定项中VALUE成员所指符号的内存地址
    GOT:运行时,.got段的结束地址
// g.c
static int g4;
static int g5 = 0x05050505;

void fun(int a[2])
{
	a[0] = g4;
	a[1] = g5;
}

    将g.c编译为g.o文件,并且链接为libg.so文件,观察包含的重定项信息:
    
    编译阶段不知道g.o中的.bss/.data节会被链接到libg.so中的什么位置,所以设置了R_386_GOTOFF重定项,要求静态ld根据"S(.bss/.data内存地址)-GOT(运行时.got结束地址)",修改被重定位处的值。
    前面已经介绍过,运行时ecx寄存器保存的一定是.got的结束地址,再加上g4/g5具有static属性,加载到内存后,仍然在libg.so镜像的.bss/.data段中,那么通过g4/g4在.bss/.data中的偏移,以及.bss/.data与.got结束位置的偏移,在静态链接阶段就能知道运行时g4/g5的内存地址,从而libg.so中就不存在对g4/g5引用处的重定项了。

9.  R_386_PLT32
    公式:L+A-P
    L:<重定项中VALUE成员所指符号@plt>的内存地址
    A:被重定位处原值,表示"被重定位处"相对于"下一条指令"的偏移
    P:被重定位处的内存地址
// f.c
extern void f1();
void f2() {}
static void f3() {}

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

    将f.c编译成f.o文件,观察包含的重定项信息:
    
  • "0000001d R_386_PLT32 f1":要求静态ld生成<f1@plt>函数,并根据"L(<f1@plt>函数地址)+A(-4)-P",即<f1@plt>相对于下一条指令的相对地址,修改f.o镜像中0x1d偏移处的值;
  • "00000022 R_386_PLT32 f2":要求静态ld生成<f2@plt>函数,并根据"L(<f2@plt>函数地址)+A(-4)-P",即<f2@plt>相对于下一条指令的相对地址,修改f.o镜像中0x22偏移处的值。
    可以看出,源码中调用f1()、f2()函数的语句,对应的机器码,并没有直接跳转到f1、f2指令块,而是调用了<f1@plt>、<f2@plt>函数,接下来通过分析R_386_JMP_SLOT类型就会知道,这两个函数相当于"中间跳板",用于实现重定项的延迟处理。

10.R_386_JMP_SLOT
    公式:S(与R_386_GLOB_DAT的公式一样,但对于动态ld,R_386_JMP_SLOT类型与R_386_RELATIVE等价)
    S:重定项中VALUE成员所指符号的内存地址
    将上述f.o,链接为libf.so文件,观察包含的重定项信息:
    
  • "00002018 R_386_JUMP_SLOT f1":被重定位处在libf.so镜像的0x2018偏移处(.got.plt段中),0x567处的指令第一次被执行时,由_dl_runtime_resolve()函数根据"S(f1内存地址)",修改被重定位处的值;
  • "0000200c R_386_JUMP_SLOT f2":被重定位处在libf.so镜像的0x200c偏移处(.got.plt段中),0x56c处的指令第一次被执行时,由_dl_runtime_resolve()函数根据"S(f2内存地址)",修改被重定位处的值;
    R_386_JMP_SLOT是10种类型中最复杂的,必须先要了解.got.plt前三项的含义:
  • 第1项:用于保存.dynamic段的内存地址,初始值为.dynamic段在libg.so文件中的偏移;
  • 第2项:用于保存内存中libf.so模块的id,用于区分各个已加载的.so模块;
  • 第3项:用于保存_dl_runtime_resolve()函数的内存地址,由动态ld完成填写。
    另外,通过前面的介绍可以知道,ebx寄存器存的一定是.got结束地址B+0x2000,然后按照图中标记的执行顺序,在大脑中连续2次模拟执行B+0x567处的指令,就会看出如下规律:
  • 第一次执行时,B+0x410处的jmp指令,会跳转到B+0x416处(因为0x18(%ebx)指向B+0x2018处,而此处初始值为B+0x416,接着将被重定位处地址压栈,并再跳转到0x3d0将libf.so模块id压栈,最终进入_dl_runtime_resolve()函数,确定f1地址后覆盖到B+0x2018处;
  • 第二次执行时,由于B+0x2018处已经是f1的地址了,从而B+0x410处的jmp指令,就会直接进入f1()函数。
    对f2()函数的调用同理,这样做虽然会多一次跳转,但是保证了程序执行的平滑性,避免大量调用libg.so中的函数时,在加载.so时出现"卡顿"的现象,而且有时候很多分支根本没有机会被执行,所以这是一种折衷的处理。

参考:




[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

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