dll plugin loading heavily improved
This commit is contained in:
@@ -3,18 +3,49 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func loadNativePlugin(data []byte) (NativePlugin, error) {
|
||||
const (
|
||||
loadLibrarySearchDLLLoadDir = 0x00000100
|
||||
loadLibrarySearchDefaultDirs = 0x00001000
|
||||
)
|
||||
|
||||
var procLoadLibraryExW = modKernel32.NewProc("LoadLibraryExW")
|
||||
|
||||
func loadNativePlugin(manifest PluginManifest, data []byte) (NativePlugin, error) {
|
||||
if len(data) == 0 {
|
||||
return nil, errors.New("empty plugin binary")
|
||||
}
|
||||
if shouldUseOSNativeLoader(manifest.NativeLoader) {
|
||||
return loadNativePluginOS(manifest, data)
|
||||
}
|
||||
return loadNativePluginMemory(manifest, data)
|
||||
}
|
||||
|
||||
func shouldUseOSNativeLoader(loader string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(loader)) {
|
||||
case "os", "disk", "file", "loadlibrary", "loadlibraryex":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func loadNativePluginMemory(manifest PluginManifest, data []byte) (NativePlugin, error) {
|
||||
entries := nativeEntries(manifest)
|
||||
|
||||
type initResult struct {
|
||||
dp *dllPlugin
|
||||
@@ -44,21 +75,21 @@ func loadNativePlugin(data []byte) (NativePlugin, error) {
|
||||
return
|
||||
}
|
||||
|
||||
onLoad, err := mm.GetExport("PluginOnLoad")
|
||||
onLoad, err := mm.GetExport(entries.onLoad)
|
||||
if err != nil {
|
||||
mm.Free()
|
||||
ch <- initResult{err: err}
|
||||
runtime.UnlockOSThread()
|
||||
return
|
||||
}
|
||||
onEvent, err := mm.GetExport("PluginOnEvent")
|
||||
onEvent, err := mm.GetExport(entries.onEvent)
|
||||
if err != nil {
|
||||
mm.Free()
|
||||
ch <- initResult{err: err}
|
||||
runtime.UnlockOSThread()
|
||||
return
|
||||
}
|
||||
onUnload, err := mm.GetExport("PluginOnUnload")
|
||||
onUnload, err := mm.GetExport(entries.onUnload)
|
||||
if err != nil {
|
||||
mm.Free()
|
||||
ch <- initResult{err: err}
|
||||
@@ -66,10 +97,10 @@ func loadNativePlugin(data []byte) (NativePlugin, error) {
|
||||
return
|
||||
}
|
||||
|
||||
setCallback, _ := mm.GetExport("PluginSetCallback")
|
||||
setCallback, _ := mm.GetExport(entries.setCallback)
|
||||
|
||||
rt := "go"
|
||||
if getRuntimeAddr, err := mm.GetExport("PluginGetRuntime"); err == nil {
|
||||
if getRuntimeAddr, err := mm.GetExport(entries.getRuntime); err == nil {
|
||||
ret, _, _ := syscall.SyscallN(getRuntimeAddr)
|
||||
if ret != 0 {
|
||||
var buf [32]byte
|
||||
@@ -108,6 +139,125 @@ func loadNativePlugin(data []byte) (NativePlugin, error) {
|
||||
return res.dp, nil
|
||||
}
|
||||
|
||||
func loadNativePluginOS(manifest PluginManifest, data []byte) (NativePlugin, error) {
|
||||
entries := nativeEntries(manifest)
|
||||
dllPath, err := stageNativePluginDLL(manifest, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path16, err := windows.UTF16PtrFromString(dllPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
flags := uintptr(loadLibrarySearchDLLLoadDir | loadLibrarySearchDefaultDirs)
|
||||
h, _, callErr := procLoadLibraryExW.Call(uintptr(unsafe.Pointer(path16)), 0, flags)
|
||||
if h == 0 {
|
||||
return nil, fmt.Errorf("LoadLibraryExW(%s): %w", dllPath, callErr)
|
||||
}
|
||||
module := windows.Handle(h)
|
||||
|
||||
resolve := func(name string, required bool) (uintptr, error) {
|
||||
addr, err := windows.GetProcAddress(module, name)
|
||||
if err != nil {
|
||||
if required {
|
||||
return 0, fmt.Errorf("GetProcAddress(%s): %w", name, err)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
onLoad, err := resolve(entries.onLoad, true)
|
||||
if err != nil {
|
||||
_ = windows.FreeLibrary(module)
|
||||
return nil, err
|
||||
}
|
||||
onEvent, err := resolve(entries.onEvent, true)
|
||||
if err != nil {
|
||||
_ = windows.FreeLibrary(module)
|
||||
return nil, err
|
||||
}
|
||||
onUnload, err := resolve(entries.onUnload, true)
|
||||
if err != nil {
|
||||
_ = windows.FreeLibrary(module)
|
||||
return nil, err
|
||||
}
|
||||
setCallback, _ := resolve(entries.setCallback, false)
|
||||
|
||||
rt := "go"
|
||||
if getRuntimeAddr, err := resolve(entries.getRuntime, false); err == nil && getRuntimeAddr != 0 {
|
||||
ret, _, _ := syscall.SyscallN(getRuntimeAddr)
|
||||
if ret != 0 {
|
||||
var buf [32]byte
|
||||
for i := range buf {
|
||||
b := *(*byte)(unsafe.Pointer(ret + uintptr(i)))
|
||||
if b == 0 {
|
||||
rt = string(buf[:i])
|
||||
break
|
||||
}
|
||||
buf[i] = b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dp := &dllPlugin{
|
||||
module: module,
|
||||
modulePath: dllPath,
|
||||
onLoadAddr: onLoad,
|
||||
onEventAddr: onEvent,
|
||||
onUnloadAddr: onUnload,
|
||||
setCallbackAddr: setCallback,
|
||||
pluginRuntime: rt,
|
||||
workCh: make(chan pluginWork),
|
||||
}
|
||||
go dp.workerLoop()
|
||||
return dp, nil
|
||||
}
|
||||
|
||||
func stageNativePluginDLL(manifest PluginManifest, data []byte) (string, error) {
|
||||
sum := sha256.Sum256(data)
|
||||
hash := hex.EncodeToString(sum[:])
|
||||
root, err := os.UserCacheDir()
|
||||
if err != nil || strings.TrimSpace(root) == "" {
|
||||
root = os.TempDir()
|
||||
}
|
||||
pluginID := sanitizeCacheName(manifest.ID)
|
||||
if pluginID == "" {
|
||||
pluginID = "plugin"
|
||||
}
|
||||
dir := filepath.Join(root, "Overlord", "plugins", pluginID, hash)
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
return "", fmt.Errorf("create plugin cache: %w", err)
|
||||
}
|
||||
target := filepath.Join(dir, pluginID+".dll")
|
||||
if st, err := os.Stat(target); err == nil && st.Size() == int64(len(data)) {
|
||||
return target, nil
|
||||
}
|
||||
tmp := target + ".tmp"
|
||||
if err := os.WriteFile(tmp, data, 0600); err != nil {
|
||||
return "", fmt.Errorf("write plugin cache: %w", err)
|
||||
}
|
||||
if err := os.Rename(tmp, target); err != nil {
|
||||
_ = os.Remove(tmp)
|
||||
if st, statErr := os.Stat(target); statErr == nil && st.Size() == int64(len(data)) {
|
||||
return target, nil
|
||||
}
|
||||
return "", fmt.Errorf("finalize plugin cache: %w", err)
|
||||
}
|
||||
return target, nil
|
||||
}
|
||||
|
||||
func sanitizeCacheName(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
var b strings.Builder
|
||||
for _, r := range value {
|
||||
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '_' || r == '.' {
|
||||
b.WriteRune(r)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
type pluginWork struct {
|
||||
fn func() error
|
||||
done chan error
|
||||
@@ -115,6 +265,8 @@ type pluginWork struct {
|
||||
|
||||
type dllPlugin struct {
|
||||
mem *MemoryModule
|
||||
module windows.Handle
|
||||
modulePath string
|
||||
onLoadAddr uintptr
|
||||
onEventAddr uintptr
|
||||
onUnloadAddr uintptr
|
||||
@@ -181,23 +333,24 @@ func (p *dllPlugin) Event(event string, payload []byte) error {
|
||||
log.Printf("[panic] plugin event panic: %v", r)
|
||||
}
|
||||
}()
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
cleanup := p.mem.SetupThreadTLS()
|
||||
defer cleanup()
|
||||
|
||||
var eventPtr, payloadPtr uintptr
|
||||
eventLen := uintptr(len(eventBytes))
|
||||
payloadLen := uintptr(len(payloadCopy))
|
||||
if len(eventBytes) > 0 {
|
||||
eventPtr = uintptr(unsafe.Pointer(&eventBytes[0]))
|
||||
if err := p.runOnWorker(func() error {
|
||||
var eventPtr, payloadPtr uintptr
|
||||
eventLen := uintptr(len(eventBytes))
|
||||
payloadLen := uintptr(len(payloadCopy))
|
||||
if len(eventBytes) > 0 {
|
||||
eventPtr = uintptr(unsafe.Pointer(&eventBytes[0]))
|
||||
}
|
||||
if len(payloadCopy) > 0 {
|
||||
payloadPtr = uintptr(unsafe.Pointer(&payloadCopy[0]))
|
||||
}
|
||||
ret, _, _ := syscall.SyscallN(p.onEventAddr, eventPtr, eventLen, payloadPtr, payloadLen)
|
||||
if int32(ret) != 0 {
|
||||
return errors.New("PluginOnEvent returned non-zero")
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Printf("[plugin] event error: %v", err)
|
||||
}
|
||||
if len(payloadCopy) > 0 {
|
||||
payloadPtr = uintptr(unsafe.Pointer(&payloadCopy[0]))
|
||||
}
|
||||
|
||||
syscall.SyscallN(p.onEventAddr, eventPtr, eventLen, payloadPtr, payloadLen)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
@@ -219,8 +372,13 @@ func (p *dllPlugin) Close() error {
|
||||
p.mem.Free()
|
||||
p.mem = nil
|
||||
}
|
||||
if p.module != 0 {
|
||||
_ = windows.FreeLibrary(p.module)
|
||||
p.module = 0
|
||||
}
|
||||
} else {
|
||||
p.mem = nil
|
||||
p.module = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user