【发布时间】:2022-01-06 22:28:21
【问题描述】:
在 aarch64、Linux 内核中调用 ptrace SINGLESTEP 会发生什么?
此问题的 Linux 参考:5.15.5(2021 年 11 月最新稳定版)。
命名法:tracer(被跟踪的进程)和tracee(被跟踪的进程)。
通过静态分析 + ftracing linux 内核,我试图重建 ptrace SINGLESTEP 调用时发生的确切情况。我试图在学习操作系统中编写我所理解的内容,但未能获得相同的行为。在问我的问题之前,让我总结一下我试图重构的过程:
- 在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));
}
- 在 arm64 中,这包括设置单步位 SPSR_EL1.SS(位置 21):
/*
* Single step API and exception handling.
*/
static void set_user_regs_spsr_ss(struct user_pt_regs *regs)
{
regs->pstate |= DBG_SPSR_SS;
}
- 我想,这应该引发“调试异常”(用户级别,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中定义,由于是软件步骤,所以应该调用debug_fault_info数据结构的函数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" },
};
- 后者在traps.c中定义,它调用bug_handler函数,后者又调用arm64_skip_faulting_instruction():后者更新了4字节的
PC(被跟踪者)(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;
}
- 最后,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 register 上设置 SS 位、MDE 位和 KDE 位来强制生成硬件调试异常。
一旦我做了这个技巧,我的步骤就得到了验证,但实际上,异常被跟踪器捕获(而不是被跟踪器应该):跟踪器的 PC 已更新,等等。 我认为我通过对代码和 ftrace 的静态分析检索到的步骤并不完全正确。你能帮忙确定在哪里吗?
【问题讨论】:
标签: linux linux-kernel arm arm64 ptrace