ftrace新功能 -- objtrace代码分析

  1. 功能描述

我们在开发和调试代码的过程中,经常会遇到一个场景,变量在函数之间传递的过程中,我们需要追踪这个变量的值变化的过程,比如下面这个例子中的bio:

bio_add_page(struct bio *bio ...) -->
__bio_try_merge_page(bio ...) -->
     		bio_flagged(bio...)
 	__bio_add_page(bio...)
...   

通常,我们会增加很多printk,然后编译安装内核,重启。如果有的函数分支忽略掉的话,还需要再增加printk,再次编译安装内核和重启,是不是很笨重?

内核中的kprobe提供了可以不用修改代码而直接查看函数参数值的方法:

cd /sys/kernel/debug/tracing
echo 'p bio_add_page arg1=$arg1 arg2=$arg2' > ./kprobe_events
echo 1 > events/kprobes/p_bio_add_page_0/enable
cat your_file > /dev/null
cat trace

     jbd2/sda5-8-220     [000] ...1. 55531.677782: p_bio_add_page_0: (bio_add_page+0x0/0x90) arg1=0xffff8ecac98aba00 arg2=0xfffff58a0122a400
     jbd2/sda5-8-220     [000] ...1. 55531.677786: p_bio_add_page_0: (bio_add_page+0x0/0x90) arg1=0xffff8ecac98abc00 arg2=0xfffff58a04141140
     jbd2/sda5-8-220     [000] ...1. 55531.677790: p_bio_add_page_0: (bio_add_page+0x0/0x90) arg1=0xffff8ecac98ab100 arg2=0xfffff58a02e9f880
     jbd2/sda5-8-220     [000] ...1. 55531.677794: p_bio_add_page_0: (bio_add_page+0x0/0x90) arg1=0xffff8ecac98ab300 arg2=0xfffff58a041d1b00
     jbd2/sda5-8-220     [000] ...1. 55531.677798: p_bio_add_page_0: (bio_add_page+0x0/0x90) arg1=0xffff8ecac98ab600 arg2=0xfffff58a04141280
...

Kprobe利用了ftrace中function tracer的架构,将代码段中__fentry__(或__mcount)替换为kprobe_ftrace_handler,kprobe_ftrace_handler将函数名称和参数值打印到了ftrace的ringbuffer之中。

但还有一个问题,kprobe只能追踪变量在bio_add_page内的值,不能跟踪变量在其之后的变化。

国内社区的jeff老师近期开发了objtrace可以实现变量持续跟踪的功能,objtrace结合使用了kprobe和function tracer的功能,持续跟踪一个或多个变量,同时还可以设置偏移量,获取变量结构内部元素的值。

目前objtrace正在评审中,很快就会进入到内核代码树的主线。

使用方法

1) 进入debugfs的目录

cd /sys/kernel/debug/tracing

2)设置kprobe

echo 'p bio_add_page arg1=$arg1 arg2=$arg2' > ./kprobe_events
查看kprobe:
ls events/kprobes/ -al
里面增加了p_bio_add_page_0
cat events/kprobes/p_bio_add_page_0/trigger
# Available triggers:
# traceon traceoff snapshot stacktrace enable_event disable_event enable_hist disable_hist hist objtrace

可以看到除了原有的traceon traceoff snapshot stacktrace之外,增加了一个新的触发器objtrace

3)设置objtrace

echo 'objtrace:add:arg1,0x28:u32:5 if comm == "cat"' > ./events/kprobes/p_bio_add_page_0/trigger
表示在kprobe:bio_add_page需要跟踪第一个参数,也就是结构bio偏移量为0x28的变量的值。
cat events/kprobes/p_bio_add_page_0/trigger
objtrace:add:arg1,0x28:u32:count=0 if comm == "cat"

4) 触发

cat your_file > /dev/null  //读取一个大文件,触发bio_add_page,也就是内核需要调用到bio_add_page函数

5)查看日志

cat trace
# tracer: nop
#
# entries-in-buffer/entries-written: 309/401   #P:4
#
#                                _-----=> irqs-off/BH-disabled
#                               / _----=> need-resched
#                              | / _---=> hardirq/softirq
#                              || / _--=> preempt-depth
#                              ||| / _-=> migrate-disable
#                              |||| /     delay
#           TASK-PID     CPU#  |||||  TIMESTAMP  FUNCTION
#              | |         |   |||||     |         |
    kworker/3:1H-79      [003] ...1.   248.076143: wbt_issue <-__rq_qos_issue object:0xffffa0c886cc8700 value:0x1000
    kworker/3:1H-79      [003] ...1.   248.076143: blk_add_timer <-blk_mq_start_request object:0xffffa0c886cc8700 value:0x1000
