avyos.dev/pkg/dl

package dl

Overview

Package dl implements a pure-Go ELF dynamic loader / runtime linker for Linux amd64 and arm64. It provides dlopen/dlsym semantics: loading shared objects (.so files) at runtime, resolving symbols, performing relocations, and calling exported functions — all without cgo.

SECURITY WARNING: Loading a shared object executes arbitrary native machine code in the current process. No sandboxing is provided or claimed. Treat all .so files as untrusted unless you have independently verified them.

Quick start

ld := dl.New(dl.WithRoot("/linux")) h, err := ld.Open("libc.so.6", dl.RTLD_NOW|dl.RTLD_GLOBAL) if err != nil { log.Fatal(err) } defer h.Close() addr, err := h.Sym("puts") if err != nil { log.Fatal(err) } dl.Call1(addr, dl.CString("hello world"))

Package dl provides a pure-Go ELF dynamic loader / runtime linker.

SECURITY WARNING: This package loads and executes native machine code from disk. Treat all .so files as untrusted unless independently verified. Loading a shared object is equivalent to executing arbitrary native code in the current process. No sandboxing is provided or claimed.

Export GroupCount
Constants0
Variables1
Functions7
Types10

Variables

ErrNotFound, ErrNotELF, ErrWrongArch, ErrNotShared, ErrSymbolNotFound, ErrClosed, ErrUnsupported

var (
	ErrNotFound       = fmt.Errorf("dl: library not found")
	ErrNotELF         = fmt.Errorf("dl: not an ELF file")
	ErrWrongArch      = fmt.Errorf("dl: wrong ELF machine type for this architecture")
	ErrNotShared      = fmt.Errorf("dl: ELF type is not ET_DYN (shared object / PIE)")
	ErrSymbolNotFound = fmt.Errorf("dl: symbol not found")
	ErrClosed         = fmt.Errorf("dl: handle is closed")
	ErrUnsupported    = fmt.Errorf("dl: unsupported")
)

Common sentinel errors.

Functions

CString

func CString(s string) ([]byte, uintptr)

CString allocates a NUL-terminated byte slice suitable for passing to C functions expecting a const char*. The returned uintptr points to the first byte. The caller must keep the returned []byte alive for the duration of the call (to prevent GC from collecting it).

Example:

s, p := dl.CString("hello") _ = s // keep alive dl.Call1(putsAddr, p)

CStringPtr

func CStringPtr(s string) uintptr

CStringPtr is a convenience that returns just the pointer. The caller must ensure the string stays referenced until after the native call returns. In practice, the Go compiler will keep the string argument alive.

Call0

func Call0(addr uintptr) uintptr

Call0 calls a native void(*)(void) function at addr.

Call1

func Call1(addr uintptr, a1 uintptr) uintptr

Call1 calls a native function with 1 integer/pointer argument.

Call2

func Call2(addr uintptr, a1, a2 uintptr) uintptr

Call2 calls a native function with 2 integer/pointer arguments.

Call3

func Call3(addr uintptr, a1, a2, a3 uintptr) uintptr

Call3 calls a native function with 3 integer/pointer arguments.

Call6

func Call6(addr uintptr, a1, a2, a3, a4, a5, a6 uintptr) uintptr

Call6 calls a native function with up to 6 integer/pointer arguments.

Types

Flags

type Flags uint32

Flags control how a shared object is loaded and bound.

Constants

RTLD_LAZY, RTLD_NOW, RTLD_GLOBAL, RTLD_LOCAL, RTLD_DEEPBIND, RTLD_NODELETE, RTLD_NOLOAD

const (
	// RTLD_LAZY defers function binding until first call (PLT lazy binding).
	RTLD_LAZY Flags = 0x00001

	// RTLD_NOW resolves all symbols before Open returns.
	RTLD_NOW Flags = 0x00002

	// RTLD_GLOBAL makes the object's symbols available for subsequent loads.
	RTLD_GLOBAL Flags = 0x00100

	// RTLD_LOCAL restricts symbol scope to the handle (default).
	RTLD_LOCAL Flags = 0x00000

	// RTLD_DEEPBIND causes the loaded object to prefer its own symbols over
	// those in the global scope when resolving references.
	RTLD_DEEPBIND Flags = 0x00008

	// RTLD_NODELETE prevents unloading even when refcount reaches zero.
	RTLD_NODELETE Flags = 0x01000

	// RTLD_NOLOAD does not load the object; returns a handle only if already
	// loaded (useful for promoting to RTLD_GLOBAL or getting stats).
	RTLD_NOLOAD Flags = 0x00004
)

FormatError

type FormatError struct {
	Path   string
	Detail string
}

FormatError is returned when an ELF file is malformed.

Methods

Error

func (e *FormatError) Error() string

Handle

type Handle struct {
	// contains filtered or unexported fields
}

Handle represents a loaded shared object (or group of objects for transitive dependencies). Use Sym to resolve exported symbols and Close to decrement the reference count.

Methods

