author: hjjdebug
date: 2026年 01月 08日 星期四 12:45:41 CST
descrip: 计算机中的符号是什么意思?



符号, 符号化调试, 计算机编译,连接,调试离不开符号,到底什么是符号呢?

甲. 什么叫符号?

研究符号, 也应该从hello-world 开始, 这里的符号,就是elf文件中的符号

0 下面是测试代码

$ cat test.c
#include "stdio.h"
int main() 
{ 
	printf("hello\n");
	return 0; 
} 

把它编译成test 可执行文件
$ gcc -g -no-pie -o test test.c

对于名叫test 的elf 文件,

1 用 $ nm test 可列出其所有符号,非常简明

$ nm test
0000000000601030 B __bss_start
0000000000601030 b completed.7698
0000000000601020 D __data_start
0000000000601020 W data_start
0000000000400440 t deregister_tm_clones
0000000000400430 T _dl_relocate_static_pie
00000000004004b0 t __do_global_dtors_aux
0000000000600e18 t __do_global_dtors_aux_fini_array_entry
0000000000601028 D __dso_handle
0000000000600e20 d _DYNAMIC
0000000000601030 D _edata
0000000000601038 B _end
0000000000400574 T _fini
00000000004004e0 t frame_dummy
0000000000600e10 t __frame_dummy_init_array_entry
00000000004006c4 r __FRAME_END__
0000000000601000 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
000000000040058c r __GNU_EH_FRAME_HDR
00000000004003c8 T _init
0000000000600e18 t __init_array_end
0000000000600e10 t __init_array_start
0000000000400580 R _IO_stdin_used
0000000000400570 T __libc_csu_fini
0000000000400500 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
00000000004004e7 T main
                 U puts@@GLIBC_2.2.5
0000000000400470 t register_tm_clones
0000000000400400 T _start
0000000000601030 D __TMC_END__

一下子多出来这么多符号, 其实从源代码中,我们只知道main 符号 和 printf 符号,
printf 在这里被puts@@GLIBC_2.2.5 替代了.
其它的先不管了.
由此看出符号的几个特点:

  1. 符号首先要有名字,用以区别是这个符号还是哪个符号
    计算机中用一个4字节数表示.
    为什么是4字节?
    因为所有的名字字符串构成一个字符串节,排列在一起,这个4字节32位数,
    表示这个名字的首个字符在字符串节中的偏移位置, 4字节足够使用了

  2. 符号要有值. 这是符号存在的意义.
    例如:
    00000000004004e7 T main
    其值,在64bits 机器上占用8bytes 地址值,也足够大了

  3. 符号有类型. 例如这里的T,t,D,d,w,B,R 等等

2. 用 $ readelf -s test 有对符号更细致的描述.

它会列出动态符号与静态符号集合.

3. 用 $ readelf --dyn-syms test 会只列出动态符号

$ readelf --dyn-syms test

Symbol table '.dynsym' contains 4 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__

关注 puts(即转义的printf), 及__libc_start_main
动态符号, 其值暂时为0, 将来由loader 去确定符号具体的数值即内存偏移地址.
静态符号太长 66项,

