author: hjjdebug
date: 2026年 01月 08日 星期四 12:45:41 CST
descrip: 计算机中的符号是什么意思?
文章目录
- 甲. 什么叫符号?
- 0 下面是测试代码
- 1 用 $ nm test 可列出其所有符号,非常简明
- 2. 用 $ readelf -s test 有对符号更细致的描述.
- 3. 用 $ readelf --dyn-syms test 会只列出动态符号
- 4 符号的构成要素
- 5 符号在计算机中是如下定义的.
- 6 补充1: type 的 定义
- 7 补充2: bind 的 定义
- 8 补充3: visibility 的 定义, 空间留了一个byte, 256种可能,但实际只有4种
- 9 补充4: section header index, 留了2个bytes, 默认是索引号, 但也有几个特例.
- 10. 小结:
符号, 符号化调试, 计算机编译,连接,调试离不开符号,到底什么是符号呢?
甲. 什么叫符号?
研究符号, 也应该从hello-world 开始, 这里的符号,就是elf文件中的符号
0 下面是测试代码
$ cat test.c#include"stdio.h"intmain(){printf("hello\n");return0;}把它编译成test 可执行文件
$ gcc -g -no-pie -o test test.c
对于名叫test 的elf 文件,
1 用 $ nm test 可列出其所有符号,非常简明
$ nm test0000000000601030B __bss_start0000000000601030b completed.76980000000000601020D __data_start0000000000601020W data_start0000000000400440t deregister_tm_clones0000000000400430T _dl_relocate_static_pie00000000004004b0 t __do_global_dtors_aux0000000000600e18t __do_global_dtors_aux_fini_array_entry0000000000601028D __dso_handle0000000000600e20d _DYNAMIC0000000000601030D _edata0000000000601038B _end0000000000400574T _fini00000000004004e0t frame_dummy0000000000600e10t __frame_dummy_init_array_entry00000000004006c4 r __FRAME_END__0000000000601000d _GLOBAL_OFFSET_TABLE_ w __gmon_start__000000000040058c r __GNU_EH_FRAME_HDR00000000004003c8 T _init0000000000600e18t __init_array_end0000000000600e10t __init_array_start0000000000400580R _IO_stdin_used0000000000400570T __libc_csu_fini0000000000400500T __libc_csu_init U __libc_start_main@@GLIBC_2.2.500000000004004e7T main U puts@@GLIBC_2.2.50000000000400470t register_tm_clones0000000000400400T _start0000000000601030D __TMC_END__一下子多出来这么多符号, 其实从源代码中,我们只知道main 符号 和 printf 符号,
printf 在这里被puts@@GLIBC_2.2.5 替代了.
其它的先不管了.
由此看出符号的几个特点:
符号首先要有名字,用以区别是这个符号还是哪个符号
计算机中用一个4字节数表示.
为什么是4字节?
因为所有的名字字符串构成一个字符串节,排列在一起,这个4字节32位数,
表示这个名字的首个字符在字符串节中的偏移位置, 4字节足够使用了符号要有值. 这是符号存在的意义.
例如:
00000000004004e7 T main
其值,在64bits 机器上占用8bytes 地址值,也足够大了符号有类型. 例如这里的T,t,D,d,w,B,R 等等
2. 用 $ readelf -s test 有对符号更细致的描述.
它会列出动态符号与静态符号集合.
3. 用 $ readelf --dyn-syms test 会只列出动态符号
$ readelf--dyn-syms test Symbol table'.dynsym'contains4entries:Num:Value Size Type Bind Vis Ndx Name0:00000000000000000NOTYPE LOCAL DEFAULT UND1:00000000000000000FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5(2)2:00000000000000000FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5(2)3:00000000000000000NOTYPE WEAK DEFAULT UND __gmon_start__关注 puts(即转义的printf), 及__libc_start_main
动态符号, 其值暂时为0, 将来由loader 去确定符号具体的数值即内存偏移地址.
静态符号太长 66项,
Symbol table'.symtab'contains66entries:Num:Value Size Type Bind Vis Ndx Name0:00000000000000000NOTYPE LOCAL DEFAULT UND1:00000000004002380SECTION LOCAL DEFAULT12:00000000004002540SECTION LOCAL DEFAULT23:00000000004002740SECTION LOCAL DEFAULT34:00000000004002980SECTION LOCAL DEFAULT45:00000000004002b80SECTION LOCAL DEFAULT56:00000000004003180SECTION LOCAL DEFAULT67:00000000004003560SECTION LOCAL DEFAULT78:00000000004003600SECTION LOCAL DEFAULT89:00000000004003800SECTION LOCAL DEFAULT910:00000000004003b00SECTION LOCAL DEFAULT1011:00000000004003c80SECTION LOCAL DEFAULT1112:00000000004003e00SECTION LOCAL DEFAULT1213:00000000004004000SECTION LOCAL DEFAULT1314:00000000004005740SECTION LOCAL DEFAULT1415:00000000004005800SECTION LOCAL DEFAULT1516:000000000040058c0SECTION LOCAL DEFAULT1617:00000000004005c80SECTION LOCAL DEFAULT1718:0000000000600e100SECTION LOCAL DEFAULT1819:0000000000600e180SECTION LOCAL DEFAULT1920:0000000000600e200SECTION LOCAL DEFAULT2021:0000000000600ff00SECTION LOCAL DEFAULT2122:00000000006010000SECTION LOCAL DEFAULT2223:00000000006010200SECTION LOCAL DEFAULT2324:00000000006010300SECTION LOCAL DEFAULT2425:00000000000000000SECTION LOCAL DEFAULT2526:00000000000000000SECTION LOCAL DEFAULT2627:00000000000000000SECTION LOCAL DEFAULT2728:00000000000000000SECTION LOCAL DEFAULT2829:00000000000000000SECTION LOCAL DEFAULT2930:00000000000000000SECTION LOCAL DEFAULT3031:00000000000000000FILE LOCAL DEFAULT ABS crtstuff.c32:00000000004004400FUNC LOCAL DEFAULT13deregister_tm_clones33:00000000004004700FUNC LOCAL DEFAULT13register_tm_clones34:00000000004004b00FUNC LOCAL DEFAULT13__do_global_dtors_aux35:00000000006010301OBJECT LOCAL DEFAULT24completed.769836:0000000000600e180OBJECT LOCAL DEFAULT19__do_global_dtors_aux_fin37:00000000004004e00FUNC LOCAL DEFAULT13frame_dummy38:0000000000600e100OBJECT LOCAL DEFAULT18__frame_dummy_init_array_39:00000000000000000FILE LOCAL DEFAULT ABS test.c40:00000000000000000FILE LOCAL DEFAULT ABS crtstuff.c41:00000000004006c40OBJECT LOCAL DEFAULT17__FRAME_END__42:00000000000000000FILE LOCAL DEFAULT ABS43:0000000000600e180NOTYPE LOCAL DEFAULT18__init_array_end44:0000000000600e200OBJECT LOCAL DEFAULT20_DYNAMIC45:0000000000600e100NOTYPE LOCAL DEFAULT18__init_array_start46:000000000040058c0NOTYPE LOCAL DEFAULT16__GNU_EH_FRAME_HDR47:00000000006010000OBJECT LOCAL DEFAULT22_GLOBAL_OFFSET_TABLE_48:00000000004005702FUNC GLOBAL DEFAULT13__libc_csu_fini49:00000000006010200NOTYPE WEAK DEFAULT23data_start50:00000000000000000FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.551:00000000006010300NOTYPE GLOBAL DEFAULT23_edata52:00000000004005740FUNC GLOBAL DEFAULT14_fini53:00000000000000000FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_54:00000000006010200NOTYPE GLOBAL DEFAULT23__data_start55:00000000000000000NOTYPE WEAK DEFAULT UND __gmon_start__56:00000000006010280OBJECT GLOBAL HIDDEN23__dso_handle57:00000000004005804OBJECT GLOBAL DEFAULT15_IO_stdin_used58:0000000000400500101FUNC GLOBAL DEFAULT13__libc_csu_init59:00000000006010380NOTYPE GLOBAL DEFAULT24_end60:00000000004004302FUNC GLOBAL HIDDEN13_dl_relocate_static_pie61:000000000040040043FUNC GLOBAL DEFAULT13_start62:00000000006010300NOTYPE GLOBAL DEFAULT24__bss_start63:00000000004004e723FUNC GLOBAL DEFAULT13main64:00000000006010300OBJECT GLOBAL HIDDEN23__TMC_END__65:00000000004003c80FUNC GLOBAL DEFAULT11_init拣我们已知的. main, puts
能看懂名字的. _start, _init, __bss_start, _end, _edata, test.c, crtstuff.c,
其它就先不管了.
4 符号的构成要素
用readelf, 我们看到符号不仅有value, 而且还有size,
不仅有type, 而且还有Bind, Visibility, 还有index.
名称呢? 有的符号没有名称.
下面就要从计算机的角度来解释一下什么叫符号了.
不是含糊的概念, 而是准确的概念.
以64位cpu 为例.
- 前面说了, name 占4个bytes
- value 是一个8bytes 地址值
- size 是一个8bytes, 的大小值, 例如 main 函数,占用23bytes 大小
- index 是什么意思? 是说该符号属于哪个section, index 是section的index,用word表示,2bytes
- 把type 和 binding 合成一个byte, type 占低4bits, bind 占高4bits
5 符号在计算机中是如下定义的.
typedefstruct{Elf64_Word st_name;/* Symbol name (string tbl index) */4字节unsignedcharst_info;/* Symbol type and binding */1字节unsignedcharst_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 中的定义typedefuint32_tElf64_Word;typedefuint16_tElf64_Section;typedefuint64_tElf64_Addr;typedefuint64_tElf64_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