[英]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 调用时发生的确切情况。 我试图在学习操作系统中编写我所理解的内容,但未能获得相同的行为。 在问我的问题之前,让我总结一下我试图重构的过程:
/* 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));
}
在 arm64 中,这包括设置单步位SPSR_EL1.SS (位置 21):
/*
我想,这应该引发一个“调试异常”(用户级别,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);
}
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" },
};
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;
}
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)
并且其子进程收到SIGSTOP
或SIGTSTP
(例如,终端中的 Ctrl-Z 命中)的流程大致相同; 相关的 kernel 代码并非特定于调试。 特别是,没有从 tracee 直接到 tracer 的显式上下文切换。 相反,被跟踪者停止并且跟踪器准备好运行。 然后 CPU 进入调度程序。 跟踪器可能是下一个选择运行的进程,要么是运气好,要么是所有其他进程都在休眠; 否则它必须像其他人一样等待时间片。 跟踪器甚至可能在不同的核心上运行,这也很好。
您的分析在第 4 步中偏离了方向。断点处理程序在启动时被初始化为early_brk64
,但正如early_brk64
上的注释所暗示的,在启动期间, debug_traps_init
single_step_handler
。 然后我们 go 通过send_user_sigtrap
到arm64_force_sig_fault
到force_sig_fault
,这是在通用架构无关的 kernel 代码中。
请注意,所有这些都在被跟踪者的上下文中。
特别是,在此过程中不会调用bug_handler()
。 function 旨在处理kernel错误,可能是通过 OOPS 杀死进程或使 kernel 恐慌。 early_brk64
无条件调用它,我认为是因为它仅在早期启动时作为处理程序安装,在任何用户空间进程存在之前,并且 kernel 在任何情况下都不应该出现调试异常。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.