Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions internal/cbridge/linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//go:build linux && !android && cgo

package cbridge

/*
#cgo LDFLAGS: -ldl
#include <dlfcn.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>

typedef struct {
void* value;
const char* error;
} goffi_dl_result;

static void goffi_dlopen(const char* path, int mode, goffi_dl_result* result) {
result->value = dlopen(path, mode);
result->error = result->value == NULL ? dlerror() : NULL;
}

static void goffi_dlsym(uintptr_t handle, const char* name, goffi_dl_result* result) {
dlerror();
result->value = dlsym((void*)handle, name);
result->error = result->value == NULL ? dlerror() : NULL;
}

static void* goffi_errno_location_address(void) {
return (void*)&__errno_location;
}
*/
import "C"

import "unsafe"

// Dlopen loads a shared library and captures the loader error, if any.
func Dlopen(path string, mode int) (uintptr, string) {
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))

var result C.goffi_dl_result
C.goffi_dlopen(cpath, C.int(mode), &result)
if result.value == nil {
return 0, dlerrorString(result.error)
}
return uintptr(result.value), ""
}

// Dlsym resolves a symbol and captures the loader error, if any.
func Dlsym(handle uintptr, name string) (uintptr, string) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))

var result C.goffi_dl_result
C.goffi_dlsym(C.uintptr_t(handle), cname, &result)
if result.value == nil {
return 0, dlerrorString(result.error)
}
return uintptr(result.value), ""
}

func dlerrorString(message *C.char) string {
if message == nil {
return ""
}
return C.GoString(message)
}

// ErrnoLocationAddress returns the address of libc's errno accessor.
func ErrnoLocationAddress() uintptr {
return uintptr(C.goffi_errno_location_address())
}
54 changes: 54 additions & 0 deletions internal/cbridge/linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//go:build linux && !android && cgo

package cbridge

import "testing"

const rtldNow = 0x00002

func openStandardCLibrary(t *testing.T) uintptr {
t.Helper()

candidates := []string{
"libc.so.6",
"libdl.so.2",
"libc.musl-x86_64.so.1",
"libc.musl-aarch64.so.1",
}
for _, candidate := range candidates {
if handle, _ := Dlopen(candidate, rtldNow); handle != 0 {
return handle
}
}

t.Skipf("no standard libc or libdl candidate available; tried %v", candidates)
return 0
}

func TestDlopenAndDlsym(t *testing.T) {
handle := openStandardCLibrary(t)

symbol, errMessage := Dlsym(handle, "malloc")
if symbol == 0 {
t.Fatalf("Dlsym(malloc) returned 0: %s", errMessage)
}
if errMessage != "" {
t.Fatalf("Dlsym(malloc) returned an unexpected error: %s", errMessage)
}
}

func TestDlopenInvalidLibrary(t *testing.T) {
handle, errMessage := Dlopen("libgoffi-does-not-exist.so", rtldNow)
if handle != 0 {
t.Fatalf("Dlopen returned handle %#x for an invalid library", handle)
}
if errMessage == "" {
t.Fatal("Dlopen returned an empty error for an invalid library")
}
}

func TestErrnoLocationAddress(t *testing.T) {
if address := ErrnoLocationAddress(); address == 0 {
t.Fatal("ErrnoLocationAddress returned 0")
}
}
33 changes: 7 additions & 26 deletions internal/dl/dl_linux.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
//go:build linux && !android
//go:build linux && !android && !cgo

// Linux-specific constants for dynamic library loading.
// Linux no-cgo implementation: link to libdl.so.2 functions via
// cgo_import_dynamic. The shared RTLD_* constants live in dl_linux_consts.go
// (compiled for both the cgo and !cgo paths).
//
// These constants differ from macOS values but the dlopen/dlsym API is identical
// (POSIX standardized). The calling convention is System V AMD64 ABI on both platforms.
// The dlopen/dlsym API is POSIX-standardized; the calling convention is
// System V AMD64 ABI.
//
// Reference: https://codebrowser.dev/glibc/glibc/bits/dlfcn.h.html

package dl

