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, &c) in buf.iter().enumerate() {
83            if i < s && (c.is_ascii_graphic() || c == b' ') {
84                core::write!(writer, "{}", c as char)?;
85            } else {
86                core::write!(writer, ".")?;
87            }
88        }
89        core::writeln!(writer, "|")?;
90    }
91    Ok(())
92}
93
94/// Do a hex dump against a writer, formatting data as individual 8-bit bytes
95/// alongside an 8-bit ASCII panel, displaying up to 16 bytes per line.
96pub fn hexdump8_very_ex_rs<W: Write>(
97    writer: &mut W,
98    data: &[u8],
99    disp_addr: u64,
100) -> core::fmt::Result {
101    // SAFETY: A standard safe slice reference is guaranteed to point to mapped, valid, and unpoisoned memory.
102    unsafe { hexdump8_very_ex_raw(writer, data.as_ptr(), data.len(), disp_addr) }
103}
104
105/// Do a hex dump against a writer, formatting data as individual 8-bit bytes
106/// alongside an 8-bit ASCII panel, displaying up to 16 bytes per line, using suspect raw pointers.
107///
108/// # Safety
109/// The caller must ensure that the memory range `[ptr, ptr + len)` is mapped and readable.
110/// KASan instrumentation is bypassed during pointer accesses to prevent panic loops.
111pub unsafe fn hexdump8_very_ex_raw<W: Write>(
112    writer: &mut W,
113    ptr: *const u8,
114    len: usize,
115    disp_addr: u64,
116) -> core::fmt::Result {
117    for count in (0..len).step_by(16) {
118        let chunk_len = core::cmp::min(len - count, 16);
119
120        // Copy available bytes to a local buffer, padded with 0.
121        let mut buf = [0u8; 16];
122        // SAFETY: The range [ptr + count, ptr + count + chunk_len) is guaranteed mapped by caller.
123        unsafe {
124            unsanitized_copy(ptr.add(count), buf.as_mut_ptr(), chunk_len);
125        }
126
127        if disp_addr + len as u64 > 0xFFFFFFFF {
128            core::write!(writer, "0x{:016x}: ", disp_addr + count as u64)?;
129        } else {
130            core::write!(writer, "0x{:08x}: ", disp_addr + count as u64)?;
131        }
132
133        for &b in &buf[..chunk_len] {
134            core::write!(writer, "{:02x} ", b)?;
135        }
136        for _ in chunk_len..16 {
137            core::write!(writer, "   ")?;
138        }
139
140        core::write!(writer, "|")?;
141
142        for &c in &buf[..chunk_len] {
143            if c.is_ascii_graphic() || c == b' ' {
144                core::write!(writer, "{}", c as char)?;
145            } else {
146                core::write!(writer, ".")?;
147            }
148        }
149        core::writeln!(writer)?;
150    }
151    Ok(())
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    extern crate std;
158    use std::string::String;
159
160    #[test]
161    fn test_hexdump_very_ex() {
162        let input = [0u8, 1, 2, 3, b'a', b'b', b'c', b'd'];
163        let test_display_addr = 0x1000;
164        let expected = "0x00001000: 03020100 64636261                   |....abcd........|\n";
165
166        let mut output = String::new();
167        hexdump_very_ex_rs(&mut output, &input, test_display_addr).unwrap();
168        assert_eq!(output, expected);
169    }
170
171    #[test]
172    fn test_hexdump_very_ex_raw() {
173        let input = [0u8, 1, 2, 3, b'a', b'b', b'c', b'd'];
174        let test_display_addr = 0x1000;
175        let expected = "0x00001000: 03020100 64636261                   |....abcd........|\n";
176
177        let mut output = String::new();
178        // SAFETY: The static test array memory range is mapped and valid.
179        unsafe {
180            hexdump_very_ex_raw(&mut output, input.as_ptr(), input.len(), test_display_addr)
181                .unwrap();
182        }
183        assert_eq!(output, expected);
184    }
185
186    #[test]
187    fn test_hexdump8_very_ex() {
188        let input = [0u8, 1, 2, 3, b'a', b'b', b'c', b'd'];
189        let test_display_addr = 0x1000;
190        let expected = "0x00001000: 00 01 02 03 61 62 63 64                         |....abcd\n";
191
192        let mut output = String::new();
193        hexdump8_very_ex_rs(&mut output, &input, test_display_addr).unwrap();
194        assert_eq!(output, expected);
195    }
196
197    #[test]
198    fn test_hexdump8_very_ex_raw() {
199        let input = [0u8, 1, 2, 3, b'a', b'b', b'c', b'd'];
200        let test_display_addr = 0x1000;
201        let expected = "0x00001000: 00 01 02 03 61 62 63 64                         |....abcd\n";
202
203        let mut output = String::new();
204        // SAFETY: The static test array memory range is mapped and valid.
205        unsafe {
206            hexdump8_very_ex_raw(&mut output, input.as_ptr(), input.len(), test_display_addr)
207                .unwrap();
208        }
209        assert_eq!(output, expected);
210    }
211}