符号表和符号解析

  • 符号和符号表
  • 符号解析
    • 全局符号的强弱性
    • 符号解析过程
  • 与静态库的链接

符号和符号表

连接器需要使用符号表进行符号解析然后生成可执行文件,目标文件中通常有一个符号表,表中包含了在该文件中定义的所有符号的信息。C 文件包含以下 3 种符号:

  1. 全局符号: 包括非静态的函数名和非静态的全局变量。
  2. 外部符号: 包括在其他模块定义的外部函数名和外部变量名。
  3. 本地符号: 包括带 static 的函数名和全局变量名。

static 属性的本地变量在 .data 和 .bss 中分配空间。如果要链接的两个可重定位文件中包含了同名的 static 变量,则需要分别为他们分配空间。
例如:

int func1(){static int x = 0;return x;
}
int func2(){static int x = 1;return x;
}

两个函数中包含静态变量 x,且都初始化,编译器则会在 .data 节为两者分配空间,并在符号表中创建 func1.x 和 func2.x 两个符号。

上图中的全局符号有:

  • main.c 中的变量 buf,函数 main
  • swap.c 中的变量 bufp0,函数 swap

外部符号有:

  • swap.c 中的 buf
  • main.c 中的 swap

本地符号有:

  • swap.c 中的 bufp1

swap.c 中的 temp 是运行时动态分配到栈中的,不是符号。

ELF 文件符号表中每个表项具有以下数据结构:

  • st_name: 给出符号在字符串表中的索引,指向在字符串表中的一个以 null 结尾的字符串。
  • st_value: 给出符号的值。在可重定位文件中,是符号所在位置相对于所在节起始位置的偏移量;在可执行文件中是符号所在的虚拟地址。
  • st_size: 给出符号表示对象的字节个数。若符号是函数名,则是函数所占字节的大小。
  • st_info: 给出符号的类型和绑定属性,从以下定义的宏可以看出,符号类型占低 4 位,绑定类型占高 4 位。
    符号类型可以是未指定(NOTYPE)、变量(OBJECT)、函数(FUNC)、节(SECTION)。绑定属性可以是本地(LOCAL)、全局(GLOBAL)、弱(WEAK)等。
#define ELF_32_ST_INFO(bind, type) (((bind << 4) + ((type) & 0xf))
  • st_other: 指出该符号的可见性,通常在可重定位文件中指出。定义了符号成为可执行文件或共享库文件一部分后该符号的访问形式。
  • st_shndx: 指出符号所在节在节头表中的索引。

使用命令 readelf -s main.o 查看 main.o 中的符号表:

main 模块中三个全局符号中,buf 是 变量(Type = OBJECT),它位于节头表中的第三个表项(Ndx = 3)对应的 .data 节中偏移量为 0 (Value = 0),占 8 个字节;main 是函数(Type = FUNC),它位于节头表中第一个表项(Ndx = 1)对应的 .text 节中偏移量为 0 处,占 20 字节;swap 是 未指定(NOTYPE)且未定义(UND)的符号,说明 swap 是 main 中被引用的外部符号。

符号解析

符号解析的目的是将模块中符号的引用与某个模块中符号的定义建立关联,用来在重定位时将引用符号的地址重定位为相关联的定义符号的地址。

全局符号的强弱性

已初始化的全局符号为强符号,未初始化的为弱符号。上面的 main.c 和 swap.c 模块中,所有全局变量都是强符号。
链接器处理多重定义符号主要有以下几个规则:

  • 强符号只能定义一次,多次会出现链接错误
  • 若一个符号有一次强符号定义和多次弱符号定义,则以强符号为准
  • 若一个符号有多次弱符号定义,则任选一个。

例一:

上图中强符号 x 被定义了两次,所以会抛出链接错误:

例二:


y 在 main.c 中定义为强符号,则 p1.c 中的 y 为引用符号。这两个 y 就是一个变量,y 被初始化为 100 ,调用 p1 后 y 的值被改为 200。z 在两个文件中都为弱符号,则按顺序来 main.c 中的 z 为定义,p1.c 中的 z 为符号引用,调用 p1 后 z 的值被修改为 2000。

所以输出结果为 y=200, z=2000。

例三:

