Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
628d42f
v0
zhengyu123 Jul 24, 2026
a300d48
v1
zhengyu123 Jul 24, 2026
30511b9
v2
zhengyu123 Jul 25, 2026
bbed591
v3
zhengyu123 Jul 25, 2026
9ba087e
Fix
zhengyu123 Jul 24, 2026
bdf0431
Fix
zhengyu123 Jul 24, 2026
008f443
Fix
zhengyu123 Jul 24, 2026
5cf9771
Potential fix for pull request finding
zhengyu123 Jul 24, 2026
b4ddaf6
Fix test
zhengyu123 Jul 25, 2026
df16d80
v0
zhengyu123 Jul 24, 2026
2a481a5
v1
zhengyu123 Jul 24, 2026
7b5c657
Rebase
zhengyu123 Jul 26, 2026
2be8182
Fix
zhengyu123 Jul 26, 2026
0df0f05
Potential fix for pull request finding
zhengyu123 Jul 26, 2026
9b6a9e1
Potential fix for pull request finding
zhengyu123 Jul 26, 2026
82b31e9
Potential fix for pull request finding
zhengyu123 Jul 26, 2026
46c4341
Potential fix for pull request finding
zhengyu123 Jul 26, 2026
728559c
Fix
zhengyu123 Jul 26, 2026
da218ef
Fix comments
zhengyu123 Jul 26, 2026
741eb8e
Fix test
zhengyu123 Jul 26, 2026
85d0655
Potential fix for pull request finding
zhengyu123 Jul 26, 2026
bed222c
Potential fix for pull request finding
zhengyu123 Jul 26, 2026
468d05f
Fix
zhengyu123 Jul 27, 2026
ce9dd3f
Merge branch 'zgu/recordSample' of github.com:DataDog/java-profiler i…
zhengyu123 Jul 27, 2026
1f6c6d5
fix
zhengyu123 Jul 28, 2026
2b47e15
Longjmp protection leaver
zhengyu123 Jul 28, 2026
c240983
Potential fix for pull request finding
zhengyu123 Jul 29, 2026
dd2d48c
Potential fix for pull request finding
zhengyu123 Jul 29, 2026
39cf23d
build(deps): bump the codeql-action group with 2 updates (#688)
dependabot[bot] Jul 27, 2026
73bd569
Merge
zhengyu123 Jul 29, 2026
67e8b04
Fix merge
zhengyu123 Jul 29, 2026
b99ba1b
[Automated] Bump dev version to 1.49.0 (#690)
dd-octo-sts[bot] Jul 27, 2026
a99b910
Merge
zhengyu123 Jul 29, 2026
ad43806
Potential fix for pull request finding
zhengyu123 Jul 29, 2026
4e5d059
Protect HotspotSupport::walkJavaStack()
zhengyu123 Jul 29, 2026
71d01ce
Merge branch 'zgu/recordSample' of github.com:DataDog/java-profiler i…
zhengyu123 Jul 29, 2026
040baed
Cleanup
zhengyu123 Jul 29, 2026
f6dca96
Merge branch 'main' into zgu/recordSample
zhengyu123 Jul 29, 2026
94c4cbd
Fix compilation
zhengyu123 Jul 29, 2026
d8d7b36
Fix test
zhengyu123 Jul 29, 2026
ed3cfa5
Fix
zhengyu123 Jul 29, 2026
9e8d45f
Add tests
zhengyu123 Jul 29, 2026
4c11e8d
Consolidate duplicated code and fix tests
zhengyu123 Jul 30, 2026
275088b
Fix musl getContext()
zhengyu123 Jul 30, 2026
f6f16f8
Fix mac/aarch64
zhengyu123 Jul 31, 2026
1cd80be
Merge branch 'main' into zgu/recordSample
zhengyu123 Jul 31, 2026
505c164
Restore ucontext
zhengyu123 Jul 31, 2026
cc89f34
Merge branch 'zgu/recordSample' of github.com:DataDog/java-profiler i…
zhengyu123 Jul 31, 2026
e07dff9
Potential fix for pull request finding
zhengyu123 Jul 31, 2026
948bf80
Potential fix for pull request finding
zhengyu123 Jul 31, 2026
7568a27
Fix
zhengyu123 Jul 31, 2026
16b64f6
Merge branch 'zgu/recordSample' of github.com:DataDog/java-profiler i…
zhengyu123 Jul 31, 2026
6c6a3d7
Fix test
zhengyu123 Jul 31, 2026
42432ff
Add copyright
zhengyu123 Jul 31, 2026
be7e84d
Revert code consolidation change in HotspotSupport::getJavaTraceAsync()
zhengyu123 Jul 31, 2026
47b385d
Merge branch 'main' into zgu/recordSample
zhengyu123 Jul 31, 2026
b8f9b17
remove unused
zhengyu123 Jul 31, 2026
5b97812
Merge branch 'zgu/recordSample' of github.com:DataDog/java-profiler i…
zhengyu123 Jul 31, 2026
2492825
Potential fix for pull request finding
zhengyu123 Jul 31, 2026
371afc5
Enforce memory ordering
zhengyu123 Aug 1, 2026
5c39ccb
Merge branch 'zgu/recordSample' of github.com:DataDog/java-profiler i…
zhengyu123 Aug 1, 2026
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
16 changes: 16 additions & 0 deletions ddprof-lib/src/main/cpp/asyncSampleMutex.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/*
* Copyright 2026, Datadog, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef ASYNCSAMPLEMUTEX_H
#define ASYNCSAMPLEMUTEX_H

Expand Down
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/counters.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
X(SAMPLES_DROPPED_THREAD_LOCAL, "samples_dropped_thread_local") \
X(SAFECOPY_FAILED, "safecopy_failed") \
X(SAFEFETCH_FAILED, "safefetch_failed") \
X(WALKVM_LONGJMP_RECOVERED, "walkvm_longjmp_recovered") \
X(STACKWALK_LONGJMP_RECOVERED, "stackwalk_longjmp_recovered") \
DD_COUNTER_TABLE_FAULT_INJECTION(X) \
DD_COUNTER_TABLE_FI_DEBUG(X) \
DD_COUNTER_TABLE_DEBUG(X)
Expand Down
5 changes: 5 additions & 0 deletions ddprof-lib/src/main/cpp/faultInjection.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@
#ifndef _FAULT_INJECTION_H
#define _FAULT_INJECTION_H

#include <cassert>

#ifdef __FAULT_INJECTION__

#include "arch.h" // u64
#include <cstdint>

#define NO_INJECTION_ASSERT(a)
namespace faultinj {

// Firing probability expressed as an xorshift64 threshold (round(p * 2^64)), so
Expand Down Expand Up @@ -129,6 +132,8 @@ inline T injectValue(T orig, T faulty, u64 threshold, const char* fn) {
#define INJECT_FAULT_BOOL_UNLIKELY(v) (v)
#define INJECT_FAULT_BOOL_LIKELY(v) (v)

#define NO_INJECTION_ASSERT(a) (assert(a))

#endif // __FAULT_INJECTION__

#endif // _FAULT_INJECTION_H
78 changes: 46 additions & 32 deletions ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ static void fillFrameTypes(ASGCT_CallFrame *frames, int num_frames, VMNMethod *n

// Fill the frame with raw method pointer
static void fillFrameRaw(ASGCT_CallFrame& frame, FrameTypeId type, int bci, const VMMethod* method) {
assert(method != nullptr);
NO_INJECTION_ASSERT(method != nullptr);
frame.bci = FrameType::encode(type, bci, true /*raw method pointer*/);
frame.method = static_cast<const void*>(method);
}
Expand All @@ -201,7 +201,7 @@ void HotspotSupport::fillJavaFrame(ASGCT_CallFrame& frame, FrameTypeId type, int
} else if (method_id != nullptr) {
fillFrame(frame, type, bci, method_id);
} else {
assert(method != nullptr);
NO_INJECTION_ASSERT(method != nullptr);
fillFrameRaw(frame, type, bci, method);
}
}
Expand Down Expand Up @@ -983,22 +983,6 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex
return depth;
}

