Skip to content

Commit d3a3565

Browse files
authored
Merge branch 'main' into zgu/refactor-vmstructs
2 parents 8a7fb67 + 0ec65ff commit d3a3565

17 files changed

Lines changed: 375 additions & 34 deletions

File tree

.github/workflows/gh_release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
3030
with:
3131
fetch-depth: 0
32-
- uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
32+
- uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0
3333
with:
3434
ssh-private-key: ${{ secrets.SSH_PUSH_SECRET }}
3535
- name: Create Release [automatic]

.github/workflows/release-validated.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ jobs:
153153
echo "::error::Pre-release tests failed. Cannot proceed with release."
154154
exit 1
155155
156-
- uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
156+
- uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0
157157
with:
158158
ssh-private-key: ${{ secrets.SSH_PUSH_SECRET }}
159159

build-logic/conventions/src/main/kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -252,19 +252,14 @@ class ProfilerTestPlugin : Plugin<Project> {
252252

253253
// Sanitizer conditions
254254
when (testConfig.configName) {
255-
"asan" -> testTask.onlyIf { PlatformUtils.locateLibasan() != null }
256-
"tsan" -> testTask.onlyIf { PlatformUtils.locateLibtsan() != null }
257-
}
258-
259-
// J9+ASAN: isolate each test class in its own JVM to limit crash blast radius
260-
if (testConfig.configName == "asan") {
261-
testTask.doFirst {
262-
if (PlatformUtils.isTestJvmJ9()) {
263-
testTask.setForkEvery(1)
264-
}
255+
"asan" -> testTask.onlyIf {
256+
PlatformUtils.locateLibasan() != null &&
257+
// Skip J9+ASAN: OpenJ9 has known GC stack-scanning and defineClass
258+
// race bugs exposed by ASAN timing
259+
// https://github.com/eclipse-openj9/openj9/issues/23514
260+
!PlatformUtils.isTestJvmJ9()
265261
}
266-
// Kill hung ASAN test tasks after 30 minutes
267-
testTask.timeout.set(Duration.ofMinutes(30))
262+
"tsan" -> testTask.onlyIf { PlatformUtils.locateLibtsan() != null }
268263
}
269264
}
270265
}
@@ -345,7 +340,13 @@ class ProfilerTestPlugin : Plugin<Project> {
345340

346341
// Sanitizer conditions
347342
when (testConfig.configName) {
348-
"asan" -> execTask.onlyIf { PlatformUtils.locateLibasan() != null }
343+
"asan" -> execTask.onlyIf {
344+
PlatformUtils.locateLibasan() != null &&
345+
// Skip J9+ASAN: OpenJ9 has known GC stack-scanning and defineClass
346+
// race bugs exposed by ASAN timing
347+
// https://github.com/eclipse-openj9/openj9/issues/23514
348+
!PlatformUtils.isTestJvmJ9()
349+
}
349350
"tsan" -> execTask.onlyIf { PlatformUtils.locateLibtsan() != null }
350351
}
351352
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,11 @@ void CodeCache::addImport(void **entry, const char *name) {
338338
saveImport(im_realloc, entry);
339339
}
340340
break;
341+
case 's':
342+
if (strcmp(name, "sigaction") == 0) {
343+
saveImport(im_sigaction, entry);
344+
}
345+
break;
341346
}
342347
}
343348

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#ifndef _CODECACHE_H
77
#define _CODECACHE_H
88

9+
#include "common.h"
10+
#include "counters.h"
911
#include "utils.h"
1012

1113
#include <jvmti.h>
@@ -31,6 +33,7 @@ enum ImportId {
3133
im_calloc,
3234
im_realloc,
3335
im_free,
36+
im_sigaction,
3437
NUM_IMPORTS
3538
};
3639

