强符号(Strong Symbol)与弱符号(Weak Symbol)

在 C 和 C++ 中,符号(Symbol)是指程序中定义的变量、函数或对象的名称,它们在编译时或链接时被用来标识程序中的实体。
编译阶段:符号用于标识变量、函数等实体,并生成符号表(Symbol Table),记录符号的名称、类型、作用域等信息。
链接阶段:链接器根据符号表,解析符号的定义和引用,完成不同编译单元之间的符号绑定。
运行阶段:符号可能用于调试、动态链接或反射(特别是在动态语言或支持反射的语言中)。

1、符号类型

符号可以分为以下几种类型:

1.1、强符号(Strong Symbol)

由明确定义的变量或函数产生。
同一个程序中不能有多个强符号定义同名符号,否则链接器报错(重定义错误)。
定义全局变量或函数。
确保唯一性,避免冲突。
提供明确的符号定义,链接器优先选择强符号。
例如:

int x = 42;  // 强符号
void func() {
    // 强符号
}

1.2、弱符号(Weak Symbol)

通过特殊方式声明(如 attribute((weak))),表示它是备选符号。
如果有强符号与弱符号同名,链接器选择强符号;如果只有弱符号存在,链接器选择弱符号。
多个弱符号同名时,链接器只选择其中一个。
提供默认实现,允许用户通过强符号覆盖。
支持动态链接中的符号解析。
提供灵活性,支持符号覆盖机制。(malloc\free 都是弱符号,因此tcmalloc才能实现自己的malloc、free函数进行覆盖)
例如:

int x __attribute__((weak)) = 10;  // 弱符号

1.3、本地符号(Local Symbol)

由 static 修饰的变量或函数产生,作用域仅限于当前文件。
不会暴露给其他编译单元。
定义文件范围内的变量或函数。
防止符号污染,避免与其他文件的符号冲突。
限制作用域,提高代码的可维护性。
例如:

static int x = 42;  // 本地符号
static void func() {
    // 本地符号
}

1.4、全局符号(Global Symbol)

未被 static 修饰的变量或函数,作用域为整个程序。
在多个编译单元之间可见。
定义跨文件访问的变量或函数。
提供全局访问,支持符号共享。
例如:

int x = 42;  // 全局符号
void func() {
    // 全局符号
}

1.5、动态符号(Dynamic Symbol)

在动态链接中使用,用于解析动态库中的函数或变量。
动态库的符号解析。
支持动态链接和延迟加载。
使用extern 用于告诉编译器某个变量或函数在其他编译单元中定义,例如:

extern void dynamic_func();  // 动态符号

1.6、调试符号(Debug Symbol)

包含变量名、函数名和源代码行号等信息,用于调试。
通常在编译时通过 -g 选项生成。
调试程序。
帮助开发者定位错误。
编译时生成调试符号,如 : gcc -g -o program program.c

2、强符号与弱符号

2.1、强符号(Strong Symbol)

强符号是指明确定义的符号,通常由程序中的变量或函数的定义产生。链接器会优先使用强符号,如果有多个强符号定义同名符号,链接器会报错(重定义错误)。

int global_var = 42;  // 强符号
void my_function() { // 强符号
    // function body
}

2.2、弱符号(Weak Symbol)

弱符号是指通过特殊方式声明的符号,表示它是一个备选符号,链接器可以选择使用它,也可以被强符号覆盖。在 GCC 中,可以通过 attribute((weak)) 或 #pragma weak 来定义弱符号。
弱符号是“备选”的符号,它在链接器中被设计为可选使用。
如果有强符号与弱符号同名,链接器总是选择强符号,并忽略弱符号。
如果只有弱符号存在,链接器会使用弱符号。
如果多个弱符号同名,链接器会选择其中一个(通常是第一个找到的符号),而不会报错。(弱符号的选择顺序与链接器的输入文件顺序有关。)

int weak_var __attribute__((weak)) = 10;  // 弱符号
void weak_function() __attribute__((weak)); // 弱符号
void weak_function() {
    // function body
}

2.3、符号覆盖(Symbol Overriding)

符号覆盖是指在链接过程中,弱符号可以被强符号覆盖的行为。具体规则如下:
如果在多个模块中出现同名符号:
强符号覆盖弱符号:链接器会选择强符号,并忽略弱符号。
弱符号不会覆盖强符号:弱符号只能作为备选符号。
如果只有弱符号存在:链接器会使用弱符号。
弱符号可以用于实现 ABI 的向后兼容性。库的开发者可以通过弱符号提供默认实现,而用户可以通过强符号覆盖这些实现。
malloc \ free 都是弱符号,因此tcmalloc才能实现自己的malloc、free函数进行覆盖。

3、查看符号的工具

3.1、nm命令

