在编译和链接 C/C++ 程序时,符号(函数、变量)有三个关键属性决定它们如何被链接和访问:
- LLVM IR Linkage - 编译器层面的链接类型
- ELF Bind - 静态链接时的符号绑定
- ELF Visibility - 动态链接时的符号可见性
核心概念
1
2
3
4
5
| 编译 汇编 静态链接 动态链接
C/C++ 代码 ────────→ LLVM IR ────────→ .o 文件 ────────────→ .so/exe ────────────→ 进程内存
(Linkage) (Bind+Vis) ↓ ↓
Bind 决定 Vis 决定
哪个定义胜出 是否导出/可抢占
|
| 属性 | 生成阶段 | 生效阶段 | 核心问题 |
|---|
| Linkage | 编译时 | IR → .o | 符号在 IR 中的链接属性 |
| Bind | 编译时写入 .o | .o → .so/exe(静态链接) | 多个 .o 有同名符号,选哪个? |
| Vis | 编译时写入 .o | .so 加载到进程(动态链接) | 符号能否被其他 .so 看到/覆盖? |
快速参考
Bind(绑定) - 静态链接行为:
LOCAL: .o 文件内局部,外部看不到GLOBAL: 所有 .o 可见,参与链接WEAK: 所有 .o 可见,可被 GLOBAL 覆盖
Vis(可见性) - 动态链接行为:
DEFAULT: 根据 Bind 决定,可被抢占HIDDEN: .so 外不可见(不在 .dynsym)PROTECTED: .so 外可见,且内部调用一定用本 .so 的实现,不会被抢占
示例代码
demo.cpp - 所有链接类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
| // demo.cpp - ELF Symbol Attributes & LLVM IR Linkage Demo
// Target: aarch64-linux-ohos
// readelf 输出格式: Num: Value Size Type Bind Vis Ndx Name
#include <cstdio>
// 1. EXTERNAL LINKAGE (default)
// ELF: GLOBAL DEFAULT
int external_var = 42;
// IR: @external_var = global i32 42
// .o: 57: 0000000000000000 4 OBJECT GLOBAL DEFAULT 20 external_var
// .so: 10: 0000000000004968 4 OBJECT GLOBAL DEFAULT 20 external_var
void external_func() {
printf("external_func called\n");
}
// IR: define void @_Z13external_funcv()
// .o: 43: 0000000000000000 28 FUNC GLOBAL DEFAULT 2 _Z13external_funcv
// .so: 19: 000000000000224c 28 FUNC GLOBAL DEFAULT 11 _Z13external_funcv
// 2. INTERNAL LINKAGE (static)
// ELF: LOCAL DEFAULT
static int static_var = 100;
// IR: @_ZL10static_var = internal global i32 100
// .o: 13: 0000000000000010 4 OBJECT LOCAL DEFAULT 20 _ZL10static_var
// .so: 不在 .dynsym(LOCAL 不导出)
static void static_func() {
printf("static_func: static_var = %d\n", static_var);
}
// IR: define internal void @_ZL11static_funcv()
// .o: 12: 0000000000000224 36 FUNC LOCAL DEFAULT 2 _ZL11static_funcv
// .so: 不在 .dynsym(LOCAL 不导出)
// 3. WEAK LINKAGE
// ELF: WEAK DEFAULT
__attribute__((weak))
int weak_var = 200;
// IR: @weak_var = weak global i32 200
// .o: 58: 0000000000000004 4 OBJECT WEAK DEFAULT 20 weak_var
// .so: 11: 000000000000496c 4 OBJECT WEAK DEFAULT 20 weak_var
__attribute__((weak))
void weak_func() {
printf("weak_func (default impl)\n");
}
// IR: define weak void @_Z9weak_funcv()
// .o: 45: 000000000000001c 28 FUNC WEAK DEFAULT 2 _Z9weak_funcv
// .so: 20: 0000000000002268 28 FUNC WEAK DEFAULT 11 _Z9weak_funcv
// 4. HIDDEN VISIBILITY
// ELF: GLOBAL HIDDEN (in .symtab, NOT in .dynsym)
__attribute__((visibility("hidden")))
int hidden_var = 300;
// IR: @hidden_var = hidden global i32 300
// .o: 47: 0000000000000008 4 OBJECT GLOBAL HIDDEN 20 hidden_var
// .so: 不在 .dynsym!(HIDDEN 不导出到动态符号表)
__attribute__((visibility("hidden")))
void hidden_func() {
printf("hidden_func: hidden_var = %d\n", hidden_var);
}
// IR: define hidden void @_Z11hidden_funcv()
// .o: 46: 0000000000000038 36 FUNC GLOBAL HIDDEN 2 _Z11hidden_funcv
// .so: 不在 .dynsym!(HIDDEN 不导出到动态符号表)
// 5. PROTECTED VISIBILITY
// ELF: GLOBAL PROTECTED (in .dynsym, but non-preemptable)
__attribute__((visibility("protected")))
int protected_var = 400;
// IR: @protected_var = protected global i32 400
// .o: 49: 000000000000000c 4 OBJECT GLOBAL PROTECTED 20 protected_var
// .so: 21: 0000000000004974 4 OBJECT GLOBAL PROTECTED 20 protected_var
__attribute__((visibility("protected")))
void protected_func() {
printf("protected_func: protected_var = %d\n", protected_var);
}
// IR: define protected void @_Z14protected_funcv()
// .o: 48: 000000000000005c 36 FUNC GLOBAL PROTECTED 2 _Z14protected_funcv
// .so: 9: 00000000000022a8 36 FUNC GLOBAL PROTECTED 11 _Z14protected_funcv
// 6. THREAD LOCAL STORAGE
// ELF: Type=TLS, GLOBAL DEFAULT
__thread int tls_var = 500;
// IR: @tls_var = thread_local global i32 500
// .o: TLS GLOBAL DEFAULT tls_var
__thread int tls_zero; // Uninitialized → .tbss
// IR: @tls_zero = thread_local global i32 0
// .o: TLS GLOBAL DEFAULT tls_zero
// 7. LINKONCE_ODR (inline functions)
// ELF: WEAK DEFAULT (with COMDAT, may be discarded if unreferenced)
inline void inline_func() {
printf("inline_func\n");
}
// IR: define linkonce_odr void @_Z11inline_funcv() comdat
// .o: 62: 0000000000000000 28 FUNC WEAK DEFAULT 15 _Z11inline_funcv
// .so: 29: 0000000000002530 28 FUNC WEAK DEFAULT 11 _Z11inline_funcv
inline int inline_counter() {
static int count = 0;
// IR: @_ZZ14inline_countervE5count = linkonce_odr global i32 0, comdat
// .o: 65: 0000000000000000 4 OBJECT WEAK DEFAULT 28 _ZZ14inline_countervE5count
return ++count;
}
// IR: define linkonce_odr i32 @_Z14inline_counterv() comdat
// .o: 63: 0000000000000000 24 FUNC WEAK DEFAULT 18 _Z14inline_counterv
// .so: 13: 000000000000254c 24 FUNC WEAK DEFAULT 11 _Z14inline_counterv
// 8. WEAK_ODR (templates)
// ELF: WEAK DEFAULT (with COMDAT)
template<typename T>
T template_add(T a, T b) {
return a + b;
}
// IR: define weak_odr i32 @_Z12template_addIiET_S0_S0_(i32, i32) comdat
// .o: 50: 0000000000000000 32 FUNC WEAK DEFAULT 5 _Z12template_addIiET_S0_S0_
// .so: 22: 00000000000024c0 32 FUNC WEAK DEFAULT 11 _Z12template_addIiET_S0_S0_
template int template_add<int>(int, int);
template double template_add<double>(double, double);
template<typename T>
int template_counter() {
static int count = 0;
// IR: @_ZZ16template_counterIiEivE5count = linkonce_odr global i32 0, comdat
// .o: 53: 0000000000000000 4 OBJECT WEAK DEFAULT 24 _ZZ16template_counterIiEivE5count
return ++count;
}
// IR: define weak_odr i32 @_Z16template_counterIiEiv() comdat
// .o: 52: 0000000000000000 24 FUNC WEAK DEFAULT 9 _Z16template_counterIiEiv
template int template_counter<int>();
template int template_counter<float>();
// 9. EXTERN_WEAK (optional symbols)
// ELF: WEAK DEFAULT, Ndx=UND (becomes null if not linked)
__attribute__((weak))
extern void optional_func();
// IR: declare extern_weak void @_Z13optional_funcv()
// .o: 64: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Z13optional_funcv
// .so: 7: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Z13optional_funcv
__attribute__((weak))
extern int optional_var;
// 10. PRIVATE LINKAGE (compiler-generated, cannot write directly)
// ELF: NOT in symbol table
printf("Linkage Demo");
// IR: @.str = private unnamed_addr constant [13 x i8] c"Linkage Demo\00"
// .o: 不在符号表(private 符号)
|
demo_weak_lib.cpp - WEAK 定义(被覆盖方)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // demo_weak_lib.cpp - Weak definitions (to be overridden)
// readelf 输出格式: Num: Value Size Type Bind Vis Ndx Name
#include <cstdio>
__attribute__((weak))
int override_var = 111;
// IR: @override_var = weak global i32 111
// .o: 25: 0000000000000000 4 OBJECT WEAK DEFAULT 4 override_var
__attribute__((weak))
void override_func() {
printf("[demo_weak_lib.cpp] override_func (WEAK default)\n");
}
// IR: define weak void @_Z13override_funcv()
// .o: 22: 0000000000000000 28 FUNC WEAK DEFAULT 2 _Z13override_funcv
__attribute__((weak))
void weak_only_func() {
printf("[demo_weak_lib.cpp] weak_only_func (no override exists)\n");
}
// IR: define weak void @_Z14weak_only_funcv()
// .o: 24: 000000000000001c 28 FUNC WEAK DEFAULT 2 _Z14weak_only_funcv
|
demo_weak_main.cpp - GLOBAL 覆盖(覆盖方)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // demo_weak_main.cpp - GLOBAL overrides for WEAK symbols
// readelf 输出格式: Num: Value Size Type Bind Vis Ndx Name
#include <cstdio>
extern int override_var;
extern void override_func();
extern void weak_only_func();
int override_var = 999;
// IR: @override_var = global i32 999 ← GLOBAL(无 weak 关键字)
// .o: 25: 0000000000000000 4 OBJECT GLOBAL DEFAULT 4 override_var
void override_func() {
printf("[demo_weak_main.cpp] override_func (GLOBAL override!)\n");
}
// IR: define void @_Z13override_funcv() ← GLOBAL(无 weak 关键字)
// .o: 22: 0000000000000000 28 FUNC GLOBAL DEFAULT 2 _Z13override_funcv
// weak_only_func 只有声明
// IR: declare void @_Z14weak_only_funcv()
// .o: 26: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z14weak_only_funcv
|
链接后 demo_weak.so (.dynsym):
1
2
3
4
| Num: Value Size Type Bind Vis Ndx Name
8: 0000000000001a04 28 FUNC GLOBAL DEFAULT 11 _Z13override_funcv ← GLOBAL 胜出
10: 0000000000003dc8 4 OBJECT GLOBAL DEFAULT 20 override_var ← GLOBAL 胜出
11: 0000000000001ab4 28 FUNC WEAK DEFAULT 11 _Z14weak_only_funcv ← 无覆盖,保持 WEAK
|
demo_common.c - COMMON 链接(仅 C 语言)
需要 -fcommon 标志(Clang 11+ 默认 -fno-common)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // demo_common.c - COMMON linkage (C only)
// readelf 输出格式: Num: Value Size Type Bind Vis Ndx Name
#include <stdio.h>
// COMMON - 无初始化的全局变量
int common_var;
// IR: @common_var = common global i32 0
// .o: 26: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM common_var
int common_array[100];
// IR: @common_array = common global [100 x i32] zeroinitializer
// .o: 27: 0000000000000004 400 OBJECT GLOBAL DEFAULT COM common_array
// NOT COMMON - 有初始化
int defined_zero = 0;
// IR: @defined_zero = global i32 0 ← 不是 common
// .o: 28: 0000000000000000 4 OBJECT GLOBAL DEFAULT 4 defined_zero
int defined_value = 42;
// IR: @defined_value = global i32 42 ← 不是 common
// .o: 29: 0000000000000000 4 OBJECT GLOBAL DEFAULT 5 defined_value
|
COMDAT 组
COMDAT 用于处理 linkonce_odr / weak_odr 符号的去重。查看命令:
1
| $ llvm-readelf --section-groups demo.o
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| COMDAT group section [ 4] `.group' [_Z12template_addIiET_S0_S0_] contains 1 sections:
[Index] Name
[ 5] .text._Z12template_addIiET_S0_S0_
COMDAT group section [ 14] `.group' [_Z11inline_funcv] contains 2 sections:
[Index] Name
[ 15] .text._Z11inline_funcv
[ 16] .rela.text._Z11inline_funcv
COMDAT group section [ 17] `.group' [_Z14inline_counterv] contains 2 sections:
[Index] Name
[ 18] .text._Z14inline_counterv
[ 19] .rela.text._Z14inline_counterv
COMDAT group section [ 27] `.group' [_ZZ14inline_countervE5count] contains 1 sections:
[Index] Name
[ 28] .bss._ZZ14inline_countervE5count
|
当多个 .o 文件包含相同的 COMDAT 组时,链接器只保留一份,丢弃重复。
总结表格
LLVM IR Linkage → ELF 属性
| C/C++ 代码 | LLVM IR | ELF Bind | ELF Vis | 跨 .o 访问 | 导出到 .dynsym |
|---|
int x = 1; | global | GLOBAL | DEFAULT | ✅ | ✅ |
static int x; | internal | LOCAL | DEFAULT | ❌ | ❌ |
| (字符串字面量) | private | — | — | ❌ | ❌ |
__attribute__((weak)) | weak | WEAK | DEFAULT | ✅ | ✅ |
template<T> f() | weak_odr | WEAK | DEFAULT | ✅ | ✅ |
inline void f() | linkonce_odr | WEAK | DEFAULT | ✅ | ✅ |
int x; (C, 无初始化) | common | GLOBAL | DEFAULT | ✅ | ✅ |
weak extern | extern_weak | WEAK | DEFAULT | ✅ | — |
visibility("hidden") | hidden | GLOBAL | HIDDEN | ❌ | ❌ |
visibility("protected") | protected | GLOBAL | PROTECTED | ✅ | ✅ |
__thread | thread_local | GLOBAL | DEFAULT | ✅ | ✅ |
ELF 头文件定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // 符号绑定 (STB_) - st_info 高 4 位
// 控制静态链接行为:.o → .so/exe
#define STB_LOCAL 0 // .o 文件内局部,外部看不到
#define STB_GLOBAL 1 // 所有 .o 可见,参与链接
#define STB_WEAK 2 // 所有 .o 可见,可被 GLOBAL 覆盖
// 符号可见性 (STV_) - st_other 低 2 位
// 控制动态链接行为:.so 载进应用时能不能被调
#define STV_DEFAULT 0 // 默认,根据 Bind 决定
#define STV_HIDDEN 2 // .so 外不可见(不在 .dynsym)
#define STV_PROTECTED 3 // .so 外可见,内部调用一定用本 .so 的实现
// 符号类型 (STT_) - st_info 低 4 位
#define STT_NOTYPE 0 // 未指定
#define STT_OBJECT 1 // 数据(变量)
#define STT_FUNC 2 // 函数
#define STT_COMMON 5 // 未初始化公共块(C,Ndx=COM)
#define STT_TLS 6 // 线程局部存储
|
验证命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 构建
./build.sh
# 查看 LLVM IR
grep -E "^@|^define" demo.ll
# 查看 .o 符号表
llvm-readelf --symbols demo.o
# 查看 .so 动态符号表(导出符号)
llvm-readelf --dyn-syms demo.so
# 查看 COMDAT 组
llvm-readelf --section-groups demo.o
# 查看 COMMON 符号
llvm-readelf --symbols demo_common.o | grep COM
|
相关资料
- https://llvm.org/docs/LangRef.html#linkage
- /Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native/sysroot/usr/include/elf.h