Description
What version of Go are you using (go version
)?
$ go version go version go1.12.1 linux/amd64
Does this issue reproduce with the latest release?
Yes.
What operating system and processor architecture are you using (go env
)?
go env
Output
$ go env GOARCH="amd64" GOBIN="" GOCACHE="/home/mem/.cache/go-build" GOEXE="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOOS="linux" GOPATH="/home/mem/go" GOPROXY="" GORACE="" GOROOT="/home/mem/devel/go/go-1.12" GOTMPDIR="" GOTOOLDIR="/home/mem/devel/go/go-1.12/pkg/tool/linux_amd64" GCCGO="gccgo" CC="gcc" CXX="g++" CGO_ENABLED="1" GOMOD="/home/mem/devel/go/plugin-modules/go-plugin-modules/go.mod" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build318788666=/tmp/go-build -gno-record-gcc-switches"
What did you do?
The code to reproduce the issue is available at https://github.com/mem/go-plugin-modules
You can use the makefile in there to build the code and run the tests using make test
.
When building a program and a plugin using the -mod=vendor flag (and vendoring the corresponding packages in the respective modules using go mod vendor
), the plugin code fails to load:
$ cd go-plugin-loader/ && go build -mod=vendor -o /home/mem/devel/go/plugin-modules/go-plugin-modules/go-plugin-loader/go-plugin-loader-vendor main.go
$ cd go-plugin-hook/ && go build -mod=vendor -buildmode=plugin -o /home/mem/devel/go/plugin-modules/go-plugin-modules/go-plugin-hook/go-plugin-hook-vendor.so main.go
And then running this program as:
$ go-plugin-loader/go-plugin-loader-vendor go-plugin-hook/go-plugin-hook-vendor.so
produces the error:
plugin.Open("go-plugin-hook/go-plugin-hook-vendor"): plugin was built with a different version of package golang.org/x/xerrors/internal
What did you expect to see?
The plugin is loaded and the code runs. This is demonstrated in the above code by running make test-no-vendor
, which simply does not pass the -mod=vendor
flag when building.
What did you see instead?
plugin.Open("go-plugin-hook/go-plugin-hook-vendor"): plugin was built with a different version of package golang.org/x/xerrors/internal
It seems the recorded import path when passing -mod=vendor is different.
This is related (duplicate?) of #28983, but I found that issue only until I was submitting this one, so I think there's missing documentation that explains what the issue is and how it should be dealt with.
Activity
Do not pass -mod=vendor when building singularity
bcmills commentedon Apr 12, 2019
Have you verified that
go-plugin-loader
andgo-plugin-hook
are in fact using the same version ofgolang.org/x/xerrors/internal
? (What doesgo list -m golang.org/x/xerrors
report in your two repos?)CC @jayconrod
mem commentedon Apr 13, 2019
Thanks for looking at this Bryan.
Each module has a go.mod file that cotains:
the line is the same in both cases:
https://github.com/mem/go-plugin-modules/blob/master/go-plugin-hook/go.mod#L5
https://github.com/mem/go-plugin-modules/blob/master/go-plugin-loader/go.mod#L5
The output of
go list -m golang.org/x/xerrors
is:in both cases.
If I look at the stored hashes by looking at the data stored in the ELF data, for the program, I see:
For the plugin that works (the one without the -mod=vendor flag), I see:
(they match)
For the plugin that does not work (with the -mod=vendor flag), I see:
(does not match).
I've been looking the linker code but I can't figure out how these hashes are computed.
Investigating a little further (and guessing a lot), I can get this to work if I pass
-mod=vendor
and-gcflags=all=-trimpath="${dir_with_go_mod}" -asmflags=all=-trimpath="${dir_with_go_mod}"
, where ${dir_with_go_mod} is the directory containing the go.mod file.It does not interoperate, and I'm not sure if it should. What I mean is that if I build the program with
-mod=vendor -gcflags=... -asmflags=...
and the plugin without (or the other way around), the plugin cannot be loaded (and the hashes are again different).I gues the questions are this point are:
mem commentedon Apr 14, 2019
Additional info. For the non-working case, the hashes are:
Adding -trimpath to get it working:
Since -trimpath doesn't seem to have an effect with / without -mod=vendor, I'm guessing $GOPATH/pkg/mod/ is somehow involved in the computation of the hash?
Just to be clear, everything above has been done without GOPATH set (so it's using the $HOME/go default).
If I set GOPATH to something (just to hold the pkg/mod files in a different directory), I can see the hashes in the "without -mod=vendor" case changing, but the "with -mod=vendor" case stays the same.
So the end result is that I have to use -mod=vendor (otherwise I have to build using the same path for the code) and I have to set -trimpath (otherwise the code cannot be loaded).
Should this be captured in a document?
bcmills commentedon Apr 15, 2019
Probably at least one difference here is in the debug information, which by default includes the full file paths. That's why you need
-trimpath
in-mod=vendor
mode, and why the two versions (with and without-mod=vendor
) differ.If that's correct, then you may find that if you set
-trimpath
for both builds, then you can combine a plugin and binary with and without-mod=vendor
.[-]Plugin built with -mod=vendor cannot be loaded[/-][+]plugin: spurious version-mismatch error for packages that differ only in debug information[/+]bcmills commentedon Apr 15, 2019
@ianlancetaylor and @cherrymui: is there something we can do in the
plugin
package to make the version-mismatch detection code less sensitive to differences that don't affect the executable code or ABI of the package?ianlancetaylor commentedon Apr 15, 2019
As far as I can tell the hash value is based only on the externally visible ABI. Changes in the debug information shouldn't affect it. But I don't think anybody really understands this other than @crawshaw.
mem commentedon Apr 22, 2019
While revisiting this for a different reason, I also noticed that -trimpath is a string (
flag.StringVar(&pathPrefix, "trimpath", "", "remove
prefixfrom recorded source file paths")
fromsrc/cmd/compile/internal/gc/main.go
).The build command uses the -trimpath flag internally, calling compile for example as
/.../pkg/tool/linux_amd64/compile -o $WORK/b003/_pkg_.a -trimpath $WORK/b003 ...
That means if I pass
-gcflags=all=-trimpath=...
on the command line, I end up with a compile command with two-trimpath
flags (verified this usinggo build -x ...
) and because of the way the flag is defined, only the second value is used, dropping the one the compiler uses internally.Does this matter?
19 remaining items