Linux内核实时调试,如何完成以及使用哪些工具?

Linux kernel live debugging, how it's done and what tools are used?

在Linux内核上进行实时调试的最常用方法和工具为何? 我知道例如Linus。 反对这种针对Linux内核的调试,或者至少是反对这种调试,因此在那些年里,在这种意义上没有做太多事情,但是老实说,自2000年以来已经过去了很多时间,我很想知道关于Linux的观念是否已改变 项目以及当前使用哪些当前方法在Linux内核上进行实时调试(本地或远程)?

欢迎参考有关所提到的技术和工具的演练和教程。


另一种选择是使用ICE / JTAG控制器和GDB。这种"硬件"解决方案特别适用于嵌入式系统,

但例如Qemu提供类似的功能:

  • 用侦听'localhost:1234'的gdb'remote'存根启动qemu:qemu -s ...

  • 然后使用GDB打开用调试信息编译的内核文件vmlinux(您可以查看此邮件列表线程,他们在其中讨论了内核的未优化)。

  • 连接GDB和Qemu:target remote localhost:1234

  • 查看您的实时内核:

    1
    2
    3
    4
    5
    6
    (gdb) where
    #0  cpu_v7_do_idle () at arch/arm/mm/proc-v7.S:77
    #1  0xc0029728 in arch_idle () atarm/mach-realview/include/mach/system.h:36
    #2  default_idle () at arm/kernel/process.c:166
    #3  0xc00298a8 in cpu_idle () at arch/arm/kernel/process.c:199
    #4  0xc00089c0 in start_kernel () at init/main.c:713

不幸的是,到目前为止,使用GDB无法进行用户空间调试(没有任务列表信息,没有MMU重新编程以查看不同的进程上下文,...),但是如果留在内核空间中,那将非常方便。

  • info threads将为您提供不同CPU的列表和状态

编辑:

您可以在此PDF中获取有关该过程的更多详细信息:

Debugging Linux systems using GDB and QEMU.


在调试Linux内核时,我们可以利用多种工具,例如调试器(KDB,KGDB),崩溃时转储(LKCD),跟踪工具包(LTT,LTTV,LTTng),自定义内核工具(dprobes,kprobes)。在下一节中,我试图总结其中的大部分内容,希望对您有所帮助。

好。

LKCD(Linux内核崩溃转储)工具允许Linux系统在发生崩溃时写入其内存内容。可以进一步分析这些日志以查找崩溃的根本原因。有关LKCD的资源

