Skip to content

Commit

Permalink
Refactoring code to provide high-level SymNext and SymDefault APIs.
Browse files Browse the repository at this point in the history
  • Loading branch information
kenshaw committed Dec 31, 2017
1 parent 1b01514 commit 8dc1795
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 127 deletions.
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
215 changes: 102 additions & 113 deletions dl.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
package dl

// #include <dlfcn.h>
// #include <stdlib.h>
// #cgo LDFLAGS: -ldl
/*
#cgo LDFLAGS: -ldl
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdlib.h>
*/
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)
Expand All @@ -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)
}
26 changes: 18 additions & 8 deletions dl_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package dl

import (
"log"
"os"
"os/exec"
"path/filepath"
"testing"
Expand All @@ -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)
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
2 changes: 1 addition & 1 deletion examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"bytes"
"fmt"

"dl"
"github.com/rainycape/dl"
)

func ExampleOpen_snprintf() {
Expand Down
Loading

0 comments on commit 8dc1795

Please sign in to comment.