-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathlibraryPatcher_linux.cpp
More file actions
321 lines (285 loc) · 10.2 KB
/
libraryPatcher_linux.cpp
File metadata and controls
321 lines (285 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
/*
* Copyright 2026, Datadog, Inc.
* SPDX-License-Identifier: Apache-2.0
*/
#include "libraryPatcher.h"
#ifdef __linux__
#include "counters.h"
#include "profiler.h"
#include "guards.h"
#include <cassert>
#include <dlfcn.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
typedef void* (*func_start_routine)(void*);
SpinLock LibraryPatcher::_lock;
const char* LibraryPatcher::_profiler_name = nullptr;
PatchEntry LibraryPatcher::_patched_entries[MAX_NATIVE_LIBS];
int LibraryPatcher::_size = 0;
PatchEntry LibraryPatcher::_sigaction_entries[MAX_NATIVE_LIBS];
int LibraryPatcher::_sigaction_size = 0;
void LibraryPatcher::initialize() {
if (_profiler_name == nullptr) {
Dl_info info;
void* caller_address = __builtin_return_address(0); // Get return address of caller
bool ret = dladdr(caller_address, &info);
assert(ret);
_profiler_name = realpath(info.dli_fname, nullptr);
_size = 0;
}
}
class RoutineInfo {
private:
func_start_routine _routine;
void* _args;
public:
RoutineInfo(func_start_routine routine, void* args) :
_routine(routine), _args(args) {
}
func_start_routine routine() const {
return _routine;
}
void* args() const {
return _args;
}
};
#ifdef __aarch64__
// Delete RoutineInfo with profiling signals blocked to prevent ASAN
// allocator lock reentrancy. Kept noinline so SignalBlocker's sigset_t
// does not trigger stack-protector canary in the caller on aarch64.
__attribute__((noinline))
static void delete_routine_info(RoutineInfo* thr) {
SignalBlocker blocker;
delete thr;
}
// Initialize the current thread's TLS with profiling signals blocked.
// Kept noinline for the same stack-protector reason as delete_routine_info.
__attribute__((noinline))
static void init_thread_tls() {
SignalBlocker blocker;
ProfiledThread::initCurrentThread();
}
// Arm the CPU timer with profiling signals blocked and open the init window
// (PROF-13072). Kept noinline for the same stack-protector reason as
// delete_routine_info: SignalBlocker's sigset_t must not appear in
// start_routine_wrapper_spec's own stack frame on musl/aarch64.
__attribute__((noinline))
static void start_window_and_register(int tid) {
SignalBlocker blocker;
ProfiledThread::currentSignalSafe()->startInitWindow();
Profiler::registerThread(tid);
}
// Wrapper around the real start routine.
// The wrapper:
// 1. Register the newly created thread to profiler
// 2. Call real start routine
// 3. Unregister the thread from profiler once the routine is completed.
// This version is to workaround a precarious stack guard corruption,
// which only happens in Linux/musl/aarch64/jdk11
__attribute__((visibility("hidden")))
static void* start_routine_wrapper_spec(void* args) {
RoutineInfo* thr = (RoutineInfo*)args;
func_start_routine routine = thr->routine();
void* params = thr->args();
delete_routine_info(thr);
init_thread_tls();
int tid = ProfiledThread::currentTid();
start_window_and_register(tid);
void* result = routine(params);
Profiler::unregisterThread(tid);
ProfiledThread::release();
return result;
}
static int pthread_create_hook_spec(pthread_t* thread,
const pthread_attr_t* attr,
func_start_routine start_routine,
void* args) {
RoutineInfo* thr;
{
SignalBlocker blocker;
thr = new RoutineInfo(start_routine, args);
}
int ret = pthread_create(thread, attr, start_routine_wrapper_spec, (void*)thr);
if (ret != 0) {
SignalBlocker blocker;
delete thr;
}
return ret;
}
#endif // __aarch64__
static void thread_cleanup(void* arg) {
int tid = *static_cast<int*>(arg);
Profiler::unregisterThread(tid);
ProfiledThread::release();
}
// Wrapper around the real start routine.
// See comments for start_routine_wrapper_spec() for details
__attribute__((visibility("hidden")))
static void* start_routine_wrapper(void* args) {
RoutineInfo* thr = (RoutineInfo*)args;
func_start_routine routine;
void* params;
int tid;
{
// Block profiling signals while accessing and freeing RoutineInfo
// and during TLS initialization. Under ASAN, new/delete/
// pthread_setspecific are intercepted and acquire ASAN's internal
// allocator lock. A profiling signal during any of these calls
// runs ASAN-instrumented code that tries to acquire the same
// lock, causing deadlock.
// registerThread is also kept inside the blocker so that the CPU
// timer is armed while SIGPROF/SIGVTALRM are masked. Any pending
// signal fires only after signals are re-enabled (when the blocker
// scope exits), at which point JVMThread::current() is still null
// and the guard in CTimer::signalHandler discards the sample safely.
SignalBlocker blocker;
routine = thr->routine();
params = thr->args();
delete thr;
ProfiledThread::initCurrentThread();
tid = ProfiledThread::currentTid();
ProfiledThread::currentSignalSafe()->startInitWindow();
Profiler::registerThread(tid);
}
void* result = nullptr;
// Handle pthread_exit() bypass - the thread calls pthread_exit()
// instead of normal termination
pthread_cleanup_push(thread_cleanup, &tid);
result = routine(params);
pthread_cleanup_pop(1);
return result;
}
static int pthread_create_hook(pthread_t* thread,
const pthread_attr_t* attr,
func_start_routine start_routine,
void* args) {
RoutineInfo* thr;
{
SignalBlocker blocker;
thr = new RoutineInfo(start_routine, args);
}
int ret = pthread_create(thread, attr, start_routine_wrapper, (void*)thr);
if (ret != 0) {
SignalBlocker blocker;
delete thr;
}
return ret;
}
void LibraryPatcher::patch_libraries() {
// LibraryPatcher has yet initialized, only happens in Gtest
if (_profiler_name == nullptr) {
return;
}
TEST_LOG("Patching libraries");
patch_pthread_create();
TEST_LOG("%d libraries patched", _size);
}
void LibraryPatcher::patch_library_unlocked(CodeCache* lib) {
if (lib->name() == nullptr) return;
char path[PATH_MAX];
char* resolved_path = realpath(lib->name(), path);
if (resolved_path != nullptr && // filter out virtual file, e.g. [vdso], etc.
strcmp(resolved_path, _profiler_name) == 0) { // Don't patch self
return;
}
// Don't patch sanitizer runtime libraries — intercepting their internal
// pthread_create calls causes reentrancy and heap corruption under ASAN.
const char* base = strrchr(lib->name(), '/');
base = (base != nullptr) ? base + 1 : lib->name();
if (strncmp(base, "libasan", 7) == 0 ||
strncmp(base, "libtsan", 7) == 0 ||
strncmp(base, "libubsan", 8) == 0) {
return;
}
void** pthread_create_location = (void**)lib->findImport(im_pthread_create);
if (pthread_create_location == nullptr) {
return;
}
for (int index = 0; index < _size; index++) {
// Already patched
if (_patched_entries[index]._lib == lib) {
return;
}
}
TEST_LOG("Patching: %s", lib->name());
void* func = (void*)pthread_create_hook;
#ifdef __aarch64__
// Workaround stack guard corruption in Linux/aarch64/musl/jdk11
if (VM::isHotspot() && OS::isMusl() && VM::java_version() == 11) {
func = (void*)pthread_create_hook_spec;
}
#endif
_patched_entries[_size]._lib = lib;
_patched_entries[_size]._location = pthread_create_location;
_patched_entries[_size]._func = (void*)__atomic_load_n(pthread_create_location, __ATOMIC_RELAXED);
__atomic_store_n(pthread_create_location, func, __ATOMIC_RELAXED);
_size++;
}
void LibraryPatcher::unpatch_libraries() {
TEST_LOG("Restore libraries");
ExclusiveLockGuard locker(&_lock);
for (int index = 0; index < _size; index++) {
__atomic_store_n(_patched_entries[index]._location, _patched_entries[index]._func, __ATOMIC_RELAXED);
}
_size = 0;
}
void LibraryPatcher::patch_pthread_create() {
const CodeCacheArray& native_libs = Libraries::instance()->native_libs();
int num_of_libs = native_libs.count();
ExclusiveLockGuard locker(&_lock);
for (int index = 0; index < num_of_libs; index++) {
CodeCache* lib = native_libs.at(index);
if (lib != nullptr) {
patch_library_unlocked(lib);
}
}
}
// Patch sigaction in all libraries to prevent any library from overwriting
// our SIGSEGV/SIGBUS handlers. This protects against misbehaving libraries
// (like wasmtime) that install broken signal handlers calling malloc().
void LibraryPatcher::patch_sigaction_in_library(CodeCache* lib) {
if (lib->name() == nullptr) return;
if (_profiler_name == nullptr) return; // Not initialized yet
// Don't patch ourselves
char path[PATH_MAX];
char* resolved_path = realpath(lib->name(), path);
if (resolved_path != nullptr && strcmp(resolved_path, _profiler_name) == 0) {
return;
}
// Note: We intentionally patch sanitizer libraries (libasan, libtsan, libubsan) here.
// This keeps our handler on top for recoverable SIGSEGVs (e.g., safefetch) while
// still chaining to the sanitizer's handler for unexpected crashes.
void** sigaction_location = (void**)lib->findImport(im_sigaction);
if (sigaction_location == nullptr) {
return;
}
// Check if already patched or array is full
if (_sigaction_size >= MAX_NATIVE_LIBS) {
return;
}
for (int index = 0; index < _sigaction_size; index++) {
if (_sigaction_entries[index]._lib == lib) {
return;
}
}
void* hook = OS::getSigactionHook();
_sigaction_entries[_sigaction_size]._lib = lib;
_sigaction_entries[_sigaction_size]._location = sigaction_location;
_sigaction_entries[_sigaction_size]._func = (void*)__atomic_load_n(sigaction_location, __ATOMIC_RELAXED);
__atomic_store_n(sigaction_location, hook, __ATOMIC_RELAXED);
_sigaction_size++;
Counters::increment(SIGACTION_PATCHED_LIBS);
}
void LibraryPatcher::patch_sigaction() {
const CodeCacheArray& native_libs = Libraries::instance()->native_libs();
int num_of_libs = native_libs.count();
ExclusiveLockGuard locker(&_lock);
for (int index = 0; index < num_of_libs; index++) {
CodeCache* lib = native_libs.at(index);
if (lib != nullptr) {
patch_sigaction_in_library(lib);
}
}
}
#endif // __linux__