11use anyhow;
22use std:: collections:: HashMap ;
3- use std:: ffi:: c_void;
3+ use std:: ffi:: { c_void, c_char , c_int , c_long , CStr } ;
44use std:: sync:: atomic:: { AtomicU8 , Ordering } ;
55use std:: sync:: Once ;
66use std:: time:: Duration ;
7+ use std:: ptr;
78
89use datadog_crashtracker:: {
910 register_runtime_stack_callback, CallbackError , CrashtrackerConfiguration ,
@@ -12,6 +13,50 @@ use datadog_crashtracker::{
1213use ddcommon:: Endpoint ;
1314use pyo3:: prelude:: * ;
1415
16+ // Python C API structures for direct stack walking
17+ // These are opaque pointers to avoid ABI dependencies
18+ #[ repr( C ) ]
19+ struct PyObject {
20+ _private : [ u8 ; 0 ] ,
21+ }
22+
23+ #[ repr( C ) ]
24+ struct PyCodeObject {
25+ _private : [ u8 ; 0 ] ,
26+ }
27+
28+ #[ repr( C ) ]
29+ struct PyFrameObject {
30+ _private : [ u8 ; 0 ] ,
31+ }
32+
33+ #[ repr( C ) ]
34+ struct PyThreadState {
35+ _private : [ u8 ; 0 ] ,
36+ }
37+
38+ #[ repr( C ) ]
39+ struct PyInterpreterState {
40+ _private : [ u8 ; 0 ] ,
41+ }
42+
43+ // External C API functions we'll link to
44+ // Using more stable public API functions that are available across Python versions
45+ extern "C" {
46+ // Minimal set of stable Python C API functions available across versions
47+
48+ // Frame access - most stable approach
49+ fn PyEval_GetFrame ( ) -> * mut PyFrameObject ;
50+ fn PyFrame_GetBack ( frame : * mut PyFrameObject ) -> * mut PyFrameObject ;
51+ fn PyFrame_GetCode ( frame : * mut PyFrameObject ) -> * mut PyCodeObject ;
52+ fn PyFrame_GetLineNumber ( frame : * mut PyFrameObject ) -> c_int ;
53+
54+ // Object attribute access - very stable API
55+ fn PyObject_GetAttrString ( obj : * mut PyObject , attr_name : * const c_char ) -> * mut PyObject ;
56+ fn PyUnicode_AsUTF8 ( unicode : * mut PyObject ) -> * const c_char ;
57+
58+ }
59+
1560pub trait RustWrapper {
1661 type Inner ;
1762 const INNER_TYPE_NAME : & ' static str ;
@@ -418,100 +463,134 @@ impl StackBuffer {
418463 self . len = copy_len;
419464 }
420465
466+ fn set_from_cstr ( & mut self , cstr_ptr : * const c_char ) {
467+ if cstr_ptr. is_null ( ) {
468+ self . set_from_str ( "<null>" ) ;
469+ return ;
470+ }
471+
472+ unsafe {
473+ let cstr = match CStr :: from_ptr ( cstr_ptr) . to_str ( ) {
474+ Ok ( s) => s,
475+ Err ( _) => "<invalid_utf8>" ,
476+ } ;
477+ self . set_from_str ( cstr) ;
478+ }
479+ }
421480}
422481
423- // Improved signal-safer Python frame walker
424- // This approach reuses the existing Python high-level API but with better bounds checking
425- // and stack-allocated buffers to minimize allocations
426- unsafe extern "C" fn native_runtime_stack_callback (
427- emit_frame : unsafe extern "C" fn ( * const RuntimeStackFrame ) ,
428- _context : * mut c_void ,
429- ) {
430- // For maximum safety, we'll fall back to the Python with_gil approach
431- // but with improved buffer management and bounds checking
432- Python :: with_gil ( |py| {
433- // Try to get the current frame using Python's sys._getframe()
434- if let Ok ( sys_module) = py. import ( "sys" ) {
435- if let Ok ( getframe_fn) = sys_module. getattr ( "_getframe" ) {
436- if let Ok ( frame_obj) = getframe_fn. call0 ( ) {
437- emit_python_stack_with_bounds ( emit_frame, py, frame_obj, 0 ) ;
438- }
439- }
482+
483+ // Static string constants for attribute names (must be null-terminated)
484+ static CO_NAME : & [ u8 ] = b"co_name\0 " ;
485+ static CO_FILENAME : & [ u8 ] = b"co_filename\0 " ;
486+
487+ unsafe fn extract_frame_info (
488+ frame : * mut PyFrameObject ,
489+ function_buf : & mut StackBuffer ,
490+ file_buf : & mut StackBuffer ,
491+ ) -> u32 {
492+ if frame. is_null ( ) {
493+ function_buf. set_from_str ( "<null_frame>" ) ;
494+ file_buf. set_from_str ( "<null_frame>" ) ;
495+ return 0 ;
496+ }
497+
498+ // Get code object
499+ let code = PyFrame_GetCode ( frame) ;
500+ if code. is_null ( ) {
501+ function_buf. set_from_str ( "<no_code>" ) ;
502+ file_buf. set_from_str ( "<no_code>" ) ;
503+ return 0 ;
504+ }
505+
506+ // Extract function name
507+ let name_obj = PyObject_GetAttrString ( code as * mut PyObject , CO_NAME . as_ptr ( ) as * const c_char ) ;
508+ if !name_obj. is_null ( ) {
509+ let name_cstr = PyUnicode_AsUTF8 ( name_obj) ;
510+ if !name_cstr. is_null ( ) {
511+ function_buf. set_from_cstr ( name_cstr) ;
512+ } else {
513+ function_buf. set_from_str ( "<invalid_name>" ) ;
440514 }
441- } ) ;
515+ } else {
516+ function_buf. set_from_str ( "<unknown_function>" ) ;
517+ }
518+
519+ // Extract filename
520+ let filename_obj = PyObject_GetAttrString ( code as * mut PyObject , CO_FILENAME . as_ptr ( ) as * const c_char ) ;
521+ if !filename_obj. is_null ( ) {
522+ let filename_cstr = PyUnicode_AsUTF8 ( filename_obj) ;
523+ if !filename_cstr. is_null ( ) {
524+ file_buf. set_from_cstr ( filename_cstr) ;
525+ } else {
526+ file_buf. set_from_str ( "<invalid_filename>" ) ;
527+ }
528+ } else {
529+ file_buf. set_from_str ( "<unknown_file>" ) ;
530+ }
531+
532+ // Get line number
533+ let line_num = PyFrame_GetLineNumber ( frame) ;
534+ if line_num > 0 {
535+ line_num as u32
536+ } else {
537+ 0
538+ }
442539}
443540
444- // Stack walking with bounds checking and stack-allocated buffers
445- fn emit_python_stack_with_bounds (
541+ unsafe fn walk_frame_chain (
446542 emit_frame : unsafe extern "C" fn ( * const RuntimeStackFrame ) ,
447- py : Python ,
448- frame_obj : Bound < ' _ , PyAny > ,
543+ mut frame : * mut PyFrameObject ,
449544 depth : usize ,
450545) {
451- if depth >= MAX_FRAMES {
452- return ; // Prevent stack overflow
546+ if depth >= MAX_FRAMES || frame . is_null ( ) {
547+ return ;
453548 }
454549
455- // Get frame information with error handling
456- let function_name = get_frame_attr_string ( py, & frame_obj, "f_code" , "co_name" ) ;
457- let file_name = get_frame_attr_string ( py, & frame_obj, "f_code" , "co_filename" ) ;
458- let line_number = get_frame_line_number ( py, & frame_obj) ;
459-
460- // Use stack-allocated buffers
550+ // Stack-allocate buffers for this frame
461551 let mut function_buf = StackBuffer :: new ( ) ;
462552 let mut file_buf = StackBuffer :: new ( ) ;
463553
464- function_buf. set_from_str ( & function_name) ;
465- file_buf. set_from_str ( & file_name) ;
554+ let line_number = extract_frame_info ( frame, & mut function_buf, & mut file_buf) ;
466555
467556 let c_frame = RuntimeStackFrame {
468557 function_name : function_buf. as_ptr ( ) ,
469558 file_name : file_buf. as_ptr ( ) ,
470559 line_number,
471560 column_number : 0 ,
472- class_name : std :: ptr:: null ( ) ,
473- module_name : std :: ptr:: null ( ) ,
561+ class_name : ptr:: null ( ) ,
562+ module_name : ptr:: null ( ) ,
474563 } ;
475564
476- unsafe {
477- emit_frame ( & c_frame) ;
478- }
565+ emit_frame ( & c_frame) ;
479566
480- // Get the back frame and recurse with depth limit
481- if let Ok ( back_frame) = frame_obj. getattr ( "f_back" ) {
482- if !back_frame. is_none ( ) {
483- emit_python_stack_with_bounds ( emit_frame, py, back_frame, depth + 1 ) ;
484- }
567+ // Get the previous frame in the chain
568+ let back_frame = PyFrame_GetBack ( frame) ;
569+ if !back_frame. is_null ( ) {
570+ walk_frame_chain ( emit_frame, back_frame, depth + 1 ) ;
485571 }
486572}
487573
488- // Safe attribute extraction with fallback
489- fn get_frame_attr_string ( _py : Python , frame : & Bound < ' _ , PyAny > , attr1 : & str , attr2 : & str ) -> String {
490- if let Ok ( code) = frame. getattr ( attr1) {
491- if let Ok ( name) = code. getattr ( attr2) {
492- if let Ok ( name_str) = name. extract :: < String > ( ) {
493- // Truncate if too long for our buffer
494- if name_str. len ( ) < MAX_STRING_LEN {
495- return name_str;
496- } else {
497- return name_str. chars ( ) . take ( MAX_STRING_LEN - 4 ) . collect :: < String > ( ) + "..." ;
498- }
499- }
500- }
574+ unsafe fn walk_current_thread_stack (
575+ emit_frame : unsafe extern "C" fn ( * const RuntimeStackFrame ) ,
576+ ) {
577+ let frame = PyEval_GetFrame ( ) ;
578+ if !frame. is_null ( ) {
579+ walk_frame_chain ( emit_frame, frame, 0 ) ;
501580 }
502- "<unknown>" . to_string ( )
503581}
504582
505- // Safe line number extraction
506- fn get_frame_line_number ( _py : Python , frame : & Bound < ' _ , PyAny > ) -> u32 {
507- if let Ok ( lineno ) = frame . getattr ( "f_lineno" ) {
508- if let Ok ( line_num ) = lineno . extract :: < u32 > ( ) {
509- return line_num ;
510- }
511- }
512- 0
583+ // Native signal-safe Python frame walker
584+ // This implementation uses direct Python C API calls
585+ unsafe extern "C" fn native_runtime_stack_callback (
586+ emit_frame : unsafe extern "C" fn ( * const RuntimeStackFrame ) ,
587+ _context : * mut c_void ,
588+ ) {
589+ // Walk the current thread's stack
590+ walk_current_thread_stack ( emit_frame ) ;
513591}
514592
593+
515594/// Register the native runtime stack collection callback
516595///
517596/// This function registers a native callback that directly collects Python runtime
0 commit comments