Symbol table '.symtab' contains 66 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000400238     0 SECTION LOCAL  DEFAULT    1
     2: 0000000000400254     0 SECTION LOCAL  DEFAULT    2
     3: 0000000000400274     0 SECTION LOCAL  DEFAULT    3
     4: 0000000000400298     0 SECTION LOCAL  DEFAULT    4
     5: 00000000004002b8     0 SECTION LOCAL  DEFAULT    5
     6: 0000000000400318     0 SECTION LOCAL  DEFAULT    6
     7: 0000000000400356     0 SECTION LOCAL  DEFAULT    7
     8: 0000000000400360     0 SECTION LOCAL  DEFAULT    8
     9: 0000000000400380     0 SECTION LOCAL  DEFAULT    9
    10: 00000000004003b0     0 SECTION LOCAL  DEFAULT   10
    11: 00000000004003c8     0 SECTION LOCAL  DEFAULT   11
    12: 00000000004003e0     0 SECTION LOCAL  DEFAULT   12
    13: 0000000000400400     0 SECTION LOCAL  DEFAULT   13
    14: 0000000000400574     0 SECTION LOCAL  DEFAULT   14
    15: 0000000000400580     0 SECTION LOCAL  DEFAULT   15
    16: 000000000040058c     0 SECTION LOCAL  DEFAULT   16
    17: 00000000004005c8     0 SECTION LOCAL  DEFAULT   17
    18: 0000000000600e10     0 SECTION LOCAL  DEFAULT   18
    19: 0000000000600e18     0 SECTION LOCAL  DEFAULT   19
    20: 0000000000600e20     0 SECTION LOCAL  DEFAULT   20
    21: 0000000000600ff0     0 SECTION LOCAL  DEFAULT   21
    22: 0000000000601000     0 SECTION LOCAL  DEFAULT   22
    23: 0000000000601020     0 SECTION LOCAL  DEFAULT   23
    24: 0000000000601030     0 SECTION LOCAL  DEFAULT   24
    25: 0000000000000000     0 SECTION LOCAL  DEFAULT   25
    26: 0000000000000000     0 SECTION LOCAL  DEFAULT   26
    27: 0000000000000000     0 SECTION LOCAL  DEFAULT   27
    28: 0000000000000000     0 SECTION LOCAL  DEFAULT   28
    29: 0000000000000000     0 SECTION LOCAL  DEFAULT   29
    30: 0000000000000000     0 SECTION LOCAL  DEFAULT   30
    31: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    32: 0000000000400440     0 FUNC    LOCAL  DEFAULT   13 deregister_tm_clones
    33: 0000000000400470     0 FUNC    LOCAL  DEFAULT   13 register_tm_clones
    34: 00000000004004b0     0 FUNC    LOCAL  DEFAULT   13 __do_global_dtors_aux
    35: 0000000000601030     1 OBJECT  LOCAL  DEFAULT   24 completed.7698
    36: 0000000000600e18     0 OBJECT  LOCAL  DEFAULT   19 __do_global_dtors_aux_fin
    37: 00000000004004e0     0 FUNC    LOCAL  DEFAULT   13 frame_dummy
    38: 0000000000600e10     0 OBJECT  LOCAL  DEFAULT   18 __frame_dummy_init_array_
    39: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS test.c
    40: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    41: 00000000004006c4     0 OBJECT  LOCAL  DEFAULT   17 __FRAME_END__
    42: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS
    43: 0000000000600e18     0 NOTYPE  LOCAL  DEFAULT   18 __init_array_end
    44: 0000000000600e20     0 OBJECT  LOCAL  DEFAULT   20 _DYNAMIC
    45: 0000000000600e10     0 NOTYPE  LOCAL  DEFAULT   18 __init_array_start
    46: 000000000040058c     0 NOTYPE  LOCAL  DEFAULT   16 __GNU_EH_FRAME_HDR
    47: 0000000000601000     0 OBJECT  LOCAL  DEFAULT   22 _GLOBAL_OFFSET_TABLE_
    48: 0000000000400570     2 FUNC    GLOBAL DEFAULT   13 __libc_csu_fini
    49: 0000000000601020     0 NOTYPE  WEAK   DEFAULT   23 data_start
    50: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.5
    51: 0000000000601030     0 NOTYPE  GLOBAL DEFAULT   23 _edata
    52: 0000000000400574     0 FUNC    GLOBAL DEFAULT   14 _fini
    53: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_
    54: 0000000000601020     0 NOTYPE  GLOBAL DEFAULT   23 __data_start
    55: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    56: 0000000000601028     0 OBJECT  GLOBAL HIDDEN    23 __dso_handle
    57: 0000000000400580     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used
    58: 0000000000400500   101 FUNC    GLOBAL DEFAULT   13 __libc_csu_init
    59: 0000000000601038     0 NOTYPE  GLOBAL DEFAULT   24 _end
    60: 0000000000400430     2 FUNC    GLOBAL HIDDEN    13 _dl_relocate_static_pie
    61: 0000000000400400    43 FUNC    GLOBAL DEFAULT   13 _start
    62: 0000000000601030     0 NOTYPE  GLOBAL DEFAULT   24 __bss_start
    63: 00000000004004e7    23 FUNC    GLOBAL DEFAULT   13 main
    64: 0000000000601030     0 OBJECT  GLOBAL HIDDEN    23 __TMC_END__
    65: 00000000004003c8     0 FUNC    GLOBAL DEFAULT   11 _init

