gdbc 0.1.0

comprehensive terminal-based GDB client
Documentation
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
use colored::Colorize;
use std::{fmt::Write, io::Cursor, str::FromStr};

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DisplayMode {
    Hex,
    Ascii,
    Int16,
    Int32,
    Int64,
}

impl FromStr for DisplayMode {
    type Err = &'static str;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "hex" => Ok(DisplayMode::Hex),
            "ascii" => Ok(DisplayMode::Ascii),
            "int16" => Ok(DisplayMode::Int16),
            "int32" => Ok(DisplayMode::Int32),
            "int64" => Ok(DisplayMode::Int64),
            _ => Err("Unknown display mode"),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Endianness {
    Little,
    Big,
}

impl FromStr for Endianness {
    type Err = &'static str;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "little" => Ok(Endianness::Little),
            "big" => Ok(Endianness::Big),
            _ => Err("Unknown endianness"),
        }
    }
}

pub fn format_memory_display(
    data: &[u8],
    address: u64,
    mode: DisplayMode,
    endianness: Endianness,
) -> String {
    match mode {
        DisplayMode::Hex => format_hex(data, address),
        DisplayMode::Ascii => format_ascii(data, address),
        DisplayMode::Int16 => format_int::<u16>(data, address, endianness),
        DisplayMode::Int32 => format_int::<u32>(data, address, endianness),
        DisplayMode::Int64 => format_int::<u64>(data, address, endianness),
    }
}

fn format_hex(data: &[u8], address: u64) -> String {
    let mut result = String::new();
    let bytes_per_line = 16;

    for (i, chunk) in data.chunks(bytes_per_line).enumerate() {
        let line_addr = address + (i * bytes_per_line) as u64;
        write!(result, "{:016x}:  ", line_addr).unwrap();

        // Hex display
        for (j, byte) in chunk.iter().enumerate() {
            write!(result, "{:02x} ", byte).unwrap();

            // Extra space at 8 bytes
            if j == 7 {
                result.push(' ');
            }
        }

        // Padding if incomplete line
        if chunk.len() < bytes_per_line {
            let padding = (bytes_per_line - chunk.len()) * 3 + if chunk.len() <= 8 { 1 } else { 0 };
            for _ in 0..padding {
                result.push(' ');
            }
        }

        // ASCII display
        result.push_str(" |");
        for &byte in chunk {
            if byte >= 32 && byte <= 126 {
                // Printable ASCII
                result.push(byte as char);
            } else {
                // Non-printable
                result.push('.');
            }
        }

        // Padding for incomplete line
        for _ in chunk.len()..bytes_per_line {
            result.push(' ');
        }
        result.push_str("|\n");
    }

    result
}

fn format_ascii(data: &[u8], address: u64) -> String {
    let mut result = String::new();
    write!(result, "ASCII string at address {:016x}:\n\"", address).unwrap();

    // Convert to ASCII string
    for &byte in data {
        if byte == 0 {
            // Null terminator
            break;
        } else if byte >= 32 && byte <= 126 {
            // Printable ASCII
            result.push(byte as char);
        } else {
            // Non-printable
            write!(result, "\\x{:02x}", byte).unwrap();
        }
    }

    result.push_str("\"\n");
    result
}

fn format_int<T: std::fmt::Display>(data: &[u8], address: u64, endianness: Endianness) -> String {
    let type_size = std::mem::size_of::<T>();
    let mut result = String::new();
    let type_name = match type_size {
        2 => "int16",
        4 => "int32",
        8 => "int64",
        _ => "unknown",
    };

    write!(
        result,
        "{} values at address {:016x}:\n",
        type_name, address
    )
    .unwrap();

    for (i, chunk) in data.chunks(type_size).enumerate() {
        if chunk.len() == type_size {
            let value = read_int::<T>(chunk, endianness);
            write!(
                result,
                "[{:04x}] {:016x} = {}\n",
                i * type_size,
                address + (i * type_size) as u64,
                value
            )
            .unwrap();
        }
    }

    result
}

fn read_int<T>(data: &[u8], endianness: Endianness) -> u64 {
    let mut cursor = Cursor::new(data);

    match std::mem::size_of::<T>() {
        2 => match endianness {
            Endianness::Little => cursor.read_u16::<LittleEndian>().unwrap_or(0) as u64,
            Endianness::Big => cursor.read_u16::<BigEndian>().unwrap_or(0) as u64,
        },
        4 => match endianness {
            Endianness::Little => cursor.read_u32::<LittleEndian>().unwrap_or(0) as u64,
            Endianness::Big => cursor.read_u32::<BigEndian>().unwrap_or(0) as u64,
        },
        8 => match endianness {
            Endianness::Little => cursor.read_u64::<LittleEndian>().unwrap_or(0),
            Endianness::Big => cursor.read_u64::<BigEndian>().unwrap_or(0),
        },
        _ => 0,
    }
}

pub fn colorize_output(text: &str, use_colors: bool) -> String {
    if !use_colors {
        return text.to_string();
    }

    // Apply coloring based on content
    // This is a simple example - can be extended for more sophisticated coloring
    let mut result = String::new();

    for line in text.lines() {
        if line.contains("Error") || line.contains("error") {
            result.push_str(&line.red().to_string());
        } else if line.contains("Warning") || line.contains("warning") {
            result.push_str(&line.yellow().to_string());
        } else if line.starts_with("0x") || line.contains(":") && line.contains("|") {
            // Memory display lines
            result.push_str(&colorize_memory_line(line));
        } else {
            result.push_str(line);
        }
        result.push('\n');
    }

    result
}

fn colorize_memory_line(line: &str) -> String {
    // Simple colorization of memory dump lines
    let parts: Vec<&str> = line.splitn(2, ":").collect();
    if parts.len() == 2 {
        return format!("{}: {}", parts[0].cyan().to_string(), parts[1].to_string());
    }
    line.to_string()
}