TDEngine在M1 macOS下go connector编译报ld: symbol(s) not found for architecture x86_64错误的解决方法和思路

-1 后期本文修订

后期编辑:这篇文章在发布后作者发现了下文有很多不正确的地方,请批判看待
之所以写在前面,是因为后来作者发现了问题的核心原因并非作者下文所述。证明的点在于:比如为什么CGO_ENABLED是需要再交叉编译情况下开启的,但是arm64是作者当时本机的架构,不应该涉及交叉编译。

实际上,在M1上安装了arm架构的go语言就不会存在任何问题,但是不小心装了intel x86_64架构的go语言就会给作者造成本文所述的困扰。所以如果有遇到相同问题的朋友,请先确保自己在本机装了对应架构的go。如果一定需要再本机使用Intel版本的Go的朋友,估计也很清楚该怎么解决问题了,但是不清楚的可以往下看看。

0. 背景

第0、1章是随便啰嗦几句做个日记,如果想看解决方案的病友请直接去2或3.

最近由于公司项目,需要使用Go语言连接TDEngine实现一些增删改查。由于本人即是Go新手,又是TDEngine新手,在第一个链接测试的代码实现上就碰上了一个坑。
本人使用的是M1的Macbook Pro + GoLand + Go 1.17 + TDEngine 3.x,从官网下载了arm64版本的TDEngine Client,兴高采烈写了如下TDEngine的Helloworld:

package main

import (
	"database/sql"
	"fmt"
	"log"
	
	_ "github.com/taosdata/driver-go/v3/taosSql"

)

func main() {
	var taosDSN = "root:taosdata@tcp(localhost:6030)/"
	taos, err := sql.Open("taosSql", taosDSN)
	if err != nil {
		log.Fatalln("failed to connect TDengine, err:", err)
		return
	}
	fmt.Println("Connected")

	if _, err = taos.Exec("SHOW CONNECTIONS;"); err != nil {
		fmt.Println(err)
	}
	defer taos.Close()
}

然后去go build,结果一通报错:

Undefined symbols for architecture x86_64:
  "_taos_affected_rows", referenced from:
      __cgo_51e6d1b5140c_Cfunc_taos_affected_rows in _x011.o
     (maybe you meant: __cgo_51e6d1b5140c_Cfunc_taos_affected_rows)
  "_taos_close", referenced from:
      __cgo_51e6d1b5140c_Cfunc_taos_close in _x011.o
     (maybe you meant: __cgo_51e6d1b5140c_Cfunc_taos_close)
......
......
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

1. 解决过程

解决这个问题大概花了我半天时间吧,首先第一个想到的就是去G**和某度搜索TDEngine类似的问题,可能是TDEngine比较小众,也就没有搜到什么有价值的信息。

于是转而从问题本身下手做一些研究。首先可以明确的是,编译过程没有报错,最后的链接出了错,信息也很明确:没有在x86_64架构的编译环境下找到上面列的一大堆symbols。

看到这条信息,我产生了几条疑惑,和一些猜想:

  1. 疑惑:为啥我M1的Mac编译了个x86_64的目标?
  2. 猜想:是不是我下载了个x86的Go?如果不是的话,Go是不是可以指定编译目标是x86还是arm64?
  3. 猜想:是不是Go编译过程中没找到TDEngine的库文件?
  4. 猜想:如果找到库文件了,是不是库文件只支持arm64不支持x86(有可能),或者确实没有这些符号(不太可能)

抱着这些疑惑和猜想我开始了如下路径的研究

首先:设法看一下go的编译连接过程,既然你是用的clang,那么应该有办法把执行clang的过程都打印出来吧?上网一查,果然go build -x可以打印go执行命令的详细步骤:

$ go build -x
.....
.....
TERM='dumb' clang -I /Users/hydra/go/pkg/mod/github.com/taosdata/driver-go/[email protected]/wrapper -fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=$WORK/b127=/tmp/go-build -gno-record-gcc-switches -fno-common -o $WORK/b127/_cgo_.o $WORK/b127/_cgo_main.o $WORK/b127/_x001.o $WORK/b127/_x002.o $WORK/b127/_x003.o $WORK/b127/_x004.o $WORK/b127/_x005.o $WORK/b127/_x006.o $WORK/b127/_x007.o $WORK/b127/_x008.o $WORK/b127/_x009.o $WORK/b127/_x010.o $WORK/b127/_x011.o $WORK/b127/_x012.o $WORK/b127/_x013.o -g -O2 -L/usr/local/lib -ltaos
# github.com/taosdata/driver-go/v3/wrapper
Undefined symbols for architecture x86_64:
  "_taos_affected_rows", referenced from:
      __cgo_51e6d1b5140c_Cfunc_taos_affected_rows in _x011.o
......
......

从clang -I这一行我们能看到一大堆.o是编译好的目标文件, L/usr/local/lib是链接库的位置,-ltaos是链接库的名字。

于是我进行了下列操作:

$ cd /usr/local/lib
localhost:lib hydra$ ls
libtaos.1.dylib libtaos.dylib   libtaosws.dylib


$ file libtaos.dylib
libtaos.dylib: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit dynamically linked shared library x86_64Mach-O 64-bit dynamically linked shared library x86_64] [arm64:Mach-O 64-bit dynamically linked shared library arm64Mach-O 64-bit dynamically linked shared library arm64]
libtaos.dylib (for architecture x86_64):        Mach-O 64-bit dynamically linked shared library x86_64
libtaos.dylib (for architecture arm64): Mach-O 64-bit dynamically linked shared library arm64

一看,是个universal的library,有x86_64和arm64支持的二进制(这个地方是重点误导我的地方,导致我多花了至少几个小时)。我当时直接排除了没有找到库文件的猜想(这个是正确的),和库文件不支持x86的猜想(这个是错误的)。于是就花费了大把时间研究为啥找不到symbol。

首先我通过$ nm -gU libtaos.dylib(G* 搜索macos list symbols in dylib),得到了下列结果:

.....
000000000001d828 T _taos_affected_rows
000000000001d8b0 T _taos_affected_rows64
0000000000058390 T _taos_block_sigalrm
00000000000135c8 T _taos_check_server_status
000000000001bdfc T _taos_cleanup
000000000001c318 T _taos_close
.....

一看,这符号命名就摆在那里!所以又排除了符号不存在的猜想。
这下进死胡同了——既然这个库之前写的支持x86_64、arm64,编译器也能找到这个文件,符号又存在,为啥就报错符号不存在呢?
这里直接给出后面研究的结论,因为虽然这个库写的是universal,但是实际上并没有针对x86_64编译这些符号及其内容,后面我研究了下nm这个工具,里面有个-arch参数可以指定列出对应平台的符号,于是我执行$nm -gU -arch x86_64 libtaos.dylib,会给出结果:libtaos.dylib: no symbols,而执行$nm -gU -arch arm64 libtaos.dylib会和之前的结果一致。

这条路没走通,于是我就转而研究Go为啥会在M1 Mac上编译x86目标。经过研究发现Go有两个环境变量:GOARCH/GOHOSTOS可以组成一对以便一些交叉编译需求(所谓交叉编译,就是在A平台编译B平台的可执行文件,比如在Windows平台下编译Linux下的执行文件用于部署)
我们可以使用命令go env来获取go默认的环境。在我的机器上,GOHOSTOS=drawin, GOARCH=amd64amd64相当于x86_64,原来如此!于是我赶紧查了一下如何让编译平台变成arm64,方法也很容易查到:

  1. go env -w GOARCH=arm64可以直接将默认值修改成arm64平台,这样别的项目也会默认用arm64编译了
  2. 编译前先设好环境变量,具体方法:GOARCH=arm64 go build

然而,正当我以为问题就要解决的时候,新问题又来了:

$ GOARCH=arm64 go build
go build github.com/taosdata/driver-go/v3/wrapper: build constraints exclude all Go files in /Users/hydra/go/pkg/mod/github.com/taosdata/driver-go/[email protected]/wrapper

意思就是go编译taosdata的这个包时不满足约束条件导致一个文件都没有编译。所以我就直接查这个build constraints exclude all Go files关键字,得到了如下几种声音:

  1. 条件编译的编译条件导致所有文件的条件都没满足
  2. 交叉编译条件下如果依赖的包使用了cgo,则需要开启CGO_ENABLED环境变量

下面这几篇文章讲的很好:

学习golang的条件编译
【Golang】排查 Build constraints exclude all the go files 的几个思路

回过头来我翻查了一下TDEngine Go Connector的源码发现其并没有做编译条件的限制,但是使用了cgo相关内容,于是我开启了CGO_ENABLED再次尝试,终于成功编译,具体命令可见下文。

由于之前并不理解(也就是还没有学会用nm -arch来看平台symbol)为啥libtaos.dylib明明是支持双平台但是又报无法找到符号的问题,我又尝试了另一种思路来看看是不是这个库确实不只是x86_64的架构。方法是:从TDEngine官网下载x86版本的Client安装,然后在amd64的架构下进行编译:

$ GOARCH=amd64 go build
$

直接编译通过,至此我才恍然大悟之前用file看dylib支持架构的地方误导了我。也就有了后来我对nm工具进行研究的工作。

上面是整体的分析过程,下面给出解决方案

2. 解决方案0 - 正确安装Go对应的架构

官方的Go分为x86_64和arm64两种版本,如果您使用的是M1版本的Mac,请确保下载了arm64版本的go,这样就不会出现这些奇奇怪怪的问题了,难受。

2. 解决方案1 - 启用Go的arm64编译环境

shell

此方法适用于对项目进行一次性编译,仅有编译时环境生效,没有任何副作用。

$ CGO_ENABLED=1 GOARCH=arm64 go build

goenv

此方法可以改变go的全局环境,一旦更改,所有的项目,无论是用shell还是用IDE去编译,只要没有再编译时显示指定环境变量,都会默认启用arm64平台和cgo,请谨慎使用。

$ go env -w CGO_ENABLED=1
$ go env -w GOARCH=arm64

GoLand等IDE

只在IDE的项目生效,废话不说直接上图:

TDEngine在M1 macOS下go connector编译报ld: symbol(s) not found for architecture x86_64错误的解决方法和思路_第1张图片

TDEngine在M1 macOS下go connector编译报ld: symbol(s) not found for architecture x86_64错误的解决方法和思路_第2张图片
然后保存配置即可。

3. 解决方案2

方案二简单粗暴,直接用TDEngine的x86版本的Client即可。无需任何额外配置,缺点是无法编译arm64架构的可执行文件,只能编译x86的。

你可能感兴趣的:(tdengine,macos,golang)