欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

linux内核调试器ftrace使用

程序员文章站 2022-07-13 22:06:24
...

本文的实验是在ubuntu(内核版本3.19.0)上运行的。

ftrace原理

ftrace是一个追踪器框架,其中一个强大的追踪器就是函数追踪器(即函数的调用过程)。它使用gcc的-pg选项让内核中的每个函数在执行前都调用一个特殊的函数mcount()。该函数本来是在c库中实现,用于prof跟踪调试性能。内核不链接C库,所以就利用了这个gcc特性。mcount函数必现在汇编中实现,因为调用不遵循正常的C ABI。

如果每个函数都调用这个函数,势必会影响性能,所以内核支持动态开启函数跟踪。配置选项为CONFIG_DYNAMIC_FTRACE。在编译期间,使用了一个脚本将所有的mcount调用都替换为NOP指令,并把位置点记录在一个表里面。当用户通过proc开启函数跟踪功能后,再把NOP替换为mcount调用。听起来就很高大上,有没有。

设置ftrace

我们可以先看一下内核是否支持ftrace,
# ls /sys/kernel/debug/tracing/
如果有东西则表示支持。
为了方便,我们先进入这个目录
# cd /sys/kernel/debug/tracing/
current_tracer表示当前的追踪器是哪个,我们可以先设置追踪器。先可以查看目前支持哪些追踪器。
# cat available_tracers
blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop
function就是函数追踪器。

函数追踪器

设置fucntion为当前追踪器
# echo function> current_tracer

查看追踪结果,因为内容非常多,所以我们使用head参数只查看头20行
# cat trace | head -20

# tracer: function
#
# entries-in-buffer/entries-written: 72213/32836651   #P:1
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
             cat-19588 [000] ...1 141284.960228: native_flush_tlb_single <-__kunmap_atomic
             cat-19588 [000] .... 141284.960228: up_read <-__do_page_fault
             cat-19588 [000] d... 141284.960230: __do_page_fault <-do_page_fault
             cat-19588 [000] .... 141284.960230: down_read_trylock <-__do_page_fault
             cat-19588 [000] .... 141284.960231: _cond_resched <-__do_page_fault
             cat-19588 [000] .... 141284.960231: find_vma <-__do_page_fault
             cat-19588 [000] .... 141284.960231: vmacache_find <-find_vma
             cat-19588 [000] .... 141284.960231: handle_mm_fault <-__do_page_fault
             cat-19588 [000] .... 141284.960231: __mem_cgroup_count_vm_event <-handle_mm_fault

从这个可以看到进程名,PID,执行的CPU,调用时间戳等信息。调用过程要倒着看,A <- B,表示B函数调用了A函数。即便这样还是难以理解调用过程。我们可以使用函数流图追踪器。

函数流图追踪器

打开函数流图追踪器
# echo function_graph> current_tracer
# cat trace | head -20

# tracer: function_graph
#
# CPU  DURATION                  FUNCTION CALLS
# |     |   |                     |   |   |   |
 0)   0.878 us    |          } /* do_set_pte */
 0)               |          unlock_page() {
 0)   0.041 us    |            __wake_up_bit();
 0)   0.249 us    |          }
 0)               |          do_set_pte() {
 0)   0.026 us    |            add_mm_counter_fast();
 0)               |            page_add_file_rmap() {
 0)   0.026 us    |              mem_cgroup_begin_page_stat();
 0)   0.023 us    |              mem_cgroup_end_page_stat();
 0)   0.435 us    |            }
 0)   0.852 us    |          }
 0)               |          unlock_page() {
 0)   0.029 us    |            __wake_up_bit();
 0)   0.238 us    |          }
 0)               |          do_set_pte() {
 0)   0.026 us    |            add_mm_counter_fast();

这个读起来就很方便了,就如同C代码一样。如果时间栏显示+表示执行时间大于10微妙,如果显示!表示执行时间大于100微妙。

trace_printk

相比较于printk和trace_printk。printk会将内容直接输入到console,这会有非常大的cpu消耗(可能需要几毫秒),trace_printk写入环形缓冲区,消耗CPU很小(大约1/10微妙),对系统影响很小。trace_printk的打印将写入追踪器(任何追踪器里面都会写入),cat trace时就会看到。

启用和停止跟踪

关闭跟踪
# echo 0 > tracing_on
打开跟踪
#echo 1 > tracing_on
一般情况下这样用,先关闭,先后设置一个跟踪器,然后打开跟踪,运行某个想要跟踪的程序,一段时候后关闭跟踪。
#echo 1 > tracing_on; RUN_TEST; echo 0 > tracing_on

