繁体   English   中英

调试异常(ptrace single_step)如何在 Linux kernel 上的 arm64 中工作?

[英]How does debug exceptions (ptrace single_step) work in arm64 on Linux kernel?

在 aarch64 中调用 ptrace SINGLESTEP 会发生什么,Linux kernel?

Linux 此问题的参考:5.15.5(2021 年 11 月最新稳定版)。

命名法:tracer(被跟踪的进程)和tracee(被跟踪的进程)。

通过 static 分析 + 跟踪 linux kernel,我试图重建 ptrace SINGLESTEP 调用时发生的确切情况。 我试图在学习操作系统中编写我所理解的内容,但未能获得相同的行为。 在问我的问题之前,让我总结一下我试图重构的过程:

  1. debug_monitor.c中启用了单步:
/* ptrace API */
void user_enable_single_step(struct task_struct *task)
{
    struct thread_info *ti = task_thread_info(task);

    if (!test_and_set_ti_thread_flag(ti, TIF_SINGLESTEP))
        set_regs_spsr_ss(task_pt_regs(task));
}
  1. 在 arm64 中,这包括设置单步位SPSR_EL1.SS (位置 21):

    /*

    • 单步 API 和异常处理。 */ static void set_user_regs_spsr_ss(struct user_pt_regs *regs) { regs->pstate |= DBG_SPSR_SS; }
  2. 我想,这应该引发一个“调试异常”(用户级别,EL0),在entry-common.c中捕获和处理:

static void noinstr el0_dbg(struct pt_regs *regs, unsigned long esr)
{
    /* Only watchpoints write FAR_EL1, otherwise its UNKNOWN */
    unsigned long far = read_sysreg(far_el1);

    enter_from_user_mode(regs);
    do_debug_exception(far, esr, regs);
    local_daif_restore(DAIF_PROCCTX);
    exit_to_user_mode(regs);
}
  1. do_debug_exception()fault.c中定义,由于是软件步骤,所以应该调用early_brk64数据结构的 function early_brk64
/*
 * __refdata because early_brk64 is __init, but the reference to it is
 * clobbered at arch_initcall time.
 * See traps.c and debug-monitors.c:debug_traps_init().
 */
static struct fault_info __refdata debug_fault_info[] = {
    { do_bad,   SIGTRAP,    TRAP_HWBKPT,    "hardware breakpoint"   },
    { do_bad,   SIGTRAP,    TRAP_HWBKPT,    "hardware single-step"  },
    { do_bad,   SIGTRAP,    TRAP_HWBKPT,    "hardware watchpoint"   },
    { do_bad,   SIGKILL,    SI_KERNEL,  "unknown 3"     },
    { do_bad,   SIGTRAP,    TRAP_BRKPT, "aarch32 BKPT"      },
    { do_bad,   SIGKILL,    SI_KERNEL,  "aarch32 vector catch"  },
    { early_brk64,  SIGTRAP,    TRAP_BRKPT, "aarch64 BRK"       },
    { do_bad,   SIGKILL,    SI_KERNEL,  "unknown 7"     },
};
  1. 后者在traps.c中定义,它调用bug_handler function,进而调用arm64_skip_faulting_instruction() :后者更新PC (tracee 上的 4 个字节:aarch64 指令是 32 位)
void arm64_skip_faulting_instruction(struct pt_regs *regs, unsigned long size)
{
    regs->pc += size;

    /*
     * If we were single stepping, we want to get the step exception after
     * we return from the trap.
     */
    if (user_mode(regs))
        user_fastforward_single_step(current);

    if (compat_user_mode(regs))
        advance_itstate(regs);
    else
        regs->pstate &= ~PSR_BTYPE_MASK;
}
  1. 最后,调用user_fastforward_single_step ,然后调用clear_user_regs_spsr_ss ,它只是重置之前设置的 SS 位:
static void clear_user_regs_spsr_ss(struct user_pt_regs *regs)
{
    regs->pstate &= ~DBG_SPSR_SS;
}

如果这个调用链是正确的,我无法理解上下文切换(从跟踪器到跟踪器)在哪里以及如何发生。 事实上,从这些调用来看,第 2-6 点似乎与示踪剂有关。 我没有注意到这个链中的上下文切换,但它应该。

我试图在学习操作系统中复制所有这些步骤。 为了清楚起见,我在设置 SPSR.SS 位(第 2 点)后未能生成异常,但我通过在MDSCR_EL1 寄存器上设置 SS 位、MDE 位和 KDE 位来强制生成硬件调试异常。

一旦我完成了这个技巧,我的步骤就得到了验证,但实际上,异常被跟踪器捕获(而不是被跟踪器捕获):跟踪器的 PC 被更新,等等。 我认为我通过 static 分析代码和 ftrace 检索到的步骤并不完全正确。 你能帮忙确定在哪里吗?

您想查看信号传递代码的内部,最终在kernel/signal.c中。 这是在do_debug_exception中启动的,它调用arm64_notify_die 您可以将其追溯到force_sig_fault ,然后我们将使用通用架构独立的信号代码。

断点异常导致SIGTRAP被传递给被跟踪的进程,并且由于它正在被跟踪,每个信号都会导致被跟踪者停止,就像它被发送了SIGSTOP一样。 此时会通知跟踪器,就像在子进程退出或停止时通知进程一样:如果它在waitpid上被阻塞,那么waitpid现在将返回; 如果不是,那么下一次调用waitpid将立即返回。 跟踪器可以进行进一步的ptrace调用以检查或更改被跟踪者的 state,并在准备好让被跟踪者采取另一个步骤时调用ptrace(PTRACE_CONT) (类似于SIGCONT )。 它将设置适当的标志,以便被跟踪者忽略SIGTRAP ,否则通常会终止它。

因此,这与父进程处于waitpid(pid, &status, WUNTRACED)并且其子进程收到SIGSTOPSIGTSTP (例如,终端中的 Ctrl-Z 命中)的流程大致相同; 相关的 kernel 代码并非特定于调试。 特别是,没有从 tracee 直接到 tracer 的显式上下文切换。 相反,被跟踪者停止并且跟踪器准备好运行。 然后 CPU 进入调度程序。 跟踪器可能是下一个选择运行的进程,要么是运气好,要么是所有其他进程都在休眠; 否则它必须像其他人一样等待时间片。 跟踪器甚至可能在不同的核心上运行,这也很好。


您的分析在第 4 步中偏离了方向。断点处理程序在启动时被初始化为early_brk64 ,但正如early_brk64上的注释所暗示的,在启动期间, debug_traps_init single_step_handler 然后我们 go 通过send_user_sigtraparm64_force_sig_faultforce_sig_fault ,这是在通用架构无关的 kernel 代码中。

请注意,所有这些都在被跟踪者的上下文中。

特别是,在此过程中不会调用bug_handler() function 旨在处理kernel错误,可能是通过 OOPS 杀死进程或使 kernel 恐慌。 early_brk64无条件调用它,我认为是因为它仅在早期启动时作为处理程序安装,在任何用户空间进程存在之前,并且 kernel 在任何情况下都不应该出现调试异常。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM