1 2# 如何使用GDB调试内核 3 4## 前言 5  GDB是一个功能强大的开源调试工具,能够帮助您更好的诊断和修复程序中的错误。 6  它提供了一套丰富的功能,使您能够检查程序的执行状态、跟踪代码的执行流程、查看和修改变量的值、分析内存状态等。它可以与编译器配合使用,以便您在调试过程中访问程序的调试信息。 7 8  此教程将告诉您如何在DragonOS中使用`rust-gdb`来调试内核,包括如何开始调试以及相应的调试命令。 9 10:::{note} 11如果您已经熟悉了`rust-gdb`的各种命令,那您只需要阅读此教程的第一部分即可。 12::: 13 14--- 15## 1.从何开始 16 17### 1.1 准备工作 18 19  在您开始调试内核之前,需要在/Kernel/Cargo.toml中开启调试模式,将Cargo.toml中的`debug = false`更改为`debug = true`。 20 21```shell 22debug = false 23``` 24  **更改为** 25```shell 26debug = true 27``` 28 29### 1.2 运行DragonOS 30 31  准备工作完成后,您就可以编译、运行DragonOS来开展后续的调试工作了。 32  在DragonOS根目录中开启终端,使用`make run`即可开始编译运行DragonOS,如需更多编译命令方面的帮助,详见 33> [构建DragonOS](https://docs.dragonos.org/zh_CN/latest/introduction/build_system.html)。 34 35### 1.3 运行GDB 36  当DragonOS开始运行后,您就可以启动GDB开始调试了。 37 38  **您只需要开启一个新的终端,运行`make gdb`即可运行GDB调试器。** 39 40```shell 41❯ make gdb 42rust-gdb -n -x tools/.gdbinit 43GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1 44Copyright (C) 2022 Free Software Foundation, Inc. 45License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> 46This is free software: you are free to change and redistribute it. 47There is NO WARRANTY, to the extent permitted by law. 48Type "show copying" and "show warranty" for details. 49This GDB was configured as "x86_64-linux-gnu". 50Type "show configuration" for configuration details. 51For bug reporting instructions, please see: 52<https://www.gnu.org/software/gdb/bugs/>. 53Find the GDB manual and other documentation resources online at: 54 <http://www.gnu.org/software/gdb/documentation/>. 55 56--Type <RET> for more, q to quit, c to continue without paging-- 57``` 58 59:::{note} 60若出现以上信息,输入c再回车即可。 61::: 62 63--- 64 65## 2.调试 66 67### 2.1 开始 68 69  当以上步骤完成后,就已经可以开始调试了。 70 71```shell 72For help, type "help". 73Type "apropos word" to search for commands related to "word". 74warning: No executable has been specified and target does not support 75determining executable automatically. Try using the "file" command. 760xffff8000001f8f63 in ?? () 77(gdb) 78``` 79 80:::{note} 81GDB输出的信息中`0xffff8000001f8f63 in ?? ()`表明DragonOS还在引导加载的过程中。 82::: 83 84  **输入`continue`或者`c`,程序将继续执行。** 85 86```shell 87For help, type "help". 88Type "apropos word" to search for commands related to "word". 89warning: No executable has been specified and target does not support 90determining executable automatically. Try using the "file" command. 910xffff8000001f8f63 in ?? () 92(gdb) continue 93Continuing. 94``` 95 96  在DragonOS运行时,您可以随时按下`Ctrl+C`来发送中断信息。来查看内核当前状态。 97 98```shell 99(gdb) continue 100Continuing. 101^C 102Thread 1 received signal SIGINT, Interrupt. 1030xffff800000140c21 in io_in8 (port=113) at common/glib.h:136 104136 __asm__ __volatile__("inb %%dx, %0 \n\t" 105(gdb) 106``` 107 108### 2.2 设置断点和监视点 109 110  设置断点和监视点是程序调试中最基础的一步。 111 112- **设置断点** 113 114  您可以使用`break`或者`b`命令来设置断点。 115 116  关于`break`或者`b`命令的使用: 117 118```shell 119b <line_number> #在当前活动源文件的相应行号打断点 120 121b <file>:<line_number> #在对应文件的相应行号打断点 122 123b <function_name> #为一个命名函数打断点 124``` 125 126- **设置监视点** 127 128  您可以使用`watch`命令来设置监视点 129 130```shell 131watch <variable> # 设置对特定变量的监视点,将在特定变量发生变化的时候触发断点 132 133watch <expression> # 设置对特定表达式的监视点,比如watch *(int*)0x12345678会在内存地址0x12345678处 134 # 的整数值发生更改时触发断点。 135``` 136 137- **管理断点与监视点** 138 139  当我们打上断点之后,我们该如何查看我们所有的断点信息呢? 140 141  您可以通过`info b`,`info break`或者`info breakpoints`来查看所有的断点信息: 142 143```shell 144(gdb) b 309 145Breakpoint 12 at 0xffff8000001f8f16: file /home/heyicong/.cargo/registry/src/mirrors.tuna.tsinghua.edu.cn-df7c3c540f42cdbd/thingbuf-0.1.4/src/lib.rs, line 315. 146(gdb) watch slots 147Watchpoint 13: slots 148(gdb) info b 149Num Type Disp Enb Address What 15012 breakpoint keep y 0xffff8000001f8f16 in thingbuf::Core::pop_ref<u8> 151 at /home/heyicong/.cargo/registry/src/mirrors.tuna.tsinghua.edu.cn-df7c3c540f42cdbd/thingbuf-0.1.4/src/lib.rs:315 15213 watchpoint keep y slots 153(gdb) 154``` 155 156  以上信息中,编号为12的断点即是我们在活动源文件309行打的断点,若其`Address`为`<MULTIPLE>`,则表示在多个地址上存在相同的断点位置。这在循环中是非常常见的情况。编号为13的便是我们对`slots`变量设置的监视点。 157 158  我们可以通过以下命令对断点或者监视点进行操作: 159 160```shell 161delete <breakpoint#> # 或 d <breakpoint#> 删除对应编号的断点,在您不再需要使用这个断点的时候可以通过此命令删除断点 162delete <watchpoint#> # 或 d <watchpoint##> 删除对应编号的监视点,在您不再需要使用这个监视点的时候可以通过此命令删除监视点 163 164disable <breakpoint#> # 禁用对应编号的断点,这适合于您只是暂时不需要使用这个断点时使用,当您禁用一个断点,下 165 # 次程序运行到该断点处将不会停下来 166disable <watchpoint#> # 禁用对应编号的监视点,这适合于您只是暂时不需要使用这个监视点时使用 167 168enable <breakpoint#> # 启用对应编号的断点 169enable <watchpoint#> # 启用对应编号的监视点 170 171#clear命令 172clear # 清除当前活动源文件的断点以及监视点 173clear <point_number> # 清除对应编号的所有断点或监视点,这与delete行为是一致的 174clear <file> # 清除指定文件的所有断点与监视点 175``` 176 177## 2.3 变量和内存查看 178 179- **print 和 display** 180 181  您可以通过`print`或者`p`来打印变量值。 182 183  `print`命令用于打印变量或表达式的值。它允许您在调试过程中查看程序中的数据。 184 185```shell 186print <variable> # 打印对应变量名的值,例如:print my_variable 或者 p my_variable 187 188print <expression> # 打印合法表达式的值,例如:print a+b 或者 p a+b 189 190# 示例输出 191(gdb) print order 192$3 = core::sync::atomic::Ordering::SeqCst 193``` 194 195```{note} 196如果您不仅想打印值,还想显示更多详细信息(例如类型信息),可以使用ptype命令。 197``` 198 199  您可以使用`display`命令来持续追踪变量或者表达式,`display`命令用于设置需要持续跟踪并在每次程序停止时显示的表达式。它类似于print命令,但与print不同的是,display命令在每次程序停止时自动打印指定表达式的值,而无需手动输入命令。 200 201```shell 202display <variable> # 打印对应变量名的值,例如:display my_variable 203 204display <expression> # 打印合法表达式的值,例如:display a+b 205 206# 示例输出 207(gdb) display order 2081: order = core::sync::atomic::Ordering::SeqCst #其中1表示display编号, 209 #您可以通过info display命令来查看所有display编号 210``` 211 212```{note} 213一旦您设置了display命令,每当程序停止(例如,在断点处停止)时,GDB将自动打印指定表达式的值。 214 215display命令非常有用,因为它允许您在调试过程中持续监视表达式的值,而无需每次都手动输入print命令。它特别适用于那些您希望持续跟踪的变量或表达式。 216``` 217 218  **要取消已设置的display命令并停止自动显示表达式的值,可以使用undisplay命令:** 219 220```shell 221undisplay <display编号> # 如果不指定<display编号>,则将取消所有已设置的display命令, 222 # 您可以通过info display命令来查看所有display编号 223``` 224 225```{note} 226请注意,print和display命令只会在程序暂停执行时评估变量或表达式的值。如果程序正在运行,您需要通过设置断点或使用其他调试命令来暂停程序,然后才能使用print命令查看数据的值,display命令设置的值将会在程序暂停时自动输出。 227``` 228 229- **输出格式** 230 231  您可以设置输出格式来获取更多您需要的信息,例如:`print /a var` 232> 参考至[GDB Cheat Sheet](https://darkdust.net/files/GDB%20Cheat%20Sheet.pdf) 233 234```shell 235Format 236a Pointer. 237c Read as integer, print as character. 238d Integer, signed decimal. 239f Floating point number. 240o Integer, print as octal. 241s Try to treat as C string. 242t Integer, print as binary (t = „two“). 243u Integer, unsigned decimal. 244x Integer, print as hexadecimal. 245``` 246 247### 2.4 查看调用堆栈 248 249- **查看调用栈** 250 251  当程序在断点处暂停时,应该怎样追踪程序行为呢? 252 253  您可以通过`backtarce`命令来查看调用栈。`backtrace`命令用于打印当前调用栈的回溯信息。它显示了程序在执行过程中所有活动的函数调用链,包括每个函数的名称、参数和源文件中的行号。 254 255```shell 256# 示例输出 257(gdb) backtrace 258#0 function1 (arg1=10, arg2=20) at file1.c:15 259#1 function2 () at file2.c:25 260#2 xx () at xx.c:8 261``` 262 263  每一行回溯信息都以#<frame_number>开头,指示帧的编号。然后是函数名和参数列表,最后是源文件名和行号。 264通过查看回溯信息,您可以了解程序在哪些函数中执行,以及每个函数在调用栈中的位置。这对于调试程序和定位问题非常有用。 265 266- **切换堆栈** 267 268  您可以通过`frame`或者`f`命令来切换对应的栈帧获取更多信息以及操作。 269 270```shell 271frame <frame_number> 272f <frame_number> 273``` 274 275  除了简单地执行backtrace命令,还可以使用一些选项来自定义回溯信息的输出。例如: 276```shell 277backtrace full #显示完整的符号信息,包括函数参数和局部变量。 278backtrace <frame_count> #限制回溯信息的帧数,只显示指定数量的帧。 279backtrace <frame_start>-<frame_end> #指定要显示的帧范围。 280backtrace thread <thread_id> #显示指定线程的回溯信息。 281``` 282 283### 2.5 多核心 284 285  在调试内核时,您可能需要查看各个核心的运行状态。 286 287  您可以通过`info threads`命令来查看各个核心的运行状态 288 289```shell 290(gdb) info threads 291 Id Target Id Frame 292 1 Thread 1.1 (CPU#0 [halted ]) 0xffff800000140a3e in Start_Kernel () at main.c:227 293* 2 Thread 1.2 (CPU#1 [running]) thingbuf::Core::pop_ref<u8> () 294 at /home/heyicong/.cargo/registry/src/mirrors.tuna.tsinghua.edu.cn-df7c3c540f42cdbd/thingbuf-0.1.4/src/lib.rs:315 295(gdb) 296``` 297 298  您可以使用`thread <thread_id>`命令切换到指定的核心上下文,以便查看和调试特定核心的状态。例如: 299 300```shell 301(gdb) thread 1 302[Switching to thread 1 (Thread 1.1)] 303#0 0xffff800000140a3e in Start_Kernel () at main.c:227 304227 hlt(); 305``` 306 307### 2.6 更多 308 309  接下来,我将为您介绍更多您可能在调试中能够使用的命令: 310 311```shell 312step #或者s,逐行执行程序,并进入到函数调用中。可以在step命令后加执行次数,例:step 3 表示要连续执行3个步骤 313step <function> #进入指定的函数,并停止在函数内的第一行。 314 315next #或者n,逐行执行程序,但跳过函数调用,直接执行函数调用后的下一行代码。 316 #它允许你在不进入函数内部的情况下执行代码,从而快速跳过函数调用的细节。 317 #同样,next也可以在命令后加执行次数 318 319finish #用于从当前函数中一直执行到函数返回为止,并停在调用该函数的地方。 320 #它允许你快速执行完当前函数的剩余部分,并返回到调用函数的上下文中。 321 322continue #用于继续程序的执行,直到遇到下一个断点或 323 #程序正常结束或者程序暂停。 324 325quit #退出调试 326 327list #或者l,显示当前活动源文件源代码的片段,以及当前执行的位置。 328list <filename>:<function> #显示<filename>文件里面的<funtion>函数的源代码片段 329list <filename>:<line_number> #显示<filename>文件里面的<line_number>附近的源代码片段 330list <first>,<last> #显示当前活动源文件的<first>至<last>之间的源代码片段 331set listsize <count> #设置list命令显示的源代码行数。默认情况下,list命令显示当前行和其周围的几行代码。 332 333info args #显示当前函数的参数及其值 334info breakpoints #显示断点以及监视点信息 335info display #显示当前设置的display列表 336info locals #显示当前函数/栈帧中的局部变量及其值 337info sharedlibrary #显示当前已加载的共享库(shared library)信息 338info signals #显示当前程序所支持的信号信息。它可以列出程序可以接收和处理的不同信号的列表。 339info threads #显示各个核心/线程信息,它可以列出当前正在运行的核心/线程以及它们的状态。 340 341show directories #显示当前源代码文件的搜索路径列表。这些搜索路径决定了GDB在查找源代码文件时的搜索范围。 342show listsize #显示打印源代码时的上下文行数。它确定了在使用list命令(或其简写形式l)时显示的源代码行数。 343 344whatis variable_name #查看给定变量或表达式的类型信息。它可以帮助你了解变量的数据类型。 345ptype #显示给定类型或变量的详细类型信息。它可以帮助你了解类型的结构和成员。 346 #相较于whatis命令,ptype命令更加详细。 347 348set var <variable_name>=<value> #设置变量值 349 350return <expression> #强制使当前函数返回设定值 351``` 352 353--- 354 355## 最后 356 357  现在,您已经可以使用rust-gdb来调试DragonOS内核代码了。 358 359> 您可以参阅GDB命令文档来获取更多帮助:[GDB Cheat Sheet](https://darkdust.net/files/GDB%20Cheat%20Sheet.pdf)