LLVM Linkage & ELF Bind,Visibility
本文探讨 C/C++ 程序编译链接过程中符号的三个关键属性:LLVM IR Linkage(编译器层面)、ELF Bind(静态链接)和 ELF Visibility(动态链接)。通过完整的代码示例,演示了 external、static、weak、hidden、protected、TLS、inline、template 等各种链接类型,并展示对应的 LLVM IR 和 llvm-readelf 输出。重点阐述了 Bind 决定多个 .o 文件链接时哪个定义胜出,Vis 决定 .so 加载后符号能否被其他库访问或覆盖。适合想深入理解符号可见性和链接行为的开发者。
在编译和链接 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 决定
哪个定义胜出 其他so是否可见/可抢占
| 属性 | 生成阶段 | 生效阶段 | 核心问题 |
|---|---|---|---|
| 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 组时,链接器只保留一份,丢弃重复。
静态链接符号选择
静态链接规则总结
在静态链接阶段(.o → .so/exe),多个目标文件中的同名符号按以下规则选择:
| 场景 | 规则 | 说明 |
|---|---|---|
| GLOBAL vs GLOBAL | 错误(除非使用 --allow-multiple-definition) | 默认不允许重复定义,使用 --allow-multiple-definition 时,第一个提供的符号胜出(代码大小无关) |
| GLOBAL vs WEAK | GLOBAL 总是胜出 | |
| WEAK vs WEAK | 第一个提供的符号胜出 | 代码大小无关,按链接器命令行中的顺序选择 |
关键点:
- 代码大小(codesize)不影响符号选择
- 链接器命令行中的顺序决定哪个符号被使用
- GLOBAL 符号的优先级总是高于 WEAK 符号
动态链接规则总结(PLT 调用)
在动态链接阶段(.so 加载到进程内存),当多个库包含同名符号且都是 DEFAULT 可见性时:
| 场景 | PLT 解析规则 | 说明 |
|---|---|---|
| GLOBAL vs WEAK | 第一个 GLOBAL 符号胜出 | 动态链接器按库加载顺序搜索,找到的第一个 GLOBAL 符号被用于 PLT 解析 |
| GLOBAL vs GLOBAL | 第一个 GLOBAL 符号胜出 | 如果多个库都有 GLOBAL 符号,按加载顺序选择第一个 |
| WEAK vs WEAK | 第一个 WEAK 符号胜出 | 如果只有 WEAK 符号,按加载顺序选择第一个 |
PLT(Procedure Linkage Table)机制:
- 外部函数调用通过 PLT 进行间接跳转
- PLT 条目在运行时由动态链接器解析到实际的符号地址
- 对于
DEFAULT可见性的符号,PLT 解析遵循上述规则 PROTECTED可见性的符号不会被抢占,PLT 总是解析到本.so内的实现
重要:PLT 解析时机与 dlopen 的影响
PLT 解析发生在首次调用时(lazy binding,默认行为):
- WEAK 先加载,GLOBAL 后通过 dlopen 加载:
- 如果 PLT 尚未解析(函数未被调用),GLOBAL 符号会在解析时胜出
- 如果 PLT 已经解析到 WEAK 符号(函数已被调用),已解析的地址不会自动更新为 GLOBAL
- 已解析的 PLT 条目会被缓存,后续调用继续使用 WEAK 符号
- GLOBAL 先加载,WEAK 后通过 dlopen 加载:
- PLT 解析时,GLOBAL 符号优先,即使 WEAK 后加载也不会覆盖已解析的 GLOBAL
- Eager binding(LD_BIND_NOW):
- 所有符号在加载时立即解析
- 如果 WEAK 先加载并解析,后续 dlopen 的 GLOBAL 不会覆盖已解析的 WEAK
总结:GLOBAL 符号只有在解析时刻可用时才会胜出。如果 WEAK 符号已经完成 PLT 解析,后续加载的 GLOBAL 不会自动替换它。
静态链接demo
为了示例代码看起来简洁预先设置
1
2
alias clang='/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native/llvm/bin/clang -g -target aarch64-linux-ohos -O0'
export PATH=/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native/llvm/bin/:$PATH
global vs global
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
echo 'int test_func(void) { return 0; }
int caller_a(void) { return test_func(); }' > demo_global_a.c
echo 'int test_func(void) { int var; var = 1 + 1; return var; }
int caller_b(void) { return test_func(); }' > demo_global_b.c
clang -fPIC -c demo_global_a.c -o demo_global_a.o
clang -fPIC -c demo_global_b.c -o demo_global_b.o
# 错误:重复符号
clang -shared demo_global_a.o demo_global_b.o -o demo_gg_error.so 2>&1
# ld.lld: error: duplicate symbol: test_func
# >>> defined at demo_global_a.c:1
# >>> demo_global_a.o:(test_func)
# >>> defined at demo_global_b.c:1
# >>> demo_global_b.o:(.text+0x0)
# 使用 --allow-multiple-definition:第一个胜出(即使第一个代码更小)
clang -shared -Wl,--allow-multiple-definition demo_global_a.o demo_global_b.o -o demo_gg_ab.so
llvm-readelf -s demo_gg_ab.so | grep test_func
# Num: Value Size Type Bind Vis Ndx Name
# 7: 000000000000175c 8 FUNC GLOBAL DEFAULT 10 test_func
llvm-addr2line -e demo_gg_ab.so $(nm -D demo_gg_ab.so | grep test_func | awk '{print $1}')
# demo_global_a.c:1
# caller_a() 通过 plt 调用 test_func()
llvm-objdump -d demo_gg_ab.so | grep -A 5 "caller_a>:"
# 0000000000001764 <caller_a>:
# 1764: a9bf7bfd stp x29, x30, [sp, #-16]!
# 1768: 910003fd mov x29, sp
# 176c: 94000035 bl 0x1840 <test_func@plt>
# 1770: a8c17bfd ldp x29, x30, [sp], #16
# 1774: d65f03c0 ret
# b 先提供,b 胜出
clang -shared -Wl,--allow-multiple-definition demo_global_b.o demo_global_a.o -o demo_gg_ba.so
llvm-readelf -s demo_gg_ba.so | grep test_func
# Num: Value Size Type Bind Vis Ndx Name
# 7: 000000000000175c 24 FUNC GLOBAL DEFAULT 10 test_func
llvm-addr2line -e demo_gg_ba.so $(nm -D demo_gg_ba.so | grep test_func | awk '{print $1}')
# demo_global_b.c:1
global vs weak
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
echo 'int test_func(void) { return 0; }
int caller_global(void) { return test_func(); }' > demo_global.c
echo '__attribute__((weak)) int test_func(void) { return 2; }
int caller_weak(void) { return test_func(); }' > demo_weak.c
clang -fPIC -c demo_global.c -o demo_global.o
clang -fPIC -c demo_weak.c -o demo_weak.o
# GLOBAL 总是胜出(代码大小无关)
clang -shared demo_global.o demo_weak.o -o demo_gw.so
llvm-readelf -s demo_gw.so | grep test_func
# Num: Value Size Type Bind Vis Ndx Name
# 7: 0000000000001764 8 FUNC GLOBAL DEFAULT 10 test_func
llvm-addr2line -e demo_gw.so $(nm -D demo_gw.so | grep test_func | awk '{print $1}')
# demo_global.c:1
# caller_global() 通过 plt 调用 test_func()
llvm-objdump -d demo_gw.so | grep -A 5 "caller_global>:"
# 000000000000176c <caller_global>:
# 176c: a9bf7bfd stp x29, x30, [sp, #-16]!
# 1770: 910003fd mov x29, sp
# 1774: 94000033 bl 0x1840 <test_func@plt>
# 1778: a8c17bfd ldp x29, x30, [sp], #16
# 177c: d65f03c0 ret
# caller_weak() 也通过 plt 调用 test_func()
llvm-objdump -d demo_gw.so | grep -A 5 "caller_weak>:"
# 0000000000001788 <caller_weak>:
# 1788: a9bf7bfd stp x29, x30, [sp, #-16]!
# 178c: 910003fd mov x29, sp
# 1790: 9400002c bl 0x1840 <test_func@plt>
# 1794: a8c17bfd ldp x29, x30, [sp], #16
# 1798: d65f03c0 ret
weak vs weak
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
echo '__attribute__((weak)) int test_func(void) { return 0; }
int caller_a(void) { return test_func(); }' > demo_weak_a.c
echo '__attribute__((weak)) int test_func(void) { int var; var = 1 + 1; return var; }
int caller_b(void) { return test_func(); }' > demo_weak_b.c
clang -fPIC -c demo_weak_a.c -o demo_weak_a.o
# Num: Value Size Type Bind Vis Ndx Name
# 20: 0000000000000000 8 FUNC WEAK DEFAULT 2 test_func
# 0000000000000000 <test_func>:
# 0: 2a1f03e0 mov w0, wzr
# 4: d65f03c0 ret
clang -fPIC -c demo_weak_b.c -o demo_weak_b.o
# Num: Value Size Type Bind Vis Ndx Name
# 20: 0000000000000000 24 FUNC WEAK DEFAULT 2 test_func
# 0000000000000000 <test_func>:
# 0: d10043ff sub sp, sp, #16
# 4: 52800048 mov w8, #2
# 8: b9000fe8 str w8, [sp, #12]
# c: b9400fe0 ldr w0, [sp, #12]
# 10: 910043ff add sp, sp, #16
# 14: d65f03c0 ret
# A 先提供 → A 被选中,即使 B 更大
clang -shared demo_weak_a.o demo_weak_b.o -o demo_ww_ab.so
llvm-readelf -s demo_ww_ab.so | grep test_func
# Num: Value Size Type Bind Vis Ndx Name
# 7: 000000000000175c 8 FUNC WEAK DEFAULT 10 test_func
llvm-addr2line -e demo_ww_ab.so $(nm -D demo_ww_ab.so | grep test_func | awk '{print $1}')
# demo_weak_a.c:1
llvm-objdump -d demo_ww_ab.so | grep -A 5 "caller_a>:"
# 0000000000001764 <caller_a>:
# 1764: a9bf7bfd stp x29, x30, [sp, #-16]!
# 1768: 910003fd mov x29, sp
# 176c: 94000035 bl 0x1840 <test_func@plt> # PLT 调用
# 1770: a8c17bfd ldp x29, x30, [sp], #16
# B 先提供 → B 被选中
clang -shared demo_weak_b.o demo_weak_a.o -o demo_ww_ba.so
llvm-readelf -s demo_ww_ba.so | grep test_func
# Num: Value Size Type Bind Vis Ndx Name
# 7: 000000000000175c 24 FUNC WEAK DEFAULT 10 test_func
llvm-addr2line -e demo_ww_ba.so $(nm -D demo_ww_ba.so | grep test_func | awk '{print $1}')
# demo_weak_b.c:1
llvm-objdump -d demo_ww_ba.so | grep -A 5 "caller_b>:"
# 0000000000001774 <caller_b>:
# 1774: a9bf7bfd stp x29, x30, [sp, #-16]!
# 1778: 910003fd mov x29, sp
# 177c: 94000031 bl 0x1840 <test_func@plt> # PLT 调用
# 1780: a8c17bfd ldp x29, x30, [sp], #16
动态链接 PLT 示例
当函数在不同 .so 文件中定义时,调用会通过 PLT:
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
# 创建两个库,一个 GLOBAL,一个 WEAK
echo 'int test_func(void) { return 1; }' > libglobal.c
echo '__attribute__((weak)) int test_func(void) { return 2; }' > libweak.c
cat > caller.c << 'EOF'
#include <stdio.h>
extern int test_func(void);
int main(void) {
printf("%d\n", test_func());
return 0;
}
EOF
clang -fPIC -shared libglobal.c -o libglobal.so
clang -fPIC -shared libweak.c -o libweak.so
# 示例 1:只链接 WEAK 库
clang caller.c -L. -lweak -o test_weak
LD_LIBRARY_PATH=. ./test_weak
# 2
# 示例 2:同时链接 WEAK 和 GLOBAL 库
clang caller.c -L. -lglobal -lweak -o test_both
LD_LIBRARY_PATH=. ./test_both
# 1
# 示例 3:验证 PLT 解析时机 - WEAK 先加载,GLOBAL 后通过 dlopen 加载
cat > test_dlopen.c << 'EOF'
#include <stdio.h>
#include <dlfcn.h>
extern int test_func(void);
int main(void) {
printf("exe 只 -l 了 weak, 此时PLT 尚未解析, 首次调用: %d\n", test_func());
void *handle = dlopen("./libglobal.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}
printf("dlopen GLOBAL 后再次调用: %d\n", test_func()); // PLT 已解析到 WEAK, 不会自动更新为 GLOBAL
return 0;
}
EOF
clang test_dlopen.c -L. -lweak -ldl -o test_dlopen
LD_LIBRARY_PATH=. ./test_dlopen
# exe 只 -l 了 weak, 此时PLT 尚未解析, 首次调用: 2
# dlopen GLOBAL 后再次调用: 2
总结
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
