当一个工程中有多个文件的时候,链接的本质就是要把多个不同的目标文件相互粘到一起。就想玩具积木一样整合成一个整体。为了使不同的目标文件之间能够相互粘合,这些目标文件之间必须要有固定的规则才行。比如目标文件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 符号表的更多相关文章

  1. 程序运行之ELF文件结构

    ELF目标文件格式的最前部是ELF文件头.包含了整个文件的基本属性.比如ELF文件版本,目标机器型号,程序入口地址等.然后是ELF的各个段,其中ELF文件中与段有关的重要结构就是段表.段表描述了ELF ...

  2. 程序运行之ELF文件的段

    我们将之前的代码增加下变量来具体看下 在代码中增加了全局变量以及静态变量,还有一个简单的函数. #include <stdio.h> int global_var=1; int globa ...

  3. elf 文件格式探秘——程序运行背后的故事

    摘要:本文主要讲解elf文件格式,通过readelf命令结合底层的相关数据结构,讲解相关内容,分析程序运行的基本原理. 本文来源:elf 文件格式探秘——程序运行背后的故事 http://blog.c ...

  4. ELF格式文件符号表全解析及readelf命令使用方法

    http://blog.csdn.net/edonlii/article/details/8779075 1. 读取ELF文件头: $ readelf -h signELF Header:  Magi ...

  5. ELF Format 笔记(七)—— 符号表

    最是那一低头的温柔,像一朵水莲花不胜凉风的娇羞,道一声珍重,道一声珍重,那一声珍重里有蜜甜的忧愁 —— 徐志摩 ilocker:关注 Android 安全(新手) QQ: 2597294287 符号表 ...

  6. 程序减肥,strip,eu-strip 及其符号表

    程序减肥,strip,eu-strip 及其符号表 我们要给我们生成的可执行文件和DSO瘦身,因为这样可以节省更多的磁盘空间,所以我们移除了debug信息,移除了符号表信息,同时我们还希望万一出事了, ...

  7. GDB如何调试没有符号表(未加-g选项的编译)的程序

    /********************************************************************* * Author  : Samson * Date    ...

  8. ELF 动态链接 so的动态符号表(.dynsym)

    静态链接中有一个专门的段叫符号表 -- ".symtab"(Symbol Table), 里面保存了所有关于该目标文件的符号的定义和引用. 动态链接中同样有一个段叫 动态符号表 - ...

  9. linux下实现在程序运行时的函数替换(热补丁)

    声明:以下的代码成果,是参考了网上的injso技术,在本文的最后会给出地址,同时非常感谢injso技术原作者的分享. 但是injso文章中的代码存在一些问题,所以后面出现的代码是经过作者修改和检测的. ...

随机推荐

  1. Linux下的各文件夹的作用(转)

    linux下的文件结构,看看每个文件夹都是干吗用的/bin 二进制可执行命令 /dev 设备特殊文件 /etc 系统管理和配置文件 /etc/rc.d 启动的配置文件和脚本 /home 用户主目录的基 ...

  2. Linux组件封装(五)一个生产者消费者问题示例

    生产者消费者问题是计算机中一类重要的模型,主要描述的是:生产者往缓冲区中放入产品.消费者取走产品.生产者和消费者指的可以是线程也可以是进程. 生产者消费者问题的难点在于: 为了缓冲区数据的安全性,一次 ...

  3. iOS语言本地化,中文显示

    尽管一直相信xcode肯定提供有语言本地化的设置地方,可是一直也没凑着去改.非常多的汉化,还是使用代码去控制:比方navagition的return使用代码改动为"返回"! 近期在 ...

  4. java计算时间差 Java问题通用解决代码

    java实现计算时间差     正式版:       /**        * 计算时间差,求出两者相隔的时间        *        * @param nowDate        *    ...

  5. E - Hangover(1.4.1)

    Time Limit:1000MS     Memory Limit:10000KB     64bit IO Format:%I64d & %I64u Submit cid=1006#sta ...

  6. leetcode第一刷_Binary Tree Zigzag Level Order Traversal

    以出现的频率来看.树的层序遍历一定是考察的重点,除非工作人员想找题水数量. zigzag,还是有几道题的,层序的这个非常easy,假设是奇数层.reverse下面就可以.无他.我写的时候预计还不知道这 ...

  7. quartz项目中的运用

    下面是之前项目中quartz的运用,我将它梳理出来. 测试类: public class OrdExpireTaskMain { public static void main(String[] ar ...

  8. 跳转 nginx 跳转 apache跳转

    公司在google上投广告,需要做一些很简单的站去google上投广告,当用户在google上点击那些很简单的网站的时候,就会跳转到真实的网站.但是,如果用户直接在浏览器输入域名,并访问的话,那样就不 ...

  9. MIC中示例程序计算π

    mic中编程十分简单,只需在普通程序中简单加几句就可以,使用 lspci|grep -i -co-processor 命令可以查看机器中是否插入MIC卡以及MIC卡的数目,MIC编程环境的配置这里就不 ...

  10. lnmp 环境require(): open_basedir restriction in effect 错误

    最近配置开发用的lnmp环境,环境配置完成后,爆500错误,查看nginx错误日志 open_basedir 将 PHP 所能打开的文件限制在指定的目录树,包括文件本身 错误日志显示,访问脚本不在 o ...