zoukankan      html  css  js  c++  java
  • 程序运行之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,并将它们都初始化,那么链接器将AB进行链接时会报错:

    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() ");

    }

    int main(int argc,char *argv[]){

    f();

        return 0;

    }

    test1.c中的代码如下:

    #include <stdio.h>

    void f(void){

    printf("override f() ");

    }

    执行结果如下:

    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! ");  

                // run the multi-thread version  

                // main_multi_thread()  

        }   

        else   

        {  

                printf("This is single-thread version! ");     

                // 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! 

  • 相关阅读:
    Quartz.Net 作业调度后台管理系统,基于Extjs
    [备份]EntityFramework
    WebMisSharp升级说明,最新版本1.6.0
    AllPay(欧付宝)支付接口集成
    Paypal Rest Api自定义物流地址(跳过填写物流地址)
    根据IP获取国家
    ViewBag 找不到编译动态表达式所需的一种或多种类型,是否缺少引用?
    Extjs4 DateTimeField,日期时间控件完美版
    IOS Swift 训练
    .Net集成PayPal的Demo
  • 原文地址:https://www.cnblogs.com/zhanghongfeng/p/9074505.html
Copyright © 2011-2022 走看看