dll plugin loading heavily improved

This commit is contained in:
KingKDot
2026-06-28 23:46:56 -05:00
parent d3ded9f877
commit 2c42a989cf
13 changed files with 471 additions and 63 deletions
@@ -74,10 +74,11 @@ func goPluginHostCallback(ctx C.uintptr_t, event *C.char, eventLen C.int, payloa
send(ev, pl)
}
func loadNativePlugin(data []byte) (NativePlugin, error) {
func loadNativePlugin(manifest PluginManifest, data []byte) (NativePlugin, error) {
if len(data) == 0 {
return nil, errors.New("empty plugin binary")
}
entries := nativeEntries(manifest)
f, err := os.CreateTemp("", "*.dylib")
if err != nil {
@@ -114,24 +115,24 @@ func loadNativePlugin(data []byte) (NativePlugin, error) {
return s, nil
}
onLoad, err := resolve("PluginOnLoad")
onLoad, err := resolve(entries.onLoad)
if err != nil {
C.so_dlclose(handle)
return nil, err
}
onEvent, err := resolve("PluginOnEvent")
onEvent, err := resolve(entries.onEvent)
if err != nil {
C.so_dlclose(handle)
return nil, err
}
onUnload, err := resolve("PluginOnUnload")
onUnload, err := resolve(entries.onUnload)
if err != nil {
C.so_dlclose(handle)
return nil, err
}
pluginRT := "go"
if getRuntimeFn, err := resolve("PluginGetRuntime"); err == nil {
if getRuntimeFn, err := resolve(entries.getRuntime); err == nil {
if cs := C.so_call_getruntime(getRuntimeFn); cs != nil {
pluginRT = C.GoString(cs)
}
@@ -101,10 +101,11 @@ func goPluginHostCallback(ctx C.uintptr_t, event *C.char, eventLen C.int, payloa
send(ev, pl)
}
func loadNativePlugin(data []byte) (NativePlugin, error) {
func loadNativePlugin(manifest PluginManifest, data []byte) (NativePlugin, error) {
if len(data) == 0 {
return nil, errors.New("empty plugin binary")
}
entries := nativeEntries(manifest)
// Prefer the subprocess shim approach: the main agent binary is statically
// linked (musl -static) which prevents dlopen from working in-process.
@@ -145,24 +146,24 @@ func loadNativePlugin(data []byte) (NativePlugin, error) {
return s, nil
}
onLoad, err := resolve("PluginOnLoad")
onLoad, err := resolve(entries.onLoad)
if err != nil {
C.so_dlclose(handle)
return nil, err
}
onEvent, err := resolve("PluginOnEvent")
onEvent, err := resolve(entries.onEvent)
if err != nil {
C.so_dlclose(handle)
return nil, err
}
onUnload, err := resolve("PluginOnUnload")
onUnload, err := resolve(entries.onUnload)
if err != nil {
C.so_dlclose(handle)
return nil, err
}
pluginRT := "go"
if getRuntimeFn, err := resolve("PluginGetRuntime"); err == nil {
if getRuntimeFn, err := resolve(entries.getRuntime); err == nil {
if cs := C.so_call_getruntime(getRuntimeFn); cs != nil {
pluginRT = C.GoString(cs)
}
@@ -4,6 +4,6 @@ package plugins
import "errors"
func loadNativePlugin(data []byte) (NativePlugin, error) {
func loadNativePlugin(manifest PluginManifest, data []byte) (NativePlugin, error) {
return nil, errors.New("native plugins not supported on this platform (requires cgo on linux/darwin, or windows)")
}
@@ -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
}
+1 -1
View File
@@ -54,7 +54,7 @@ func (m *Manager) Load(ctx context.Context, manifest PluginManifest, binary []by
if manifest.RuntimeKind == "wasm" || manifest.WASM != "" {
np, err = loadWASMPlugin(ctx, manifest, binary)
} else {
np, err = loadNativePlugin(binary)
np, err = loadNativePlugin(manifest, binary)
}
if err != nil {
return err
@@ -0,0 +1,35 @@
package plugins
type nativeEntryNames struct {
onLoad string
onEvent string
onUnload string
setCallback string
getRuntime string
}
func nativeEntries(manifest PluginManifest) nativeEntryNames {
names := nativeEntryNames{
onLoad: "PluginOnLoad",
onEvent: "PluginOnEvent",
onUnload: "PluginOnUnload",
setCallback: "PluginSetCallback",
getRuntime: "PluginGetRuntime",
}
if manifest.NativeEntrypoints.OnLoad != "" {
names.onLoad = manifest.NativeEntrypoints.OnLoad
}
if manifest.NativeEntrypoints.OnEvent != "" {
names.onEvent = manifest.NativeEntrypoints.OnEvent
}
if manifest.NativeEntrypoints.OnUnload != "" {
names.onUnload = manifest.NativeEntrypoints.OnUnload
}
if manifest.NativeEntrypoints.SetCallback != "" {
names.setCallback = manifest.NativeEntrypoints.SetCallback
}
if manifest.NativeEntrypoints.GetRuntime != "" {
names.getRuntime = manifest.NativeEntrypoints.GetRuntime
}
return names
}
@@ -19,6 +19,7 @@ const (
imageDirectoryEntryExport = 0
imageDirectoryEntryImport = 1
imageDirectoryEntryException = 3
imageDirectoryEntryBaseReloc = 5
imageDirectoryEntryTLS = 9
@@ -122,17 +123,26 @@ type imageTLSDirectory64 struct {
Characteristics uint32
}
type imageRuntimeFunction struct {
BeginAddress uint32
EndAddress uint32
UnwindInfoAddress uint32
}
type MemoryModule struct {
base uintptr
size uintptr
entryPoint uintptr
exports map[string]uintptr
importDLLs []windows.Handle
initialized bool
tlsIndex uint32 // index returned by TlsAlloc; 0xFFFFFFFF if no TLS dir
tlsTemplSrc uintptr // PE template raw data start (for copying to new threads)
tlsTemplSize uintptr // dataSize (template bytes to copy)
tlsTotalSize uintptr // dataSize + zeroFill (total allocation per thread)
base uintptr
size uintptr
entryPoint uintptr
exports map[string]uintptr
importDLLs []windows.Handle
initialized bool
functionTable uintptr
functionCount uint32
functionTableRegistered bool
tlsIndex uint32 // index returned by TlsAlloc; 0xFFFFFFFF if no TLS dir
tlsTemplSrc uintptr // PE template raw data start (for copying to new threads)
tlsTemplSize uintptr // dataSize (template bytes to copy)
tlsTotalSize uintptr // dataSize + zeroFill (total allocation per thread)
}
func LoadMemoryModule(data []byte) (*MemoryModule, error) {
@@ -251,6 +261,11 @@ func LoadMemoryModule(data []byte) (*MemoryModule, error) {
_ = windows.VirtualProtect(base+uintptr(sec.VirtualAddress), size, prot, &oldProt)
}
if err := mm.registerExceptionTable(); err != nil {
mm.Free()
return nil, err
}
mm.setupTLS(dllProcessAttach)
if mappedNT.OptionalHeader.AddressOfEntryPoint != 0 {
@@ -293,6 +308,10 @@ func (mm *MemoryModule) Free() {
syscall.SyscallN(mm.entryPoint, mm.base, dllProcessDetach, 0)
mm.initialized = false
}
if mm.functionTableRegistered {
_, _, _ = procRtlDeleteFunctionTable.Call(mm.functionTable)
mm.functionTableRegistered = false
}
if mm.tlsIndex != tlsOutOfIndexes {
// Free our load-thread per-thread block (held in the slot)
// before releasing the slot itself, so the OS can reuse it.
@@ -346,6 +365,32 @@ func (mm *MemoryModule) processRelocations(dir imageDataDirectory, delta int64)
return nil
}
func (mm *MemoryModule) registerExceptionTable() error {
dosHdr := (*imageDOSHeader)(unsafe.Pointer(mm.base))
ntHdr := (*imageNTHeaders64)(unsafe.Pointer(mm.base + uintptr(dosHdr.LfaNew)))
excDir := ntHdr.OptionalHeader.DataDirectory[imageDirectoryEntryException]
if excDir.VirtualAddress == 0 || excDir.Size == 0 {
return nil
}
entrySize := uint32(unsafe.Sizeof(imageRuntimeFunction{}))
if excDir.Size%entrySize != 0 {
return fmt.Errorf("pe: invalid exception directory size")
}
if uintptr(excDir.VirtualAddress)+uintptr(excDir.Size) > mm.size {
return fmt.Errorf("pe: exception directory out of bounds")
}
table := mm.base + uintptr(excDir.VirtualAddress)
count := excDir.Size / entrySize
r, _, err := procRtlAddFunctionTable.Call(table, uintptr(count), mm.base)
if r == 0 {
return fmt.Errorf("pe: RtlAddFunctionTable failed: %w", err)
}
mm.functionTable = table
mm.functionCount = count
mm.functionTableRegistered = true
return nil
}
func (mm *MemoryModule) resolveImports(dir imageDataDirectory) error {
descSize := unsafe.Sizeof(imageImportDescriptor{})
offset := uintptr(dir.VirtualAddress)
@@ -448,8 +493,14 @@ func (mm *MemoryModule) setupTLS(reason uint32) {
}
tls := (*imageTLSDirectory64)(unsafe.Pointer(mm.base + uintptr(tlsDir.VirtualAddress)))
if tls.EndAddressOfRawData < tls.StartAddressOfRawData {
return
}
dataSize := uintptr(tls.EndAddressOfRawData - tls.StartAddressOfRawData)
totalSize := dataSize + uintptr(tls.SizeOfZeroFill)
if dataSize > 0 && !mm.containsAddress(uintptr(tls.StartAddressOfRawData), dataSize) {
return
}
// Reserve a real TLS slot through the Win32 loader. ntdll grows the
// TEB's TLS array to fit, and this slot is interoperable with the
@@ -494,6 +545,9 @@ func (mm *MemoryModule) setupTLS(reason uint32) {
if tls.AddressOfCallBacks != 0 {
cbAddr := uintptr(tls.AddressOfCallBacks)
if !mm.containsAddress(cbAddr, unsafe.Sizeof(uintptr(0))) {
return
}
for {
cb := *(*uintptr)(unsafe.Pointer(cbAddr))
if cb == 0 {
@@ -501,6 +555,9 @@ func (mm *MemoryModule) setupTLS(reason uint32) {
}
syscall.SyscallN(cb, mm.base, uintptr(reason), 0)
cbAddr += 8
if !mm.containsAddress(cbAddr, unsafe.Sizeof(uintptr(0))) {
break
}
}
}
}
@@ -537,6 +594,17 @@ func (mm *MemoryModule) SetupThreadTLS() func() {
}
}
func (mm *MemoryModule) containsAddress(addr uintptr, size uintptr) bool {
if size == 0 {
return addr >= mm.base && addr <= mm.base+mm.size
}
if addr < mm.base {
return false
}
offset := addr - mm.base
return offset <= mm.size && size <= mm.size-offset
}
func peString(addr uintptr) string {
var buf []byte
for i := 0; i < 4096; i++ { // safety limit
@@ -577,12 +645,14 @@ func sectionProtection(chars uint32) uint32 {
}
var (
modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
procGetProcAddr = modKernel32.NewProc("GetProcAddress")
procTlsAlloc = modKernel32.NewProc("TlsAlloc")
procTlsFree = modKernel32.NewProc("TlsFree")
procTlsSetValue = modKernel32.NewProc("TlsSetValue")
procTlsGetValue = modKernel32.NewProc("TlsGetValue")
modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
procGetProcAddr = modKernel32.NewProc("GetProcAddress")
procRtlAddFunctionTable = modKernel32.NewProc("RtlAddFunctionTable")
procRtlDeleteFunctionTable = modKernel32.NewProc("RtlDeleteFunctionTable")
procTlsAlloc = modKernel32.NewProc("TlsAlloc")
procTlsFree = modKernel32.NewProc("TlsFree")
procTlsSetValue = modKernel32.NewProc("TlsSetValue")
procTlsGetValue = modKernel32.NewProc("TlsGetValue")
)
const tlsOutOfIndexes uint32 = 0xFFFFFFFF
+32 -12
View File
@@ -9,24 +9,34 @@ type PluginAssets struct {
}
type PluginManifest struct {
ID string `msgpack:"id" json:"id"`
Name string `msgpack:"name" json:"name"`
APIVersion int `msgpack:"apiVersion,omitempty" json:"apiVersion,omitempty"`
RuntimeKind string `msgpack:"runtime,omitempty" json:"runtime,omitempty"`
Version string `msgpack:"version,omitempty" json:"version,omitempty"`
Description string `msgpack:"description,omitempty" json:"description,omitempty"`
Binary string `msgpack:"binary,omitempty" json:"binary,omitempty"`
Binaries map[string]string `msgpack:"binaries,omitempty" json:"binaries,omitempty"`
WASM string `msgpack:"wasm,omitempty" json:"wasm,omitempty"`
Needs PluginNeeds `msgpack:"needs,omitempty" json:"needs,omitempty"`
Entry string `msgpack:"entry,omitempty" json:"entry,omitempty"`
Assets PluginAssets `msgpack:"assets,omitempty" json:"assets,omitempty"`
ID string `msgpack:"id" json:"id"`
Name string `msgpack:"name" json:"name"`
APIVersion int `msgpack:"apiVersion,omitempty" json:"apiVersion,omitempty"`
RuntimeKind string `msgpack:"runtime,omitempty" json:"runtime,omitempty"`
NativeLoader string `msgpack:"nativeLoader,omitempty" json:"nativeLoader,omitempty"`
NativeEntrypoints NativeEntrypoints `msgpack:"nativeEntrypoints,omitempty" json:"nativeEntrypoints,omitempty"`
Version string `msgpack:"version,omitempty" json:"version,omitempty"`
Description string `msgpack:"description,omitempty" json:"description,omitempty"`
Binary string `msgpack:"binary,omitempty" json:"binary,omitempty"`
Binaries map[string]string `msgpack:"binaries,omitempty" json:"binaries,omitempty"`
WASM string `msgpack:"wasm,omitempty" json:"wasm,omitempty"`
Needs PluginNeeds `msgpack:"needs,omitempty" json:"needs,omitempty"`
Entry string `msgpack:"entry,omitempty" json:"entry,omitempty"`
Assets PluginAssets `msgpack:"assets,omitempty" json:"assets,omitempty"`
}
type PluginNeeds struct {
Files []PluginFileNeed `msgpack:"files,omitempty" json:"files,omitempty"`
}
type NativeEntrypoints struct {
OnLoad string `msgpack:"onLoad,omitempty" json:"onLoad,omitempty"`
OnEvent string `msgpack:"onEvent,omitempty" json:"onEvent,omitempty"`
OnUnload string `msgpack:"onUnload,omitempty" json:"onUnload,omitempty"`
SetCallback string `msgpack:"setCallback,omitempty" json:"setCallback,omitempty"`
GetRuntime string `msgpack:"getRuntime,omitempty" json:"getRuntime,omitempty"`
}
type PluginFileNeed struct {
Bucket string `msgpack:"bucket" json:"bucket"`
Access []string `msgpack:"access" json:"access"`
@@ -67,11 +77,21 @@ func ManifestFromMap(m map[string]interface{}) (PluginManifest, error) {
manifest.Name = stringVal(m["name"])
manifest.APIVersion = intVal(m["apiVersion"])
manifest.RuntimeKind = stringVal(m["runtime"])
manifest.NativeLoader = stringVal(m["nativeLoader"])
manifest.Version = stringVal(m["version"])
manifest.Description = stringVal(m["description"])
manifest.Binary = stringVal(m["binary"])
manifest.WASM = stringVal(m["wasm"])
manifest.Entry = stringVal(m["entry"])
if entryRaw, ok := m["nativeEntrypoints"].(map[string]interface{}); ok {
manifest.NativeEntrypoints = NativeEntrypoints{
OnLoad: stringVal(entryRaw["onLoad"]),
OnEvent: stringVal(entryRaw["onEvent"]),
OnUnload: stringVal(entryRaw["onUnload"]),
SetCallback: stringVal(entryRaw["setCallback"]),
GetRuntime: stringVal(entryRaw["getRuntime"]),
}
}
if binariesRaw, ok := m["binaries"].(map[string]interface{}); ok {
manifest.Binaries = make(map[string]string, len(binariesRaw))