@@ -257,9 +260,10 @@ class CodeCacheArray {
257260
volatile int _reserved; // next slot to reserve (CAS by writers)
258261
volatile int _count; // published count (all indices < _count have non-NULL pointers)
259262
volatile size_t _used_memory;
263+
bool _overflow_reported;
260264

261265
public:
262-
CodeCacheArray() : _reserved(0), _count(0), _used_memory(0) {
266+
CodeCacheArray() : _reserved(0), _count(0), _used_memory(0), _overflow_reported(false) {
263267
memset(_libs, 0, MAX_NATIVE_LIBS * sizeof(CodeCache *));
264268
}
265269

@@ -271,10 +275,17 @@ class CodeCacheArray {
271275
// Pointer-first add: reserve a slot via CAS on _reserved, store the
272276
// pointer with RELEASE, then advance _count. Readers see count() grow
273277
// only after the pointer is visible, so indices < count() never yield NULL.
274-
void add(CodeCache *lib) {
278+
bool add(CodeCache *lib) {
275279
int slot = __atomic_load_n(&_reserved, __ATOMIC_RELAXED);
276280
do {
277-
if (slot >= MAX_NATIVE_LIBS) return;
281+
if (slot >= MAX_NATIVE_LIBS) {
282+
Counters::increment(NATIVE_LIBS_DROPPED);
283+
if (!_overflow_reported) {
284+
_overflow_reported = true;
285+
LOG_WARN("Native library limit reached (%d). Additional libraries will not be tracked.", MAX_NATIVE_LIBS);
286+
}
287+
return false;
288+
}
278289
} while (!__atomic_compare_exchange_n(&_reserved, &slot, slot + 1,
279290
true, __ATOMIC_RELAXED, __ATOMIC_RELAXED));
280291
assert(__atomic_load_n(&_libs[slot], __ATOMIC_RELAXED) == nullptr);
@@ -288,6 +299,7 @@ class CodeCacheArray {
288299
// wait for preceding slots to publish
289300
}
290301
__atomic_store_n(&_count, slot + 1, __ATOMIC_RELEASE);
302+
return true;
291303
}
292304

293305
CodeCache* at(int index) const {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define _COMMON_H
33

44
#include <cstddef>
5+
#include <cstdio>
56

67
// Knuth's multiplicative constant (golden ratio * 2^64 for 64-bit)
78
// Used for hash distribution in various components
@@ -16,4 +17,10 @@ constexpr size_t KNUTH_MULTIPLICATIVE_CONSTANT = 0x9e3779b97f4a7c15ULL;
1617
#define TEST_LOG(fmt, ...) // No-op in non-debug mode
1718
#endif
1819

20+
// Lightweight stderr warning that does not depend on the Log subsystem.
21+
// Safe to call from low-level code where Log may not be initialized.
22+
#define LOG_WARN(fmt, ...) do { \
23+
fprintf(stderr, "[ddprof] [WARN] " fmt "\n", ##__VA_ARGS__); \
24+
} while (0)
25+
1926
#endif // _COMMON_H

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,10 @@
9292
X(WALKVM_STUB_FRAMESIZE_FALLBACK, "walkvm_stub_framesize_fallback") \
9393
X(WALKVM_FP_CHAIN_ATTEMPT, "walkvm_fp_chain_attempt") \
9494
X(WALKVM_FP_CHAIN_REACHED_CODEHEAP, "walkvm_fp_chain_reached_codeheap") \
95-
X(WALKVM_ANCHOR_NOT_IN_JAVA, "walkvm_anchor_not_in_java")
95+
X(WALKVM_ANCHOR_NOT_IN_JAVA, "walkvm_anchor_not_in_java") \
96+
X(NATIVE_LIBS_DROPPED, "native_libs_dropped") \
97+
X(SIGACTION_PATCHED_LIBS, "sigaction_patched_libs") \
98+
X(SIGACTION_INTERCEPTED, "sigaction_intercepted")
9699
#define X_ENUM(a, b) a,
97100
typedef enum CounterId : int {
98101
DD_COUNTER_TABLE(X_ENUM) DD_NUM_COUNTERS

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class Libraries {
2424
// Note: Parameter is uint32_t to match lib_index packing (17 bits = max 131K libraries)
2525
CodeCache *getLibraryByIndex(uint32_t index) const {
2626
if (index < _native_libs.count()) {
27-
return _native_libs[index]; // may be NULL during concurrent add()
27+
return _native_libs[index];
2828
}
2929
return nullptr;
3030
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,19 @@ class LibraryPatcher {
2424
static int _size;
2525
static bool _patch_pthread_create;
2626

27+
// Separate tracking for sigaction patches
28+
static PatchEntry _sigaction_entries[MAX_NATIVE_LIBS];
29+
static int _sigaction_size;
30+
2731
static void patch_library_unlocked(CodeCache* lib);
2832
static void patch_pthread_create();
2933
static void patch_pthread_setspecific();
34+
static void patch_sigaction_in_library(CodeCache* lib);
3035
public:
3136
static void initialize();
3237
static void patch_libraries();
3338
static void unpatch_libraries();
39+
static void patch_sigaction();
3440
};
3541

3642
#else
@@ -40,6 +46,7 @@ class LibraryPatcher {
4046
static void initialize() { }
4147
static void patch_libraries() { }
4248
static void unpatch_libraries() { }
49+
static void patch_sigaction() { }
4350
};
4451

4552
#endif

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

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "libraryPatcher.h"
22

33
#ifdef __linux__
4+
#include "counters.h"
45
#include "profiler.h"
56
#include "vmStructs.h"
67
#include "guards.h"
@@ -17,6 +18,8 @@ SpinLock LibraryPatcher::_lock;
1718
const char* LibraryPatcher::_profiler_name = nullptr;
1819
PatchEntry LibraryPatcher::_patched_entries[MAX_NATIVE_LIBS];
1920
int LibraryPatcher::_size = 0;
21+
PatchEntry LibraryPatcher::_sigaction_entries[MAX_NATIVE_LIBS];
22+
int LibraryPatcher::_sigaction_size = 0;
2023

2124
void LibraryPatcher::initialize() {
2225
if (_profiler_name == nullptr) {
@@ -238,8 +241,59 @@ void LibraryPatcher::patch_pthread_create() {
238241
}
239242
}
240243
}
241-
#endif // __linux__
242244

245+
// Patch sigaction in all libraries to prevent any library from overwriting
246+
// our SIGSEGV/SIGBUS handlers. This protects against misbehaving libraries
247+
// (like wasmtime) that install broken signal handlers calling malloc().
248+
void LibraryPatcher::patch_sigaction_in_library(CodeCache* lib) {
249+
if (lib->name() == nullptr) return;
250+
if (_profiler_name == nullptr) return; // Not initialized yet
251+
252+
// Don't patch ourselves
253+
char path[PATH_MAX];
254+
char* resolved_path = realpath(lib->name(), path);
255+
if (resolved_path != nullptr && strcmp(resolved_path, _profiler_name) == 0) {
256+
return;
257+
}
258+
259+
// Note: We intentionally patch sanitizer libraries (libasan, libtsan, libubsan) here.
260+
// This keeps our handler on top for recoverable SIGSEGVs (e.g., safefetch) while
261+
// still chaining to the sanitizer's handler for unexpected crashes.
262+
263+
void** sigaction_location = (void**)lib->findImport(im_sigaction);
264+
if (sigaction_location == nullptr) {
265+
return;
266+
}
267+
268+
// Check if already patched or array is full
269+
if (_sigaction_size >= MAX_NATIVE_LIBS) {
270+
return;
271+
}
272+
for (int index = 0; index < _sigaction_size; index++) {
273+
if (_sigaction_entries[index]._lib == lib) {
274+
return;
275+
}
276+
}
243277

278+
void* hook = OS::getSigactionHook();
279+
_sigaction_entries[_sigaction_size]._lib = lib;
280+
_sigaction_entries[_sigaction_size]._location = sigaction_location;
281+
_sigaction_entries[_sigaction_size]._func = (void*)__atomic_load_n(sigaction_location, __ATOMIC_RELAXED);
282+
__atomic_store_n(sigaction_location, hook, __ATOMIC_RELAXED);
283+
_sigaction_size++;
284+
Counters::increment(SIGACTION_PATCHED_LIBS);
285+
}
244286

287+
void LibraryPatcher::patch_sigaction() {
288+
const CodeCacheArray& native_libs = Libraries::instance()->native_libs();
289+
int num_of_libs = native_libs.count();
290+
ExclusiveLockGuard locker(&_lock);
291+
for (int index = 0; index < num_of_libs; index++) {
292+
CodeCache* lib = native_libs.at(index);
293+
if (lib != nullptr) {
294+
patch_sigaction_in_library(lib);
295+
}
296+
}
297+
}
245298

299+
#endif // __linux__

0 commit comments

Comments
 (0)