《程序员的自我修养——链接、装载与库》读书笔记—— 3.5 链接的接口:符号

概念

符号(Symbel):函数和变量
符号名(Symbel Name):函数名和变量名

每个目标文件中都有符号表(Synbel Table),在上篇文章中提到的段表中可以找到它:
《程序员的自我修养——链接、装载与库》读书笔记—— 3.5 链接的接口:符号_第1张图片

其中包含有:

  • 全局符号,包括本文件中的函数,以及全局变量
  • 外部符号,本目标文件中引用但未定义的符号
  • 局部符号,主要为局部变量,在链接过程中不起作用
  • 段名(如".text",".bss"),由编译器产生,其值为该段的起始地址。
  • 行号信息,目标文件指令对应源码中的行号

链接过程主要针对上述前两种类型

ELF符号表结构

符号表保存在".symtab"段中,为一个 Elf32_Sym 结构体数组,该结构体的定义如下

代码段1
/*./include/linux/elf.h +178*/
typedef struct elf32_sym{
  Elf32_Word    st_name; 	//符号名在字符串表(elf文件中的".strtab"段)中的下标
  Elf32_Addr    st_value;	//通常是符号在其所在段中的偏移地址,有时候是一个值
  Elf32_Word    st_size;	//符号大小。如double型的符号(全局变量等),st_size为 8 byte。
  unsigned char st_info;	//符号类型(包括数据,函数,段,当前elf对应的源文件名等),
  							//和绑定信息(包括局部,全局,弱引用)
  unsigned char st_other;	//
  Elf32_Half    st_shndx;	//符号所在段的下标
} Elf32_Sym;

使readelf -s可以查看符号表,每一行即为一个Elf32_Sym 结构体,如下

代码段2
zqxl@ubuntu:/work/myprojects/code3-1$ readelf -s simple_section.o

Symbol table '.symtab' contains 19 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx     Name
//符号表                  大小                        所在段
//数组下标  符号值        (字节) 类型    绑定信息        下标    符号名
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS simple_section.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: 0000000000000000     0 SECTION LOCAL  DEFAULT    6
     7: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    3 var.1803
     8: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    4 var2.1804
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    8
    10: 0000000000000000     0 SECTION LOCAL  DEFAULT    9
    11: 0000000000000000     0 SECTION LOCAL  DEFAULT    7
    12: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 global_init_var
    13: 0000000000000004     4 OBJECT  GLOBAL DEFAULT  COM g_uninit_var
    14: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    5 var
    15: 0000000000000000    36 FUNC    GLOBAL DEFAULT    1 func1
    16: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    17: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
    18: 0000000000000024    43 FUNC    GLOBAL DEFAULT    1 main

查看上边的符号表信息时可以对照段表结构(见本文附录)。如符号表下标为7的符号为已初始化的静态局部变量var(见附录源码);该符号的 Ndx 为3,从段表结构可以看出该段为.data段。又如未初始化的静态局部变量位于4段,即.bss段。
值得注意的是,未初始化的全局变量g_uninit_var 以及静态局部变量的 Ndx 是“COM”,即.comment段,而不是预想的.bss段。这与编译器有关。这里的g_uninit_var目前只是一个符号,等最终链接生成了可执行文件,才会在.bss段分配空间

强/弱符号

  1. 定义
    编译器默认将函数和初始化了的全局变量作为强符号,未初始化的全局变量为弱符号

  2. 规则

    1. 同名强符号只能有一个,但是同名弱符号可以同时存在多个。
    2. 编译时优先使用强符号
    3. 没有强符号,只有多个弱符号时,优先使用占用内存较大的那个弱符号
  3. 举例
    在不同的文件中定义两个同名全局变量将导致符号重复定义错误。但是如果将其中一个定义为弱符号,将不会报错,如下:
    在code1.c文件中:
    int g_var=1;
    在code2.c文件中:
    __attribute__((weak)) int g_var=1;
    这样将编译通过。

强/弱引用

对于强引用,链接器找不到符号的定义就会报错;对于弱引用则不会。同时,弱引用的符号如果未被定义,会被赋值为0 或者一个特殊值。
《程序员的自我修养——链接、装载与库》读书笔记—— 3.5 链接的接口:符号_第2张图片

附录 源码

int printf(const char *format,...);

int global_init_var=90;
int g_uninit_var;

__attribute__((section("Test_Section"))) int var=126;

void func1(int i)
{
        printf("a=%d\n",i);
}

int main(void)
{
    static int var = 89;
    static int var2;
    int a=1;
    int b;

    func1(var + var2);
    return 0;
}

附录 段表结构

使用readelf -S查看对应的段表结构:

代码段3
zqxl@ubuntu:/work/myprojects/code3-1$ readelf -S simple_section.o
There are 14 section headers, starting at offset 0x478:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       000000000000004f  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  00000360
       0000000000000078  0000000000000018   I      11     1     8
  [ 3] .data             PROGBITS         0000000000000000  00000090
       0000000000000008  0000000000000000  WA       0     0     4
  [ 4] .bss              NOBITS           0000000000000000  00000098
       0000000000000004  0000000000000000  WA       0     0     4
  [ 5] TestSection       PROGBITS         0000000000000000  00000098
       0000000000000004  0000000000000000  WA       0     0     4
  [ 6] .rodata           PROGBITS         0000000000000000  0000009c
       0000000000000006  0000000000000000   A       0     0     1
  [ 7] .comment          PROGBITS         0000000000000000  000000a2
       000000000000002c  0000000000000001  MS       0     0     1
  [ 8] .note.GNU-stack   PROGBITS         0000000000000000  000000ce
       0000000000000000  0000000000000000           0     0     1
  [ 9] .eh_frame         PROGBITS         0000000000000000  000000d0
       0000000000000058  0000000000000000   A       0     0     8
  [10] .rela.eh_frame    RELA             0000000000000000  000003d8
       0000000000000030  0000000000000018   I      11     9     8
  [11] .symtab           SYMTAB           0000000000000000  00000128
       00000000000001c8  0000000000000018          12    12     8
  [12] .strtab           STRTAB           0000000000000000  000002f0
       000000000000006a  0000000000000000           0     0     1
  [13] .shstrtab         STRTAB           0000000000000000  00000408
       000000000000006d  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

你可能感兴趣的:(《程序员的自我修养——链接,装载与库》读书笔记,linux,嵌入式,c语言,elf,符号)