##### CPU 1 buffer started ####
          <idle>-0       [001] .Ns2.   248.080898: bio_endio <-blk_update_request object:0xffffa0c886cc8700 value:0x0
          <idle>-0       [001] .Ns2.   248.080900: __rq_qos_done_bio <-bio_endio object:0xffffa0c886cc8700 value:0x0
          <idle>-0       [001] .Ns2.   248.080900: bio_uninit <-bio_endio object:0xffffa0c886cc8700 value:0x0
          <idle>-0       [001] .Ns2.   248.080901: bio_uninit <-bio_endio object:0xffffa0c886cc8700 value:0x0
          <idle>-0       [001] .Ns2.   248.080901: __rcu_read_lock <-bio_uninit object:0xffffa0c886cc8700 value:0x0
          <idle>-0       [001] .Ns2.   248.080901: __rcu_read_lock <-bio_uninit object:0xffffa0c886cc8700 value:0x0

代码分析:

1,初始化

初始化就是让内核的ftrace架构知道有objtrace这样的一个trigger(触发器),当开发者创建一个kprobe的时候,在这个kprobe的trigger目录下,会出现objtrace的选项。代码流程如下:

start_kernel -->
trace_init -->
trace_event_init -->
event_trace_enable -->
register_trigger_cmds -->
register_trigger_object_cmd -- > 
register_event_command(&trigger_object_cmd); 注册trigger_object_cmd
trigger_object_cmd包含了event_command所需要的函数,如event_object_trigger_parse就是工程是输入objtrace:add...之后,将这个字符串解析的过程。
static struct event_command trigger_object_cmd = {
     .name           = "objtrace",
     .trigger_type       = ETT_TRACE_OBJECT,
     .flags          = EVENT_CMD_FL_NEEDS_REC,
     .parse          = event_object_trigger_parse,
     .reg            = register_trigger,
     .unreg          = unregister_trigger,
     .get_trigger_ops    = objecttrace_get_trigger_ops,
     .set_filter     = set_trigger_filter,
 };

此外,start_kernel还有一个路径初始化objtrace的另一部分
start_kernel -->
early_trace_init -->
tracer_alloc_buffers -->
allocate_objtrace_data
allocate_objtrace_data中申请并初始化了一个结构体objtrace_data *obj_data,其中最重要的是obj_data->fops->func = trace_object_events_call,函数trace_object_events_call负责跟踪参数值并打印到ftrace的ring buffer中,可以说trace_object_events_call是objtrace中最重要的一个函数。
注意,此时trace_object_events_call只是放到了obj_data->fops->func中,并没有开始使用。

2,设置 “echo 'objtrace:add:arg1,0x28:u32:5 if comm == "cat"' > ./events/kprobes/p_bio_add_page_0/trigger”

