1. 简介

要使用ftrace进行boottime追踪,可以使用内核参数(bootargs)或者bootconfig来进行配置。

关键的函数如下:

 1+-- start_kernel
 2/// ... ...
 3|   +-- setup_arch
 4|   +-- setup_boot_config
 5|   |   +-- get_boot_config_from_initrd
 6|   |   +-- xbc_get_embedded_bootconfig
 7|   |   +-- xbc_init            /// 解析bootconfig,创建树
 8|   +-- setup_command_line
 9/// ... ...
10|   +-- parse_early_param
11|   +-- parse_args              /// 解析传入的ftrace等启动参数
12|   +-- random_init_early
13|   +-- setup_log_buf
14|   +-- vfs_caches_init_early
15|   +-- sort_main_extable
16|   +-- trap_init
17|   +-- mm_core_init
18|   +-- poking_init
19|   +-- ftrace_init
20|   |   +-- set_ftrace_early_filters    /// 根据bootargs,设置function tracer和function_graph tracer要追踪的函数,event等
21|   +-- early_trace_init                /* trace_printk can be enabled here */
22|   |   +-- tracer_alloc_buffers
23|   |   |   +-- init_function_trace     /* Function tracing may start here (via kernel command line) */
24|   |   +-- init_events
25|   +-- sched_init                      /// 在function tracer下,这是第一个可以被ftrace追踪的函数
26/// ... ...
27|   +-- trace_init
28|   |   +-- trace_event_init
29|   |   +-- enable_instances            /// if (boot_instance_index)

后续操作使用qemu模拟ARM64环境,可以参考https://gitee.com/kingdix10/eel

2. bootargs方式

u-boot可以通过bootargs环境变量或者设备树向内核传递参数,qemu则可以使用-append参数向内核传递参数。

2.1. function tracer

early_trace_init -> tracer_alloc_buffers -> init_function_trace后,function tracer就可以开始根据bootargs中的ftrace_filter参数,设置追踪的函数。

qemu启动时传递给内核的启动参数,具体参考https://gitee.com/kingdix10/eel/blob/main/configs/kl_args.mk

1KL_ARGS					+= trace_options=sym-addr,print-parent trace_event=initcall:* trace_buf_size=1M
2# tracer: function
3KL_ARGS					+= ftrace=function ftrace_filter="early_trace_init,sched_init,*_rootfs*,*mount*"

启动qemu

1make qemu-kernel FTRACE=1

启动完成后,可以使用trace-cmd show或者cat /sys/kernel/debug/tracing/trace查看追踪结果。结果内容较多,可以将输出重定向到文件查看。

为了方便查看,这里为ftrace_filter指定了sched_init,可以看到输出结果的第一行就是sched_init

 1# tracer: function
 2#
 3# entries-in-buffer/entries-written: 2032/2032   #P:2
 4#
 5#                                _-----=> irqs-off/BH-disabled
 6#                               / _----=> need-resched
 7#                              | / _---=> hardirq/softirq
 8#                              || / _--=> preempt-depth
 9#                              ||| / _-=> migrate-disable
10#                              |||| /     delay
11#           TASK-PID     CPU#  |||||  TIMESTAMP  FUNCTION
12#              | |         |   |||||     |         |
13          <idle>-0       [000] dp.2.     0.000000: sched_init <ffff80008145ec8c> <-start_kernel <ffff800081451270>
14          <idle>-0       [000] ...1.     0.011784: initcall_level: level=console
15          <idle>-0       [000] ...1.     0.012104: initcall_start: func=con_init+0x0/0x220
16          <idle>-0       [000] ...1.     0.014390: initcall_finish: func=con_init+0x0/0x220 ret=0
17/// ... ...

2.2. function graph tracer

function graph tracer依赖init_graph_trace进行初始化。

1/// kernel/trace/trace_functions_graph.c
2core_initcall(init_graph_trace)

core_initcall的处理是在1号进程中进行的,所以function graph tracer可以追踪的时机要比function tracer晚。

