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