精华内容
下载资源
问答
  • 2、何为符号解析和符号重定位?        在链接中,将函数和变量统称为符号。函数名或变量名称为符号名。        链接过程中很关键的一部分...
    1、什么是符号表?
    2、何为符号解析和符号重定位?

           在链接中,将函数和变量统称为符号。函数名或变量名称为符号名

           链接过程中很关键的一部分就是符号的管理,每一个目标文件都会有一个相应的符号表,这个表里记录了目标文件中所用到的所有符号。

           每一个定义的符号有一个对应的值,叫做符号值。对于函数和变量来说,符号值就是它们的地址。

    符号表中所有的分类
        1.定义在本目标文件的全局符号,可以被其他目标文件引用。
        2.在本目标文件中引用的全局符号,却没有定义在本目标文件,这一般叫做外部符号。这里就是引用外部符号。
        3.段名,这种符号由编译器产生,它的值就是其段的起始地址。如目标文件里的.text、.data 段。
        4.局部符号,只在编译单元内部可见。局部符号对于链接过程是没有作用的。
        5.行号信息,即目标文件指令与源代码中代码行的对应关系。

    对于链接过程来说,只关注全局符号的相互“粘合”,其它的符号都是次要

    符号值(st_value)的几种情况
        1.在目标文件中,如果是符号的定义并且该符号不是在COMMON块类型的,则st_value表示该符号在段中的偏移。
        2.在目标文件中,如果符号时COMMON块类型的,则st_value表示该符号的对齐属性。
        3.在可执行文件中,st_value表示符号的虚拟地址。

           目标文件:就是源代码编译后但并未进行链接的那些中间文件。在Windows下统称为PE-COFF文件格式,在Linux下统称为ELF文件
    .bss段只是为未初始化的全局变量和局部静态变量预留位置而已,它并没有内容,所以它在文件中也不占据空间

           程序源代码被被编译以后主要分成两种段:程序指令和程序数据。代码段(.text)属于程序指令,而数据段(.text)和.bss段属于程序数据。

           重定位就是程序进行修改导致目标地址变化,需要重新计算各个目标的地址的过程。
           符号重定位中的符号指的是用来命名子程序或跳转目标的这个名称,它所表示的实际上就是一个地址,它可以是子程序(函数)的地址,也可以是一个变量的起始地址。如:int Sum(int,int); 这里的Sum就是一个符号。或者是int a;这里的a也是一个符号。

    例如:有个全局变量var,在目标文件A中,然后我们在目标文件B 中要访问这个变量,在目标文件B中的指令为var = 30; 在编译目标文件B 的时候,我们是不知道var的目标地址的,所以编译器在没法确定地址的情况下,会先将这条命令的目标地址置为0,等待链接器把A和B文件链接起来后,变量var的地址最终确定下来为0x1000,然后链接器就会将这个指令的目标地址部分修正为0x1000,这个地址修正的过程也被叫做重定位。

    展开全文
  • 对于编译和链接的基本过程,这里只对链接过过程的符号重定位做了解释,因为个人认为在链接过程中符号的重定位是最重要的一步,也是其精华所在,知道了这一步的实现过程可以解决很多问题,包括面试中可能会问到的关于...

    前情提要

    • 对于编译和链接的基本过程,这里只对链接过过程的符号重定位做了解释,因为个人认为在链接过程中符号的重定位是最重要的一步,也是其精华所在,知道了这一步的实现过程可以解决很多问题,包括面试中可能会问到的关于extern等的方面。所以这里只有对符号重定位进行了详细的说明,如果想了解更多可以参看《程序员的自我修养》第2,3,6 章节。里面有很详细的解释。
      如果对虚拟地址空间的内存分布还不够了解的建议先看上一篇博客IA32位Linux内核中的虚拟地址映射
      其中的前面一部分有对虚拟内存分布的分析。

    一,编译和链接的总体流程:

    1. 预编译:处理预编译指令(包括宏替换等#指令),删除注释。
      具体如下:
      1)将所有的“#define”删除,并且展开所有的宏定义
      2)处理所有的条件预编译指令,将被包含的文件插入到该预编译指令的位置,注意:这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。
      3)删除所有的注释”//”,”/* */”。
      4)添加行号和文件名标识。e.g #2 “hello” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号。
      5)保留所有的#pragma 编译器指令。

    2. 编译阶段:进行一系列词法,语法,语义分析及优化后生成相应的汇编代码文件。(gcc -01最高级别优化),汇总符号。各个源文件的编译都是独立的。
      1)扩充gcc
      这里写图片描述
      gcc命令只是对这些后台程序的包装,它会根据不同的参数要求去调用预编译程序ccl,汇编器as,链接器ld
      2)编译分为6步:
      (1)扫描:源代码输入到扫描器,进行简单的词法分析并将源代码的字符序列分割成一系列的记号。
      (2)语法分析:进入语法分析器,对扫描器产生的记号进行语法分析,产生语法树。
      (3)语义分析:由语义分析器完成,(编译器所能分析的语义是静态语义——在编译期间可以确定的语义。与之对应的动态语义就是 只有在运行期才能确定的语义)
      (4)源代码优化:在源代码级别上的优化(三地址码),生成中间代码,中间代码使得编译器可以被分为前端和后端,前端负责产生机器无关的中间代码,后端将中间代码转化为目标机器代码。
      后面的步骤为编译器后端
      (5)代码生成:将中间代码转化成目标机器代码。
      (6)目标代码优化:对上述目标代码进行优化(选择合适的寻址方式等)

    3. 汇编:把汇编指令翻译成二进制格式,生成各个段(section)符号表。这一步可生成二进制可重定位目标文件(main.o、main.obj)

    4. 链接
      知识储备:
      符号:用来表示一个地址,这个地址可能是一段子程序的起始地址,也可以是一个变量的起始地址。
      模块化程序:C/C++中有1,模块间的函数调用。2,模块间的变量访问。
      1)合并各个段(section),并调整其起始位移(段偏移)和段大小
      2)合并符号表并进行符号解析。将.o/.obj文件和.lib/.a库链接为.exe等文件。在解析完成之后就给符号分配虚拟内存地址。并且编译器不看局部(local)变量
      3)符号重定位。修改符号地址,每个要被修正的地方叫一个重定位入口.E.g给例如外部定义的符号(内存中为UND段)找到其定义的地址,并在链接阶段进行强弱符号的合并(C++中无强弱符号一说)原来外部声明的地址更新为具体定义的地址


    看完了一大段晦涩的理论,来清新以下,看一下图文结合:

    • 代码
    a.c文件:
    #include <stdio.h>
    extern int data_one;//外部声明变量
    int sum(int a,int b);//外声明函数
    
    int main()
    {
    int b = data_one;
    int c = 12;
    sum(b.c);
    return 0;  
    } 
    
    b.c文件
    int data_one = 10;
    int sum(int a,int b)
    {
       return a+b;
    }
    • Linux中的内存段查看
      这里写图片描述
    • 在看一下可执行程序run中的内存布局:
      这里写图片描述
      强弱符号:
      只要初始化都是强符号,未初始化为弱符号,出现多个同名强符号,编译出错;出现多个同名弱符号,选择内存占用字节最大的,若占用都一样大看编译器。出现同名的强符号和弱符号,选弱符号。(C++没有强弱符号)

    这个流程为:
    1. 在a.c和b.o编译时都是独立编译,在生成.o文件后,只要在本文件中的变量或指令都在.data 或 .text段,外部声明的都为UND(undefine)段,第一幅图就是编译过程之后的内存布局,也就是没有进行符号合并和解析之前的布局。
    2. 在执行 ld -e a -o run *.o 命令之后,生成可执行程序run,在该过程中,链接器先将各个段合并(就是将同名符号按照强弱符号选择规律合并),再并调整其起始位移(段偏移)和段大小,然后进行符号的解析,解析完成之后为符号分配虚拟内存地址,最后进行符号的重定位。
    在符号的重定位中总的流程又有如下:(重定位一般对外部声明)
    1)在编译过程中先在内存中为外部声明分配一个无效的等待更新的地址。(因为编译器在编译过程中只看声明是不知道其真实值的)
    2)在链接时文件已经是二进制可重定位目标文件了,这时会进行符号的合并,因为链接是对多个文件的操作,这时,编译器就会在别的文件中寻找extern声明的变量(这时编译器就会知道你声明的变量到底是啥值),得到值之后就将其移动到相应的段中。原来的待更新地址也会变为实际有效的虚拟地址。

    展开全文
  • 目录链接符号引用重定位简介例子重定位条目简介offsettypesymboladdend重定位PC相对引用重定位PC绝对引用 链接符号引用重定位 简介 我们知道一个.c文件可以被编译为.o文件,即目标文件,而假如一个.c中引用了别的.c...

    链接符号引用重定位

    简介

    我们知道一个.c文件可以被编译为.o文件,即目标文件,而假如一个.c中引用了别的.c中的函数或者是变量,这时候的.o其实是不知道引用函数实际的内存位置的,也就无法跳转,这就需要【重定位】的操作了,而针对函数名(也是符号)的重定位

    例子

    我们编写两个.c文件,分别是main.c和func.c,main.c引用了func.c中的func函数

    // main.c
    int func(int x, int y);
    
    int main()
    {
    	int c = func(1, 2);
    	return 0;
    }
    
    // func.c
    int func(int x, int y)
    {
    	return 2*x + y;
    }
    

    我们使用gcc来编译出两个.o文件,使用如下命令将会生成main.o与func.o

    gcc -c main.c func.c
    

    然后我们对main.o与func.o反汇编,使用如下命令可以生成main_dump.txt与func_dump.txt的反汇编文件

    objdump -d main.o > main_dump.txt
    objdump -d func.o > func_dump.txt
    

    因为main.c调用func.c而func.c并未调用其他函数,所以我们查看main.o反汇编结果,如下图蓝色区域,因为在链接之前每个.c相对是独立的,并不知道如何跳转到其他文件的函数,所以无法正确的写成跳转指令
    在这里插入图片描述
    但是我们将两个.o文件进一步编译成可执行文件,并查看其反汇编。执行以下命令做进一步的编译,将main.o和func.o链接进而生成exe可执行的二进制文件

    gcc -o exe main.o func.o
    

    如图
    在这里插入图片描述
    我们对exe反汇编,得到exe的汇编代码

    objdump -d exe > exe_dump.txt
    

    发现可以正确的跳转到func所指定的地址,这是我们想要的,也是链接中重定位所做的功劳
    在这里插入图片描述

    重定位条目

    简介

    在这里插入图片描述
    ----引用自《深入理解计算机系统》

    但是书上讲的很抽象,而且没有例子,接下来简单解释以下这些条目中各个符号的意义

    offset

    因为在链接之前,每个.c相当于一个独立的部分,我们称之为【节】,而offset表明这个符号引用相对于【节】起始处的偏移(其实就是相对地址)

    值得注意的是,这个offset指向的是callq指令中需要被修改的操作数,而不是callq指令本身

    如图 main.c 的节
    在这里插入图片描述

    type

    是何种重定位方式,因为编译的时候可以有很多种重定位方式,但是接下来只讨论书上描述的比较难懂的【重定位PC相对引用】

    symbol

    即这个引用指向的是哪个符号,比如main.c引用func函数那么就指向func函数

    addend

    在PC+偏移跳转计算实际地址时用到的计算偏移,待会会讲解

    重定位PC相对引用

    这里是书上讲的比较晦涩的地方,书上直接给出了公式
    在这里插入图片描述
    这个公式很难懂,但是我们可以翻译一下。仍然以main.c引用func.c为例子

    符号意义
    ADDR(s)main节在实际的可执行文件中,位于内存的实际地址
    ADDR(r.symbol)func函数在实际可执行文件中,位于内存的实际地址
    r.offset上文提到的offset,计算出需要修改的引用的操作数相对main节的偏移
    r.addend上文提到的addend,计算实际callq语句操作数时需要用到的计算偏移

    结合exe可执行文件的反汇编代码,我们进一步理解:
    在这里插入图片描述
    现在再来看这个公式,其中offset为0x13,根据上面offset定义推得

    refaddr            	= ADDR(s)        + r.offset
    要修改的引用的实际地址 	= main函数实际地址 + 要修改的引用的相对地址
    0x60d				= 0x5fa			 + 0x13
    

    addend是由命令查看精灵 ELF readelf -r main.o查询得的
    在这里插入图片描述

    *refptr 	= ADDR(r.symbol) + r.addend - refaddr
    修改后的内容x	= func函数实际地址 + 计算偏移 	- 要修改的引用的实际地址
    0xa			= 0x61b			 + -0x4		- 0x60d
    
    注:
    因为是【PC+偏移】寻址,修改后的内容x就是call的操作数
    要想使跳转到func函数,就需要【PC+x = func函数实际地址】
    PC + 0xa = 0x611 + 0xa = 0x61b 确实是func函数的地址,达到效果
    

    注:此处r.addend=-4,是因为要修改的操作数地址(refaddr)和该操作数所在指令的下一条指令地址(即PC)相差4字节。因为操作数是4字节的,填充操作数之后,才是下一条指令的地址(PC),所以做差之后,要补偿操作数占用的4字节地址回去。

    换句话说,call的操作数有 x 字节,r.addend的就等于 -x

    可以看到编译后的结果,也就是链接的时候修改后的结果,确实是0xa(小端表示牢记于心)
    在这里插入图片描述

    附:图解,以main函数调用sum函数为例
    在这里插入图片描述

    现在再看书上的公式是否变简单了?
    在这里插入图片描述
    在这里插入图片描述

    重定位PC绝对引用

    这个简单,没有那么多做差和偏移,直接将实际的func函数的地址赋值给操作数,但是一般用于外部变量数据的引用中

    展开全文
  • 符号解析与重定位

    2019-03-05 19:27:00
    编译器并不知道引用符号的真正地址,暂时用临时的假地址代替着,把真正的地址计算工作留给了链接器,链接器可以根据符号的地址对每个需要重定位的指令进行地位修正。 重定位表 在ELF文件中,有一个叫重定位表的结构...

    重定位

    编译器并不知道引用符号的真正地址,暂时用临时的假地址代替着,把真正的地址计算工作留给了链接器,链接器可以根据符号的地址对每个需要重定位的指令进行地位修正。

    重定位表

    在ELF文件中,有一个叫重定位表的结构专门用来保存这些鱼重定位相关的信息。对于可重定位的ELF文件来说,它必须包含有重定位表,用来描述如何修改相应的段里的内容。
    每个要被重定位的地方叫一个重定位入口(Relocation Entry),重定位入口的偏移(Offset)表示该入口在要被重定位的段中的位置,“RELOCATION RECORDS FOR [.text]”表示这个重定位表是代码段的重定位表。
    重定位入口的定义如下:

    typedef struct{
        Elf32_Addr r_offset;    // 重定位入口的偏移
        Elf32_Word r_info;      // 重定位入口的类型(低8位)和符号(高24位表示符号在符号表中的下标)
    }Elf32_Rel;

    符号解析

    我们平时在编写程序的时候最常碰到的问题之一,就是链接时符号未定义。导致这个问题的原因很多,最常见的一般都是链接时缺少了某个库,或者输入目标文件路径不正确或符号的声明与定义不一样。所以从普通程序员的角度看,符号的解析占据了链接过程的主要内容。

    其实重定位过程也伴随着符号的解析过程,每个目标文件都可能定义一些符号,也可能引用到定义在其他目标文件的符号。重定位的过程中,每个重定位的入口都是对一个符号的引用,那么当链接器需要对某个符号的引用进行重定位时,它就要确定这符号的目标地址。这时候链接器就会去查找由所有输入目标文件的符号表组成的全局符号表,找到相应的符号后进行重定位。若找不到就报符号未定义错误。

    指令修正方式

    不同的处理器指令对于地址的格式和方式都不一样。这些寻址方式有如下几方面的区别:
    近址寻址或远址寻址
    绝对寻址或相对寻址
    寻址长度为8位、16位、32位或64位

    但对于32位x86平台下的ELF文件的重定位入口所修正的指令寻址方式只有两种:
    绝对近址32位寻址
    相对近址32位寻址

    每个被修正的位置的长度都为32位,即4个字节。

    宏定义重定位修正方法
    R_386_321绝对寻址修正 S+A
    R_386_PC322相对寻址修正 S+A-P

    A=保存在被修正位置的值
    P=被修正的位置(相对于段开始的偏移量或虚拟地址),注意,该值可通过r_offset计算得到
    S=符号的实际地址,即由r_info的高24位指定的符号的实际地址

    转载于:https://www.cnblogs.com/fr-ruiyang/p/10457817.html

    展开全文
  • 符号解析和重定位 在上一篇博客空间的地址与分配介绍过,链接器链接目标文件分为两个步骤。这一篇博客就是介绍第二步符号解析和重定位的。这也是静态静态链接中的核心内容。符号详解 重定位 看下边一个例子: #...
  • 重定位目标文件由汇编器as生成,包含了当前模块定义的符号及引用的外部模块符号信息,可以与其他可重定位目标文件链接为一个可执行目标文件。 有三种不同类型的符号,第一种,由当前模块定义的全局符号;第二种,...
  • 编译过程和符号重定位问题:转载至:点击打开链接 对于代码的编译问题千头万绪从何说起呢,首先来说一下计算机是如何处理应用程序的,实质上应用程序是通过操作系统来应用机器指令操控硬件设施完成各种任务的,...
  • 链接器必须对这些可重定位目标文件完成两个主要...链接器把每个符号定义与一个虚拟地址联系起来,然后修改所有对这些符号的引用,使得它们指向这个存储位置,从而重定位这些节。 符号分为四类: 导出符号(...
  • 我们知道一个可执行的C程序要经过语言预处理、编译器、汇编器生成扩展名为.o的可重定位目标文件,再通过链接器链接生成可执行的目标文件。 1.什么是可重定位目标文件 一个扩展名为.s 的ASCII汇编语言文件经由汇编...
  • 链接二( 重定位)

    2020-05-16 11:47:45
    链接二( 重定位&&动态链接) 1.重定位过程 可重定位文件 = 用到.o文件... 对引用符号重定位(修改.text、.data节中对每个符号的引用,需要用到.rel_data、.rel_text中保存的重定位信息) ①重定位信息 ...
  • ① buf由关键字extend引入,是外部变量,swap.o.symtab条目中会存放由其他模块定义并被模块 m 引用的全局符号,这个外部是指的main.o,查看CSAPP前面的main.c代码可以发现buf是已经被初始化的全局变量,因此保存在节...
  • Linux逆向---ELF符号重定位

    千次阅读 2018-12-02 15:08:12
    .dynsym保存了引用来自外部文件符号的全局符号,.symtab中还保存了可执行文件的本地符号,如全局变量等,.dynsym保存的符号是.symtab保存的符号的子集。 .dynsym在程序运行时会被分配并装载进内存,主要用于动态...
  • 重定位

    千次阅读 2017-08-29 11:27:50
    由于重定位过程的两个步骤,判断程序地址计算最初的非空段,和解析外部符号的引用,是依次、共同处理的,所以我们讲重定位即同时涉及这两个过程。 链接器的第一次扫描会列出各个段的位置,并收集程序中全局符号与段...
  • 对引用符号重定位:修改.text和.data节对符号的引用(地址) 重定位条目:记录在.reltext 和 .reldata节 重定位目标文件中对需要符号引用的描述(节中偏移,引用符号,重定位方式) PC相对地址:由偏移量和PC(由...
  • 地址重定位:静态重定位和动态重定位

    千次阅读 多人点赞 2019-04-24 09:06:18
    地址重定位就是操作系统将逻辑地址转变为物理地址的过程。。。也就是对目标程序中的指令和数据进行修改的过程 将逻辑地址空间重定位到物理地址空间的时机有三种: 1、程序编译连接时:符号绑定,各Obj模块的相对...
  • 链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来,即使用的符号一定要找到相应的定义。可分为局部符号解析和全局符号解析。 局部符号解析:引用定义在相同...
  • 一:符号解析 1.符号表: 由汇编器构造,使用编译器输出到汇编语言.s文件中的符号 (1)Local符号与global符号 任何带有static属性声明的全局变量或者函数的模块都是私有的,即都是local属性的(local属性符号...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 70,185
精华内容 28,074
关键字:

符号重定位