BLOG

Record, summarize, and improve.

内核调试方法

配置文件
Kernel hacking  --->      

[*]   Magic SysRq key 

[*]   Kernel debugging 

[*]   Debug slab memory allocations   

[*]   Spinlock and rw-lock debugging: basic checks 

[*]   Spinlock debugging: sleep-inside-spinlock checking 

           [*]   Compile the kernel with debug info   

Device Drivers  --->   

           Generic Driver Options  ---> 

           [*]   Driver Core verbose debug messages 

General setup  ---> 

           [*]   Configure standard kernel features (for small systems)  ---> 

           [*]   Load all symbols for debugging/ksymoops
slab layer debugging(slab层调试选项) 

high-memory debugging(高端内存调试选项) 

I/O mapping debugging(I/O映射调试选项) 

spin-lock debugging(自旋锁调试选项) 

stack-overflow checking(栈溢出检查选项) 

sleep-inside-spinlock checking(自旋锁内睡眠选项)

原子操作计数器

内核提供了一个原子操作计数器,它可以配置成,一旦在原子操作过程中,进程进入睡眠或者做了一些可能引起睡眠的操作,就打印警告信息并提供追踪线索。

所以,包括在使用锁的时候调用schedule(),正使用锁的时候以阻塞方式请求分配内存等,各种潜在的bug都能够被探测到。

下面这些选项可以最大限度地利用该特性:

CONFIG_PREEMPT = y 

CONFIG_DEBUG_KERNEL = y 

CONFIG_KLLSYMS = y 

CONFIG_SPINLOCK_SLEEP = y

BUG和BUG_ON

一些内核调用可以用来方便标记bug,提供断言并输出信息。最常用的两个是BUG()BUG_ON()

定义在<include/asm-generic>中:

当调用这两个宏的时候,它们会引发OOPS,导致栈的回溯和错误消息的打印。

※ 可以把这两个调用当作断言使用,如:BUG_ON(bad_thing);

dump_stack和__backtrace

有些时候,只需要在终端上打印一下栈的回溯信息来帮助你调试。这时可以使用dump_stack()。这个函数只在终端上打印寄存器上下文和函数的跟踪线索

在linux内核调试中,经常用到的打印函数调用堆栈的方法非常简单,只需在需要查看堆栈的函数中加入:

dump_stack();或 __backtrace();即可。

dump_stack()在~/kernel/ lib/Dump_stack.c中定义

void dump_stack(void)
{
printk(KERN_NOTICE
"This architecture does not implement dump_stack()/n");
}

__backtrace()的定义在~/kernel/arch/arm/lib/backtrace.S中

ENTRY(__backtrace)
mov r1, #0x10
mov r0, fp

klogd

在标准的Linux系统上,用户空间的守护进程klogd从纪录缓冲区中获取内核消息,再通过syslogd守护进程把这些消息保存在系统日志文件中。klogd进程既可以从/proc/kmsg文件中,也可以通过syslog()系统调用读取这些消息。默认情况下,它选择读取/proc方式实现。klogd守护进程在消息缓冲区有新的消息之前,一直处于阻塞状态。一旦有新的内核消息,klogd被唤醒,读出内核消息并进行处理。默认情况下,处理例程就是把内核消息传给syslogd守护进程。syslogd守护进程一般把接收到的消息写入/var/log/messages文件中。不过,还是可以通过/etc/syslog.conf文件来进行配置,可以选择其他的输出文件。

MEMWATCH和YAMD

MEMWATCH 和 YAMD 都是很有用的调试工具,它们的使用方法有所不同。对于 MEMWATCH,您需要添加包含文件memwatch.h 并打开两个编译时间标记。对于链接(link)语句,YAMD 只需要 -g 选项。

OOPS

OOPS(也称 Panic)消息包含系统错误的细节,如 CPU 寄存器的内容等。是内核告知用户有不幸发生的最常用的方式。内核报oops,这个时候不见得会panic,它可能只是报个oops,杀死进程而已

内核只能发布OOPS,这个过程包括向终端上输出错误消息,输出寄存器保存的信息,并输出可供跟踪的回溯线索。通常,发送完OOPS之后,内核会处于一种不稳定的状态。

OOPS的产生有很多可能原因,其中包括内存访问越界或非法的指令等。

在 Linux 中,调试系统崩溃的传统方法是分析在发生崩溃时发送到系统控制台的 Oops 消息。一旦您掌握了细节,就可以将消息发送到 ksymoops 实用程序,它将试图将代码转换为指令并将堆栈值映射到内核符号。

