程序运行之ELF 符号表

当一个工程中有多个文件的时候,链接的本质就是要把多个不同的目标文件相互粘到一起。就想玩具积木一样整合成一个整体。为了使不同的目标文件之间能够相互粘合,这些目标文件之间必须要有固定的规则才行。比如目标文件B用到了目标文件A中的函数”foo”,那么我们就称目标文件A定义了函数foo,目标文件B引用了函数foo。每个函数和变量都有自己独特的名字,避免链接过程中不同变量和函数之间的混淆。在链接过程中,我们将函数和变量统称为符号。函数或者变量名就是符号名

每一个目标文件都会有一个相应的符号表,这个表里面记录了目标文件中所用到的所有符号。每个定义的符号有一个对应的值,叫做符号值,对于变量和函数来说, 符号值就是它们的地址。我们可以通过nm命令来查看目标文件中的符号结果。

[email protected]:/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文件中的符号。下面的结果和上面的表可以进行一一对应。

[email protected]:/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");

}

执行结果如下:

[email protected]:/home/zhf/c_prj# gcc -c test.c test1.c

[email protected]:/home/zhf/c_prj# gcc -o test test.o test1.o

[email protected]:/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!

原文地址:https://www.cnblogs.com/zhanghongfeng/p/9074505.html

时间: 2024-12-18 07:21:50

程序运行之ELF 符号表的相关文章

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

http://blog.csdn.net/edonlii/article/details/8779075 1. 读取ELF文件头: $ readelf -h signELF Header:  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00  Class:                                                  ELF64  Data:                            

转 我修改的注册表,但是程序运行起来,还是记着以前的

我修改的注册表,但是程序运行起来,还是记着以前的,我查看了相关资料,说是修改只是暂时保存在memory,并没有保存到harddisk中,请高手指点一下,我用python写的,代码如下import win32apiimport win32con def RegSubkeySet(dbName, dbVersion):        key = win32api.RegOpenKey(win32con.HKEY_CURRENT_USER,'Software\\DSA\\PRODIS Office\\

查看程序符号表的几个命令

objdump -t xxx.o [email protected]:~/Documents/encrypchip$ objdump -t main.o main.o: file format elf32-little SYMBOL TABLE: 00000000 l df *ABS* 00000000 main.c 00000000 l d .text 00000000 .text 00000000 l d .data 00000000 .data 00000000 l d .bss 0000

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

最是那一低头的温柔,像一朵水莲花不胜凉风的娇羞,道一声珍重,道一声珍重,那一声珍重里有蜜甜的忧愁 —— 徐志摩 符号表 (symbol table) 中保存着符号的定义或者引用信息.对于 android so 文件来说,.dynsym 符号表保存着库文件的导入和导出符号. 用 readelf 看一下 android liblog.so 的 .dynsym 动态符号表: 符号 __cxa_finalize 的 Ndx(st_shndx) 为 UND,表明该符号在本 so 中未定义,需要去 libl

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

程序减肥,strip,eu-strip 及其符号表 我们要给我们生成的可执行文件和DSO瘦身,因为这样可以节省更多的磁盘空间,所以我们移除了debug信息,移除了符号表信息,同时我们还希望万一出事了,比如coredump了,我们能获取更多的信息,这时候我们又希望有符号表.     我们等不能做到呢.Linux下是怎么解决这个矛盾的呢?先看第一个问题,程序减肥.      1 程序减肥     我写了个简单的代码,main调用了foo,foo调用了bar,其中bar故意访问了非法地址,为了引起co

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

/*********************************************************************  * Author  : Samson  * Date    : 01/30/2015  * Test platform:  *              3.13.0-24-generic  *              GNU bash, 4.3.11(1)-release  * ************************************

使用objdump objcopy查看与修改符号表

使用objdump objcopy查看与修改符号表动态库Linuxgccfunction    我们在 Linux 下运行一个程序,有时会无法启动,报缺少某某库.这时需要查看可执行程序或者动态库中的符号表,动态库的依赖项, Linux 有现成的工具可用:objdump .    有时我们拿到一个静态库,想调用其中的函数,而某些函数作用域非全局,也可以通过修改符号来达到目的. Linux 有现成的工具可用: objcopy . 下面我们来看看具体怎么使用.    objdump 是 gcc 套件中

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

转自:http://www.cnblogs.com/leo0000/p/5632642.html 声明:以下的代码成果,是参考了网上的injso技术,在本文的最后会给出地址,同时非常感谢injso技术原作者的分享. 但是injso文章中的代码存在一些问题,所以后面出现的代码是经过作者修改和检测的.也正因为这些错误,加深了我的学习深度. 最近因为在学习一些调试的技术,但是很少有提到如何在函数运行时实现函数替换的. 为什么会想到这一点?因为在学习调试时,难免会看到一些内核方面的调试技术,内核中的调试

在程序运行时实现函数替换

声明:以下的代码成果,是参考了网上的injso技术,文章最后会给出地址. 另外一个,injso文章中的代码实际上不能够运行起来的,后面出现的代码都是经过我个人修改和检测的. 最近因为在学习一些调试的技术,但是很少有提到如何在函数运行时实现函数替换的. 为什么会想到这一点?因为在学习调试时,难免会看到一些内核方面的调试技术,内核中的调试有一个kprobe,很强大,可以实现运行时的函数替换.其原理就是hook,钩子,但是学习了这个kprobe之后会发现,kprobe内部有检测所要钩的函数是不是属于内