更加精准的追踪

不论如何上面的函数追踪总是会输入很多东西,缓冲区可能一下就满了,看不到想要的东西。这时我们可以在代码在来关闭和开启追踪器,以满足我们的需求。

应用层追踪某个系统调用
先关闭追踪器,然后在执行这个函数时打开追踪器,函数退出时打开追踪器。这样就实现了只追踪单个函数的功能。应用层还可以使用trace_maker来在trace中产生输出,以便用户搜索或是其他用处。使用方法为:
echo hello world > trace_marker

内核追踪某个函数
方法和上面差不多,只是内核不用echo 0/1,有专门的开启关闭函数可以使用,
tracing_on(),
tracing_off(),内核还可以使用trace_printk来与输出产生对应的交互关系。

多核系统中显示某个核调用

cat per_cpu/cpu0/trace

栈跟踪

打开栈跟踪
# echo 1 > /proc/sys/kernel/stack_tracer_enabled
查看结果
# cat stack_trace

        Depth    Size   Location    (41 entries)
        -----    ----   --------
  0)     2672      32   update_min_vruntime+0xe/0xb0
  1)     2640      64   update_curr+0x90/0x1e0
  2)     2576      20   update_cfs_shares+0xb7/0xc0
  3)     2556      92   task_tick_fair+0x106/0x7e0
  4)     2464      24   scheduler_tick+0x4c/0xc0
  5)     2440      16   update_process_times+0x49/0x60
  6)     2424      16   tick_sched_handle.isra.14+0x26/0x60
  7)     2408      32   tick_sched_timer+0x37/0x80
  8)     2376      48   __run_hrtimer+0x6f/0x200
  9)     2328     100   hrtimer_interrupt+0x210/0x2d0
 10)     2228      20   local_apic_timer_interrupt+0x32/0x60
 11)     2208      12   smp_apic_timer_interrupt+0x33/0x50
 12)     2196      72   apic_timer_interrupt+0x34/0x3c
 13)     2124       8   trace_clock_local+0x8/0x10
 14)     2116     104   rb_reserve_next_event+0x3e/0x470
 15)     2012      16   ring_buffer_lock_reserve+0x74/0xd0
 16)     1996      20   trace_buffer_lock_reserve+0xf/0x40
 17)     1976      32   trace_function+0x39/0x90
 18)     1944      36   function_trace_call+0x10b/0x140
 19)     1908      40   ftrace_ops_list_func+0x8d/0x150
 20)     1868      84   ftrace_call+0x5/0xb
 21)     1784     140   fib_create_info+0x962/0xb70
 22)     1644      96   fib_table_insert+0x68/0x920
 23)     1548     100   fib_magic.isra.16+0xb5/0xd0
 24)     1448      44   fib_add_ifaddr+0x114/0x1b0
 25)     1404      20   fib_inetaddr_event+0x67/0xa0
 26)     1384      32   notifier_call_chain+0x4e/0x70
 27)     1352      28   __blocking_notifier_call_chain+0x39/0x60
 28)     1324      16   blocking_notifier_call_chain+0x1f/0x30
 29)     1308      80   inet_rtm_newaddr+0x141/0x350
 30)     1228      52   rtnetlink_rcv_msg+0x84/0x1f0
 31)     1176      20   netlink_rcv_skb+0x8e/0xb0
 32)     1156      12   rtnetlink_rcv+0x21/0x30
 33)     1144      32   netlink_unicast+0xee/0x190
 34)     1112      96   netlink_sendmsg+0x249/0x610
 35)     1016     156   do_sock_sendmsg+0xbd/0xe0
 36)      860     272   ___sys_sendmsg+0x20f/0x250
 37)      588      76   __sys_sendmsg+0x38/0x70
 38)      512     424   SYSC_socketcall+0x7f6/0x9c0
 39)       88       8   SyS_socketcall+0x13/0x20
 40)       80      80   sysenter_after_call+0x0/0x16

可以看到栈深度,以及栈大小。

查看最大的栈使用大小
# cat stack_max_size
如果要重新记录,那么往里面echo 0即可。

注意,如果内核有使用单独的栈,那么跟踪器将跟踪不到最大使用的栈大小。例如中断有自己的栈,跟踪器就无法跟踪到。

如果在当前的嵌入式工作中打开这些内核选项

目前还不知道-_-!


参考:
Debugging the kernel using Ftrace - part 1
Debugging the kernel using Ftrace - part 2


人生苦短,远离bug