main.c 中 d 被定义为强符号,类型为 int,p1.c 中引用 d,因为 AMD 64 为小端存储,调用 p1 后 d 的地址 &d 被修改成了 1.0 对应机器数的低 32 位,由于 x 在 d 后面定义,所以 x 在 d 的高两个字节处,则 x 的地址 &x 被修改成了 1.0 对应机器数的高 32 位 0xF30F0000,对应的十进制数为 1072693248。

最后输出结果(编译会由警告:将8字节的数赋值给只有4字节长度的符号d):

符号解析过程

链接器按从左到右的顺序扫描出现在命令行的可重定位文件和静态库文件,同时维护三个集合:

  • E: 要被合并到一起组合成可执行文件的所有目标文件的集合。
  • U: 未解析的符号集合,未解析的符号即为未与定义符号建立关联的引用符号。
  • D: 是指当前为止已被加入到 E 的所有目标文件中定义符号的集合。

符号解析前,三个集合都是空的。

符号解析的三个过程:

  1. 对命令行中的每一个输入文件 f ,如果是目标文件,就加入 E,根据 f 中未解析符号和定义符号分别对 U、D 进行修改,然后处理下一个文件。
  2. 如果 f 是库文件,链接器会尝试把 U 中所有为解析的符号与 f 中各目标模块定义的符号进行匹配。如果某个模块 m 定义了 U 中的一个未解析符号 x,那么就把 m 加入到 E 中,并把符号 x 从 U 移入 D 中。不断对 f 中所有目标模块重复这个步骤,直到 U、D 不改变。未加入到 E 中的 f 里的目标模块就被丢弃,继续处理下一输入文件。
  3. 如果处理过程中往 D 中加入一个已存在的符号或者扫描完所有文件时 U 非空,链接器报错。否则 E 中所有目标文件重定位生成可执行文件。

与静态库的链接

类 UNIX 系统中,静态库文件的格式为存档文件(archive),后缀为 .a。如,标准 C 函数库文件名为 libc.a,包含了 atoi、printf、scanf、strcpy 等广泛使用的函数。它是默认的用于静态链接的库文件,无需在链接命令中指出。还有其他的库函数,如浮点数运算库:libm.a。

咱们也可以自己定义静态库,让我们以下面的例子说明怎样生成自己的静态库文件。
例如有两个源文件 myproc1.c 和 myproc2.c :
muproc1.c

#include<stdio.h>
void myfunc1(){printf("%s", "This is myfunc1 from mylib!\n");
}

myproc2.c

#include<stdio.h>
void myfunc2(){printf("%s", "This is myfunc2 from mylib!\n");
}

并有一个 main.c 程序调用了 mylib.a 中的 myfunc1:

void myfunc1(void);
int main(){myfunc1();return 0;
}

生成静态库前先将两个文件编译成可重定位文件,然后使用 AR 工具生成静态库:

使用:

将 main.c 汇编为可重定位文件 main.o 然后再将 main.o 和 mylib.a 以及 libc.a链接,生成可执行文件 myproc。

-static 选项只是链接器生成一个能直接加载到存储器执行的可执行文件。

执行 myfunc:

链接器中符号解析的过程如下图所示:


符号解析的结果也命令行指定的文件顺序有关,如果使用:

gcc -static -o myproc ./mylib.a main.o

则会发生链接错误,先从静态库 mylib.a 中查找 U 中未定义的符号,但此时 U 中为空,则 mylib.a 中没有目标模块被加入到 E 中,当扫描到 main.o 时,其引用符号 myfunc1 不能解析,最后导致 U 非空,就会出错。

静态库链接顺序的准则: 将静态库文件放在可执行文件后。如果静态库文件的目标模块中的符号没有引用关系,则顺序可独立;如果有引用关系,则引用符号的静态库在前,定义符号的静态库在后。

