11use anyhow;
2+ #[ cfg( unix) ]
3+ use std:: cmp;
24use std:: collections:: HashMap ;
35use std:: ffi:: { c_char, c_int, c_void} ;
46use std:: ptr;
7+ #[ cfg( unix) ]
8+ use std:: slice;
9+ #[ cfg( unix) ]
10+ use std:: sync:: atomic:: AtomicBool ;
511use std:: sync:: atomic:: { AtomicU8 , Ordering } ;
612use std:: sync:: Once ;
713use std:: time:: Duration ;
814
915use libdd_common:: Endpoint ;
1016use libdd_crashtracker:: {
11- register_runtime_stacktrace_string_callback, CrashtrackerConfiguration ,
12- CrashtrackerReceiverConfig , Metadata , StacktraceCollection ,
17+ register_runtime_frame_callback, register_runtime_stacktrace_string_callback,
18+ CrashtrackerConfiguration , CrashtrackerReceiverConfig , Metadata , RuntimeStackFrame ,
19+ StacktraceCollection ,
1320} ;
1421use pyo3:: prelude:: * ;
1522
@@ -29,8 +36,11 @@ static DUMP_TRACEBACK_INIT: std::sync::Once = std::sync::Once::new();
2936extern "C" {
3037 fn pipe ( pipefd : * mut [ c_int ; 2 ] ) -> c_int ;
3138 fn read ( fd : c_int , buf : * mut c_void , count : usize ) -> isize ;
39+ fn write ( fd : c_int , buf : * const c_void , count : usize ) -> isize ;
3240 fn close ( fd : c_int ) -> c_int ;
3341 fn fcntl ( fd : c_int , cmd : c_int , arg : c_int ) -> c_int ;
42+ #[ cfg( unix) ]
43+ fn PyThreadState_Next ( prev : * mut pyo3_ffi:: PyThreadState ) -> * mut pyo3_ffi:: PyThreadState ;
3444}
3545
3646pub trait RustWrapper {
@@ -273,8 +283,19 @@ pub fn crashtracker_init<'py>(
273283 unsafe {
274284 init_dump_traceback_fn ( ) ;
275285 }
276- if let Err ( e) = register_runtime_stacktrace_string_callback ( native_runtime_stack_callback) {
277- eprintln ! ( "Failed to register runtime callback: {}" , e) ;
286+ let dump_fn_available = unsafe { get_cached_dump_traceback_fn ( ) . is_some ( ) } ;
287+ if dump_fn_available {
288+ if let Err ( e) =
289+ register_runtime_stacktrace_string_callback (
290+ native_runtime_stack_string_callback,
291+ )
292+ {
293+ eprintln ! ( "Failed to register runtime stacktrace callback: {}" , e) ;
294+ }
295+ } else if let Err ( e) =
296+ register_runtime_frame_callback ( native_runtime_stack_frame_callback)
297+ {
298+ eprintln ! ( "Failed to register runtime frame callback: {}" , e) ;
278299 }
279300 }
280301 match libdd_crashtracker:: init ( config, receiver_config, metadata) {
@@ -333,6 +354,138 @@ pub fn crashtracker_receiver() -> anyhow::Result<()> {
333354
334355const MAX_TRACEBACK_SIZE : usize = 8 * 1024 ; // 8KB
335356
357+ #[ cfg( unix) ]
358+ const FRAME_FUNCTION_CAP : usize = 256 ;
359+ #[ cfg( unix) ]
360+ const FRAME_FILE_CAP : usize = 512 ;
361+ #[ cfg( unix) ]
362+ const FRAME_TYPE_CAP : usize = 256 ;
363+
364+ #[ cfg( unix) ]
365+ static FRAME_COLLECTION_GUARD : AtomicBool = AtomicBool :: new ( false ) ;
366+
367+ #[ cfg( unix) ]
368+ unsafe fn capture_frames_via_python ( emit_frame : unsafe extern "C" fn ( & RuntimeStackFrame ) ) {
369+ let mut emitted = false ;
370+
371+ let current = pyo3_ffi:: PyThreadState_Get ( ) ;
372+
373+ if !current. is_null ( ) {
374+ let _ = collect_and_emit_frames_for_thread ( current, emit_frame) ;
375+ }
376+ }
377+
378+ #[ cfg( unix) ]
379+ unsafe fn collect_and_emit_frames_for_thread (
380+ tstate : * mut pyo3_ffi:: PyThreadState ,
381+ emit_frame : unsafe extern "C" fn ( & RuntimeStackFrame ) ,
382+ ) -> bool {
383+ if tstate. is_null ( ) {
384+ return false ;
385+ }
386+
387+ let mut emitted = false ;
388+ let mut frame = thread_top_frame ( tstate) ;
389+
390+ while !frame. is_null ( ) {
391+ if emit_python_frame ( frame, emit_frame) {
392+ emitted = true ;
393+ }
394+ frame = advance_frame ( frame) ;
395+ }
396+
397+ emitted
398+ }
399+
400+ unsafe fn thread_top_frame ( tstate : * mut pyo3_ffi:: PyThreadState ) -> * mut pyo3_ffi:: PyFrameObject {
401+ if tstate. is_null ( ) {
402+ ptr:: null_mut ( )
403+ } else {
404+ let frame = pyo3_ffi:: PyThreadState_GetFrame ( tstate) ;
405+ #[ cfg( not( Py_3_11 ) ) ]
406+ {
407+ if !frame. is_null ( ) {
408+ pyo3_ffi:: Py_XINCREF ( frame as * mut pyo3_ffi:: PyObject ) ;
409+ }
410+ }
411+ frame
412+ }
413+ }
414+
415+ unsafe fn advance_frame ( frame : * mut pyo3_ffi:: PyFrameObject ) -> * mut pyo3_ffi:: PyFrameObject {
416+ if frame. is_null ( ) {
417+ return ptr:: null_mut ( ) ;
418+ }
419+ let back = pyo3_ffi:: PyFrame_GetBack ( frame) ;
420+ pyo3_ffi:: Py_DecRef ( frame as * mut pyo3_ffi:: PyObject ) ;
421+ back
422+ }
423+
424+ #[ cfg( unix) ]
425+ unsafe fn emit_python_frame (
426+ frame : * mut pyo3_ffi:: PyFrameObject ,
427+ emit_frame : unsafe extern "C" fn ( & RuntimeStackFrame ) ,
428+ ) -> bool {
429+ if frame. is_null ( ) {
430+ return false ;
431+ }
432+
433+ let mut file = get_code_attr_utf8 ( frame, b"co_filename\0 " ) ;
434+ if file. len ( ) > FRAME_FILE_CAP {
435+ file. truncate ( FRAME_FILE_CAP ) ;
436+ }
437+
438+ let mut function = get_code_attr_utf8 ( frame, b"co_name\0 " ) ;
439+ if function. len ( ) > FRAME_FUNCTION_CAP {
440+ function. truncate ( FRAME_FUNCTION_CAP ) ;
441+ }
442+ let line_number = pyo3_ffi:: PyFrame_GetLineNumber ( frame) ;
443+
444+ let runtime_frame = RuntimeStackFrame {
445+ line : if line_number < 0 {
446+ 0
447+ } else {
448+ line_number as u32
449+ } ,
450+ column : 0 ,
451+ function : function. as_slice ( ) ,
452+ file : file. as_slice ( ) ,
453+ type_name : & [ ] ,
454+ } ;
455+
456+ emit_frame ( & runtime_frame) ;
457+ true
458+ }
459+
460+ #[ cfg( unix) ]
461+ unsafe fn get_code_attr_utf8 ( frame : * mut pyo3_ffi:: PyFrameObject , attr : & [ u8 ] ) -> Vec < u8 > {
462+ let code_obj = pyo3_ffi:: PyFrame_GetCode ( frame) as * mut pyo3_ffi:: PyObject ;
463+ if code_obj. is_null ( ) {
464+ return Vec :: new ( ) ;
465+ }
466+ let attr_obj = pyo3_ffi:: PyObject_GetAttrString ( code_obj, attr. as_ptr ( ) as * const c_char ) ;
467+ pyo3_ffi:: Py_DecRef ( code_obj) ;
468+ if attr_obj. is_null ( ) {
469+ return Vec :: new ( ) ;
470+ }
471+ let data = py_unicode_to_vec ( attr_obj) ;
472+ pyo3_ffi:: Py_DecRef ( attr_obj) ;
473+ data
474+ }
475+
476+ #[ cfg( unix) ]
477+ unsafe fn py_unicode_to_vec ( obj : * mut pyo3_ffi:: PyObject ) -> Vec < u8 > {
478+ if obj. is_null ( ) {
479+ return Vec :: new ( ) ;
480+ }
481+ let mut size: pyo3_ffi:: Py_ssize_t = 0 ;
482+ let data = pyo3_ffi:: PyUnicode_AsUTF8AndSize ( obj, & mut size) ;
483+ if data. is_null ( ) || size <= 0 {
484+ return Vec :: new ( ) ;
485+ }
486+ slice:: from_raw_parts ( data as * const u8 , size as usize ) . to_vec ( )
487+ }
488+
336489// Attempt to resolve _Py_DumpTracebackThreads at runtime
337490// Try to link once during registration
338491unsafe fn init_dump_traceback_fn ( ) {
@@ -438,8 +591,39 @@ unsafe fn dump_python_traceback_as_string(
438591 emit_stacktrace_string ( "<traceback_read_failed>\0 " . as_ptr ( ) as * const c_char ) ;
439592}
440593
441- unsafe extern "C" fn native_runtime_stack_callback (
594+ unsafe fn dump_python_traceback_as_frames ( emit_frame : unsafe extern "C" fn ( & RuntimeStackFrame ) ) {
595+ #[ cfg( unix) ]
596+ {
597+ if emit_frame as usize == 0 {
598+ return ;
599+ }
600+
601+ if FRAME_COLLECTION_GUARD
602+ . compare_exchange ( false , true , Ordering :: SeqCst , Ordering :: SeqCst )
603+ . is_err ( )
604+ {
605+ return ;
606+ }
607+
608+ capture_frames_via_python ( emit_frame) ;
609+
610+ FRAME_COLLECTION_GUARD . store ( false , Ordering :: SeqCst ) ;
611+ }
612+
613+ #[ cfg( not( unix) ) ]
614+ {
615+ let _ = emit_frame;
616+ }
617+ }
618+
619+ unsafe extern "C" fn native_runtime_stack_string_callback (
442620 emit_stacktrace_string : unsafe extern "C" fn ( * const c_char ) ,
443621) {
444622 dump_python_traceback_as_string ( emit_stacktrace_string) ;
445623}
624+
625+ unsafe extern "C" fn native_runtime_stack_frame_callback (
626+ emit_frame : unsafe extern "C" fn ( & RuntimeStackFrame ) ,
627+ ) {
628+ dump_python_traceback_as_frames ( emit_frame) ;
629+ }
0 commit comments