plugin_dlopen.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. // Copyright 2016 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. //go:build (linux && cgo) || (darwin && cgo) || (freebsd && cgo) || (hurd & cgo)
  5. package plugin
  6. /*
  7. #cgo linux LDFLAGS: -ldl
  8. #include <dlfcn.h>
  9. #include <limits.h>
  10. #include <stdlib.h>
  11. #include <stdint.h>
  12. #include <stdio.h>
  13. static uintptr_t pluginOpen(const char* path, char** err) {
  14. void* h = dlopen(path, RTLD_NOW|RTLD_GLOBAL);
  15. if (h == NULL) {
  16. *err = (char*)dlerror();
  17. }
  18. return (uintptr_t)h;
  19. }
  20. static void* pluginLookup(uintptr_t h, const char* name, char** err) {
  21. void* r = dlsym((void*)h, name);
  22. if (r == NULL) {
  23. *err = (char*)dlerror();
  24. }
  25. return r;
  26. }
  27. */
  28. import "C"
  29. import (
  30. "errors"
  31. "sync"
  32. "unsafe"
  33. )
  34. func open(name string) (*Plugin, error) {
  35. cPath := make([]byte, C.PATH_MAX+1)
  36. cRelName := make([]byte, len(name)+1)
  37. copy(cRelName, name)
  38. if C.realpath(
  39. (*C.char)(unsafe.Pointer(&cRelName[0])),
  40. (*C.char)(unsafe.Pointer(&cPath[0]))) == nil {
  41. return nil, errors.New(`plugin.Open("` + name + `"): realpath failed`)
  42. }
  43. filepath := C.GoString((*C.char)(unsafe.Pointer(&cPath[0])))
  44. pluginsMu.Lock()
  45. if p := plugins[filepath]; p != nil {
  46. pluginsMu.Unlock()
  47. if p.err != "" {
  48. return nil, errors.New(`plugin.Open("` + name + `"): ` + p.err + ` (previous failure)`)
  49. }
  50. <-p.loaded
  51. return p, nil
  52. }
  53. var cErr *C.char
  54. h := C.pluginOpen((*C.char)(unsafe.Pointer(&cPath[0])), &cErr)
  55. if h == 0 {
  56. pluginsMu.Unlock()
  57. return nil, errors.New(`plugin.Open("` + name + `"): ` + C.GoString(cErr))
  58. }
  59. // TODO(crawshaw): look for plugin note, confirm it is a Go plugin
  60. // and it was built with the correct toolchain.
  61. if len(name) > 3 && name[len(name)-3:] == ".so" {
  62. name = name[:len(name)-3]
  63. }
  64. if plugins == nil {
  65. plugins = make(map[string]*Plugin)
  66. }
  67. pluginpath, syms, errstr := lastmoduleinit()
  68. if errstr != "" {
  69. plugins[filepath] = &Plugin{
  70. pluginpath: pluginpath,
  71. err: errstr,
  72. }
  73. pluginsMu.Unlock()
  74. return nil, errors.New(`plugin.Open("` + name + `"): ` + errstr)
  75. }
  76. // This function can be called from the init function of a plugin.
  77. // Drop a placeholder in the map so subsequent opens can wait on it.
  78. p := &Plugin{
  79. pluginpath: pluginpath,
  80. loaded: make(chan struct{}),
  81. }
  82. plugins[filepath] = p
  83. pluginsMu.Unlock()
  84. initStr := make([]byte, len(pluginpath)+len("..inittask")+1) // +1 for terminating NUL
  85. copy(initStr, pluginpath)
  86. copy(initStr[len(pluginpath):], "..inittask")
  87. initTask := C.pluginLookup(h, (*C.char)(unsafe.Pointer(&initStr[0])), &cErr)
  88. if initTask != nil {
  89. doInit(initTask)
  90. }
  91. // Fill out the value of each plugin symbol.
  92. updatedSyms := map[string]any{}
  93. for symName, sym := range syms {
  94. isFunc := symName[0] == '.'
  95. if isFunc {
  96. delete(syms, symName)
  97. symName = symName[1:]
  98. }
  99. fullName := pluginpath + "." + symName
  100. cname := make([]byte, len(fullName)+1)
  101. copy(cname, fullName)
  102. p := C.pluginLookup(h, (*C.char)(unsafe.Pointer(&cname[0])), &cErr)
  103. if p == nil {
  104. return nil, errors.New(`plugin.Open("` + name + `"): could not find symbol ` + symName + `: ` + C.GoString(cErr))
  105. }
  106. valp := (*[2]unsafe.Pointer)(unsafe.Pointer(&sym))
  107. if isFunc {
  108. (*valp)[1] = unsafe.Pointer(&p)
  109. } else {
  110. (*valp)[1] = p
  111. }
  112. // we can't add to syms during iteration as we'll end up processing
  113. // some symbols twice with the inability to tell if the symbol is a function
  114. updatedSyms[symName] = sym
  115. }
  116. p.syms = updatedSyms
  117. close(p.loaded)
  118. return p, nil
  119. }
  120. func lookup(p *Plugin, symName string) (Symbol, error) {
  121. if s := p.syms[symName]; s != nil {
  122. return s, nil
  123. }
  124. return nil, errors.New("plugin: symbol " + symName + " not found in plugin " + p.pluginpath)
  125. }
  126. var (
  127. pluginsMu sync.Mutex
  128. plugins map[string]*Plugin
  129. )
  130. // lastmoduleinit is defined in package runtime
  131. func lastmoduleinit() (pluginpath string, syms map[string]any, errstr string)
  132. // doInit is defined in package runtime
  133. //go:linkname doInit runtime.doInit
  134. func doInit(t unsafe.Pointer) // t should be a *runtime.initTask