程序运行之ELF 符号表
当一个工程中有多个文件的时候,链接的本质就是要把多个不同的目标文件相互粘到一起。就想玩具积木一样整合成一个整体。为了使不同的目标文件之间能够相互粘合,这些目标文件之间必须要有固定的规则才行。比如目标文件B用到了目标文件A中的函数”foo”,那么我们就称目标文件A定义了函数foo,目标文件B引用了函数foo。每个函数和变量都有自己独特的名字,避免链接过程中不同变量和函数之间的混淆。在链接过程中,我们将函数和变量统称为符号。函数或者变量名就是符号名
每一个目标文件都会有一个相应的符号表,这个表里面记录了目标文件中所用到的所有符号。每个定义的符号有一个对应的值,叫做符号值,对于变量和函数来说, 符号值就是它们的地址。我们可以通过nm命令来查看目标文件中的符号结果。
root@zhf-maple:/home/zhf/c_prj# nm main.o
0000000000000000 T func1
0000000000000004 C global_init_var
U _GLOBAL_OFFSET_TABLE_
0000000000000000 D global_var
0000000000000024 T main
U printf
0000000000000000 b static_var2.2257
0000000000000004 d static_var.2256
符号表条目有如下结构(from elf.h):
typedef struct {
ELF32_Word st_name;
ELF32_Addr st_value;
ELF32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half sth_shndx;
} Elf32_Sym;
ELF符号表域说明:
域 |
描述 |
st_name |
符号串表索引. 串表用于保存符号名. |
st_value |
符号值: 符号的section索引为SHN_COMMON:符号对齐要求. 重定位文件:离section起始位置的偏移. 执行文件:符号的地址. |
st_size |
对象大小. |
st_info >> 4 |
高4位定义符号的绑定[binding ]: STB_LOCAL (0) symbol is local to the file STB_GLOBAL (1) symbol is visible to all object files STB_WEAK (2) symbol is global with lower precedence |
st_info & 15 |
低4位定义符号的类型: STT_NOTYPE (0) 无类型 STT_OBJECT (1) 数据对象(变量) STT_FUNC (2) 函数 STT_SECTION (3) section名 STT_FILE (4) 文件名 |
st_other |
未使用. |
st_shndx |
定义符号sectiond的索引.特殊的section数包括: SHN_UNDEF (0x0000) 未定义section SHN_ABS (0xfff1) 绝对, 不可重定位符号 SHN_COMMON (0xfff2) 不分配, 外部变量 |
符号所在的段
宏定义名 |
值 |
说明 |
SHN_ABS |
0xfff1 |
该符号包含了一个绝对值,比如表示文件名的符号 |
SHN_COMMON |
0xfff2 |
表示该符号是一个"COMMON块"的符号 一般来说,未初始化的全局符号定义就是这种类型的。 |
SHN_UNDEF |
0 |
该符号在本目标文件中被引用到,但是定义在其他目标文件中 |
我们还是通过readelf命令来查看下main.o文件中的符号。下面的结果和上面的表可以进行一一对应。
root@zhf-maple:/home/zhf/c_prj# readelf -s main.o
Symbol table '.symtab' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 static_var.2256
7: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 static_var2.2257
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 6
11: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_var
12: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM global_init_var
13: 0000000000000000 36 FUNC GLOBAL DEFAULT 1 func1
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
16: 0000000000000024 40 FUNC GLOBAL DEFAULT 1 main
弱符号与强符号
我们经常在编程中碰到一种情况叫符号重复定义。多个目标文件中含有相同名字全局符号的定义,那么这些目标文件链接的时候将会出现符号重复定义的错误。比如我们在目标文件A和目标文件B都定义了一个全局整形变量global,并将它们都初始化,那么链接器将A和B进行链接时会报错:
1 b.o:(.data+0x0): multiple definition of `global'
2 a.o:(.data+0x0): first defined here
这种符号的定义可以被称为强符号(Strong Symbol)。有些符号的定义可以被称为弱符号(Weak Symbol)。对于C语言来说,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号(C++并没有将未初始化的全局符号视为弱符号)。我们也可以通过GCC的"__attribute__((weak))"来定义任何一个强符号为弱符号。注意,强符号和弱符号都是针对定义来说的,不是针对符号的引用。比如我们有下面这段程序:
extern int ext;
int weak1;
int strong = 1;
int __attribute__((weak)) weak2 = 2;
int main()
{
return 0;
}
上面这段程序中,"weak"和"weak2"是弱符号,"strong"和"main"是强符号,而"ext"既非强符号也非弱符号,因为它是一个外部变量的引用。链接器会按照如下的规则处理被多次定义的全局符号:
规则1:不允许强符号被多次定义。
规则2:如果一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号。
规则3:如果一个符号在所有的目标文件中都是弱符号,那么选择其中占用空间最大的一个。
我们来看一个实际的例子:在下面的代码中f()没有被定义,因此会报错
int main()
{
f();
return 0;
}
g++ -o bin/Debug/linux_c obj/Debug/chapter8.o obj/Debug/main.o
obj/Debug/main.o:在函数‘main’中:
/home/zhf/codeblocks_prj/linux_c/main.c:15:对‘f’未定义的引用
如果将代码改成如下:
void __attribute__((weak)) f();
int main()
{
if (f){
f();
}
return 0;
}
居然编译通过了,甚至成功执行!让我们看看为什么?
首先声明了一个符号f(),属性为weak,但并不定义它,这样,链接器会将此未定义的weak symbol赋值为0,也就是说f()并没有真正被调用,试试看,去掉if条件,肯定core dump!
我们甚至可以定义强符号来override弱符号:
test.c中代码如下
#include<stdlib.h>
#include<stdio.h>
void __attribute__((weak)) f(){
printf("original f()\n");
}
int main(int argc,char *argv[]){
f();
return 0;
}
test1.c中的代码如下:
#include <stdio.h>
void f(void){
printf("override f()\n");
}
执行结果如下:
root@zhf-maple:/home/zhf/c_prj# gcc -c test.c test1.c
root@zhf-maple:/home/zhf/c_prj# gcc -o test test.o test1.o
root@zhf-maple:/home/zhf/c_prj# ./test
override f()
在Linux程序的设计中,如果一个程序被设计成可以支持单线程或多线程的模式,就可以通过弱引用的方法来判断当前的程序是链接到了单线程的Glibc库还是多线程的Glibc库(是否在编译时有-lpthread选项),从而执行单线程版本的程序或多线程版本的程序。我们可以在程序中定义一个pthread_create函数的弱引用,然后程序在运行时动态判断是否链接到pthread库从而决定执行多线程版本还是单线程版本:
#include <stdio.h>
#include <pthread.h>
int pthread_create( pthread_t*, const pthread_attr_t*,
void* (*)(void*), void*) __attribute__ ((weak));
int main()
{
if(pthread_create)
{
printf("This is multi-thread version!\n");
// run the multi-thread version
// main_multi_thread()
}
else
{
printf("This is single-thread version!\n");
// run the single-thread version
// main_single_thread()
}
}
执行结果如下:
$ gcc pthread.c -o pt
$ ./pt
This is single-thread version!
$ gcc pthread.c -lpthread -o pt
$ ./pt
This is multi-thread version!
程序运行之ELF 符号表的更多相关文章
- 程序运行之ELF文件结构
ELF目标文件格式的最前部是ELF文件头.包含了整个文件的基本属性.比如ELF文件版本,目标机器型号,程序入口地址等.然后是ELF的各个段,其中ELF文件中与段有关的重要结构就是段表.段表描述了ELF ...
- 程序运行之ELF文件的段
我们将之前的代码增加下变量来具体看下 在代码中增加了全局变量以及静态变量,还有一个简单的函数. #include <stdio.h> int global_var=1; int globa ...
- elf 文件格式探秘——程序运行背后的故事
摘要:本文主要讲解elf文件格式,通过readelf命令结合底层的相关数据结构,讲解相关内容,分析程序运行的基本原理. 本文来源:elf 文件格式探秘——程序运行背后的故事 http://blog.c ...
- ELF格式文件符号表全解析及readelf命令使用方法
http://blog.csdn.net/edonlii/article/details/8779075 1. 读取ELF文件头: $ readelf -h signELF Header: Magi ...
- ELF Format 笔记(七)—— 符号表
最是那一低头的温柔,像一朵水莲花不胜凉风的娇羞,道一声珍重,道一声珍重,那一声珍重里有蜜甜的忧愁 —— 徐志摩 ilocker:关注 Android 安全(新手) QQ: 2597294287 符号表 ...
- 程序减肥,strip,eu-strip 及其符号表
程序减肥,strip,eu-strip 及其符号表 我们要给我们生成的可执行文件和DSO瘦身,因为这样可以节省更多的磁盘空间,所以我们移除了debug信息,移除了符号表信息,同时我们还希望万一出事了, ...
- GDB如何调试没有符号表(未加-g选项的编译)的程序
/********************************************************************* * Author : Samson * Date ...
- ELF 动态链接 so的动态符号表(.dynsym)
静态链接中有一个专门的段叫符号表 -- ".symtab"(Symbol Table), 里面保存了所有关于该目标文件的符号的定义和引用. 动态链接中同样有一个段叫 动态符号表 - ...
- linux下实现在程序运行时的函数替换(热补丁)
声明:以下的代码成果,是参考了网上的injso技术,在本文的最后会给出地址,同时非常感谢injso技术原作者的分享. 但是injso文章中的代码存在一些问题,所以后面出现的代码是经过作者修改和检测的. ...
随机推荐
- 使用PowerDesigner进行代码生成
很多代码生成器都选择了从表结构来生成领域模型,这样的方案有一个前提,就是领域模型和数据库表结构是同构的,也就是说领域模型中的类和数据库中的记录结构十分吻合,这样数据库表结构可以简单的直接映射到领域模型 ...
- android中Invalidate和postInvalidate的差别
Android中实现view的更新有两组方法,一组是invalidate.还有一组是postInvalidate.当中前者是在UI线 程自身中使用,而后者在非UI线程中使用. Android提供了In ...
- ionic中actionsheet在安卓中显示样式问题
可以看到在浏览器上是正常的,在安卓上的样式没了 建议不要直接去动ionic的css文件,容易影响全局 方法:注释掉_action-sheet.sass中文件123行,针对安卓样式去写的样sass 保存 ...
- 10.3.1 一个CONNECT BY的样例
10.3.1 一个CONNECT BY的样例正在更新内容,请稍后
- 实战Jquery(一)--username校验
歇息了好几天,最终又開始学习了.jQuery? JQuery is designed to change the way that you write JavaScript. The focus of ...
- GMGC记实(上篇)
24日和25日參加了2014年GMGC大会,整体感觉今年的大会比前2届大会办的更符合听众的需求.由于今年的大会开设了开发人员训练营的分会场.在成都这样一个CP占主流的IT圈中非常有意义.另一点就是在会 ...
- 键盘上所有键位的ascii值
上次用的时候一直找,这次找到了,收藏起来. 0x1 鼠标左键 0x2 鼠标右键 0x3 CANCEL 键 0x4 鼠标中键 0x8 BACKSPACE 键 0x9 TAB 键 0xC CLEAR 键 ...
- AspectJ风格的Aop切点表达式
execution(*com.aptech.jb.epet.dao.hibimpl.*.*(..)) 这样写应该就可以了,这是com.aptech.jb.epet.dao.hibimpl 包下所有的类 ...
- win7不休眠方式设置
方式1:命令行下以管理员方式执行:powercfg -h off 方式2:右键个性化-->屏幕保护程序-->更改电源设置--->更改计算机睡眠时间--->是计算机进入睡眠状态选 ...
- JS 手势长按代码
同时支持长按和点击事件,无依赖版 <!DOCTYPE html> <html lang="en"> <head> <meta charse ...