Post

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 加载后符号能否被其他库访问或覆盖。适合想深入理解符号可见性和链接行为的开发者。

LLVM Linkage & ELF Bind,Visibility

在编译和链接 C/C++ 程序时,符号(函数、变量)有三个关键属性决定它们如何被链接和访问:

  1. LLVM IR Linkage - 编译器层面的链接类型
  2. ELF Bind - 静态链接时的符号绑定
  3. 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 IRELF BindELF Vis跨 .o 访问导出到 .dynsym
int x = 1;globalGLOBALDEFAULT
static int x;internalLOCALDEFAULT
(字符串字面量)private
__attribute__((weak))weakWEAKDEFAULT
template<T> f()weak_odrWEAKDEFAULT
inline void f()linkonce_odrWEAKDEFAULT
int x; (C, 无初始化)commonGLOBALDEFAULT
weak externextern_weakWEAKDEFAULT
visibility("hidden")hiddenGLOBALHIDDEN
visibility("protected")protectedGLOBALPROTECTED
__threadthread_localGLOBALDEFAULT

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
  • alt text
  • /Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native/sysroot/usr/include/elf.h
All Rights Reserved.