当开发者输入这个命令的时候,系统需要将 'objtrace:add:arg1,0x28:u32:5 if comm == "cat"' 解析出来,并调用event_trigger_register注册到ftrace中,这样当内核走到bio_add_page这个函数的时候,就会触发kprobe。函数调用过程如下:
我在event_trigger_set_filter中增加了一个WARN_ONCE,可以看到调用栈,同时在event_object_trigger_parse中增加了一个printk,看到一些变量。通常我都是使用function_grath来看代码调用的流程,但ftrace本身的函数不能使用ftrace来观测,这样会造成递归调用,很不幸不是,只能使用这种笨办法了:
[  180.024670] chensong: event_object_trigger_parse :param:add:arg1,0x28:u32:5, objtrace_cmd:add
[  180.024685] ------------[ cut here ]------------
[  180.024688] chensong: set_trigger_filter, filter_str:if comm == "cat"
[  180.024698] WARNING: CPU: 3 PID: 4332 at kernel/trace/trace_events_trigger.c:1056 set_trigger_filter+0x13d/0x170
...
[  180.024844] CPU: 3 PID: 4332 Comm: bash Not tainted 5.18.0-rc2-objtrace-v10+ #2
[  180.024848] Hardware name: LENOVO YangTianT4900v-00/, BIOS FCKT65AUS 01/12/2015
[  180.024851] RIP: 0010:set_trigger_filter+0x13d/0x170
[  180.024856] Code: 83 c4 18 44 89 e0 5b 41 5c 41 5d 5d c3 48 89 fa 48 c7 c6 e0 15 23 84 48 c7 c7 15 e4 5a 84 c6 05 27 84 94 01 01 e8 d2 f7 9c 00 <0f> 0b 48 8b 45 d0 e9 f6 fe ff ff e8 73 57 a3 00 48 8b 7b 20 41 bc
[  180.024859] RSP: 0018:ffffa28003a77d60 EFLAGS: 00010286
...
[  180.024879] CR2: 00007fbeb41fa020 CR3: 000000000a0f6003 CR4: 00000000001706e0
[  180.024882] Call Trace:
[  180.024884]  <TASK>
[  180.024888]  event_trigger_set_filter+0x22/0x30
[  180.024894]  event_object_trigger_parse.cold+0x313/0x345
[  180.024904]  trigger_process_regex+0xc1/0x110
[  180.024908]  event_trigger_write+0x70/0xd0
...
[  180.024913]  vfs_write+0xc0/0x2b0
[  180.024920]  ksys_write+0x67/0xe0
[  180.024926]  __x64_sys_write+0x1a/0x20
[  180.024931]  do_syscall_64+0x3b/0x90
[  180.024937]  entry_SYSCALL_64_after_hwframe+0x44/0xae

event_object_trigger_parse首先解析’objtrace:add:arg1,0x28:u32:5’,并将结果保存到变量trigger_data中,最后调用event_trigger_register,过程如下:

static int
 event_object_trigger_parse(struct event_command *cmd_ops,...
 {
     struct event_trigger_data *trigger_data;
     struct objtrace_trigger_data *obj_data;          
 ...
     obj_data->field = field;                                             --- (1)
     obj_data->obj_offset = offset;
     obj_data->obj_value_type_size = obj_value_type_size;
     obj_data->tr = file->tr;
     snprintf(obj_data->objtrace_cmd, OBJTRACE_CMD_LEN, objtrace_cmd);
 
     trigger_data = event_trigger_alloc(cmd_ops, cmd, param, obj_data);   ---(2)
 ...
     ret = event_trigger_set_filter(cmd_ops, file, filter, trigger_data); ---(3)
     if (ret < 0)
         goto out_free;
 
     ret = event_trigger_register(cmd_ops, file, glob, trigger_data);     ---(4)
    ...
    }
 int event_trigger_register(struct event_command *cmd_ops,
 {  
     return cmd_ops->reg(glob, trigger_data, file);                      ---(5)
 }
 int event_trigger_set_filter(struct event_command *cmd_ops,
 {  
     if (param && cmd_ops->set_filter)
         return cmd_ops->set_filter(param, trigger_data, file);          ---(6)
     return 0;
 }    

static struct event_command trigger_object_cmd = {    --- (7)
    .name           = "objtrace",
    .trigger_type       = ETT_TRACE_OBJECT,
    .flags          = EVENT_CMD_FL_NEEDS_REC,
    .parse          = event_object_trigger_parse,
    .reg            = register_trigger,                                  
    .unreg          = unregister_trigger,
    .get_trigger_ops    = objecttrace_get_trigger_ops,
    .set_filter     = set_trigger_filter,                                
};
  1. )解析的结果放到objtrace_trigger_data中。
  2. )同时,将objtrace_trigger_data放置到event_trigger_data中。
  3. )注册filter,也就是’if comm == "cat"’。
  4. )注册trigger。
  5. )event_trigger_register就是调用trigger_object_cmd中的.reg,也就是register_trigger。
  6. )event_trigger_set_filter就是调用trigger_object_cmd中的.set_filter,也就是set_trigger_filter。
  7. )trigger_object_cmd的定义。

objtrace还有一个重要的工作,就是要跟踪变量的流向,这个就超出了kprobe的功能范围,需要借助function tracer了,所以kprobe部分还有一个重要的工作,就是使能function tracer。我花了很多时间来找如何使能function tracer的,一直没有找到。但如果你了解function tracer的话,会知道在使能function tracer,必然会调用ftrace_replace_code来替换代码段,将__fentry__(或__mcount)替换为ftrace_call,那么我就在ftrace_replace_code中增加一个WARN,看一下调用栈:

增加调试代码:
arch/x86/kernel/ftrace.c  +194
  ftrace_replace_code
    +static int counter = 0;
 void ftrace_replace_code(int enable)
 {
                rec = ftrace_rec_iter_record(iter);
+               WARN_ONCE(1, "chensong: ip:%pS \n", rec->ip);
+               counter++;
+               if (counter%100 == 0) 
+                 printk("chensong:%s, ip:%pS\n", __FUNCTION__, rec->ip);
输入echo 'objtrace:add:arg1,0x28:u32:5 if comm == "cat"' > ./events/kprobes/p_bio_add_page_0/trigger后,得到调用栈
[  168.674941] chensong: ip:__traceiter_initcall_level+0x0/0x40 
[  168.674954] WARNING: CPU: 3 PID: 3032 at arch/x86/kernel/ftrace.c:205 ftrace_replace_code+0x10b/0x1d0
...
[  168.675108] CPU: 3 PID: 3032 Comm: bash Not tainted 5.18.0-rc2-objtrace-v10+ #6
[  168.675112] Hardware name: LENOVO YangTianT4900v-00/, BIOS FCKT65AUS 01/12/2015
[  168.675115] RIP: 0010:ftrace_replace_code+0x10b/0x1d0
[  168.675121] Code: 89 fe 89 c7 e8 f7 97 b4 00 5b 41 5c 41 5d 41 5e 41 5f 5d c3 48 8b 30 48 c7 c7 78 c5 b8 a5 c6 05 f0 6e ab 01 01 e8 4f f3 b3 00 <0f> 0b eb 80 e8 bc d1 13 00 31 db 45 85 f6 49 c7 c6 30 32 49 a6 0f
[  168.675124] RSP: 0018:ffffb8a90299fc78 EFLAGS: 00010286
[  168.675128] RAX: 0000000000000000 RBX: ffffffffa6493230 RCX: 0000000000000000
...
[  168.675149] Call Trace:
[  168.675151]  <TASK>
[  168.675156]  ftrace_modify_all_code+0xcc/0x160
[  168.675165]  arch_ftrace_update_code+0x9/0x10
[  168.675170]  ftrace_run_update_code+0x1a/0x70
[  168.675176]  ftrace_startup_enable+0x2e/0x50
[  168.675181]  ftrace_startup+0xa8/0x120
[  168.675185]  register_ftrace_function+0x25/0x70
[  168.675189]  event_object_trigger_init+0x32/0x50
[  168.675194]  register_trigger+0x69/0xd0
[  168.675200]  event_trigger_register+0x1c/0x20
[  168.675204]  event_object_trigger_parse+0x323/0x390
[  168.675211]  trigger_process_regex+0xc1/0x110
[  168.675216]  event_trigger_write+0x70/0xd0
[  168.675221]  vfs_write+0xc0/0x2b0
[  168.675228]  ksys_write+0x67/0xe0
[  168.675234]  __x64_sys_write+0x1a/0x20
[  168.675240]  do_syscall_64+0x3b/0x90
[  168.675246]  entry_SYSCALL_64_after_hwframe+0x44/0xae

这部分代码也是从register_trigger调用过来的,最终调用了

register_trigger -->
event_object_trigger_init --> 
register_ftrace_function -->
ftrace_modify_all_code
其中,register_ftrace_function还将&tr->obj_data->fops注册到function tracer的框架中,那么函数调用function tracer时,function tracer会调用tr->obj_data->fops中的回调函数func,也就是在allocate_objtrace_data中赋值的trace_object_events_call函数,此时trace_object_events_call开始工作。

至此,function tracer被打开,以后内核中所有可以trace的函数,都将要走过function tracer这条路径,也就是每一个函数调用之后,都会运行到trace_object_events_call中。

3, 触发 “cat /home/kylin/workspace/opensource/linux-objtrace-jeff/kernel/sched/core.c > /dev/null”

本节是objtrace中最重要的一个环节,当kprobe和objtrace都设置好了,一旦内核调用到bio_add_page,就要开始跟踪bio这个变量了,objtrace是如何在原有的kprobe的基础上实现这个功能的呢?

我在阅读代码的时候,将这个过程分为两部分,一个是kprobe,另一个是function tracer,我们先来看看当kprobe被触发的时候,objtrace做了什么工作:

同样的,我还是使用了printk和WARN_ONCE来进行调试,我首先在函数set_trace_object中增加了一个WARN_ONCE,得到的调用栈如下:
[  248.069971] chensong:set_trace_object, call stack of set trace object
[  248.069980] WARNING: CPU: 3 PID: 4343 at kernel/trace/trace_object.c:162 trace_object_trigger+0x184/0x1a0
....
[  248.070450] Hardware name: LENOVO YangTianT4900v-00/, BIOS FCKT65AUS 01/12/2015
[  248.070455] RIP: 0010:trace_object_trigger+0x184/0x1a0
[  248.070462] Code: 7a ae 48 c7 c6 2d 69 3d ad e8 28 82 fe ff eb bc 48 c7 c6 60 05 43 ae 48 c7 c7 80 ce 7a ae c6 05 2c 11 95 01 01 e8 db 84 9d 00 <0f> 0b eb 9e 48 85 db 0f 84 16 ff ff ff 48 c7 c0 80 96 bc ae e9 d1
[  248.070467] RSP: 0018:ffffb122007d36a8 EFLAGS: 00010086
....
[  248.070515] Call Trace:
[  248.070520]  <TASK>
[  248.070533]  trace_object_count_trigger+0x1e/0x30
[  248.070541]  event_triggers_call+0x5d/0xe0

[  248.070556]  trace_event_buffer_commit+0x1a2/0x260
[  248.070573]  kprobe_trace_func+0x1ad/0x2a0
[  248.070602]  kprobe_dispatcher+0x42/0x70


[  248.070610]  ? bio_add_page+0x1/0x90
[  248.070622]  kprobe_ftrace_handler+0x168/0x1f0
[  248.070629]  ? bio_add_page+0x5/0x90
[  248.070636]  ? __bio_try_merge_page+0x140/0x140
[  248.070648]  arch_ftrace_ops_list_func+0xe5/0x160
[  248.070655]  ? submit_bh_wbc+0xca/0x140
[  248.070677]  ftrace_regs_call+0x5/0x52
[  248.070689] RIP: 0010:bio_add_page+0x1/0x90
....
[  248.070781]  ? bio_add_page+0x5/0x90
[  248.070789]  ? submit_bh_wbc+0xca/0x140

我们可以看到,当bio_add_page被触发之后,进入和kprobe的调用路径:

bio_add_page -->
ftrace_regs_call -->
arch_ftrace_ops_list_func-->
kprobe_ftrace_handler -->
kprobe_dispatcher -->
kprobe_trace_func -->
event_triggers_call --> 
trace_object_count_trigger --> 
set_trace_object

其中trace_object_count_trigger也是trigger_object_cmd中定义的回调函数,在trigger条件符合被触发的时候被调用,我在set_trace_object中还增加了一个printk,打印obj的地址,也就是需要跟踪的变量的地址。

     static void set_trace_object(void *obj, int obj_offset,
             int obj_value_type_size, struct trace_array *tr)
 {
     unsigned long flags;
     struct object_instance *obj_ins;
     struct objtrace_data *obj_data;
 
...
     obj_ins = &obj_data->traced_obj[atomic_read(&obj_data->num_traced_obj)];
     obj_ins->obj = obj;                                     --- (1)
     printk("chensong:%s, obj:0x%lx", __FUNCTION__, obj);
     obj_ins->obj_value_type_size = obj_value_type_size;
     obj_ins->obj_offset = obj_offset;
     obj_ins->tr = tr;
     /* make sure the num_traced_obj update always appears after traced_obj update */
     smp_wmb();
     atomic_inc(&obj_data->num_traced_obj);
 
     //WARN_ONCE(1, "chensong:%s, call stack of set trace object\n", __FUNCTION__);
 out:
     raw_spin_unlock_irqrestore(&obj_data->obj_data_lock, flags);
 }

这个时候内核已经走到了bio_add_page,所以已经知道了bio的地址了,所以需要将bio的地址保存到object_instance的obj中,object_instance中其余的元素保存偏移量,变量大小等信息。

接下来是function tracer的部分,在上一部分讲过,开发者运行命令“echo 'objtrace:add:arg1,0x28:u32:5 if comm == "cat"' > ./events/kprobes/p_bio_add_page_0/trigger”之后,function tracer被打开,以后内核中所有可以trace的函数,都将要走过function tracer这条路径,每一个函数调用之后,都会运行到trace_object_events_call中,trace_object_events_call的代码如下:

static void
trace_object_events_call(unsigned long ip, unsigned long parent_ip,
        struct ftrace_ops *op, struct ftrace_regs *fregs)
{
    struct pt_regs *pt_regs = ftrace_get_regs(fregs);
    int cpu, n;
        for (n = 0; n < max_args_num; n++) {
            obj = regs_get_kernel_argument(pt_regs, n);                            --- (1)
            if (object_exist((void *)obj, tr)) {                                       --- (2)
         ...
                if (get_object_value(&val, (void *)(obj + obj_offset), val_type_size))  --- (3)
                    continue;

                submit_trace_object(ip, parent_ip, obj, val, tr);                     ---(4)
            }
...
}
static inline unsigned long regs_get_register(struct pt_regs *regs,
                          unsigned int offset)
{
    return *(unsigned long *)((unsigned long)regs + offset);     --- (5)
}

static bool object_exist(void *obj, struct trace_array *tr)
{
 ...
    for (i = 0; i < max; i++) {
        if (obj_data->traced_obj[i].obj == obj)                   --- (6)
            return true;
    }
    return false;
}
  1. )Function tracer使能后,每个可以trace的函数都会调用到trace_object_events_call中,调用栈如下:
Any kernel tracable function -->
ftrace_regs_call -->
		arch_ftrace_ops_list_func -->
    			__ftrace_ops_list_func -->
        			op->func(ip, parent_ip, op, fregs); -->
              			trace_object_events_call -->
                   				get_object_value

trace_object_events_call首先调用regs_get_kernel_argument,依次获得函数的参数,本例中为bio_add_page的参数。

2)调用object_exist来检查是不是需要跟踪的变量,这个变量的地址在开发者输入”cat file > /dev/null”之后, 内核代码走到了bio_add_page,bio就会在set_trace_object中保存到了object_instance的obj中,所以在这个时候,后续函数如果使用了bio,object_exist返回为TRUE。如果内核代码没有走到过bio_add_page或者该函数的参数中没有bio,object_exist会返回FALSE。