1+-- kernel_init
2|   +-- kernel_init_freeable
3|   |   +-- do_basic_setup
4|   |   |   +-- do_initcalls

类似function tracer,具体参考https://gitee.com/kingdix10/eel/blob/main/configs/kl_args.mk

1KL_ARGS					+= trace_options=sym-addr,print-parent trace_event=initcall:* trace_buf_size=1M
2# tracer: function_graph
3KL_ARGS					+= ftrace=function_graph ftrace_graph_max_depth=5 ftrace_graph_filter="sched_init,*_rootfs*,*mount*" ftrace_graph_notrace="*_*lock"

启动qemu

1make qemu-kernel FTRACE=1

同样使用trace-cmd show或者cat /sys/kernel/debug/tracing/trace查看追踪结果。 注意initcall_finish: func=init_graph_trace,这说明在init_graph_trace完成后,function graph tracer就可以开始追踪了。

 1# tracer: function_graph
 2#
 3# CPU  DURATION                  FUNCTION CALLS
 4# |     |   |                     |   |   |   |
 5 0)               |  /* initcall_finish: func=init_graph_trace+0x0/0x88 ret=0 */
 6 0)               |  /* initcall_start: func=trace_events_eprobe_init_early+0x0/0x48 */
 7 0)               |  /* initcall_finish: func=trace_events_eprobe_init_early+0x0/0x48 ret=0 */
 8 0)               |  /* initcall_start: func=trace_events_synth_init_early+0x0/0x48 */
 9 0)               |  /* initcall_finish: func=trace_events_synth_init_early+0x0/0x48 ret=0 */
10/// ... ...
11 0)               |  /* initcall_start: func=init_elf_binfmt+0x0/0x40 */
12 0)               |  /* initcall_finish: func=init_elf_binfmt+0x0/0x40 ret=0 */
13 0)               |  /* initcall_start: func=init_compat_elf_binfmt+0x0/0x40 */
14 0)               |  /* initcall_finish: func=init_compat_elf_binfmt+0x0/0x40 ret=0 */
15 0)               |  /* initcall_start: func=configfs_init+0x0/0xd0 */
16 0)               |  sysfs_create_mount_point() {
17 0)               |    irq_enter_rcu() {
18 0)   4.640 us    |      preempt_count_add();
19/// ... ...

3. bootconfig方式

由于bootargs不足以控制ftrace复杂的功能,内核使用bootconfig扩展了现有的内核命令行,用以跟踪功能编程。具体查看《参考资料》部分的文档或链接。

do_initcalls中,core_initcall_sync是在core_initcall之后执行的,bootconfig方式可以追踪的时机要比function graph tracer更晚。

1/// kernel/trace/trace_boot.c
2core_initcall_sync(trace_boot_init)

3.1. bootrace.bconf

以下例子来自于tools/testing/ktest/examples/bootconfigs/boottrace.bconf,将ftrace_filter修改为sched_init,*mount*

1kernel {
2    trace_options = sym-addr
3    trace_event = "initcall:*"
4    trace_buf_size = 1M
5    ftrace = function
6    ftrace_filter = "sched_init,*mount*"
7}

3.2. initrd

目前bootconfig只支持追加到initrd镜像之后或者与内核镜像编译到一起,这里使用initrd来实现。

同样使用https://gitee.com/kingdix10/eel

为initrd添加或修改bootconfig,可以使用make initrd-addbconf,输出如下:

1/data/eel/source/kernel/linux-6.6/tools/bootconfig/bootconfig -a presets/boottrace.bconf \
2        /data/eel/output/arm64/rootfs/buildroot-2024.02/mine_arm64_defconfig/images/rootfs.cpio
3Apply presets/boottrace.bconf to /data/eel/output/arm64/rootfs/buildroot-2024.02/mine_arm64_defconfig/images/rootfs.cpio
4        Number of nodes: 91
5        Size: 1045 bytes
6        Checksum: 85503

