链接器与符号表
链接是 Kotlin/Native 编译的最后阶段。理解链接过程和符号管理能够解决大部分构建问题。
链接过程
编译流程
Kotlin 源码
↓
K2 Frontend
↓
Kotlin IR
↓
LLVM IR
↓
LLVM 优化
↓
机器码 (.o)
↓
链接器 ──→ 可执行文件/库
↑
依赖库链接器类型
Kotlin/Native 支持多种链接器:
| 链接器 | 平台 | 特点 |
|---|---|---|
| ld | Linux/macOS | 传统链接器 |
| lld | 全平台 | LLVM 链接器,速度快 |
| mold | Linux | 现代链接器,极速 |
| link.exe | Windows | MSVC 链接器 |
符号表
符号类型
kotlin
// Kotlin 代码
package com.example
fun publicFunction() { }
internal fun internalFunction() { }
private fun privateFunction() { }
class MyClass {
fun method() { }
}生成的符号(简化):
符号表:
- _publicFunction [GLOBAL]
- _internalFunction [LOCAL]
- _MyClass_method [GLOBAL]
- _privateFunction [LOCAL]查看符号
bash
# 编译
kotlinc-native main.kt -o app
# 查看符号
nm app.kexe
# 输出示例
0000000000401000 T _ZN3com7example14publicFunctionEv
0000000000401100 t _ZN3com7example16internalFunctionEv
U _ZN6kotlin2io7printlnbash
nm -g app.kexe # 只显示全局符号
# 输出
0000000100000000 T _kfun:com.example#publicFunction(){}
U _kfun:kotlin.io#println(kotlin.String){}bash
dumpbin /SYMBOLS app.exe
# 输出
000 00000001 ABS notype Static | @comp.id
001 00000000 SECT1 notype Static | .text
002 00000000 SECT1 notype () External | kfun:main符号修饰 (Name Mangling)
Kotlin/Native 使用名称修饰区分重载函数:
kotlin
// Kotlin
fun add(a: Int, b: Int): Int
fun add(a: Double, b: Double): Double# 符号表
kfun:add(kotlin.Int,kotlin.Int)kotlin.Int
kfun:add(kotlin.Double,kotlin.Double)kotlin.Double链接选项
基础链接
bash
# 链接系统库
kotlinc-native main.kt -o app \
-linker-option -lm # 数学库
-linker-option -lpthread # 线程库
# 链接自定义库
kotlinc-native main.kt -o app \
-linker-option -L/usr/local/lib \
-linker-option -lmylib静态vs动态链接
bash
# 动态链接
kotlinc-native -produce dynamic mylib.kt -o libmylib
# 使用
kotlinc-native app.kt -o app \
-linker-option -L. \
-linker-option -lmylib
# 运行时需要 libmylib.so
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./app.kexebash
# 静态库
kotlinc-native -produce static mylib.kt -o libmylib
# 使用
kotlinc-native app.kt -o app \
-linker-option -L. \
-linker-option -lmylib \
-linker-option -static
# 独立可执行文件
./app.kexe # 无需额外库符号可见性
kotlin
// 控制符号导出
@CName("my_exported_function")
fun exportedFunction() { }
// 内部符号(不导出)
internal fun internalFunction() { }bash
# Gradle 配置
kotlin {
linuxX64 {
binaries.sharedLib {
// 导出符号配置
export(project(":shared"))
// 链接器可见性
freeCompilerArgs += "-Xexport-kdoc"
}
}
}链接错误排查
undefined reference
bash
# 错误信息
undefined reference to `my_function'
# 原因1:缺少库
kotlinc-native main.kt -o app \
-linker-option -L/path/to/lib \
-linker-option -lmissing_lib
# 原因2:符号名称不匹配
# 检查实际符号名
nm libmylib.so | grep my_function
# 原因3:链接顺序
# 依赖库应放在后面
kotlinc-native main.kt -o app \
-linker-option -lmylib \ # 使用 myfunction
-linker-option -lbase # 被 mylib 依赖duplicate symbol
bash
# 错误
duplicate symbol '_kfun:main' in:
obj1.o
obj2.o
# 原因:多个文件定义同名函数
# 解决:使用不同的包名或internal
// file1.kt
package com.example.module1
fun main() { }
// file2.kt
package com.example.module2
fun main() { }符号版本冲突
bash
# GLIBC 版本错误
./app: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found
# 解决:在旧系统上编译,或静态链接
kotlinc-native main.kt -o app \
-linker-option -static-libgcc \
-linker-option -static-libstdc++高级技巧
链接脚本
ld
/* linker.ld */
SECTIONS
{
.text : { *(.text*) }
.data : { *(.data*) }
.bss : { *(.bss*) }
}bash
# 使用自定义链接脚本
kotlinc-native main.kt -o app \
-linker-option -T \
-linker-option linker.ld减小二进制大小
bash
# 1. 启用优化
kotlinc-native -opt -Xlto main.kt
# 2. 移除调试符号
strip app.kexe
# 减少 30-40%
# 3. 压缩(UPX)
upx --best app.kexe
# 额外减少 50-70%符号隐藏
kotlin
// 在 Gradle 中配置
kotlin {
linuxX64 {
binaries.sharedLib {
// 默认隐藏所有符号
freeCompilerArgs += "-Xvisibility=hidden"
// 仅导出标记的符号
exportDeclarations {
export("com.example.publicAPI")
}
}
}
}性能影响
链接器对比
| 链接器 | 链接时间 (大项目) | 内存占用 | 支持平台 |
|---|---|---|---|
| ld | 基准 (100s) | 高 | Linux/macOS |
| lld | 30-40s (3x) | 中 | 全平台 |
| mold | 10-15s (10x) | 低 | Linux |
启用 lld
kotlin
// build.gradle.kts
kotlin {
targets.withType<KotlinNativeTarget> {
binaries.all {
// 使用 lld
freeCompilerArgs += "-linker-option"
freeCompilerArgs += "--use-lld"
}
}
}理解链接过程能够避免 90% 的构建问题。遇到链接错误时,首先检查符号表,然后验证库路径和链接顺序。