64
64
#endif
65
65
66
66
#ifdef Py_GIL_DISABLED
67
- #define INTERP_STATE_MIN_SIZE MAX(MAX(offsetof(PyInterpreterState, _code_object_generation) + sizeof(uint64_t), \
68
- offsetof(PyInterpreterState, tlbc_indices.tlbc_generation) + sizeof(uint32_t)), \
69
- offsetof(PyInterpreterState, threads.head) + sizeof(void*))
67
+ #define INTERP_STATE_MIN_SIZE MAX(MAX(MAX(offsetof(PyInterpreterState, _code_object_generation) + sizeof(uint64_t), \
68
+ offsetof(PyInterpreterState, tlbc_indices.tlbc_generation) + sizeof(uint32_t)), \
69
+ offsetof(PyInterpreterState, threads.head) + sizeof(void*)), \
70
+ offsetof(PyInterpreterState, _gil.last_holder) + sizeof(PyThreadState*))
70
71
#else
71
- #define INTERP_STATE_MIN_SIZE MAX(offsetof(PyInterpreterState, _code_object_generation) + sizeof(uint64_t), \
72
- offsetof(PyInterpreterState, threads.head) + sizeof(void*))
72
+ #define INTERP_STATE_MIN_SIZE MAX(MAX(offsetof(PyInterpreterState, _code_object_generation) + sizeof(uint64_t), \
73
+ offsetof(PyInterpreterState, threads.head) + sizeof(void*)), \
74
+ offsetof(PyInterpreterState, _gil.last_holder) + sizeof(PyThreadState*))
73
75
#endif
74
76
#define INTERP_STATE_BUFFER_SIZE MAX(INTERP_STATE_MIN_SIZE, 256)
75
77
@@ -206,6 +208,7 @@ typedef struct {
206
208
uint64_t code_object_generation ;
207
209
_Py_hashtable_t * code_object_cache ;
208
210
int debug ;
211
+ int only_active_thread ;
209
212
RemoteDebuggingState * cached_state ; // Cached module state
210
213
#ifdef Py_GIL_DISABLED
211
214
// TLBC cache invalidation tracking
@@ -2496,6 +2499,7 @@ _remote_debugging.RemoteUnwinder.__init__
2496
2499
pid: int
2497
2500
*
2498
2501
all_threads: bool = False
2502
+ only_active_thread: bool = False
2499
2503
debug: bool = False
2500
2504
2501
2505
Initialize a new RemoteUnwinder object for debugging a remote Python process.
@@ -2504,6 +2508,8 @@ Initialize a new RemoteUnwinder object for debugging a remote Python process.
2504
2508
pid: Process ID of the target Python process to debug
2505
2509
all_threads: If True, initialize state for all threads in the process.
2506
2510
If False, only initialize for the main thread.
2511
+ only_active_thread: If True, only sample the thread holding the GIL.
2512
+ Cannot be used together with all_threads=True.
2507
2513
debug: If True, chain exceptions to explain the sequence of events that
2508
2514
lead to the exception.
2509
2515
@@ -2514,15 +2520,33 @@ process, including examining thread states, stack frames and other runtime data.
2514
2520
PermissionError: If access to the target process is denied
2515
2521
OSError: If unable to attach to the target process or access its memory
2516
2522
RuntimeError: If unable to read debug information from the target process
2523
+ ValueError: If both all_threads and only_active_thread are True
2517
2524
[clinic start generated code]*/
2518
2525
2519
2526
static int
2520
2527
_remote_debugging_RemoteUnwinder___init___impl (RemoteUnwinderObject * self ,
2521
2528
int pid , int all_threads ,
2529
+ int only_active_thread ,
2522
2530
int debug )
2523
- /*[clinic end generated code: output=3982f2a7eba49334 input=48a762566b828e91 ]*/
2531
+ /*[clinic end generated code: output=13ba77598ecdcbe1 input=8f8f12504e17da04 ]*/
2524
2532
{
2533
+ // Validate that all_threads and only_active_thread are not both True
2534
+ if (all_threads && only_active_thread ) {
2535
+ PyErr_SetString (PyExc_ValueError ,
2536
+ "all_threads and only_active_thread cannot both be True" );
2537
+ return -1 ;
2538
+ }
2539
+
2540
+ #ifdef Py_GIL_DISABLED
2541
+ if (only_active_thread ) {
2542
+ PyErr_SetString (PyExc_ValueError ,
2543
+ "only_active_thread is not supported when Py_GIL_DISABLED is not defined" );
2544
+ return -1 ;
2545
+ }
2546
+ #endif
2547
+
2525
2548
self -> debug = debug ;
2549
+ self -> only_active_thread = only_active_thread ;
2526
2550
self -> cached_state = NULL ;
2527
2551
if (_Py_RemoteDebug_InitProcHandle (& self -> handle , pid ) < 0 ) {
2528
2552
set_exception_cause (self , PyExc_RuntimeError , "Failed to initialize process handle" );
@@ -2602,13 +2626,18 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
2602
2626
@critical_section
2603
2627
_remote_debugging.RemoteUnwinder.get_stack_trace
2604
2628
2605
- Returns a list of stack traces for all threads in the target process.
2629
+ Returns a list of stack traces for threads in the target process.
2606
2630
2607
2631
Each element in the returned list is a tuple of (thread_id, frame_list), where:
2608
2632
- thread_id is the OS thread identifier
2609
2633
- frame_list is a list of tuples (function_name, filename, line_number) representing
2610
2634
the Python stack frames for that thread, ordered from most recent to oldest
2611
2635
2636
+ The threads returned depend on the initialization parameters:
2637
+ - If only_active_thread was True: returns only the thread holding the GIL
2638
+ - If all_threads was True: returns all threads
2639
+ - Otherwise: returns only the main thread
2640
+
2612
2641
Example:
2613
2642
[
2614
2643
(1234, [
@@ -2632,7 +2661,7 @@ Each element in the returned list is a tuple of (thread_id, frame_list), where:
2632
2661
2633
2662
static PyObject *
2634
2663
_remote_debugging_RemoteUnwinder_get_stack_trace_impl (RemoteUnwinderObject * self )
2635
- /*[clinic end generated code: output=666192b90c69d567 input=331dbe370578badf ]*/
2664
+ /*[clinic end generated code: output=666192b90c69d567 input=f756f341206f9116 ]*/
2636
2665
{
2637
2666
PyObject * result = NULL ;
2638
2667
// Read interpreter state into opaque buffer
@@ -2655,6 +2684,28 @@ _remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self
2655
2684
_Py_hashtable_clear (self -> code_object_cache );
2656
2685
}
2657
2686
2687
+ // If only_active_thread is true, we need to determine which thread holds the GIL
2688
+ PyThreadState * gil_holder = NULL ;
2689
+ if (self -> only_active_thread ) {
2690
+ // The GIL state is already in interp_state_buffer, just read from there
2691
+ // Check if GIL is locked
2692
+ int gil_locked = GET_MEMBER (int , interp_state_buffer ,
2693
+ self -> debug_offsets .interpreter_state .gil_runtime_state_locked );
2694
+
2695
+ if (gil_locked ) {
2696
+ // Get the last holder (current holder when GIL is locked)
2697
+ gil_holder = GET_MEMBER (PyThreadState * , interp_state_buffer ,
2698
+ self -> debug_offsets .interpreter_state .gil_runtime_state_holder );
2699
+ } else {
2700
+ // GIL is not locked, return empty list
2701
+ result = PyList_New (0 );
2702
+ if (!result ) {
2703
+ set_exception_cause (self , PyExc_MemoryError , "Failed to create empty result list" );
2704
+ }
2705
+ goto exit ;
2706
+ }
2707
+ }
2708
+
2658
2709
#ifdef Py_GIL_DISABLED
2659
2710
// Check TLBC generation and invalidate cache if needed
2660
2711
uint32_t current_tlbc_generation = GET_MEMBER (uint32_t , interp_state_buffer ,
@@ -2666,7 +2717,10 @@ _remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self
2666
2717
#endif
2667
2718
2668
2719
uintptr_t current_tstate ;
2669
- if (self -> tstate_addr == 0 ) {
2720
+ if (self -> only_active_thread && gil_holder != NULL ) {
2721
+ // We have the GIL holder, process only that thread
2722
+ current_tstate = (uintptr_t )gil_holder ;
2723
+ } else if (self -> tstate_addr == 0 ) {
2670
2724
// Get threads head from buffer
2671
2725
current_tstate = GET_MEMBER (uintptr_t , interp_state_buffer ,
2672
2726
self -> debug_offsets .interpreter_state .threads_head );
@@ -2700,10 +2754,14 @@ _remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self
2700
2754
if (self -> tstate_addr ) {
2701
2755
break ;
2702
2756
}
2757
+
2758
+ // If we're only processing the GIL holder, we're done after one iteration
2759
+ if (self -> only_active_thread && gil_holder != NULL ) {
2760
+ break ;
2761
+ }
2703
2762
}
2704
2763
2705
2764
exit :
2706
- _Py_RemoteDebug_ClearCache (& self -> handle );
2707
2765
return result ;
2708
2766
}
2709
2767
@@ -2827,11 +2885,9 @@ _remote_debugging_RemoteUnwinder_get_all_awaited_by_impl(RemoteUnwinderObject *s
2827
2885
goto result_err ;
2828
2886
}
2829
2887
2830
- _Py_RemoteDebug_ClearCache (& self -> handle );
2831
2888
return result ;
2832
2889
2833
2890
result_err :
2834
- _Py_RemoteDebug_ClearCache (& self -> handle );
2835
2891
Py_XDECREF (result );
2836
2892
return NULL ;
2837
2893
}
@@ -2898,11 +2954,9 @@ _remote_debugging_RemoteUnwinder_get_async_stack_trace_impl(RemoteUnwinderObject
2898
2954
goto cleanup ;
2899
2955
}
2900
2956
2901
- _Py_RemoteDebug_ClearCache (& self -> handle );
2902
2957
return result ;
2903
2958
2904
2959
cleanup :
2905
- _Py_RemoteDebug_ClearCache (& self -> handle );
2906
2960
Py_XDECREF (result );
2907
2961
return NULL ;
2908
2962
}
@@ -2928,7 +2982,6 @@ RemoteUnwinder_dealloc(PyObject *op)
2928
2982
}
2929
2983
#endif
2930
2984
if (self -> handle .pid != 0 ) {
2931
- _Py_RemoteDebug_ClearCache (& self -> handle );
2932
2985
_Py_RemoteDebug_CleanupProcHandle (& self -> handle );
2933
2986
}
2934
2987
PyObject_Del (self );
0 commit comments