Skip to content

Commit 0366015

Browse files
committed
Cleanup/chaining jmp_buf/test cases
1 parent 563d97f commit 0366015

8 files changed

Lines changed: 148 additions & 27 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@
122122
* paths (delegated and direct) go into SAMPLES_DROPPED_REC_LOCK. */ \
123123
X(JVMTI_STACKS_DROPPED_LOCK, "jvmti_stacks_dropped_lock") \
124124
X(SAMPLES_DROPPED_REC_LOCK, "samples_dropped_rec_lock") \
125-
X(SAMPLES_DROOPED_THREAD_LOCAL, "samples_dropped_thread_local")
125+
X(SAMPLES_DROPPED_THREAD_LOCAL, "samples_dropped_thread_local")
126126
#define X_ENUM(a, b) a,
127127
typedef enum CounterId : int {
128128
DD_COUNTER_TABLE(X_ENUM) DD_NUM_COUNTERS

ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
using StackWalkValidation::inDeadZone;
2121
using StackWalkValidation::aligned;
2222
using StackWalkValidation::MAX_FRAME_SIZE;
23-
using StackWalkValidation::sameStack;
2423

2524
// Initialize once, they survive on profiler restart
2625
static jobject JAVA_PLATFORM_CLASSLOADER = nullptr;
@@ -234,7 +233,7 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex
234233

235234
ProfiledThread* prof_thread = ProfiledThread::currentSignalSafe();
236235
if (prof_thread == nullptr) {
237-
Counters::increment(SAMPLES_DROOPED_THREAD_LOCAL);
236+
Counters::increment(SAMPLES_DROPPED_THREAD_LOCAL);
238237
return 0;
239238
}
240239

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,6 @@ bool JVMThread::initialize() {
2323
return _jvm_thread.isKeyValid();
2424
}
2525

26-
/**
27-
* Per-thread initialization
28-
*
29-
* This method *must* be called before signal is enabled
30-
* for the thread.
31-
*/
32-
void JVMThread::initThread() {
33-
}
34-
3526
bool JVMThread::isInitialized() {
3627
return (_tid != nullptr && _jvm_thread.isKeyValid());
3728
}

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,6 @@ static void init_tls_and_register() {
259259
pt->startInitWindow();
260260
}
261261
Profiler::registerThread(ProfiledThread::currentTid());
262-
JVMThread::initThread();
263262
}
264263

265264
// Wrapper around the real start routine.
@@ -375,7 +374,6 @@ static void* start_routine_wrapper(void* args) {
375374
ProfiledThread::initCurrentThread();
376375
ProfiledThread::currentSignalSafe()->startInitWindow();
377376
Profiler::registerThread(ProfiledThread::currentTid());
378-
JVMThread::initThread();
379377
}
380378
// Use POSIX cleanup instead of C++ RAII to handle pthread_exit(): see run_with_cleanup.
381379
// cleanup_unregister has already run on run_with_cleanup's normal return path.

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ static CTimer ctimer;
7373
static CTimerJvmti ctimer_jvmti;
7474

7575
void Profiler::onThreadStart(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread) {
76-
JVMThread::initThread();
7776
ProfiledThread::initCurrentThread();
7877
ProfiledThread *current = ProfiledThread::current();
7978
current->setJavaThread(true);
@@ -946,8 +945,8 @@ int Profiler::crashHandlerInternal(int signo, siginfo_t *siginfo, void *ucontext
946945
have_tls_protection = true;
947946
}
948947
// If thrd == nullptr, we proceed but with limited handling capability.
949-
// Only HotspotSupport::checkFault (which has its own sameStack fallback)
950-
// and the JDK-8313796 workaround can safely handle faults without TLS.
948+
// Only HotspotSupport::checkFault and the JDK-8313796 workaround can safely
949+
// handle faults without TLS.
951950

952951
StackFrame frame(ucontext);
953952
uintptr_t pc = frame.pc();

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,6 @@ class ProfiledThread : public ThreadLocalData {
210210
return _jmp_buf != nullptr;
211211
}
212212

213-
void resetJmpCtx() {
214-
_jmp_buf = nullptr;
215-
}
216-
217213
// Signal-handler depth counter used by SignalHandlerScope (guards.h). All
218214
// access happens on the owning thread (signal handlers are delivered to the
219215
// thread that's interrupted), so plain reads/writes are AS-safe — no locks,

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -541,8 +541,7 @@ bool VM::initProfilerBridge(JavaVM *vm, bool attach) {
541541

542542

543543
if (attach) {
544-
JVMThread::initThread();
545-
_jvmti->GenerateEvents(JVMTI_EVENT_DYNAMIC_CODE_GENERATED);
544+
2 _jvmti->GenerateEvents(JVMTI_EVENT_DYNAMIC_CODE_GENERATED);
546545
_jvmti->GenerateEvents(JVMTI_EVENT_COMPILED_METHOD_LOAD);
547546
} else {
548547
_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL);

ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp

Lines changed: 143 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,21 @@
1111
* VMThread::isJavaThread() provides the gate.
1212
*
1313
* Crash recovery inside walkVM relies on setjmp/longjmp:
14-
* 1. walkVM stores a jmp_buf* in HotspotSupport::_jmp_ctx (thread-local).
15-
* 2. If a fault fires during the walk, checkFault() detects the live context
16-
* via isThreadProtectedByLongjmp() and calls longjmp() to unwind.
14+
* 1. walkVM stores a jmp_buf* on ProfiledThread (setJmpCtx/getJmpCtx),
15+
* chaining it with whatever context was already installed so a
16+
* signal-based sampler interrupting a non-signal-based sampler's own
17+
* in-flight walkVM() call doesn't clobber the outer call's context.
18+
* 2. If a fault fires during the walk, checkFault() detects the live
19+
* context via ProfiledThread::isProtected() and calls longjmp() to
20+
* unwind through whatever context is currently installed.
1721
* 3. ProfiledThread tracks nested crash-handler depth so recursive faults
1822
* (e.g. wall-clock signal inside a crash handler) are capped safely.
1923
*
2024
* Tests cover:
2125
* A. ProfiledThread thread-type classification (isJavaThread fast path)
2226
* B. Vtable majority-vote logic (isJavaThread slow path)
2327
* C. Crash-handler nesting depth (ProfiledThread crash handler state)
24-
* D. Longjmp-protection state via HotspotSupport public API
28+
* D. jmp_buf chaining across nested/interrupted walkVM() calls
2529
*/
2630

2731
#include <gtest/gtest.h>
@@ -253,4 +257,139 @@ TEST_F(CrashHandlerNestingTest, IsDeepOnlyAboveLimit) {
253257
_pt->resetCrashHandler();
254258
}
255259

260+
// ---------------------------------------------------------------------------
261+
// D. jmp_buf chaining (ProfiledThread::setJmpCtx/getJmpCtx/isProtected)
262+
//
263+
// A non-signal-based sampler's walkVM() call can itself be interrupted by a
264+
// signal-based sampler, putting two walkVM() frames on the same thread's
265+
// stack. Each frame follows the same protocol:
266+
// jmp_buf* prev = prof_thread->getJmpCtx(); // save whatever was there
267+
// prof_thread->setJmpCtx(&my_ctx); // install this frame's ctx
268+
// ... walk ...
269+
// prof_thread->setJmpCtx(prev); // restore on every exit path
270+
// checkFault() always longjmps through whatever is currently installed
271+
// (thrd->getJmpCtx()), so the inner frame must never leave the outer frame's
272+
// context installed while the inner frame is doing its own protected work,
273+
// and must always hand it back — via normal completion or fault recovery —
274+
// before returning control to the outer frame.
275+
// ---------------------------------------------------------------------------
276+
277+
class JmpCtxChainingTest : public ::testing::Test {
278+
protected:
279+
void SetUp() override {
280+
ProfiledThread::initCurrentThread();
281+
_pt = ProfiledThread::currentSignalSafe();
282+
ASSERT_NE(nullptr, _pt);
283+
}
284+
285+
void TearDown() override {
286+
ProfiledThread::release();
287+
}
288+
289+
ProfiledThread* _pt = nullptr;
290+
};
291+
292+
TEST_F(JmpCtxChainingTest, InitiallyUnprotected) {
293+
EXPECT_FALSE(_pt->isProtected());
294+
EXPECT_EQ(nullptr, _pt->getJmpCtx());
295+
}
296+
297+
TEST_F(JmpCtxChainingTest, SetAndGetRoundTrip) {
298+
jmp_buf ctx;
299+
_pt->setJmpCtx(&ctx);
300+
EXPECT_TRUE(_pt->isProtected());
301+
EXPECT_EQ(&ctx, _pt->getJmpCtx());
302+
}
303+
304+
// Replicates a single walkVM() call's save/install/restore around its body.
305+
TEST_F(JmpCtxChainingTest, SingleFrameRestoresPreviousOnExit) {
306+
jmp_buf outer;
307+
jmp_buf* prev = _pt->getJmpCtx(); // nullptr: no enclosing walkVM() call
308+
ASSERT_EQ(nullptr, prev);
309+
310+
_pt->setJmpCtx(&outer);
311+
EXPECT_EQ(&outer, _pt->getJmpCtx());
312+
313+
// Simulate walkVM()'s `done:` path.
314+
_pt->setJmpCtx(prev);
315+
EXPECT_EQ(nullptr, _pt->getJmpCtx());
316+
EXPECT_FALSE(_pt->isProtected());
317+
}
318+
319+
// Replicates two nested walkVM() calls: a signal-based sampler interrupting a
320+
// non-signal-based sampler's own in-flight walkVM(). The inner call must
321+
// chain off the outer's jmp_buf*, install its own, and hand the outer's back
322+
// on its way out — leaving the outer frame's context exactly as it left it.
323+
TEST_F(JmpCtxChainingTest, NestedFramesChainAndUnwindInOrder) {
324+
jmp_buf outer_ctx;
325+
jmp_buf* outer_prev = _pt->getJmpCtx();
326+
ASSERT_EQ(nullptr, outer_prev);
327+
_pt->setJmpCtx(&outer_ctx);
328+
EXPECT_EQ(&outer_ctx, _pt->getJmpCtx());
329+
330+
{
331+
// Inner walkVM() call, as if a signal fired while the outer one was
332+
// mid-walk.
333+
jmp_buf inner_ctx;
334+
jmp_buf* inner_prev = _pt->getJmpCtx();
335+
EXPECT_EQ(&outer_ctx, inner_prev); // chained off the outer frame
336+
337+
_pt->setJmpCtx(&inner_ctx);
338+
EXPECT_EQ(&inner_ctx, _pt->getJmpCtx());
339+
340+
// Inner call completes via its own `done:` path.
341+
_pt->setJmpCtx(inner_prev);
342+
}
343+
344+
// The outer frame's context must be untouched by the inner call.
345+
EXPECT_EQ(&outer_ctx, _pt->getJmpCtx());
346+
347+
_pt->setJmpCtx(outer_prev);
348+
EXPECT_EQ(nullptr, _pt->getJmpCtx());
349+
}
350+
351+
// End-to-end with real setjmp/longjmp: a fault inside the inner frame must
352+
// land in the inner frame's own recovery branch — checkFault() always
353+
// longjmps through whatever is currently installed — and once the inner
354+
// frame has recovered and restored the outer's context, the outer frame must
355+
// be left exactly as it was, never having been unwound itself.
356+
TEST_F(JmpCtxChainingTest, FaultInInnerFrameDoesNotDisturbOuterFrame) {
357+
jmp_buf outer_ctx;
358+
jmp_buf* outer_prev = _pt->getJmpCtx();
359+
int outer_landed = 0;
360+
int inner_landed = 0;
361+
362+
if (setjmp(outer_ctx) != 0) {
363+
outer_landed++;
364+
} else {
365+
_pt->setJmpCtx(&outer_ctx);
366+
367+
// --- inner "walkVM" call, interrupted mid-flight by a fault ---
368+
jmp_buf inner_ctx;
369+
jmp_buf* inner_prev = _pt->getJmpCtx();
370+
ASSERT_EQ(&outer_ctx, inner_prev);
371+
372+
if (setjmp(inner_ctx) != 0) {
373+
inner_landed++;
374+
_pt->setJmpCtx(inner_prev);
375+
} else {
376+
_pt->setJmpCtx(&inner_ctx);
377+
// Simulate checkFault(): longjmp through whatever is currently
378+
// installed — this must hit the inner frame, not the outer.
379+
longjmp(*_pt->getJmpCtx(), 1);
380+
FAIL() << "unreachable: longjmp does not return";
381+
}
382+
// --- inner call has returned normally after recovering ---
383+
384+
EXPECT_EQ(&outer_ctx, _pt->getJmpCtx())
385+
<< "outer frame's context must survive the inner frame's fault";
386+
387+
_pt->setJmpCtx(outer_prev);
388+
}
389+
390+
EXPECT_EQ(1, inner_landed);
391+
EXPECT_EQ(0, outer_landed) << "the fault must not have unwound past the inner frame";
392+
EXPECT_FALSE(_pt->isProtected());
393+
}
394+
256395
#endif // __linux__

0 commit comments

Comments
 (0)