// Link to libdl.so.2 functions using cgo_import_dynamic.
// This works under both CGO_ENABLED=0 (where fakecgo provides the cgo runtime)
// and CGO_ENABLED=1 (where the standard runtime/cgo is linked, see cgo.go).
// This is used with CGO_ENABLED=0, where fakecgo provides the cgo runtime.
//
// Note on glibc >= 2.34: libdl.so.2 is a stub (an empty .so with a versioned
// symlink to libc.so.6). dlopen/dlsym/dlerror/dlclose all live in libc.so.6
Expand All @@ -29,23 +30,3 @@ package dl

// Force dependency on libdl.so.2
//go:cgo_import_dynamic _ _ "libdl.so.2"

// RTLD constants from <dlfcn.h> for dynamic library loading on Linux.
const (
// RTLD_LAZY performs relocations at an implementation-dependent time.
RTLD_LAZY = 0x00001

// RTLD_NOW resolves all symbols when loading the library (recommended).
RTLD_NOW = 0x00002

// RTLD_GLOBAL makes all symbols available for relocation processing of other modules.
// NOTE: Different from macOS (0x8) - Linux uses 0x00100
RTLD_GLOBAL = 0x00100

// RTLD_LOCAL makes symbols not available for relocation processing by other modules.
RTLD_LOCAL = 0x00000
)

// RTLD_DEFAULT is a pseudo-handle for dlsym to search for any loaded symbol.
// NOTE: Different from macOS (1<<64 - 2) - Linux uses 0
const RTLD_DEFAULT = 0x00000
39 changes: 39 additions & 0 deletions internal/dl/dl_linux_cgo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//go:build linux && !android && cgo

package dl

import (
"fmt"

"github.com/go-webgpu/goffi/internal/cbridge"
)

// RTLD_* constants are shared with the !cgo path; see dl_linux_consts.go.

// Dlopen loads a shared library.
func Dlopen(path string, mode int) (uintptr, error) {
handle, errMessage := cbridge.Dlopen(path, mode)
if handle == 0 {
if errMessage == "" {
errMessage = "unknown error"
}
return 0, fmt.Errorf("dlopen failed: %s", errMessage)
}
return handle, nil
}

// Dlsym returns the address of a symbol in a loaded library.
func Dlsym(handle uintptr, name string) (uintptr, error) {
symbol, errMessage := cbridge.Dlsym(handle, name)
if symbol == 0 {
if errMessage == "" {
errMessage = "unknown error"
}
return 0, fmt.Errorf("dlsym failed: %s", errMessage)
}
return symbol, nil
}

// Dlclose intentionally retains process-lifetime mappings, matching the
// existing implementation's semantics.
func Dlclose(uintptr) error { return nil }
35 changes: 35 additions & 0 deletions internal/dl/dl_linux_consts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//go:build linux && !android

// Linux-specific constants for dynamic library loading.
//
// Shared between the no-cgo path (dl_linux.go, which declares the
// cgo_import_dynamic directives) and the cgo path (dl_linux_cgo.go, which
// routes through internal/cbridge). The values are identical across both
// paths; only the resolver mechanism differs.
//
// Calling convention is System V AMD64 ABI on Linux. The dlopen/dlsym API is
// POSIX-standardized.
//
// Reference: https://codebrowser.dev/glibc/glibc/bits/dlfcn.h.html

package dl

// RTLD constants from <dlfcn.h> for dynamic library loading on Linux.
const (
// RTLD_LAZY performs relocations at an implementation-dependent time.
RTLD_LAZY = 0x00001

// RTLD_NOW resolves all symbols when loading the library (recommended).
RTLD_NOW = 0x00002

// RTLD_GLOBAL makes all symbols available for relocation processing of other modules.
// NOTE: Different from macOS (0x8) - Linux uses 0x00100
RTLD_GLOBAL = 0x00100

// RTLD_LOCAL makes symbols not available for relocation processing by other modules.
RTLD_LOCAL = 0x00000
)

// RTLD_DEFAULT is a pseudo-handle for dlsym to search for any loaded symbol.
// NOTE: Different from macOS (1<<64 - 2) - Linux uses 0
const RTLD_DEFAULT = 0x00000
6 changes: 3 additions & 3 deletions internal/dl/dl_stubs_arm64.s
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//go:build ((linux && !android) || darwin || freebsd) && arm64
//go:build ((linux && !android && !cgo) || darwin || freebsd) && arm64

#include "textflag.h"

