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 决定
                                                     哪个定义胜出          其他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 WEAKGLOBAL 总是胜出 
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,默认行为):

  1. WEAK 先加载,GLOBAL 后通过 dlopen 加载
    • 如果 PLT 尚未解析(函数未被调用),GLOBAL 符号会在解析时胜出
    • 如果 PLT 已经解析到 WEAK 符号(函数已被调用),已解析的地址不会自动更新为 GLOBAL
    • 已解析的 PLT 条目会被缓存,后续调用继续使用 WEAK 符号
  2. GLOBAL 先加载,WEAK 后通过 dlopen 加载
    • PLT 解析时,GLOBAL 符号优先,即使 WEAK 后加载也不会覆盖已解析的 GLOBAL
  3. 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 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.