最近对rust语言比较感兴趣,也在探索它的各种优秀特性,学习了一段时间它的基础语法之后,最近正打算写个小项目练练手。考虑到日常工作中经常需要分析各种ELF格式的二进制文件,而传统的xxd工具界面相对简陋,我决定用Rust写一个现代化的终端十六进制查看器。最终的实现效果感觉还不错,所以就把开发过程和运行效果,跟大家做个分享。

为什么选择Rust?

对于这种系统级工具,Rust是一个非常好的选择:

  1. 零成本抽象:性能与C/C++相当
  2. 内存安全:避免缓冲区溢出等问题
  3. 优秀的生态系统:有丰富的第三方库可用
  4. 跨平台支持:一次编写到处编译

功能需求

我们要实现的hex viewer需要具备以下功能:

  1. 显示文件的十六进制内容,同时展示对应的ASCII字符
  2. 支持键盘导航(上下滚动、翻页)
  3. 搜索功能(支持文本搜索)
  4. 美观的TUI界面
  5. 命令行参数解析

技术选型

为了快速开发,我们使用了一些优秀的第三方库:

  • clap:用于命令行参数解析
  • ratatui:构建终端用户界面
  • crossterm:处理终端交互

这些库都是Rust生态中相应领域的首选方案。

核心实现

数据结构设计

首先我们需要定义程序的核心数据结构:

struct HexViewer {
    file_data: Vec<u8>,          // 存储文件的所有字节
    scroll_offset: usize,        // 滚动偏移量
    search_term: String,         // 当前搜索词
    search_results: Vec<usize>,  // 搜索结果位置列表
    current_search_index: usize, // 当前聚焦的搜索结果索引
    bytes_per_line: usize,       // 每行显示多少字节
    input_mode: InputMode,       // 输入模式(导航/搜索)
    search_input: String,        // 正在输入的搜索词
    case_sensitive: bool,        // 是否区分大小写
}

TUI界面渲染

使用ratatui库我们可以创建美观的终端界面。主要分为两个区域:

  1. 主显示区域:展示hex内容
  2. 底部状态栏:显示状态信息和提示
fn render_ui(f: &mut Frame, app: &HexViewer) {
    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Min(1),
            Constraint::Length(1), // 状态栏
        ])
        .split(f.area());

    // 主显示区域
    let hex_block = Block::default()
        .borders(Borders::ALL)
        .title(Title::from("Hex Viewer").alignment(Alignment::Center));

    // 构建显示行
    let mut lines = Vec::new();
    let visible_lines = app.visible_lines_count(chunks[0].height as usize);
    
    for i in 0..visible_lines {
        if app.scroll_offset + i < app.total_lines() {
            let offset = (app.scroll_offset + i) * app.bytes_per_line;
            let line = app.hex_line(offset);
            lines.push(line);
        } else {
            lines.push(Line::from(""));
        }
    }

    let paragraph = Paragraph::new(lines)
        .block(hex_block)
        .wrap(Wrap { trim: false });

    f.render_widget(paragraph, chunks[0]);
    
    // 状态栏渲染逻辑...
}

键盘事件处理

为了提供良好的用户体验,我们需要处理各种键盘事件:

fn handle_key_event(event: KeyEvent, app: &mut HexViewer, height: usize) -> bool {
    match app.input_mode {
        InputMode::Navigation => {
            match event.code {
                KeyCode::Char('q') | KeyCode::Esc => return false,
                KeyCode::Char('j') | KeyCode::Down => {
                    app.scroll_down(1, height)
                },
                KeyCode::Char('k') | KeyCode::Up => {
                    app.scroll_up(1)
                },
                KeyCode::PageDown => {
                    app.scroll_down(height / 2, height);
                },
                KeyCode::PageUp => {
                    app.scroll_up(height / 2);
                },
                KeyCode::Char('/') => {
                    app.enter_search_mode();
                },
                KeyCode::Char('n') => {
                    app.next_search_result(height);
                },
                KeyCode::Char('N') => {
                    app.prev_search_result(height);
                },
                _ => {}
            }
        }
        InputMode::Searching => {
            match event.code {
                KeyCode::Enter => {
                    app.search();
                    app.input_mode = InputMode::Navigation;
                },
                KeyCode::Esc => {
                    app.input_mode = InputMode::Navigation;
                },
                KeyCode::Backspace => {
                    app.search_input.pop();
                },
                KeyCode::Char(c) => {
                    app.search_input.push(c);
                },
                KeyCode::Tab => {
                    app.toggle_case_sensitivity();
                }
                _ => {}
            }
        }
    }
    true
}

搜索功能实现

搜索是这个工具的一个亮点功能,它不仅支持精确匹配,还能在结果间跳转:

fn search(&mut self) {
    self.search_term = self.search_input.clone();
    if self.search_term.is_empty() {
        self.search_results.clear();
        return;
    }

    self.search_results.clear();
    
    // 根据是否区分大小写进行搜索
    if self.case_sensitive {
        // 区分大小写的搜索
        let search_bytes = self.search_term.as_bytes();
        for i in 0..=self.file_data.len().saturating_sub(search_bytes.len()) {
            if self.file_data[i..].starts_with(search_bytes) {
                self.search_results.push(i);
            }
        }
    } else {
        // 不区分大小写的搜索
        // 将搜索词转为小写
        let search_term_lower = self.search_term.to_lowercase();
        let search_bytes = search_term_lower.as_bytes();
        
        // 对于不区分大小写的搜索,我们仍然在字节级别进行操作
        for i in 0..=self.file_data.len().saturating_sub(search_bytes.len()) {
            // 获取当前窗口的字节并转换为字符串再转为小写进行比较
            let current_slice = &self.file_data[i..i + search_bytes.len()];
            if let Ok(current_str) = std::str::from_utf8(current_slice) {
                if current_str.to_lowercase().starts_with(&search_term_lower) {
                    self.search_results.push(i);
                }
            }
            // 如果不是有效的UTF-8,则跳过这个位置
        }
    }
    
    self.current_search_index = 0;
}

运行效果

通过简单的测试可以看到,我们的hex viewer运行良好。它不仅能清晰地展示文件的十六进制内容,还提供了便捷的导航和搜索功能,同时还支持↑↓方向键翻页。

当使用搜索功能时,匹配的结果会被高亮显示,并且可以使用n/N坚在多个匹配项之间切换,大大提高了查找特定内容的效率和用户体验。

同时,还支持通过tab键配置大小写敏感性,如下图所示,是忽略大小写时的elf搜索结果,明显比上张图中大小的ELF搜索结果要多。

总结

通过这个项目,我深刻体会到了Rust在系统编程方面的优势:

  1. 强大的类型系统帮助我们在编译期就发现很多潜在问题
  2. 丰富的生态系统让开发变得高效
  3. 内存安全保障让我们可以专注于业务逻辑而不是内存管理

这个hex viewer虽然功能相对简单,但它展示了如何使用Rust构建一个实用的命令行工具。后续还可以添加更多功能,比如:

  • 编辑功能
  • 多种显示格式(十进制、八进制等)
  • 导出功能
  • 更复杂的搜索选项,如正则表达式等。

想了解更多关于Rust语言的知识及应用,可前往华为开放原子旋武开源社区(https://xuanwu.openatom.cn/),了解更多资讯~

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