|
1 | 1 | /* |
2 | 2 | * Copyright The async-profiler authors |
| 3 | + * Copyright 2025, 2026 Datadog, Inc |
3 | 4 | * SPDX-License-Identifier: Apache-2.0 |
4 | 5 | */ |
5 | 6 |
|
6 | 7 | #include <pthread.h> |
7 | 8 | #include <string.h> |
8 | 9 | #include <unistd.h> |
9 | 10 | #include <stdarg.h> |
10 | | -#include "vmStructs.h" |
| 11 | +#include "hotspot/vmStructs.inline.h" |
11 | 12 | #include "vmEntry.h" |
12 | | -#include "j9Ext.h" |
13 | 13 | #include "jniHelper.h" |
14 | 14 | #include "jvmHeap.h" |
| 15 | +#include "jvmThread.h" |
15 | 16 | #include "safeAccess.h" |
16 | 17 | #include "spinLock.h" |
17 | | -#include "common.h" |
| 18 | +#include "threadState.h" |
18 | 19 |
|
19 | 20 | CodeCache* VMStructs::_libjvm = nullptr; |
20 | 21 | bool VMStructs::_has_class_names = false; |
@@ -79,11 +80,8 @@ DECLARE_LONG_CONSTANTS_DO(INIT_LONG_CONSTANT, INIT_LONG_CONSTANT) |
79 | 80 | #undef INIT_INT_CONSTANT |
80 | 81 | #undef INIT_LONG_CONSTANT |
81 | 82 |
|
82 | | - |
83 | | -jfieldID VMStructs::_eetop; |
84 | | -jfieldID VMStructs::_tid; |
| 83 | +jfieldID VMStructs::_eetop = NULL; |
85 | 84 | jfieldID VMStructs::_klass = NULL; |
86 | | -int VMStructs::_tls_index = -1; |
87 | 85 | intptr_t VMStructs::_env_offset = -1; |
88 | 86 | void* VMStructs::_java_thread_vtbl[6]; |
89 | 87 |
|
@@ -122,7 +120,6 @@ void VMStructs::init(CodeCache* libjvm) { |
122 | 120 | void VMStructs::ready() { |
123 | 121 | resolveOffsets(); |
124 | 122 | patchSafeFetch(); |
125 | | - initThreadBridge(); |
126 | 123 | } |
127 | 124 |
|
128 | 125 | bool matchAny(const char* target_name, std::initializer_list<const char*> names) { |
@@ -482,51 +479,6 @@ void VMStructs::patchSafeFetch() { |
482 | 479 | } |
483 | 480 | } |
484 | 481 |
|
485 | | -void VMStructs::initTLS(void* vm_thread) { |
486 | | - for (int i = 0; i < 1024; i++) { |
487 | | - if (pthread_getspecific((pthread_key_t)i) == vm_thread) { |
488 | | - _tls_index = i; |
489 | | - break; |
490 | | - } |
491 | | - } |
492 | | -} |
493 | | - |
494 | | -void VMStructs::initThreadBridge() { |
495 | | - jthread thread; |
496 | | - if (VM::jvmti()->GetCurrentThread(&thread) != 0) { |
497 | | - return; |
498 | | - } |
499 | | - |
500 | | - JNIEnv* env = VM::jni(); |
501 | | - jclass thread_class = env->FindClass("java/lang/Thread"); |
502 | | - if (thread_class == NULL || (_tid = env->GetFieldID(thread_class, "tid", "J")) == NULL) { |
503 | | - env->ExceptionClear(); |
504 | | - return; |
505 | | - } |
506 | | - |
507 | | - if (VM::isOpenJ9()) { |
508 | | - void* j9thread = J9Ext::j9thread_self(); |
509 | | - if (j9thread != NULL) { |
510 | | - initTLS(j9thread); |
511 | | - } |
512 | | - } else { |
513 | | - // Get eetop field - a bridge from Java Thread to VMThread |
514 | | - if ((_eetop = env->GetFieldID(thread_class, "eetop", "J")) == NULL) { |
515 | | - // No such field - probably not a HotSpot JVM |
516 | | - env->ExceptionClear(); |
517 | | - return; |
518 | | - } |
519 | | - |
520 | | - VMThread* vm_thread = VMThread::fromJavaThread(env, thread); |
521 | | - if (vm_thread != NULL) { |
522 | | - _has_native_thread_id = _thread_osthread_offset >= 0 && _osthread_id_offset >= 0; |
523 | | - initTLS(vm_thread); |
524 | | - _env_offset = (intptr_t)env - (intptr_t)vm_thread; |
525 | | - memcpy(_java_thread_vtbl, vm_thread->vtable(), sizeof(_java_thread_vtbl)); |
526 | | - } |
527 | | - } |
528 | | -} |
529 | | - |
530 | 482 | // ===== Datadog-specific VMStructs extensions ===== |
531 | 483 |
|
532 | 484 | void VMStructs::initUnsafeFunctions() { |
@@ -654,16 +606,89 @@ void VMStructs::checkNativeBinding(jvmtiEnv *jvmti, JNIEnv *jni, |
654 | 606 | jvmti->Deallocate((unsigned char *)method_name); |
655 | 607 | } |
656 | 608 |
|
657 | | -VMThread* VMThread::current() { |
658 | | - return _tls_index >= 0 ? (VMThread*)pthread_getspecific((pthread_key_t)_tls_index) : NULL; |
659 | | -} |
| 609 | +void* VMThread::initialize(jthread thread) { |
| 610 | + JNIEnv* env = VM::jni(); |
| 611 | + jclass thread_class = env->FindClass("java/lang/Thread"); |
| 612 | + if (thread_class == NULL) { |
| 613 | + env->ExceptionClear(); |
| 614 | + return nullptr; |
| 615 | + } |
660 | 616 |
|
661 | | -int VMThread::nativeThreadId(JNIEnv* jni, jthread thread) { |
662 | | - if (_has_native_thread_id) { |
663 | | - VMThread* vm_thread = fromJavaThread(jni, thread); |
664 | | - return vm_thread != NULL ? vm_thread->osThreadId() : -1; |
| 617 | + // Get eetop field - a bridge from Java Thread to VMThread |
| 618 | + if ((_eetop = env->GetFieldID(thread_class, "eetop", "J")) == NULL) { |
| 619 | + // No such field - probably not a HotSpot JVM |
| 620 | + env->ExceptionClear(); |
| 621 | + return nullptr; |
665 | 622 | } |
666 | | - return VM::isOpenJ9() ? J9Ext::GetOSThreadID(thread) : -1; |
| 623 | + |
| 624 | + void* vm_thread = fromJavaThread(env, thread); |
| 625 | + assert(vm_thread != nullptr); |
| 626 | + _has_native_thread_id = _thread_osthread_offset >= 0 && _osthread_id_offset >= 0; |
| 627 | + _env_offset = (intptr_t)env - (intptr_t)vm_thread; |
| 628 | + memcpy(_java_thread_vtbl, VMThread::cast(vm_thread)->vtable(), sizeof(_java_thread_vtbl)); |
| 629 | + return vm_thread; |
| 630 | +} |
| 631 | + |
| 632 | +static ExecutionMode convertJvmExecutionState(int state) { |
| 633 | + switch (state) { |
| 634 | + case 4: |
| 635 | + case 5: |
| 636 | + return ExecutionMode::NATIVE; |
| 637 | + case 6: |
| 638 | + case 7: |
| 639 | + return ExecutionMode::JVM; |
| 640 | + case 8: |
| 641 | + case 9: |
| 642 | + return ExecutionMode::JAVA; |
| 643 | + case 10: |
| 644 | + case 11: |
| 645 | + return ExecutionMode::SAFEPOINT; |
| 646 | + default: |
| 647 | + return ExecutionMode::UNKNOWN; |
| 648 | + } |
| 649 | +} |
| 650 | + |
| 651 | +ExecutionMode VMThread::getExecutionMode() { |
| 652 | + assert(VM::isHotspot()); |
| 653 | + |
| 654 | + VMThread* vm_thread = VMThread::current(); |
| 655 | + // Not a JVM thread - native thread, e.g. thread launched by JNI code |
| 656 | + if (vm_thread == nullptr) { |
| 657 | + return ExecutionMode::NATIVE; |
| 658 | + } |
| 659 | + |
| 660 | + ProfiledThread *prof_thread = ProfiledThread::currentSignalSafe(); |
| 661 | + bool is_java_thread = prof_thread != nullptr && prof_thread->isJavaThread(); |
| 662 | + |
| 663 | + // A Java thread that JVM tells us via jvmti `ThreadStart()` callback. |
| 664 | + if (is_java_thread) { |
| 665 | + int raw_thread_state = vm_thread->state(); |
| 666 | + |
| 667 | + // Java threads: [4, 12) = [_thread_in_native, _thread_max_state) |
| 668 | + // JVM internal threads: 0 or outside this range |
| 669 | + is_java_thread = raw_thread_state >= 4 && raw_thread_state < 12; |
| 670 | + |
| 671 | + return is_java_thread ? convertJvmExecutionState(raw_thread_state) |
| 672 | + : ExecutionMode::JVM; |
| 673 | + } else { |
| 674 | + // It is a JVM internal thread, may or may not be a Java thread, |
| 675 | + // e.g. Compiler thread or GC thread, etc |
| 676 | + return ExecutionMode::JVM; |
| 677 | + } |
| 678 | +} |
| 679 | + |
| 680 | +OSThreadState VMThread::getOSThreadState() { |
| 681 | + VMThread* vm_thread = VMThread::current(); |
| 682 | + if (vm_thread == nullptr) { |
| 683 | + return OSThreadState::UNKNOWN; |
| 684 | + } |
| 685 | + int raw_thread_state = vm_thread->state(); |
| 686 | + bool is_java_thread = raw_thread_state >= 4 && raw_thread_state < 12; |
| 687 | + OSThreadState state = OSThreadState::UNKNOWN; |
| 688 | + if (is_java_thread) { |
| 689 | + state = vm_thread->osThreadState(); |
| 690 | + } |
| 691 | + return state; |
667 | 692 | } |
668 | 693 |
|
669 | 694 | int VMThread::osThreadId() { |
|
0 commit comments