使用 tracer 进行调试

版权所有 2024 Google LLC.

作者:

Steven Rostedt <rostedt@goodmis.org>

许可证:

GNU 自由文档许可证,版本 1.2(在 GPL v2 下双重许可)

  • 编写于: 6.12

简介

跟踪基础设施对于调试 Linux 内核非常有用。本文档用于添加各种使用 tracer 进行调试的方法。

首先,请确保已挂载 tracefs 文件系统

$ sudo mount -t tracefs tracefs /sys/kernel/tracing

使用 trace_printk()

trace_printk() 是一个非常轻量级的实用程序,可以在内核中的任何上下文中使用,除了“noinstr”部分。 它可以在 normal、softirq、interrupt 甚至 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 选项将每个 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”标志禁用跟踪,并在启动后启用跟踪。 否则,来自最新启动的跟踪将与来自上一次启动的跟踪混合,这可能会使读取变得混乱。