添加动态库加载功能及测试用例

This commit is contained in:
程广 2025-09-19 18:57:22 +08:00
parent 64e48a4094
commit 7624246633
9 changed files with 162 additions and 5 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
vendor/

5
go.mod
View File

@ -2,4 +2,7 @@ module git.kingecg.top/kingecg/gosh
go 1.23.1 go 1.23.1
require github.com/mattn/anko v0.1.10 // indirect require (
github.com/ebitengine/purego v0.8.4
github.com/mattn/anko v0.1.10
)

2
go.sum
View File

@ -1,2 +1,4 @@
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/mattn/anko v0.1.10 h1:3QcIxCLirIxOZhIVtvo9eWz8tym/iZ9Nb29VCnzaMvc= github.com/mattn/anko v0.1.10 h1:3QcIxCLirIxOZhIVtvo9eWz8tym/iZ9Nb29VCnzaMvc=
github.com/mattn/anko v0.1.10/go.mod h1:gjrudvzf1t7FWTZo1Nbywnr75g3uDnGjXdp2nkguBjQ= github.com/mattn/anko v0.1.10/go.mod h1:gjrudvzf1t7FWTZo1Nbywnr75g3uDnGjXdp2nkguBjQ=

105
interceptor/dl.go Normal file
View File

@ -0,0 +1,105 @@
package interceptor
import (
"fmt"
"path/filepath"
"reflect"
"strings"
"github.com/ebitengine/purego"
)
type funcType struct {
ParamTypes []string
ReturnTypes []string
}
var typeMap = map[string]reflect.Type{
"int": reflect.TypeOf(int(0)),
"int32": reflect.TypeOf(int32(0)),
"int64": reflect.TypeOf(int64(0)),
"uint": reflect.TypeOf(uint(0)),
"float32": reflect.TypeOf(float32(0)),
"float64": reflect.TypeOf(float64(0)),
"string": reflect.TypeOf(""),
"bool": reflect.TypeOf(false),
"void": nil, // 用于表示无返回值
}
func makeFuncType(paramTypes, returnTypes []string) (reflect.Type, error) {
var in, out []reflect.Type
// 参数类型
for _, t := range paramTypes {
rt, ok := typeMap[t]
if !ok {
return nil, fmt.Errorf("unknown parameter type: %s", t)
}
in = append(in, rt)
}
// 返回值类型
for _, t := range returnTypes {
if t == "void" {
continue // 无返回值
}
rt, ok := typeMap[t]
if !ok {
return nil, fmt.Errorf("unknown return type: %s", t)
}
out = append(out, rt)
}
return reflect.FuncOf(in, out, false), nil
}
func (a *Ankointerceptor) loadLibrary(libPath string, libFuncMap map[string]funcType) interface{} {
libName := filepath.Base(libPath)
libName = strings.TrimSuffix(libName, filepath.Ext(".dll"))
// 是不是so文件libName里边是否包含.so
if strings.Contains(libName, ".so") {
soIndex := strings.Index(libName, ".so")
libName = libName[0:soIndex]
}
libName = strings.TrimPrefix(libName, "lib")
if _, ok := a.libMap[libName]; ok {
return a.libMap[libName] // 如果已经加载过,直接返回
}
lib, err := purego.Dlopen(libPath, purego.RTLD_LAZY)
if err != nil {
panic("Load library error")
}
a.libhabdles = append(a.libhabdles, lib)
fnmap := make(map[string]interface{})
fileds := make([]reflect.StructField, 0, len(fnmap))
for name, types := range libFuncMap {
ft, err := makeFuncType(types.ParamTypes, types.ReturnTypes)
if err != nil {
panic(err)
}
fn := reflect.New(ft).Elem().Addr().Interface()
purego.RegisterLibFunc(fn, lib, name)
//Get ft 指针类型
// ftp := reflect.PtrTo(ft)
fileds = append(fileds, reflect.StructField{
Name: name,
Type: ft,
})
fnmap[name] = fn
}
// 根据fnmap构建一个匿名结构体结构体的字段名为函数名字段值为函数指针
libstruct := reflect.StructOf(fileds)
// 创建一个匿名结构体的实例
libinstance := reflect.New(libstruct).Elem()
for name, fn := range fnmap {
// 将函数指针指向的函数设置到结构体实例的对应字段中
libinstance.FieldByName(name).Set(reflect.ValueOf(fn).Elem())
// libinstance.FieldByName(name).Set()
}
a.libMap[libName] = libinstance.Interface()
return a.libMap[libName]
}