好。

  • http://www-01.ibm.com/support/knowledgecenter/linuxonibm/liaax/lkcd.pdf
  • https://www.novell.com/coolsolutions/feature/15284.html
  • https://www.novell.com/support/kb/doc.php?id=3044267
  • 好。

    糟糕,当内核检测到问题时,它会打印一条糟糕的消息。这样的消息是由故障处理程序(arch / * / kernel / traps.c)中的printk语句生成的。 printk语句使用的内核中的专用环形缓冲区。 Oops包含以下信息:发生Oops的CPU,CPU寄存器的内容,Oops的数量,描述,堆栈回溯跟踪等。有关内核Oop的资源

    好。

  • https://www.kernel.org/doc/Documentation/oops-tracing.txt
  • http://madwifi-project.org/wiki/DevDocs/KernelOops
  • https://wiki.ubuntu.com/DebuggingKernelOops
  • 好。

    Dynamic Probes是IBM开发的流行的Linux调试工具之一。该工具允许在用户和内核空间中的几乎任何位置放置"探针"。该探针由一些代码(用专用的,面向堆栈的语言编写)组成,这些代码在控件到达给定点时执行。下面列出了有关动态探针的资源

    好。

  • http://www-01.ibm.com/support/knowledgecenter/linuxonibm/liaax/dprobesltt.pdf
  • http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.107.6212&rep=rep1&type=pdf
  • 好。

    Linux Trace Toolkit是一个内核修补程序和一组相关的实用程序,允许跟踪内核中的事件。跟踪包括时序信息,并且可以创建给定时间段内发生的事情的合理完整的图片。 LTT,LTT Viewer和下一代LTT的资源

    好。

  • http://elinux.org/Linux_Trace_Toolkit
  • http://www.linuxjournal.com/article/3829
  • http://multivax.blogspot.com/2010/11/introduction-to-linux-tracing-toolkit.html
  • 好。

    MEMWATCH是一种开源内存错误检测工具。它通过在gcc语句中定义MEMWATCH并将标题文件添加到我们的代码中来工作。通过这种方式,我们可以跟踪内存泄漏和内存损坏。有关MEMWATCH的资源

    好。

  • http://www.linuxjournal.com/article/6059
  • 好。

    ftrace是Linux内核的良好跟踪框架。 ftrace跟踪内核的内部操作。该工具包含在2.6.27的Linux内核中。借助其各种跟踪器插件,ftrace可以针对不同的静态跟踪点,例如调度事件,中断,内存映射的I / O,CPU电源状态转换以及与文件系统和虚拟化相关的操作。此外,还提供对内核函数调用的动态跟踪,可以选择使用glob将其限制为函数的子集,并且可以生成调用图并提供堆栈使用情况。您可以在https://events.linuxfoundation.org/slides/2010/linuxcon_japan/linuxcon_jp2010_rostedt.pdf上找到有关ftrace的很好的教程。

    好。

    ltrace是Linux中的调试实用程序,用于显示用户空间应用程序对共享库的调用。该工具可用于跟踪任何动态库函数调用。它截获并记录由执行的过程调用的动态库调用以及该过程接收的信号。它还可以拦截并打印程序执行的系统调用。

    好。

  • Getting started with ltrace: how does it do that?

  • http://developerblog.redhat.com/2014/07/10/ltrace-for-rhel-6-and-7/
  • 好。

    KDB是Linux内核的内核内调试器。 KDB遵循简单的shell风格的界面。我们可以使用它来检查内存,寄存器,进程列表,dmesg,甚至设置断点以停止在某个位置。通过KDB,我们可以设置断点并执行一些基本的内核运行控制(尽管KDB不是源级调试器)。有关KDB的一些方便资源

    好。

  • http://www.drdobbs.com/open-source/linux-kernel-debugging/184406318
  • http://elinux.org/KDB
  • http://dev.man-online.org/man1/kdb/
  • https://www.kernel.org/pub/linux/kernel/people/jwessel/kdb/usingKDB.html
  • 好。

    KGDB旨在用作Linux内核的源代码级调试器。它与gdb一起用于调试Linux内核。使用kgdb需要两台机器。其中一台机器是开发机器,另一台是目标机器。要调试的内核在目标计算机上运行。期望gdb可用于"侵入"内核以检查内存,变量并查看调用堆栈信息,类似于应用程序开发人员使用gdb调试应用程序的方式。可以在内核代码中放置断点并执行一些有限的执行步骤。有关KGDB的一些方便资源

    好。

  • http://landley.net/kdocs/Documentation/DocBook/xhtml-nochunks/kgdb.html
  • 好。

    好。


    根据Wiki,kgdb在最近几年内已合并到2.6.26的内核中。 kgdb是一个远程调试器,因此您可以在内核中激活它,然后以某种方式将gdb附加到它。我以某种方式说,因为似乎有很多选择-请参见连接gdb。鉴于kgdb现在位于源代码树中,我想说这就是您要使用的东西。

    因此,看起来Linus屈服了。但是,我要强调他的观点-您应该知道自己在做什么,并且对系统非常了解。这是核心土地。如果出了什么问题,您就不会得到segfault,从后来的一些晦涩的问题到整个系统崩溃,您都不会得到任何收益。这是龙。继续小心,您已被警告。


    另一个用于"实时"调试的好工具是kprobes /动态探测器。

    这使您可以动态地构建一些小的模块,这些模块在执行某些地址时便会运行-有点像断点。

    它们的最大优点是:

  • 它们不会影响系统-即当命中某个位置时-它只会执行代码-不会停止整个内核。
  • 您不需要像kgdb那样互连两个不同的系统(目标和调试)
  • 最好执行诸如达到断点,查看数据值或检查事物是否已更改/覆盖等操作。如果要"单步执行代码",则不这样做。

    增加-2018年:

    另一个非常强大的方法是一个简单地称为" perf"的程序,该程序可以汇总许多工具(例如动态探针),并且可以替换/贬低其他工具(例如oprofile)。

    特别是,perf probe命令可用于轻松创建动态探针/向系统添加动态探针,然后perf record可以对系统进行采样并在探针被命中以通过perf report报告时报告信息(和回溯)(或perf script)。如果您在内核中具有良好的调试符号,则即使不关闭内核,也可以从系统中获得出色的信息。进行man perf(在Google或您的系统上)以获取有关此工具的更多信息,或在其上查看以下出色的页面:

    http://www.brendangregg.com/perf.html


    KGDB + QEMU逐步

    KGDB是内核子系统,允许您从主机GDB逐步调试内核本身。

    我的QEMU + Buildroot示例是在没有实际硬件的情况下品尝它的好方法:https://github.com/cirosantilli/linux-kernel-module-cheat/tree/1969cd6f8d30dace81d9848c6bacbb8bad9dacd8#kgdb

    优缺点与其他方法:

    • 与QEMU的优势:

      • 您通常没有为您的设备提供软件仿真,因为硬件供应商不喜欢为其设备发布准确的软件模型
      • 真正的硬件方式比QEMU更快
    • 与JTAG相比的优势:无需额外的JTAG硬件,易于设置
    • 与QEMU和JTAG相比的缺点:可见性较低,更具侵入性。 KGDB依赖于内核的某些部分才能与主机进行通信。所以例如它崩溃时崩溃了,您无法查看启动顺序。

    主要步骤是:

  • 使用以下命令编译内核:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    CONFIG_DEBUG_KERNEL=y
    CONFIG_DEBUG_INFO=y

    CONFIG_CONSOLE_POLL=y
    CONFIG_KDB_CONTINUE_CATASTROPHIC=0
    CONFIG_KDB_DEFAULT_ENABLE=0x1
    CONFIG_KDB_KEYBOARD=y
    CONFIG_KGDB=y
    CONFIG_KGDB_KDB=y
    CONFIG_KGDB_LOW_LEVEL_TRAP=y
    CONFIG_KGDB_SERIAL_CONSOLE=y
    CONFIG_KGDB_TESTS=y
    CONFIG_KGDB_TESTS_ON_BOOT=n
    CONFIG_MAGIC_SYSRQ=y
    CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1
    CONFIG_SERIAL_KGDB_NMI=n

    其中大多数不是强制性的,但这是我测试过的。

  • 添加到您的QEMU命令:

    1
    2
    -append 'kgdbwait kgdboc=ttyS0,115200' \
    -serial tcp::1234,server,nowait
  • 使用以下命令从Linux内核源树的根目录运行GDB:

    1
    gdb -ex 'file vmlinux' -ex 'target remote localhost:1234'
  • 在GDB中:

    1
    (gdb) c

    并且启动应该完成。

  • 在QEMU中:

    1
    echo g > /proc/sysrq-trigger

    GDB应该崩溃了。

  • 现在我们完成了,您可以照常使用GDB:

    1
    2
    b sys_write
    c
  • 在Ubuntu 14.04中测试。

    KGDB +树莓派

    与上面完全相同的设置几乎可以在Raspberry Pi 2,Raspbian Jessie 2016-05-27上运行。

    您只需要学习在Pi上执行QEMU步骤即可,这很容易
    可谷歌搜索:

    • 按照https://www.raspberrypi.org/documentation/linux/kernel/building.md的说明添加配置选项并重新编译内核。不幸的是,默认内核构建中缺少选项,尤其是没有调试符号,因此重新编译是需要。

    • 编辑引导分区的cmdline.txt并添加:

      1
      kgdbwait kgdboc=ttyAMA0,115200
    • 使用以下命令将gdb连接到串行:

      1
      arm-linux-gnueabihf-gdb -ex 'file vmlinux' -ex 'target remote /dev/ttyUSB0'

      如果您不熟悉该序列号,请查看以下内容:https://www.youtube.com/watch?v=da5Q7xL_OTo您只需要一个像这样的便宜适配器即可。在尝试KGDB之前,请确保您可以通过序列获得Shell以确保其正常工作。

    • 做:

      1
      echo g | sudo tee /proc/sysrq-trigger

      从SSH会话内部进行,因为串行已由GDB进行。

    通过此设置,我能够在sys_write中放置一个断点,暂停程序执行,列出源并继续。

    但是,有时当我在sys_write中执行next时,GDB只是挂起并多次打印此错误消息:

    1
    Ignoring packet error, continuing...

    因此,我不确定我的设置是否有问题,还是因为某些后台进程在更复杂的Raspbian图像中正在执行操作而导致出现这种情况。

    还告诉我尝试使用Linux引导选项禁用多处理,但是我还没有尝试过。


    实际上,笑话是Linux自2.2.12起具有一个内核内调试器,即xmon,但仅适用于powerpc体系结构(实际上,当时是ppc)。

    它不是源代码级调试器,并且几乎完全没有文档记录,但仍然如此。

    http://lxr.linux.no/linux-old+v2.2.12/arch/ppc/xmon/xmon.c#L119


    在Ubuntu 16.10主机上测试了QEMU + GDB分步过程

    为了快速从头开始,我在以下位置制作了一个最小的全自动QEMU + Buildroot示例:https://github.com/cirosantilli/linux-kernel-module-cheat下面介绍了主要步骤。

    首先获取根文件系统rootfs.cpio.gz。如果您需要一个,请考虑:

    • 最小的init唯一可执行映像:https://unix.stackexchange.com/questions/122717/custom-linux-distro-that-runs-just-one-program-nothing-else/238579#238579
    • 一个Busybox交互式系统:https://unix.stackexchange.com/questions/2692/what-is-the-smallest-possible-linux-implementation/203902#203902

    然后在Linux内核上:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    git checkout v4.9
    make mrproper
    make x86_64_defconfig
    cat <<EOF >.config-fragment
    CONFIG_DEBUG_INFO=y
    CONFIG_DEBUG_KERNEL=y
    CONFIG_GDB_SCRIPTS=y
    EOF
    ./scripts/kconfig/merge_config.sh .config .config-fragment
    make -j"$(nproc)"
    qemu-system-x86_64 -kernel arch/x86/boot/bzImage \
                       -initrd rootfs.cpio.gz -S -s

    在另一个终端上,假设您要从start_kernel开始调试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    gdb \
        -ex"add-auto-load-safe-path $(pwd)" \
        -ex"file vmlinux" \
        -ex 'set arch i386:x86-64:intel' \
        -ex 'target remote localhost:1234' \
        -ex 'break start_kernel' \
        -ex 'continue' \
        -ex 'disconnect' \
        -ex 'set arch i386:x86-64' \
        -ex 'target remote localhost:1234'

    我们完成了!!

    有关内核模块,请参见:如何使用QEMU调试Linux内核模块?

    对于Ubuntu 14.04,GDB 7.7.1,需要hbreakbreak软件断点将被忽略。 16.10中不再是这种情况。另请参阅:https://bugs.launchpad.net/ubuntu/+source/qemu-kvm/+bug/901944

    混乱的disconnect及其后将要解决该错误:

    1
    Remote 'g' packet reply is too long: 000000000000000017d11000008ef4810120008000000000fdfb8b07000000000d352828000000004040010000000000903fe081ffffffff883fe081ffffffff00000000000e0000ffffffffffe0ffffffffffff07ffffffffffffffff9fffff17d11000008ef4810000000000800000fffffffff8ffffffffff0000ffffffff2ddbf481ffffffff4600000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f0300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000801f0000

    相关主题:

    • https://sourceware.org/bugzilla/show_bug.cgi?id=13984可能是GDB错误
    • 远程g数据包回复太长
    • http://wiki.osdev.org/QEMU_and_GDB_in_long_mode osdev.org像往常一样是解决这些问题的绝佳来源
    • https://lists.nongnu.org/archive/html/qemu-discuss/2014-10/msg00069.html

    也可以看看:

    • https://github.com/torvalds/linux/blob/v4.9/Documentation/dev-tools/gdb-kernel-debugging.rst官方Linux内核"文档"
    • 如何使用GDB和QEMU调试Linux内核?

    已知限制:

    • Linux内核不支持-O0(甚至没有补丁也不会编译):如何对Linux内核进行优化,并使用-O0进行编译?
    • 即使在max-completions修复之后,GDB 7.11也会在某些类型的制表符完成上消耗您的内存:大型二进制文件的制表符完成中断可能是该补丁中未涉及的某些特殊情况。因此,ulimit -Sv 500000是调试之前的明智之举。当我为sys_execvefilename自变量完成file选项卡时特别炸了,如下所示:https://stackoverflow.com/a/42290593/895245

    作为经常编写内核代码的人,我不得不说我从未使用过kgdb,而很少使用kprobes等。

    仍然通常是引发某些战略性printks的最佳方法。在较新的内核中,trace_printk是实现此目标而不散布dmesg的好方法。


    用户模式Linux(UML)

    https://zh.wikipedia.org/wiki/用户模式_Linux

    另一种虚拟化方法是允许逐步调试内核代码的另一种方法。

    UML非常巧妙:它像x86一样被实现为ARCH,但是它不是使用低级指令,而是通过用户级系统调用来实现ARCH函数。

    结果是您可以在Linux主机上将Linux内核代码作为用户级进程运行!

    首先进行rootfs的运行,如下所示:https://unix.stackexchange.com/questions/73203/how-to-create-rootfs-for-user-mode-linux-on-fedora-18/372207#372207

    um defconfig默认情况下设置CONFIG_DEBUG_INFO=y(是的,这是开发工作),所以我们很好。

    在来宾上:

    1
    2
    i=0
    while true; do echo $i; i=$(($i+1)); done

    在另一个外壳中的主机上:

    1
    2
    ps aux | grep ./linux
    gdb -pid"$pid"

    在GDB中:

    1
    2
    3
    break sys_write
    continue
    continue

    现在,您正在控制来自GDB的计数,并且可以按预期看到源。

    优点:

    • 完全包含在Linux内核主线树中
    • 比QEMU的整个系统仿真更轻便

    缺点:

    • 非常具有侵入性,因为它改变了内核本身的编译方式。

      但是ARCH规范之外的更高级别的API应该保持不变。

    • 可以说不是很活跃:用户模式linux(UML)项目是否已停止?

    另请参阅:https://unix.stackexchange.com/questions/127829/why-would-someone-want-to-run-usermode-linux-uml


    你们错了,kgdb对于最新的内核仍然运行良好,您需要注意分割映像的内核配置,随机优化。

    串行端口上的kgdb没用,因为今天没有计算机在主板串行端口上支持DB9,USB串行端口不支持轮询模式。

    新游戏是kgdboe,以下是日志跟踪:

    以下是主机,vmlinux来自目标计算机

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    root@Thinkpad-T510:~/KGDBOE# gdb vmlinux
    Reading symbols from vmlinux...done.
    (gdb) target remote udp:192.168.1.22:31337
    1077    kernel/debug/debug_core.c: No such file or directory.
    (gdb) l oom_kill_process
    828 mm/oom_kill.c: No such file or directory.
    (gdb) l oom_kill_process
    828 in mm/oom_kill.c
    (gdb) break oom_kill_process
    Breakpoint 1 at 0xffffffff8119e0c0: file mm/oom_kill.c, line 833.
    (gdb) c
    Continuing.
    [New Thread 1779]
    [New Thread 1782]
    [New Thread 1777]
    [New Thread 1778]
    [New Thread 1780]
    [New Thread 1781]
    [Switching to Thread 1779]

    Thread 388 hit Breakpoint 1, oom_kill_process (oc=0xffffc90000d93ce8, message=0xffffffff82098fbc"Out of memory")
    at mm/oom_kill.c:833
    833 in mm/oom_kill.c
    (gdb) s
    834 in mm/oom_kill.c
    (gdb)

    在对等目标计算机上,以下是如何使其崩溃并被主机捕获的方法

    1
    2
    #swapoff -a
    #stress -m 4 --vm-bytes=500m

    kgdb和gdb对于调试内核几乎是无用的,因为代码是如此优化,它与原始源没有关系,并且许多变量都已优化。这将导致步进,因此无法单步执行源代码,无法检查变量,因此几乎是无用的。

    实际上,这比没用还糟,它实际上为您提供了虚假的信息,因此您将代码与实际的运行代码分开查看。

    不,您不能关闭内核中的优化,它不会编译。

    我不得不说,来自Windows内核环境,由于存在要维护的垃圾代码,缺少像样的调试器的困扰。