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 remaining = raw.len().saturating_sub(*cursor);
698 let mut args = Vec::with_capacity(arg_count.min(remaining / 5));
699 for _ in 0..arg_count {
700 let tag = Self::read_u8(raw, cursor)?;
701 let arg = match tag {
702 0 => {
703 let val = Self::read_i64(raw, cursor)?;
704 PrintfArg::Int(val)
705 }
706 1 => {
707 let val = Self::read_f64(raw, cursor)?;
708 PrintfArg::Float(val)
709 }
710 2 => {
711 let slen = Self::read_u32(raw, cursor)? as usize;
712 let sbytes = Self::read_bytes(raw, cursor, slen)?;
713 PrintfArg::String(String::from_utf8_lossy(sbytes).into_owned())
714 }
715 _ => return None,
716 };
717 args.push(arg);
718 }
719
720 Some(PrintfEntry {
721 thread_id: (tx, ty, tz),
722 block_id: (bx, by, bz),
723 format_string,
724 args,
725 })
726 }
727
728 fn read_u8(raw: &[u8], cursor: &mut usize) -> Option<u8> {
731 if *cursor >= raw.len() {
732 return None;
733 }
734 let val = raw[*cursor];
735 *cursor += 1;
736 Some(val)
737 }
738
739 fn read_u32(raw: &[u8], cursor: &mut usize) -> Option<u32> {
740 if *cursor + 4 > raw.len() {
741 return None;
742 }
743 let bytes: [u8; 4] = raw[*cursor..*cursor + 4].try_into().ok()?;
744 *cursor += 4;
745 Some(u32::from_le_bytes(bytes))
746 }
747
748 fn read_i64(raw: &[u8], cursor: &mut usize) -> Option<i64> {
749 if *cursor + 8 > raw.len() {
750 return None;
751 }
752 let bytes: [u8; 8] = raw[*cursor..*cursor + 8].try_into().ok()?;
753 *cursor += 8;
754 Some(i64::from_le_bytes(bytes))
755 }
756
757 fn read_f64(raw: &[u8], cursor: &mut usize) -> Option<f64> {
758 if *cursor + 8 > raw.len() {
759 return None;
760 }
761 let bytes: [u8; 8] = raw[*cursor..*cursor + 8].try_into().ok()?;
762 *cursor += 8;
763 Some(f64::from_le_bytes(bytes))
764 }
765
766 fn read_bytes<'a>(raw: &'a [u8], cursor: &mut usize, len: usize) -> Option<&'a [u8]> {
767 if *cursor + len > raw.len() {
768 return None;
769 }
770 let slice = &raw[*cursor..*cursor + len];
771 *cursor += len;
772 Some(slice)
773 }
774}
775
776#[derive(Debug, Clone, Copy)]
783pub struct KernelAssertions;
784
785impl KernelAssertions {
786 pub fn assert_bounds(index: usize, len: usize, name: &str) -> Option<DebugEvent> {
788 if index < len {
789 return None;
790 }
791 Some(DebugEvent {
792 event_type: DebugEventType::Assertion {
793 condition: format!("{name}[{index}] < {len}"),
794 file: String::new(),
795 line: 0,
796 },
797 thread_id: (0, 0, 0),
798 block_id: (0, 0, 0),
799 timestamp_ns: 0,
800 message: format!("Bounds check failed: {name}[{index}] out of range (len={len})"),
801 })
802 }
803
804 pub fn assert_not_nan(value: f64, name: &str) -> Option<DebugEvent> {
806 if !value.is_nan() {
807 return None;
808 }
809 Some(DebugEvent {
810 event_type: DebugEventType::NanDetected {
811 register: name.to_owned(),
812 value,
813 },
814 thread_id: (0, 0, 0),
815 block_id: (0, 0, 0),
816 timestamp_ns: 0,
817 message: format!("NaN detected in '{name}'"),
818 })
819 }
820
821 pub fn assert_finite(value: f64, name: &str) -> Option<DebugEvent> {
824 if value.is_finite() {
825 return None;
826 }
827 if value.is_nan() {
828 return Some(DebugEvent {
829 event_type: DebugEventType::NanDetected {
830 register: name.to_owned(),
831 value,
832 },
833 thread_id: (0, 0, 0),
834 block_id: (0, 0, 0),
835 timestamp_ns: 0,
836 message: format!("Non-finite (NaN) value in '{name}'"),
837 });
838 }
839 Some(DebugEvent {
840 event_type: DebugEventType::InfDetected {
841 register: name.to_owned(),
842 },
843 thread_id: (0, 0, 0),
844 block_id: (0, 0, 0),
845 timestamp_ns: 0,
846 message: format!("Non-finite (Inf) value in '{name}'"),
847 })
848 }
849
850 pub fn assert_positive(value: f64, name: &str) -> Option<DebugEvent> {
853 if value > 0.0 {
854 return None;
855 }
856 if value.is_nan() {
857 return Some(DebugEvent {
858 event_type: DebugEventType::NanDetected {
859 register: name.to_owned(),
860 value,
861 },
862 thread_id: (0, 0, 0),
863 block_id: (0, 0, 0),
864 timestamp_ns: 0,
865 message: format!("Expected positive value for '{name}', got NaN"),
866 });
867 }
868 Some(DebugEvent {
869 event_type: DebugEventType::Assertion {
870 condition: format!("{name} > 0"),
871 file: String::new(),
872 line: 0,
873 },
874 thread_id: (0, 0, 0),
875 block_id: (0, 0, 0),
876 timestamp_ns: 0,
877 message: format!("Expected positive value for '{name}', got {value}"),
878 })
879 }
880}
881
882#[derive(Debug)]
893pub struct DebugPtxInstrumenter {
894 enable_bounds_check: bool,
895 enable_nan_check: bool,
896 enable_printf: bool,
897}
898
899impl DebugPtxInstrumenter {
900 pub fn new(config: &KernelDebugConfig) -> Self {
902 Self {
903 enable_bounds_check: config.enable_bounds_check,
904 enable_nan_check: config.enable_nan_check,
905 enable_printf: config.print_buffer_size > 0,
906 }
907 }
908
909 pub fn instrument_bounds_checks(&self, ptx: &str) -> String {
915 if !self.enable_bounds_check {
916 return ptx.to_owned();
917 }
918
919 let mut output = String::with_capacity(ptx.len() + ptx.len() / 4);
920 let mut added_param = false;
922
923 for line in ptx.lines() {
924 let trimmed = line.trim();
925 if trimmed.starts_with(".entry") && !added_param {
927 output.push_str(line);
928 output.push('\n');
929 output.push_str(" // [oxicuda-debug] bounds-check instrumentation\n");
930 output.push_str(" .param .u64 __oxicuda_debug_buf;\n");
931 added_param = true;
932 continue;
933 }
934
935 if (trimmed.starts_with("ld.global") || trimmed.starts_with("st.global"))
937 && !trimmed.starts_with("// [oxicuda-debug]")
938 {
939 output.push_str(line);
940 output.push('\n');
941 output.push_str(" // [oxicuda-debug] bounds check for above access\n");
942 output.push_str(" setp.ge.u64 %p_oob, %rd_addr, %rd_alloc_end;\n");
943 output.push_str(" @%p_oob trap;\n");
944 } else {
945 output.push_str(line);
946 output.push('\n');
947 }
948 }
949
950 output
951 }
952
953 pub fn instrument_nan_checks(&self, ptx: &str) -> String {
958 if !self.enable_nan_check {
959 return ptx.to_owned();
960 }
961
962 let fp_ops = [
963 "add.f32", "add.f64", "sub.f32", "sub.f64", "mul.f32", "mul.f64", "div.f32", "div.f64",
964 "fma.f32", "fma.f64",
965 ];
966
967 let mut output = String::with_capacity(ptx.len() + ptx.len() / 4);
968
969 for line in ptx.lines() {
970 output.push_str(line);
971 output.push('\n');
972
973 let trimmed = line.trim();
974 if fp_ops.iter().any(|op| trimmed.starts_with(op)) {
975 if let Some(dest) = trimmed.split_whitespace().nth(1) {
977 let dest_clean = dest.trim_end_matches(',');
978 let width = if trimmed.contains(".f64") {
979 "f64"
980 } else {
981 "f32"
982 };
983 output.push_str(&format!(
984 " // [oxicuda-debug] NaN check for {dest_clean}\n"
985 ));
986 output.push_str(&format!(" testp.nan.{width} %p_nan, {dest_clean};\n"));
987 output.push_str(" @%p_nan trap;\n");
988 }
989 }
990 }
991
992 output
993 }
994
995 pub fn instrument_printf(&self, ptx: &str) -> String {
1000 if !self.enable_printf {
1001 return ptx.to_owned();
1002 }
1003
1004 let mut output = String::with_capacity(ptx.len() + ptx.len() / 4);
1005 let mut added_param = false;
1006
1007 for line in ptx.lines() {
1008 let trimmed = line.trim();
1009 if trimmed.starts_with(".entry") && !added_param {
1010 output.push_str(line);
1011 output.push('\n');
1012 output.push_str(" // [oxicuda-debug] printf buffer parameter\n");
1013 output.push_str(" .param .u64 __oxicuda_printf_buf;\n");
1014 added_param = true;
1015 continue;
1016 }
1017
1018 if trimmed.starts_with("// PRINTF") {
1019 output.push_str(" // [oxicuda-debug] printf store sequence\n");
1020 output.push_str(" ld.param.u64 %rd_pbuf, [__oxicuda_printf_buf];\n");
1021 output.push_str(" atom.global.add.u32 %r_poff, [%rd_pbuf], 1;\n");
1022 } else {
1023 output.push_str(line);
1024 output.push('\n');
1025 }
1026 }
1027
1028 output
1029 }
1030
1031 pub fn strip_debug(&self, ptx: &str) -> String {
1033 let mut output = String::with_capacity(ptx.len());
1034 let mut skip_next = false;
1035
1036 for line in ptx.lines() {
1037 if skip_next {
1038 skip_next = false;
1039 continue;
1040 }
1041
1042 let trimmed = line.trim();
1043
1044 if trimmed.starts_with("// [oxicuda-debug]") {
1046 skip_next = true;
1048 continue;
1049 }
1050
1051 if trimmed.contains("__oxicuda_debug_buf") || trimmed.contains("__oxicuda_printf_buf") {
1053 continue;
1054 }
1055
1056 output.push_str(line);
1057 output.push('\n');
1058 }
1059
1060 output
1061 }
1062}
1063
1064#[cfg(test)]
1069mod tests {
1070 use super::*;
1071
1072 #[test]
1075 fn config_default_values() {
1076 let cfg = KernelDebugConfig::default();
1077 assert_eq!(cfg.debug_level, DebugLevel::Info);
1078 assert!(cfg.enable_bounds_check);
1079 assert!(cfg.enable_nan_check);
1080 assert!(cfg.enable_inf_check);
1081 assert!(!cfg.enable_race_detection);
1082 assert_eq!(cfg.print_buffer_size, 1024 * 1024);
1083 assert_eq!(cfg.max_print_per_thread, 32);
1084 }
1085
1086 #[test]
1089 fn debugger_creation_with_config() {
1090 let cfg = KernelDebugConfig {
1091 debug_level: DebugLevel::Trace,
1092 enable_bounds_check: false,
1093 ..KernelDebugConfig::default()
1094 };
1095 let debugger = KernelDebugger::new(cfg);
1096 assert_eq!(debugger.config().debug_level, DebugLevel::Trace);
1097 assert!(!debugger.config().enable_bounds_check);
1098 }
1099
1100 #[test]
1103 fn debug_session_lifecycle() {
1104 let cfg = KernelDebugConfig::default();
1105 let mut debugger = KernelDebugger::new(cfg);
1106 let session = debugger.attach("test_kernel");
1107 assert!(session.is_ok());
1108 let session = session.expect("session");
1109 assert_eq!(session.kernel_name(), "test_kernel");
1110 assert!(session.events().is_empty());
1111
1112 let err = debugger.attach("");
1114 assert!(err.is_err());
1115 }
1116
1117 #[test]
1120 fn breakpoint_set_and_remove() {
1121 let mut debugger = KernelDebugger::new(KernelDebugConfig::default());
1122 let bp1 = debugger.set_breakpoint(42);
1123 let bp2 = debugger.set_breakpoint(100);
1124 assert_ne!(bp1, bp2);
1125
1126 assert!(debugger.remove_breakpoint(bp1));
1127 assert!(!debugger.remove_breakpoint(bp1));
1129 assert!(debugger.remove_breakpoint(bp2));
1131 }
1132
1133 #[test]
1136 fn memory_checker_valid_access() {
1137 let checker = MemoryChecker::new(vec![MemoryRegion {
1138 base_address: 0x1000,
1139 size: 256,
1140 name: "buf_a".into(),
1141 is_readonly: false,
1142 }]);
1143 assert!(checker.check_access(0x1000, 16, false).is_none());
1145 assert!(checker.check_access(0x1080, 32, true).is_none());
1147 }
1148
1149 #[test]
1152 fn memory_checker_out_of_bounds() {
1153 let checker = MemoryChecker::new(vec![MemoryRegion {
1154 base_address: 0x1000,
1155 size: 256,
1156 name: "buf_a".into(),
1157 is_readonly: false,
1158 }]);
1159 let ev = checker.check_access(0x1100, 16, false);
1161 assert!(ev.is_some());
1162 let ev = ev.expect("oob event");
1163 assert!(matches!(ev.event_type, DebugEventType::OutOfBounds { .. }));
1164
1165 let ev2 = checker.check_access(0x5000, 4, true);
1167 assert!(ev2.is_some());
1168 }
1169
1170 #[test]
1173 fn nan_detection_f32() {
1174 let data = [1.0_f32, f32::NAN, 3.0, f32::NAN];
1175 let locs = NanInfChecker::check_f32(&data);
1176 assert_eq!(locs.len(), 2);
1177 assert_eq!(locs[0].index, 1);
1178 assert!(locs[0].is_nan);
1179 assert_eq!(locs[1].index, 3);
1180 }
1181
1182 #[test]
1185 fn inf_detection_f64() {
1186 let data = [1.0_f64, f64::INFINITY, f64::NEG_INFINITY, 4.0];
1187 let locs = NanInfChecker::check_f64(&data);
1188 assert_eq!(locs.len(), 2);
1189 assert!(!locs[0].is_nan);
1190 assert_eq!(locs[0].index, 1);
1191 assert!(!locs[1].is_nan);
1192 assert_eq!(locs[1].index, 2);
1193 }
1194
1195 #[test]
1198 fn printf_buffer_parsing() {
1199 let buf = PrintfBuffer::new(4096);
1200
1201 let mut raw = Vec::new();
1203 raw.extend_from_slice(&1_u32.to_le_bytes());
1205 raw.extend_from_slice(&1_u32.to_le_bytes());
1207 raw.extend_from_slice(&0_u32.to_le_bytes());
1208 raw.extend_from_slice(&0_u32.to_le_bytes());
1209 raw.extend_from_slice(&0_u32.to_le_bytes());
1211 raw.extend_from_slice(&0_u32.to_le_bytes());
1212 raw.extend_from_slice(&0_u32.to_le_bytes());
1213 let fmt = b"val=%d";
1215 raw.extend_from_slice(&(fmt.len() as u32).to_le_bytes());
1216 raw.extend_from_slice(fmt);
1217 raw.extend_from_slice(&1_u32.to_le_bytes());
1219 raw.push(0);
1221 raw.extend_from_slice(&42_i64.to_le_bytes());
1222
1223 let entries = buf.parse_entries(&raw);
1224 assert_eq!(entries.len(), 1);
1225 assert_eq!(entries[0].thread_id, (1, 0, 0));
1226 assert_eq!(entries[0].format_string, "val=%d");
1227 assert_eq!(entries[0].args.len(), 1);
1228 assert_eq!(entries[0].args[0], PrintfArg::Int(42));
1229 }
1230
1231 #[test]
1234 fn assertion_checks() {
1235 assert!(KernelAssertions::assert_bounds(5, 10, "arr").is_none());
1237 let ev = KernelAssertions::assert_bounds(10, 10, "arr");
1239 assert!(ev.is_some());
1240
1241 assert!(KernelAssertions::assert_not_nan(1.0, "x").is_none());
1243 assert!(KernelAssertions::assert_not_nan(f64::NAN, "x").is_some());
1244
1245 assert!(KernelAssertions::assert_finite(1.0, "x").is_none());
1247 assert!(KernelAssertions::assert_finite(f64::INFINITY, "x").is_some());
1248 assert!(KernelAssertions::assert_finite(f64::NAN, "x").is_some());
1249
1250 assert!(KernelAssertions::assert_positive(1.0, "x").is_none());
1252 assert!(KernelAssertions::assert_positive(0.0, "x").is_some());
1253 assert!(KernelAssertions::assert_positive(-1.0, "x").is_some());
1254 assert!(KernelAssertions::assert_positive(f64::NAN, "x").is_some());
1255 }
1256
1257 #[test]
1260 fn debug_event_filtering() {
1261 let cfg = KernelDebugConfig::default();
1262 let mut debugger = KernelDebugger::new(cfg);
1263 let mut session = debugger.attach("filter_test").expect("session");
1264
1265 session.add_event(DebugEvent {
1266 event_type: DebugEventType::NanDetected {
1267 register: "f0".into(),
1268 value: f64::NAN,
1269 },
1270 thread_id: (0, 0, 0),
1271 block_id: (0, 0, 0),
1272 timestamp_ns: 100,
1273 message: "nan".into(),
1274 });
1275 session.add_event(DebugEvent {
1276 event_type: DebugEventType::OutOfBounds {
1277 address: 0xDEAD,
1278 size: 4,
1279 },
1280 thread_id: (1, 0, 0),
1281 block_id: (0, 0, 0),
1282 timestamp_ns: 200,
1283 message: "oob".into(),
1284 });
1285 session.add_event(DebugEvent {
1286 event_type: DebugEventType::NanDetected {
1287 register: "f1".into(),
1288 value: f64::NAN,
1289 },
1290 thread_id: (2, 0, 0),
1291 block_id: (0, 0, 0),
1292 timestamp_ns: 300,
1293 message: "nan2".into(),
1294 });
1295
1296 let nans = session.filter_events(&DebugEventType::NanDetected {
1297 register: String::new(),
1298 value: 0.0,
1299 });
1300 assert_eq!(nans.len(), 2);
1301
1302 let oobs = session.filter_events(&DebugEventType::OutOfBounds {
1303 address: 0,
1304 size: 0,
1305 });
1306 assert_eq!(oobs.len(), 1);
1307 }
1308
1309 #[test]
1312 fn summary_statistics() {
1313 let cfg = KernelDebugConfig::default();
1314 let mut debugger = KernelDebugger::new(cfg);
1315 let mut session = debugger.attach("summary_test").expect("session");
1316
1317 session.add_event(DebugEvent {
1318 event_type: DebugEventType::NanDetected {
1319 register: "f0".into(),
1320 value: f64::NAN,
1321 },
1322 thread_id: (0, 0, 0),
1323 block_id: (0, 0, 0),
1324 timestamp_ns: 0,
1325 message: String::new(),
1326 });
1327 session.add_event(DebugEvent {
1328 event_type: DebugEventType::InfDetected {
1329 register: "f1".into(),
1330 },
1331 thread_id: (0, 0, 0),
1332 block_id: (0, 0, 0),
1333 timestamp_ns: 0,
1334 message: String::new(),
1335 });
1336 session.add_event(DebugEvent {
1337 event_type: DebugEventType::OutOfBounds {
1338 address: 0x100,
1339 size: 4,
1340 },
1341 thread_id: (0, 0, 0),
1342 block_id: (0, 0, 0),
1343 timestamp_ns: 0,
1344 message: String::new(),
1345 });
1346 session.add_event(DebugEvent {
1347 event_type: DebugEventType::RaceCondition { address: 0x200 },
1348 thread_id: (0, 0, 0),
1349 block_id: (0, 0, 0),
1350 timestamp_ns: 0,
1351 message: String::new(),
1352 });
1353
1354 let s = session.summary();
1355 assert_eq!(s.total_events, 4);
1356 assert_eq!(s.errors, 2); assert_eq!(s.warnings, 2); assert_eq!(s.nan_count, 1);
1359 assert_eq!(s.inf_count, 1);
1360 assert_eq!(s.oob_count, 1);
1361 assert_eq!(s.race_count, 1);
1362 }
1363
1364 #[test]
1367 fn format_report_output() {
1368 let cfg = KernelDebugConfig::default();
1369 let mut debugger = KernelDebugger::new(cfg);
1370 let mut session = debugger.attach("report_test").expect("session");
1371
1372 session.add_event(DebugEvent {
1373 event_type: DebugEventType::NanDetected {
1374 register: "f0".into(),
1375 value: f64::NAN,
1376 },
1377 thread_id: (0, 0, 0),
1378 block_id: (0, 0, 0),
1379 timestamp_ns: 42,
1380 message: "NaN found".into(),
1381 });
1382
1383 let report = session.format_report();
1384 assert!(report.contains("report_test"));
1385 assert!(report.contains("Total events: 1"));
1386 assert!(report.contains("NaN detected: 1"));
1387 assert!(report.contains("NaN found"));
1388 assert!(report.contains("=== End Report ==="));
1389 }
1390
1391 #[test]
1394 fn ptx_instrumentation_bounds_checks() {
1395 let cfg = KernelDebugConfig::default();
1396 let inst = DebugPtxInstrumenter::new(&cfg);
1397
1398 let ptx = ".entry my_kernel {\n ld.global.f32 %f0, [%rd0];\n ret;\n}\n";
1399 let result = inst.instrument_bounds_checks(ptx);
1400
1401 assert!(result.contains("__oxicuda_debug_buf"));
1402 assert!(result.contains("setp.ge.u64"));
1403 assert!(result.contains("@%p_oob trap"));
1404 }
1405
1406 #[test]
1409 fn ptx_strip_debug_roundtrip() {
1410 let cfg = KernelDebugConfig::default();
1411 let inst = DebugPtxInstrumenter::new(&cfg);
1412
1413 let original = ".entry kern {\n add.f32 %f0, %f1, %f2;\n ret;\n}\n";
1414 let instrumented = inst.instrument_nan_checks(original);
1415 assert!(instrumented.contains("[oxicuda-debug]"));
1416
1417 let stripped = inst.strip_debug(&instrumented);
1418 assert!(!stripped.contains("[oxicuda-debug]"));
1420 assert!(stripped.contains("add.f32"));
1422 assert!(stripped.contains("ret;"));
1423 }
1424}