※ 如:回溯线索中的地址,会通过ksymoops转化成名称可见的函数名。

ksymoops需要几项内容:Oops 消息输出、来自正在运行的内核的 System.map 文件,还有 /proc/ksyms、vmlinux和/proc/modules。 关于如何使用 ksymoops,内核源代码 /usr/src/linux/Documentation/oops-tracing.txt 中或 ksymoops 手册页上有完整的说明可以参考。Ksymoops 反汇编代码部分,指出发生错误的指令,并显示一个跟踪部分表明代码如何被调用。

非中断上下文的oops只是oops;中断上下文oops要panic;如果设置了panic_on_oops,任何oops都是panic

panic

致命错误

Kdump

3.1Kdump 的基本概念

3.1.1  什么是 kexec ?

Kexec 是实现 kdump 机制的关键,它包括 2 个组成部分:一是内核空间的系统调用 kexec_load,负责在生产内核(production kernel 或 first kernel)启动时将捕获内核(capture kernel 或 sencond kernel)加载到指定地址。二是用户空间的工具 kexec-tools,他将捕获内核的地址传递给生产内核,从而在系统崩溃的时候能够找到捕获内核的地址并运行。没有 kexec 就没有 kdump。先有 kexec 实现了在一个内核中可以启动另一个内核,才让 kdump 有了用武之地。kexec 原来的目的是为了节省 kernel 开发人员重启系统的时间,谁能想到这个“偷懒”的技术却孕育了最成功的内存转存机制呢?

3.1.2  什么是 kdump ?

Kdump 的概念出现在 2005 左右,是迄今为止最可靠的内核转存机制,已经被主要的 linux™ 厂商选用。kdump是一种先进的基于 kexec 的内核崩溃转储机制。当系统崩溃时,kdump 使用 kexec 启动到第二个内核。第二个内核通常叫做捕获内核,以很小内存启动以捕获转储镜像。第一个内核保留了内存的一部分给第二内核启动用。由于 kdump 利用 kexec 启动捕获内核,绕过了 BIOS,所以第一个内核的内存得以保留。这是内核崩溃转储的本质。

kdump 需要两个不同目的的内核,生产内核和捕获内核。生产内核是捕获内核服务的对像。捕获内核会在生产内核崩溃时启动起来,与相应的 ramdisk 一起组建一个微环境,用以对生产内核下的内存进行收集和转存。

3.1.3  如何使用 kdump

构建系统和 dump-capture 内核,此操作有 2 种方式可选:

1)构建一个单独的自定义转储捕获内核以捕获内核转储;

2) 或者将系统内核本身作为转储捕获内核,这就不需要构建一个单独的转储捕获内核。

方法(2)只能用于可支持可重定位内核的体系结构上;目前 i386,x86_64,ppc64 和 ia64 体系结构支持可重定位内核。构建一个可重定位内核使得不需要构建第二个内核就可以捕获转储。但是可能有时想构建一个自定义转储捕获内核以满足特定要求。

3.1.4  如何访问捕获内存

在内核崩溃之前所有关于核心映像的必要信息都用 ELF 格式编码并存储在保留的内存区域中。ELF 头所在的物理地址被作为命令行参数(fcorehdr=)传递给新启动的转储内核。

在 i386 体系结构上,启动的时候需要使用物理内存开始的 640K,而不管操作系统内核转载在何处。因此,这个640K 的区域在重新启动第二个内核的时候由 kexec 备份。

在第二个内核中,“前一个系统的内存”可以通过两种方式访问:

1) 通过 /dev/oldmem 这个设备接口。

一个“捕捉”设备可以使用“raw”(裸的)方式 “读”这个设备文件并写出到文件。这是关于内存的 “裸”的数据转储,同时这些分析 / 捕捉工具应该足够“智能”从而可以知道从哪里可以得到正确的信息。ELF 文件头(通过命令行参数传递过来的 elfcorehdr)可能会有帮助。

2) 通过 /proc/vmcore。

这个方式是将转储输出为一个 ELF 格式的文件,并且可以使用一些文件拷贝命令(比如 cp,scp 等)将信息读出来。同时,gdb 可以在得到的转储文件上做一些调试(有限的)。这种方式保证了内存中的页面都以正确的途径被保存 ( 注意内存开始的 640K 被重新映射了 )。

获取module加载地址

Linux 2.6之后的内核中,由于module-init-tools工具的更改,insmod命令不再支持-m参数,只有采取其他的方法来获取模块加载到内核的地址。通过分析ELF文件格式,我们知道程序中各段的意义如下:

.text(代码段):用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存种的镜像。

.data(数据段):数据段用来存放可执行文件中已初始化全局变量,也就是存放程序静态分配的变量和全局变量。

.bss(BSS段):BSS段包含了程序中未初始化全局变量,在内存中 bss段全部置零。

.rodata(只读段):该段保存着只读数据,在进程映象中构造不可写的段。

通过在模块初始化函数中放置一下代码,我们可以很容易地获得模块加载到内存中的地址。

int bss_var;

static int hello_init(void)
{
printk(KERN_ALERT "Text location .text(Code Segment):%p\n",hello_init);
static int data_var=0;
printk(KERN_ALERT "Data Location .data(Data Segment):%p\n",&data_var);
printk(KERN_ALERT "BSS Location: .bss(BSS Segment):%p\n",&bss_var);
……
}

Module_init(hello_init);

/proc/modules 模块符号地址

这里,通过在模块的初始化函数中添加一段简单的程序,使模块在加载时打印出在内核中的加载地址。.rodata段的地址可以通过执行命令readelf -e hello.ko,取得.rodata在文件中的偏移量并加上段的align值得出。

为了使读者能够更好地进行模块的调试,kgdb项目还发布了一些脚本程序能够自动探测模块的插入并自动更新gdb中模块的符号信息。

kernel调试工具

kdump是Linux系统中用于捕获内核崩溃转储信息的工具。以下是一些类似于kdump的工具:

  1. crash:crash是一个命令行工具,可以用于分析内核转储文件和运行中的内核。它支持多种架构和内核版本,并提供了许多有用的命令来分析内核状态和调试问题。
  2. netdump:netdump是一个网络转储工具,可以用于在远程系统上捕获内核转储信息。当系统崩溃时,它可以将转储信息通过网络发送到另一个系统上进行分析。
  3. vmcore-dmesg:vmcore-dmesg是一个命令行工具,可以用于解析内核转储文件并显示与内核消息缓冲区相关的消息。它可以帮助您快速识别内核转储文件中的问题。
  4. makedumpfile:makedumpfile是一个命令行工具,可以用于从运行中的内核中创建转储文件。它支持多种内核版本和架构,并提供了许多选项来定制转储文件的内容和大小

pstack是一个常用的Linux工具,用于显示进程堆栈跟踪信息。以下是一些类似于pstack的工具:

  1. gstack:gstack是一个类似于pstack的工具,用于显示正在运行的进程的堆栈跟踪信息。它可以帮助您诊断进程崩溃或死锁的问题。
  2. strace:strace是一个跟踪系统调用的工具,可以用于诊断进程的问题。它可以显示进程执行的每个系统调用,并提供有关调用参数和返回值的信息。
  3. ltrace:ltrace是一个跟踪库函数调用的工具,可以用于诊断进程的问题。它可以显示进程执行的每个库函数调用,并提供有关调用参数和返回值的信息。
  4. perf:perf是一个性能分析工具,可以用于诊断进程的性能问题。它可以显示进程的CPU使用率、内存使用率和其他指标,并提供有关每个指标的详细信息。
  5. bt:bt是一个简单的命令行工具,用于显示正在运行的进程的堆栈跟踪信息。它可以帮助您快速诊断进程的问题。
  6. jstack:jstack是一个Java虚拟机工具,用于显示Java线程的堆栈跟踪信息。它可以帮助您诊断Java应用程序的问题。
  7. pmap:pmap是一个命令行工具,用于显示正在运行的进程的内存映射信息。它可以帮助您了解进程使用的内存布局和分配情况。
  8. objdump:objdump是一个命令行工具,用于显示可执行文件和共享库的反汇编信息。它可以帮助您了解正在运行的进程的代码执行路径和指令流程。

  1. lldb:lldb是一个类似于gdb的调试器,可以用于调试C、C++、Objective-C和Swift程序。它支持多种平台和架构,并提供了许多有用的命令和功能来调试程序。
  2. ddd:ddd是一个基于gdb的图形化调试器,可以用于调试C、C++和Fortran程序。它提供了一个易于使用的界面,可以帮助您更轻松地调试程序。
  3. valgrind:valgrind是一个内存调试和性能分析工具,可以用于调试C、C++和Fortran程序。它可以检测内存泄漏、越界访问和其他常见的内存错误,并提供有关程序性能的详细信息。
  4. strace:strace是一个跟踪系统调用的工具,可以用于调试程序。它可以显示程序执行的每个系统调用,并提供有关调用参数和返回值的信息。
  5. gdbgui:gdbgui是一个基于Web的图形化调试器,可以用于调试C、C++和Python程序。它提供了一个易于使用的界面,可以帮助您更轻松地调试程序。
  6. edb:edb是一个基于gdb的图形化调试器,可以用于调试C、C++和汇编语言程序。它提供了一个易于使用的界面,可以帮助您更轻松地调试程序。
  7. ddd-gdb:ddd-gdb是一个基于gdb的图形化调试器,可以用于调试C、C++和Fortran程序。它提供了一个易于使用的界面,可以帮助您更轻松地调试程序。
  8. radare2:radare2是一个基于命令行的反汇编器和调试器,可以用于调试各种类型的程序。它提供了许多有用的命令和功能,可以帮助您更轻松地调试程序。
  9. IDA Pro:IDA Pro是一个反汇编器和调试器,可以用于分析和调试各种类型的程序。它提供了许多有用的命令和功能,可以帮助您更轻松地分析和调试程序。
  10. Ghidra:Ghidra是一个反汇编器和逆向工程工具,可以用于分析和调试各种类型的程序。它提供了许多有用的命令和功能,可以帮助您更轻松地分析和调试程序

