Getting Function Arguments Using Kprobes

Getting function arguments using kprobes

There are at least two approaches.

Approach 1: Jprobes

Probably the most easy one: if Jprobes are suitable for your task, you could give them a try. They are the close relatives of kprobes (see their detailed description and links to the examples in the kernel docs).

Jprobes allow to call your function with the same signature as the probed one on entry to the latter. You get all of the arguments automatically this way.

Approach 2: Registers and Stack

Another approach could be to extend a bit what you already do and retrieve the arguments from the registers and the stack. From the output log in your question, I assume you are working on a 32 bit x86 system.

x86, 32 bit

As far as I have seen, there are two most common conventions on argument passing in the Linux kernel on x86 (the details are available in the manual by Agner Fog). Note that system calls follow other conventions (see the manual for details) but I assume you are interested in analysing an "ordinary" function rather than a system call.

Convention 1

For the functions marked with asmlinkage as well as for the functions with variable argument lists, all parameters are passed on stack. The return address of the function should be at the top of the stack on entry to the function, the first parameter being located right below it. The second parameter is below the first one, and so on.

As you have the saved value of esp, you can find what it points to. *(esp+4) should be the first argument, *(esp+8) - the second one, etc., if this convention is used.

Convention 2

It seems to be used for the most of the kernel functions including the one you mentioned in the question.

The kernel is compiled with -mregparm=3 and therefore the first 3 arguments are passed in eax, edx and ecx, in that order, the rest go on stack. *(esp+4) should be the 4th argument, *(esp+8) - the 5th one, and so on.

x86, 64bit

It seems that things are a bit simpler on x86-64. Most of the kernel functions (including those with variable argument list) get the first 6 arguments in rdi, rsi, rdx, rcx, r8, r9, in that order, the remaining ones go on stack. *(esp+8) should be the 7th argument, *(esp+16) - the 8th and so on.

EDIT:

Note that on x86-32, the value of esp is not saved in pt_regs for kernel-mode traps (including the software breakpoints that KProbes rely upon). <asm/ptrace.h> provides kernel_stack_pointer() function to retrieve the correct value of esp, it works both on x86-32 and on x86-64. See the description of kernel_stack_pointer() in that header file for details.

In addition, regs_get_kernel_stack_nth() (also defined in that header) provides a convenient way to get the contents of the stack in the handler.

Getting args from kprobe not finding regs-rdi x86_64

The definition of pt_reg changes when __KERNEL__ is defined. Try using di instead.

Get a probed function's arguments in the entry_handler of a kretprobe

The conventions for argument passing in the kernel on x86 are described in the comments in asm/calling.h.

On 32-bit x86 systems, the first parameters of the functions in the Linux kernel (except system calls and some other stuff) are usually passed in %eax, %edx, %ecx, in order. This is because the sources are compiled with '-mregparm=3' GCC option, set by default. This is the case since kernel 2.6.32 at least, or may be from even earlier.

The remaining parameters are passed on stack.

If the function had a variable argument list (like sprintf()), all parameters were passed on stack, as far as I had seen.

So, in your case, size should be in %eax and flags - in %edx on entry to the function. If these registers are not clobbered somehow by the kretprobe, you should be able to find them in pt_regs.

On 64-bit x86 systems, the convention is simpler and more in line with x86-64 ABI. The first arguments of the kernel functions (again, except system calls and some special functions) are passed in %rdi, %rsi, %rdx, %rcx, %r8, %r9, in order, the remaining ones are on stack.

kprobe handler not getting triggered for specific function

You did no provide code calling the func.

What most likely happens is that he compiler inlines the body at the callsite and the addition of priting the address convinces it to generate full body and call it instead. Should be easy to check by disassembling.

However, the actual question is always the same: what are you doing?

Get syscall parameters with kretprobes post handler

This structure is architecture dependent so I will just assume you are on x86.

On entry:

  orig_eax = syscall_nr;
ebx = 1st argument of the function;
ecx = 2nd arg...
edx = 3rd arg...
esi = 4th arg...
edi = 5th arg... (you are lucky, on other arch this can be on the stack...)

