1use crate::trace_context::TraceContext;
6use crate::trace_event::VariableStatus;
7use crate::type_info::TypeInfo;
8
9#[derive(Debug, Clone)]
13pub struct ParsedComplexVariable {
14 pub var_name_index: u16,
15 pub type_index: u16,
16 pub access_path: String,
17 pub status: u8, pub data: Vec<u8>,
19}
20
21pub struct FormatPrinter;
23
24impl FormatPrinter {
25 pub fn format_complex_print_data(
27 format_string_index: u16,
28 complex_variables: &[ParsedComplexVariable],
29 trace_context: &TraceContext,
30 ) -> String {
31 let format_string = match trace_context.get_string(format_string_index) {
33 Some(s) => s,
34 None => {
35 return format!("<INVALID_FORMAT_INDEX_{format_string_index}>");
36 }
37 };
38
39 Self::apply_format_with_specs(format_string, complex_variables, trace_context)
41 }
42
43 #[cfg(test)]
45 fn apply_format_strings(format_string: &str, formatted_values: &[String]) -> String {
46 let mut result = String::new();
47 let mut chars = format_string.chars().peekable();
48 let mut var_index = 0;
49
50 while let Some(ch) = chars.next() {
51 match ch {
52 '{' => {
53 if chars.peek() == Some(&'{') {
54 chars.next();
55 result.push('{');
56 } else {
57 let mut found = false;
59 for c in chars.by_ref() {
60 if c == '}' {
61 found = true;
62 break;
63 }
64 }
65 if found {
66 if var_index < formatted_values.len() {
67 result.push_str(&formatted_values[var_index]);
68 var_index += 1;
69 } else {
70 result.push_str("<MISSING_ARG>");
71 }
72 } else {
73 result.push_str("<MALFORMED_PLACEHOLDER>");
74 }
75 }
76 }
77 '}' => {
78 if chars.peek() == Some(&'}') {
79 chars.next();
80 result.push('}');
81 } else {
82 result.push('}');
83 }
84 }
85 _ => result.push(ch),
86 }
87 }
88 result
89 }
90
91 fn apply_format_with_specs(
94 format_string: &str,
95 vars: &[ParsedComplexVariable],
96 trace_context: &TraceContext,
97 ) -> String {
98 let mut result = String::new();
99 let mut chars = format_string.chars().peekable();
100 let mut var_index: usize = 0;
101
102 while let Some(ch) = chars.next() {
103 match ch {
104 '{' => {
105 if chars.peek() == Some(&'{') {
106 chars.next();
107 result.push('{');
108 } else {
109 let mut found = false;
110 let mut content = String::new();
111 for c in chars.by_ref() {
112 if c == '}' {
113 found = true;
114 break;
115 }
116 content.push(c);
117 }
118 if !found {
119 result.push_str("<MALFORMED_PLACEHOLDER>");
120 continue;
121 }
122
123 if content.is_empty() {
124 if var_index < vars.len() {
126 let v = &vars[var_index];
127 let s = Self::format_complex_variable_with_status(
128 v.var_name_index,
129 v.type_index,
130 &v.access_path,
131 &v.data,
132 v.status,
133 trace_context,
134 );
135 let value_part = s.split(" = ").last().unwrap_or(&s);
136 result.push_str(value_part);
137 var_index += 1;
138 } else {
139 result.push_str("<MISSING_ARG>");
140 }
141 continue;
142 }
143
144 if !content.starts_with(':') {
145 result.push_str("<INVALID_SPEC>");
146 continue;
147 }
148 let tail = &content[1..];
149 let mut it = tail.chars();
150 let conv = it.next().unwrap_or(' ');
151 let rest: String = it.collect();
152
153 enum Len {
156 None,
157 Static(usize),
158 Star,
159 Capture,
160 }
161 fn parse_static_len(spec: &str) -> Option<usize> {
163 if spec.chars().all(|c| c.is_ascii_digit()) {
164 return spec.parse::<usize>().ok();
165 }
166 if let Some(hex) = spec.strip_prefix("0x") {
167 if !hex.is_empty() && hex.chars().all(|c| c.is_ascii_hexdigit()) {
168 return usize::from_str_radix(hex, 16).ok();
169 }
170 }
171 if let Some(oct) = spec.strip_prefix("0o") {
172 if !oct.is_empty() && oct.chars().all(|c| matches!(c, '0'..='7')) {
173 return usize::from_str_radix(oct, 8).ok();
174 }
175 }
176 if let Some(bin) = spec.strip_prefix("0b") {
177 if !bin.is_empty() && bin.chars().all(|c| matches!(c, '0' | '1')) {
178 return usize::from_str_radix(bin, 2).ok();
179 }
180 }
181 None
182 }
183
184 let lenspec = if rest.is_empty() {
185 Len::None
186 } else if let Some(r) = rest.strip_prefix('.') {
187 if r == "*" {
188 Len::Star
189 } else if r.ends_with('$') {
190 Len::Capture
191 } else if let Some(n) = parse_static_len(r) {
192 Len::Static(n)
193 } else {
194 Len::None
195 }
196 } else {
197 Len::None
198 };
199
200 fn parse_len_usize(lenb: &[u8]) -> usize {
202 if lenb.len() >= 8 {
203 let arr = [
204 lenb[0], lenb[1], lenb[2], lenb[3], lenb[4], lenb[5], lenb[6],
205 lenb[7],
206 ];
207 let v = i64::from_le_bytes(arr);
208 if v <= 0 {
209 0
210 } else {
211 v as usize
212 }
213 } else {
214 0
215 }
216 }
217
218 let err_value_part = |idx: usize| -> Option<String> {
220 if idx >= vars.len() {
221 return None;
222 }
223 let v = &vars[idx];
224 if v.status == VariableStatus::Ok as u8
225 || v.status == VariableStatus::ZeroLength as u8
226 {
227 None
228 } else {
229 let s = Self::format_complex_variable_with_status(
230 v.var_name_index,
231 v.type_index,
232 &v.access_path,
233 &v.data,
234 v.status,
235 trace_context,
236 );
237 Some(s.split(" = ").last().unwrap_or(&s).to_string())
238 }
239 };
240
241 match conv {
242 'x' | 'X' => {
243 match lenspec {
244 Len::Star => {
245 if var_index + 1 >= vars.len() {
246 result.push_str("<MISSING_ARG>");
247 } else if let Some(err) = err_value_part(var_index) {
248 result.push_str(&err);
250 var_index += 2;
251 continue;
252 } else if let Some(err) = err_value_part(var_index + 1) {
253 result.push_str(&err);
255 var_index += 2;
256 continue;
257 } else {
258 let lenb = vars[var_index].data.as_slice();
260 let n = parse_len_usize(lenb);
261 let v = &vars[var_index + 1];
262 let full = v.data.as_slice();
263 let take =
264 if v.status == VariableStatus::ZeroLength as u8 {
265 0
266 } else {
267 std::cmp::min(n, full.len())
268 };
269 let b = &full[..take];
270 let s = b
271 .iter()
272 .map(|vv| {
273 if conv == 'x' {
274 format!("{vv:02x}")
275 } else {
276 format!("{vv:02X}")
277 }
278 })
279 .collect::<Vec<_>>()
280 .join(" ");
281 result.push_str(&s);
282 var_index += 2;
283 continue;
284 }
285 }
287 Len::Static(n) => {
288 if var_index >= vars.len() {
289 result.push_str("<MISSING_ARG>");
290 } else if let Some(err) = err_value_part(var_index) {
291 result.push_str(&err);
292 var_index += 1;
293 continue;
294 } else {
295 let v = &vars[var_index];
296 let full = v.data.as_slice();
297 let take =
298 if v.status == VariableStatus::ZeroLength as u8 {
299 0
300 } else {
301 std::cmp::min(n, full.len())
302 };
303 let b = &full[..take];
304 let s = b
305 .iter()
306 .map(|vv| {
307 if conv == 'x' {
308 format!("{vv:02x}")
309 } else {
310 format!("{vv:02X}")
311 }
312 })
313 .collect::<Vec<_>>()
314 .join(" ");
315 result.push_str(&s);
316 var_index += 1;
317 continue;
318 }
319 }
320 Len::Capture => {
321 if var_index + 1 >= vars.len() {
322 result.push_str("<MISSING_ARG>");
323 } else if let Some(err) = err_value_part(var_index) {
324 result.push_str(&err);
325 var_index += 2;
326 continue;
327 } else if let Some(err) = err_value_part(var_index + 1) {
328 result.push_str(&err);
329 var_index += 2;
330 continue;
331 } else {
332 let lenb = vars[var_index].data.as_slice();
333 let n = parse_len_usize(lenb);
334 let v = &vars[var_index + 1];
335 let full = v.data.as_slice();
336 let take =
337 if v.status == VariableStatus::ZeroLength as u8 {
338 0
339 } else {
340 std::cmp::min(n, full.len())
341 };
342 let b = &full[..take];
343 let s = b
344 .iter()
345 .map(|vv| {
346 if conv == 'x' {
347 format!("{vv:02x}")
348 } else {
349 format!("{vv:02X}")
350 }
351 })
352 .collect::<Vec<_>>()
353 .join(" ");
354 result.push_str(&s);
355 var_index += 2;
356 continue;
357 }
358 }
360 Len::None => {
361 if var_index >= vars.len() {
362 result.push_str("<MISSING_ARG>");
363 } else if let Some(err) = err_value_part(var_index) {
364 result.push_str(&err);
365 var_index += 1;
366 continue;
367 } else {
368 let v = &vars[var_index];
369 let b = if v.status == VariableStatus::ZeroLength as u8
370 {
371 &[][..]
372 } else {
373 v.data.as_slice()
374 };
375 let s = b
376 .iter()
377 .map(|vv| {
378 if conv == 'x' {
379 format!("{vv:02x}")
380 } else {
381 format!("{vv:02X}")
382 }
383 })
384 .collect::<Vec<_>>()
385 .join(" ");
386 result.push_str(&s);
387 var_index += 1;
388 continue;
389 }
390 }
391 }
392 }
393 's' => {
394 let mut render_bytes = |b: &[u8]| {
395 let mut out = String::new();
396 for &c in b.iter() {
397 if c == 0 {
398 break;
399 }
400 if (0x20..=0x7e).contains(&c) {
401 out.push(c as char);
402 } else {
403 out.push_str(&format!("\\x{c:02x}"));
404 }
405 }
406 result.push_str(&out);
407 };
408
409 match lenspec {
410 Len::Star => {
411 if var_index + 1 >= vars.len() {
412 result.push_str("<MISSING_ARG>");
413 } else if let Some(err) = err_value_part(var_index) {
414 result.push_str(&err);
415 var_index += 2;
416 continue;
417 } else if let Some(err) = err_value_part(var_index + 1) {
418 result.push_str(&err);
419 var_index += 2;
420 continue;
421 } else {
422 let lenb = vars[var_index].data.as_slice();
423 let n = parse_len_usize(lenb);
424 let v = &vars[var_index + 1];
425 let full = v.data.as_slice();
426 let take =
427 if v.status == VariableStatus::ZeroLength as u8 {
428 0
429 } else {
430 std::cmp::min(n, full.len())
431 };
432 render_bytes(&full[..take]);
433 var_index += 2;
434 continue;
435 }
436 }
437 Len::Static(n) => {
438 if var_index >= vars.len() {
439 result.push_str("<MISSING_ARG>");
440 } else if let Some(err) = err_value_part(var_index) {
441 result.push_str(&err);
442 var_index += 1;
443 continue;
444 } else {
445 let v = &vars[var_index];
446 let full = v.data.as_slice();
447 let take =
448 if v.status == VariableStatus::ZeroLength as u8 {
449 0
450 } else {
451 std::cmp::min(n, full.len())
452 };
453 render_bytes(&full[..take]);
454 var_index += 1;
455 continue;
456 }
457 }
458 Len::Capture => {
459 if var_index + 1 >= vars.len() {
460 result.push_str("<MISSING_ARG>");
461 } else if let Some(err) = err_value_part(var_index) {
462 result.push_str(&err);
463 var_index += 2;
464 continue;
465 } else if let Some(err) = err_value_part(var_index + 1) {
466 result.push_str(&err);
467 var_index += 2;
468 continue;
469 } else {
470 let lenb = vars[var_index].data.as_slice();
471 let n = parse_len_usize(lenb);
472 let v = &vars[var_index + 1];
473 let full = v.data.as_slice();
474 let take =
475 if v.status == VariableStatus::ZeroLength as u8 {
476 0
477 } else {
478 std::cmp::min(n, full.len())
479 };
480 render_bytes(&full[..take]);
481 var_index += 2;
482 continue;
483 }
484 }
485 Len::None => {
486 if var_index >= vars.len() {
487 result.push_str("<MISSING_ARG>");
488 } else if let Some(err) = err_value_part(var_index) {
489 result.push_str(&err);
490 var_index += 1;
491 continue;
492 } else {
493 let v = &vars[var_index];
494 let b = if v.status == VariableStatus::ZeroLength as u8
495 {
496 &[][..]
497 } else {
498 v.data.as_slice()
499 };
500 render_bytes(b);
501 var_index += 1;
502 continue;
503 }
504 }
505 }
506 }
507 'p' => {
508 if var_index >= vars.len() {
509 result.push_str("<MISSING_ARG>");
510 } else if let Some(err) = err_value_part(var_index) {
511 result.push_str(&err);
512 var_index += 1;
513 continue;
514 } else {
515 let b = vars[var_index].data.as_slice();
516 if b.len() >= 8 {
517 let addr = u64::from_le_bytes([
518 b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
519 ]);
520 result.push_str(&format!("0x{addr:x}"));
521 } else {
522 result.push_str("<INVALID_POINTER>");
523 }
524 var_index += 1;
525 continue;
526 }
527 }
528 _ => {
529 if var_index < vars.len() {
531 let v = &vars[var_index];
532 let s = Self::format_complex_variable_with_status(
533 v.var_name_index,
534 v.type_index,
535 &v.access_path,
536 &v.data,
537 v.status,
538 trace_context,
539 );
540 let value_part = s.split(" = ").last().unwrap_or(&s);
541 result.push_str(value_part);
542 var_index += 1;
543 } else {
544 result.push_str("<MISSING_ARG>");
545 }
546 }
547 }
548 }
549 }
550 '}' => {
551 if chars.peek() == Some(&'}') {
552 chars.next();
553 result.push('}');
554 } else {
555 result.push('}');
556 }
557 }
558 _ => result.push(ch),
559 }
560 }
561 result
562 }
563
564 pub fn format_complex_variable(
566 var_name_index: u16,
567 type_index: u16,
568 access_path: &str,
569 data: &[u8],
570 trace_context: &TraceContext,
571 ) -> String {
572 let var_name = trace_context
573 .get_variable_name(var_name_index)
574 .unwrap_or("<INVALID_VAR_NAME>");
575
576 let type_info = match trace_context.get_type(type_index) {
577 Some(t) => t,
578 None => return format!("<INVALID_TYPE_INDEX_{type_index}>: {var_name}"),
579 };
580
581 let formatted_data = Self::format_data_with_type_info(data, type_info);
582
583 if access_path.is_empty() {
584 format!("{var_name} = {formatted_data}")
585 } else {
586 format!("{var_name}.{access_path} = {formatted_data}")
587 }
588 }
589
590 pub fn format_complex_variable_with_status(
592 var_name_index: u16,
593 type_index: u16,
594 access_path: &str,
595 data: &[u8],
596 status: u8,
597 trace_context: &TraceContext,
598 ) -> String {
599 let var_name = trace_context
600 .get_variable_name(var_name_index)
601 .unwrap_or("<INVALID_VAR_NAME>");
602 let type_info = match trace_context.get_type(type_index) {
603 Some(t) => t,
604 None => return format!("<INVALID_TYPE_INDEX_{type_index}>: {var_name}"),
605 };
606
607 if status == VariableStatus::Ok as u8 {
609 return Self::format_complex_variable(
610 var_name_index,
611 type_index,
612 access_path,
613 data,
614 trace_context,
615 );
616 }
617
618 let (errno, addr) = if data.len() >= 12 {
620 let errno = i32::from_le_bytes([data[0], data[1], data[2], data[3]]);
621 let addr = u64::from_le_bytes([
622 data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11],
623 ]);
624 (Some(errno), Some(addr))
625 } else {
626 (None, None)
627 };
628
629 let type_suffix = type_info.type_name();
630 let err_text = match status {
631 s if s == VariableStatus::NullDeref as u8 => {
632 format!("<error: null pointer dereference> ({type_suffix}*)")
633 }
634 s if s == VariableStatus::ReadError as u8 => match (errno, addr) {
635 (Some(e), Some(a)) => {
636 format!("<read_user failed errno={e} at 0x{a:x}> ({type_suffix}*)")
637 }
638 _ => format!("<read_user failed> ({type_suffix}*)"),
639 },
640 s if s == VariableStatus::AccessError as u8 => {
641 format!("<address compute failed> ({type_suffix}*)")
642 }
643 s if s == VariableStatus::OffsetsUnavailable as u8 => {
644 format!("<proc offsets unavailable> ({type_suffix}*)")
645 }
646 s if s == VariableStatus::Truncated as u8 => format!("<truncated> ({type_suffix}*)"),
647 s if s == VariableStatus::ZeroLength as u8 => format!("<len<=0> ({type_suffix})"),
648 _ => format!("<error status={status}> ({type_suffix}*)"),
649 };
650
651 if access_path.is_empty() {
652 format!("{var_name} = {err_text}")
653 } else {
654 format!("{var_name}.{access_path} = {err_text}")
655 }
656 }
657
658 pub fn format_data_with_type_info(data: &[u8], type_info: &TypeInfo) -> String {
660 Self::format_data_with_type_info_impl(data, type_info, 0, 32)
662 }
663
664 fn format_data_with_type_info_impl(
666 data: &[u8],
667 type_info: &TypeInfo,
668 current_depth: usize,
669 max_depth: usize,
670 ) -> String {
671 if current_depth > max_depth {
672 return "<MAX_DEPTH_EXCEEDED>".to_string();
673 }
674
675 match type_info {
676 TypeInfo::BaseType { size, encoding, .. } => {
677 Self::format_base_type_data(data, *size, *encoding)
678 }
679 TypeInfo::BitfieldType {
680 underlying_type,
681 bit_offset,
682 bit_size,
683 } => {
684 let u_size = underlying_type.size() as usize;
685 if data.len() < u_size || *bit_size == 0 {
686 return "<INVALID_BITFIELD>".to_string();
687 }
688 let val =
689 Self::extract_bits_le(&data[..u_size], *bit_offset as u32, *bit_size as u32);
690 Self::format_bitfield_value(val, underlying_type, *bit_size as u32)
691 }
692 TypeInfo::PointerType { target_type, .. } => {
693 if data.len() < 8 {
694 "<INVALID_POINTER>".to_string()
695 } else {
696 let addr = u64::from_le_bytes([
697 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
698 ]);
699 let ty = format!("{}*", target_type.type_name());
700 if addr == 0 {
701 format!("NULL ({ty})")
702 } else {
703 format!("0x{addr:x} ({ty})")
704 }
705 }
706 }
707 TypeInfo::ArrayType {
708 element_type,
709 element_count,
710 ..
711 } => {
712 if Self::is_char_byte_type(element_type) {
714 return Self::format_char_array_as_string(data, element_count);
715 }
716 let elem_size = element_type.size() as usize;
717 if elem_size == 0 {
718 return "<ZERO_SIZE_ELEMENT>".to_string();
719 }
720
721 let count = element_count.unwrap_or(data.len() as u64 / elem_size as u64);
722 let actual_count = std::cmp::min(count, data.len() as u64 / elem_size as u64);
723
724 if actual_count == 0 {
725 return "[]".to_string();
726 }
727
728 let mut result = String::from("[");
729 for i in 0..actual_count {
730 if i > 0 {
731 result.push_str(", ");
732 }
733
734 let start = i as usize * elem_size;
735 let end = std::cmp::min(start + elem_size, data.len());
736 let elem_data = &data[start..end];
737
738 let formatted_elem = Self::format_data_with_type_info_impl(
739 elem_data,
740 element_type,
741 current_depth + 1,
742 max_depth,
743 );
744 result.push_str(&formatted_elem);
745 }
746 result.push(']');
747 result
748 }
749 TypeInfo::StructType { name, members, .. } => {
750 if current_depth > max_depth {
752 return format!("<STRUCT_{name}>");
753 }
754
755 let mut result = format!("{name} {{ ");
756 let mut first = true;
757
758 for member in members.iter() {
759 if !first {
760 result.push_str(", ");
761 }
762 first = false;
763
764 let offset = member.offset as usize;
765 if let TypeInfo::BitfieldType {
767 underlying_type,
768 bit_offset,
769 bit_size,
770 } = &member.member_type
771 {
772 let u_size = underlying_type.size() as usize;
773 if offset + u_size <= data.len() && *bit_size > 0 && *bit_size <= 64 {
774 let raw = &data[offset..offset + u_size];
775 let val_u64 =
776 Self::extract_bits_le(raw, *bit_offset as u32, *bit_size as u32);
777 let formatted_value = Self::format_bitfield_value(
778 val_u64,
779 underlying_type,
780 *bit_size as u32,
781 );
782 result.push_str(&format!("{}: {}", member.name, formatted_value));
783 } else {
784 result.push_str(&format!("{}: <OUT_OF_BOUNDS>", member.name));
785 }
786 } else if let (Some(bit_size), maybe_bit_offset) =
787 (member.bit_size, member.bit_offset)
788 {
789 let bit_size = bit_size as u32;
791 let bit_offset = maybe_bit_offset.unwrap_or(0) as u32;
792 let bytes_needed = (bit_offset + bit_size).div_ceil(8) as usize;
793 if offset + bytes_needed <= data.len() && bit_size > 0 && bit_size <= 64 {
794 let raw = &data[offset..offset + bytes_needed];
795 let val_u64 = Self::extract_bits_le(raw, bit_offset, bit_size);
796 let formatted_value =
797 Self::format_bitfield_value(val_u64, &member.member_type, bit_size);
798 result.push_str(&format!("{}: {}", member.name, formatted_value));
799 } else {
800 result.push_str(&format!("{}: <OUT_OF_BOUNDS>", member.name));
801 }
802 } else {
803 let member_size = member.member_type.size() as usize;
804 if offset + member_size <= data.len() {
805 let member_data = &data[offset..offset + member_size];
806 let formatted_value = Self::format_data_with_type_info_impl(
807 member_data,
808 &member.member_type,
809 current_depth + 1,
810 max_depth,
811 );
812 result.push_str(&format!("{}: {}", member.name, formatted_value));
813 } else {
814 result.push_str(&format!("{}: <OUT_OF_BOUNDS>", member.name));
815 }
816 }
817 }
818
819 result.push_str(" }");
821 result
822 }
823 TypeInfo::UnionType { name, members, .. } => {
824 if members.is_empty() {
825 format!("union {name} {{}}")
826 } else {
827 let first_member = &members[0];
829 let member_size = first_member.member_type.size() as usize;
830 let member_data = if member_size <= data.len() {
831 &data[..member_size]
832 } else {
833 data
834 };
835
836 let formatted_value = Self::format_data_with_type_info_impl(
837 member_data,
838 &first_member.member_type,
839 current_depth + 1,
840 max_depth,
841 );
842 format!(
843 "union {} {{ {} = {} }}",
844 name, first_member.name, formatted_value
845 )
846 }
847 }
848 TypeInfo::EnumType {
849 name,
850 base_type,
851 variants,
852 ..
853 } => {
854 let base_value = Self::format_data_with_type_info_impl(
855 data,
856 base_type,
857 current_depth + 1,
858 max_depth,
859 );
860
861 if let Ok(int_val) = base_value.parse::<i64>() {
863 for variant in variants {
864 if variant.value == int_val {
865 return format!("{}::{}({})", name, variant.name, base_value);
866 }
867 }
868 }
869
870 format!("{name}({base_value})")
872 }
873 TypeInfo::TypedefType {
874 name,
875 underlying_type,
876 ..
877 } => {
878 match &**underlying_type {
880 TypeInfo::StructType { size, members, .. } => {
881 let alias_struct = TypeInfo::StructType {
882 name: name.clone(),
883 size: *size,
884 members: members.clone(),
885 };
886 Self::format_data_with_type_info_impl(
887 data,
888 &alias_struct,
889 current_depth,
890 max_depth,
891 )
892 }
893 TypeInfo::UnionType { size, members, .. } => {
894 let alias_union = TypeInfo::UnionType {
895 name: name.clone(),
896 size: *size,
897 members: members.clone(),
898 };
899 Self::format_data_with_type_info_impl(
900 data,
901 &alias_union,
902 current_depth,
903 max_depth,
904 )
905 }
906 _ => {
907 let underlying_formatted = Self::format_data_with_type_info_impl(
908 data,
909 underlying_type,
910 current_depth,
911 max_depth,
912 );
913 format!("{name}({underlying_formatted})")
914 }
915 }
916 }
917 TypeInfo::QualifiedType {
918 underlying_type, ..
919 } => Self::format_data_with_type_info_impl(
920 data,
921 underlying_type,
922 current_depth,
923 max_depth,
924 ),
925 TypeInfo::FunctionType { .. } => {
926 if data.len() >= 8 {
927 let addr = u64::from_le_bytes([
928 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
929 ]);
930 format!("<FUNCTION@0x{addr:x}>")
931 } else {
932 "<INVALID_FUNCTION_POINTER>".to_string()
933 }
934 }
935 TypeInfo::UnknownType { name } => {
936 format!("<UNKNOWN_TYPE_{name}_{}_BYTES>", data.len())
937 }
938 TypeInfo::OptimizedOut { .. } => "<optimized out>".to_string(),
939 }
940 }
941
942 fn format_base_type_data(data: &[u8], size: u64, encoding: u16) -> String {
944 if encoding == gimli::constants::DW_ATE_boolean.0 as u16 {
945 if data.is_empty() {
946 "<EMPTY_BOOL>".to_string()
947 } else {
948 (data[0] != 0).to_string()
949 }
950 } else if encoding == gimli::constants::DW_ATE_float.0 as u16 {
951 match size {
952 4 => {
953 if data.len() >= 4 {
954 let bytes: [u8; 4] = [data[0], data[1], data[2], data[3]];
955 f32::from_le_bytes(bytes).to_string()
956 } else {
957 "<INVALID_F32>".to_string()
958 }
959 }
960 8 => {
961 if data.len() >= 8 {
962 let bytes: [u8; 8] = [
963 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
964 ];
965 f64::from_le_bytes(bytes).to_string()
966 } else {
967 "<INVALID_F64>".to_string()
968 }
969 }
970 _ => format!("<UNSUPPORTED_FLOAT_SIZE_{size}>"),
971 }
972 } else if encoding == gimli::constants::DW_ATE_signed.0 as u16
973 || encoding == gimli::constants::DW_ATE_signed_char.0 as u16
974 {
975 match size {
976 1 => {
977 if !data.is_empty() {
978 (data[0] as i8).to_string()
979 } else {
980 "<EMPTY_I8>".to_string()
981 }
982 }
983 2 => {
984 if data.len() >= 2 {
985 let bytes: [u8; 2] = [data[0], data[1]];
986 i16::from_le_bytes(bytes).to_string()
987 } else {
988 "<INVALID_I16>".to_string()
989 }
990 }
991 4 => {
992 if data.len() >= 4 {
993 let bytes: [u8; 4] = [data[0], data[1], data[2], data[3]];
994 i32::from_le_bytes(bytes).to_string()
995 } else {
996 "<INVALID_I32>".to_string()
997 }
998 }
999 8 => {
1000 if data.len() >= 8 {
1001 let bytes: [u8; 8] = [
1002 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1003 ];
1004 i64::from_le_bytes(bytes).to_string()
1005 } else {
1006 "<INVALID_I64>".to_string()
1007 }
1008 }
1009 _ => format!("<UNSUPPORTED_SIGNED_SIZE_{size}>"),
1010 }
1011 } else if encoding == gimli::constants::DW_ATE_unsigned.0 as u16
1012 || encoding == gimli::constants::DW_ATE_unsigned_char.0 as u16
1013 {
1014 match size {
1015 1 => {
1016 if !data.is_empty() {
1017 data[0].to_string()
1018 } else {
1019 "<EMPTY_U8>".to_string()
1020 }
1021 }
1022 2 => {
1023 if data.len() >= 2 {
1024 let bytes: [u8; 2] = [data[0], data[1]];
1025 u16::from_le_bytes(bytes).to_string()
1026 } else {
1027 "<INVALID_U16>".to_string()
1028 }
1029 }
1030 4 => {
1031 if data.len() >= 4 {
1032 let bytes: [u8; 4] = [data[0], data[1], data[2], data[3]];
1033 u32::from_le_bytes(bytes).to_string()
1034 } else {
1035 "<INVALID_U32>".to_string()
1036 }
1037 }
1038 8 => {
1039 if data.len() >= 8 {
1040 let bytes: [u8; 8] = [
1041 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1042 ];
1043 u64::from_le_bytes(bytes).to_string()
1044 } else {
1045 "<INVALID_U64>".to_string()
1046 }
1047 }
1048 _ => format!("<UNSUPPORTED_UNSIGNED_SIZE_{size}>"),
1049 }
1050 } else {
1051 if (encoding == gimli::constants::DW_ATE_signed_char.0 as u16
1053 || encoding == gimli::constants::DW_ATE_unsigned_char.0 as u16)
1054 && size == 1
1055 {
1056 if !data.is_empty() {
1057 if data[0] >= 32 && data[0] <= 126 {
1058 format!("'{}'", data[0] as char)
1059 } else {
1060 format!("'\\x{:02x}'", data[0])
1061 }
1062 } else {
1063 "<EMPTY_CHAR>".to_string()
1064 }
1065 } else {
1066 format!("<UNKNOWN_ENCODING_{encoding}_SIZE_{size}_BYTES>")
1068 }
1069 }
1070 }
1071
1072 fn is_char_byte_type(t: &TypeInfo) -> bool {
1074 match t {
1075 TypeInfo::BaseType { size, encoding, .. } => {
1076 *size == 1
1077 && (*encoding == gimli::constants::DW_ATE_signed_char.0 as u16
1078 || *encoding == gimli::constants::DW_ATE_unsigned_char.0 as u16
1079 || *encoding == gimli::constants::DW_ATE_unsigned.0 as u16
1080 || *encoding == gimli::constants::DW_ATE_signed.0 as u16)
1081 }
1082 TypeInfo::TypedefType {
1083 underlying_type, ..
1084 }
1085 | TypeInfo::QualifiedType {
1086 underlying_type, ..
1087 } => Self::is_char_byte_type(underlying_type),
1088 _ => false,
1089 }
1090 }
1091
1092 fn format_char_array_as_string(data: &[u8], element_count: &Option<u64>) -> String {
1094 let max_len = element_count.map(|c| c as usize).unwrap_or(data.len());
1095 let mut s = String::new();
1096 s.push('"');
1097 let mut i = 0usize;
1098 while i < data.len() && i < max_len {
1099 let b = data[i];
1100 if b == 0 {
1101 break; }
1103 match b {
1104 b'"' => s.push_str("\\\""),
1105 b'\\' => s.push_str("\\\\"),
1106 0x20..=0x7E => s.push(b as char),
1107 _ => s.push_str(&format!("\\x{b:02x}")),
1108 }
1109 i += 1;
1110 }
1111 s.push('"');
1112 s
1113 }
1114
1115 fn extract_bits_le(raw: &[u8], bit_offset: u32, bit_size: u32) -> u64 {
1117 let mut word: u64 = 0;
1119 let take = std::cmp::min(8, raw.len());
1120 for (i, byte) in raw.iter().take(take).enumerate() {
1121 word |= (*byte as u64) << (8 * i);
1122 }
1123 let shifted = word >> bit_offset;
1124 let mask: u64 = if bit_size == 64 {
1125 u64::MAX
1126 } else {
1127 (1u64 << bit_size) - 1
1128 };
1129 shifted & mask
1130 }
1131
1132 fn format_bitfield_value(val: u64, ty: &TypeInfo, bit_size: u32) -> String {
1134 if let TypeInfo::BaseType { encoding, .. } = ty {
1136 if *encoding == gimli::constants::DW_ATE_boolean.0 as u16 {
1137 return if val != 0 {
1138 "true".to_string()
1139 } else {
1140 "false".to_string()
1141 };
1142 }
1143 }
1144
1145 if let TypeInfo::EnumType { variants, .. } = ty {
1147 let sval = val as i64; for v in variants {
1149 if v.value == sval {
1150 return v.name.clone();
1151 }
1152 }
1153 }
1154
1155 let is_signed = ty.is_signed_int();
1157 if is_signed && bit_size > 0 && bit_size <= 64 {
1158 let sign_bit = 1u64 << (bit_size - 1);
1159 let signed_val: i64 = if (val & sign_bit) != 0 {
1160 let ext_mask = (!0u64) << bit_size;
1162 (val | ext_mask) as i64
1163 } else {
1164 val as i64
1165 };
1166 return signed_val.to_string();
1167 }
1168
1169 val.to_string()
1171 }
1172}
1173
1174#[cfg(test)]
1175mod tests {
1176 use super::*;
1177
1178 #[test]
1179 fn test_apply_format_basic() {
1180 let fmt = "pid: {}, name: {}";
1181 let rendered: Vec<String> = vec!["42".to_string(), "hello".to_string()];
1182 let result = FormatPrinter::apply_format_strings(fmt, &rendered);
1183 assert_eq!(result, "pid: 42, name: hello");
1184 }
1185
1186 #[test]
1187 fn test_apply_format_escape_sequences() {
1188 let rendered: Vec<String> = vec!["123".to_string()];
1189 let result =
1190 FormatPrinter::apply_format_strings("use {{}} for braces, value: {}", &rendered);
1191 assert_eq!(result, "use {} for braces, value: 123");
1192 }
1193
1194 #[test]
1195 fn test_missing_arguments() {
1196 let result = FormatPrinter::apply_format_strings("need arg: {}", &[]);
1197 assert_eq!(result, "need arg: <MISSING_ARG>");
1198 }
1199
1200 #[test]
1201 fn test_format_print_data_with_trace_context() {
1202 let mut trace_context = TraceContext::new();
1203 let format_index = trace_context.add_string("Hello {}, you are {} years old!".to_string());
1204 let rendered: Vec<String> = vec!["Alice".to_string(), "25".to_string()];
1205 let fmt = trace_context.get_string(format_index).unwrap();
1206 let result = FormatPrinter::apply_format_strings(fmt, &rendered);
1207 assert_eq!(result, "Hello Alice, you are 25 years old!");
1208 }
1209
1210 #[test]
1211 fn test_format_complex_variable_struct() {
1212 use crate::type_info::{StructMember, TypeInfo};
1213
1214 let mut trace_context = TraceContext::new();
1215 let var_name_idx = trace_context.add_variable_name("person".to_string());
1216
1217 let person_type = TypeInfo::StructType {
1218 name: "Person".to_string(),
1219 size: 36,
1220 members: vec![
1221 StructMember {
1222 name: "age".to_string(),
1223 member_type: TypeInfo::BaseType {
1224 name: "int".to_string(),
1225 size: 4,
1226 encoding: gimli::constants::DW_ATE_signed.0 as u16,
1227 },
1228 offset: 0,
1229 bit_offset: None,
1230 bit_size: None,
1231 },
1232 StructMember {
1233 name: "id".to_string(),
1234 member_type: TypeInfo::BaseType {
1235 name: "long".to_string(),
1236 size: 8,
1237 encoding: gimli::constants::DW_ATE_signed.0 as u16,
1238 },
1239 offset: 4,
1240 bit_offset: None,
1241 bit_size: None,
1242 },
1243 ],
1244 };
1245
1246 let type_idx = trace_context.add_type(person_type);
1247
1248 let data = vec![
1250 25, 0, 0, 0, 57, 48, 0, 0, 0, 0, 0, 0, ];
1253
1254 let result = FormatPrinter::format_complex_variable(
1255 var_name_idx,
1256 type_idx,
1257 "",
1258 &data,
1259 &trace_context,
1260 );
1261
1262 assert!(result.contains("person = Person"));
1263 assert!(result.contains("age: 25"));
1264 assert!(result.contains("id: 12345"));
1265 }
1266
1267 #[test]
1268 fn test_complex_format_char_array() {
1269 use crate::type_info::TypeInfo;
1270
1271 let mut trace_context = TraceContext::new();
1272 let var_name_idx = trace_context.add_variable_name("name".to_string());
1273 let char_type = TypeInfo::BaseType {
1275 name: "char".to_string(),
1276 size: 1,
1277 encoding: gimli::constants::DW_ATE_unsigned_char.0 as u16,
1278 };
1279 let arr_type = TypeInfo::ArrayType {
1280 element_type: Box::new(char_type),
1281 element_count: Some(16),
1282 total_size: Some(16),
1283 };
1284 let type_idx = trace_context.add_type(arr_type);
1285
1286 let mut data = b"Alice\0".to_vec();
1288 data.resize(16, 0u8);
1289
1290 let fmt_idx = trace_context.add_string("{}".to_string());
1291 let complex_vars = vec![ParsedComplexVariable {
1292 var_name_index: var_name_idx,
1293 type_index: type_idx,
1294 access_path: String::new(),
1295 status: 0,
1296 data,
1297 }];
1298
1299 let result =
1300 FormatPrinter::format_complex_print_data(fmt_idx, &complex_vars, &trace_context);
1301 assert_eq!(result, "\"Alice\"");
1302 }
1303
1304 #[test]
1305 fn test_format_data_with_type_info_array() {
1306 let array_type = TypeInfo::ArrayType {
1307 element_type: Box::new(TypeInfo::BaseType {
1308 name: "int".to_string(),
1309 size: 4,
1310 encoding: gimli::constants::DW_ATE_signed.0 as u16,
1311 }),
1312 element_count: Some(3),
1313 total_size: Some(12),
1314 };
1315
1316 let data = vec![
1317 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, ];
1321
1322 let result = FormatPrinter::format_data_with_type_info(&data, &array_type);
1323 assert_eq!(result, "[1, 2, 3]");
1324 }
1325
1326 #[test]
1327 fn test_bitfield_value_signed_and_unsigned() {
1328 use crate::type_info::TypeInfo;
1329
1330 let u32_type = TypeInfo::BaseType {
1332 name: "unsigned int".to_string(),
1333 size: 4,
1334 encoding: gimli::constants::DW_ATE_unsigned.0 as u16,
1335 };
1336 let bf_unsigned = TypeInfo::BitfieldType {
1337 underlying_type: Box::new(u32_type.clone()),
1338 bit_offset: 0,
1339 bit_size: 3,
1340 };
1341 let data = [0b0000_0101u8, 0, 0, 0]; let res = FormatPrinter::format_data_with_type_info(&data, &bf_unsigned);
1343 assert_eq!(res, "5");
1344
1345 let i32_type = TypeInfo::BaseType {
1347 name: "int".to_string(),
1348 size: 4,
1349 encoding: gimli::constants::DW_ATE_signed.0 as u16,
1350 };
1351 let bf_signed = TypeInfo::BitfieldType {
1352 underlying_type: Box::new(i32_type),
1353 bit_offset: 0,
1354 bit_size: 3,
1355 };
1356 let data_neg1 = [0b0000_0111u8, 0, 0, 0];
1357 let res2 = FormatPrinter::format_data_with_type_info(&data_neg1, &bf_signed);
1358 assert_eq!(res2, "-1");
1359
1360 let bool_type = TypeInfo::BaseType {
1362 name: "bool".to_string(),
1363 size: 1,
1364 encoding: gimli::constants::DW_ATE_boolean.0 as u16,
1365 };
1366 let bf_bool = TypeInfo::BitfieldType {
1367 underlying_type: Box::new(bool_type),
1368 bit_offset: 0,
1369 bit_size: 1,
1370 };
1371 let data_true = [0x01u8];
1372 let res3 = FormatPrinter::format_data_with_type_info(&data_true, &bf_bool);
1373 assert_eq!(res3, "true");
1374 }
1375
1376 #[test]
1377 fn test_struct_with_bitfields() {
1378 use crate::type_info::{StructMember, TypeInfo};
1379
1380 let u32_type = TypeInfo::BaseType {
1382 name: "unsigned int".to_string(),
1383 size: 4,
1384 encoding: gimli::constants::DW_ATE_unsigned.0 as u16,
1385 };
1386
1387 let s_type = TypeInfo::StructType {
1388 name: "S".to_string(),
1389 size: 4,
1390 members: vec![
1391 StructMember {
1392 name: "active".to_string(),
1393 member_type: TypeInfo::BitfieldType {
1394 underlying_type: Box::new(u32_type.clone()),
1395 bit_offset: 0,
1396 bit_size: 1,
1397 },
1398 offset: 0,
1399 bit_offset: Some(0),
1400 bit_size: Some(1),
1401 },
1402 StructMember {
1403 name: "flags".to_string(),
1404 member_type: TypeInfo::BitfieldType {
1405 underlying_type: Box::new(u32_type.clone()),
1406 bit_offset: 1,
1407 bit_size: 3,
1408 },
1409 offset: 0,
1410 bit_offset: Some(1),
1411 bit_size: Some(3),
1412 },
1413 ],
1414 };
1415
1416 let data = [0b0000_0111u8, 0, 0, 0];
1418 let res = FormatPrinter::format_data_with_type_info(&data, &s_type);
1419 assert!(res.contains("S {"));
1420 assert!(res.contains("active: 1"));
1421 assert!(res.contains("flags: 3"));
1422 }
1423
1424 #[test]
1425 fn test_ext_hex_preserves_null_deref_error() {
1426 let mut trace_context = TraceContext::new();
1427 let fmt_idx = trace_context.add_string("{:x.16}".to_string());
1428
1429 let arr_type = TypeInfo::ArrayType {
1431 element_type: Box::new(TypeInfo::BaseType {
1432 name: "u8".to_string(),
1433 size: 1,
1434 encoding: gimli::constants::DW_ATE_unsigned_char.0 as u16,
1435 }),
1436 element_count: Some(16),
1437 total_size: Some(16),
1438 };
1439 let type_idx = trace_context.add_type(arr_type);
1440 let var_name_idx = trace_context.add_variable_name("buf".to_string());
1441
1442 let vars = vec![ParsedComplexVariable {
1443 var_name_index: var_name_idx,
1444 type_index: type_idx,
1445 access_path: String::new(),
1446 status: VariableStatus::NullDeref as u8,
1447 data: vec![],
1448 }];
1449
1450 let out = FormatPrinter::format_complex_print_data(fmt_idx, &vars, &trace_context);
1451 assert!(
1452 out.contains("null pointer dereference"),
1453 "unexpected output: {out}"
1454 );
1455 assert!(
1456 !out.contains("<MISSING_ARG>"),
1457 "should not hide error: {out}"
1458 );
1459 }
1460
1461 #[test]
1462 fn test_ext_s_preserves_read_error_errno_addr() {
1463 let mut trace_context = TraceContext::new();
1464 let fmt_idx = trace_context.add_string("{:s.16}".to_string());
1465
1466 let arr_type = TypeInfo::ArrayType {
1468 element_type: Box::new(TypeInfo::BaseType {
1469 name: "u8".to_string(),
1470 size: 1,
1471 encoding: gimli::constants::DW_ATE_unsigned_char.0 as u16,
1472 }),
1473 element_count: Some(16),
1474 total_size: Some(16),
1475 };
1476 let type_idx = trace_context.add_type(arr_type);
1477 let var_name_idx = trace_context.add_variable_name("buf".to_string());
1478
1479 let errno: i32 = -14; let addr: u64 = 0x1234_5678_9abc_def0;
1482 let mut data = Vec::new();
1483 data.extend_from_slice(&errno.to_le_bytes());
1484 data.extend_from_slice(&addr.to_le_bytes());
1485
1486 let vars = vec![ParsedComplexVariable {
1487 var_name_index: var_name_idx,
1488 type_index: type_idx,
1489 access_path: String::new(),
1490 status: VariableStatus::ReadError as u8,
1491 data,
1492 }];
1493
1494 let out = FormatPrinter::format_complex_print_data(fmt_idx, &vars, &trace_context);
1495 assert!(
1496 out.contains("read_user failed errno=-14"),
1497 "unexpected: {out}"
1498 );
1499 assert!(out.contains("0x123456789abcdef0"), "missing addr: {out}");
1500 }
1501
1502 #[test]
1503 fn test_ext_p_preserves_offsets_unavailable() {
1504 let mut trace_context = TraceContext::new();
1505 let fmt_idx = trace_context.add_string("P={:p}".to_string());
1506
1507 let ptr_type = TypeInfo::PointerType {
1508 target_type: Box::new(TypeInfo::BaseType {
1509 name: "u8".to_string(),
1510 size: 1,
1511 encoding: gimli::constants::DW_ATE_unsigned_char.0 as u16,
1512 }),
1513 size: 8,
1514 };
1515 let type_idx = trace_context.add_type(ptr_type);
1516 let var_name_idx = trace_context.add_variable_name("ptr".to_string());
1517
1518 let vars = vec![ParsedComplexVariable {
1519 var_name_index: var_name_idx,
1520 type_index: type_idx,
1521 access_path: String::new(),
1522 status: VariableStatus::OffsetsUnavailable as u8,
1523 data: vec![],
1524 }];
1525
1526 let out = FormatPrinter::format_complex_print_data(fmt_idx, &vars, &trace_context);
1527 assert!(out.starts_with("P="), "prefix lost: {out}");
1528 assert!(
1529 out.contains("proc offsets unavailable"),
1530 "unexpected: {out}"
1531 );
1532 }
1533
1534 #[test]
1535 fn test_ext_star_len_error_precedence() {
1536 let mut trace_context = TraceContext::new();
1537 let fmt_idx = trace_context.add_string("S={:x.*}".to_string());
1538
1539 let len_type = TypeInfo::BaseType {
1541 name: "i64".to_string(),
1542 size: 8,
1543 encoding: gimli::constants::DW_ATE_signed.0 as u16,
1544 };
1545 let len_ty_idx = trace_context.add_type(len_type);
1546 let len_name_idx = trace_context.add_variable_name("len".to_string());
1547
1548 let arr_type = TypeInfo::ArrayType {
1550 element_type: Box::new(TypeInfo::BaseType {
1551 name: "u8".to_string(),
1552 size: 1,
1553 encoding: gimli::constants::DW_ATE_unsigned_char.0 as u16,
1554 }),
1555 element_count: Some(16),
1556 total_size: Some(16),
1557 };
1558 let val_ty_idx = trace_context.add_type(arr_type);
1559 let val_name_idx = trace_context.add_variable_name("buf".to_string());
1560 let val_data: Vec<u8> = (0u8..16).collect();
1561
1562 let vars = vec![
1563 ParsedComplexVariable {
1564 var_name_index: len_name_idx,
1565 type_index: len_ty_idx,
1566 access_path: String::new(),
1567 status: VariableStatus::NullDeref as u8,
1568 data: vec![],
1569 },
1570 ParsedComplexVariable {
1571 var_name_index: val_name_idx,
1572 type_index: val_ty_idx,
1573 access_path: String::new(),
1574 status: VariableStatus::Ok as u8,
1575 data: val_data,
1576 },
1577 ];
1578
1579 let out = FormatPrinter::format_complex_print_data(fmt_idx, &vars, &trace_context);
1580 assert!(out.starts_with("S="), "prefix lost: {out}");
1581 assert!(
1582 out.contains("null pointer"),
1583 "should surface len arg error: {out}"
1584 );
1585 assert!(
1587 !out.contains("00 01 02 03"),
1588 "should not render bytes: {out}"
1589 );
1590 }
1591}