nm 命令用于查看目标文件(如可执行文件或库)的符号表,列出程序中定义或引用的符号。每行对应一个符号,列表示该符号的属性。
nm 命令参数
-C:解码 C++ 符号。
-g:仅显示全局符号。
-u:仅显示未定义符号。
-A:显示文件名。
使用 -C 命令,输出如下:
在这里插入图片描述
什么都不加的,输出的是编译后的符号,函数名是加上其他信息的,如下:
在这里插入图片描述
输出具体含义如下:
列的含义
nm 输出通常包括以下列:
地址:符号在内存中的地址(如果是未定义符号,则地址为空)。
类型:符号的类型,表示符号的定义位置或作用。
名称:符号的名称。
符号类型(第二列)
符号类型可能有以下几种:
U:未定义符号(Undefined)。程序引用了该符号,但没有在当前文件中定义。
T:代码段(Text)。符号定义在代码段中,通常是函数。全局符号
D:数据段(Data)。符号定义在数据段中,通常是全局变量。全局符号
B:BSS段。符号定义在 BSS 段中,通常是未初始化的全局变量。全局符号
R:只读段(Read-only)。符号定义在只读数据段中,通常是常量。
W:弱符号(Weak)。弱符号可以被强符号覆盖。
V:弱符号的版本。
A:绝对地址(Absolute)。符号的地址是固定的,不会随加载位置变化。
C:公共符号(Common)。未初始化的变量,可能在多个文件中定义。
?:未知类型。
t:本地符号(代码段中的静态函数)。
d:本地符号(数据段中的静态变量)。

3.2、objdump 命令

objdump 是一个功能强大的工具,可以显示目标文件的各种信息。你使用的是 objdump -t,它用于显示符号表。
objdump 命令参数
-t:显示符号表。
-d:反汇编代码段。
-f:显示文件头信息。
-r:显示重定位信息。
-s:显示段内容。
使用 -t 参数,输出如下:
在这里插入图片描述
objdump -t
列的含义
objdump -t 输出的每行符号包括以下列:
地址:符号的内存地址。
类型:符号类型(如函数、变量等)。
段名:符号所在的段(如 .text、.data)。
大小:符号占用的字节数。
名称:符号的名称。
段名表示符号所在的段。常见的段名有:
.text:代码段,存储程序的机器指令。
.data:数据段,存储已初始化的全局变量。
.bss:BSS 段,存储未初始化的全局变量。
.rodata:只读数据段,存储常量和字符串字面量。
.dynamic:动态链接相关信息。
.got:全局偏移表(Global Offset Table),用于动态链接。
.plt:过程链接表(Procedure Linkage Table),用于动态链接函数调用。
.eh_frame:异常处理相关数据。
.symtab 和 .strtab:符号表和字符串表。
.debug_:调试信息(仅在调试版本中)。
这些段用于:
分离代码和数据:将代码和数据分开存储,便于管理和保护。
动态链接支持:通过 .plt 和 .got 支持动态链接。
调试支持:通过 .debug_
提供调试信息。
符号类型
符号类型部分包括以下内容:
l:局部符号(Local)。
g:全局符号(Global)。
w:弱符号(Weak)。
F:函数符号(Function)。
O:对象符号(Object)(变量)。
UND:未定义符号(Undefined)。

3.3、readelf 命令

readelf 是一个专门用于解析 ELF 文件的工具。你使用的是 readelf -s,它用于显示符号表。
readelf 命令参数
-s:显示符号表。
-h:显示 ELF 文件头。
-l:显示程序头表。
-r:显示重定位表。
-x:显示段内容。
–wide参数显示完整符号名。
在这里插入图片描述

1)readelf -s --wide a.out
在这里插入图片描述

2)readelf -s --wide a.out
列的含义
readelf -s 输出的每行符号包括以下列:
序号(Num):符号的编号。
值(Value):符号的地址。
大小(Size):符号占用的字节数。
类型(Type):符号的类型(如函数、对象等)。
绑定(Bind):符号的绑定类型(如局部、全局、弱符号)。
可见性(Vis):符号的可见性(如默认、隐藏等)。
段索引(Ndx):符号所在的段(如 .text、.data)。
名称(Name):符号的名称。
符号类型(Type)
NOTYPE:未指定类型。
FUNC:函数。
OBJECT:对象(如变量)。
SECTION:段符号。
FILE:文件符号。
绑定类型(Bind)
LOCAL:局部符号。
GLOBAL:全局符号。
WEAK:弱符号。
可见性(Vis)
DEFAULT:默认可见性。
HIDDEN:隐藏符号。
段索引(Ndx)
UND:未定义符号。
ABS:绝对地址。
数字:段的索引号。
常见的符号表和相关段:
.dynsym:
动态符号表,包含动态链接所需的符号(如外部库中的函数和变量)。
解决的问题:支持动态链接器解析符号。
.symtab:
静态符号表,包含所有符号(全局和局部符号)。
解决的问题:提供调试和静态分析信息。
.strtab:
字符串表,存储 .symtab 中符号名称的字符串。
解决的问题:为符号表中的符号提供名称。
.dynstr:
动态字符串表,存储 .dynsym 中符号名称的字符串。
解决的问题:为动态符号表中的符号提供名称。
.debug_*(如 .debug_info、.debug_line):
调试信息段,用于存储调试器(如 GDB)所需的符号和源代码信息。
解决的问题:支持调试功能。
.rela.dyn 和 .rela.plt:
重定位表,存储需要动态链接时修正的地址。
解决的问题:支持动态链接器修正符号地址。
.hash 或 .gnu.hash:
符号哈希表,用于加速动态链接器查找符号。
解决的问题:提高动态链接器解析符号的效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值