Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thead-safe library initialization #37

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
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
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2017-2024 Yury Gribov
Copyright (c) 2017-2025 Yury Gribov

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ where `TARGET` can be any of
Script generates two files: `libxyz.so.tramp.S` and `libxyz.so.init.c` which need to be linked to your application (instead of `-lxyz`):

```
$ gcc myfile1.c myfile2.c ... libxyz.so.tramp.S libxyz.so.init.c ... -ldl
$ gcc myfile1.c myfile2.c ... libxyz.so.tramp.S libxyz.so.init.c ... -ldl -pthread
```

Note that you need to link against libdl.so. On ARM in case your app is compiled to Thumb code (which e.g. Ubuntu's `arm-linux-gnueabihf-gcc` does by default) you'll also need to add `-mthumb-interwork`.
Note that you need to link against libdl.so and libpthread.so (unless you disable thread safety via `--no-thread-safe`). On ARM in case your app is compiled to Thumb code (which e.g. Ubuntu's `arm-linux-gnueabihf-gcc` does by default) you'll also need to add `-mthumb-interwork`.

Application can then freely call functions from `libxyz.so` _without linking to it_. Library will be loaded (via `dlopen`) on first call to any of its functions. If you want to forcedly resolve all symbols (e.g. if you want to avoid delays further on) you can call `void libxyz_init_all()`.

Expand Down Expand Up @@ -142,7 +142,6 @@ The tool does not transparently support all features of POSIX shared libraries.
* it may change semantics because shared library constructors are delayed until when library is loaded

The tool also lacks the following important features:
* proper support for multi-threading
* symbol versions are not handled at all
* keep fast paths of shims together to reduce I$ pressure
* support for macOS and BSDs (actually BSDs mostly work)
Expand Down
79 changes: 63 additions & 16 deletions arch/common/init.c.tpl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2022 Yury Gribov
* Copyright 2018-2025 Yury Gribov
*
* The MIT License (MIT)
*
Expand All @@ -11,12 +11,22 @@
#define _GNU_SOURCE // For RTLD_DEFAULT
#endif

#define HAS_DLOPEN_CALLBACK $has_dlopen_callback
#define HAS_DLSYM_CALLBACK $has_dlsym_callback
#define NO_DLOPEN $no_dlopen
#define LAZY_LOAD $lazy_load
#define THREAD_SAFE $thread_safe

#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>

#if THREAD_SAFE
#include <pthread.h>
#endif

// Sanity check for ARM to avoid puzzling runtime crashes
#ifdef __arm__
# if defined __thumb__ && ! defined __THUMB_INTERWORK__
Expand All @@ -36,21 +46,60 @@ extern "C" {
} \
} while(0)

#define HAS_DLOPEN_CALLBACK $has_dlopen_callback
#define HAS_DLSYM_CALLBACK $has_dlsym_callback
#define NO_DLOPEN $no_dlopen
#define LAZY_LOAD $lazy_load

static void *lib_handle;
static int do_dlclose;
static int is_lib_loading;

#if ! NO_DLOPEN
static void *load_library() {
if(lib_handle)
return lib_handle;

is_lib_loading = 1;
#if THREAD_SAFE

// We need to consider two cases:
// - different threads calling intercepted APIs in parallel
// - same thread calling 2 intercepted APIs recursively
// due to dlopen calling library constructors
// (only if IMPLIB_EXPORT_SHIMS is used)

static pthread_mutex_t mtx;

static void init_lock() {
// We need recursive lock because dlopen will call library constructors
// which may call other intercepted APIs that will call load_library again.
// PTHREAD_RECURSIVE_MUTEX_INITIALIZER is not portable
// so we do it hard way.

pthread_mutexattr_t attr;
CHECK(0 == pthread_mutexattr_init(&attr), "failed to init mutex");
CHECK(0 == pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE), "failed to init mutex");

CHECK(0 == pthread_mutex_init(&mtx, &attr), "failed to init mutex");
}

static void lock() {
static pthread_once_t once = PTHREAD_ONCE_INIT;
CHECK(0 == pthread_once(&once, init_lock), "failed to init lock");

CHECK(0 == pthread_mutex_lock(&mtx), "failed to lock mutex");
}

static void unlock() {
CHECK(0 == pthread_mutex_unlock(&mtx), "failed to unlock mutex");
}
#else
static void lock() {}
static void unlock() {}
#endif

static void load_library() {
lock();

if (lib_handle) {
unlock();
return;
}

// With (non-default) IMPLIB_EXPORT_SHIMS we may call dlopen more than once,
// not sure if this is a problem. We could fix this with first checking
// with RTLD_NOLOAD?

#if HAS_DLOPEN_CALLBACK
extern void *$dlopen_callback(const char *lib_name);
Expand All @@ -62,9 +111,8 @@ static void *load_library() {
#endif

do_dlclose = 1;
is_lib_loading = 0;

return lib_handle;
unlock();
}

static void __attribute__((destructor)) unload_lib() {
Expand Down Expand Up @@ -93,8 +141,6 @@ extern void *_${lib_suffix}_tramp_table[];
void _${lib_suffix}_tramp_resolve(int i) {
assert((unsigned)i < SYM_COUNT);

CHECK(!is_lib_loading, "library function '%s' called during library load", sym_names[i]);

void *h = 0;
#if NO_DLOPEN
// Library with implementations must have already been loaded.
Expand All @@ -113,7 +159,8 @@ void _${lib_suffix}_tramp_resolve(int i) {
# endif
}
#else
h = load_library();
load_library();
h = lib_handle;
CHECK(h, "failed to resolve symbol '%s', library failed to load", sym_names[i]);
#endif

Expand Down
10 changes: 9 additions & 1 deletion implib-gen.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3

# Copyright 2017-2024 Yury Gribov
# Copyright 2017-2025 Yury Gribov
#
# The MIT License (MIT)
#
Expand Down Expand Up @@ -382,6 +382,12 @@ def main():
parser.add_argument('--no-lazy-load',
help="Load library at program start",
dest='lazy_load', action='store_false')
parser.add_argument('--thread-safe',
help="Ensure thread-safety (default)",
dest='thread_safe', action='store_true', default=True)
parser.add_argument('--no-thread-safe',
help="Do not ensure thread-safety",
dest='thread_safe', action='store_false')
parser.add_argument('--vtables',
help="Intercept virtual tables (EXPERIMENTAL)",
dest='vtables', action='store_true', default=False)
Expand Down Expand Up @@ -419,6 +425,7 @@ def main():
dlsym_callback = args.dlsym_callback
dlopen = args.dlopen
lazy_load = args.lazy_load
thread_safe = args.thread_safe
if args.target.startswith('arm'):
target = 'arm' # Handle armhf-..., armel-...
elif re.match(r'^i[0-9]86', args.target):
Expand Down Expand Up @@ -617,6 +624,7 @@ def is_data_symbol(s):
has_dlsym_callback=int(bool(dlsym_callback)),
no_dlopen=int(not dlopen),
lazy_load=int(lazy_load),
thread_safe=int(thread_safe),
sym_names=sym_names)
f.write(init_text)
if args.vtables:
Expand Down
1 change: 1 addition & 0 deletions scripts/ld
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ Flags can be specified directly or via IMPLIBSO_LD_OPTIONS environment variable.
info.replaced = True
if changed:
args.append('-ldl')
args.append('-lpthread')

for name, info in sorted(libs.items()):
if not info.replaced:
Expand Down
4 changes: 4 additions & 0 deletions scripts/travis.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ if ! echo "$ARCH" | grep -q 'powerpc\|mips\|riscv'; then
# TODO: support vector types for remaining platforms
tests/vector-args/run.sh $ARCH
fi
if ! echo "$ARCH" | grep -q 'mipsel'; then
# TODO: investigate mipsel (can't repro locally...)
tests/thread/run.sh $ARCH
fi
4 changes: 2 additions & 2 deletions tests/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ else
fi

if uname | grep -q BSD; then
LIBS=
LIBS='-pthread'
else
LIBS=-ldl
LIBS='-ldl -pthread'
fi
61 changes: 61 additions & 0 deletions tests/thread/interposed.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2025 Yury Gribov
*
* The MIT License (MIT)
*
* Use of this source code is governed by MIT license that can be
* found in the LICENSE.txt file.
*/

#include <stdio.h>
#include "interposed.h"

__attribute__((visibility("default")))
int foo0(int x) {
return x + 0;
}

__attribute__((visibility("default")))
int foo1(int x) {
return x + 1;
}

__attribute__((visibility("default")))
int foo2(int x) {
return x + 2;
}

__attribute__((visibility("default")))
int foo3(int x) {
return x + 3;
}

__attribute__((visibility("default")))
int foo4(int x) {
return x + 4;
}

__attribute__((visibility("default")))
int foo5(int x) {
return x + 5;
}

__attribute__((visibility("default")))
int foo6(int x) {
return x + 6;
}

__attribute__((visibility("default")))
int foo7(int x) {
return x + 7;
}

__attribute__((visibility("default")))
int foo8(int x) {
return x + 8;
}

__attribute__((visibility("default")))
int foo9(int x) {
return x + 9;
}
24 changes: 24 additions & 0 deletions tests/thread/interposed.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2025 Yury Gribov
*
* The MIT License (MIT)
*
* Use of this source code is governed by MIT license that can be
* found in the LICENSE.txt file.
*/

#ifndef INTERPOSED_H
#define INTERPOSED_H

extern int foo0(int x);
extern int foo1(int x);
extern int foo2(int x);
extern int foo3(int x);
extern int foo4(int x);
extern int foo5(int x);
extern int foo6(int x);
extern int foo7(int x);
extern int foo8(int x);
extern int foo9(int x);

#endif
Loading
Loading