golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.78k stars 17.64k forks source link

plugin: can't open the same plugin with different names, like dlopen #29525

Open JesseGuoX opened 5 years ago

JesseGuoX commented 5 years ago

What version of Go are you using (go version)?

$ go version
go version go1.11.4 linux/386

Does this issue reproduce with the latest release?

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GOARCH="386"
GOBIN=""
GOCACHE="/home/pw/.cache/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="386"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/pw/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_386"
GCCGO="gccgo"
GO386="sse2"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/home/pw/Documents/hello/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 -m32 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build808633565=/tmp/go-build -gno-record-gcc-switches"

What did you do?

go build -buildmode=plugin -o scanner.so scanner.go
cp scanner.so scanner2.so
package main

import (
    "fmt"
    "os"
    "plugin"
)

type Greeter interface {
    Greet()
}

func main() {

    plug, err := plugin.Open("./plugins/scanner.so")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    symGreeter, err := plug.Lookup("Greeter")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    var greeter Greeter
    greeter, ok := symGreeter.(Greeter)
    if !ok {
        fmt.Println("unexpected type from module symbol")
        os.Exit(1)
    }

    plug2, err := plugin.Open("./plugins/scanner2.so")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    symGreeter2, err := plug2.Lookup("Greeter")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    var greeter2 Greeter
    greeter2, ok2 := symGreeter2.(Greeter)
    if !ok2 {
        fmt.Println("unexpected type from module symbol")
        os.Exit(1)
    }

    // 4. use the module
    greeter.Greet()
    greeter2.Greet()

}

What did you expect to see?

Same plugin copyed to different names can plugin.Open successfully and have different instances. For example, I have a lot of scanners connect to my device and the number of scanners is uncertain, the scanners have same protocol but the serial port is different, so I need to load the same scanner plugin and pass different serial port to the plugin to scan,so it is not suitable to complie different plugin by 'pluginpath', if I need 100 scanners I need to complie 100 times. Why can't just like dlopen distinguish by names?

What did you see instead?

plugin.Open("./plugins/scanner2.so"): plugin already loaded
mvdan commented 5 years ago

I presume it's because of this bit of doc:

When a plugin is first opened, the init functions of all packages not already part of the program are called. The main function is not run. A plugin is only initialized once, and cannot be closed.

If you could load the same plugin twice with different paths, you'd effectively run the same init function twice. That seems to go against how plugins should be used, but the documentation isn't terribly clear about this edge case.

/cc @ianlancetaylor @cherrymui as per https://dev.golang.org/owners/

JesseGuoX commented 5 years ago

Thank you @mvdan , I am a newcomer to golang. I am evaluating whether golang is suitable for current arm embedded projects. Is plugin not implemented for linux/arm yet?

cherrymui commented 5 years ago

@Jexbat Plugin is supported on Linux/ARM.

JesseGuoX commented 5 years ago

@cherrymui GOOS=linux GOARCH=arm go build and return:plugin: not implemented.

$ file hello
hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped

I found same issue #19569, then I enable cgo like

CGO_ENABLED=1 GOOS=linux GOARCH=arm CC="arm-poky-linux-gnueabi-gcc" CXX="arm-poky-linux-gnueabi-g++" LD="arm-poky-linux-gnueabi-ld.gold" CGO_CFLAGS="-march=armv7-a -mfpu=neon  -mfloat-abi=hard -mcpu=cortex-a9 --sysroot=/opt/fsl-imx-fb/4.1.15-2.0.1/sysroots/cortexa9hf-neon-poky-linux-gnueabi" CGO_CXXFLAGS="-march=armv7-a -mfpu=neon  -mfloat-abi=hard -mcpu=cortex-a9 --sysroot=/opt/fsl-imx-fb/4.1.15-2.0.1/sysroots/cortexa9hf-neon-poky-linux-gnueabi" CGO_LDFLAGS="  --sysroot=/opt/fsl-imx-fb/4.1.15-2.0.1/sysroots/cortexa9hf-neon-poky-linux-gnueabi" go build

Compile is succuss but when I exec it return No such file or directory.

$ file hello
hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 2.6.32, BuildID[sha1]=cd89bbf6e60242498f31375ee63e89ef7ca9c1a3, not stripped

Could it be cross-compile toolchain problem?

ianlancetaylor commented 5 years ago

I assume you are copying the executable built with GOARCH=arm to an ARM system in order to run it. If you are getting "No such file or directory" on that system then the problem is almost certainly a disagreement about the location of the dynamic linker. You can use readelf -l EXECUTABLE to see the dynamic linker that it requests; look for the "Requesting program interpreter" line. See if that file exists on your ARM system. (When using cgo the dynamic linker is set by the C linker, and is not controlled by the Go tools.)

JesseGuoX commented 5 years ago

@ianlancetaylor sovled with ln -s /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3, thanks. But I am still confused with plugin, if I compiled same plugin with different -pluginpath does two plugins share the same variable when I calling same func in two plugins?

hello.go

package main

import (
    "fmt"
    "os"
    "plugin"
)

type Greeter interface {
    Greet()
}

func main() {

    plug, err := plugin.Open("./plugins/scanner.so")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    symGreeter, err := plug.Lookup("Greeter")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    var greeter Greeter
    greeter, ok := symGreeter.(Greeter)
    if !ok {
        fmt.Println("unexpected type from module symbol")
        os.Exit(1)
    }
    greeter.Greet()
    print("=======\n")

    plug2, err := plugin.Open("./plugins/scanner2.so")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    symGreeter2, err := plug2.Lookup("Greeter")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    var greeter2 Greeter
    greeter2, ok2 := symGreeter2.(Greeter)
    if !ok2 {
        fmt.Println("unexpected type from module symbol")
        os.Exit(1)
    }

    greeter2.Greet()

}

