Skip to content

Instantly share code, notes, and snippets.

@hucsmn
Last active October 7, 2019 12:07
Show Gist options
  • Save hucsmn/8235d31c1c3e764873e00f8ef2c01855 to your computer and use it in GitHub Desktop.
Save hucsmn/8235d31c1c3e764873e00f8ef2c01855 to your computer and use it in GitHub Desktop.
Compare binary data in terminal, which could be helpful for debugging

Place hexcmp.rs in a crate, as a module of some code like:

#[macro_use]
mod hexcmp;

use hexcmp::*;

fn main() {
    // sample data
    let s: &[u8] = &[...];
    let t: &[u8] = &[...];
    let d: Vec<_> = s.iter().zip(t.iter()).map(|(x, y)| y.wrapping_sub(x)).collect();

    let mut xcmp = hex_compare! {
        "source" => s;
        "target" => t;
        "delta" => &d[..];
    };
    xcmp = xcmp
        .ident("#   ")         // set identation
        .tab_head(true)        // show table header
        .max_line(16)          // maximum line count
        .text("source", true)  // show ascii text in the source column
        .text("target", true); // show ascii text in the target column
    println!("{}", xcmp);
}

The output would look like this:

#        |         source         |         target         |      delta      
#   0000 |54 0e 00 90 48 83 T...H.|1f 08 00 90 48 83 ....H.|cb fa 00 00 00 00
#   0006 |c4 28 5b 5f 5e 41 .([_^A|c4 28 5b 5f 5e 41 .([_^A|00 00 00 00 00 00
#   000c |5c 41 5d 41 5e 41 \A]A^A|5c 41 5d 41 5e 41 \A]A^A|00 00 00 00 00 00
#   0012 |5f 5d c3 cc cc cc _]....|5f 5d c3 cc cc cc _]....|00 00 00 00 00 00
#   0018 |cc cc cc 55 41 57 ...UAW|cc cc cc 55 41 57 ...UAW|00 00 00 00 00 00
#   001e |41 56 41 55 41 54 AVAUAT|41 56 41 55 41 54 AVAUAT|00 00 00 00 00 00
#   0024 |56 57 53 48 81 ec VWSH..|56 57 53 48 81 ec VWSH..|00 00 00 00 00 00
#    ... |          ...           |          ...           |       ...       
#   0030 |ac 24 80 00 00 00 .$....|ac 24 80 00 00 00 .$....|00 00 00 00 00 00
#   0036 |48 c7 85 c0 03 00 H.....|48 c7 85 c0 03 00 H.....|00 00 00 00 00 00
#   003c |00 fe ff ff ff 4c .....L|00 fe ff ff ff 4c .....L|00 00 00 00 00 00
#   0042 |89 85 98 03 00 00 ......|89 85 98 03 00 00 ......|00 00 00 00 00 00
#   0048 |49 89 d7 49 89 ce I..I..|49 89 d7 49 89 ce I..I..|00 00 00 00 00 00
#   004e |48 8b 95 30 04 00 H..0..|48 8b 95 30 04 00 H..0..|00 00 00 00 00 00
#   0054 |00 4c 89 c9 e8 be .L....|00 4c 89 c9 e8 fe .L....|00 00 00 00 00 40
#   005a |e0 30             .0    |7e 2f             ~/    |9e ff            
#![allow(unused)]
use std::fmt::{self, Display};
use std::iter::repeat;
#[macro_export]
macro_rules! hex_compare {
($($name:expr => $data:expr;)*) => {
HexCompare::new(vec![$(
HexColumn::new($name, $data, false),
)*])
};
}
pub struct HexColumn<'a> {
name: String,
data: &'a [u8],
text: bool,
}
impl<'a> HexColumn<'a> {
pub fn new<S: Into<String>>(name: S, data: &'a [u8], text: bool) -> Self {
HexColumn{
name: name.into(),
data,
text: false,
}
}
}
pub struct HexCompare<'a> {
ident: String,
tab_head: bool,
line_num: bool,
data_cols: Option<usize>,
max_line: Option<usize>,
max_width: Option<usize>,
groups: Vec<HexColumn<'a>>,
}
impl<'a> HexCompare<'a> {
pub fn new(groups: Vec<HexColumn<'a>>) -> Self {
HexCompare {
ident: String::new(),
tab_head: false,
line_num: true,
data_cols: None,
max_line: None,
max_width: None,
groups,
}
}
pub fn ident(mut self, ident: &str) -> Self {
self.ident = ident.to_owned();
self
}
pub fn tab_head(mut self, th: bool) -> Self {
self.tab_head = th;
self
}
pub fn line_num(mut self, ln: bool) -> Self {
self.line_num = ln;
self
}
pub fn auto_data_cols(mut self) -> Self {
self.data_cols = None;
self
}
pub fn data_cols(mut self, pl: usize) -> Self {
self.data_cols = Some(pl);
self
}
pub fn no_max_line(mut self) -> Self {
self.max_line = None;
self
}
pub fn max_line(mut self, ml: usize) -> Self {
self.max_line = Some(ml);
self
}
pub fn max_width(mut self, mw: usize) -> Self {
self.max_width = Some(mw);
self
}
pub fn auto_width(mut self) -> Self {
self.max_width = None;
self
}
pub fn text(mut self, name: &str, text: bool) -> Self {
for i in 0..self.groups.len() {
if self.groups[i].name == name {
self.groups[i].text = text;
}
}
self
}
}
impl<'a> Display for HexCompare<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.groups.len() == 0 {
return Ok(());
}
// g: count of groups, t: count of groups with text, n: maximun data length,
// p: identation length, k: line number column length, w: maximum width,
// c: data column count, l: line count, omit: range of lines to be omited,
let g = self.groups.len();
let t = self.groups.iter().filter(|d| d.text).count();
let n = self.groups.iter().map(|d| d.data.len()).max().unwrap();
let p = self.ident.len();
let k = if !self.line_num {
0
} else {
hex_width(n).max(4) + 2
};
let mut w = self.max_width.unwrap_or(80);
if w + 1 < p + k + t + 3 * g + t {
w = p + k + t + 3 * g + t - 1;
}
let c = self
.data_cols
.unwrap_or_else(|| (w + 1 - p - k - t) / (3 * g + t))
.max(1);
let l = if n % c == 0 { n / c } else { n / c + 1 };
let mut omit = 0..0;
if let Some(mut m) = self.max_line {
if self.tab_head && m > 1 {
m -= 1;
}
if m > 1 && m < l {
omit = m / 2..m / 2 + (l - m);
}
}
if self.tab_head {
write!(f, "{}", self.ident)?;
write!(f, "{:>width$}", "|", width = k)?;
for (i, grp) in self.groups.iter().enumerate() {
if i != 0 {
write!(f, "|")?;
}
let w = if grp.text { 4 * c } else { 3 * c - 1 };
write!(f, "{:^width$}", grp.name, width = w)?;
}
writeln!(f, "")?;
}
let mut xbuf: Vec<u8> = Vec::with_capacity(c);
let mut tbuf = String::with_capacity(c);
let mut ln = 0;
while ln < l {
write!(f, "{}", self.ident)?;
if ln >= omit.start && ln < omit.end {
write!(f, "{:^width$}|", "...", width = k - 1)?;
for (i, grp) in self.groups.iter().enumerate() {
if i != 0 {
write!(f, "|")?;
}
let w = if grp.text { 4 * c } else { 3 * c - 1 };
write!(f, "{:^width$}", "...", width = w)?;
}
writeln!(f, "")?;
ln = omit.end;
continue;
}
let off = ln * c;
if self.line_num {
write!(f, "{:0width$x} |", off, width = k - 2)?;
}
for (i, grp) in self.groups.iter().enumerate() {
if i != 0 {
write!(f, "|")?;
}
xbuf.truncate(0);
tbuf.truncate(0);
if off + c <= grp.data.len() {
xbuf.extend(&grp.data[off..off + c]);
tbuf.extend(grp.data[off..off + c].iter().map(|&b| view_acsii(b)));
} else if off < grp.data.len() {
xbuf.extend(&grp.data[off..]);
tbuf.extend(grp.data[off..].iter().map(|&b| view_acsii(b)));
}
for (j, mayb) in xbuf
.iter()
.map(Some)
.chain(repeat(None).take(c - xbuf.len()))
.enumerate()
{
if j != 0 {
write!(f, " ")?;
}
if let Some(&b) = mayb {
write!(f, "{:02x}", b)?;
} else {
write!(f, " ")?;
}
}
if grp.text {
write!(f, " {:<width$}", tbuf, width = c)?;
}
}
writeln!(f, "")?;
ln += 1;
}
Ok(())
}
}
fn hex_width(x: usize) -> usize {
let mut mask = std::usize::MAX;
while mask != 0 && x & (mask >> 1) == x {
mask >>= 1;
}
let mut n = 0;
while mask != 0 {
mask >>= 1;
n += 1;
}
if n % 4 == 0 {
n / 4
} else {
n / 4 + 1
}
}
fn view_acsii(byte: u8) -> char {
if byte < 32 || byte >= 127 {
'.'
} else {
byte as char
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment