Golang中runtime模块的init()函数使用求助

Golang中runtime模块的init()函数使用求助

在 Go 源码 runtime 文件夹下的 symtab.go 文件中,modulesInit() 函数的注释中提到,当一个模块首次被动态链接器加载时,会调用 addmoduledata,并且可以加载多个使用 -linkshared 构建的模块以及插件。

当我运行一个使用 -buildmode=shared-linkshared 标志构建的应用程序,并且插件在 LD_PRELOAD 中被使用时,modulesinit() 函数会被调用两次。

我的观察:

  1. myapplication 使用 -linkshared 构建,并且插件通过 plugin.Open()LD_PRELOAD 加载。在这种情况下,symtab.go 中的 modulesinit() 会被调用两次。
  2. 应用程序使用 -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

1 回复

更多关于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()
}

具体来说:

  1. 主程序启动时调用一次modulesinit()
  2. 插件加载时,如果插件也是用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()被调用两次的根本原因是:

  1. 主程序(使用-linkshared构建)在启动时初始化自己的模块数据
  2. 插件(Go共享库)被加载时,会执行自己的初始化路径,可能再次触发模块注册

这是Go运行时动态链接支持的预期行为。每个动态加载的Go共享库都需要注册自己的模块数据到运行时系统中,以便垃圾收集器、栈跟踪等功能能正确工作。

回到顶部