拣我们已知的. main, puts
能看懂名字的. _start, _init, __bss_start, _end, _edata, test.c, crtstuff.c,
其它就先不管了.

4 符号的构成要素

用readelf, 我们看到符号不仅有value, 而且还有size,
不仅有type, 而且还有Bind, Visibility, 还有index.
名称呢? 有的符号没有名称.
下面就要从计算机的角度来解释一下什么叫符号了.
不是含糊的概念, 而是准确的概念.
以64位cpu 为例.

  1. 前面说了, name 占4个bytes
  2. value 是一个8bytes 地址值
  3. size 是一个8bytes, 的大小值, 例如 main 函数,占用23bytes 大小
  4. index 是什么意思? 是说该符号属于哪个section, index 是section的index,用word表示,2bytes
  5. 把type 和 binding 合成一个byte, type 占低4bits, bind 占高4bits

5 符号在计算机中是如下定义的.

typedef struct
{
  Elf64_Word	st_name;		/* Symbol name (string tbl index) */ 4字节
  unsigned char	st_info;		/* Symbol type and binding */ 1字节
  unsigned char st_other;		/* Symbol visibility */       1字节
  Elf64_Section	st_shndx;		/* Section index */           2字节
  Elf64_Addr	st_value;		/* Symbol value */            8字节
  Elf64_Xword	st_size;		/* Symbol size */             8字节
} Elf64_Sym;

对大小还不敢确认, 看看其elf64.h 中的定义
typedef uint32_t Elf64_Word;
typedef uint16_t Elf64_Section;
typedef uint64_t Elf64_Addr;
typedef uint64_t Elf64_Xword;

6 补充1: type 的 定义

switch(sym_type) {
    case 0: return "NOTYPE";
    case 1: return "OBJECT";
    case 2: return "FUNC";
    case 3: return "SECTION";
    case 4: return "FILE";
    case 6: return "TLS";
    case 7: return "NUM";
    case 10: return "LOOS";
    case 12: return "HIOS";
    default: return "UNKNOWN";
}

7 补充2: bind 的 定义

switch(sym_bind) {
    case 0: return "LOCAL";
    case 1: return "GLOBAL";
    case 2: return "WEAK";
    case 3: return "NUM";
    case 10: return "UNIQUE";
    case 12: return "HIOS";
    case 13: return "LOPROC";
    default: return "UNKNOWN";

8 补充3: visibility 的 定义, 空间留了一个byte, 256种可能,但实际只有4种

switch(sym_vis) {
    case 0: return "DEFAULT";   //可见
    case 1: return "INTERNAL";
    case 2: return "HIDDEN";
    case 3: return "PROTECTED";
    default: return "UNKNOWN";
}

9 补充4: section header index, 留了2个bytes, 默认是索引号, 但也有几个特例.

switch(shdr_idx) {
    case SHN_ABS: return "ABS";
    case SHN_COMMON: return "COM";
    case SHN_UNDEF: return "UND";
    case SHN_XINDEX: return "COM";
    default: return std::to_string(shdr_idx);
}

#define SHN_UNDEF 0 /* Undefined section /
#define SHN_ABS 0xfff1 /
Associated symbol is absolute /
#define SHN_COMMON 0xfff2 /
Associated symbol is common /
#define SHN_XINDEX 0xffff /
Index is in extra table. */

10. 小结:

我们从形态,构造,功能方面用白话介绍了符号的概念, 希望对想了解编译,连接,调试的朋友有所帮助.
想了解重定位的概念, 请参考链接:
elf 格式 relocation 概念

参考代码: https://gitee.com/hejinjing/elf-parser.git

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