On exit:

  orig_eax = return value of the function

You cannot assume that the value inside ebx,ecx,edx... still points to the argument of the function on exit (i.e. when your post_handler is called) so here you would need to register two handlers, one on entry, from which you can can save a pointer to the struct socket, and one on exit, from which you will actually inspect the content of the struct socket now that it's been filled. To pass data between the two handlers, you can use the filed data of the kretprobe_instance structure. Don't forget to increase the data_size filed of the struct kretprobe when you register your kretprobe.

This might seem a little complex but it should be alright once you are a bit more familiar with it.

Please also remember that if the only thing you need to do is trace those events, you might just use the ftrace infrastructure in sysfs and with minimum efforts get the trace that you want.

Use of __kprobes and how it works?

TL;DR

  1. __attribute__((__section__(".kprobes.text"))) will place that function in separate section which is not findable by kprobes thus preventing infinite breakpoints.
  2. You must use it before "function_name" to place whole "function_name" symbol in separate section.

Real answer

kprobes (kernel probes) is Linux kernel mechanism for dynamic tracing. It allows you to insert breakpoint at almost any kernel function, invoke your handler and then continue executing. It works by runtime patching kernel image with so-called kernel probe/kprobe - see struct kprobe. This probe will allow you to pass control to your handler, and that handler is usually do some tracing.

So, what's going under the hood:

  • You create your struct kprobe by defining address at which to break and handler to pass reference.
  • You register your probe with register_kprobe
  • Kernel kprobe subsystem finds address from your probe
  • Then kprobe:
    • inserts breakpoint CPU instruction (int 3 for x86) at given address
    • adds some wrapper code to save context(registers, etc.)
    • adds even more code to help you get access to function arguments or return values.
  • Now when kernel execution hits that probed address:
    • it will fall into CPU trap
    • it will save context
    • it will pass control to your handler via notifier_call_chain
    • ...
    • after all it will restore context

That's how it works. As you can see it's a really neat and dirty hack, but some kernel function is so terribly low-level that it's just pointless to probe them. notify_page_fault is one of those functions - as a part of notifier_call_chain it's used in passing control to your handler.

So if you probe at notify_page_fault you'll get infinite loop of breakpoints, which is not what you want. What you really want is to protect that kind of functions and kprobes do this by placing it in separate section .kprobes.text. This will prevent to probe at that functions because kprobe will not lookup for address in that section. And that's a job for __attribute__((__section__(".kprobes.text"))).

Linux, kprobes/kretprobes: a way to recover [from registers?] probe function's arguments?

The answer is jprobes.

Be afraid of redhat's example code missing my_jprobe.kp.addr = (kprobe_opcode_t *) kallsyms_lookup_name(FUNCAME);

Another problem is, that to caught and modify a return value, a better place for probing should be found (refer to http://lwn.net/Articles/132196/ for better understanding).

How to use BPF to filter kernel function arguments?

eBPF is probably what you want. If you have not found them already, you should have a look at the examples provided with the bcc (BPF Compiler Collection) tools.

In particular, the example tool argdist relies on kprobes indeed and could be of some interest to you:

argdist probes functions you specify and collects parameter values into a
histogram or a frequency count. This can be used to understand the distribution
of values a certain parameter takes, filter and print interesting parameters
without attaching a debugger, and obtain general execution statistics on
various functions.

For example, suppose you want to find what allocation sizes are common in
your application:

# ./argdist -p 2420 -C 'p:c:malloc(size_t size):size_t:size'
[01:42:29]
p:c:malloc(size_t size):size_t:size
COUNT EVENT
[01:42:30]
p:c:malloc(size_t size):size_t:size
COUNT EVENT

[…]

(extract from the argdist example uses).

For the record, most examples I found so far with eBPF were located in one of those locations:

  • Under linux/samples/bpf within the Linux kernel sources.
  • In the bcc/tools directory of bcc.
  • (For networking examples involoving tc, under iproute2/examples/tc directory in the iproute2 package sources.)


Related Topics



Leave a reply



Submit