(4.3)符号表和符号解析相关推荐

  1. 【编译原理笔记15】运行存储分配概述,静态存储分配,栈式存储分配,调用序列和返回序列,非局部数据的访问,符号表,符号表建立

    本次笔记内容: 7-1 运行存储分配概述 7-2 静态存储分配 7-3 栈式存储分配 7-4 调用序列和返回序列 7-5 非局部数据的访问 7-6 符号表 7-7 符号表建立 本节课幻灯片,见于我的 ...

  2. 【计算机系统基础】符号表、符号解析(详解)

    一.符号.符号表 符号:通俗地说,就是前面跟着类型(如int/void等)的函数名,或变量名. 注意:这里的变量必须是除了非静态局部变量之外的其它变量. 所有符号载入史册--符号表. 分类: ①Glo ...

  3. 什么是符号表、符号解析、符号重定位?

    1.什么是符号表? 2.何为符号解析和符号重定位? 在链接中,将函数和变量统称为符号.函数名或变量名称为符号名. 链接过程中很关键的一部分就是符号的管理,每一个目标文件都会有一个相应的符号表,这个表里 ...

  4. 【Android 逆向】ELF 文件格式 ( 程序头数据 | 节区头数据 | 动态符号表 )

    文章目录 一.程序头数据 二.节区头数据 三.动态符号表 一.程序头数据 在上一篇博客 [Android 逆向]ELF 文件格式 ( ELF 程序头入口大小 | ELF 程序头入口个数 | ELF 文 ...

  5. unity android 符号表,记录腾讯bugly关于符号表的配置

    Bugly bugly的熟练使用,可以帮助移动开发迅速定位线上bug,帮助解决问题.在使用过程中,我们需要知道什么是符号表,为什么要配置符号表. 符号表 符号表是内存地址与函数名.文件名.行号的映射表 ...

  6. Linux内核符号表

    一,什么是符号(Symbols)?     什么是Symbol?  其实就是kernel中的变量(Variable Name)或函数名称(Function Name),     这样可以方便程序员在写 ...

  7. C语言的符号表和类型系统1

    阅读博客的朋友可以到我的网易云课堂中,通过视频的方式查看代码的调试和执行过程: http://study.163.com/course/courseMain.htm?courseId=10028300 ...

  8. 计算机基础--符号及符号表

    链接分为两个步骤: 符号解析 重定位 符号解析 符号定义和符号引用 注意局部变量分配在栈中,不在过程外被引用,不算符号定义 符号表 全局符号.外部符号.局部符号 .symtab节记录符号表信息 强符号 ...

  9. 自己动手写编译器:符号表及其实现

    本节我们要完成一个任务,给定如下一段代码: {int x; char y; {bool y; x; y;} x; y;} 解析上面代码后输出结果为: {{x:int; y:bool} x:int; y ...

最新文章

  1. 洛谷1216 数字三角形
  2. 最终篇:简洁易懂,初学者挑战学习Python编程30天 (五)
  3. Python基础编程——字典
  4. 分布式锁实现:Redis
  5. js/jquery遇到的坑总结
  6. Spring MVC 解读——@Autowired、@Controller、@Service从原理层面来分析
  7. 通过Grafana访问Mysql/MariaDB -- Web端数据库管理、分析、可视化工具
  8. PVFS2 1.4.0的安装、配置与性能测试
  9. php 显示html文件后缀,[求助]此jQuery在html后缀文件可用,在php后缀文件就用不了!...
  10. USB-CAN模块使用
  11. Inpaint图片去水印工具
  12. 泛在网、物联网与传感器网络有什么区别
  13. 关于前端一个用于设置渐变色的css代码网站
  14. 加强c语言,加强学法指导 提高C语言学习能力
  15. 智慧社区可研究性报告
  16. Bitwise Operators
  17. python如何打开npy文件_python实现npy格式文件转换为txt文件操作
  18. 8个输入法高效使用技巧,大幅提升你的打字效率
  19. Macbook外置移动硬盘安装win10教程
  20. 2亿简历信息泄露-你的信息还安全吗?

热门文章

  1. java微信平台验证票据
  2. 【498. 对角线遍历】
  3. winrar 命令解压缩
  4. 背着房贷被裁员是一种什么样的体验?
  5. OpenCV + CPP 系列(九)颜色空间
  6. 2018-12-22-jekyll-theme-H2O
  7. QT The inferior stopped because it received a signal from the operating system.
  8. python下载自己网易云歌单的歌曲
  9. 提供在Linux上运行最新版腾讯QQ与TIM的解决方案 Easiest Way to Run QQTIM on Linux
  10. 齐齐哈尔鹤城计算机学校,齐齐哈尔阳光学校