发生了什么:
Kprobe 是通过用int 3 指令替换原始指令来实现的,这将导致CPU 产生软件中断。在这种情况下,CPU 上下文必须保存在内核堆栈中,然后您的处理程序将被执行。所以regs 是int 3 的上下文,而不是glibc 用来触发内核系统调用的syscall 指令的上下文。当 CPU 中断/异常发生时,您得到的值 orig_ax 是错误代码。它的值为零,因为int 3 中断不会产生任何错误,因此内核将零压入堆栈作为占位符,这使得整个实现更加通用。
你应该怎么做:
如果你想获取系统调用号,你应该在do_syscall_64 上植入一个探针,这是调用系统调用时执行的第一个 C 函数。或者你可以探测entry_SYSCALL_64,它是syscall/int 0x80汇编指令的中断处理程序。
详情:
系统调用机制是使用 CPU 陷阱门实现的。当你在 C 语言中调用fork() 时,glibc 将执行syscall 汇编指令,系统调用号存储在rax 中,正如你所知。 CPU 将产生一个软件生成的中断,并开始执行系统调用的中断处理程序,其地址存储在 IDT 中。
以下代码是 x86_64 上系统调用的中断处理程序。
ENTRY(entry_SYSCALL_64)
UNWIND_HINT_EMPTY
/*
* Interrupts are off on entry.
* We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON,
* it is too small to ever cause noticeable irq latency.
*/
swapgs
/* tss.sp2 is scratch space. */
movq %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp
/* Construct struct pt_regs on stack */
pushq $__USER_DS /* pt_regs->ss */
pushq PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /* pt_regs->sp */
pushq %r11 /* pt_regs->flags */
pushq $__USER_CS /* pt_regs->cs */
pushq %rcx /* pt_regs->ip */
GLOBAL(entry_SYSCALL_64_after_hwframe)
pushq %rax /* pt_regs->orig_ax */
PUSH_AND_CLEAR_REGS rax=$-ENOSYS
TRACE_IRQS_OFF
/* IRQs are off. */
movq %rax, %rdi
movq %rsp, %rsi
call do_syscall_64 /* returns with IRQs disabled */
pushq %rax 指令将rax(又名系统调用号)保存在内核堆栈上,然后调用do_syscall_64。
__visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)
{
struct thread_info *ti;
enter_from_user_mode();
local_irq_enable();
ti = current_thread_info();
if (READ_ONCE(ti->flags) & _TIF_WORK_SYSCALL_ENTRY)
nr = syscall_trace_enter(regs);
if (likely(nr < NR_syscalls)) {
nr = array_index_nospec(nr, NR_syscalls);
regs->ax = sys_call_table[nr](regs);
#ifdef CONFIG_X86_X32_ABI
} else if (likely((nr & __X32_SYSCALL_BIT) &&
(nr & ~__X32_SYSCALL_BIT) < X32_NR_syscalls)) {
nr = array_index_nospec(nr & ~__X32_SYSCALL_BIT,
X32_NR_syscalls);
regs->ax = x32_sys_call_table[nr](regs);
#endif
}
syscall_return_slowpath(regs);
}
最重要的语句是regs->ax = sys_call_table[nr](regs);,它将调用与fork相关的函数。当_do_fork被调用时,regs的信息已经丢失,所以你无法获取任何与系统调用相关的信息。