Golang编译二进制文件时如何避免使用vDSO
Golang编译二进制文件时如何避免使用vDSO
你好,我正在尝试在Linux上编译一个Go二进制文件,然后在另一个没有vDSO的系统(一个单内核)上运行它。目前,当它应该调用 gettimeofday 时,在某些时候会失败,因为它获得的是一个无效地址。根据我的调试,我认为这是因为单内核没有vDSO。有没有办法可以编译一个不使用vDSO的Go二进制文件?
当你尝试运行你的二进制文件时,你看到了什么?
更多关于Golang编译二进制文件时如何避免使用vDSO的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
线性地址 ffffffffff600000 处发生页面错误,rip ffffffffff600000,寄存器 0x24ff50,sp 40009fa98,our_sp 0x24ff38,代码 10
该页面错误是由一条 call rax 指令引起的。在 rax 中本应是 gettimeofday 的地址,这应该已经通过 runtime.vdsoParseSymbols 解析完成。
在Go中编译二进制文件避免使用vDSO,可以通过设置环境变量GODEBUG=asyncpreemptoff=1来禁用异步抢占,这会间接减少对vDSO的依赖。但更直接的方法是使用-ldflags链接器参数来静态链接系统调用。以下是具体步骤:
-
使用
-ldflags链接器参数:在编译时,通过-ldflags传递-extldflags '-static'来强制静态链接C库,这可以减少对vDSO的动态依赖。示例命令如下:CGO_ENABLED=0 go build -ldflags '-extldflags "-static"' -o myapp main.go注意:这要求你的程序不依赖CGO,且目标系统有兼容的静态C库。
-
禁用特定系统调用的vDSO:在Go运行时中,vDSO的使用是硬编码的,但你可以通过修改Go源代码来禁用。例如,在
runtime包中,找到vdso_linux_amd64.go文件(根据架构不同),注释掉或修改相关代码以跳过vDSO调用。但这需要自定义Go工具链,不推荐生产使用。 -
使用
syscall包直接调用系统调用:在代码中,避免使用time.Now()等可能依赖vDSO的函数,改用syscall.Gettimeofday(如果可用)或直接汇编调用。示例代码:package main import ( "fmt" "syscall" "time" ) func getTimeWithoutVDSO() (sec, usec int64) { var tv syscall.Timeval err := syscall.Gettimeofday(&tv) if err != nil { return 0, 0 } return int64(tv.Sec), int64(tv.Usec) } func main() { sec, usec := getTimeWithoutVDSO() fmt.Printf("Time: %d.%d\n", sec, usec) // 对比标准库(可能用vDSO) fmt.Println("Standard time:", time.Now()) }注意:
syscall.Gettimeofday在某些架构上可能仍使用vDSO,但通常更可控。 -
交叉编译时指定静态链接:如果你在非目标系统上编译,确保使用正确的环境变量。例如,为Linux编译静态二进制文件:
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-s -w -extldflags "-static"' -o myapp main.go -
验证二进制文件是否依赖vDSO:编译后,使用
ldd或readelf检查动态链接库。对于静态二进制文件,应显示"not a dynamic executable"。示例:ldd myapp # 应输出 "not a dynamic executable" readelf -d myapp | grep vdso # 不应有输出
根据你的单内核系统,如果它完全缺乏vDSO支持,建议优先使用静态编译方法。如果问题仍存在,可能需要检查内核配置或考虑在运行时禁用异步抢占(通过GODEBUG=asyncpreemptoff=1),但这可能影响性能。

