欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

看看俺是怎么样编写一个Linux 调试器(二):断点

来源:本站整理 作者:佚名 时间:2017-10-11 TAG: 我要投稿

第一部分中咱们写了一个小型的进程启动器作为咱们的调试器,在这一篇中,咱们将进修在x86 linux下断点是若何运作的和为咱们的工具增加设置断点的功效。
断点是怎样构成的?
断点的范例有两种:硬件断点和内存断点。硬件断点平日经由进程设置架构指定的寄存器来设置中止,而内存断点则是经由进程改动正在履行的代码来设置中止。这篇文章咱们把精神会合在内存断点上,由于它们较为简略且没有数目限定。在x86上,你在统一时候至多只能设置4个硬件断点,但它们不只能在代码履行到此处的时候断下,还能在被读取或是被写入的时候触发。
前面说内存断点是经由进程改动今后正在履行的代码来完成的,那末成绩是:
- 咱们若何改动代码?
- 若何改动代码能力设置断点?
- 若何让调试器注意到?
第一个成绩的谜底很明显,ptrace。咱们以前应用它来设置咱们的法式来跟踪和继承履行,但咱们也能够或许应用它来读取和写入内存。
当履行到断点地位的时候,咱们的改动要让处置器停息并向调试器发送旌旗灯号。在x86上这是经由进程将必要下断的地点上的指令设置为int 3来完成的。x86上有一个中止向量表(interrupt vector table),操纵系统经由进程中止向量表可以或许为很多变乱注册处置函数,好比缺页中止(page faults),掩护差错(protection faults),有效操纵码(invalid opcodes)等。它有点像注册差错的回调函数,然则在硬件层面完成的。当处置器履行到int 3指令时,控制权就被通报给了断点中止处置法式(breakpoint interrupt handler),就Linux来讲,是给进程发送SIGTRAP旌旗灯号。下图展示了这个进程,当把mov指令的第一个字节笼罩为0xcc,即int 3的机械码。

末了一个成绩是若何让调试器注意到这个中止。假如你还记得上一篇中咱们应用waitpid函数来监听被调试端发送的旌旗灯号的办法,咱们在此处也能够或许用异样的办法来处置:设置断点,让法式继承履行,挪用waitpid直到收到SIGTRAP旌旗灯号。而后就能够或许经由进程打印今后代码的地位或转变图形界面当选中的行来将这个断点转达给用户。
完成内存断点
咱们完成一个breakpoint类来表示一个断点断在某个地位,而后依据需要抉择启用或是停用这个断点。
class breakpoint {
public:
    breakpoint(pid_t pid, std::intptr_t addr)
        : m_pid{pid}, m_addr{addr}, m_enabled{false}, m_saved_data{}
    {}
    void enable();
    void disable();
    auto is_enabled() const -> bool { return m_enabled; }
    auto get_address() const -> std::intptr_t { return m_addr; }
private:
    pid_t m_pid;
    std::intptr_t m_addr;
    bool m_enabled;
    uint8_t m_saved_data; //存储断点地点
};
这些代码多半只是跟踪状况,真正完成部分在enable和disable函数中。
正如咱们下面了解到的,咱们必要将用户给定地点上的指令改动为int 3,即0xcc。咱们还要保留那条指令底本的机械码,以便后续规复这行代码。并且咱们不克不及忘怀去履行这行代码。
void breakpoint::enable() {
    auto data = ptrace(PTRACE_PEEKDATA, m_pid, m_addr, nullptr);
    m_saved_data = static_cast(data & 0xff); //保留最低一字节
    uint64_t int3 = 0xcc;
    uint64_t data_with_int3 = ((data & ~0xff) | int3); //将最低一字节改成0xcc
    ptrace(PTRACE_POKEDATA, m_pid, m_addr, data_with_int3);
    m_enabled = true;
}
PTRACE_PEEKDATA这个request奉告ptrace若何去读取被调试法式的内存。咱们通报给它一个pid和地点,而后它将指定地点上的64位长度的值前往给咱们。 (m_saved_data & ~0xff)将前往数据的最低字节置零,而后咱们经由进程OR指令把int 3和最低字节置零的指令做或操纵,从而获得能发生中止的指令。末了,咱们经由进程PTRACE_POKEDATA将这条指令写入内存原地位来设置断点。
disable比拟简略,但也有点奇妙。由于`ptrace`的内存操纵针对付words而不是一个字节,是以咱们要先把words读返来,而后将最低一字节复原,再将words写回内存。
void breakpoint::disable() {
    auto data = ptrace(PTRACE_PEEKDATA, m_pid, m_addr, nullptr);
    auto restored_data = ((data & ~0xff) | m_saved_data);
    ptrace(PTRACE_POKEDATA, m_pid, m_addr, restored_data);
    m_enabled = false;
}
给调试器增加断点
为了能经由进程用户界面设置断点,咱们必要对debugger类做三处改动。
1. 为debugger增加断点数据贮存布局体
2. 增加一个set_breakpoint_at_address函数
3. 给handle_command函数增加break指令
我把断点存在std::unordered_map范例的布局体中,是以可以或许简练敏捷地检测给定的地点上能否曾经有断点,假如有就取回这个断点工具。
class debugger {
    //...
    void set_breakpoint_at_address(std::intptr_t addr);
    //...
private:
    //...
    std::unordered_mapintptr_t,breakpoint> m_breakpoints;
}
在set_breakpoint_at_address函数中咱们将创立一个新的断点,启用它,把它参加布局体中,并向用户打印一条信息。你爱好的话可以或许将所有的信息掏出而后就能够或许像一个敕令行工具同样应用你的调试器。为了简练,我把它们都整合到了一路。

[1] [2]  下一页

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载