|
1 | 1 | use anyhow; |
2 | 2 | use std::collections::HashMap; |
3 | | -use std::ffi::{c_void, CString}; |
4 | | -use pyo3::ffi; |
5 | | -use std::sync::atomic::{AtomicU8, AtomicPtr, Ordering}; |
| 3 | +use std::ffi::c_void; |
| 4 | +use std::sync::atomic::{AtomicU8, Ordering}; |
6 | 5 | use std::sync::Once; |
7 | 6 | use std::time::Duration; |
8 | | -use std::ptr; |
9 | 7 |
|
10 | 8 | use datadog_crashtracker::{ |
11 | 9 | register_runtime_stack_callback, CallbackError, CrashtrackerConfiguration, |
@@ -322,8 +320,6 @@ impl From<CallbackError> for CallbackResult { |
322 | 320 | } |
323 | 321 |
|
324 | 322 |
|
325 | | -// Global storage for the Python callback - using atomic pointer for signal safety |
326 | | -static PYTHON_CALLBACK: AtomicPtr<PyObject> = AtomicPtr::new(ptr::null_mut()); |
327 | 323 |
|
328 | 324 | /// Runtime-specific stack frame representation for FFI |
329 | 325 | /// |
@@ -392,185 +388,128 @@ impl RuntimeStackFramePy { |
392 | 388 | } |
393 | 389 | } |
394 | 390 |
|
395 | | -// Thread-local storage for the current emit_frame function (signal context) |
396 | | -thread_local! { |
397 | | - static EMIT_FRAME_FN: std::cell::Cell<Option<unsafe extern "C" fn(*const RuntimeStackFrame)>> = |
398 | | - const { std::cell::Cell::new(None) }; |
399 | | -} |
| 391 | +// Constants for signal-safe operation |
| 392 | +const MAX_FRAMES: usize = 64; |
| 393 | +const MAX_STRING_LEN: usize = 256; |
400 | 394 |
|
401 | | -// Helper function to emit a frame from Python during crash context |
402 | | -#[pyfunction(name = "emit_python_frame")] |
403 | | -pub fn emit_python_frame(frame: &RuntimeStackFramePy) -> PyResult<()> { |
404 | | - eprintln!("DEBUG: emit_python_frame called with function: {:?}", frame.function_name); |
405 | | - |
406 | | - EMIT_FRAME_FN.with(|emit_fn_cell| { |
407 | | - if let Some(emit_frame_fn) = emit_fn_cell.get() { |
408 | | - eprintln!("DEBUG: Found emit_frame_fn, creating C frame"); |
409 | | - |
410 | | - // Create CStrings that will live for the duration of this function call |
411 | | - let function_c_str = frame.function_name.as_ref().and_then(|s| CString::new(s.as_str()).ok()); |
412 | | - let file_c_str = frame.file_name.as_ref().and_then(|s| CString::new(s.as_str()).ok()); |
413 | | - let class_c_str = frame.class_name.as_ref().and_then(|s| CString::new(s.as_str()).ok()); |
414 | | - let module_c_str = frame.module_name.as_ref().and_then(|s| CString::new(s.as_str()).ok()); |
415 | | - |
416 | | - let c_frame = RuntimeStackFrame { |
417 | | - function_name: function_c_str.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), |
418 | | - file_name: file_c_str.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), |
419 | | - line_number: frame.line_number, |
420 | | - column_number: frame.column_number, |
421 | | - class_name: class_c_str.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), |
422 | | - module_name: module_c_str.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), |
423 | | - }; |
424 | | - |
425 | | - eprintln!("DEBUG: About to call emit_frame_fn"); |
426 | | - unsafe { |
427 | | - emit_frame_fn(&c_frame); |
428 | | - } |
429 | | - eprintln!("DEBUG: emit_frame_fn called successfully"); |
430 | | - } else { |
431 | | - eprintln!("DEBUG: emit_python_frame called but no emit function available"); |
432 | | - } |
433 | | - }); |
434 | | - Ok(()) |
| 395 | +// Stack-allocated buffer for signal-safe string handling |
| 396 | +struct StackBuffer { |
| 397 | + data: [u8; MAX_STRING_LEN], |
| 398 | + len: usize, |
435 | 399 | } |
436 | 400 |
|
437 | | -// C callback function that bridges to Python |
438 | | -unsafe extern "C" fn python_callback_bridge( |
439 | | - emit_frame: unsafe extern "C" fn(*const RuntimeStackFrame), |
440 | | - _context: *mut c_void, |
441 | | -) { |
442 | | - eprintln!("DEBUG: Python callback bridge invoked"); |
443 | | - |
444 | | - // Store the emit_frame function in thread-local storage for this callback execution |
445 | | - EMIT_FRAME_FN.with(|emit_fn_cell| { |
446 | | - emit_fn_cell.set(Some(emit_frame)); |
447 | | - }); |
448 | | - |
449 | | - let callback_ptr = PYTHON_CALLBACK.load(Ordering::SeqCst); |
450 | | - if !callback_ptr.is_null() { |
451 | | - eprintln!("DEBUG: Found Python callback, calling it"); |
452 | | - |
453 | | - // This is unsafe, but necessary for runtime callbacks |
454 | | - Python::with_gil(|py| { |
455 | | - let py_callback = &*callback_ptr; |
456 | | - // Call the Python callback - it should call emit_python_frame for each frame |
457 | | - if let Err(_e) = py_callback.call0(py) { |
458 | | - eprintln!("DEBUG: Error calling Python callback"); |
459 | | - } else { |
460 | | - eprintln!("DEBUG: Python callback executed successfully"); |
461 | | - } |
462 | | - }); |
463 | | - } else { |
464 | | - eprintln!("DEBUG: No Python callback found"); |
| 401 | +impl StackBuffer { |
| 402 | + const fn new() -> Self { |
| 403 | + Self { |
| 404 | + data: [0u8; MAX_STRING_LEN], |
| 405 | + len: 0, |
| 406 | + } |
465 | 407 | } |
466 | 408 |
|
467 | | - // Clear the emit_frame function after callback execution |
468 | | - EMIT_FRAME_FN.with(|emit_fn_cell| { |
469 | | - emit_fn_cell.set(None); |
470 | | - }); |
471 | | -} |
| 409 | + fn as_ptr(&self) -> *const i8 { |
| 410 | + self.data.as_ptr() as *const i8 |
| 411 | + } |
472 | 412 |
|
473 | | -/// Register a runtime stack collection callback |
474 | | -/// |
475 | | -/// This function allows language runtimes to register a callback that will be invoked |
476 | | -/// during crash handling to collect runtime-specific stack traces. |
477 | | -/// |
478 | | -/// # Arguments |
479 | | -/// - `callback`: The Python callback function to invoke during crashes |
480 | | -/// |
481 | | -/// # Returns |
482 | | -/// - `CallbackResult::Ok` if registration succeeds |
483 | | -/// - `CallbackResult::AlreadyRegistered` if a callback is already registered |
484 | | -/// - `CallbackResult::NullCallback` if the callback function is null |
485 | | -/// |
486 | | -/// # Safety |
487 | | -/// - The callback must be signal-safe |
488 | | -/// - Only one callback can be registered at a time |
489 | | -#[pyfunction(name = "crashtracker_register_runtime_callback")] |
490 | | -pub fn crashtracker_register_runtime_callback( |
491 | | - py: Python, |
492 | | - callback: PyObject, |
493 | | -) -> CallbackResult { |
494 | | - // Create a boxed PyObject on the heap so it has a stable address |
495 | | - let callback_box = Box::new(callback.clone_ref(py)); |
496 | | - let callback_ptr = Box::into_raw(callback_box); |
497 | | - |
498 | | - // Try to store the callback pointer atomically |
499 | | - let previous = PYTHON_CALLBACK.compare_exchange( |
500 | | - std::ptr::null_mut(), |
501 | | - callback_ptr, |
502 | | - Ordering::SeqCst, |
503 | | - Ordering::SeqCst, |
504 | | - ); |
505 | | - |
506 | | - match previous { |
507 | | - Ok(_) => { |
508 | | - match register_runtime_stack_callback(python_callback_bridge, std::ptr::null_mut()) { |
509 | | - Ok(()) => CallbackResult::Ok, |
510 | | - Err(e) => e.into(), |
511 | | - } |
512 | | - } |
513 | | - Err(_) => { |
514 | | - let _ = unsafe { Box::from_raw(callback_ptr) }; |
515 | | - CallbackResult::AlreadyRegistered |
516 | | - } |
| 413 | + fn set_from_str(&mut self, s: &str) { |
| 414 | + let bytes = s.as_bytes(); |
| 415 | + let copy_len = bytes.len().min(MAX_STRING_LEN - 1); |
| 416 | + self.data[..copy_len].copy_from_slice(&bytes[..copy_len]); |
| 417 | + self.data[copy_len] = 0; // null terminator |
| 418 | + self.len = copy_len; |
517 | 419 | } |
| 420 | + |
518 | 421 | } |
519 | 422 |
|
520 | | -// Native C callback function that directly emits runtime stack frames |
| 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 |
521 | 426 | unsafe extern "C" fn native_runtime_stack_callback( |
522 | 427 | emit_frame: unsafe extern "C" fn(*const RuntimeStackFrame), |
523 | 428 | _context: *mut c_void, |
524 | 429 | ) { |
| 430 | + // For maximum safety, we'll fall back to the Python with_gil approach |
| 431 | + // but with improved buffer management and bounds checking |
525 | 432 | Python::with_gil(|py| { |
526 | | - let frame = py.eval(ffi::c_str!("__import__('sys')._getframe()"), None, None); |
527 | | - if let Ok(frame_obj) = frame { |
528 | | - emit_python_stack_from_frame(emit_frame, py, frame_obj); |
| 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 | + } |
529 | 440 | } |
530 | 441 | }); |
531 | 442 | } |
532 | 443 |
|
533 | | -fn emit_python_stack_from_frame( |
| 444 | +// Stack walking with bounds checking and stack-allocated buffers |
| 445 | +fn emit_python_stack_with_bounds( |
534 | 446 | emit_frame: unsafe extern "C" fn(*const RuntimeStackFrame), |
535 | 447 | py: Python, |
536 | 448 | frame_obj: Bound<'_, PyAny>, |
| 449 | + depth: usize, |
537 | 450 | ) { |
538 | | - let code = frame_obj.getattr("f_code"); |
539 | | - let back = frame_obj.getattr("f_back"); |
540 | | - |
541 | | - if let (Ok(code_obj), Ok(back_obj)) = (code, back) { |
542 | | - let function_name = code_obj.getattr("co_name") |
543 | | - .and_then(|n| n.extract::<String>()) |
544 | | - .unwrap_or_else(|_| "<unknown>".to_string()); |
545 | | - |
546 | | - let file_name = code_obj.getattr("co_filename") |
547 | | - .and_then(|f| f.extract::<String>()) |
548 | | - .unwrap_or_else(|_| "<unknown>".to_string()); |
549 | | - |
550 | | - let line_number = frame_obj.getattr("f_lineno") |
551 | | - .and_then(|l| l.extract::<u32>()) |
552 | | - .unwrap_or(0); |
| 451 | + if depth >= MAX_FRAMES { |
| 452 | + return; // Prevent stack overflow |
| 453 | + } |
553 | 454 |
|
554 | | - let function_c_str = CString::new(function_name).unwrap_or_else(|_| CString::new("<invalid>").unwrap()); |
555 | | - let file_c_str = CString::new(file_name).unwrap_or_else(|_| CString::new("<invalid>").unwrap()); |
| 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 |
| 461 | + let mut function_buf = StackBuffer::new(); |
| 462 | + let mut file_buf = StackBuffer::new(); |
| 463 | + |
| 464 | + function_buf.set_from_str(&function_name); |
| 465 | + file_buf.set_from_str(&file_name); |
| 466 | + |
| 467 | + let c_frame = RuntimeStackFrame { |
| 468 | + function_name: function_buf.as_ptr(), |
| 469 | + file_name: file_buf.as_ptr(), |
| 470 | + line_number, |
| 471 | + column_number: 0, |
| 472 | + class_name: std::ptr::null(), |
| 473 | + module_name: std::ptr::null(), |
| 474 | + }; |
| 475 | + |
| 476 | + unsafe { |
| 477 | + emit_frame(&c_frame); |
| 478 | + } |
556 | 479 |
|
557 | | - let c_frame = RuntimeStackFrame { |
558 | | - function_name: function_c_str.as_ptr(), |
559 | | - file_name: file_c_str.as_ptr(), |
560 | | - line_number, |
561 | | - column_number: 0, |
562 | | - class_name: std::ptr::null(), |
563 | | - module_name: std::ptr::null(), |
564 | | - }; |
| 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 | + } |
| 485 | + } |
| 486 | +} |
565 | 487 |
|
566 | | - unsafe { |
567 | | - emit_frame(&c_frame); |
| 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 | + } |
568 | 500 | } |
| 501 | + } |
| 502 | + "<unknown>".to_string() |
| 503 | +} |
569 | 504 |
|
570 | | - if !back_obj.is_none() { |
571 | | - emit_python_stack_from_frame(emit_frame, py, back_obj); |
| 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; |
572 | 510 | } |
573 | 511 | } |
| 512 | + 0 |
574 | 513 | } |
575 | 514 |
|
576 | 515 | /// Register the native runtime stack collection callback |
|
0 commit comments