发生了什么:
Kprobe 是通过将原始指令替换为int 3
会导致 CPU 产生软件中断的指令来实现的。在这种情况下,CPU 上下文必须保存在内核堆栈中,然后您的处理程序将被执行。所以这不是glibc用来触发内核系统调用的指令regs
上下文int 3
的上下文。syscall
您获得的值orig_ax
是发生 CPU 中断/异常时的错误代码。它的值为零,因为int 3
中断不会产生任何错误,因此内核将零压入堆栈作为占位符,这使整个实现更加通用。
你该怎么办:
如果要获取系统调用号,则应植入一个探针,在do_syscall_64
该探针上调用系统调用时执行的第一个 C 函数。或者您可以探测/汇编指令entry_SYSCALL_64
的中断处理程序。syscall
int 0x80
细节:
系统调用机制是使用 CPU 陷阱门实现的。当你fork()
用 C 语言调用时,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
-related 函数。_do_fork
调用时,信息regs
已经丢失,因此您无法获取与系统调用相关的任何信息。