void HotspotSupport::checkFault(ProfiledThread* thrd) {
// Should not get to here (?)
if (thrd == nullptr) {
return;
}

// Check if siglongjmp is set up for this thread
if (!thrd->isProtected()) {
return;
}

thrd->resetCrashHandler();
Counters::increment(WALKVM_LONGJMP_RECOVERED);
siglongjmp(*thrd->getJmpCtx(), 1);
}

int HotspotSupport::getJavaTraceAsync(void *ucontext, ASGCT_CallFrame *frames,
int max_depth, StackContext *java_ctx,
bool *truncated) {
Expand Down Expand Up @@ -1076,7 +1060,7 @@ int HotspotSupport::getJavaTraceAsync(void *ucontext, ASGCT_CallFrame *frames,
JitWriteProtection jit(false);
// AsyncGetCallTrace writes to ASGCT_CallFrame array
ASGCT_CallTrace trace = {jni, 0, frames};
VM::_asyncGetCallTrace(&trace, max_depth, ucontext);
JVMSupport::jvmAsyncGetCallTrace(&trace, max_depth, ucontext);

if (trace.num_frames > 0) {
frame.restore(saved_pc, saved_sp, saved_fp);
Expand All @@ -1097,7 +1081,7 @@ int HotspotSupport::getJavaTraceAsync(void *ucontext, ASGCT_CallFrame *frames,
if (!(safe_mode & POP_STUB) &&
frame.unwindStub((instruction_t *)stub->_start, stub->_name) &&
isAddressInCode((const void *)frame.pc())) {
VM::_asyncGetCallTrace(&trace, max_depth, ucontext);
JVMSupport::jvmAsyncGetCallTrace(&trace, max_depth, ucontext);
}
} else if (VMStructs::hasMethodStructs()) {
VMNMethod *nmethod = CodeHeap::findNMethod((const void *)frame.pc());
Expand All @@ -1110,15 +1094,15 @@ int HotspotSupport::getJavaTraceAsync(void *ucontext, ASGCT_CallFrame *frames,
}
if (!(safe_mode & POP_METHOD) && frame.unwindCompiled(nmethod) &&
isAddressInCode((const void *)frame.pc())) {
VM::_asyncGetCallTrace(&trace, max_depth, ucontext);
JVMSupport::jvmAsyncGetCallTrace(&trace, max_depth, ucontext);
}
if ((safe_mode & PROBE_SP) && trace.num_frames < 0) {
if (isValidJMethodID(method_id)) {
trace.frames--;
}
for (int i = 0; trace.num_frames < 0 && i < PROBE_SP_LIMIT; i++) {
frame.sp() += sizeof(void*);
VM::_asyncGetCallTrace(&trace, max_depth, ucontext);
JVMSupport::jvmAsyncGetCallTrace(&trace, max_depth, ucontext);
}
}
}
Expand All @@ -1130,7 +1114,7 @@ int HotspotSupport::getJavaTraceAsync(void *ucontext, ASGCT_CallFrame *frames,
if (!(safe_mode & POP_STUB) &&
frame.unwindStub(NULL, nmethod->name()) &&
isAddressInCode((const void *)frame.pc())) {
VM::_asyncGetCallTrace(&trace, max_depth, ucontext);
JVMSupport::jvmAsyncGetCallTrace(&trace, max_depth, ucontext);
}
}
}
Expand All @@ -1157,9 +1141,9 @@ int HotspotSupport::getJavaTraceAsync(void *ucontext, ASGCT_CallFrame *frames,
m->frameCompleteOffset() == -1) {
m->setFrameCompleteOffset(0);
}
VM::_asyncGetCallTrace(&trace, max_depth, ucontext);
JVMSupport::jvmAsyncGetCallTrace(&trace, max_depth, ucontext);
} else if (libs->findLibraryByAddress(pc) != NULL) {
VM::_asyncGetCallTrace(&trace, max_depth, ucontext);
JVMSupport::jvmAsyncGetCallTrace(&trace, max_depth, ucontext);
}

anchor->setLastJavaPC(nullptr);
Expand All @@ -1177,7 +1161,7 @@ int HotspotSupport::getJavaTraceAsync(void *ucontext, ASGCT_CallFrame *frames,
if (m != NULL && !m->isNMethod() && m->frameSize() > 0 &&
m->frameCompleteOffset() == -1) {
m->setFrameCompleteOffset(0);
VM::_asyncGetCallTrace(&trace, max_depth, ucontext);
JVMSupport::jvmAsyncGetCallTrace(&trace, max_depth, ucontext);
}
}
} else if (trace.num_frames == ticks_GC_active && !(safe_mode & GC_TRACES)) {
Expand Down Expand Up @@ -1218,7 +1202,36 @@ int HotspotSupport::walkJavaStack(StackWalkRequest& request) {
bool* truncated = request.truncated;
u32 lock_index = request.lock_index;

int java_frames = 0;
volatile int java_frames = 0;
// walkVM() installs its own sigsetjmp/siglongjmp crash protection (chained
// with any pre-existing jmp ctx, see the comment in walkVM), but the
// getJavaTraceAsync() path below runs without one: it dereferences
// VMThread/anchor state directly and calls into HotSpot's own
// AsyncGetCallTrace. Install a jmp ctx here too, so a SIGSEGV anywhere in
// walkJavaStack, except HotSpot's AsyncGetCallTrace call, is caught by
// Profiler::checkFault() and siglongjmp'd back here instead of crashing the process.
ProfiledThread* prof_thread = ProfiledThread::current();
const bool prev_unwinding_java = prof_thread != nullptr ? prof_thread->is_unwinding_Java() : false;
sigjmp_buf crash_protection_ctx;
sigjmp_buf* prev_jmp_buf = prof_thread != nullptr ? prof_thread->getJmpCtx() : nullptr;

if (prof_thread != nullptr && sigsetjmp(crash_protection_ctx, 1) != 0) {
// checkFault() does a siglongjmp from inside segvHandler, bypassing
// segvHandler's SignalHandlerScope destructor. Compensate.
SIGNAL_HANDLER_UNWIND_AFTER_LONGJMP();
prof_thread->setJmpCtx(prev_jmp_buf);
// A recovered siglongjmp bypasses AsyncSampleMutex destructors, so restore
// the per-thread guard to its pre-walk value.
prof_thread->set_unwinding_Java(prev_unwinding_java);
if (truncated) {
*truncated = true;
}
return java_frames;
Comment thread
zhengyu123 marked this conversation as resolved.
}
Comment thread
Copilot marked this conversation as resolved.
if (prof_thread != nullptr) {
prof_thread->setJmpCtx(&crash_protection_ctx);
}

if (features.mixed) {
java_frames = walkVM(ucontext, frames, max_depth, features, eventTypeFromBCI(request.event_type), lock_index, truncated);
} else if (isHookPrefixedSample(request.event_type)) {
Expand Down Expand Up @@ -1249,7 +1262,6 @@ int HotspotSupport::walkJavaStack(StackWalkRequest& request) {
if (cstack >= CSTACK_VM) {
java_frames = walkVM(ucontext, frames, max_depth, features, eventTypeFromBCI(request.event_type), lock_index, truncated);
} else {
// Async events
AsyncSampleMutex mutex(ProfiledThread::current());
if (mutex.acquired()) {
java_frames = getJavaTraceAsync(ucontext, frames, max_depth, java_ctx, truncated);
Expand All @@ -1260,8 +1272,6 @@ int HotspotSupport::walkJavaStack(StackWalkRequest& request) {
}
}
}
// ASGCT stops at the continuation boundary for virtual threads (JDK 21+).
// Append a synthetic root frame so the UI does not show "Missing Frames".
if (java_frames > 0 && VM::hotspot_version() >= 21 && java_frames < max_depth) {
VMThread* carrier = VMThread::current();
if (carrier != nullptr && carrier->isCarryingVirtualThread()) {
Expand All @@ -1273,7 +1283,11 @@ int HotspotSupport::walkJavaStack(StackWalkRequest& request) {
}
}
}
return java_frames;

if (prof_thread != nullptr) {
prof_thread->setJmpCtx(prev_jmp_buf);
}
return java_frames;
}

static void patchClassLoaderData(JNIEnv* jni, jclass klass) {
Expand Down Expand Up @@ -1380,7 +1394,7 @@ bool HotspotSupport::loadMethodIDsIfNeededImpl(jvmtiEnv *jvmti, JNIEnv *jni, jcl
// This method only resolves methods that are loaded by system class loaders
jmethodID HotspotSupport::resolve(const void* method) {
assert(VM::isHotspot());
assert(method != nullptr);
NO_INJECTION_ASSERT(method != nullptr);
// We packed not walkable method as a raw pointer,
// map it back to nullptr, as JMETHODID_NOT_WALKABLE is only
// known in hotspot.
Expand Down
1 change: 0 additions & 1 deletion ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ class HotspotSupport {
public:
static void initClassloaderInfo(JNIEnv* jni);

static void checkFault(ProfiledThread* thrd = nullptr);
static int walkJavaStack(StackWalkRequest& request);
static inline bool canUnwind(const StackFrame& frame, const void*& pc) {
return HotspotStackFrame::unwindAtomicStub(frame, pc);
Expand Down
5 changes: 3 additions & 2 deletions ddprof-lib/src/main/cpp/jvmSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

#include "jvmSupport.h"
#include "jvmSupport.inline.h"

#include "asyncSampleMutex.h"
#include "frames.h"
Expand Down Expand Up @@ -106,10 +106,11 @@ int JVMSupport::asyncGetCallTrace(ASGCT_CallFrame *frames, int max_depth, void*
return 0;
}


JitWriteProtection jit(false);
// AsyncGetCallTrace writes to ASGCT_CallFrame array
ASGCT_CallTrace trace = {jni, 0, frames};
VM::_asyncGetCallTrace(&trace, max_depth, ucontext);
jvmAsyncGetCallTrace(&trace, max_depth, ucontext);
if (trace.num_frames > 0) {
return trace.num_frames;
}
Expand Down
3 changes: 3 additions & 0 deletions ddprof-lib/src/main/cpp/jvmSupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class JVMSupport {
static Mutex _initialization_lock;
static volatile JMethodIDLoadStats jmethodID_load_state;

// Call JVM AsyncGetCallTrace implementation
static inline void jvmAsyncGetCallTrace(ASGCT_CallTrace *frames, int max_depth, void* ucontext);

static int asyncGetCallTrace(ASGCT_CallFrame *frames, int max_depth, void* ucontext);
Comment thread
zhengyu123 marked this conversation as resolved.
// J9 and Zing shared implementation, load jmethodIDs of the method unconditionally.
static bool loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass);
Expand Down
5 changes: 5 additions & 0 deletions ddprof-lib/src/main/cpp/jvmSupport.inline.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "hotspot/hotspotSupport.h"
#include "jvmSupport.h"
#include "threadLocalData.h"
#include "vmEntry.h"

bool JVMSupport::canUnwind(const StackFrame& frame, const void*& pc) {
Expand Down Expand Up @@ -53,4 +54,8 @@ bool JVMSupport::isHidden(jint modifiers) {
((modifiers & hidden_mask) != 0);
}

void JVMSupport::jvmAsyncGetCallTrace(ASGCT_CallTrace *frames, int max_depth, void* ucontext) {
VM::_asyncGetCallTrace(frames, max_depth, ucontext);
}

#endif // _JVMSUPPORT_INLINE_H
10 changes: 5 additions & 5 deletions ddprof-lib/src/main/cpp/libraryPatcher_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,13 @@ static void cleanup_unregister(void*) {
//
// The fix: use __pthread_register_cancel / __pthread_unregister_cancel
// directly — the same thing the C macro form of pthread_cleanup_push does.
// This registers cleanup via a setjmp buffer in a runtime linked-list, NOT
// This registers cleanup via a sigsetjmp buffer in a runtime linked-list, NOT
// via an LSDA destructor. _Unwind_ForcedUnwind's stop function
// (__pthread_unwind_stop) handles the cleanup without ever calling
// __gxx_personality_v0 for this frame, so _Unwind_SetGR is never called and
// the cross-version incompatibility is never triggered.
//
// On musl: pthread_cleanup_push already uses the C/setjmp form (no RAII),
// On musl: pthread_cleanup_push already uses the C/sigsetjmp form (no RAII),
// and pthread_exit does not use _Unwind_ForcedUnwind, so there is no issue.
// The __GLIBC__ guard keeps the musl path unchanged.
#ifdef __GLIBC__
Expand All @@ -131,7 +131,7 @@ void run_with_cleanup(func_start_routine routine, void* params,
static_assert(offsetof(__pthread_unwind_buf_t, __cancel_jmp_buf) == 0 &&
sizeof(cancel_buf.__cancel_jmp_buf[0]) == offsetof(struct __jmp_buf_tag, __saved_mask),
"glibc __pthread_unwind_buf_t inner layout incompatible with struct __jmp_buf_tag");
// __sigsetjmp/longjmp only intercepts _Unwind_ForcedUnwind (pthread_exit /
// __sigsetjmp/siglongjmp only intercepts _Unwind_ForcedUnwind (pthread_exit /
// cancellation). routine(params) must NOT throw a regular C++ exception
// across this boundary: an escaping exception would skip both
// __pthread_unregister_cancel and cleanup_fn below, leaking the thread
Expand All @@ -144,7 +144,7 @@ void run_with_cleanup(func_start_routine routine, void* params,
// set __sigsetjmp's savemask=0 (the second parameter, noting that the signal mask is NOT
// saved/restored, which is correct because the cancel mechanism does not depend on signal mask state.
__sigsetjmp((struct __jmp_buf_tag*)(void*)cancel_buf.__cancel_jmp_buf, 0), 0)) {
// Reached via longjmp from glibc's stop function when pthread_exit
// Reached via siglongjmp from glibc's stop function when pthread_exit
// (or cancellation) fires. Run cleanup and continue unwinding.
cleanup_fn(cleanup_arg);
__pthread_unwind_next(&cancel_buf);
Expand All @@ -163,7 +163,7 @@ void run_with_cleanup(func_start_routine routine, void* params,
__pthread_unregister_cancel(&cancel_buf);
cleanup_fn(cleanup_arg);
#else
// musl / non-glibc: pthread_cleanup_push uses the C/setjmp form, no RAII.
// musl / non-glibc: pthread_cleanup_push uses the C/sigsetjmp form, no RAII.
pthread_cleanup_push(cleanup_fn, cleanup_arg);
routine(params);
pthread_cleanup_pop(1);
Expand Down
Loading
Loading