Close

func (h *Handle) Close() error

Close decrements the reference count of all objects in this handle's scope. When an object's count reaches zero, its destructors (DT_FINI_ARRAY, DT_FINI) are called and its mappings are released — in reverse dependency order.

Sym

func (h *Handle) Sym(name string) (uintptr, error)

Sym resolves a symbol by name and returns its absolute virtual address. The returned uintptr can be passed to Call0..Call6 helpers.

SymVersion

func (h *Handle) SymVersion(name, version string) (uintptr, error)

SymVersion resolves a versioned symbol. Version should match the version string from the library's version definition (e.g. "GLIBC_2.17").

LoadError

type LoadError struct {
	Path string
	Err  error
}

LoadError is returned when an ELF object cannot be loaded.

Methods

Error

func (e *LoadError) Error() string

Unwrap

func (e *LoadError) Unwrap() error

Loader

type Loader struct {
	// contains filtered or unexported fields
}

Loader holds global state for the dynamic linker: loaded objects, search paths, the global symbol scope, and caching. It is safe for concurrent use.

Functions

New

func New(opts ...Option) *Loader

New creates a Loader with the given options.

Methods

AddSearchPath

func (ld *Loader) AddSearchPath(path string)

AddSearchPath appends a directory to the library search list.

Objects

func (ld *Loader) Objects() []*Object

Objects returns a deduplicated snapshot of all loaded objects for debugging. The map may contain both path and soname keys pointing to the same object; this method deduplicates by pointer identity.

Open

func (ld *Loader) Open(name string, flags Flags) (*Handle, error)

Open loads a shared object and its dependencies, applies relocations, and runs constructors. The returned Handle provides Sym/Close.

Flags control binding and scope behavior:

  • RTLD_NOW: resolve all symbols immediately (default if RTLD_LAZY not set)
  • RTLD_LAZY: defer PLT binding (currently resolved eagerly for correctness)
  • RTLD_GLOBAL: add symbols to the global scope
  • RTLD_LOCAL: keep symbols in handle-local scope (default)
  • RTLD_DEEPBIND: prefer object's own symbols
  • RTLD_NOLOAD: return handle only if already loaded
  • RTLD_NODELETE: prevent unloading

SetRoot

func (ld *Loader) SetRoot(root string)

SetRoot changes the filesystem root for library search.

Stats

func (ld *Loader) Stats() Stats

Stats returns a snapshot of loader statistics.

Object

type Object struct {
	// Identity
	Path   string // resolved filesystem path
	Soname string // DT_SONAME if present

	// Memory layout
	Base     uintptr // load bias (difference between mapped and vaddr)
	MinVaddr uint64  // lowest vaddr of PT_LOAD segments
	MaxVaddr uint64  // highest vaddr+memsz (page-aligned)
	MapSize  uintptr // total mapped region size

	// Program headers (in-memory copy)
	Phdrs []elf64Phdr

	// Dependencies (sonames from DT_NEEDED)
	Needed []string

	// RPATH / RUNPATH for dependency search
	Rpath   string
	Runpath string
	// contains filtered or unexported fields
}

Object represents a single loaded ELF shared object. It contains the parsed headers, memory mappings, dynamic tables, symbol/string tables, relocation info, TLS metadata, and dependency list.

Option

type Option func(*Loader)

Option configures a Loader.

Functions

WithDebug

func WithDebug() Option

WithDebug enables verbose debug logging to stderr.

WithLDLibraryPath

func WithLDLibraryPath() Option

WithLDLibraryPath enables reading LD_LIBRARY_PATH from the environment and prepending those directories to the search list.

WithLogger

func WithLogger(l *log.Logger) Option

WithLogger sets a custom logger for debug output.

WithRoot

func WithRoot(root string) Option

WithRoot sets an alternate filesystem root. Library search paths like /lib, /usr/lib become <root>/lib, <root>/usr/lib. Default is "/".

WithSearchPaths

func WithSearchPaths(paths ...string) Option

WithSearchPaths adds extra directories to the default library search list.

RelocError

type RelocError struct {
	Type   uint32 // R_* relocation type number
	Symbol string // symbol name, if any
	Object string // object containing the relocation
	Err    error
}

RelocError is returned when a relocation cannot be applied.

Methods

Error

func (e *RelocError) Error() string

Unwrap

func (e *RelocError) Unwrap() error

Stats

type Stats struct {
	LoadedObjects int    // number of currently loaded ELF objects
	TotalRelocs   uint64 // total relocations applied across all objects
	SymbolLookups uint64 // total symbol lookup attempts
	CacheHits     uint64 // symbol cache hits
}

Stats contains diagnostic counters for a Loader.

SymbolError

type SymbolError struct {
	Name    string
	Version string // empty if unversioned
	Object  string // object that references the symbol
	Err     error
}

SymbolError is returned when a symbol cannot be resolved.

Methods

Error

func (e *SymbolError) Error() string

Unwrap

func (e *SymbolError) Unwrap() error