cosmopolitan/libc/dlopen/dlopen.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

915 lines
26 KiB
C
Raw Normal View History

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2023 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/atomic.h"
#include "libc/calls/blockcancel.internal.h"
#include "libc/calls/calls.h"
2023-11-12 09:19:04 +00:00
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/cosmo.h"
#include "libc/dce.h"
#include "libc/dlopen/dlfcn.h"
#include "libc/elf/def.h"
#include "libc/elf/elf.h"
#include "libc/elf/scalar.h"
#include "libc/elf/struct/auxv.h"
#include "libc/elf/struct/ehdr.h"
#include "libc/elf/struct/phdr.h"
#include "libc/errno.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/atomic.h"
2023-11-18 12:32:11 +00:00
#include "libc/intrin/kprintf.h"
#include "libc/intrin/strace.h"
#include "libc/limits.h"
#include "libc/nt/dll.h"
#include "libc/nt/enum/filemapflags.h"
#include "libc/nt/enum/pageflags.h"
#include "libc/nt/memory.h"
#include "libc/nt/runtime.h"
#include "libc/proc/posix_spawn.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/syslib.internal.h"
2023-12-06 14:33:50 +00:00
#include "libc/serialize.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/auxv.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/prot.h"
#include "libc/sysv/errfuns.h"
#include "libc/temp.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
/**
* @fileoverview Cosmopolitan Dynamic Linker.
*
* Every program built using Cosmopolitan is statically-linked. However
* there are some cases, e.g. GUIs and video drivers, where linking the
* host platform libraries is desirable. So what we do in such cases is
* launch a stub executable using the host platform's libc, and longjmp
* back into this executable. The stub executable passes back to us the
* platform-specific dlopen() implementation, which we shall then wrap.
*
* @kudos jacereda for figuring out how to do this
*/
#define AMD_REXB 0x41
#define AMD_REXW 0x48
#define AMD_MOV_IMM 0xb8
#define ARM_REG_OFF 0
#define ARM_IMM_OFF 5
#define ARM_IDX_OFF 21
#define ARM_MOV_NEX 0xf2800000u
#define XNU_RTLD_LAZY 1
#define XNU_RTLD_NOW 2
#define XNU_RTLD_LOCAL 4
#define XNU_RTLD_GLOBAL 8
#define HELPER \
"#include <dlfcn.h>\n\
#include <stdio.h>\n\
#include <stdlib.h>\n\
int main(int argc, char **argv, char **envp) {\n\
char *ep;\n\
long addr;\n\
if (argc != 2) {\n\
fprintf(stderr, \"%s: not intended to be run directly\\n\", argv[0]);\n\
return 1;\n\
}\n\
addr = strtol(argv[1], &ep, 10);\n\
if (*ep) {\n\
fprintf(stderr, \"%s: invalid function address\\n\", argv[0]);\n\
return 2;\n\
}\n\
return ((int (*)(void *))addr)((void *[]){\n\
dlopen,\n\
dlsym,\n\
dlclose,\n\
dlerror,\n\
});\n\
}\n"
struct Loaded {
char *base;
char *entry;
Elf64_Ehdr eh;
Elf64_Phdr ph[25];
};
struct {
atomic_uint once;
bool is_supported;
struct CosmoTib *tib;
void *(*dlopen)(const char *, int);
void *(*dlsym)(void *, const char *);
int (*dlclose)(void *);
char *(*dlerror)(void);
jmp_buf jb;
} __foreign;
long __sysv2nt14();
long foreign_tramp();
void __dlopen_lock(void);
void __dlopen_unlock(void);
static _Thread_local char dlerror_buf[128];
static const char *get_tmp_dir(void) {
const char *tmpdir;
if (!(tmpdir = getenv("TMPDIR")) || !*tmpdir)
if (!(tmpdir = getenv("HOME")) || !*tmpdir)
tmpdir = ".";
return tmpdir;
}
static int is_file_newer_than(const char *path, const char *other) {
struct stat st1, st2;
if (stat(path, &st1))
return -1;
if (stat(other, &st2)) {
if (errno == ENOENT) {
2023-11-18 12:32:11 +00:00
return 2;
} else {
return -1;
}
}
return timespec_cmp(st1.st_mtim, st2.st_mtim) > 0;
}
static unsigned elf2prot(unsigned x) {
unsigned r = 0;
if (x & PF_R)
r += PROT_READ;
if (x & PF_W)
r += PROT_WRITE;
if (x & PF_X)
r += PROT_EXEC;
return r;
}
static int get_host_elf_machine(void) {
#ifdef __x86_64__
return EM_NEXGEN32E;
#elif defined(__aarch64__)
return EM_AARCH64;
#elif defined(__powerpc64__)
return EM_PPC64;
#elif defined(__riscv)
return EM_RISCV;
#elif defined(__s390x__)
return EM_S390;
#else
#error "unsupported architecture"
#endif
}
static char *elf_map(int fd, Elf64_Ehdr *ehdr, Elf64_Phdr *phdr, long pagesz,
char *interp_path, size_t interp_size) {
Elf64_Addr maxva = 0;
Elf64_Addr minva = -1;
for (Elf64_Phdr *p = phdr; p < phdr + ehdr->e_phnum; p++) {
if (p->p_type != PT_LOAD)
continue;
if (p->p_vaddr < minva)
minva = p->p_vaddr & -pagesz;
if (p->p_vaddr + p->p_memsz > maxva)
maxva = p->p_vaddr + p->p_memsz;
}
uint8_t *base =
__sys_mmap(0, maxva - minva, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0, 0);
if (base == MAP_FAILED)
return MAP_FAILED;
for (Elf64_Phdr *p = phdr; p < phdr + ehdr->e_phnum; p++) {
if (p->p_type != PT_LOAD) {
if (p->p_type == PT_INTERP && interp_size &&
(p->p_filesz >= interp_size - 1 ||
pread(fd, interp_path, p->p_filesz, p->p_offset) != p->p_filesz))
return MAP_FAILED;
continue;
}
Elf64_Addr skew = p->p_vaddr & (pagesz - 1);
Elf64_Off off = p->p_offset - skew;
Elf64_Addr a = p->p_vaddr + p->p_filesz;
Elf64_Addr b = (a + (pagesz - 1)) & -pagesz;
Elf64_Addr c = p->p_vaddr + p->p_memsz;
int prot2 = elf2prot(p->p_flags);
int prot1 = prot2;
if (b > a) {
prot1 |= PROT_WRITE;
prot1 &= ~PROT_EXEC;
}
if (__sys_mmap(base + p->p_vaddr - skew, skew + p->p_filesz, prot1,
MAP_FIXED | MAP_PRIVATE, fd, off, off) == MAP_FAILED)
return MAP_FAILED;
if (b > a)
bzero(base + a, b - a);
if (c > b && __sys_mmap(base + b, c - b, prot2,
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0,
0) == MAP_FAILED)
return MAP_FAILED;
if (prot1 != prot2 &&
sys_mprotect(base + p->p_vaddr - skew, skew + p->p_filesz, prot2))
return MAP_FAILED;
}
return (void *)base;
}
static bool elf_slurp(struct Loaded *l, int fd, const char *file) {
if (pread(fd, &l->eh, 64, 0) != 64)
return false;
if (!IsElf64Binary(&l->eh, 64) || //
l->eh.e_phnum > sizeof(l->ph) / sizeof(*l->ph) || //
l->eh.e_machine != get_host_elf_machine()) {
enoexec();
return false;
}
int bytes = l->eh.e_phnum * sizeof(l->ph[0]);
if (pread(fd, l->ph, bytes, l->eh.e_phoff) != bytes)
return false;
l->entry = (char *)l->eh.e_entry;
return true;
}
dontinline static bool elf_load(struct Loaded *l, const char *file, long pagesz,
char *interp_path, size_t interp_size) {
int fd;
if ((fd = open(file, O_RDONLY | O_CLOEXEC)) == -1)
return false;
if (!elf_slurp(l, fd, file)) {
close(fd);
return false;
}
if ((l->base = elf_map(fd, &l->eh, l->ph, pagesz, interp_path,
interp_size)) == MAP_FAILED) {
close(fd);
return false;
}
l->entry += (uintptr_t)l->base;
close(fd);
return true;
}
static long *push_strs(long *sp, char **list, int count) {
*--sp = 0;
while (count)
*--sp = (long)list[--count];
return sp;
}
wontreturn dontinstrument static void foreign_helper(void **p) {
__foreign.dlopen = p[0];
__foreign.dlsym = p[1];
__foreign.dlclose = p[2];
__foreign.dlerror = p[3];
_longjmp(__foreign.jb, 1);
}
dontinline static void elf_exec(const char *file, char **envp) {
// get microprocessor page size
2024-07-19 04:02:59 +00:00
long pagesz = __pagesize;
// load helper executable into address space
struct Loaded prog;
char interp_path[256] = {0};
if (!elf_load(&prog, file, pagesz, interp_path, sizeof(interp_path)))
return;
// load platform c library into address space
struct Loaded interp;
if (!elf_load(&interp, interp_path, pagesz, 0, 0))
return;
// count environment variables
int envc = 0;
while (envp[envc])
envc++;
// count auxiliary values
int auxc = 0;
Elf64_auxv_t *av;
for (av = (Elf64_auxv_t *)__auxv; av->a_type; ++av)
auxc++;
// create environment block for embedded process
// the platform libc will save its location for getenv(), etc.
// we need just enough stack memory beneath it for initialization
char *map;
size_t stksize = 65536;
size_t stkalign = 8 * 2;
size_t argsize = (1 + 2 + 1 + envc + 1 + auxc * 2 + 1 + 3) * 8;
size_t mapsize = (stksize + argsize + (pagesz - 1)) & -pagesz;
size_t skew = (mapsize - argsize) & (stkalign - 1);
2023-11-18 12:32:11 +00:00
if (IsFreebsd())
skew += 8; // FreeBSD calls _start() like a C function
map = __sys_mmap(0, mapsize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0, 0);
if (map == MAP_FAILED)
return;
long *sp = (long *)(map + mapsize - skew);
// push argument string
char *address_argument = (char *)(sp -= 3);
FormatInt64(address_argument, (uintptr_t)foreign_helper);
// push auxiliary values
// these tell the platform libc how to load the executable
*--sp = 0;
unsigned long key, val;
for (av = (Elf64_auxv_t *)__auxv; (key = av->a_type); ++av) {
val = av->a_un.a_val;
if (key == AT_PHDR)
val = (long)(prog.base + prog.eh.e_phoff);
if (key == AT_PHENT)
val = prog.eh.e_phentsize;
if (key == AT_PHNUM)
val = prog.eh.e_phnum;
if (key == AT_PAGESZ)
val = pagesz;
if (key == AT_BASE)
val = (long)interp.base;
if (key == AT_FLAGS)
val = 0;
if (key == AT_ENTRY)
val = (long)prog.entry;
if (key == AT_EXECFN)
val = (long)program_invocation_name;
*--sp = val;
*--sp = key;
}
// push environment variable pointers
sp = push_strs(sp, envp, envc);
envp = (char **)sp;
// push argument pointers
*--sp = 0;
*--sp = (long)address_argument;
*--sp = (long)program_invocation_name;
char **argv = (char **)sp;
(void)argv;
int argc = 2;
*--sp = argc;
STRACE("running dlopen importer %p...", interp.entry);
// XXX: ideally we should set most registers to zero
#ifdef __x86_64__
struct ps_strings {
char **argv;
int argc;
char **envp;
int envc;
} pss = {argv, argc, envp, envc};
asm volatile("mov\t%2,%%rsp\n\t"
"jmpq\t*%1"
: /* no outputs */
: "D"(IsFreebsd() ? sp : 0), "S"(interp.entry), "d"(sp),
"b"(IsNetbsd() ? &pss : 0)
: "memory");
__builtin_unreachable();
#elif defined(__aarch64__)
register long x0 asm("x0") = IsFreebsd() ? (long)sp : 0;
register long x9 asm("x9") = (long)sp;
register long x16 asm("x16") = (long)interp.entry;
asm volatile("mov\tsp,x9\n\t"
"br\tx16"
: /* no outputs */
: "r"(x0), "r"(x9), "r"(x16)
: "memory");
__builtin_unreachable();
#else
#error "unsupported architecture"
#endif
}
static char *dlerror_set(const char *str) {
strlcpy(dlerror_buf, str, sizeof(dlerror_buf));
return dlerror_buf;
}
dontinline static char *foreign_alloc_block(void) {
char *p = 0;
size_t sz = 65536;
if (!IsWindows()) {
p = __sys_mmap(0, sz, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_JIT, -1, 0, 0);
if (p == MAP_FAILED)
p = 0;
} else {
uintptr_t h;
if ((h = CreateFileMapping(-1, 0, kNtPageExecuteReadwrite, 0, sz, 0))) {
p = MapViewOfFileEx(h, kNtFileMapWrite | kNtFileMapExecute, 0, 0, sz, 0);
CloseHandle(h);
}
}
if (p) {
WRITE32LE(p, 4); // store used index
} else {
dlerror_set("out of memory");
}
return p;
}
dontinline static void *foreign_alloc(size_t n) {
void *res;
static char *block;
__dlopen_lock();
if (!block || READ32LE(block) + n > 65536)
if (!(block = foreign_alloc_block()))
return 0;
res = block + READ32LE(block);
WRITE32LE(block, READ32LE(block) + n);
__dlopen_unlock();
return res;
}
static uint8_t *movimm(uint8_t p[static 16], int reg, uint64_t val) {
#ifdef __x86_64__
int rex;
rex = AMD_REXW;
if (reg & 8)
rex |= AMD_REXB;
*p++ = rex;
*p++ = AMD_MOV_IMM | (reg & 7);
p = WRITE64LE(p, val);
#elif defined(__aarch64__)
// ARM immediate moves are encoded as:
//
// ┌64-bit
// │
// │┌{sign,???,zero,non}-extending
// ││
// ││ ┌short[4] index
// ││ │
// ││ MOV │ immediate register
// │├┐┌─┴──┐├┐┌──────┴───────┐┌─┴─┐
// 0bmxx100101iivvvvvvvvvvvvvvvvrrrrr
//
// Which allows 16 bits to be loaded into a register at a time, with
// tricks for clearing other parts of the register. For example, the
// sign-extending mode will set the higher order shorts to all ones,
// and it expects the immediate to be encoded using ones' complement
uint32_t op;
for (unsigned i = 0; i < 4; ++i) {
op = ARM_MOV_NEX;
op |= i << ARM_IDX_OFF;
op |= reg << ARM_REG_OFF;
op |= (val & 0xffff) << ARM_IMM_OFF;
val >>= 16;
*(uint32_t *)p = op;
p += sizeof(uint32_t);
}
#else
#error "unsupported architecture"
#endif
return p;
}
static void *foreign_thunk_sysv(void *func) {
uint8_t *code, *p;
#ifdef __x86_64__
// it is no longer needed
if (1)
return func;
// movabs $func,%rax
// movabs $foreign_tramp,%r10
// jmp *%r10
if (!(p = code = foreign_alloc(23)))
return 0; // 10 + 10 + 3 = 23
p = movimm(p, 0, (uintptr_t)func);
p = movimm(p, 10, (uintptr_t)foreign_tramp);
*p++ = 0x41;
*p++ = 0xff;
*p++ = 0xe2;
#elif defined(__aarch64__)
__jit_begin();
if ((p = code = foreign_alloc(36))) {
p = movimm(p, 8, (uintptr_t)func);
p = movimm(p, 10, (uintptr_t)foreign_tramp);
*(uint32_t *)p = 0xd61f0140; // br x10
__clear_cache(code, p + 4);
}
__jit_end();
#else
#error "unsupported architecture"
#endif
return code;
}
static void *foreign_thunk_nt(void *func) {
uint8_t *code;
if (!(code = foreign_alloc(27)))
return 0;
// push %rbp
code[0] = 0x55;
// mov %rsp,%rbp
code[1] = 0x48;
code[2] = 0x89;
code[3] = 0xe5;
// movabs $func,%rax
code[4] = 0x48;
code[5] = 0xb8;
WRITE64LE(code + 6, (uintptr_t)func);
// movabs $tramp,%r10
code[14] = 0x49;
code[15] = 0xba;
#ifdef __x86_64__
WRITE64LE(code + 16, (uintptr_t)__sysv2nt14);
#endif
// jmp *%r10
code[24] = 0x41;
code[25] = 0xff;
code[26] = 0xe2;
return code;
}
dontinline static bool foreign_compile(char exe[hasatleast PATH_MAX]) {
// construct path
strlcpy(exe, get_tmp_dir(), PATH_MAX);
strlcat(exe, "/.cosmo/", PATH_MAX);
if (mkdir(exe, 0755) && errno != EEXIST)
return false;
strlcat(exe, "dlopen-helper", PATH_MAX);
// skip build if helper exists and this program is older
2023-11-18 12:32:11 +00:00
bool helper_exe_exists;
switch (is_file_newer_than(GetProgramExecutableName(), exe)) {
case -1:
return false;
2023-11-18 12:32:11 +00:00
case 0:
return true;
2023-11-18 12:32:11 +00:00
case 1:
helper_exe_exists = true;
break;
case 2:
helper_exe_exists = false;
break;
default:
__builtin_unreachable();
}
// skip build if helper has same source code
int fd;
char src[PATH_MAX];
char sauce[sizeof(HELPER)];
strlcpy(src, exe, PATH_MAX);
strlcat(src, ".c", PATH_MAX);
2023-11-18 12:32:11 +00:00
if (helper_exe_exists) {
if ((fd = open(src, O_RDONLY | O_CLOEXEC)) != -1) {
ssize_t got = pread(fd, sauce, sizeof(HELPER), 0);
close(fd);
if (got == sizeof(HELPER) - 1 &&
!memcmp(sauce, HELPER, sizeof(HELPER) - 1))
2023-11-18 12:32:11 +00:00
return true;
}
}
// create source file
char tmp[PATH_MAX];
strlcpy(tmp, src, PATH_MAX);
strlcat(tmp, ".XXXXXX", PATH_MAX);
if ((fd = mkostemp(tmp, O_CLOEXEC)) == -1)
return false;
if (write(fd, HELPER, sizeof(HELPER) - 1) != sizeof(HELPER) - 1) {
close(fd);
unlink(tmp);
return false;
}
if (close(fd)) {
unlink(tmp);
return false;
}
if (rename(tmp, src)) {
unlink(tmp);
return false;
}
// create executable
strlcpy(tmp, exe, PATH_MAX);
strlcat(tmp, ".XXXXXX", PATH_MAX);
if ((fd = mkostemp(tmp, O_CLOEXEC)) == -1)
return false;
int pid, ws;
2023-11-18 16:08:15 +00:00
char *args[] = {
"cc",
"-pie",
"-fPIC",
src,
"-o",
tmp,
IsLinux() ? "-Wl,-z,execstack" : "-DIGNORE",
IsNetbsd() ? 0 : "-ldl",
0,
2023-11-18 16:08:15 +00:00
};
errno_t err = posix_spawnp(&pid, args[0], NULL, NULL, args, environ);
if (err) {
unlink(tmp);
errno = err;
return false;
}
if (waitpid(pid, &ws, 0) == -1) {
// signals and cancelation are blocked
// therefore this must be a real error
unlink(tmp);
return false;
}
if (ws) {
unlink(tmp);
return false;
}
if (rename(tmp, exe)) {
unlink(tmp);
return false;
}
return true;
}
static bool foreign_setup(void) {
// geth path of helper executable
char exe[PATH_MAX];
if (!foreign_compile(exe)) {
return false;
}
// load helper executable into address space
#ifdef __x86_64__
struct CosmoTib *cosmo_tib = __get_tls();
#endif
if (!setjmp(__foreign.jb)) {
elf_exec(exe, environ);
return false; // if elf_exec() returns, it failed
}
#ifdef __x86_64__
__foreign.tib = __get_tls();
__set_tls(cosmo_tib);
#endif
__foreign.dlopen = foreign_thunk_sysv(__foreign.dlopen);
__foreign.dlsym = foreign_thunk_sysv(__foreign.dlsym);
__foreign.dlclose = foreign_thunk_sysv(__foreign.dlclose);
__foreign.dlerror = foreign_thunk_sysv(__foreign.dlerror);
__foreign.is_supported = true;
return true;
}
static void foreign_once(void) {
foreign_setup();
}
static bool foreign_init(void) {
bool res;
cosmo_once(&__foreign.once, foreign_once);
if (!(res = __foreign.is_supported))
dlerror_set("dlopen() isn't supported on this platform");
return res;
}
static int dlclose_nt(void *handle) {
int res;
if (FreeLibrary((uintptr_t)handle)) {
res = 0;
} else {
dlerror_set("FreeLibrary() failed");
res = -1;
}
return res;
}
static void *dlopen_nt(const char *path, int mode) {
int n;
uintptr_t handle;
char16_t path16[PATH_MAX + 2];
if (mode & ~(RTLD_LOCAL | RTLD_LAZY | RTLD_NOW)) {
dlerror_set("invalid mode");
return 0;
}
if ((n = __mkntpath(path, path16)) == -1) {
dlerror_set("path invalid");
return 0;
}
if (n > 3 && //
path16[n - 3] == '.' && //
path16[n - 2] == 's' && //
path16[n - 1] == 'o') {
path16[n - 2] = 'd';
path16[n - 1] = 'l';
path16[n + 0] = 'l';
path16[n + 1] = 0;
}
if (!(handle = LoadLibrary(path16)))
dlerror_set("library not found");
return (void *)handle;
}
static void *dlsym_nt(void *handle, const char *name) {
void *x64_abi_func;
if ((x64_abi_func = GetProcAddress((uintptr_t)handle, name))) {
2024-01-07 10:14:28 +00:00
return x64_abi_func;
} else {
dlerror_set("symbol not found: ");
strlcat(dlerror_buf, name, sizeof(dlerror_buf));
2024-01-07 10:14:28 +00:00
return 0;
}
}
static void *dlopen_silicon(const char *path, int mode) {
int n;
int xnu_mode = 0;
char path2[PATH_MAX + 5];
if (mode & ~(RTLD_LOCAL | RTLD_LAZY | RTLD_NOW | RTLD_GLOBAL))
xnu_mode = -1; // punt error to system dlerror() impl
if (!(mode & RTLD_GLOBAL))
xnu_mode |= XNU_RTLD_LOCAL; // unlike Linux, XNU defaults to RTLD_GLOBAL
if (mode & RTLD_NOW)
xnu_mode |= XNU_RTLD_NOW;
if (mode & RTLD_LAZY)
xnu_mode |= XNU_RTLD_LAZY;
if ((n = strlen(path)) < PATH_MAX && n > 3 && //
path[n - 3] == '.' && //
path[n - 2] == 's' && //
path[n - 1] == 'o') {
memcpy(path2, path, n);
path2[n - 2] = 'd';
path2[n - 1] = 'y';
path2[n + 0] = 'l';
path2[n + 1] = 'i';
path2[n + 2] = 'b';
path2[n + 3] = 0;
path = path2;
}
return __syslib->__dlopen(path, xnu_mode);
}
/**
* Opens dynamic shared object using host platform libc.
*
* If a `path` ending with `.so` is passed on Windows or MacOS, then
* this wrapper will automatically change it to `.dll` or `.dylib` to
* increase its chance of successfully loading.
*
2023-12-06 14:33:50 +00:00
* WARNING: Our API uses a different naming because cosmo_dlopen() lacks
* many of the features one would reasonably expect from a UNIX dlopen()
* implementation; and we don't want to lead ./configure scripts astray.
* Foreign libraries also can't link symbols defined by your executable,
* which means using this for high-level language plugins is completely
* out of the question. What cosmo_dlopen() can do is help you talk to
* GPU and GUI libraries like CUDA and SDL.
2023-11-12 09:19:04 +00:00
*
* @param mode is a bitmask that can contain:
* - `RTLD_LOCAL` (default)
* - `RTLD_GLOBAL` (not supported on Windows)
* - `RTLD_LAZY`
* - `RTLD_NOW`
* @return dso handle, or NULL w/ dlerror()
2023-12-06 14:33:50 +00:00
* @note this non-standard API is feature gated; you need to pass the
* `-mcosmo` flag to `cosmocc` so that `<dlfcn.h>` will define it
*/
2023-11-12 09:19:04 +00:00
void *cosmo_dlopen(const char *path, int mode) {
void *res;
BLOCK_SIGNALS;
BLOCK_CANCELATION;
if (IsWindows()) {
res = dlopen_nt(path, mode);
} else if (IsXnuSilicon()) {
res = dlopen_silicon(path, mode);
} else if (IsXnu()) {
dlerror_set("dlopen() isn't supported on x86-64 MacOS");
res = 0;
2023-12-06 14:33:50 +00:00
} else if (IsOpenbsd()) {
// TODO(jart): implement workaround for msyscall() dilemma
dlerror_set("dlopen() isn't supported on OpenBSD yet");
res = 0;
} else if (foreign_init()) {
res = __foreign.dlopen(path, mode);
} else {
res = 0;
}
ALLOW_CANCELATION;
ALLOW_SIGNALS;
STRACE("cosmo_dlopen(%#s, %d) → %p% m", path, mode, res);
return res;
}
/**
* Obtains address of symbol from dynamic shared object.
*
* WARNING: You almost always want to say this:
*
* pFunction = cosmo_dltramp(cosmo_dlsym(dso, "function"));
*
* That will generate code at runtime for automatically translating to
* Microsoft's x64 calling convention when appropriate. However the
* automated solution doesn't always work. For example, the prototype:
*
* void func(int, float);
*
* Won't be translated correctly, due to the differences in ABI. We're
* able to smooth over most of them, but that's just one of several
* examples where we can't. A good rule of thumb is:
*
* - More than four float/double args is problematic
* - Having both integral and floating point parameters is bad
*
* For those kinds of functions, you need to translate the ABI by hand.
* This can be accomplished using the GCC `__ms_abi__` attribute, where
* you'd have two function pointer types branched upon `IsWindows()`.
*
* @param handle was opened by dlopen()
* @return address of symbol, or NULL w/ dlerror()
*/
2023-11-12 09:19:04 +00:00
void *cosmo_dlsym(void *handle, const char *name) {
void *func;
if (IsWindows()) {
func = dlsym_nt(handle, name);
} else if (IsXnuSilicon()) {
func = __syslib->__dlsym(handle, name);
} else if (IsXnu()) {
dlerror_set("dlopen() isn't supported on x86-64 MacOS");
func = 0;
} else if (foreign_init()) {
func = __foreign.dlsym(handle, name);
} else {
func = 0;
}
STRACE("cosmo_dlsym(%p, %#s) → %p", handle, name, func);
return func;
}
2024-01-05 17:11:51 +00:00
/**
* Trampolines foreign function pointer so it can be called safely.
*/
void *cosmo_dltramp(void *foreign_func) {
if (!IsWindows()) {
return foreign_thunk_sysv(foreign_func);
} else {
return foreign_thunk_nt(foreign_func);
}
}
/**
* Closes dynamic shared object.
*
* @param handle was opened by dlopen()
* @return 0 on success, or -1 w/ dlerror()
*/
2023-11-12 09:19:04 +00:00
int cosmo_dlclose(void *handle) {
int res;
if (IsWindows()) {
res = dlclose_nt(handle);
} else if (IsXnuSilicon()) {
res = __syslib->__dlclose(handle);
} else if (IsXnu()) {
dlerror_set("dlopen() isn't supported on x86-64 MacOS");
res = -1;
} else if (foreign_init()) {
res = __foreign.dlclose(handle);
} else {
res = -1;
}
STRACE("cosmo_dlclose(%p) → %d", handle, res);
return res;
}
/**
* Returns string describing last dlopen/dlsym/dlclose error.
*/
2023-11-12 09:19:04 +00:00
char *cosmo_dlerror(void) {
char *res;
if (IsXnuSilicon()) {
res = __syslib->__dlerror();
} else if (IsWindows() || IsXnu()) {
res = dlerror_buf;
} else if (foreign_init()) {
res = __foreign.dlerror();
res = dlerror_set(res);
} else {
res = dlerror_buf;
}
STRACE("cosmo_dlerror() → %#s", res);
return res;
}