// JMP stubs to dynamically linked symbols (ARM64)
// These symbols are linked via //go:cgo_import_dynamic in:
// - dl_linux_nocgo.go (Linux: libdl.so.2)
// - dl_darwin_nocgo.go (macOS: libSystem.B.dylib)
// - dl_linux.go (Linux: libdl.so.2)
// - dl_darwin.go (macOS: libSystem.B.dylib)

// dlopen_stub: B to dlopen
TEXT dlopen_stub(SB), NOSPLIT|NOFRAME, $0-0
Expand Down
6 changes: 3 additions & 3 deletions internal/dl/dl_stubs_unix.s
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//go:build (linux || darwin || freebsd) && amd64
//go:build ((linux && !android && !cgo) || darwin || freebsd) && amd64

#include "textflag.h"

// JMP stubs to dynamically linked symbols
// These symbols are linked via //go:cgo_import_dynamic in:
// - dl_linux_nocgo.go (Linux: libdl.so.2)
// - dl_darwin_nocgo.go (macOS: libSystem.B.dylib)
// - dl_linux.go (Linux: libdl.so.2)
// - dl_darwin.go (macOS: libSystem.B.dylib)

// dlopen_stub: JMP to dlopen
TEXT dlopen_stub(SB), NOSPLIT|NOFRAME, $0-0
Expand Down
11 changes: 7 additions & 4 deletions internal/dl/dl_unix.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
//go:build (linux && !android) || darwin || freebsd
//go:build (linux && !android && !cgo) || darwin || freebsd

// OUR OWN Dlopen/Dlsym implementation - NO dependencies!
// Uses runtime.cgocall approach similar to syscall6.
//
// This implementation uses System V AMD64 ABI calling convention, which is
// IDENTICAL on Linux and macOS. Platform-specific constants (RTLD_*) are
// defined in:
// - dl_linux.go (Linux-specific constants)
// - dl_linux_consts.go (Linux-specific constants, shared by cgo and !cgo)
// - dl_darwin.go (macOS-specific constants)
// - dl_freebsd.go (FreeBSD-specific constants)
//
// The assembly wrappers (dl_wrappers_unix.s, dl_stubs_unix.s) and the
// core logic here work identically on both platforms.
// core logic here work identically on Linux, macOS, and FreeBSD (their
// paths; cgo is handled separately in dl_linux_cgo.go).

package dl

Expand All @@ -20,7 +22,8 @@ import (
"unsafe"
)

// RTLD constants are platform-specific - see dl_linux.go and dl_darwin.go
// RTLD constants are platform-specific - see dl_linux_consts.go,
// dl_darwin.go and dl_freebsd.go.

//go:linkname runtime_cgocall runtime.cgocall
//go:noescape
Expand Down
2 changes: 1 addition & 1 deletion internal/syscall/errno_linux.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build linux && !android && (amd64 || arm64)
//go:build linux && !android && !cgo && (amd64 || arm64)

package syscall

Expand Down
10 changes: 10 additions & 0 deletions internal/syscall/errno_linux_cgo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//go:build linux && !android && cgo && (amd64 || arm64)

package syscall

import "github.com/go-webgpu/goffi/internal/cbridge"

// ErrnoFnAddr returns the address of glibc's errno-location function.
func ErrnoFnAddr() uintptr {
return cbridge.ErrnoLocationAddress()
}
2 changes: 1 addition & 1 deletion internal/syscall/errno_stubs_amd64.s
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build ((linux && !android) || darwin || freebsd) && amd64
//go:build ((linux && !android && !cgo) || darwin || freebsd) && amd64

#include "textflag.h"

Expand Down
2 changes: 1 addition & 1 deletion internal/syscall/errno_stubs_arm64.s
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build ((linux && !android) || (android && !cgo) || darwin || freebsd) && arm64
//go:build ((linux && !android && !cgo) || (android && !cgo) || darwin || freebsd) && arm64

#include "textflag.h"

Expand Down
2 changes: 1 addition & 1 deletion internal/syscall/errno_unix.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build ((linux && !android) || (android && !cgo) || darwin || freebsd) && (amd64 || arm64)
//go:build ((linux && !android && !cgo) || (android && !cgo) || darwin || freebsd) && (amd64 || arm64)

package syscall

Expand Down
Loading