查看initrd的bootconfig,可以使用make initrd-listbconf,输出如下:

1/data/eel/source/kernel/linux-6.6/tools/bootconfig/bootconfig -l \
2        /data/eel/output/arm64/rootfs/buildroot-2024.02/mine_arm64_defconfig/images/rootfs.cpio
3ftrace.event.task.task_newtask.filter = "pid < 128"
4ftrace.event.task.task_newtask.enable = ""
5ftrace.event.kprobes.vfs_read.probes = "vfs_read $arg1 $arg2"
6ftrace.event.kprobes.vfs_read.filter = "common_pid < 200"
7ftrace.event.kprobes.vfs_read.enable = ""
8... ...

删除bootconfig,可以使用make initrd-delbconf

1/data/eel/source/kernel/linux-6.6/tools/bootconfig/bootconfig -d \
2        /data/eel/output/arm64/rootfs/buildroot-2024.02/mine_arm64_defconfig/images/rootfs.cpio

3.3. qemu启动

为initrd增加bootconfig后,就可以启动qemu了。与bootargs方式不同,这里使用如下命令启动qemu

1make qemu-kernel-initrd FTRACE=1

可以使用make qemu-kernel-initrd FTRACE=1 -n来查看实际使用的命令,如下:

1qemu-system-aarch64 \
2        -nographic -smp 2 -m 1024 -cpu cortex-a57 \
3        -M type=virt,mte=off,virtualization=false,gic-version=3 \
4         \
5        -semihosting -semihosting-config enable=on,target=native  -netdev user,id=net0 -device virtio-net-device,netdev=net0 -object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0,max-bytes=1024,period=1000  -fsdev local,id=share_comm,path=/data/eel/share,security_model=none -device virtio-9p-device,fsdev=share_comm,mount_tag=mnt_comm -fsdev local,id=share_arch,path=/data/eel/output/arm64,security_model=none -device virtio-9p-device,fsdev=share_arch,mount_tag=mnt_arch -fsdev local,id=share_eel,path=/data/eel,security_model=passthrough,readonly=on -device virtio-9p-device,fsdev=share_eel,mount_tag=mnt_eel \
6         \
7        -kernel /data/eel/output/arm64/kernel/linux-6.6/arm64_debug_defconfig/arch/arm64/boot/Image \
8        -append "console=ttyAMA0 nokaslr crashkernel=512M-1G:64M,1G-:128M bootconfig trace_options=sym-addr,print-parent trace_event=initcall:* trace_buf_size=1M ftrace=function_graph ftrace_graph_max_depth=5 ftrace_graph_filter="sched_init,*_rootfs*,*mount*" ftrace_graph_notrace="*_*lock" loglevel=6 initcall_debug no_console_suspend root=/dev/vda rw init=/linuxrc" \
9        -initrd /data/eel/output/arm64/rootfs/buildroot-2024.02/mine_arm64_defconfig/images/rootfs.cpio

同样使用trace-cmd show或者cat /sys/kernel/debug/tracing/trace查看追踪结果。

4. 其他说明

4.1. 根文件系统

使用bootargs,根文件系统默认使用rootfs.ext4。 使用bootconfig,根文件系统默认使用添加bootconfigrootfs.cpio

4.2. trace buffer

trace_buf_size是一个有限的值,如果启动过程中记录的信息过多,可能导致trace buffer回滚,丢失开始的记录。 这里在init进程启动后,关闭了trace,防止trace buffer回滚。见https://gitee.com/kingdix10/eel/blob/main/share/init.sh

1# disable boottime trace, avoid trace buffer overflow
2echo 0 > /sys/kernel/debug/tracing/tracing_on

5. 参考资料

  1. Documentation/trace/ftrace.rst
  2. Documentation/trace/boottime-trace.rst
  3. Documentation/admin-guide/bootconfig.rst
  4. Documentation/translations/zh_CN/admin-guide/bootconfig.rst