View File

@ -11,6 +11,8 @@ import (
type Ankointerceptor struct { type Ankointerceptor struct {
importMap map[string]interface{} importMap map[string]interface{}
libMap map[string]interface{}
libhabdles []uintptr
ankoEnv *env.Env ankoEnv *env.Env
} }
@ -18,9 +20,14 @@ func NewAnkointerceptor(ankoEnv *env.Env) *Ankointerceptor {
a := &Ankointerceptor{ a := &Ankointerceptor{
importMap: make(map[string]interface{}), importMap: make(map[string]interface{}),
libMap: make(map[string]interface{}),
libhabdles: make([]uintptr, 0),
ankoEnv: ankoEnv, ankoEnv: ankoEnv,
} }
a.ankoEnv.Define("println", fmt.Println) a.ankoEnv.Define("println", fmt.Println)
a.ankoEnv.Define("loadLibrary", func(libPath string, libFuncMap map[string]funcType) interface{} {
return a.loadLibrary(libPath, libFuncMap)
})
a.importMap["fs"] = &FileModule{} a.importMap["fs"] = &FileModule{}
a.importMap["net"] = &NetModule{} a.importMap["net"] = &NetModule{}
a.importMap["process"] = &ProcessModule{} a.importMap["process"] = &ProcessModule{}
@ -40,6 +47,7 @@ func (a *Ankointerceptor) Exec(script string) (interface{}, error) {
scriptcode := string(scriptbytes) scriptcode := string(scriptbytes)
result, err := vm.Execute(e, nil, scriptcode) result, err := vm.Execute(e, nil, scriptcode)
if err != nil { if err != nil {
fmt.Println(err)
return nil, err return nil, err
} }
return result, nil return result, nil

View File

@ -22,3 +22,14 @@ func TestAnkointerceptor_ExecRequire(t *testing.T) {
} }
} }
func TestAnkointerceptor_loadlib(t *testing.T) {
e := env.NewEnv()
interceptor := NewAnkointerceptor(e)
_, file, _, _ := runtime.Caller(0)
scriptPath := filepath.Join(filepath.Dir(file), "testdata/test_loadso.ank")
v, _ := interceptor.Exec(scriptPath)
if v != "Hello, world!" {
t.Errorf("Loadso test failed")
}
}

14
interceptor/testdata/greeting.c vendored Normal file
View File

@ -0,0 +1,14 @@
// clib/greeting.c
#include <stdio.h>
#include <string.h>
// 返回静态缓冲区(线程不安全,仅演示)
const char* Greeting(const char* name) {
static char buf[256];
snprintf(buf, sizeof(buf), "Hello, %s!", name);
return buf;
}
int Add(int a, int b) {
return a + b;
}

BIN
interceptor/testdata/libgreeting.so vendored Executable file

Binary file not shown.

13
interceptor/testdata/test_loadso.ank vendored Normal file
View File

@ -0,0 +1,13 @@
input=[]string{"string"}
output=[]string{"string"}
libdefine=make(struct{
ParamTypes []string,
ReturnTypes []string
})
libdefine.ParamTypes=input
libdefine.ReturnTypes=output
libdefines=map[string]interface{}
libdefines["Greeting"]=libdefine
lib=loadLibrary(__dirname+'/libgreeting.so',libdefines)
x=lib.Greeting('world')
x