【问题标题】:Why is orig_ax not having proper system call number为什么 orig_ax 没有正确的系统调用号
【发布时间】:2020-04-22 18:05:48
【问题描述】:

我编写了一个基本的 kprobe linux 内核模块,它将为 fork 注册一个处理程序,并在处理程序中打印寄存器 'orig_ax' 的值。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>

MODULE_LICENSE("GPL");

static struct kprobe kp;
static char *name = "_do_fork";
module_param(name, charp, 0);

static int pre_handler(struct kprobe *p, struct pt_regs *regs)
{
    printk("orig_ax regs:%lu \t ax:%lu\n", regs->orig_ax, regs->ax);
    return 0;
}

static void post_handler(struct kprobe *p, struct pt_regs *regs,
             unsigned long flags)
{
}

static int __init hello_init(void)
{
    /* set the handler functions */
    kp.pre_handler = pre_handler;
    kp.post_handler = post_handler;
    kp.symbol_name = name;
    register_kprobe(&kp);
    return 0;
}

static void __exit hello_exit(void)
{
    unregister_kprobe(&kp);
}

module_init(hello_init);
module_exit(hello_exit);

我得到一个不同的值而不是 57

[ 9251.954392] orig_ax regs:0    ax:18446661681273651032

我是不是搞错了

【问题讨论】:

    标签: c linux linux-kernel linux-device-driver kprobe


    【解决方案1】:

    发生了什么:

    Kprobe 是通过用int 3 指令替换原始指令来实现的,这将导致CPU 产生软件中断。在这种情况下,CPU 上下文必须保存在内核堆栈中,然后您的处理程序将被执行。所以regsint 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-&gt;ax = sys_call_table[nr](regs);,它将调用与fork相关的函数。当_do_fork被调用时,regs的信息已经丢失,所以你无法获取任何与系统调用相关的信息。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-02-22
      • 1970-01-01
      • 1970-01-01
      • 2018-06-23
      • 2012-05-04
      • 1970-01-01
      • 2019-06-28
      相关资源
      最近更新 更多