1use std::fmt;
38
39use crate::error::{CudaError, CudaResult};
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
47pub enum DebugLevel {
48 Off,
50 Error,
52 Warn,
54 #[default]
56 Info,
57 Debug,
59 Trace,
61}
62
63impl fmt::Display for DebugLevel {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 match self {
66 Self::Off => write!(f, "OFF"),
67 Self::Error => write!(f, "ERROR"),
68 Self::Warn => write!(f, "WARN"),
69 Self::Info => write!(f, "INFO"),
70 Self::Debug => write!(f, "DEBUG"),
71 Self::Trace => write!(f, "TRACE"),
72 }
73 }
74}
75
76#[derive(Debug, Clone)]
82pub struct KernelDebugConfig {
83 pub debug_level: DebugLevel,
85 pub enable_bounds_check: bool,
87 pub enable_nan_check: bool,
89 pub enable_inf_check: bool,
91 pub enable_race_detection: bool,
93 pub print_buffer_size: usize,
95 pub max_print_per_thread: usize,
97}
98
99impl Default for KernelDebugConfig {
100 fn default() -> Self {
101 Self {
102 debug_level: DebugLevel::Info,
103 enable_bounds_check: true,
104 enable_nan_check: true,
105 enable_inf_check: true,
106 enable_race_detection: false,
107 print_buffer_size: 1024 * 1024, max_print_per_thread: 32,
109 }
110 }
111}
112
113#[derive(Debug, Clone, PartialEq)]
119pub enum DebugEventType {
120 OutOfBounds {
122 address: u64,
124 size: usize,
126 },
127 NanDetected {
129 register: String,
131 value: f64,
133 },
134 InfDetected {
136 register: String,
138 },
139 RaceCondition {
141 address: u64,
143 },
144 Assertion {
146 condition: String,
148 file: String,
150 line: u32,
152 },
153 Printf {
155 format: String,
157 },
158 Breakpoint {
160 id: u32,
162 },
163}
164
165impl DebugEventType {
166 fn tag(&self) -> &'static str {
168 match self {
169 Self::OutOfBounds { .. } => "OOB",
170 Self::NanDetected { .. } => "NaN",
171 Self::InfDetected { .. } => "Inf",
172 Self::RaceCondition { .. } => "RACE",
173 Self::Assertion { .. } => "ASSERT",
174 Self::Printf { .. } => "PRINTF",
175 Self::Breakpoint { .. } => "BP",
176 }
177 }
178
179 fn same_kind(&self, other: &Self) -> bool {
182 std::mem::discriminant(self) == std::mem::discriminant(other)
183 }
184}
185
186#[derive(Debug, Clone)]
192pub struct DebugEvent {
193 pub event_type: DebugEventType,
195 pub thread_id: (u32, u32, u32),
197 pub block_id: (u32, u32, u32),
199 pub timestamp_ns: u64,
201 pub message: String,
203}
204
205impl fmt::Display for DebugEvent {
206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207 write!(
208 f,
209 "[{tag}] block({bx},{by},{bz}) thread({tx},{ty},{tz}) @{ts}ns: {msg}",
210 tag = self.event_type.tag(),
211 bx = self.block_id.0,
212 by = self.block_id.1,
213 bz = self.block_id.2,
214 tx = self.thread_id.0,
215 ty = self.thread_id.1,
216 tz = self.thread_id.2,
217 ts = self.timestamp_ns,
218 msg = self.message,
219 )
220 }
221}
222
223#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
229pub enum WatchType {
230 Read,
232 Write,
234 ReadWrite,
236}
237
238#[derive(Debug, Clone)]
243#[allow(dead_code)]
244struct Breakpoint {
245 id: u32,
246 line: u32,
247}
248
249#[derive(Debug, Clone)]
250#[allow(dead_code)]
251struct Watchpoint {
252 id: u32,
253 address: u64,
254 size: usize,
255 watch_type: WatchType,
256}
257
258#[derive(Debug)]
267pub struct KernelDebugger {
268 config: KernelDebugConfig,
269 breakpoints: Vec<Breakpoint>,
270 watchpoints: Vec<Watchpoint>,
271 next_bp_id: u32,
272 next_wp_id: u32,
273}
274
275impl KernelDebugger {
276 pub fn new(config: KernelDebugConfig) -> Self {
278 Self {
279 config,
280 breakpoints: Vec::new(),
281 watchpoints: Vec::new(),
282 next_bp_id: 1,
283 next_wp_id: 1,
284 }
285 }
286
287 pub fn attach(&mut self, kernel_name: &str) -> CudaResult<DebugSession> {
296 if kernel_name.is_empty() {
297 return Err(CudaError::InvalidValue);
298 }
299 Ok(DebugSession {
300 kernel_name: kernel_name.to_owned(),
301 events: Vec::new(),
302 config: self.config.clone(),
303 })
304 }
305
306 pub fn set_breakpoint(&mut self, line: u32) -> u32 {
308 let id = self.next_bp_id;
309 self.next_bp_id = self.next_bp_id.saturating_add(1);
310 self.breakpoints.push(Breakpoint { id, line });
311 id
312 }
313
314 pub fn remove_breakpoint(&mut self, bp_id: u32) -> bool {
316 let before = self.breakpoints.len();
317 self.breakpoints.retain(|bp| bp.id != bp_id);
318 self.breakpoints.len() < before
319 }
320
321 pub fn watchpoint(&mut self, address: u64, size: usize, watch_type: WatchType) -> u32 {
323 let id = self.next_wp_id;
324 self.next_wp_id = self.next_wp_id.saturating_add(1);
325 self.watchpoints.push(Watchpoint {
326 id,
327 address,
328 size,
329 watch_type,
330 });
331 id
332 }
333
334 pub fn config(&self) -> &KernelDebugConfig {
336 &self.config
337 }
338}
339
340#[derive(Debug, Clone, Default, PartialEq, Eq)]
346pub struct DebugSummary {
347 pub total_events: usize,
349 pub errors: usize,
351 pub warnings: usize,
353 pub nan_count: usize,
355 pub inf_count: usize,
357 pub oob_count: usize,
359 pub race_count: usize,
361}
362
363#[derive(Debug)]
371pub struct DebugSession {
372 kernel_name: String,
373 events: Vec<DebugEvent>,
374 #[allow(dead_code)]
375 config: KernelDebugConfig,
376}
377
378impl DebugSession {
379 pub fn kernel_name(&self) -> &str {
381 &self.kernel_name
382 }
383
384 pub fn events(&self) -> &[DebugEvent] {
386 &self.events
387 }
388
389 pub fn add_event(&mut self, event: DebugEvent) {
391 self.events.push(event);
392 }
393
394 pub fn filter_events(&self, event_type: &DebugEventType) -> Vec<&DebugEvent> {
397 self.events
398 .iter()
399 .filter(|e| e.event_type.same_kind(event_type))
400 .collect()
401 }
402
403 pub fn summary(&self) -> DebugSummary {
405 let mut s = DebugSummary {
406 total_events: self.events.len(),
407 ..DebugSummary::default()
408 };
409 for ev in &self.events {
410 match &ev.event_type {
411 DebugEventType::OutOfBounds { .. } => {
412 s.oob_count += 1;
413 s.errors += 1;
414 }
415 DebugEventType::NanDetected { .. } => {
416 s.nan_count += 1;
417 s.warnings += 1;
418 }
419 DebugEventType::InfDetected { .. } => {
420 s.inf_count += 1;
421 s.warnings += 1;
422 }
423 DebugEventType::RaceCondition { .. } => {
424 s.race_count += 1;
425 s.errors += 1;
426 }
427 DebugEventType::Assertion { .. } => {
428 s.errors += 1;
429 }
430 DebugEventType::Printf { .. } | DebugEventType::Breakpoint { .. } => {}
431 }
432 }
433 s
434 }
435
436 pub fn format_report(&self) -> String {
438 let summary = self.summary();
439 let mut out = String::with_capacity(512);
440 out.push_str(&format!("=== Debug Report: {} ===\n", self.kernel_name));
441 out.push_str(&format!(
442 "Total events: {} (errors: {}, warnings: {})\n",
443 summary.total_events, summary.errors, summary.warnings
444 ));
445 if summary.oob_count > 0 {
446 out.push_str(&format!(" Out-of-bounds: {}\n", summary.oob_count));
447 }
448 if summary.nan_count > 0 {
449 out.push_str(&format!(" NaN detected: {}\n", summary.nan_count));
450 }
451 if summary.inf_count > 0 {
452 out.push_str(&format!(" Inf detected: {}\n", summary.inf_count));
453 }
454 if summary.race_count > 0 {
455 out.push_str(&format!(" Race cond.: {}\n", summary.race_count));
456 }
457 out.push_str("--- Events ---\n");
458 for ev in &self.events {
459 out.push_str(&format!(" {ev}\n"));
460 }
461 out.push_str("=== End Report ===\n");
462 out
463 }
464}
465
466#[derive(Debug, Clone)]
472pub struct MemoryRegion {
473 pub base_address: u64,
475 pub size: usize,
477 pub name: String,
479 pub is_readonly: bool,
481}
482
483#[derive(Debug)]
485pub struct MemoryChecker {
486 allocations: Vec<MemoryRegion>,
487}
488
489impl MemoryChecker {
490 pub fn new(allocations: Vec<MemoryRegion>) -> Self {
492 Self { allocations }
493 }
494
495 pub fn check_access(&self, address: u64, size: usize, is_write: bool) -> Option<DebugEvent> {
500 let region = self.allocations.iter().find(|r| {
502 address >= r.base_address
503 && address
504 .checked_add(size as u64)
505 .is_some_and(|end| end <= r.base_address + r.size as u64)
506 });
507
508 match region {
509 Some(r) if is_write && r.is_readonly => Some(DebugEvent {
510 event_type: DebugEventType::OutOfBounds { address, size },
511 thread_id: (0, 0, 0),
512 block_id: (0, 0, 0),
513 timestamp_ns: 0,
514 message: format!("Write to read-only region '{}' at {:#x}", r.name, address),
515 }),
516 Some(_) => None,
517 None => Some(DebugEvent {
518 event_type: DebugEventType::OutOfBounds { address, size },
519 thread_id: (0, 0, 0),
520 block_id: (0, 0, 0),
521 timestamp_ns: 0,
522 message: format!(
523 "Access at {:#x} (size {}) does not fall within any known allocation",
524 address, size
525 ),
526 }),
527 }
528 }
529}
530
531#[derive(Debug, Clone, PartialEq)]
537pub struct NanInfLocation {
538 pub index: usize,
540 pub value: f64,
542 pub is_nan: bool,
544}
545
546#[derive(Debug, Clone, Copy)]
548pub struct NanInfChecker;
549
550impl NanInfChecker {
551 pub fn check_f32(data: &[f32]) -> Vec<NanInfLocation> {
553 data.iter()
554 .enumerate()
555 .filter_map(|(i, &v)| {
556 if v.is_nan() {
557 Some(NanInfLocation {
558 index: i,
559 value: f64::from(v),
560 is_nan: true,
561 })
562 } else if v.is_infinite() {
563 Some(NanInfLocation {
564 index: i,
565 value: f64::from(v),
566 is_nan: false,
567 })
568 } else {
569 None
570 }
571 })
572 .collect()
573 }
574
575 pub fn check_f64(data: &[f64]) -> Vec<NanInfLocation> {
577 data.iter()
578 .enumerate()
579 .filter_map(|(i, &v)| {
580 if v.is_nan() {
581 Some(NanInfLocation {
582 index: i,
583 value: v,
584 is_nan: true,
585 })
586 } else if v.is_infinite() {
587 Some(NanInfLocation {
588 index: i,
589 value: v,
590 is_nan: false,
591 })
592 } else {
593 None
594 }
595 })
596 .collect()
597 }
598}
599
600#[derive(Debug, Clone, PartialEq)]
606pub enum PrintfArg {
607 Int(i64),
609 Float(f64),
611 String(String),
613}
614
615#[derive(Debug, Clone)]
617pub struct PrintfEntry {
618 pub thread_id: (u32, u32, u32),
620 pub block_id: (u32, u32, u32),
622 pub format_string: String,
624 pub args: Vec<PrintfArg>,
626}
627
628#[derive(Debug)]
643pub struct PrintfBuffer {
644 buffer_size: usize,
645}
646
647impl PrintfBuffer {
648 pub fn new(buffer_size: usize) -> Self {
650 Self { buffer_size }
651 }
652
653 pub fn buffer_size(&self) -> usize {
655 self.buffer_size
656 }
657
658 pub fn parse_entries(&self, raw: &[u8]) -> Vec<PrintfEntry> {
662 let mut entries = Vec::new();
663 let mut cursor = 0usize;
664
665 let entry_count = match Self::read_u32(raw, &mut cursor) {
666 Some(n) => n as usize,
667 None => return entries,
668 };
669
670 for _ in 0..entry_count {
671 let Some(entry) = self.parse_single_entry(raw, &mut cursor) else {
672 break;
673 };
674 entries.push(entry);
675 }
676
677 entries
678 }
679
680 fn parse_single_entry(&self, raw: &[u8], cursor: &mut usize) -> Option<PrintfEntry> {
681 let tx = Self::read_u32(raw, cursor)?;
682 let ty = Self::read_u32(raw, cursor)?;
683 let tz = Self::read_u32(raw, cursor)?;
684 let bx = Self::read_u32(raw, cursor)?;
685 let by = Self::read_u32(raw, cursor)?;
686 let bz = Self::read_u32(raw, cursor)?;
687
688 let fmt_len = Self::read_u32(raw, cursor)? as usize;
689 let fmt_bytes = Self::read_bytes(raw, cursor, fmt_len)?;
690 let format_string = String::from_utf8_lossy(fmt_bytes).into_owned();
691
692 let arg_count = Self::read_u32(raw, cursor)? as usize;
693 let mut args = Vec::with_capacity(arg_count);
694 for _ in 0..arg_count {
695 let tag = Self::read_u8(raw, cursor)?;
696 let arg = match tag {
697 0 => {
698 let val = Self::read_i64(raw, cursor)?;
699 PrintfArg::Int(val)
700 }
701 1 => {
702 let val = Self::read_f64(raw, cursor)?;
703 PrintfArg::Float(val)
704 }
705 2 => {
706 let slen = Self::read_u32(raw, cursor)? as usize;
707 let sbytes = Self::read_bytes(raw, cursor, slen)?;
708 PrintfArg::String(String::from_utf8_lossy(sbytes).into_owned())
709 }
710 _ => return None,
711 };
712 args.push(arg);
713 }
714
715 Some(PrintfEntry {
716 thread_id: (tx, ty, tz),
717 block_id: (bx, by, bz),
718 format_string,
719 args,
720 })
721 }
722
723 fn read_u8(raw: &[u8], cursor: &mut usize) -> Option<u8> {
726 if *cursor >= raw.len() {
727 return None;
728 }
729 let val = raw[*cursor];
730 *cursor += 1;
731 Some(val)
732 }
733
734 fn read_u32(raw: &[u8], cursor: &mut usize) -> Option<u32> {
735 if *cursor + 4 > raw.len() {
736 return None;
737 }
738 let bytes: [u8; 4] = raw[*cursor..*cursor + 4].try_into().ok()?;
739 *cursor += 4;
740 Some(u32::from_le_bytes(bytes))
741 }
742
743 fn read_i64(raw: &[u8], cursor: &mut usize) -> Option<i64> {
744 if *cursor + 8 > raw.len() {
745 return None;
746 }
747 let bytes: [u8; 8] = raw[*cursor..*cursor + 8].try_into().ok()?;
748 *cursor += 8;
749 Some(i64::from_le_bytes(bytes))
750 }
751
752 fn read_f64(raw: &[u8], cursor: &mut usize) -> Option<f64> {
753 if *cursor + 8 > raw.len() {
754 return None;
755 }
756 let bytes: [u8; 8] = raw[*cursor..*cursor + 8].try_into().ok()?;
757 *cursor += 8;
758 Some(f64::from_le_bytes(bytes))
759 }
760
761 fn read_bytes<'a>(raw: &'a [u8], cursor: &mut usize, len: usize) -> Option<&'a [u8]> {
762 if *cursor + len > raw.len() {
763 return None;
764 }
765 let slice = &raw[*cursor..*cursor + len];
766 *cursor += len;
767 Some(slice)
768 }
769}
770
771#[derive(Debug, Clone, Copy)]
778pub struct KernelAssertions;
779
780impl KernelAssertions {
781 pub fn assert_bounds(index: usize, len: usize, name: &str) -> Option<DebugEvent> {
783 if index < len {
784 return None;
785 }
786 Some(DebugEvent {
787 event_type: DebugEventType::Assertion {
788 condition: format!("{name}[{index}] < {len}"),
789 file: String::new(),
790 line: 0,
791 },
792 thread_id: (0, 0, 0),
793 block_id: (0, 0, 0),
794 timestamp_ns: 0,
795 message: format!("Bounds check failed: {name}[{index}] out of range (len={len})"),
796 })
797 }
798
799 pub fn assert_not_nan(value: f64, name: &str) -> Option<DebugEvent> {
801 if !value.is_nan() {
802 return None;
803 }
804 Some(DebugEvent {
805 event_type: DebugEventType::NanDetected {
806 register: name.to_owned(),
807 value,
808 },
809 thread_id: (0, 0, 0),
810 block_id: (0, 0, 0),
811 timestamp_ns: 0,
812 message: format!("NaN detected in '{name}'"),
813 })
814 }
815
816 pub fn assert_finite(value: f64, name: &str) -> Option<DebugEvent> {
819 if value.is_finite() {
820 return None;
821 }
822 if value.is_nan() {
823 return Some(DebugEvent {
824 event_type: DebugEventType::NanDetected {
825 register: name.to_owned(),
826 value,
827 },
828 thread_id: (0, 0, 0),
829 block_id: (0, 0, 0),
830 timestamp_ns: 0,
831 message: format!("Non-finite (NaN) value in '{name}'"),
832 });
833 }
834 Some(DebugEvent {
835 event_type: DebugEventType::InfDetected {
836 register: name.to_owned(),
837 },
838 thread_id: (0, 0, 0),
839 block_id: (0, 0, 0),
840 timestamp_ns: 0,
841 message: format!("Non-finite (Inf) value in '{name}'"),
842 })
843 }
844
845 pub fn assert_positive(value: f64, name: &str) -> Option<DebugEvent> {
848 if value > 0.0 {
849 return None;
850 }
851 if value.is_nan() {
852 return Some(DebugEvent {
853 event_type: DebugEventType::NanDetected {
854 register: name.to_owned(),
855 value,
856 },
857 thread_id: (0, 0, 0),
858 block_id: (0, 0, 0),
859 timestamp_ns: 0,
860 message: format!("Expected positive value for '{name}', got NaN"),
861 });
862 }
863 Some(DebugEvent {
864 event_type: DebugEventType::Assertion {
865 condition: format!("{name} > 0"),
866 file: String::new(),
867 line: 0,
868 },
869 thread_id: (0, 0, 0),
870 block_id: (0, 0, 0),
871 timestamp_ns: 0,
872 message: format!("Expected positive value for '{name}', got {value}"),
873 })
874 }
875}
876
877#[derive(Debug)]
888pub struct DebugPtxInstrumenter {
889 enable_bounds_check: bool,
890 enable_nan_check: bool,
891 enable_printf: bool,
892}
893
894impl DebugPtxInstrumenter {
895 pub fn new(config: &KernelDebugConfig) -> Self {
897 Self {
898 enable_bounds_check: config.enable_bounds_check,
899 enable_nan_check: config.enable_nan_check,
900 enable_printf: config.print_buffer_size > 0,
901 }
902 }
903
904 pub fn instrument_bounds_checks(&self, ptx: &str) -> String {
910 if !self.enable_bounds_check {
911 return ptx.to_owned();
912 }
913
914 let mut output = String::with_capacity(ptx.len() + ptx.len() / 4);
915 let mut added_param = false;
917
918 for line in ptx.lines() {
919 let trimmed = line.trim();
920 if trimmed.starts_with(".entry") && !added_param {
922 output.push_str(line);
923 output.push('\n');
924 output.push_str(" // [oxicuda-debug] bounds-check instrumentation\n");
925 output.push_str(" .param .u64 __oxicuda_debug_buf;\n");
926 added_param = true;
927 continue;
928 }
929
930 if (trimmed.starts_with("ld.global") || trimmed.starts_with("st.global"))
932 && !trimmed.starts_with("// [oxicuda-debug]")
933 {
934 output.push_str(line);
935 output.push('\n');
936 output.push_str(" // [oxicuda-debug] bounds check for above access\n");
937 output.push_str(" setp.ge.u64 %p_oob, %rd_addr, %rd_alloc_end;\n");
938 output.push_str(" @%p_oob trap;\n");
939 } else {
940 output.push_str(line);
941 output.push('\n');
942 }
943 }
944
945 output
946 }
947
948 pub fn instrument_nan_checks(&self, ptx: &str) -> String {
953 if !self.enable_nan_check {
954 return ptx.to_owned();
955 }
956
957 let fp_ops = [
958 "add.f32", "add.f64", "sub.f32", "sub.f64", "mul.f32", "mul.f64", "div.f32", "div.f64",
959 "fma.f32", "fma.f64",
960 ];
961
962 let mut output = String::with_capacity(ptx.len() + ptx.len() / 4);
963
964 for line in ptx.lines() {
965 output.push_str(line);
966 output.push('\n');
967
968 let trimmed = line.trim();
969 if fp_ops.iter().any(|op| trimmed.starts_with(op)) {
970 if let Some(dest) = trimmed.split_whitespace().nth(1) {
972 let dest_clean = dest.trim_end_matches(',');
973 let width = if trimmed.contains(".f64") {
974 "f64"
975 } else {
976 "f32"
977 };
978 output.push_str(&format!(
979 " // [oxicuda-debug] NaN check for {dest_clean}\n"
980 ));
981 output.push_str(&format!(" testp.nan.{width} %p_nan, {dest_clean};\n"));
982 output.push_str(" @%p_nan trap;\n");
983 }
984 }
985 }
986
987 output
988 }
989
990 pub fn instrument_printf(&self, ptx: &str) -> String {
995 if !self.enable_printf {
996 return ptx.to_owned();
997 }
998
999 let mut output = String::with_capacity(ptx.len() + ptx.len() / 4);
1000 let mut added_param = false;
1001
1002 for line in ptx.lines() {
1003 let trimmed = line.trim();
1004 if trimmed.starts_with(".entry") && !added_param {
1005 output.push_str(line);
1006 output.push('\n');
1007 output.push_str(" // [oxicuda-debug] printf buffer parameter\n");
1008 output.push_str(" .param .u64 __oxicuda_printf_buf;\n");
1009 added_param = true;
1010 continue;
1011 }
1012
1013 if trimmed.starts_with("// PRINTF") {
1014 output.push_str(" // [oxicuda-debug] printf store sequence\n");
1015 output.push_str(" ld.param.u64 %rd_pbuf, [__oxicuda_printf_buf];\n");
1016 output.push_str(" atom.global.add.u32 %r_poff, [%rd_pbuf], 1;\n");
1017 } else {
1018 output.push_str(line);
1019 output.push('\n');
1020 }
1021 }
1022
1023 output
1024 }
1025
1026 pub fn strip_debug(&self, ptx: &str) -> String {
1028 let mut output = String::with_capacity(ptx.len());
1029 let mut skip_next = false;
1030
1031 for line in ptx.lines() {
1032 if skip_next {
1033 skip_next = false;
1034 continue;
1035 }
1036
1037 let trimmed = line.trim();
1038
1039 if trimmed.starts_with("// [oxicuda-debug]") {
1041 skip_next = true;
1043 continue;
1044 }
1045
1046 if trimmed.contains("__oxicuda_debug_buf") || trimmed.contains("__oxicuda_printf_buf") {
1048 continue;
1049 }
1050
1051 output.push_str(line);
1052 output.push('\n');
1053 }
1054
1055 output
1056 }
1057}
1058
1059#[cfg(test)]
1064mod tests {
1065 use super::*;
1066
1067 #[test]
1070 fn config_default_values() {
1071 let cfg = KernelDebugConfig::default();
1072 assert_eq!(cfg.debug_level, DebugLevel::Info);
1073 assert!(cfg.enable_bounds_check);
1074 assert!(cfg.enable_nan_check);
1075 assert!(cfg.enable_inf_check);
1076 assert!(!cfg.enable_race_detection);
1077 assert_eq!(cfg.print_buffer_size, 1024 * 1024);
1078 assert_eq!(cfg.max_print_per_thread, 32);
1079 }
1080
1081 #[test]
1084 fn debugger_creation_with_config() {
1085 let cfg = KernelDebugConfig {
1086 debug_level: DebugLevel::Trace,
1087 enable_bounds_check: false,
1088 ..KernelDebugConfig::default()
1089 };
1090 let debugger = KernelDebugger::new(cfg);
1091 assert_eq!(debugger.config().debug_level, DebugLevel::Trace);
1092 assert!(!debugger.config().enable_bounds_check);
1093 }
1094
1095 #[test]
1098 fn debug_session_lifecycle() {
1099 let cfg = KernelDebugConfig::default();
1100 let mut debugger = KernelDebugger::new(cfg);
1101 let session = debugger.attach("test_kernel");
1102 assert!(session.is_ok());
1103 let session = session.expect("session");
1104 assert_eq!(session.kernel_name(), "test_kernel");
1105 assert!(session.events().is_empty());
1106
1107 let err = debugger.attach("");
1109 assert!(err.is_err());
1110 }
1111
1112 #[test]
1115 fn breakpoint_set_and_remove() {
1116 let mut debugger = KernelDebugger::new(KernelDebugConfig::default());
1117 let bp1 = debugger.set_breakpoint(42);
1118 let bp2 = debugger.set_breakpoint(100);
1119 assert_ne!(bp1, bp2);
1120
1121 assert!(debugger.remove_breakpoint(bp1));
1122 assert!(!debugger.remove_breakpoint(bp1));
1124 assert!(debugger.remove_breakpoint(bp2));
1126 }
1127
1128 #[test]
1131 fn memory_checker_valid_access() {
1132 let checker = MemoryChecker::new(vec![MemoryRegion {
1133 base_address: 0x1000,
1134 size: 256,
1135 name: "buf_a".into(),
1136 is_readonly: false,
1137 }]);
1138 assert!(checker.check_access(0x1000, 16, false).is_none());
1140 assert!(checker.check_access(0x1080, 32, true).is_none());
1142 }
1143
1144 #[test]
1147 fn memory_checker_out_of_bounds() {
1148 let checker = MemoryChecker::new(vec![MemoryRegion {
1149 base_address: 0x1000,
1150 size: 256,
1151 name: "buf_a".into(),
1152 is_readonly: false,
1153 }]);
1154 let ev = checker.check_access(0x1100, 16, false);
1156 assert!(ev.is_some());
1157 let ev = ev.expect("oob event");
1158 assert!(matches!(ev.event_type, DebugEventType::OutOfBounds { .. }));
1159
1160 let ev2 = checker.check_access(0x5000, 4, true);
1162 assert!(ev2.is_some());
1163 }
1164
1165 #[test]
1168 fn nan_detection_f32() {
1169 let data = [1.0_f32, f32::NAN, 3.0, f32::NAN];
1170 let locs = NanInfChecker::check_f32(&data);
1171 assert_eq!(locs.len(), 2);
1172 assert_eq!(locs[0].index, 1);
1173 assert!(locs[0].is_nan);
1174 assert_eq!(locs[1].index, 3);
1175 }
1176
1177 #[test]
1180 fn inf_detection_f64() {
1181 let data = [1.0_f64, f64::INFINITY, f64::NEG_INFINITY, 4.0];
1182 let locs = NanInfChecker::check_f64(&data);
1183 assert_eq!(locs.len(), 2);
1184 assert!(!locs[0].is_nan);
1185 assert_eq!(locs[0].index, 1);
1186 assert!(!locs[1].is_nan);
1187 assert_eq!(locs[1].index, 2);
1188 }
1189
1190 #[test]
1193 fn printf_buffer_parsing() {
1194 let buf = PrintfBuffer::new(4096);
1195
1196 let mut raw = Vec::new();
1198 raw.extend_from_slice(&1_u32.to_le_bytes());
1200 raw.extend_from_slice(&1_u32.to_le_bytes());
1202 raw.extend_from_slice(&0_u32.to_le_bytes());
1203 raw.extend_from_slice(&0_u32.to_le_bytes());
1204 raw.extend_from_slice(&0_u32.to_le_bytes());
1206 raw.extend_from_slice(&0_u32.to_le_bytes());
1207 raw.extend_from_slice(&0_u32.to_le_bytes());
1208 let fmt = b"val=%d";
1210 raw.extend_from_slice(&(fmt.len() as u32).to_le_bytes());
1211 raw.extend_from_slice(fmt);
1212 raw.extend_from_slice(&1_u32.to_le_bytes());
1214 raw.push(0);
1216 raw.extend_from_slice(&42_i64.to_le_bytes());
1217
1218 let entries = buf.parse_entries(&raw);
1219 assert_eq!(entries.len(), 1);
1220 assert_eq!(entries[0].thread_id, (1, 0, 0));
1221 assert_eq!(entries[0].format_string, "val=%d");
1222 assert_eq!(entries[0].args.len(), 1);
1223 assert_eq!(entries[0].args[0], PrintfArg::Int(42));
1224 }
1225
1226 #[test]
1229 fn assertion_checks() {
1230 assert!(KernelAssertions::assert_bounds(5, 10, "arr").is_none());
1232 let ev = KernelAssertions::assert_bounds(10, 10, "arr");
1234 assert!(ev.is_some());
1235
1236 assert!(KernelAssertions::assert_not_nan(1.0, "x").is_none());
1238 assert!(KernelAssertions::assert_not_nan(f64::NAN, "x").is_some());
1239
1240 assert!(KernelAssertions::assert_finite(1.0, "x").is_none());
1242 assert!(KernelAssertions::assert_finite(f64::INFINITY, "x").is_some());
1243 assert!(KernelAssertions::assert_finite(f64::NAN, "x").is_some());
1244
1245 assert!(KernelAssertions::assert_positive(1.0, "x").is_none());
1247 assert!(KernelAssertions::assert_positive(0.0, "x").is_some());
1248 assert!(KernelAssertions::assert_positive(-1.0, "x").is_some());
1249 assert!(KernelAssertions::assert_positive(f64::NAN, "x").is_some());
1250 }
1251
1252 #[test]
1255 fn debug_event_filtering() {
1256 let cfg = KernelDebugConfig::default();
1257 let mut debugger = KernelDebugger::new(cfg);
1258 let mut session = debugger.attach("filter_test").expect("session");
1259
1260 session.add_event(DebugEvent {
1261 event_type: DebugEventType::NanDetected {
1262 register: "f0".into(),
1263 value: f64::NAN,
1264 },
1265 thread_id: (0, 0, 0),
1266 block_id: (0, 0, 0),
1267 timestamp_ns: 100,
1268 message: "nan".into(),
1269 });
1270 session.add_event(DebugEvent {
1271 event_type: DebugEventType::OutOfBounds {
1272 address: 0xDEAD,
1273 size: 4,
1274 },
1275 thread_id: (1, 0, 0),
1276 block_id: (0, 0, 0),
1277 timestamp_ns: 200,
1278 message: "oob".into(),
1279 });
1280 session.add_event(DebugEvent {
1281 event_type: DebugEventType::NanDetected {
1282 register: "f1".into(),
1283 value: f64::NAN,
1284 },
1285 thread_id: (2, 0, 0),
1286 block_id: (0, 0, 0),
1287 timestamp_ns: 300,
1288 message: "nan2".into(),
1289 });
1290
1291 let nans = session.filter_events(&DebugEventType::NanDetected {
1292 register: String::new(),
1293 value: 0.0,
1294 });
1295 assert_eq!(nans.len(), 2);
1296
1297 let oobs = session.filter_events(&DebugEventType::OutOfBounds {
1298 address: 0,
1299 size: 0,
1300 });
1301 assert_eq!(oobs.len(), 1);
1302 }
1303
1304 #[test]
1307 fn summary_statistics() {
1308 let cfg = KernelDebugConfig::default();
1309 let mut debugger = KernelDebugger::new(cfg);
1310 let mut session = debugger.attach("summary_test").expect("session");
1311
1312 session.add_event(DebugEvent {
1313 event_type: DebugEventType::NanDetected {
1314 register: "f0".into(),
1315 value: f64::NAN,
1316 },
1317 thread_id: (0, 0, 0),
1318 block_id: (0, 0, 0),
1319 timestamp_ns: 0,
1320 message: String::new(),
1321 });
1322 session.add_event(DebugEvent {
1323 event_type: DebugEventType::InfDetected {
1324 register: "f1".into(),
1325 },
1326 thread_id: (0, 0, 0),
1327 block_id: (0, 0, 0),
1328 timestamp_ns: 0,
1329 message: String::new(),
1330 });
1331 session.add_event(DebugEvent {
1332 event_type: DebugEventType::OutOfBounds {
1333 address: 0x100,
1334 size: 4,
1335 },
1336 thread_id: (0, 0, 0),
1337 block_id: (0, 0, 0),
1338 timestamp_ns: 0,
1339 message: String::new(),
1340 });
1341 session.add_event(DebugEvent {
1342 event_type: DebugEventType::RaceCondition { address: 0x200 },
1343 thread_id: (0, 0, 0),
1344 block_id: (0, 0, 0),
1345 timestamp_ns: 0,
1346 message: String::new(),
1347 });
1348
1349 let s = session.summary();
1350 assert_eq!(s.total_events, 4);
1351 assert_eq!(s.errors, 2); assert_eq!(s.warnings, 2); assert_eq!(s.nan_count, 1);
1354 assert_eq!(s.inf_count, 1);
1355 assert_eq!(s.oob_count, 1);
1356 assert_eq!(s.race_count, 1);
1357 }
1358
1359 #[test]
1362 fn format_report_output() {
1363 let cfg = KernelDebugConfig::default();
1364 let mut debugger = KernelDebugger::new(cfg);
1365 let mut session = debugger.attach("report_test").expect("session");
1366
1367 session.add_event(DebugEvent {
1368 event_type: DebugEventType::NanDetected {
1369 register: "f0".into(),
1370 value: f64::NAN,
1371 },
1372 thread_id: (0, 0, 0),
1373 block_id: (0, 0, 0),
1374 timestamp_ns: 42,
1375 message: "NaN found".into(),
1376 });
1377
1378 let report = session.format_report();
1379 assert!(report.contains("report_test"));
1380 assert!(report.contains("Total events: 1"));
1381 assert!(report.contains("NaN detected: 1"));
1382 assert!(report.contains("NaN found"));
1383 assert!(report.contains("=== End Report ==="));
1384 }
1385
1386 #[test]
1389 fn ptx_instrumentation_bounds_checks() {
1390 let cfg = KernelDebugConfig::default();
1391 let inst = DebugPtxInstrumenter::new(&cfg);
1392
1393 let ptx = ".entry my_kernel {\n ld.global.f32 %f0, [%rd0];\n ret;\n}\n";
1394 let result = inst.instrument_bounds_checks(ptx);
1395
1396 assert!(result.contains("__oxicuda_debug_buf"));
1397 assert!(result.contains("setp.ge.u64"));
1398 assert!(result.contains("@%p_oob trap"));
1399 }
1400
1401 #[test]
1404 fn ptx_strip_debug_roundtrip() {
1405 let cfg = KernelDebugConfig::default();
1406 let inst = DebugPtxInstrumenter::new(&cfg);
1407
1408 let original = ".entry kern {\n add.f32 %f0, %f1, %f2;\n ret;\n}\n";
1409 let instrumented = inst.instrument_nan_checks(original);
1410 assert!(instrumented.contains("[oxicuda-debug]"));
1411
1412 let stripped = inst.strip_debug(&instrumented);
1413 assert!(!stripped.contains("[oxicuda-debug]"));
1415 assert!(stripped.contains("add.f32"));
1417 assert!(stripped.contains("ret;"));
1418 }
1419}