开发一个 Linux 调试器(五):源码和信号

2024年 7月 19日 95.6k 0

开发一个 Linux 调试器(五):源码和信号-1

在上一部分我们学习了关于 DWARF 的信息,以及它如何被用于读取变量和将被执行的机器码与我们的高级语言的源码联系起来。在这一部分,我们将进入实践,实现一些我们调试器后面会使用的 DWARF 原语。我们也会利用这个机会,使我们的调试器可以在命中一个断点时打印出当前的源码上下文。

系列文章索引

随着后面文章的发布,这些链接会逐渐生效。

  • 准备环境
  • 断点
  • 寄存器和内存
  • Elves 和 dwarves
  • 源码和信号
  • 源码级逐步执行
  • 源码级断点
  • 调用栈展开
  • 读取变量
  • 下一步
  • 设置我们的 DWARF 解析器

    正如我在这系列文章开始时备注的,我们会使用 libelfin 来处理我们的 DWARF 信息。希望你已经在第一部分设置好了这些,如果没有的话,现在做吧,确保你使用我仓库的 fbreg 分支。

    一旦你构建好了 libelfin,就可以把它添加到我们的调试器。第一步是解析我们的 ELF 可执行程序并从中提取 DWARF 信息。使用 libelfin 可以轻易实现,只需要对调试器作以下更改:

    class debugger {
    public:
        debugger (std::string prog_name, pid_t pid)
             : m_prog_name{std::move(prog_name)}, m_pid{pid} {
            auto fd = open(m_prog_name.c_str(), O_RDONLY);
    
            m_elf = elf::elf{elf::create_mmap_loader(fd)};
            m_dwarf = dwarf::dwarf{dwarf::elf::create_loader(m_elf)};
        }
        //...
    
    private:
        //...
        dwarf::dwarf m_dwarf;
        elf::elf m_elf;
    };
    

    我们使用了 open 而不是 std::ifstream,因为 elf 加载器需要传递一个 UNIX 文件描述符给 mmap,从而可以将文件映射到内存而不是每次读取一部分。

    调试信息原语

    下一步我们可以实现从程序计数器的值中提取行条目(line entry)以及函数 DWARF 信息条目(function DIE)的函数。我们从 get_function_from_pc 开始:

    dwarf::die debugger::get_function_from_pc(uint64_t pc) {
        for (auto &cu : m_dwarf.compilation_units()) {
            if (die_pc_range(cu.root()).contains(pc)) {
                for (const auto& die : cu.root()) {
                    if (die.tag == dwarf::DW_TAG::subprogram) {
                        if (die_pc_range(die).contains(pc)) {
                            return die;
                        }
                    }
                }
            }
        }
    
        throw std::out_of_range{"Cannot find function"};
    }
    

    这里我采用了朴素的方法,迭代遍历编译单元直到找到一个包含程序计数器的,然后迭代遍历它的子节点直到我们找到相关函数(DW_TAG_subprogram)。正如我在上一篇中提到的,如果你想要的话你可以处理类似的成员函数或者内联等情况。

    接下来是 get_line_entry_from_pc

    dwarf::line_table::iterator debugger::get_line_entry_from_pc(uint64_t pc) {
        for (auto &cu : m_dwarf.compilation_units()) {
            if (die_pc_range(cu.root()).contains(pc)) {
                auto &lt = cu.get_line_table();
                auto it = lt.find_address(pc);
                if (it == lt.end()) {
                    throw std::out_of_range{"Cannot find line entry"};
                }
                else {
                    return it;
                }
            }
        }
    
        throw std::out_of_range{"Cannot find line entry"};
    }
    

    同样,我们可以简单地找到正确的编译单元,然后查询行表获取相关的条目。

    打印源码

    当我们命中一个断点或者逐步执行我们的代码时,我们会想知道处于源码中的什么位置。

    void debugger::print_source(const std::string& file_name, unsigned line, unsigned n_lines_context) {
    std::ifstream file {file_name};

    //获得一个所需行附近的窗口
    auto start_line = line

    相关文章

    Linux 命令行的聊天工具 CenterIM
    Linux 桌面年仍未到来 但 Linux 移动之年已到来
    12 个在线学习 Linux 技能网站
    Linux Mint : 会是另一个新的 Ubuntu 吗?
    W3Conf 开发者大会将于下周召开
    Ubuntu 10.04 ARM 处理器上网本版本结束服务期

    发布评论