3)获取变量值,保存到val中。

4)调用submit_trace_object将value打印到ftrace的ringbuffer中。

5)regs_get_kernel_argument最终会调用到regs_get_register中,返回指定参数的地址。

6)object_exist通过比较地址来判断,当前函数的参数中是不是包含需要跟踪的变量。

好了,到此为止,objtrace的大部分工作都结束了,找到了变量,获得了它的值,并打印到ringbuffer中,很巧妙,避免了大量的printk和WARN这样笨重的方法。

但objtrace打开了所有函数的function tracer,每个函数都要去调用trace_object_events_call,还有调用object_exist进行变量的比对,会增加一定的系统开销,所以objtrace最好不用和性能调试的trace一起工作。

4,打印 “cat trace”

从debugfs的trace文件中打印到中断,显示给开发者,调用栈如下:

[ 1617.394055] chensong:trace_object_print
[ 1617.394073] WARNING: CPU: 2 PID: 4761 at kernel/trace/trace_output.c:1567 trace_object_print+0x73/0x90

[ 1617.394733] RIP: 0010:trace_object_print+0x73/0x90
[ 1617.394749] Code: c6 83 b5 fa b8 e8 9d 0d 00 00 4c 89 e7 be 0a 00 00 00 e8 d0 0a 00 00 48 c7 c6 30 f5 c2 b8 48 c7 c7 90 b5 fa b8 e8 ec a6 9e 00 <0f> 0b 4c 89 e7 e8 23 37 ff ff 5b 41 5c 5d c3 0f 0b eb 97 66 2e 0f

[ 1617.394841] Call Trace:
[ 1617.394850]  <TASK>
[ 1617.394864]  print_trace_line+0x25e/0x570
[ 1617.394878]  ? ring_buffer_iter_advance+0x32/0x40
[ 1617.394902]  s_show+0x4c/0x160
[ 1617.394919]  seq_read_iter+0x2a3/0x450
[ 1617.394947]  seq_read+0xee/0x120
[ 1617.394983]  vfs_read+0xa7/0x1a0
[ 1617.395006]  ksys_read+0x67/0xe0
[ 1617.395029]  __x64_sys_read+0x1a/0x20
[ 1617.395042]  do_syscall_64+0x3b/0x90
[ 1617.395056]  entry_SYSCALL_64_after_hwframe+0x44/0xae

代码分析的大体流程如下,还有一些细节需要进一步调试,加深理解,调试ftrace不容易,因为不能用到ftrace:)

源码:https://github.com/x-lugoo/linux

感谢jeff老师的指导和帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值