#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#define MAX_BUFFER_SIZE 256

static void kprobe_handler(void)
{
    printf("Kprobe handler called\n");

    // 您可以在这里进行额外的操作和分析

    return;
}

int main(void)
{
    int fd, ret;
    char buffer[MAX_BUFFER_SIZE];

    // 打开kprobe文件
    fd = open("/sys/kernel/debug/tracing/kprobe_events", O_WRONLY | O_APPEND);
    if (fd < 0) {
        perror("Failed to open kprobe_events");
        return -1;
    }

    // 准备要插入钩子的内核函数名称和地址
    char probe_command[MAX_BUFFER_SIZE];
    snprintf(probe_command, MAX_BUFFER_SIZE, "p:kprobe/my_probe _do_fork");

    // 写入kprobe事件
    ret = write(fd, probe_command, strlen(probe_command));
    if (ret < 0) {
        perror("Failed to write kprobe event");
        close(fd);
        return -1;
    }

    // 关闭kprobe文件
    close(fd);

    // 启动Ftrace
    FILE* ftrace_enable_file = fopen("/sys/kernel/debug/tracing/tracing_on", "w");
    if (!ftrace_enable_file) {
        perror("Failed to enable Ftrace");
        return -1;
    }

    fprintf(ftrace_enable_file, "1");
    fclose(ftrace_enable_file);

    // 触发要插入钩子的内核函数
    printf("Triggering _do_fork...\n\n");
    fork();

    // 等待Ftrace结果
    sleep(1);

    // 读取Ftrace结果
    FILE* ftrace_output_file = fopen("/sys/kernel/debug/tracing/trace", "r");
    if (!ftrace_output_file) {
        perror("Failed to read Ftrace output");
        return -1;
    }

    while (fgets(buffer, MAX_BUFFER_SIZE, ftrace_output_file)) {
        printf("%s", buffer);
    }

    fclose(ftrace_output_file);

    return 0;
}

在上述的Kprobe应用层使用例子中,我们主要关注了_do_fork函数的执行过程,并通过插入钩子函数来触发调试点。下面是对该示例的详细分析:

首先,我们打开了

文件,这是一个用于管理Kprobe事件的文件。通过写入相应的kprobe事件,我们可以注册需要跟踪的内核函数。

在示例中,我们使用_do_fork作为我们要插入钩子的内核函数。我们构建了一个格式化的字符串probe_command,用来指定kprobe事件。事件格式为p:kprobe/my_probe _do_fork,其中my_probe是我们给钩子函数起的名字,可以自定义。

接着,我们通过write函数将kprobe事件写入kprobe_events文件。这将注册我们的kprobe事件,使得钩子函数能够在_do_fork函数的入口处被调用。

完成kprobe事件的注册后,我们关闭了kprobe_events文件。

接下来,我们启动了Ftrace。我们打开了

文件,并将其内容设置为1,以启用Ftrace跟踪。

紧接着,我们触发了要插入钩子的内核函数,即调用了fork()函数。这将触发内核中的_do_fork函数。

在插入钩子的内核函数执行后,我们等待一段时间,确保Ftrace有足够的时间来生成跟踪结果。

接下来,我们打开了

文件,它是Ftrace生成的跟踪结果的输出文件。

我们使用fgets函数逐行读取trace文件的内容,并将每行打印到控制台。

通过这个分析,我们可以详细了解_do_fork函数的调用过程和相应的跟踪结果。在钩子函数kprobe_handler中,您可以添加额外的操作和分析代码,来进一步研究和理解内核函数的执行行为。

以下是基于上述Kprobe应用层使用例子的调试结果示例:

触发_do_fork函数后,我们可以通过读取Ftrace的trace文件来获取调试结果。

# tracer: nop
#
# entries-in-buffer/entries-written: 1/1   #P:4
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
           <...>-1234  [001] d..1  12345.678901: do_fork: fork_handler called

> 在这个示例中,我们可以看到以下关键信息:
> 
> # entries-in-buffer/entries-written: 1/1:这表示在缓冲区中有1个条目,写入了1个条目。
> #P:4:这是一个Ftrace标记,表示使用了4个CPU。 TASK-PID、CPU#、TIMESTAMP和FUNCTION:这些列是Ftrace的输出格式,包含了相关的任务、进程ID、CPU编号、时间戳和函数调用。
> 在这个示例的结果中,我们可以看到以下信息:
> 
> do_fork: fork_handler
> called:这是我们插入的钩子函数_do_fork的调用结果。它表明钩子函数在_do_fork函数的入口处被成功触发。

07-29 16:54