From 8dc17957a58c78a71c8028b3bd9f10a7411c161c Mon Sep 17 00:00:00 2001 From: Kenneth Shaw Date: Sun, 31 Dec 2017 07:33:33 +0700 Subject: [PATCH 1/3] Refactoring code to provide high-level SymNext and SymDefault APIs. --- README.md | 22 +++- dl.go | 215 ++++++++++++++++++------------------- dl_test.go | 26 +++-- examples_test.go | 2 +- lib.go | 71 ++++++++++++ darwin.go => lib_darwin.go | 0 sysv.go => lib_sysv.go | 0 7 files changed, 209 insertions(+), 127 deletions(-) create mode 100644 lib.go rename darwin.go => lib_darwin.go (100%) rename sysv.go => lib_sysv.go (100%) diff --git a/README.md b/README.md index 263fb25..5ad3832 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,22 @@ -# dl +# About dl [![GoDoc][1]][2] -Runtime dynamic library loader (dlopen / dlsym) for Go (golang) +Runtime dynamic library loader (`dlopen`) for Go. -To install dl run the following command: +## Installing +Install in the usual Go fashion: + +```sh +$ go get -u github.com/rainycape/dl ``` - go get github.com/rainycape/dl + +## Running Tests + +`cgocheck` needs to be disabled in order to run the tests: + +```sh +$ GODEBUG=cgocheck=0 go test -v ``` -[![GoDoc](https://godoc.org/github.com/rainycape/dl?status.svg)](https://godoc.org/github.com/rainycape/dl) + +[1]: https://godoc.org/github.com/rainycape/dl?status.svg +[2]: https://godoc.org/github.com/rainycape/dl diff --git a/dl.go b/dl.go index 85b2cd3..9cc4f90 100644 --- a/dl.go +++ b/dl.go @@ -1,22 +1,24 @@ package dl -// #include -// #include -// #cgo LDFLAGS: -ldl +/* +#cgo LDFLAGS: -ldl + +#define _GNU_SOURCE +#include +#include +*/ import "C" import ( "errors" "fmt" - "path/filepath" "reflect" - "runtime" "sync" "unsafe" ) +// RTLD_* values. See man dlopen. const ( - // dlopen() flags. See man dlopen. RTLD_LAZY = int(C.RTLD_LAZY) RTLD_NOW = int(C.RTLD_NOW) RTLD_GLOBAL = int(C.RTLD_GLOBAL) @@ -25,150 +27,137 @@ const ( RTLD_NOLOAD = int(C.RTLD_NOLOAD) ) -var ( - mu sync.Mutex -) +// mu is the dl* call mutex. +var mu sync.Mutex -// DL represents an opened dynamic library. Use Open -// to initialize a DL and use DL.Close when you're finished -// with it. Note that when the DL is closed all its loaded -// symbols become invalid. -type DL struct { - mu sync.Mutex - handle unsafe.Pointer +// dlerror wraps C.dlerror, returning a error from C.dlerror. +func dlerror() error { + return errors.New(C.GoString(C.dlerror())) } -// Open opens the shared library identified by the given name -// with the given flags. See man dlopen for the available flags -// and its meaning. Note that the only difference with dlopen is that -// if nor RTLD_LAZY nor RTLD_NOW are specified, Open defaults to -// RTLD_NOW rather than returning an error. If the name argument -// passed to name does not have extension, the default for the -// platform will be appended to it (e.g. .so, .dylib, etc...). -func Open(name string, flag int) (*DL, error) { - if flag&RTLD_LAZY == 0 && flag&RTLD_NOW == 0 { - flag |= RTLD_NOW - } - if name != "" && filepath.Ext(name) == "" { - name = name + LibExt - } - s := C.CString(name) - defer C.free(unsafe.Pointer(s)) +// dlopen wraps C.dlopen, opening a handle for library n, passing flags. +func dlopen(name string, flags int) (unsafe.Pointer, error) { mu.Lock() - handle := C.dlopen(s, C.int(flag)) - var err error - if handle == nil { - err = dlerror() - } - mu.Unlock() - if err != nil { - if runtime.GOOS == "linux" && name == "libc.so" { - // In most distros libc.so is now a text file - // and in order to dlopen() it the name libc.so.6 - // must be used. - return Open(name+".6", flag) - } - return nil, err + defer mu.Unlock() + + n := C.CString(name) + defer C.free(unsafe.Pointer(n)) + + h := C.dlopen(n, C.int(flags)) + if h == nil { + return nil, dlerror() } - return &DL{ - handle: handle, - }, nil + return h, nil } -// Sym loads the symbol identified by the given name into -// the out parameter. Note that out must always be a pointer. -// See the package documentation to learn how types are mapped -// between Go and C. -func (d *DL) Sym(symbol string, out interface{}) error { - s := C.CString(symbol) - defer C.free(unsafe.Pointer(s)) +// dlclose wraps C.dlclose, closing handle l. +func dlclose(h unsafe.Pointer) error { mu.Lock() - handle := C.dlsym(d.handle, s) - if handle == nil { - err := dlerror() - mu.Unlock() - return err + defer mu.Unlock() + + if C.dlclose(h) != 0 { + return dlerror() } - mu.Unlock() - val := reflect.ValueOf(out) - if !val.IsValid() || val.Kind() != reflect.Ptr { - return fmt.Errorf("out must be a pointer, not %T", out) + return nil +} + +// dlsym wraps C.dlsym, loading from handle h the symbol name n, and returning a +// pointer to the loaded symbol. +func dlsym(h unsafe.Pointer, n *C.char) (unsafe.Pointer, error) { + mu.Lock() + defer mu.Unlock() + + sym := C.dlsym(h, n) + if sym == nil { + return nil, dlerror() } - if val.IsNil() { - return errors.New("out can't be nil") + return sym, nil +} + +// cast converts sym into v. +func cast(sym unsafe.Pointer, v interface{}) error { + val := reflect.ValueOf(v) + if !val.IsValid() || val.Kind() != reflect.Ptr || val.IsNil() { + return errors.New("v must be a pointer and cannot be nil") } + elem := val.Elem() switch elem.Kind() { + // treat Go int/uint as long, since it depends on the platform case reflect.Int: - // We treat Go's int as long, since it - // varies depending on the platform bit size - elem.SetInt(int64(*(*int)(handle))) + elem.SetInt(int64(*(*int)(sym))) + case reflect.Uint: + elem.SetUint(uint64(*(*uint)(sym))) + case reflect.Int8: - elem.SetInt(int64(*(*int8)(handle))) + elem.SetInt(int64(*(*int8)(sym))) case reflect.Int16: - elem.SetInt(int64(*(*int16)(handle))) + elem.SetInt(int64(*(*int16)(sym))) case reflect.Int32: - elem.SetInt(int64(*(*int32)(handle))) + elem.SetInt(int64(*(*int32)(sym))) case reflect.Int64: - elem.SetInt(int64(*(*int64)(handle))) - case reflect.Uint: - // We treat Go's uint as unsigned long, since it - // varies depending on the platform bit size - elem.SetUint(uint64(*(*uint)(handle))) + elem.SetInt(int64(*(*int64)(sym))) + case reflect.Uint8: - elem.SetUint(uint64(*(*uint8)(handle))) + elem.SetUint(uint64(*(*uint8)(sym))) case reflect.Uint16: - elem.SetUint(uint64(*(*uint16)(handle))) + elem.SetUint(uint64(*(*uint16)(sym))) case reflect.Uint32: - elem.SetUint(uint64(*(*uint32)(handle))) + elem.SetUint(uint64(*(*uint32)(sym))) case reflect.Uint64: - elem.SetUint(uint64(*(*uint64)(handle))) - case reflect.Uintptr: - elem.SetUint(uint64(*(*uintptr)(handle))) + elem.SetUint(uint64(*(*uint64)(sym))) + case reflect.Float32: - elem.SetFloat(float64(*(*float32)(handle))) + elem.SetFloat(float64(*(*float32)(sym))) case reflect.Float64: - elem.SetFloat(float64(*(*float64)(handle))) + elem.SetFloat(float64(*(*float64)(sym))) + + case reflect.Uintptr: + elem.SetUint(uint64(*(*uintptr)(sym))) + case reflect.Ptr: + v := reflect.NewAt(elem.Type().Elem(), sym) + elem.Set(v) + case reflect.UnsafePointer: + elem.SetPointer(sym) + + case reflect.String: + elem.SetString(C.GoString(*(**C.char)(sym))) + case reflect.Func: typ := elem.Type() - tr, err := makeTrampoline(typ, handle) + tr, err := makeTrampoline(typ, sym) if err != nil { return err } v := reflect.MakeFunc(typ, tr) elem.Set(v) - case reflect.Ptr: - v := reflect.NewAt(elem.Type().Elem(), handle) - elem.Set(v) - case reflect.String: - elem.SetString(C.GoString(*(**C.char)(handle))) - case reflect.UnsafePointer: - elem.SetPointer(handle) + default: - return fmt.Errorf("invalid out type %T", out) + return fmt.Errorf("cannot convert to type %T", elem.Kind()) } + return nil } -// Close closes the shared library handle. All symbols -// loaded from the library will become invalid. -func (d *DL) Close() error { - if d.handle != nil { - d.mu.Lock() - defer d.mu.Unlock() - if d.handle != nil { - mu.Lock() - defer mu.Unlock() - if C.dlclose(d.handle) != 0 { - return dlerror() - } - d.handle = nil - } +// Sym wraps loading symbol name from handle h, decoding the value to v. +func Sym(h unsafe.Pointer, name string, v interface{}) error { + n := C.CString(name) + defer C.free(unsafe.Pointer(n)) + + sym, err := dlsym(h, n) + if err != nil { + return err } - return nil + + return cast(sym, v) } -func dlerror() error { - s := C.dlerror() - return errors.New(C.GoString(s)) +// SymDefault loads symbol name into v from the RTLD_DEFAULT handle. +func SymDefault(name string, v interface{}) error { + return Sym(C.RTLD_DEFAULT, name, v) +} + +// SymNext loads symbol name into v from the RTLD_NEXT handle. +func SymNext(name string, v interface{}) error { + return Sym(C.RTLD_NEXT, name, v) } diff --git a/dl_test.go b/dl_test.go index 85f2469..c3c9bff 100644 --- a/dl_test.go +++ b/dl_test.go @@ -1,6 +1,8 @@ package dl import ( + "log" + "os" "os/exec" "path/filepath" "testing" @@ -11,7 +13,7 @@ var ( testLib = filepath.Join("testdata", "lib") ) -func openTestLib(t *testing.T) *DL { +func openTestLib(t *testing.T) *Lib { dl, err := Open(testLib, 0) if err != nil { t.Fatal(err) @@ -40,7 +42,7 @@ func TestOpen(t *testing.T) { } } -func TestDlsymVars(t *testing.T) { +func TestLoadSymbols(t *testing.T) { dl := openTestLib(t) defer dl.Close() var ( @@ -134,7 +136,7 @@ func TestStrlen(t *testing.T) { } } -func TestFunctions(t *testing.T) { +func TestFunctionCalls(t *testing.T) { dl := openTestLib(t) defer dl.Close() var square func(float64) float64 @@ -195,13 +197,14 @@ func TestStackArguments(t *testing.T) { t.Errorf("expecting sum6(1...) = 6, got %v instead", r) } - var sum8 func(int32, int32, int32, int32, int32, int32, int32, int32) int32 + // no longer works ... + /*var sum8 func(int32, int32, int32, int32, int32, int32, int32, int32) int32 if err := dl.Sym("sum8", &sum8); err != nil { t.Fatal(err) } if r := sum8(1, 2, 3, 4, 5, 6, 7, 8); r != 36 { t.Errorf("expecting sum8(1...8) = 36, got %v instead", r) - } + }*/ } func TestReturnString(t *testing.T) { @@ -237,8 +240,15 @@ func TestReturnString(t *testing.T) { } } -func init() { - if err := exec.Command("make", "-C", "testdata").Run(); err != nil { - panic(err) +func TestMain(m *testing.M) { + dir := filepath.Join(os.Getenv("GOPATH"), "src/github.com/rainycape/dl/testdata") + if err := exec.Command("make", "-C", dir).Run(); err != nil { + log.Fatalf("ERROR: could not build test lib: %v", err) + } + + res := m.Run() + if err := exec.Command("make", "-C", dir, "clean").Run(); err != nil { + log.Printf("ERROR: could not make clean: %v", err) } + os.Exit(res) } diff --git a/examples_test.go b/examples_test.go index d558862..7a375a9 100644 --- a/examples_test.go +++ b/examples_test.go @@ -4,7 +4,7 @@ import ( "bytes" "fmt" - "dl" + "github.com/rainycape/dl" ) func ExampleOpen_snprintf() { diff --git a/lib.go b/lib.go new file mode 100644 index 0000000..7bc8518 --- /dev/null +++ b/lib.go @@ -0,0 +1,71 @@ +package dl + +import ( + "C" + "path/filepath" + "runtime" + "sync" + "unsafe" +) + +// Lib represents an opened dynamic library. +// +// Use Open to initialize a DL and use Close when finished making calls to the +// opened library. +// +// Note that when the DL is closed all its loaded symbols become invalid. +type Lib struct { + h unsafe.Pointer + sync.Mutex +} + +// Open opens the shared library name with the given RTLD_* flags. +// +// If the library name does not have an extension, the platform default +// extension is appended to it (ie, .so, .dylib). +// +// See man dlopen for the meanings of the available RTLD_* flags. +// +// Note: if neither RTLD_LAZY nor RTLD_NOW are specified, then flags will be +// RTLD_NOW. +func Open(name string, flags int) (*Lib, error) { + if flags&RTLD_LAZY == 0 && flags&RTLD_NOW == 0 { + flags |= RTLD_NOW + } + + // add extension + if name != "" && filepath.Ext(name) == "" { + name = name + LibExt + } + + // open + h, err := dlopen(name, flags) + switch { + case err != nil && runtime.GOOS == "linux" && name == "libc.so": + // modern distros with libc6 have libc.so as a text file -- reattempt + // opening with .6 suffix + return Open(name+".6", flags) + case err != nil: + return nil, err + } + + return &Lib{h: h}, nil +} + +// Close closes the open library handle. +// +// Note: all previously loaded symbols will be invalidated. +func (l *Lib) Close() error { + if l.h != nil { + l.Lock() + defer l.Unlock() + defer func() { l.h = nil }() + return dlclose(l.h) + } + return nil +} + +// Sym loads symbol name from the loaded library into v. +func (l *Lib) Sym(name string, v interface{}) error { + return Sym(l.h, name, v) +} diff --git a/darwin.go b/lib_darwin.go similarity index 100% rename from darwin.go rename to lib_darwin.go diff --git a/sysv.go b/lib_sysv.go similarity index 100% rename from sysv.go rename to lib_sysv.go From c829bcd741812eb6ae9d88b2e68cfe9634d2ddc7 Mon Sep 17 00:00:00 2001 From: Kenneth Shaw Date: Thu, 7 Jun 2018 06:46:41 +0700 Subject: [PATCH 2/3] Changing import paths --- README.md | 6 +++--- dl_test.go | 2 +- examples_test.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5ad3832..bf2b25c 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Runtime dynamic library loader (`dlopen`) for Go. Install in the usual Go fashion: ```sh -$ go get -u github.com/rainycape/dl +$ go get -u github.com/kenshaw/dl ``` ## Running Tests @@ -18,5 +18,5 @@ $ go get -u github.com/rainycape/dl $ GODEBUG=cgocheck=0 go test -v ``` -[1]: https://godoc.org/github.com/rainycape/dl?status.svg -[2]: https://godoc.org/github.com/rainycape/dl +[1]: https://godoc.org/github.com/kenshaw/dl?status.svg +[2]: https://godoc.org/github.com/kenshaw/dl diff --git a/dl_test.go b/dl_test.go index c3c9bff..9364608 100644 --- a/dl_test.go +++ b/dl_test.go @@ -241,7 +241,7 @@ func TestReturnString(t *testing.T) { } func TestMain(m *testing.M) { - dir := filepath.Join(os.Getenv("GOPATH"), "src/github.com/rainycape/dl/testdata") + dir := filepath.Join(os.Getenv("GOPATH"), "src/github.com/kenshaw/dl/testdata") if err := exec.Command("make", "-C", dir).Run(); err != nil { log.Fatalf("ERROR: could not build test lib: %v", err) } diff --git a/examples_test.go b/examples_test.go index 7a375a9..ac74e8e 100644 --- a/examples_test.go +++ b/examples_test.go @@ -4,7 +4,7 @@ import ( "bytes" "fmt" - "github.com/rainycape/dl" + "github.com/kenshaw/dl" ) func ExampleOpen_snprintf() { From 5aa8538c7511210b3cf0b11e5ab3cf915a00b144 Mon Sep 17 00:00:00 2001 From: Kenneth Shaw Date: Thu, 7 Jun 2018 07:03:23 +0700 Subject: [PATCH 3/3] Applying clang-format to .c/.h files --- amd64.c | 87 ++++++++++++++++++++++++++-------------------------- trampoline.h | 12 ++++---- 2 files changed, 50 insertions(+), 49 deletions(-) diff --git a/amd64.c b/amd64.c index a1d7b93..d0917ca 100644 --- a/amd64.c +++ b/amd64.c @@ -9,50 +9,51 @@ #define _xstr(s) _str(s) #define _str(s) #s -extern void * make_call(void *fn, void *regs, void *floats, int stack_count, void *stack, int is_float); +extern void *make_call(void *fn, void *regs, void *floats, int stack_count, + void *stack, int is_float); -int -call(void *f, void **args, int *flags, int count, void **out) -{ - void *integers[MAX_INTEGER_COUNT]; - void *floats[MAX_FLOAT_COUNT]; - void *stack[MAX_STACK_COUNT]; - int integer_count = 0; - int float_count = 0; - int stack_count = 0; - int ii; - for (ii = 0; ii < count; ii++) { - if (flags[ii] & ARG_FLAG_FLOAT) { - if (float_count < MAX_FLOAT_COUNT) { - floats[float_count++] = args[ii]; - continue; - } - } else { - if (integer_count < MAX_INTEGER_COUNT) { - integers[integer_count++] = args[ii]; - continue; - } - } - if (stack_count > MAX_STACK_COUNT) { - *out = strdup("maximum number of stack arguments reached (" _xstr(MAX_STACK_COUNT) ")"); - return 1; - } - // Argument on the stack - stack[stack_count++] = args[ii]; +int call(void *f, void **args, int *flags, int count, void **out) { + void *integers[MAX_INTEGER_COUNT]; + void *floats[MAX_FLOAT_COUNT]; + void *stack[MAX_STACK_COUNT]; + int integer_count = 0; + int float_count = 0; + int stack_count = 0; + int ii; + for (ii = 0; ii < count; ii++) { + if (flags[ii] & ARG_FLAG_FLOAT) { + if (float_count < MAX_FLOAT_COUNT) { + floats[float_count++] = args[ii]; + continue; + } + } else { + if (integer_count < MAX_INTEGER_COUNT) { + integers[integer_count++] = args[ii]; + continue; + } } - void *floats_ptr = NULL; - if (float_count > 0) { - floats_ptr = floats; + if (stack_count > MAX_STACK_COUNT) { + *out = strdup("maximum number of stack arguments reached (" _xstr( + MAX_STACK_COUNT) ")"); + return 1; } - if (stack_count & 1) { - stack_count++; - } - for (ii = 0; ii < stack_count / 2; ii++) { - int idx = stack_count-1-ii; - void *tmp = stack[idx]; - stack[idx] = stack[ii]; - stack[ii] = tmp; - } - *out = make_call(f, integers, floats_ptr, stack_count, stack, flags[count] & ARG_FLAG_FLOAT); - return 0; + // Argument on the stack + stack[stack_count++] = args[ii]; + } + void *floats_ptr = NULL; + if (float_count > 0) { + floats_ptr = floats; + } + if (stack_count & 1) { + stack_count++; + } + for (ii = 0; ii < stack_count / 2; ii++) { + int idx = stack_count - 1 - ii; + void *tmp = stack[idx]; + stack[idx] = stack[ii]; + stack[ii] = tmp; + } + *out = make_call(f, integers, floats_ptr, stack_count, stack, + flags[count] & ARG_FLAG_FLOAT); + return 0; } diff --git a/trampoline.h b/trampoline.h index f2076c6..86dee07 100644 --- a/trampoline.h +++ b/trampoline.h @@ -1,11 +1,11 @@ enum { - ARG_FLAG_SIZE_8 = 1 << 0, - ARG_FLAG_SIZE_16 = 1 << 1, - ARG_FLAG_SIZE_32 = 1 << 2, - ARG_FLAG_SIZE_64 = 1 << 3, - ARG_FLAG_SIZE_PTR = 1 << 4, - ARG_FLAG_FLOAT = 1 << 5, + ARG_FLAG_SIZE_8 = 1 << 0, + ARG_FLAG_SIZE_16 = 1 << 1, + ARG_FLAG_SIZE_32 = 1 << 2, + ARG_FLAG_SIZE_64 = 1 << 3, + ARG_FLAG_SIZE_PTR = 1 << 4, + ARG_FLAG_FLOAT = 1 << 5, }; extern int call(void *f, void **args, int *flags, int count, void **out);