kgdb

  1. kdb:也是一个Linux内核调试器,支持在内核空间进行调试,并且可以在不同的CPU核之间切换调试。
  2. gdb:虽然gdb是一个用户空间调试器,但是它也可以用于内核调试。使用gdb进行内核调试需要一些额外的设置,例如使用kgdboc模块将kgdbgdb连接起来。
  3. dprobes:是一个动态探测工具,可以用于在运行时动态地修改内核代码和数据。使用dprobes可以避免重新编译内核的麻烦。
  4. SystemTap:是一个系统级跟踪工具,可以用于在内核空间和用户空间进行跟踪和分析。SystemTap提供了一种简单的脚本语言,可以用于编写跟踪程序。
  5. ftrace:是一个内核跟踪工具,可以用于在内核空间进行跟踪。ftrace提供了多种跟踪方式,例如函数跟踪、事件跟踪等。
  6. lldb:是一个多平台调试器,支持在内核空间进行调试。lldb提供了一些高级调试功能,例如多线程调试、表达式求值等。
  7. valgrind:是一个内存调试工具,可以用于检测内存泄漏、访问越界等问题。valgrind可以在内核空间进行调试,但需要一些额外的设置。
  8. OProfile:是一个性能分析工具,可以用于在内核空间进行性能分析。OProfile可以跟踪函数调用、指令执行等信息,并生成相应的报告。
  9. strace:是一个系统调用跟踪工具,可以用于在用户空间和内核空间进行跟踪。strace可以跟踪进程的系统调用,以及系统调用的参数和返回值。
  10. System Crash Analyzer:是一个用于分析内核崩溃的工具,可以用于诊断内核崩溃的原因。System Crash Analyzer可以分析内核转储文件,并生成相应的报告。
  11. crash:是一个用于分析内核转储文件的工具,可以用于诊断内核崩溃的原因。crash提供了一些高级的分析功能,例如跟踪内核数据结构、分析内存泄漏等。
  12. LTTng:是一个跟踪工具,可以用于在内核空间进行跟踪。LTTng可以跟踪内核事件、系统调用等信息,并生成相应的跟踪数据。
  13. perf:是一个性能分析工具,可以用于在内核空间进行性能分析。perf可以跟踪函数调用、指令执行等信息,并生成相应的报告。
  14. DTrace:是一个跟踪工具,可以用于在内核空间和用户空间进行跟踪。DTrace可以跟踪系统调用、进程事件等信息,并生成相应的跟踪数据。
  15. ebpf:是一个内核跟踪工具,可以用于在内核空间进行跟踪。ebpf提供了一种简单的虚拟机,可以在内核中运行用户定义的程序,从而实现跟踪和分析功能。
  16. Kprobes:是一个动态探测工具,可以用于在运行时动态地修改内核代码和数据。使用Kprobes可以避免重新编译内核的麻烦。
  17. rr:是一个录制和重放工具,可以用于在内核空间进行调试。使用rr可以记录程序的执行过程,并在需要时重放执行过程以进行调试。
  18. Sysdig:是一个跟踪工具,可以用于在内核空间和用户空间进行跟踪。Sysdig可以跟踪系统调用、进程事件等信息,并生成相应的跟踪数据。
  19. BPF Compiler Collection:是一个内核跟踪工具,可以用于在内核空间进行跟踪。BPF Compiler Collection提供了一组工具和库,可以用于编写内核跟踪程序。