Skip to main content

pretty/
hexdump.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Rust implementation of hexdump utilities, matching zircon/system/ulib/pretty.
6
7use core::fmt::Write;
8
9/// Unsanitized copy helper that copies `chunk_len` bytes from `src` to `dest` without KASan checks.
10///
11/// # Safety
12/// The caller must ensure that the memory range `[src, src + chunk_len)` is mapped and readable by the process.
13//
14// TODO(https://fxbug.dev/521834554): KAsan doesn't work at all for Rust code currently, so all
15// Rust accesses are unsanitized. When KAsan is enabled this routine should be annotated to disable
16// instrumentation.
17#[inline(never)]
18unsafe fn unsanitized_copy(src: *const u8, dest: *mut u8, chunk_len: usize) {
19    for i in 0..chunk_len {
20        unsafe {
21            *dest.add(i) = *src.add(i);
22        }
23    }
24}
25
26/// Do a hex dump against a writer, formatting data as 32-bit words (host endianness)
27/// alongside an 8-bit ASCII panel on the side, displaying up to 16 bytes per line.
28///
29/// The "very" in the name follows the Zircon kernel naming scheme.
30pub fn hexdump_very_ex_rs<W: Write>(
31    writer: &mut W,
32    data: &[u8],
33    disp_addr: u64,
34) -> core::fmt::Result {
35    // SAFETY: A standard safe slice reference is guaranteed to point to mapped, valid, and unpoisoned memory.
36    unsafe { hexdump_very_ex_raw(writer, data.as_ptr(), data.len(), disp_addr) }
37}
38
39/// Do a hex dump against a writer, formatting data as 32-bit words (host endianness)
40/// alongside an 8-bit ASCII panel, displaying up to 16 bytes per line, using suspect raw pointers.
41///
42/// # Safety
43/// The caller must ensure that the memory range `[ptr, ptr + len)` is mapped and readable.
44/// KASan instrumentation is bypassed during pointer accesses to prevent panic loops.
45pub unsafe fn hexdump_very_ex_raw<W: Write>(
46    writer: &mut W,
47    ptr: *const u8,
48    len: usize,
49    disp_addr: u64,
50) -> core::fmt::Result {
51    for count in (0..len).step_by(16) {
52        let chunk_len = core::cmp::min(len - count, 16);
53        // Round up to next multiple of 4 to match C++ word-based printing.
54        let s = chunk_len.next_multiple_of(4);
55
56        // Copy available bytes to a local buffer, padded with 0.
57        let mut buf = [0u8; 16];
58        // SAFETY: The range [ptr + count, ptr + count + chunk_len) is guaranteed mapped by caller.
59        unsafe {
60            unsanitized_copy(ptr.add(count), buf.as_mut_ptr(), chunk_len);
61        }
62
63        if disp_addr + len as u64 > 0xFFFFFFFF {
64            core::write!(writer, "0x{:016x}: ", disp_addr + count as u64)?;
65        } else {
66            core::write!(writer, "0x{:08x}: ", disp_addr + count as u64)?;
67        }
68
69        let words = s / 4;
70        let (chunks, _) = buf[..s].as_chunks::<4>();
71        for chunk in chunks {
72            // C++ reads as uint32_t and prints with %08x.
73            // On little endian, uint32_t of [A, B, C, D] is 0xDDCCBBAA.
74            let val = u32::from_ne_bytes(*chunk);
75            core::write!(writer, "{:08x} ", val)?;
76        }
77        for _ in words..4 {
78            core::write!(writer, "         ")?;
79        }
80        core::write!(writer, "|")?;
81
82        for i in 0..16 {
83            let c = buf[i];
84            if i < s && (c.is_ascii_graphic() || c == b' ') {
85                core::write!(writer, "{}", c as char)?;
86            } else {
87                core::write!(writer, ".")?;
88            }
89        }
90        core::write!(writer, "|\n")?;
91    }
92    Ok(())
93}
94
95/// Do a hex dump against a writer, formatting data as individual 8-bit bytes
96/// alongside an 8-bit ASCII panel, displaying up to 16 bytes per line.
97pub fn hexdump8_very_ex_rs<W: Write>(
98    writer: &mut W,
99    data: &[u8],
100    disp_addr: u64,
101) -> core::fmt::Result {
102    // SAFETY: A standard safe slice reference is guaranteed to point to mapped, valid, and unpoisoned memory.
103    unsafe { hexdump8_very_ex_raw(writer, data.as_ptr(), data.len(), disp_addr) }
104}
105
106/// Do a hex dump against a writer, formatting data as individual 8-bit bytes
107/// alongside an 8-bit ASCII panel, displaying up to 16 bytes per line, using suspect raw pointers.
108///
109/// # Safety
110/// The caller must ensure that the memory range `[ptr, ptr + len)` is mapped and readable.
111/// KASan instrumentation is bypassed during pointer accesses to prevent panic loops.
112pub unsafe fn hexdump8_very_ex_raw<W: Write>(
113    writer: &mut W,
114    ptr: *const u8,
115    len: usize,
116    disp_addr: u64,
117) -> core::fmt::Result {
118    for count in (0..len).step_by(16) {
119        let chunk_len = core::cmp::min(len - count, 16);
120
121        // Copy available bytes to a local buffer, padded with 0.
122        let mut buf = [0u8; 16];
123        // SAFETY: The range [ptr + count, ptr + count + chunk_len) is guaranteed mapped by caller.
124        unsafe {
125            unsanitized_copy(ptr.add(count), buf.as_mut_ptr(), chunk_len);
126        }
127
128        if disp_addr + len as u64 > 0xFFFFFFFF {
129            core::write!(writer, "0x{:016x}: ", disp_addr + count as u64)?;
130        } else {
131            core::write!(writer, "0x{:08x}: ", disp_addr + count as u64)?;
132        }
133
134        for i in 0..chunk_len {
135            core::write!(writer, "{:02x} ", buf[i])?;
136        }
137        for _ in chunk_len..16 {
138            core::write!(writer, "   ")?;
139        }
140
141        core::write!(writer, "|")?;
142
143        for i in 0..chunk_len {
144            let c = buf[i];
145            if c.is_ascii_graphic() || c == b' ' {
146                core::write!(writer, "{}", c as char)?;
147            } else {
148                core::write!(writer, ".")?;
149            }
150        }
151        core::write!(writer, "\n")?;
152    }
153    Ok(())
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159    extern crate std;
160    use std::string::String;
161
162    #[test]
163    fn test_hexdump_very_ex() {
164        let input = [0u8, 1, 2, 3, b'a', b'b', b'c', b'd'];
165        let test_display_addr = 0x1000;
166        let expected = "0x00001000: 03020100 64636261                   |....abcd........|\n";
167
168        let mut output = String::new();
169        hexdump_very_ex_rs(&mut output, &input, test_display_addr).unwrap();
170        assert_eq!(output, expected);
171    }
172
173    #[test]
174    fn test_hexdump_very_ex_raw() {
175        let input = [0u8, 1, 2, 3, b'a', b'b', b'c', b'd'];
176        let test_display_addr = 0x1000;
177        let expected = "0x00001000: 03020100 64636261                   |....abcd........|\n";
178
179        let mut output = String::new();
180        // SAFETY: The static test array memory range is mapped and valid.
181        unsafe {
182            hexdump_very_ex_raw(&mut output, input.as_ptr(), input.len(), test_display_addr)
183                .unwrap();
184        }
185        assert_eq!(output, expected);
186    }
187
188    #[test]
189    fn test_hexdump8_very_ex() {
190        let input = [0u8, 1, 2, 3, b'a', b'b', b'c', b'd'];
191        let test_display_addr = 0x1000;
192        let expected = "0x00001000: 00 01 02 03 61 62 63 64                         |....abcd\n";
193
194        let mut output = String::new();
195        hexdump8_very_ex_rs(&mut output, &input, test_display_addr).unwrap();
196        assert_eq!(output, expected);
197    }
198
199    #[test]
200    fn test_hexdump8_very_ex_raw() {
201        let input = [0u8, 1, 2, 3, b'a', b'b', b'c', b'd'];
202        let test_display_addr = 0x1000;
203        let expected = "0x00001000: 00 01 02 03 61 62 63 64                         |....abcd\n";
204
205        let mut output = String::new();
206        // SAFETY: The static test array memory range is mapped and valid.
207        unsafe {
208            hexdump8_very_ex_raw(&mut output, input.as_ptr(), input.len(), test_display_addr)
209                .unwrap();
210        }
211        assert_eq!(output, expected);
212    }
213}