gccgo编译的Go共享库能否用dlsym查找并调用符号?
问题描述
我尝试用-buildmode=plugin编译Go插件,但生成的.so文件体积过大,哪怕是如下简单代码,生成的文件也接近1MB:
package main var URI string func Eval() bool { return (URI == "Yes") }
于是我改用gccgo编译,执行命令:
gccgo -o test.so -fPIC -shared test.go
生成的文件体积很小,仅约16KB。
现在的问题是:能否用dlsym查找上述.so文件中的符号?dlsym似乎只能检索C符号而非Go符号,有没有办法检索并设置Go符号?
更新内容:nm输出
U __gcc_personality_v0 U getenv U getpagesize w __gmon_start__ 0000000000002eb3 T __go_init_main U internal..z2fbytealg..types U internal..z2fcpu..import U internal..z2fcpu..types w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable 00000000000022d9 T main.Eval 0000000000006d20 D main.Eval..f 00000000000024bd T main.struct.4runtime.gList.2.runtime.n.0int32.5..eq 0000000000006d28 D main.struct.4runtime.gList.2.runtime.n.0int32.5..eq..f 0000000000002760 T main.struct.4Size.0uint32.2Mallocs.0uint64.2Frees.0uint64.5..eq 0000000000006d30 D main.struct.4Size.0uint32.2Mallocs.0uint64.2Frees.0uint64.5..eq..f 0000000000006d40 D main..types 0000000000007150 D main.URI U malloc U memcmp 00000000000000b8 B __morestack_current_segment 0000000000000000 B __morestack_initial_sp 00000000000000c0 B __morestack_segments U protect
函数调用代码
sym := s eval, err := h.GetSymbolPointer(sym) if err != nil { fmt.Printf("Didn't find it ") return -1, fmt.Errorf("Unable to find symbol") } x := (*func() bool)(eval) test := (*x)() //此处直接触发panic _ = test return 0, nil
解决方案
1. 用dlsym检索gccgo生成的Go符号是可行的
从你的nm输出可以看到,Go的函数和变量已经以main.Eval、main.URI这样的符号名暴露出来了,这些符号可以直接被dlsym检索到——gccgo生成的符号名符合C链接规范,不需要额外处理符号名转义。
2. 调用Go函数触发panic的原因及解决
直接把符号指针强转为*func() bool后调用会panic,核心原因是gccgo生成的Go函数调用约定和标准Go runtime的调用约定不同,不能直接用Go函数类型强转调用。
推荐两种解决方法:
方法一:给Go函数加
//export注解,强制gccgo按C调用约定生成函数
修改插件代码:package main import "C" var URI string //export Eval func Eval() bool { return URI == "Yes" } func main() {} // gccgo编译共享库需要main函数作为入口标记重新编译:
gccgo -o test.so -fPIC -shared test.go此时可以把符号指针转为C函数类型,通过cgo桥接或遵循C调用约定的方式调用。
方法二:手动适配gccgo函数调用约定(不推荐)
gccgo生成的函数第一个参数是Go上下文指针(runtime.g),调用时需要传入合法上下文,但这种方法依赖gccgo内部实现,兼容性极差。
3. 设置Go全局变量main.URI的方法
从nm输出看main.URI是全局数据符号(标记为D),可直接通过dlsym获取指针后修改内存:
// 获取URI变量的指针 uriPtr, err := h.GetSymbolPointer("main.URI") if err != nil { // 错误处理逻辑 } // Go的string是结构体,需构造合法的Go string后写入内存 targetStr := "Yes" *(*string)(uriPtr) = targetStr
额外注意事项
- gccgo编译共享库时需要显式的空
main函数,否则无法正确初始化Go runtime。 - 调用Go函数或访问变量前,必须先调用
__go_init_main符号完成runtime初始化,否则会直接崩溃。
内容的提问来源于stack exchange,提问作者kgunjikar
相关产品推荐
相关产品推荐

