Search code examples
linuxlinux-kernelkernel-modulekprobe

register_kprobe returns EINVAL (-22) error for instructions involving rip


I am trying to insert probes at different instructions with kprobes in function of kernel module.

But register_kprobe is returning EINVAL(-22) error for 0xffffffffa33c1085 instruction addresses and 0xffffffffa33c109b from below assembly code (it passes for all other instruction addresses).

Instructions giving errors:

0xffffffffa33c1085 <test_increment+5>:  mov    0x21bd(%rip),%eax        # 0xffffffffa33c3248
0xffffffffa33c109b <test_increment+27>: mov    %esi,0x21a7(%rip)        # 0xffffffffa33c3248

Observed that both these instructions use rip register. Tried with functions of other modules, observed same error with instructions which use rip register.

Why is register_kprobe failing ? does it have any constraints involving rip ? Any help is appreciated.

System has kernel 3.10.0-514 on x86_64 installed.

kprobe function:

kp = kzalloc(sizeof(struct kprobe), GFP_KERNEL);
kp->post_handler = exit_func;
kp->pre_handler = entry_func;
kp->addr = sym_addr;
atomic_set(&pcount, 0);
ret = register_kprobe(kp);
if ( ret != 0 ) {
    printk(KERN_INFO "register_kprobe returned %d for %s\n", ret, str);
    kfree(kp);
    kp=NULL;
    return ret;
}

probed function:

int race=0;
void test_increment()
{
    race++;
    printk(KERN_INFO "VALUE=%d\n",race);
    return;
}

assembly code:

crash> dis -l test_increment
0xffffffffa33c1080 <test_increment>:    nopl   0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffffa33c1085 <test_increment+5>:  mov    0x21bd(%rip),%eax        # 0xffffffffa33c3248
0xffffffffa33c108b <test_increment+11>: push   %rbp
0xffffffffa33c108c <test_increment+12>: mov    $0xffffffffa33c2024,%rdi
0xffffffffa33c1093 <test_increment+19>: mov    %rsp,%rbp
0xffffffffa33c1096 <test_increment+22>: lea    0x1(%rax),%esi
0xffffffffa33c1099 <test_increment+25>: xor    %eax,%eax
0xffffffffa33c109b <test_increment+27>: mov    %esi,0x21a7(%rip)        # 0xffffffffa33c3248
0xffffffffa33c10a1 <test_increment+33>: callq  0xffffffff81659552 <printk>
0xffffffffa33c10a6 <test_increment+38>: pop    %rbp
0xffffffffa33c10a7 <test_increment+39>: retq

Thanks


Solution

  • Turns out, register_kprobe does have limitations with instructions invoving rip relative addressing for x86_64.

    Here is snippet of __copy_instruction function code causing error (register_kprobe -> prepare_kprobe -> arch_prepare_kprobe -> arch_copy_kprobe -> __copy_instruction )

    #ifdef CONFIG_X86_64
        if (insn_rip_relative(&insn)) {
            s64 newdisp;
            u8 *disp;
            kernel_insn_init(&insn, dest);
            insn_get_displacement(&insn);
            /*
             * The copied instruction uses the %rip-relative addressing
             * mode.  Adjust the displacement for the difference between
             * the original location of this instruction and the location
             * of the copy that will actually be run.  The tricky bit here
             * is making sure that the sign extension happens correctly in
             * this calculation, since we need a signed 32-bit result to
             * be sign-extended to 64 bits when it's added to the %rip
             * value and yield the same 64-bit result that the sign-
             * extension of the original signed 32-bit displacement would
             * have given.
             */
            newdisp = (u8 *) src + (s64) insn.displacement.value - (u8 *) dest;
            if ((s64) (s32) newdisp != newdisp) {
                pr_err("Kprobes error: new displacement does not fit into s32 (%llx)\n", newdisp);
                pr_err("\tSrc: %p, Dest: %p, old disp: %x\n", src, dest, insn.displacement.value);
                return 0;
            }
            disp = (u8 *) dest + insn_offset_displacement(&insn);
            *(s32 *) disp = (s32) newdisp;
        }
    #endif
    

    http://elixir.free-electrons.com/linux/v3.10/ident/__copy_instruction

    A new displacement value is calculated based new instruction address (where orig insn is copied). If that value doesn't fit in 32 bit, it returns 0 which results in EINVAL error. Hence the failure.

    As a workaround, we can set kprobe handler post previous instruction or pre next instruction based on need (works for me).