Skip to content

Commit 2063c65

Browse files
authored
Prevent potential race in thread startup and cleanup dead code (#399)
1 parent be8ec0d commit 2063c65

10 files changed

Lines changed: 95 additions & 851 deletions

File tree

ddprof-lib/src/main/cpp/buffers.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ class Buffer {
146146
v >>= 7;
147147
}
148148
_data[_offset++] = (char)v;
149+
assert(_offset < limit());
149150
}
150151

151152
#ifdef __aarch64__
@@ -170,6 +171,7 @@ class Buffer {
170171
v >>= 7;
171172
}
172173
_data[_offset++] = (char)v;
174+
assert(_offset < limit());
173175
}
174176

175177
// the trickery of RecordingBuffer extending Buffer::_data array may trip off asan
@@ -205,6 +207,7 @@ class Buffer {
205207
__attribute__((no_sanitize("bounds")))
206208
#endif
207209
void putVar32(int offset, u32 v) {
210+
assert(offset + 4 < limit());
208211
_data[offset] = v | 0x80;
209212
_data[offset + 1] = (v >> 7) | 0x80;
210213
_data[offset + 2] = (v >> 14) | 0x80;

ddprof-lib/src/main/cpp/guards.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ class SignalBlocker {
125125
sigaddset(&prof_signals, SIGPROF); // Used by ITimer and CTimer
126126
sigaddset(&prof_signals, SIGVTALRM); // Used by WallClock
127127
#ifdef __linux__
128-
// Block RT signals used by TLS priming and potentially other profilers (Linux-only)
128+
// Block RT signals (Linux-only)
129129
// This prevents any RT signal handler from interrupting TLS initialization
130130
for (int sig = SIGRTMIN; sig <= SIGRTMIN + 5 && sig <= SIGRTMAX; sig++) {
131131
sigaddset(&prof_signals, sig);

ddprof-lib/src/main/cpp/libraryPatcher_linux.cpp

Lines changed: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#ifdef __linux__
44
#include "profiler.h"
55
#include "vmStructs.h"
6+
#include "guards.h"
67

78
#include <cassert>
89
#include <dlfcn.h>
@@ -31,7 +32,7 @@ void LibraryPatcher::initialize() {
3132
class RoutineInfo {
3233
private:
3334
func_start_routine _routine;
34-
void* const _args;
35+
void* _args;
3536
public:
3637
RoutineInfo(func_start_routine routine, void* args) :
3738
_routine(routine), _args(args) {
@@ -41,23 +42,40 @@ class RoutineInfo {
4142
return _routine;
4243
}
4344

44-
void* const args() const {
45+
void* args() const {
4546
return _args;
4647
}
4748
};
4849

50+
#ifdef __aarch64__
51+
// Initialize the current thread's TLS with profiling signals blocked.
52+
// Kept in a separate noinline function so that SignalBlocker's 128-byte
53+
// sigset_t lives in its own frame and does not trigger stack-protector
54+
// canary placement in start_routine_wrapper on aarch64.
55+
__attribute__((noinline))
56+
static void init_thread_tls() {
57+
// Block profiling signals during TLS initialization: TLS init
58+
// (pthread_once, pthread_key_create, pthread_setspecific) is not
59+
// async-signal-safe. A profiling signal here can corrupt
60+
// internal TLS bookkeeping on the stack.
61+
SignalBlocker blocker;
62+
ProfiledThread::initCurrentThread();
63+
}
64+
4965
// Wrapper around the real start routine.
5066
// The wrapper:
5167
// 1. Register the newly created thread to profiler
5268
// 2. Call real start routine
5369
// 3. Unregister the thread from profiler once the routine is completed.
54-
static void* start_routine_wrapper(void* args) {
70+
// This version is to workaround a precarious stack guard corruption,
71+
// which only happens in Linux/musl/aarch64/jdk11
72+
__attribute__((visibility("hidden")))
73+
static void* start_routine_wrapper_spec(void* args) {
5574
RoutineInfo* thr = (RoutineInfo*)args;
5675
func_start_routine routine = thr->routine();
57-
void* const params = thr->args();
76+
void* params = thr->args();
5877
delete thr;
59-
60-
ProfiledThread::initCurrentThread();
78+
init_thread_tls();
6179
int tid = ProfiledThread::currentTid();
6280
Profiler::registerThread(tid);
6381
void* result = routine(params);
@@ -66,12 +84,59 @@ static void* start_routine_wrapper(void* args) {
6684
return result;
6785
}
6886

87+
static int pthread_create_hook_spec(pthread_t* thread,
88+
const pthread_attr_t* attr,
89+
func_start_routine start_routine,
90+
void* args) {
91+
RoutineInfo* thr = new RoutineInfo(start_routine, args);
92+
int ret = pthread_create(thread, attr, start_routine_wrapper_spec, (void*)thr);
93+
if (ret != 0) delete thr;
94+
return ret;
95+
}
96+
97+
#endif // __aarch64__
98+
99+
static void thread_cleanup(void* arg) {
100+
int tid = *static_cast<int*>(arg);
101+
Profiler::unregisterThread(tid);
102+
ProfiledThread::release();
103+
}
104+
105+
// Wrapper around the real start routine.
106+
// See comments for start_routine_wrapper_spec() for details
107+
__attribute__((visibility("hidden")))
108+
static void* start_routine_wrapper(void* args) {
109+
RoutineInfo* thr = (RoutineInfo*)args;
110+
func_start_routine routine = thr->routine();
111+
void* params = thr->args();
112+
delete thr;
113+
{
114+
// Block profiling signals during TLS initialization: TLS init
115+
// (pthread_once, pthread_key_create, pthread_setspecific) is not
116+
// async-signal-safe. A profiling signal here can corrupt
117+
// internal TLS bookkeeping on the stack.
118+
SignalBlocker blocker;
119+
ProfiledThread::initCurrentThread();
120+
}
121+
int tid = ProfiledThread::currentTid();
122+
Profiler::registerThread(tid);
123+
void* result = nullptr;
124+
// Handle pthread_exit() bypass - the thread calls pthread_exit()
125+
// instead of normal termination
126+
pthread_cleanup_push(thread_cleanup, &tid);
127+
result = routine(params);
128+
pthread_cleanup_pop(1);
129+
return result;
130+
}
131+
69132
static int pthread_create_hook(pthread_t* thread,
70133
const pthread_attr_t* attr,
71134
func_start_routine start_routine,
72135
void* args) {
73136
RoutineInfo* thr = new RoutineInfo(start_routine, args);
74-
return pthread_create(thread, attr, start_routine_wrapper, (void*)thr);
137+
int ret = pthread_create(thread, attr, start_routine_wrapper, (void*)thr);
138+
if (ret != 0) delete thr;
139+
return ret;
75140
}
76141

77142
void LibraryPatcher::patch_libraries() {
@@ -105,10 +170,18 @@ void LibraryPatcher::patch_library_unlocked(CodeCache* lib) {
105170
}
106171
}
107172
TEST_LOG("Patching: %s", lib->name());
173+
void* func = (void*)pthread_create_hook;
174+
175+
#ifdef __aarch64__
176+
// Workaround stack guard corruption in Linux/aarch64/musl/jdk11
177+
if (VM::isHotspot() && OS::isMusl() && VM::java_version() == 11) {
178+
func = (void*)pthread_create_hook_spec;
179+
}
180+
#endif
108181
_patched_entries[_size]._lib = lib;
109182
_patched_entries[_size]._location = pthread_create_location;
110183
_patched_entries[_size]._func = (void*)__atomic_load_n(pthread_create_location, __ATOMIC_RELAXED);
111-
__atomic_store_n(pthread_create_location, (void*)pthread_create_hook, __ATOMIC_RELAXED);
184+
__atomic_store_n(pthread_create_location, func, __ATOMIC_RELAXED);
112185
_size++;
113186
}
114187

ddprof-lib/src/main/cpp/os.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -159,14 +159,6 @@ class OS {
159159

160160
static int truncateFile(int fd);
161161
static void mallocArenaMax(int arena_max);
162-
163-
// TLS priming support
164-
static bool isTlsPrimingAvailable();
165-
static int installTlsPrimeSignalHandler(SigHandler handler, int signal_offset = 4);
166-
static void uninstallTlsPrimeSignalHandler(int signal_num);
167-
static void enumerateThreadIds(const std::function<void(int)>& callback);
168-
static void signalThread(int tid, int signum);
169-
static int getThreadCount();
170162
};
171163

172164
#endif // _OS_H

ddprof-lib/src/main/cpp/os_linux.cpp

Lines changed: 0 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -726,91 +726,4 @@ SigAction OS::replaceSigbusHandler(SigAction action) {
726726
return old_action;
727727
}
728728

729-
bool OS::isTlsPrimingAvailable() {
730-
return true; // TLS priming supported on Linux
731-
}
732-
733-
int OS::installTlsPrimeSignalHandler(SigHandler handler, int signal_offset) {
734-
int signal_num = SIGRTMIN + signal_offset;
735-
if (signal_num > SIGRTMAX) {
736-
TEST_LOG("No available RT signal for TLS priming: offset %d exceeds range (SIGRTMIN=%d, SIGRTMAX=%d)",
737-
signal_offset, SIGRTMIN, SIGRTMAX);
738-
return -1;
739-
}
740-
741-
struct sigaction sa;
742-
sa.sa_handler = handler;
743-
sigemptyset(&sa.sa_mask);
744-
sa.sa_flags = SA_RESTART;
745-
746-
if (sigaction(signal_num, &sa, NULL) != 0) {
747-
TEST_LOG("Failed to install RT signal %d for TLS priming: %s (signal may be in use)",
748-
signal_num, strerror(errno));
749-
return -1;
750-
}
751-
752-
TEST_LOG("Successfully installed TLS priming handler on RT signal %d", signal_num);
753-
return signal_num;
754-
}
755-
756-
void OS::uninstallTlsPrimeSignalHandler(int signal_num) {
757-
if (signal_num <= 0) {
758-
return;
759-
}
760-
761-
struct sigaction sa;
762-
sa.sa_handler = SIG_DFL;
763-
sigemptyset(&sa.sa_mask);
764-
sa.sa_flags = 0;
765-
766-
if (sigaction(signal_num, &sa, NULL) != 0) {
767-
TEST_LOG("Failed to uninstall RT signal %d for TLS priming: %s",
768-
signal_num, strerror(errno));
769-
} else {
770-
TEST_LOG("Successfully uninstalled TLS priming handler for RT signal %d", signal_num);
771-
}
772-
}
773-
774-
void OS::enumerateThreadIds(const std::function<void(int)>& callback) {
775-
DIR* task_dir = opendir("/proc/self/task");
776-
if (!task_dir) {
777-
TEST_LOG("Failed to open /proc/self/task: %s", strerror(errno));
778-
return;
779-
}
780-
781-
struct dirent* entry;
782-
while ((entry = readdir(task_dir)) != nullptr) {
783-
if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9') {
784-
int tid = atoi(entry->d_name);
785-
if (tid > 0) {
786-
callback(tid);
787-
}
788-
}
789-
}
790-
closedir(task_dir);
791-
}
792-
793-
void OS::signalThread(int tid, int signum) {
794-
if (syscall(SYS_tgkill, getpid(), tid, signum) != 0 && errno != ESRCH) {
795-
TEST_LOG("Failed to signal thread %d with signal %d: %s", tid, signum, strerror(errno));
796-
}
797-
}
798-
799-
int OS::getThreadCount() {
800-
FILE* status = fopen("/proc/self/status", "r");
801-
if (!status) {
802-
return -1;
803-
}
804-
805-
char line[256];
806-
int thread_count = -1;
807-
while (fgets(line, sizeof(line), status)) {
808-
if (sscanf(line, "Threads:\t%d", &thread_count) == 1) {
809-
break;
810-
}
811-
}
812-
fclose(status);
813-
return thread_count;
814-
}
815-
816729
#endif // __linux__

ddprof-lib/src/main/cpp/os_macos.cpp

Lines changed: 0 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -484,66 +484,4 @@ SigAction OS::replaceSigsegvHandler(SigAction action) {
484484
return old_action;
485485
}
486486

487-
bool OS::isTlsPrimingAvailable() {
488-
return false; // TLS priming disabled on macOS
489-
}
490-
491-
int OS::installTlsPrimeSignalHandler(SigHandler handler, int signal_offset) {
492-
return -1; // TLS priming not supported on macOS
493-
}
494-
495-
void OS::uninstallTlsPrimeSignalHandler(int signal_num) {
496-
// No-op on macOS since TLS priming is not supported
497-
}
498-
499-
void OS::enumerateThreadIds(const std::function<void(int)>& callback) {
500-
// Get current task
501-
task_t task = mach_task_self();
502-
503-
// Get thread list
504-
thread_act_array_t thread_list;
505-
mach_msg_type_number_t thread_count;
506-
507-
kern_return_t kr = task_threads(task, &thread_list, &thread_count);
508-
if (kr != KERN_SUCCESS) {
509-
TEST_LOG("Failed to get thread list: %d", kr);
510-
return;
511-
}
512-
513-
// Call callback for each thread
514-
for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
515-
callback(static_cast<int>(thread_list[i]));
516-
}
517-
518-
// Clean up
519-
vm_deallocate(task, (vm_address_t)thread_list, thread_count * sizeof(thread_t));
520-
}
521-
522-
void OS::signalThread(int tid, int signum) {
523-
// On macOS, tid is actually a mach thread port
524-
thread_t thread = static_cast<thread_t>(tid);
525-
526-
// Convert mach thread to pthread for signaling
527-
// This is a limitation - we can't easily signal arbitrary mach threads
528-
// In practice, this is mainly used for TLS priming which is disabled on macOS
529-
TEST_LOG("Thread signaling not fully supported on macOS (thread=%d, signal=%d)", tid, signum);
530-
}
531-
532-
int OS::getThreadCount() {
533-
task_t task = mach_task_self();
534-
thread_act_array_t thread_list;
535-
mach_msg_type_number_t thread_count;
536-
537-
kern_return_t kr = task_threads(task, &thread_list, &thread_count);
538-
if (kr != KERN_SUCCESS) {
539-
TEST_LOG("Failed to get thread count: %d", kr);
540-
return 0;
541-
}
542-
543-
// Clean up
544-
vm_deallocate(task, (vm_address_t)thread_list, thread_count * sizeof(thread_t));
545-
546-
return static_cast<int>(thread_count);
547-
}
548-
549487
#endif // __APPLE__

0 commit comments

Comments
 (0)