调试它 我们已经在调试会话中,所以让我们调试我们的程序。

    在load命令之后,我们的程序在其入口点停止。这由GDB输出的“起始地址0x8000XXX”部分指示。入口点是处理器/ CPU首先执行的程序的一部分。

    我提供给你的初学者项目有一些在函数之前运行的额外代码main。这时,我们对“pre-main”部分不感兴趣,所以让我们直接跳到main函数的开头。我们将使用断点来做到这一点:

    (gdb) break main Breakpoint 1 at 0x800018c: file src/05-led-roulette/src/main.rs, line 10.

    (gdb) continue Continuing. Note: automatically using hardware breakpoints for read-only addresses.

    Breakpoint 1, main () at src/05-led-roulette/src/main.rs:10 10 let x = 42; 断点可用于停止程序的正常流程。该continue命令将让程序自由运行,直到到达断点。在这种情况下,直到它到达main 函数,因为那里有一个断点。

    请注意,GDB输出显示“Breakpoint 1”。请记住,我们的处理器只能使用其中的六个断点,因此最好注意这些消息。

    为了获得更好的调试体验,我们将使用GDB的文本用户界面(TUI)。要进入该模式,请在GDB shell上输入以下命令:

    (gdb) layout src 注意道歉Windows用户。GNU ARM嵌入式工具链附带的GDB不支持此TUI模式:-(。

    GDB会话

    您可以随时使用以下命令退出TUI模式:

    (gdb) tui disable 好。我们现在处于开始阶段main。我们可以使用该step命令按语句推进程序语句。所以让我们两次使用它来达到_y = x声明。一旦你键入step ,只需按Enter键再次运行它。

    (gdb) step 14 _y = x; 如果您没有使用TUI模式,stepGDB将在每次调用时打印当前语句及其行号。

    我们现在正在“发表” _y = x声明; 该声明尚未执行。这意味着x 初始化但_y不是。让我们使用以下print命令检查这些堆栈/局部变量:

    (gdb) print x $1 = 42

    (gdb) print &x $2 = (i32 *) 0x10001ff4

    (gdb) print _y $3 = -536810104

    (gdb) print &_y $4 = (i32 *) 0x10001ff0 正如预期的那样,x包含了价值42。_y但是,包含值-536810104(?)。因为 _y尚未初始化,它包含一些垃圾值。

    该命令print &x打印变量的地址x。这里有趣的是GDB输出显示了引用的类型:i32*,一个指向i32值的指针。另一个有趣的事情是地址x和_y彼此非常接近:它们的地址只是 4相隔字节。

    您可以使用以下info locals命令,而不是逐个打印局部变量:

    (gdb) info locals x = 42 _y = -536810104 好。另一方面step,我们将在loop {}声明之上:

    (gdb) step 17 loop {} 而_y现在应该初始化。

    (gdb) print _y $5 = 42 如果我们step在loop {}语句之上再次使用,我们就会陷入困境,因为程序永远不会传递该语句。相反,我们将使用layout asm 命令切换到反汇编视图,并使用一次前进一条指令stepi。

    注意如果你step错误地使用了命令并且GDB卡住了,你可以通过点击来解锁Ctrl+C。

    (gdb) layout asm GDB会话

    如果您没有使用TUI模式,则可以使用该disassemble /m命令反汇编您当前所在行的程序。

    (gdb) disassemble /m Dump of assembler code for function main: 7 #[entry] 0x08000188 <+0>: sub sp, #8 0x0800018a <+2>: movs r0, #42 ; 0x2a

    8 fn main() -> ! { 9 let _y; 10 let x = 42; 0x0800018c <+4>: str r0, [sp, #4]

    11 _y = x; 0x0800018e <+6>: ldr r0, [sp, #4] 0x08000190 <+8>: str r0, [sp, #0]

    12 13 // infinite loop; just so we don’t leave this stack frame 14 loop {} => 0x08000192 <+10>: b.n 0x8000194 0x08000194 <+12>: b.n 0x8000194

    End of assembler dump. 看=>左侧的胖箭头?它显示了处理器接下来要执行的指令。

    如果不在每个stepi命令的TUI模式内,GDB将打印语句,行号 和处理器接下来将执行的指令的地址。

    (gdb) stepi 0x08000194 14 loop {}

    (gdb) stepi 0x08000194 14 loop {} 在我们转向更有趣的事情之前的最后一招。在GDB中输入以下命令:

    (gdb) monitor reset halt Unable to match requested speed 1000 kHz, using 950 kHz Unable to match requested speed 1000 kHz, using 950 kHz adapter speed: 950 kHz target halted due to debug-request, current mode: Thread xPSR: 0x01000000 pc: 0x08000196 msp: 0x10002000

    (gdb) continue Continuing.

    Breakpoint 1, main () at src/05-led-roulette/src/main.rs:10 10 let x = 42; 我们现在回到了开始main!

    monitor reset halt将重置微控制器并在程序入口点停止。以下continue命令将让程序自由运行,直到它到达main 具有断点的函数。

    当你错误地跳过了你有兴趣检查的程序的一部分时,这个组合很方便。您可以轻松地将程序状态回滚到最开始状态。

    细则:此reset命令不清除或触摸RAM。该内存将保留其上一次运行的值。这应该不是问题,除非您的程序行为取决于未初始化变量的值,但这是未定义行为(UB)的定义。

    我们完成了这个调试会话。您可以使用该quit命令结束它。

    (gdb) quit A debugging session is active.

    1. Inferior 1 [Remote target] will be detached.

    Quit anyway? (y or n) y Detaching from program: $PWD/target/thumbv7em-none-eabihf/debug/led-roulette, Remote target Ending remote debugging. 注意如果您不喜欢默认的GDB CLI,请查看gdb-dashboard。它使用Python将默认的GDB CLI转换为显示寄存器,源视图,汇编视图和其他内容的仪表板。

    不要关闭OpenOCD!我们稍后会一次又一次地使用它。最好让它继续运行。

    下一步是什么?我承诺的高级API。