scanner.go

package main

import "fmt"

type greeting string

var count int

func (g greeting) Greet() {
    count++
    fmt.Printf("Hello Universe %d\n", count)
}

// exported as symbol named "Greeter"
var Greeter greeting

scanner.go compiled with two different -pluginpath:

...  go build -ldflags "-pluginpath=p2" -buildmode=plugin -o scanner2.so scanner.go
...  go build -ldflags "-pluginpath=p1" -buildmode=plugin -o scanner.so scanner.go

result is:

root@imx6dlsabresd:/tmp# ls plugins/
scanner.so  scanner2.so
root@imx6dlsabresd:/tmp# ./hello
Hello Universe 1
=======
Hello Universe 2
root@imx6dlsabresd:/tmp#

Does it should be Hello Universe 1 and Hello Universe 1? It seems like two different plugins share same variable count.

JesseGuoX commented 5 years ago

I found change file name can solve this situation.Change scanner.go to scanner2.go then compile withoutpluginpath. result is:

root@imx6dlsabresd:/tmp# ./hello
Hello Universe 1
=======
Hello Universe 1

I think pluginpath just resolved plugin.open problem and still share variable in it.

witwit commented 5 years ago

Hello, Old thread, but still confused about a detail: GOOS=linux GOARCH=arm does not work for plugins? Plugins do not run on linux/arm ? I have built a simple example, that builds a simple plugin like: CC=arm-linux-gnueabi-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="-pluginpath=blah" -buildmode=plugin -o ./arm-dist/reader.linux.arm.so /app/plugins/rea der/...

No errors,

however when a main app on a raspberrypi tries to open the plugin, it says: could not open ./arm-dist/caller.linux.arm.so plugin: not implemented

is this the case? or did I miss something? somewhat of a showstopper...

randall77 commented 5 years ago

@witwit: That should work. One thing you might be hitting - your main app also needs to be compiled with CGO_ENABLED=1.

cherrymui commented 5 years ago

@witwit this doesn't seem to be related to this issue, so I recommend discussing this somewhere else.

For your problem, how do you build the program that opens the plugin? In particular, is it built with cgo enabled?

witwit commented 5 years ago

Oh, thank you, that got me a bit further. CGO_ENABLED=1 was missing for the main app, d'oh! Now I am stuck at a different error message

could not open /home/pi/plugged/reader.linux.arm.so plugin.Open("/home/pi/plugged/reader.linux.arm.so"): /home/pi/plugged/reader.linux.arm.so: cannot open shared object file: No such file or directory

witwit commented 5 years ago

@witwit this doesn't seem to be related to this issue, so I recommend discussing this somewhere else.

For your problem, how do you build the program that opens the plugin? In particular, is it built with cgo enabled?

yes, sorry. I truggle to find any infos on this at all. I will post a question on stack overflow.

exfly commented 1 year ago
# plugin code in plugin/hello.go
package main

import "fmt"

var V int

func F() { fmt.Printf("Hello, number %d\n", V) }

# main in main.go
package main

import "plugin"

func main() {
    p, err := plugin.Open("plugin/hello.so")
    if err != nil {
        panic(err)
    }

    v, err := p.Lookup("V")
    if err != nil {
        panic(err)
    }
    f, err := p.Lookup("F")
    if err != nil {
        panic(err)
    }
    *v.(*int) = 7
    f.(func())() // prints "Hello, number 7"

    p, err = plugin.Open("plugin/hello2.so")
    if err != nil {
        panic(err)
    }
    v, err = p.Lookup("V")
    if err != nil {
        panic(err)
    }
    f, err = p.Lookup("F")
    if err != nil {
        panic(err)
    }
    _ = v
    // *v.(*int) = 7
    f.(func())() // prints "Hello, number 7"
}
go build -trimpath -ldflags "-pluginpath=plugin/hot-$(date +%s)" -buildmode=plugin -o plugin/hello.so plugin/hello.go
go build -trimpath -ldflags "-pluginpath=plugin/hot-$(date +%s)" -buildmode=plugin -o plugin/hello2.so plugin/hello.go

go run -trimpath main.go
panic: plugin.Open("plugin/hello"): could not find symbol V: dlsym(0x100205980, plugin/hot-1670990324.V): symbol not found

goroutine 1 [running]:
main.main()
        ./main.go:8 +0x4ae
exit status 2

nm plugin/hello.so| grep '.V' 00000000001d6e68 S _plugin/unnamed-b2203a0be6431d575e0f9179980c3c58f09e7987.V 000000000007e200 t _reflect.(*MapIter).Value

Is there something wrong with compiling the symbol table?

titpetric commented 11 months ago

Ok, as this is a duplicate, do we have some work arounds for more modern go versions that would allow us to:

  1. explicitly set the plugin name?
  2. --buildvcs=true to build a unique plugin while respecting -trimpath?

I particularly mention this as buildvcs is a recent addition, and there's -pluginpath https://github.com/golang/go/issues/19418 ; seems both options have no effect in plugin builds. Trimpath for plugins should, if anything, replace the plugin path with name.$(date +%s) or some equivalent?

Is there any approach considered in resolving this in the toolchain? Would we need a proposal to discuss a change, submit patches, or how can we move this forward?

titpetric commented 11 months ago

@ianlancetaylor @cherrymui as cmd/link and plugins package owners, I would appreciate your input and perhaps guidance towards contributing a patch, priorities permitting.