Golang中runtime模块的init()函数使用求助
Golang中runtime模块的init()函数使用求助
在 Go 源码 runtime 文件夹下的 symtab.go 文件中,modulesInit() 函数的注释中提到,当一个模块首次被动态链接器加载时,会调用 addmoduledata,并且可以加载多个使用
-linkshared构建的模块以及插件。
当我运行一个使用
-buildmode=shared和-linkshared标志构建的应用程序,并且插件在LD_PRELOAD中被使用时,modulesinit() 函数会被调用两次。
我的观察:
- myapplication 使用
-linkshared构建,并且插件通过plugin.Open()或LD_PRELOAD加载。在这种情况下,symtab.go 中的 modulesinit() 会被调用两次。 - 应用程序使用
-linkshared构建,并且使用-buildmode=shared创建的共享库通过LD_PRELOAD加载。在这种情况下,modulesinit() 不会被调用。
export LD_PRELOAD=/usr/lib/someplugin.so ; ./myapplication
对于插件的情况,如果我没有使用 -linkshared 编译二进制文件,那么 modulesinit() 根本不会被调用。是否有可能以某种方式在相同情况下调用它?
更多关于Golang中runtime模块的init()函数使用求助的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang中runtime模块的init()函数使用求助的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go的runtime中,modulesinit()函数负责处理动态链接模块的初始化。根据你的描述,这里有几个关键点需要澄清:
1. modulesinit()的调用机制
modulesinit()在runtime·rt0_go中被调用,主要处理通过-linkshared构建的模块。当使用动态链接时,每个共享库都会有自己的模块数据需要注册。
// runtime/symtab.go中的相关代码
func modulesinit() {
modules := moduleSlice
for _, md := range modules {
// 注册模块数据
addmoduledata(md)
}
}
2. 插件加载的两种情况
情况1:使用plugin.Open()加载
// 示例代码
p, err := plugin.Open("plugin.so")
if err != nil {
log.Fatal(err)
}
当通过plugin.Open()加载插件时,Go的运行时系统会:
- 调用
dlopen加载共享库 - 执行共享库的初始化函数
- 调用
addmoduledata注册模块数据 - 这会导致
modulesinit()被调用
情况2:通过LD_PRELOAD加载
LD_PRELOAD=./plugin.so ./myapp
在这种情况下,动态链接器在程序启动时加载共享库,但Go运行时可能无法正确识别这是Go插件。
3. 为什么modulesinit()被调用两次
当使用-linkshared构建应用程序并通过LD_PRELOAD加载插件时:
// 运行时初始化顺序
func rt0_go() {
// 第一次调用:主程序模块初始化
modulesinit()
// 当插件被加载时,动态链接器会调用插件的初始化函数
// 这可能导致第二次调用modulesinit()
}
具体来说:
- 主程序启动时调用一次
modulesinit() - 插件加载时,如果插件也是用Go构建的,它的初始化代码可能再次触发模块注册
4. 验证模块注册
你可以添加调试代码来验证模块注册情况:
// 自定义构建标签查看模块信息
//go:build debug
// +build debug
package main
import (
"fmt"
"runtime"
"unsafe"
)
// 通过cgo访问runtime内部函数
// #include <stddef.h>
// extern void printModules();
import "C"
func main() {
C.printModules()
}
//export printModules
func printModules() {
// 注意:这是内部实现,生产环境不要使用
firstmoduledata := runtime.Firstmoduledata()
fmt.Printf("Number of modules: %d\n", countModules(firstmoduledata))
}
// 简化示例,实际需要遍历模块链表
func countModules(md unsafe.Pointer) int {
count := 0
for md != nil {
count++
// 遍历下一个模块
// md = (*moduledata)(md).next
}
return count
}
5. 构建模式的影响
# 情况1:linkshared + plugin.Open()
go build -linkshared -o myapp
# modulesinit()会被调用
# 情况2:非linkshared + plugin.Open()
go build -o myapp
# modulesinit()不会被调用,因为主程序不是动态链接的
# 情况3:linkshared + LD_PRELOAD
go build -linkshared -o myapp
LD_PRELOAD=./plugin.so ./myapp
# modulesinit()可能被调用两次
6. 根本原因
modulesinit()被调用两次的根本原因是:
- 主程序(使用
-linkshared构建)在启动时初始化自己的模块数据 - 插件(Go共享库)被加载时,会执行自己的初始化路径,可能再次触发模块注册
这是Go运行时动态链接支持的预期行为。每个动态加载的Go共享库都需要注册自己的模块数据到运行时系统中,以便垃圾收集器、栈跟踪等功能能正确工作。

