使用跟踪器进行调试¶
Copyright 2024 Google LLC.
- 作者:
Steven Rostedt <rostedt@goodmis.org>
- 许可证:
GNU 自由文档许可证,版本 1.2 (根据 GPL v2 双重授权)
针对:6.12
简介¶
跟踪基础设施对于调试 Linux 内核非常有用。本文档旨在汇总使用跟踪器进行调试的各种方法。
首先,确保挂载了 tracefs 文件系统
$ sudo mount -t tracefs tracefs /sys/kernel/tracing
使用 trace_printk()¶
trace_printk() 是一个非常轻量级的工具,除了 “noinstr” 段之外,它可以在内核内部的任何上下文中使用。它可以在正常、softirq、中断甚至 NMI 上下文中使用。跟踪数据以无锁方式写入跟踪环形缓冲区。为了使其更加轻量级,在可能的情况下,它只记录格式字符串的指针,并将原始参数保存到缓冲区中。格式和参数将在读取环形缓冲区时进行后处理。这样,trace_printk() 的格式转换就不会在记录跟踪的热路径中进行。
trace_printk() 仅用于调试,绝不应该添加到内核的子系统中。如果需要调试跟踪,请改用跟踪事件。如果在内核中发现了 trace_printk(),dmesg 中将会出现以下内容
**********************************************************
** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **
** **
** trace_printk() being used. Allocating extra memory. **
** **
** This means that this is a DEBUG kernel and it is **
** unsafe for production use. **
** **
** If you see this message and you are not debugging **
** the kernel, report this immediately to your vendor! **
** **
** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **
**********************************************************
调试内核崩溃¶
获取内核崩溃时系统状态的方法有很多种。这可以来自 printk 中的 oops 消息,也可以使用 kexec/kdump。但这些只能显示崩溃发生时的情况。了解崩溃发生前发生了什么非常有用。默认情况下,跟踪环形缓冲区是一个循环缓冲区,会用较新的事件覆盖较旧的事件。当崩溃发生时,环形缓冲区的内容将是导致崩溃的所有事件。
有几个内核命令行参数可用于辅助此操作。第一个是 “ftrace_dump_on_oops”。这将在发生 oops 时将跟踪环形缓冲区转储到控制台。如果控制台日志被记录在某处,这会很有用。如果使用串行控制台,明智的做法是确保环形缓冲区相对较小,否则转储环形缓冲区可能需要几分钟到几小时才能完成。以下是内核命令行的一个示例
ftrace_dump_on_oops trace_buf_size=50K
注意,跟踪缓冲区由每个 CPU 的缓冲区组成,其中每个缓冲区又被分为默认大小为 PAGE_SIZE 的子缓冲区。上面的 trace_buf_size 选项将每个 per CPU 缓冲区设置为 50K,因此在拥有 8 个 CPU 的机器上,总大小实际上是 400K。
跨启动持久化的缓冲区¶
如果系统内存允许,可以将跟踪环形缓冲区指定在内存中的特定位置。如果该位置在多次启动间保持不变且内存未被修改,则可以在下一次启动时检索跟踪缓冲区。有两种为环形缓冲区预留内存的方法。
更可靠的方法(在 x86 上)是使用 “memmap” 内核命令行选项预留内存,然后将该内存用于 trace_instance。这需要对系统的物理内存布局有一定的了解。使用这种方法的优点是,环形缓冲区的内存将始终相同
memmap==12M$0x284500000 trace_instance=boot_map@0x284500000:12M
上面的 memmap 在物理内存位置 0x284500000 处预留了 12 兆字节的内存。然后,trace_instance 选项将在同一位置创建名为 “boot_map” 的跟踪实例,并预留相同大小的内存。由于环形缓冲区被分为每个 CPU 的缓冲区,因此这 12 兆字节将在这些 CPU 之间平分。如果你有 8 个 CPU,则每个 CPU 的环形缓冲区大小为 1.5 兆字节。注意,这也包括元数据,因此环形缓冲区实际使用的内存会略小一些。
另一种更通用但在启动时分配环形缓冲区映射时不够稳健的方法是使用 “reserve_mem” 选项
reserve_mem=12M:4096:trace trace_instance=boot_map@trace
上面的 reserve_mem 选项将在启动时查找可用的 12 兆字节内存,并按 4096 字节对其进行对齐。它会将此内存标记为 “trace”,供后续的命令行选项使用。
trace_instance 选项创建一个 “boot_map” 实例,并将使用由 reserve_mem 预留且标记为 “trace” 的内存。此方法更通用,但可能不够可靠。由于 KASLR,reserve_mem 预留的内存可能不会位于相同的位置。如果发生这种情况,则环形缓冲区将不是来自上一次启动的缓冲区,而是会被重置。
有时,通过使用更大的对齐方式,可以防止 KASLR 以改变 reserve_mem 位置的方式移动对象。通过使用更大的对齐方式,你可能会发现缓冲区在其放置位置上更加一致
reserve_mem=12M:0x2000000:trace trace_instance=boot_map@trace
在启动时,会验证为环形缓冲区预留的内存。它将经历一系列测试,以确保环形缓冲区包含有效数据。如果是,它将将其设置为可从实例中读取。如果未通过任何测试,它将清除整个环形缓冲区并将其作为新缓冲区初始化。
此映射内存的布局可能在不同的内核之间不一致,因此只有在保留映射时才能保证相同内核正常工作。切换到不同的内核版本可能会发现不同的布局并将缓冲区标记为无效。
注意:映射地址和大小都必须针对该体系结构进行页面对齐。
在启动实例中使用 trace_printk()¶
默认情况下,trace_printk() 的内容会进入顶层跟踪实例。但此实例在多次启动之间绝不会被保留。为了让 trace_printk() 的内容以及其他一些内部跟踪进入保留缓冲区(如堆栈转储),可以从内核命令行将该实例设置为 trace_printk() 的目标,或者在启动后通过 trace_printk_dest 选项进行设置。
启动后
echo 1 > /sys/kernel/tracing/instances/boot_map/options/trace_printk_dest
从内核命令行
reserve_mem=12M:4096:trace trace_instance=boot_map^traceprintk^traceoff@trace
如果从内核命令行进行设置,建议同时使用 “traceoff” 标志禁用跟踪,并在启动后启用跟踪。否则,最近一次启动的跟踪会与前一次启动的跟踪混合在一起,可能会导致阅读混乱。
使用备份实例保留前一次启动的数据¶
还可以通过使用持久环形缓冲区指定事件来在系统启动时记录跟踪数据,但在这种情况下,重启前的数据在被读取之前就会丢失。这个问题可以通过备份实例来解决。从内核命令行
reserve_mem=12M:4096:trace trace_instance=boot_map@trace,sched,irq trace_instance=backup=boot_map
在启动时,“boot_map” 中的先前数据会被复制到 “backup” 实例中,并且当前启动的 “sched:” 和 “irq:” 事件会在 “boot_map” 中进行跟踪。因此,用户无需停止跟踪即可从 “backup” 实例中读取前一次启动的数据。
请注意,此 “backup” 实例是只读的,如果你清除跟踪数据或从 “trace_pipe” 或 “trace_pipe_raw” 文件中读出所有跟踪数据,它将被自动删除。