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

Thread-safe library initialization #37

Merged
merged 33 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
62afdd2
Thread-safe initialization.
yugr Jan 18, 2025
cb534a1
FreeBSD fixes.
yugr Jan 18, 2025
1e39bf6
Update README.
yugr Jan 18, 2025
b3ba7e9
Remove volatile.
yugr Jan 18, 2025
d4b6bf5
Disable thread test on mipsel (can not repro fail locally).
yugr Jan 18, 2025
dabd5ca
Various test improvements.
yugr Jan 19, 2025
783237e
Enable on 32-bit MIPS.
yugr Jan 19, 2025
c16fa49
Print result in threads test.
yugr Jan 19, 2025
fc01eaf
Updated comment.
yugr Jan 19, 2025
8f5a484
Fix memory ordering.
yugr Jan 19, 2025
f72abbd
Add explicit void in prototypes.
yugr Jan 20, 2025
ba3672a
Fix race condition between address resolution and library global ctor.
yugr Jan 20, 2025
b1136c0
Fixed test comments
yugr Jan 20, 2025
d0de4eb
Added script for fuzz testing via unthread library.
yugr Jan 20, 2025
8788cd6
Fixed Tsan test and fix harmless data race.
yugr Jan 20, 2025
e75def0
Added another threading test with deep recursion in library ctor.
yugr Jan 20, 2025
bb902d3
Fix Tsan checks.
yugr Jan 20, 2025
05d9492
Fix Tsan checks.
yugr Jan 20, 2025
a3d9d58
Updated todo.
yugr Jan 21, 2025
6d5c357
Remove redundant barrier.
yugr Jan 22, 2025
58aebdb
Added simple TLA+ model of Implib.so initialization code.
yugr Jan 22, 2025
3cff3c2
Minor fixes in tests.
yugr Jan 22, 2025
34ac3fd
Minor spec fix.
yugr Jan 23, 2025
8fcffdf
Update spec README.
yugr Jan 23, 2025
78938c7
Minor fixes in TLA spec.
yugr Jan 23, 2025
a234adb
Added Promela initialization model.
yugr Jan 23, 2025
4843208
Get rid of widths in Promela model.
yugr Jan 23, 2025
a44d6bb
Added complaints about Promela.
yugr Jan 24, 2025
f0ff27a
Added todo.
yugr Jan 24, 2025
39ec517
Minor Promela updates.
yugr Jan 24, 2025
eab849c
More automated verification driver.
yugr Jan 24, 2025
65cb1c6
Rename variable.
yugr Jan 24, 2025
fc15374
Added comment in spec.
yugr Jan 24, 2025
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
4 changes: 2 additions & 2 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
85 changes: 66 additions & 19 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 volatile 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,14 +111,13 @@ static void *load_library() {
#endif

do_dlclose = 1;
is_lib_loading = 0;

return lib_handle;
unlock();
}

static void __attribute__((destructor)) unload_lib() {
if(do_dlclose && lib_handle)
dlclose(lib_handle);
dlclose((void *)lib_handle);
}
#endif

Expand All @@ -93,14 +141,12 @@ 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.
if (lib_handle) {
// User has specified loaded library
h = lib_handle;
h = (void *)lib_handle;
} else {
// User hasn't provided us the loaded library so search the global namespace.
# ifndef IMPLIB_EXPORT_SHIMS
Expand All @@ -113,7 +159,8 @@ void _${lib_suffix}_tramp_resolve(int i) {
# endif
}
#else
h = load_library();
load_library();
h = (void *)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
1 change: 1 addition & 0 deletions scripts/travis.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ if ! echo "$ARCH" | grep -q 'powerpc\|mips\|riscv'; then
# TODO: support vector types for remaining platforms
tests/vector-args/run.sh $ARCH
fi
tests/thread/run.sh $ARCH
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